【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以内的仿真,提速效果并不明显。鉴于复杂度并不高,无论如何大家都可以试一下,学点新知识嘛。 😀

目录
相关文章
|
15天前
|
Kubernetes 测试技术 Perl
混沌测试平台 Chaos Mesh
混沌测试平台 Chaos Mesh
34 1
|
6天前
|
运维 Kubernetes 监控
|
13天前
|
敏捷开发 测试技术 持续交付
软件测试中的探索性测试方法及其重要性
【8月更文挑战第6天】在软件开发周期中,测试环节是不可或缺的。它确保了产品的质量、性能与可靠性。然而,传统的测试方法往往侧重于预设的测试用例和场景,可能忽略了用户实际使用过程中的非预期行为。探索性测试(ET)应运而生,它强调测试人员的自主性和创造性,通过不断学习和适应来发现软件的潜在缺陷。本文将探讨探索性测试的概念、实施策略及其在现代软件开发中的重要性。
|
12天前
|
SQL 安全 测试技术
[go 面试] 接口测试的方法与技巧
[go 面试] 接口测试的方法与技巧
|
14天前
|
测试技术 持续交付
探索式测试:一种灵活的软件质量保证方法
在软件测试领域,探索式测试是一种与传统脚本测试截然不同的方法。它强调测试人员的自主性与创造性,允许他们在执行测试时即时设计和执行测试用例。这种方法的灵活性和适应性使其成为发现复杂软件系统中难以预测的错误的有效手段。本文将深入探讨探索式测试的核心概念、实施策略以及它在现代软件开发生命周期中的价值。
30 4
|
14天前
|
敏捷开发 测试技术
探索式测试:一种高效灵活的质量保证方法
在软件生命周期中,确保产品质量是至关重要的一环。传统的测试方法往往依赖于预设的测试用例,而忽视了测试过程中的灵活性和创造性。本文将介绍一种与传统测试截然不同的方法—探索式测试,它强调个人技能与经验的应用,鼓励测试人员发挥主观能动性,通过不断探索来发现更多潜在的软件缺陷。文章将详细阐述探索式测试的核心概念、实施步骤及其在现代软件开发中的实际应用案例,旨在为读者提供一种更为高效和灵活的测试策略。
|
20天前
|
测试技术
软件测试中的探索性测试方法
在软件开发周期中,测试环节扮演着至关重要的角色。本文将深入探讨一种灵活而高效的测试方法——探索性测试。我们将通过具体实例分析其实施过程,并讨论如何通过这种方法提高软件质量和测试效率。最后,我们提出一个开放性问题,邀请读者思考探索性测试在他们工作中的实际应用和潜在价值。 【7月更文挑战第31天】
|
4天前
|
Dart API C语言
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
|
4天前
|
测试技术
单元测试问题之在单元测试中,方法的返回值或异常,如何验证
单元测试问题之在单元测试中,方法的返回值或异常,如何验证
|
4天前
|
测试技术
单元测试问题之模拟一个无返回值的方法,如何操作
单元测试问题之模拟一个无返回值的方法,如何操作