2. 分析事务级建模对吞吐量的影响

任务目标

本案例通过构建两个最小可复现的UVM测试平台,分别采用事务级建模(uvm_sequence_item + uvm_tlm_fifo)与信号级建模(逐周期信号驱动)完成相同的数据传输任务,对比两者在仿真吞吐量上的差异。吞吐量以“每秒仿真时间可处理的事务数”为度量,通过统计仿真墙钟时间与完成的事务总数计算得出。

环境与依赖

  • 仿真器:QuestaSim / VCS / Xcelium(任一支持UVM-1.2的仿真器)
  • UVM库:UVM-1.2(随仿真器发布或单独安装)
  • 编译方式:命令行编译,无需额外脚本

核心实现

以下代码包含两个完整的最小测试平台:tlm_test(事务级)与sig_test(信号级)。两者均完成1000次数据传输,数据宽度为8bit。事务级通过uvm_tlm_fifo传递sequence_item;信号级则在driver中逐周期翻转valid/ready握手信号完成传输。两个平台共享相同的时钟周期(10ns)与传输延迟模型(2个周期完成一次传输)。

```systemverilog
// 文件: tb_top.sv —— 包含两个独立测试平台,通过宏选择编译
`include "uvm_macros.svh"
import uvm_pkg::*;

// ---------- 事务级建模 ----------
class tlm_item extends uvm_sequence_item;
    rand byte data;
    `uvm_object_utils_begin(tlm_item)
        `uvm_field_int(data, UVM_ALL_ON)
    `uvm_object_utils_end
    function new(string name = "tlm_item");
        super.new(name);
    endfunction
endclass

class tlm_producer extends uvm_component;
    `uvm_component_utils(tlm_producer)
    uvm_blocking_put_port #(tlm_item) put_port;
    function new(string name, uvm_component parent);
        super.new(name, parent);
        put_port = new("put_port", this);
    endfunction
    task run_phase(uvm_phase phase);
        repeat(1000) begin
            tlm_item it = tlm_item::type_id::create("it");
            assert(it.randomize());
            put_port.put(it);
        end
    endtask
endclass

class tlm_consumer extends uvm_component;
    `uvm_component_utils(tlm_consumer)
    uvm_blocking_get_port #(tlm_item) get_port;
    int count = 0;
    function new(string name, uvm_component parent);
        super.new(name, parent);
        get_port = new("get_port", this);
    endfunction
    task run_phase(uvm_phase phase);
        tlm_item it;
        forever begin
            get_port.get(it);
            count++;
            if (count == 1000) break;
        end
    endtask
endclass

class tlm_test extends uvm_test;
    `uvm_component_utils(tlm_test)
    tlm_producer prod;
    tlm_consumer cons;
    uvm_tlm_fifo #(tlm_item) fifo;
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    function void build_phase(uvm_phase phase);
        prod = tlm_producer::type_id::create("prod", this);
        cons = tlm_consumer::type_id::create("cons", this);
        fifo = new("fifo", this, 10);  // 深度10
    endfunction
    function void connect_phase(uvm_phase phase);
        prod.put_port.connect(fifo.put_export);
        cons.get_port.connect(fifo.get_export);
    endfunction
    task run_phase(uvm_phase phase);
        phase.raise_objection(this);
        #100us;  // 足够完成1000次传输
        phase.drop_objection(this);
    endtask
endclass

// ---------- 信号级建模 ----------
class sig_driver extends uvm_component;
    `uvm_component_utils(sig_driver)
    virtual interface sig_if vif;
    int count = 0;
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    task run_phase(uvm_phase phase);
        @(posedge vif.clk);
        vif.valid <= 0;
        forever begin
            @(posedge vif.clk);
            if (count < 1000) begin
                vif.valid <= 1;
                vif.data <= $urandom;
                @(posedge vif.clk);
                wait (vif.ready == 1);
                count++;
            end else begin
                vif.valid <= 0;
                break;
            end
        end
    endtask
endclass

class sig_monitor extends uvm_component;
    `uvm_component_utils(sig_monitor)
    virtual interface sig_if vif;
    int count = 0;
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    task run_phase(uvm_phase phase);
        forever begin
            @(posedge vif.clk);
            if (vif.valid && vif.ready) begin
                count++;
                if (count == 1000) break;
            end
        end
    endtask
endclass

class sig_test extends uvm_test;
    `uvm_component_utils(sig_test)
    sig_driver drv;
    sig_monitor mon;
    virtual interface sig_if vif;
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    function void build_phase(uvm_phase phase);
        drv = sig_driver::type_id::create("drv", this);
        mon = sig_monitor::type_id::create("mon", this);
        if (!uvm_config_db#(virtual sig_if)::get(this, "", "vif", vif))
            `uvm_fatal("NOVIF", "sig_if not found")
        drv.vif = vif;
        mon.vif = vif;
    endfunction
    task run_phase(uvm_phase phase);
        phase.raise_objection(this);
        #100us;
        phase.drop_objection(this);
    endtask
endclass

// ---------- 接口与顶层 ----------
interface sig_if(input logic clk);
    logic valid, ready;
    logic [7:0] data;
endinterface

module tb_top;
    logic clk = 0;
    always #5 clk = ~clk;  // 10ns周期

    sig_if sif(clk);
    // 信号级握手:2周期延迟
    always @(posedge clk) begin
        if (sif.valid) sif.ready <= #1 1;
        else sif.ready <= #1 0;
    end

    initial begin
        // 选择运行哪个测试:通过+UVM_TESTNAME指定
        run_test();
    end

    // 为信号级测试提供接口
    initial begin
        uvm_config_db#(virtual sig_if)::set(null, "uvm_test_top", "vif", sif);
    end
endmodule
```

运行与验证

  1. 编译(以QuestaSim为例):
    vlog -sv tb_top.sv +incdir+$UVM_HOME/src
    vsim -c -do "run -all; quit" +UVM_TESTNAME=tlm_test work.tb_top
    将+UVM_TESTNAME替换为sig_test可运行信号级版本。
  2. 吞吐量测量:在两种测试的run_phase中,使用$time记录开始与结束时刻,计算1000 / (结束时间-开始时间),得到每单位仿真时间的事务数。实际墙钟时间可通过仿真器的time命令或外部计时获得。
  3. 通过条件:
    • 两种测试均无UVM错误或致命错误,正常结束;
    • 事务级测试中consumer的count达到1000,信号级测试中monitor的count达到1000;
    • 记录两种测试的仿真时间与墙钟时间,计算吞吐量并对比。预期事务级建模因减少逐周期握手开销,在相同事务量下墙钟时间更短(吞吐量更高)。
验证说明:本案例不提供实测数据,实际吞吐量差异取决于仿真器实现与机器性能。判定标准为:事务级测试完成1000次传输所需的仿真时间应小于或等于信号级(因信号级需额外等待握手时序),且墙钟时间通常显著更短。若两者仿真时间相同,则说明握手逻辑未正确建模(信号级应至少多出握手等待周期)。

关键点说明

  • 事务级建模通过uvm_tlm_fifo解耦生产与消费,无需关心信号时序;信号级必须显式处理valid/ready握手,每个事务至少占用2个时钟周期。
  • 两个测试平台共享相同的时钟与传输次数,保证对比公平性。
  • 若需更精确的吞吐量对比,可在run_phase中使用uvm_root::get().set_timeout()控制最大运行时间,并利用$realtime记录仿真时刻。