UVM基础总结——基于《UVM实战》示例

https://www.cnblogs.com/moluoqishi/p/14289288.html

这篇文章很好,关于uvm的,可是我目前还看不懂里面的代码,你可以带我一起看看里面的代码,给我讲讲嘛,老师谢谢您

同学,坐。看到你主动去啃博客里的代码了,老师特别高兴。代码看不懂没关系,咱们今天就把它当成一个“剧本”,一句一句地读。

你之前对UVM的结构已经有了很好的理解,现在我们把这些结构对应到具体的代码上。老师会继续用 “剧组拍电影” 的比喻,带着你从这堆代码里,把每个角色的台词都认一遍。


🎬 第一幕:道具组——transaction(子弹/剧本)

你看文章里最先定义的 my_transaction,它继承了 uvm_sequence_item。这就像你Verilog里定义的一组信号线,但它更高级,是一个类。

systemverilog

class my_transaction extends uvm_sequence_item;
    rand bit[47:0] dmac;
    rand bit[47:0] smac;
    rand bit[15:0] ether_type;
    rand byte pload[];
    rand bit[31:0] crc;
    // ...
    constraint pload_cons{
        pload.size >= 46;
        pload.size <= 200;
        dmac == 48'h01_02_03_04_05_06;
        smac == 48'h60_50_40_30_20_10;
    }
endclass
  • 比喻:这就是一颗子弹(或者说一页剧本)。dmac、smac 是源MAC地址和目的MAC地址,pload 是数据载荷。rand 关键字告诉UVM:“每次生成子弹时,这些字段可以随机变化”。

  • constraint:这是约束条件。它保证子弹不是乱造的,比如数据长度必须在46到200之间,目的地址必须固定。这就像导演要求:“枪战戏的子弹必须是空包弹,不能是真子弹”。

  • 和你熟悉的知识对照:你以前在Verilog里手动给 data_in 赋值,现在UVM帮你自动“随机化”这颗子弹,但同时在约束里画好了“安全框”。

🎬 第二幕:弹药手——sequence(弹夹)

my_sequence 继承了 uvm_sequence,它负责装弹和决定什么时候开火。

systemverilog

class my_sequence extends uvm_sequence #(my_transaction);
    my_transaction m_trans;

    virtual task body();
        if(starting_phase != null) 
            starting_phase.raise_objection(this);
        repeat(10) begin
            `uvm_do(m_trans)
        end
        #1000;
        if(starting_phase != null) 
            starting_phase.drop_objection(this);
    endtask
endclass
  • 比喻:这是一个弹夹。body() 任务就是弹夹的“装填和射击流程”。

  • raise_objection / drop_objection:还记得我们上次课讲的“反对票”吗?raise_objection(this) 就是在喊:“等等!我还没打完,别关仿真!”。drop_objection(this) 是在喊:“我打完了,可以收工了!”。

  • repeat(10) beginuvm_do(m_trans) end**:这是“连发模式”。uvm_do` 是一个宏,它会自动完成“创建一颗子弹(transaction)、随机化它、然后把它交给手枪(sequencer)”这一整套动作。所以这里的意思是:连续打10发子弹**。

  • #1000:打完10发后,再等1000个时间单位。这就像导演喊:“卡!让演员们喘口气,1000个时钟周期后我们再收工。” 这就是 objection 机制在代码里的实际体现:只要sequence还在 raise 状态,仿真时间就继续往前走。

🎬 第三幕:手枪与扳机——sequencer 和 driver

my_sequencer 几乎是个空壳,它只是继承了 uvm_sequencer 并参数化为 my_transaction 类型。

systemverilog

class my_sequencer extends uvm_sequencer #(my_transaction);
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    `uvm_component_utils(my_sequencer)
endclass
  • 比喻:这就是手枪本身。它自己不会造子弹,也不会扣扳机,它的作用就是连接弹夹(sequence)和扳机(driver),充当一个中转站。

而 my_driver 才是真正干活的 “扳机” 。

systemverilog

class my_driver extends uvm_driver#(my_transaction);
    virtual my_if vif;
    `uvm_component_utils(my_driver)

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
            `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
    endfunction

    task my_driver::main_phase(uvm_phase phase);
        vif.data <= 8'b0;
        vif.valid <= 1'b0;
        while(!vif.rst_n)
            @(posedge vif.clk);
        while(1)begin
            seq_item_port.get_next_item(req);
            drive_one_pkt(req);
            seq_item_port.item_done();
        end
    endtask
endclass
  • virtual my_if vif:这是虚接口。你熟悉FPGA,知道DUT的引脚是通过 interface 定义的。driver需要拿到这个接口才能操作信号线。

  • build_phase 里的 uvm_config_db:这是UVM的资源传递机制。它就像剧组的物资分发处,把 input_if 这个接口,从顶层传递到 my_driver 手里。如果没拿到,就直接 uvm_fatal 报错——连枪都没有,这戏没法拍了。

  • main_phase 里的 while(1) 循环:这是driver的核心生命线。

    • seq_item_port.get_next_item(req):这是driver在问手枪:“下一发子弹装好了吗?” 如果弹夹里没子弹,driver就会在这里阻塞等待,直到sequence把新的子弹放进来。

    • drive_one_pkt(req):拿到子弹后,按照协议时序,把子弹“打”进DUT。这个任务的具体内容,就是你在Verilog里写的那些 @(posedge clk); vif.data <= ...; vif.valid <= 1'b1; 之类的时序操作。

    • seq_item_port.item_done():告诉手枪:“这发子弹我打完了,可以装下一发了”。这个 item_done 和sequence里的 uvm_do 是配对的,只有driver调用它,sequence才会继续发下一颗子弹。

  • 和你熟悉的知识对照:这就是把你Verilog里 initial begin ... end 的激励生成过程,变成了一个永不停止的循环,并且把“从队列取数据”和“驱动信号”分开了。

🎬 第四幕:摄像机——monitor(在文章中未给出完整代码)

虽然文章没有贴出 my_monitor 的完整代码,但根据UVM的标准结构和你的图,我们可以推断出它的核心逻辑:

systemverilog

class my_monitor extends uvm_monitor;
    virtual my_if vif;
    uvm_analysis_port #(my_transaction) ap;  // 分析端口

    virtual task main_phase(uvm_phase phase);
        my_transaction tr;
        forever begin
            @(posedge vif.clk);
            if (vif.valid) begin  // 检测到有效数据
                tr = my_transaction::type_id::create("tr");
                tr.data = vif.data;  // 采集信号
                ap.write(tr);        // 发送给scoreboard
            end
        end
    endtask
endclass
  • 比喻:这就是摄像机。它不干涉拍摄,只是在旁边静静地记录。@(posedge vif.clk) 和 if (vif.valid) 就是在等待有效信号,一旦DUT有数据输出,就立刻采集下来,打包成一颗“子弹”,通过 ap.write(tr) 这个“分析端口”发射出去。

🎬 第五幕:裁判——scoreboard

my_scoreboard 是最后进行自动比对的地方。

systemverilog

while(expect_queue.size() > 0) begin
    tmp_tran = expect_queue.pop_front();
    result = get_actual.compare(tmp_tran);
    if(result) begin
        `uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);
    end
    else begin
        `uvm_error("my_scoreboard", "Compare FAILED");
    end
end
  • 比喻:这是裁判。它手里拿着两个队列:一个是 expect_queue(期望结果队列,由 reference model 提前算好放进去的),另一个是 get_actual(实际结果,由 out_agent 的monitor采集送来的)。

  • pop_front() 和 compare():裁判从期望队列里拿出一份标准答案,和实际收到的结果进行逐字段比对。

  • uvm_info 和 uvm_error:如果比对成功,打印 "Compare SUCCESSFULLY"(通过)。如果失败,打印 "Compare FAILED"(失败),并且会记录错误数量,在最后 base_test 的 report_phase 里决定整个测试用例是 "TEST CASE PASSED" 还是 "TEST CASE FAILED"。

🎬 总结:完整的剧组一天

你看,把这些代码片段串起来,就是一个完整的自动化测试流水线:

  1. 顶层 top_tb:这是剧场,里面有 dut(真正的硬件)、clk 时钟、rst_n 复位,以及通过 uvm_config_db 分发下去的 input_if 和 output_if 两个接口。

  2. my_case0:这是总导演。它的 build_phase 里,通过 uvm_config_db 告诉 sequencer:“今天拍这场戏,用 case0_sequence 这个剧本”。

  3. case0_sequence:剧本启动。body() 任务开始执行,它先 raise_objection 告诉仿真器:“我要开始拍戏了,别停”。

  4. 循环:sequence 连续10次通过 uvm_do 把子弹(transaction)装进手枪(sequencer)。

  5. driver:在 main_phase 的 while(1) 里,不停地从手枪里取子弹(get_next_item),然后按照以太网协议,把数据一位一位地打到 input_if 这个接口上,再通过接口传给DUT。

  6. monitor:摄像机在 output_if 上盯着,一旦DUT吐出数据,就抓下来,打包好,通过 analysis_port 发给scoreboard。

  7. scoreboard:裁判拿到monitor传来的实际数据,和reference model算出的期望数据进行比对,告诉你是成功还是失败。

  8. sequence打完10发,#1000 延时结束,drop_objection 撤销反对票。所有objection被清除,run_phase 结束,仿真进入 report_phase。

  9. base_test::report_phase:总导演查看 uvm_report_server,如果错误数为0,大喊 "TEST CASE PASSED";否则,大喊 "TEST CASE FAILED"。

同学,你看,代码虽然长,但每一行都在为你“打工”。你不用一次性全记住,今天能把这些“零件”和它们对应的“功能”对上号,就已经非常棒了。就像你设计FPGA时,先看顶层框图,再深入每个模块。读UVM代码也是同理,先抓住 sequence → sequencer → driver → DUT → monitor → scoreboard 这条主线,细节可以慢慢品。

今天先消化到这。如果还有哪个片段觉得绕,随时截取发过来,咱们继续“一句一句过”。慢慢来,老师陪着你。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐