一些学习备注
目录
push_back
push_back
是 SystemVerilog 队列(queue) 的方法,用于在队列末尾添加元素。
在 UVM 中,它主要用于:动态管理数据集合(如事务队列、配置列表),构建灵活的数据结构(如序列项列表、覆盖率事件队列)
(1) 队列声明与操作
// 声明队列
int q[$]; // 整数队列
uvm_transaction tr_q[$]; // UVM事务队列
my_data_t data_queue[$]; // 自定义数据类型队列
// 添加元素到队列末尾
q.push_back(10); // 队列变为 {10},把10 添加到q的队尾
q.push_back(20); // 队列变为 {10, 20}
(2) UVM 中的典型应用
// 在sequence中收集生成的transaction, // 监测接口并捕获事务 class my_sequence extends uvm_sequence; uvm_transaction tr_queue[$]; task body(); for (int i = 0; i < 10; i++) begin my_transaction tr; tr = my_transaction::type_id::create("tr");//工厂创建对象 tr.randomize(); tr_queue.push_back(tr); // tr值添加到队列tr_queue里 end endtask endclass
data_q
是动态队列(bit [7:0] data_q[$]
),其大小会随着 push_back
自动增长
task my_monitor::collect_one_pkt(my_transaction tr);
bit[7:0] data_q[$];
int psize;
while(1) begin
@(posedge vif.clk); //// 等待时钟上升沿
if(vif.valid) break; // 如果 valid=1,退出循环
end
`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
while(vif.valid) begin //只要vif.valid信号为高电平(1),就在每个时钟上升沿将 vif.data存入队列 data_q。
data_q.push_back(vif.data); // 将当前数据存入队列
@(posedge vif.clk); // 等待下一个时钟上升沿
end
//pop dmac
for(int i = 0; i < 6; i++) begin
tr.dmac = {tr.mac[39:0],data_q.pop_front()};
end
//pop smac
for(int i = 0; i < 6; i++) begin
tr.smac = {tr.smac[39:0], data_q.pop_front()};
end
//pop ether_type
for(int i = 0; i < 2; i++) begin
tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};
end
//pop dmac,循环6次(对应MAC地址的6字节长度)。每次从队列 data_q头部弹出一个字节(data_q.pop_front()),并将其拼接到 tr.dmac的低8位
randomize()
SystemVerilog 的内置方法,由语言标准定义(IEEE 1800),并非 UVM 专属。所有声明了 rand
或 randc
变量的类都会自动继承此方法。
当类中声明 rand
变量时,SystemVerilog 编译器会自动为类添加 randomize()
方法。
class my_transaction extends uvm_sequence_item;
rand bit [31:0] addr; // 随机变量
rand bit [63:0] data;
bit [7:0] mode; // 非随机变量constraint valid_addr {
addr[1:0] == 0; // 地址对齐约束
}
endclassmy_transaction tr = new();
if (!tr.randomize())
`uvm_error("RAND", "Randomization failed")
constraint
SystemVerilog 中用于限制随机变量取值范围的构造块,属于约束随机验证(CRV)的核心机制。
核心作用:在随机化(randomize()
)时,自动生成符合设计规则的合法值。
语法 |
作用 |
示例 |
---|---|---|
|
限定值范围 |
|
|
权重分布 |
|
|
关系运算 |
|
class my_transaction;
rand bit [31:0] addr; // 随机变量
rand bit [63:0] data;// 约束定义
constraint valid_range {
addr inside {[0x1000:0x1FFF]}; // 地址范围限制
data dist {0 :/ 50, [1:100] :/ 50}; // 数据分布权重
}
endclass
repeat(3) @(posedge vif.clk);
在驱动数据包之前插入3个时钟周期的延迟,repeat(3)
:重复执行后续语句3次,@(posedge vif.clk)
:每次等待 vif.clk
的上升沿。组合效果:仿真器会暂停当前线程,直到观察到 3个时钟上升沿,然后继续执行后续代码。
延迟的目的
(1) 协议时序对齐
场景:在以太网、AXI 等协议中,发送数据前可能需要空闲周期(IDLE)。
作用:确保DUT(被测设计)准备好接收数据,避免因信号未稳定导致的冲突。
示例:
某些DUT要求数据包之间至少间隔2个时钟周期。
模拟真实硬件中的信号建立时间(Setup Time)。
(2) 调试与观察
波形调试:在数据包发送前留出空白周期,便于在波形工具(如Verdi)中区分不同数据包。
日志同步:
uvm_info
打印日志后,延迟3个周期再驱动数据,避免日志与波形不同步。
(3) 避免竞争条件
风险:若直接驱动数据,可能与DUT的复位或其他控制信号冲突。
解决:延迟3个周期确保DUT处于稳定状态。
my_monitor.sv
`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
//收集DUT 端口数据(interface),并转换成 transaction 交给后续组件
class my_monitor extends uvm_monitor;
virtual my_if vif;
`uvm_component_utils(my_monitor)
function new(string name="my_monitor",uvm_component parent=null);
super.new(name,parent);
endfunction
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_monitor","virtual interface must be set for vif!!!")
endfunction
extern task main_phase(uvm_phase); // 因为注册在工厂里,所以可以直接引用
extern task collect_one_pkt(my_transaction tr);
endclss
//在循环里新建对象,每次捕获到有效数据时生成一个独立的事务对象。类似于一张张照片
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr;
while(1) begin //monitor需要一直活动,所以 while(1)
tr = new("tr"); // 创建事务对象
collect_one_pkt(tr); //收集事务
end
endtask
task my_monitor::collect_one_pkt(my_transaction tr);
bit[7:0] data_q[$];
int psize;
while(1) begin
@(posedge vif.clk); //// 等待时钟上升沿
if(vif.valid) break; // 如果 valid=1,退出循环
end
`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
//只要vif.valid信号为高电平(1),就在每个时钟上升沿将 vif.data存入队列 data_q。
while(vif.valid) begin
data_q.push_back(vif.data); // 将当前数据存入队列
@(posedge vif.clk); // 等待下一个时钟上升沿
end
//pop dmac, 以太网目的地址
//循环6次,每次从队列 头部弹出一个字节,并将其拼接到 tr.dmac的低8位,bit[7:0] data_q[$];所以每次弹出8位
for(int i = 0; i < 6; i++) begin
tr.dmac = {tr.mac[39:0],data_q.pop_front()};
end
//pop smac 以太网源地址
for(int i = 0; i < 6; i++) begin
tr.smac = {tr.smac[39:0], data_q.pop_front()};
end
//pop ether_type 以太网类型
for(int i = 0; i < 2; i++) begin
tr.ether_type = {tr.ether_type[7:0], data_q.pop_front()};
end
psize = data_q.size()-4;
tr.pload = new[psize];
//pop payload 携带数据大小
for(int i = 0; i < psize; i++) begin
tr.pload[i] = data_q.pop_front();
end
//pop crc 之前所有数据的校验值
for(int i = 0; i < 4; i++) begin
tr.crc = {tr.crc[23:0], data_q.pop_front()};
end
`uvm_info("my_monitor", "end collect one pkt, print it:", UVM_LOW);
tr.my_print();
endtask
`endif
my_driver.sv
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver;
virtual my_if vif;
`uvm_component_utils(my_driver)
function new(string name="my_driver", uvm_component parent=null);
super.new(name,parent);
endfunction
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
extern task main_phase(uvm_phase phase);
extern task drive_one_pkt(my_transaction tr);
endclass
task my_driver::main_phase(uvm_phase phase);
my_transaction tr;
phase.raise_objection(this);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
for(int i = 0; i < 2; i++) begin
tr=new("tr"); //创建事务
assert(tr.randomize() with {pload.size == 200;});
drive_one_pkt(tr);
end
repeat(5) @(posedge vif.clk);
phase.drop_objection(this);
endtask
task my_driver::drive_one_pkt(my_transaction tr);
bit [47:0] tmp_data;
bit [7:0] data_q[$]; //8位动态队列
tmp_data = tr.dmac; //每次放8 位=1个字节,总共6次
for(int i=0; i<6;i++) begin
data_q.push_back(tmp_data[7:0]);
tmp_data = (tmp_data >>8);
end
tmp_data = tr.smac;
for(int i=0; i<6;i++) begin
data_q.push_back(tmp_data[7:0]);
tmp_data = (tmp_data >>8);
end
tmp_data = tr.ether_type;
for(int i=0; i<2;i++) begin
data_q.push_back(tmp_data[7:0]);
tmp_data = (tmp_data >>8);
end
// rand byte pload[];
for(int i=0; i<tr.pload.size;i++) begin
data_q.push_back(tr.pload[i]);
end
tmp_data = tr.crc;
for(int i=0; i<4;i++) begin
data_q.push_back(tmp_data[7:0]);
tmp_data = (tmp_data >>8);
end
`uvm_info("my_driver","begin to drive one pkt",UVM_LOW);
repeat(3) @(posedge vif.clk); //数据包之间间隔3个时钟周期
while(data_q.size()>0) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q.pop_front();
end
@(posedge vif.clk);
vif.valid <= 1'b0;
`uvm_info("my_driver","end drive one pkt",UVM_LOW);
endtask
`endif
UVM 端口
uvm_blocking_get_port #(my_transaction) port;
uvm_analysis_port #(my_transaction) ap;
uvm_blocking_get_port
、 uvm_analysis_port
是两种不同类型的通信端口(port),它们分别用于不同的数据传输场景。它们都是由UVM库提供的标准类,定义在UVM的基础类库中。
uvm_blocking_get_port #(my_transaction) port;
所属类:
uvm_blocking_get_port
是UVM标准库的一部分,定义在uvm_tlm
命名空间中。头文件:通常包含在
uvm_tlm.svh
或uvm_pkg.sv
中(UVM会自动导入)。作用:用于阻塞式的点对点数据传输(TLM,Transaction Level Modeling)。
用途
用于从上游组件(如
sequencer
或driver
)获取事务(transaction)。
get()
是一个阻塞方法,如果端口没有数据,它会一直等待,直到数据到达。适用于生产者-消费者模型,其中
my_model
是消费者,等待上游组件发送数据。
uvm_analysis_port #(my_transaction) ap;
来源
所属类:
uvm_analysis_port
也是UVM标准库的一部分,定义在uvm_tlm
命名空间中。头文件:同样包含在
uvm_tlm.svh
或uvm_pkg.sv
中。作用:用于非阻塞式的广播数据传输(一对多通信)。
用途
用于向下游组件(如
scoreboard
、coverage collector
或monitor
)广播事务。
write()
是一个非阻塞方法,它会立即将数据发送给所有连接的组件。适用于观察者模式,多个组件可以订阅同一个数据流
在UVM中,TLM(Transaction Level Modeling)通信主要分为:
- 阻塞式通信(Blocking):uvm_blocking_get_port,uvm_blocking_put_port,uvm_blocking_transport_port
- 非阻塞式通信(Non-blocking):
uvm_nonblocking_get_port
,uvm_nonblocking_put_port
- 分析端口(Analysis):
uvm_analysis_port
(广播),uvm_analysis_export
(接收端)
UVM_FIFO
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
i_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
分析:
uvm_tlm_analysis_fifo
是一个 UVM TLM FIFO,标准库中自定义的类,它继承自 uvm_tlm_fifo
,主要解决 analysis_port
和 blocking_get_port
的兼容性问题。主要特点是内部维护一个队列,用于缓存事务(my_transaction
)。
提供两种接口:
analysis_export
:用于接收来自analysis_port
的数据(write()
方法)。blocking_get_export
:用于提供数据给blocking_get_port
(get()
方法)。
uvm_tlm_analysis_fifo
的核心功能
功能 |
说明 |
---|---|
|
通过 |
|
通过 |
内部队列 |
缓存事务,解决生产者和消费者的速率不匹配问题。 |
线程安全 |
UVM 自动处理多线程竞争(如 monitor 和 model 运行在不同线程)。 |
objection 机制
objection
机制 是用于控制 UVM 仿真阶段(phase)执行流程的核心机制,其主要作用是 防止仿真阶段提前终止,确保测试逻辑完整执行。
UVM 的 动态运行阶段(即task phase,比如 reset_phase
、main_phase
、shutdown_phase
)都需要 objection
控制。最常用的是 sequence、scoreboard。
而 function 类的phase,不需要。
例外:静态阶段是自动执行的,无需
objection
控制。
function void build_phase(uvm_phase phase);
super.build_phase(phase); // 无需 objection
agent = my_agent::type_id::create("agent", this);
endfunction
$value$plusargs
SystemVerilog 内置函数,用于从命令行解析参数。
从仿真命令行中读取参数 i2c_dma_sel
的值,并将其赋给变量 i2c_dma_sel
。
if(!$value$plusargs("i2c_dma_sel=%d", i2c_dma_sel))
i2c_dma_sel = 0; // 默认值
仿真命令行 示例:
simv +i2c_dma_sel=2 +UVM_TESTNAME=i2c_dma_test