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
```
运行与验证
- 编译(以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可运行信号级版本。 - 吞吐量测量:在两种测试的run_phase中,使用
$time记录开始与结束时刻,计算1000 / (结束时间-开始时间),得到每单位仿真时间的事务数。实际墙钟时间可通过仿真器的time命令或外部计时获得。 - 通过条件:
- 两种测试均无UVM错误或致命错误,正常结束;
- 事务级测试中consumer的count达到1000,信号级测试中monitor的count达到1000;
- 记录两种测试的仿真时间与墙钟时间,计算吞吐量并对比。预期事务级建模因减少逐周期握手开销,在相同事务量下墙钟时间更短(吞吐量更高)。
验证说明:本案例不提供实测数据,实际吞吐量差异取决于仿真器实现与机器性能。判定标准为:事务级测试完成1000次传输所需的仿真时间应小于或等于信号级(因信号级需额外等待握手时序),且墙钟时间通常显著更短。若两者仿真时间相同,则说明握手逻辑未正确建模(信号级应至少多出握手等待周期)。
关键点说明
- 事务级建模通过
uvm_tlm_fifo解耦生产与消费,无需关心信号时序;信号级必须显式处理valid/ready握手,每个事务至少占用2个时钟周期。 - 两个测试平台共享相同的时钟与传输次数,保证对比公平性。
- 若需更精确的吞吐量对比,可在run_phase中使用
uvm_root::get().set_timeout()控制最大运行时间,并利用$realtime记录仿真时刻。