【DVCon-US-2020】基于多线程UVM测试平台的仿真加速方法

简介: 【DVCon-US-2020】基于多线程UVM测试平台的仿真加速方法

论文概述


 本文题目 Multithreading a UVM Testbench for Faster Simulation,作者是 Intel 的加拿大验证工程师。


 本文提出了一种C/C++ model和simulation并行的方法,来提升 UVM 测试平台的仿真速度。




研究目的


 为了验证仿真结果的正确性,simulation 中常用UVM来创建predictor和scoreboard组件。predictor可以用SV直接写,但对于偏算法的、较为复杂的DUT,尤其是Vender提供的IP,常采用DPI调用C/C++ model等软件模型作为参考模型(如图1)。


 正常情况下,SV DPI调用C/C++ model时,simulation会停在原地等待C model执行完毕,从而影响simulation效率。即便目前先进的simulator支持多线程的DUT仿真,但对于整个UVM测试平台而言,CPU内仍然时按照单线程运行的。本文目的便是为了提升这种DPI 调用软件模型的simulation效率,为了突破单线程桎梏,来进一步提升 UVM 测试平台的仿真速度。


a5111908e17a42ecbc70794d9b298775.png

图1 UVM testbench using a C++ Predictor Model




新方法


方法提出


本文提出一种方法(图2),开辟一个甚至多个新线程单独跑C/C++ model(其他任何软件模型),与原有的UVM TB并行,能够缩短仿真耗时。运行中的simulation和C/C++ model采用进程间通信机制来交换数据。


 为了对多进程启动顺序、数据交换等进行管理,本文构建了一个线程池 管理器(thread pool manager)。线程池是一个可配置线程数目的软件对象,其中的每个线程都可以单独执行所分配的任务(Job)。Job通过输入队列丢到线程池,由线程池中的空闲线程执行该Job。执行完毕后线程池采用异步方式返回Job执行结果。


641cd6b6b9b3471dacd746b953acf9e1.png


图2 Modified UVM testbench with an asynchronous predictor model




方法实现


线程池的初始化及访问


通过单独开辟新线程的方式来初始化及访问thread_pool class。这种设计隐藏了初始化行为及全局状态,为人所诟病。但该方法使得thread pool class能够响应多个不同组件的调度请求,多个传输单元的code只做微调即可轻松获取 thread pool instance,总体而言是利大于弊的。C++惰性计算的特性也保证了thread pool object只在初次执行thread_pool::get_instance进行调用的时候进行一次初始化。


 线程池类的实现需要 :


  1. 一个私有默认构建函数(private default constructor)


  1. get_instance 函数,以返回thread_pool静态实例的引用
class thread_pool {
public:
    static thread_pool& get_instance()
    {
        static thread_pool inst; //Class will be initialized a single time
        return inst;
    }
private:
    thread_pool() = default; //Prevent creation of other instances
}



线程调度


 线程池初始化时即生成了制定数量的线程 std::thread,初始化完毕后进入idle状态。线程由std::condition_variable唤醒,从输入任务队列中pop出job然后执行job,Input Job Queue的保证了线程的执行顺序。若线程池所有线程处于忙碌状态,则暂时停止从任务队列中pop job,待有job执行完毕、线程ilde之后再行pop。


多个job虽然是按照既定顺序开始执行的,但不同job的执行时间不同,并不能保证按顺序完成job。对于scoreboard等对job反馈结果有严格顺序要求的情况,需按照顺序同步返回job执行结果。本文采用std::packaged_task返回值std::future来解决该问题。


 std::future具有按序立即返回的特性,因此可以在调用线程池时把std::future压入一个特殊队列,在scoreboard需获取新数据时检查std::future队列相关job是否完成并将其弹出,便于scoreboard区分其所需的data,也避免了早前丢出的多个任务抢同一个返回结果。




线程池集成到UVM TB


 为了实现model和UVM并排走,需要把线程池集成到原有的UVM环境中,需要对C model和UVM TB做响应调整。


C/C++的改动


 需要对原有的C/C++ 代码做点微调才能把线程池集成到UVM环境中,主要改动如下:


   添加std::future及其队列std::queue<std::future> futures


   添加predictor中调用的function predict_call()


   添加scoreboard中调用的scoreboard_call()


 示意代码如下:

//C++ Code
//Store futures in the order they are created for a particular task type
std::queue<std::future<int>> futures;
//Function that would have previously been called by the predictor
//Returns the expected result from a single integer input
int calc_result(int num)
{
    int result = 0;
    //Do complex math work or any other modelling here
    return result;
}
//New function called from the UVM Predictor
extern "C" void predict_call(const int num)
{
    thread_pool& tp = thread_pool::get_instance();
    //Queue the job on the thread_pool and store the std::future
    futures.emplace(tp.add_job(calc_result, num));
}
//New function called from the UVM Scoreboard
extern "C" int scoreboard_call()
{
    int num = futures.front().get();
    futures.pop();
    return num;
}



SV/UVM的改动


 之前的方式是在UVM中 predictor中DPI调用C/C++ model,等model执行完毕后通过 analysis fifo 把结果传递给scoreboard。为了实现C/C++ model与UVM TB的并行,UVM环境中原有model调用方式改为在predictor中调用model(丢job)然后立即退出,在scoreboard中调用model(丢job)收集job计算结果,不再通过analysis fifo收结果。


//Predictor
//DPI call to submit work to the thread pool
import "DPI-C" function void predict_call(input int num);
class my_predictor extends uvm_subscriber #(my_item);
    //Predictor code
    function void write(my_item item);
        predict_call(item.data);
    endfunction
endclass : my_predictor
//Scoreboard
//DPI call to retrieve the next predicted value
import "DPI-C" function int scoreboard_call();
class my_scoreboard #(type T = my_item) extends uvm_scoreboard;
    uvm_analysis_imp_received #(T, my_scoreboard) received_export;
    //Scoreboard code
    virtual function void write_received(T txn);
        int rx_data;
        int pred = scoreboard_call(); //Get predicted data
        rx_data = txn.data;
        if (rx_data != pred) begin
            `uvm_error("Scoreboard", "Failure")
        end
    endfunction
endclass : my_scoreboard




实验结果


  本文以不同predictor time和有无thread pool为变量做了几组对比试验,证明了本文所提出的方法的确能够加速仿真。predictor time越大,提升效果越明显。


17523377b7954846b19bb5f64759f732.png



讨论


  根据本文实验结果,该方法对于predictor time 2~20ms的仿真,提速效果为3~4x。对于1ms以内的仿真,提速效果并不明显。鉴于复杂度并不高,无论如何大家都可以试一下,学点新知识嘛。 😀

目录
相关文章
|
26天前
|
测试技术 API 项目管理
API测试方法
【10月更文挑战第18天】API测试方法
42 1
|
24天前
|
测试技术 UED
软件测试中的“灰盒”方法:一种平衡透明度与效率的策略
在软件开发的复杂世界中,确保产品质量和用户体验至关重要。本文将探讨一种被称为“灰盒测试”的方法,它结合了白盒和黑盒测试的优点,旨在提高测试效率同时保持一定程度的透明度。我们将通过具体案例分析,展示灰盒测试如何在实际工作中发挥作用,并讨论其对现代软件开发流程的影响。
|
11天前
|
监控 安全 测试技术
构建高效的精准测试平台:设计与实现指南
在软件开发过程中,精准测试是确保产品质量和性能的关键环节。一个精准的测试平台能够自动化测试流程,提高测试效率,缩短测试周期,并提供准确的测试结果。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
48 1
|
16天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
32 2
|
24天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
24天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
24天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
16 1
|
24天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
24天前
|
Java
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅。它们用于线程间通信,使线程能够协作完成任务。通过这些方法,生产者和消费者线程可以高效地管理共享资源,确保程序的有序运行。正确使用这些方法需要遵循同步规则,避免虚假唤醒等问题。示例代码展示了如何在生产者-消费者模型中使用`wait()`和`notify()`。
24 1
|
24天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1