1. DUT
1.1 DUT 是要使用的电路;
1.2 DUT 自身不产生激励;
1.3 被包括在 TOP 中。
2. factory
2.1 factory 功能
将定义的类登记在 UVM 内部的表上。
2.2 factory 使用
在 driver 中
class my_driver extends uvm_driver
... ...
`uvm_component_utils(my_driver)
... ...
endclass
2.3 使用 factory 的功能对包含组件(env)的类实例化
格式为:实例名 = type_name::type_id::create的方式。
例如在 env 中实例化 driver 和 monitor :
class my_env extends uvm_env;
function new(string name = "me_env", uvm_component parent);
super.new(name, parents);
endfunction
my_driver drv;
my_monitor i_mon;
my_moniotr o_mon;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this);
i_mon = my_monitor::type_id::create("i_mon", this);
o_mon = my_moniotr::type_id::create("o_mon", this);
endfunction
`uvm_component_utils(my_env)
endclass
2.4 使用factory前后的不同
//instantiate driver without using `uvm_component_utils()
initial begin
my_driver drv;
drv = new("drv", null);
drv.main_phase(null);
$finish();
end
//instantiate driver by using `uvm_component_utils()
//by using `uvm_component_utils(),system instantiates a "my_driver" kind object
//and automatically calling main_phase.
initial begin
run_test("my_driver");
end
2.5 objection
使用 objection 机制控制验证平台的关闭。在 main_phase 中添加 objection,确保其完整运行。
phase.raise_objection一般放在super函数之后。
3. interface
3.1 interface 的用途
用于避免绝对路径的通信,从 top 向各个组件传递数据。
3.2 使用 build_phase 将 top 和 driver 中的 interface 对应
(1)引入 build_phase,通过 config_db 的 set 和 get 传递数据、实例化成员变量。
(2)build_phase 需要配合 super.build(phase); 使用。
(3)build_phase 是函数 phase,不消耗时间;main_phase 是任务 phase,消耗时间。
(4)build_phase 在 UVM 树中,按照从树根到树叶的顺序执行。
3.3 interface 传递数据:set、get
(1)在 build_phase 中,通过 config_db 的 set (位于发送方)和 get (位于接收方),传递数据和实例化成员变量。
(2)set 和 get 都有 4 个参数。对应的 set 和 get 的第三个参数必须相同。set 的第四个参数声明要将发送方的哪个实例化对象传递给接收方;get 的第四个参数声明要把从发送方接收到的实例化的对象传递给接收方的哪个成员变量。
(3)set 的第二个参数用于指向具体接收方。例如通过 env 例化 driver,env 作为 UVM 的树根名称是 uvm_test_top ,env 中 driver 的实例化对象是 drv ,所以 set 的第二个参数是 uvm_test_top.drv 。
(4)格式
set 的格式:uvm_config_db#(xxx)::set(xx, xx, xx, xx);
get 的格式:uvm_config_db#(xxx)::set(xx, xx, xx, xx);
(5)使用双冒号是因为 set 和 get 都是静态函数;uvm_config_db#(xx) 的括号中是要从 set 向 get 传递的类型。要传递 interface ,就是 uvm_config_db#(virtual interface_name) ;要传递 一个int 类型 ,就是 uvm_config_db#(int int_name) 。
// ------------------------------------------------------------------------------------------------------------- //
例 1 :从 top 向 driver 传送数据:
发送方 top :
my_interface input_if(a, b); // my_interface 类的实例化对象 input_if
initial begin
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top.drv", "vif", input_if);
end
接收方 driver :
class my_drive entends uvm_driver;
... ...
virtual my_interface vif;
... ...
endclass
task my_driver::main_phase(uvm_phase phase);
... ...
virtual function void build_phase(uvm_phase phase);
super.build_phase;
uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif);
endfunction
xxx <= vif.a;
xxx <= vif.b;
... ...
endtask
例 2 :将 int 类的数据传递给 drive
发送方 top :
initial begin
uvm_config_db#(int)::set(null, "uvm_test_top.drv", "var", 100);
end
接收方 driver :
class my_driver entends uvm_driver;
... ...
int var;
... ...
endclass
function void my_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(int)::get(this, "", "var", var);
endfunction
task my_driver::main_phase(uvm_phase phase);
... ...
xxx <= var;
... ...
endtask
根据传输信息的类型的不同,uvm_config_db#( ) 括号里的内容也会不同。如果是自定义的类,例如例 1 中自定义的 interface 类 virtual my_interface ,那么 uvm_config_db#(virtual my_interface) ;如果是已有的类,例如例 2 中的 int 类,那么 uvm_config_db#(int) 。
3.4 定义 interface
在 my_interface.sv 中定义 interface 类 my_interface 。
interface my_interface(input clk, input rst_n);
logic [7:0] data;
logic valid;
endinterface
3.5 top 中使用 interface
my_interface input_if(clk, rst_n);
my_interface output_if(clk, rst_n);
dut my_dut(
.clk( clk ); // 无需 interface,直接连接 top 提供的端口
.rst_n( rst_n );
.input_data( input_if.data ); // 连接 input_if 的端口
.input_valid( input_if.valid );
.output_data( output_if.data ); // 连接 output_if 的端口
.output_valid( output_if.valid );
);
3.5 driver 中使用 interface
句式:virtual interface类的类名 对象名(参数);
例如:
class my_driver extends uvm_driver;
... ...
virtual my_interface vif;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif);
endfunction
extern virtual task main_phase(uvm_phase phase);
endclass
task my_driver::main_pahse(uvm_phase phase);
... ...
endtask
4. top 结构组件一:transaction
4.1 概念和用途
(1)transaction 不是一直存在的,在仿真的某一时刻被 driver 驱动,经过 reference model 处理,由 scoredboard 比较完成后,其生命周期结束。因此 transaciton 派生自 uvm_object 类或其派生类 uvm_sequence 。
(2)transaction 被 driver 驱动。
(3)transaction 的基类是 uvm_sequence_item,只有从此基类派生,才能使用 sequence 的功能。
(4)因为 transaction 派生自 uvm_object 。所有派生自 uvm_project 的类都不能作为 UVM 树的结点。
(5)transaction 使用 `uvm_object_utils();
4.2 trancaction 的定义
class my_transaction extends uvm_sequence_item;
`uvm_object_utils(my_transaction)
rand bit[47:0] dmac; // 48bit的以太网目的地址
rand bit[47:0] smac; // 48bit的以太网源地址
rand bit[15:0] ether_type; // 以太网类型
rand byte pload[]; // 携带数据的大小
rand bit[31:0] crc; // 所有数据的校验值
// 对 pload 添加约束函数,规定 pload 的大小
constraint pload_cons{
pload.size >= 46;
pload.size <= 1500;
}
extern function new(string name, uvm_component parent);
extern function bit[31:0] calc_crc();
extern function void post_randomize();
extern function bit my_compare(my_transaction tr);
endclass
function my_transaction::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function bit[31:0] my_transaction::calc_crc(); // 模拟的校验算法
return 32'h0;
endfunction
// post_randomize 是 UVM 自带函数,在某个类的实例的 randomize 被调用后
// 自动调用。将模拟校验算法的结果赋给检验值 crc 。
function void post_randomize();
crc = calc_crc();
endfunction
function bit my_transaction::my_compare(my_transaction tr);
bit result;
if(tr==null)
`uvm_fatal("my_transaction", "tr is null");
result = ( (dmac==tr.dmac) && (smac==tr.smac) && (ether.tpye==tr.ether_type) && (crc==tr.crc) );
if(pload.size() != tr.pload.size())
result = 0;
else begin
for(int i=0; i<pload.size(); i++) begin
if(pload[i] != tr.pload[i])
result = 0;
end
end
endfunction
4.3 在 driver 中实现基于 transaction 的驱动
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual my_interface vif;
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern task drive_one_pkt(my_transaction tr);
endclass
function void my_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top.i_agt.mon", "vif", vif);
endfunction
task my_driver::main_phase(uvm_phase phase);
my_transaction tr; // 例化 transaction
tr = new("tr"); // 为例化的 tr 开辟空间
drive_one_pkt(tr);
endtask
task my_driver::drive_one_pkt(my_transaction tr);
bit [47:0] tmp_data; // 作为中间过渡存储
bit [7:0] data_q[$]; // 队列,每个元素 8 位,可动态生长
// 注意,队列是动态创建的
// 1. 将 dmac 压入 data_q
tmp_data = tr.dmac; // 将 tr 的 dmac 暂存在 tmp_data 中
for(int i=0; i<6; i++) begin
// dmac 共 48 位,data_q 每个元素 8 位,所以从 dmac 向 data_q 压入 6 次
data_q.push_back(tmp_data[7:0]); // 每次将 dmac 的低 8 位压入 data_q
tmp_data = (tmp_data >> 8); // dmac 右移 8 位
end
// 2. 将 smac 压入 data_q
tmp_data = tr.smac; // 将 tr 的 smac 暂存在 tmp_data 中
for(int i=0; i<6; i++) begin
// smac 共 48 位,data_q 每个元素 8 位,所以从 smac 向 data_q 压入 6 次
data_q.push_back(tmp_data[7:0]); // 每次将 smac 的低 8 位压入 data_q
tmp_data = (tmp_data >> 8); // smac 右移 8 位
end
// 3. 将 ether_type 压入 data_q
... ...
// 4. 将 payload 压入 data_q
... ...
// 5. 将 crc 压入 data_q
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
repeat(3) @(posedge vif.clk); // 等待 3 个时钟
// 当队列 data_q 非空,外部可以通过 interface 读取 transaction 的数据
while( data_q.size() > 0 ) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q.pop_front(); // pop 出队列的首个元素供外部读取
// 队列先进先出,每次 pop 一个 8 位的元素
end
// 执行到这段,说明上面的 while 循环结束,队列已经为空
@(posedge vif.clk);
vif.valid <= 0; // 停止读取 transaction 的数据
`uvm_info("my_driver", "drive_one_pkt ends", UVM_LOW);
endtask
5. top 结构组件二:env
uvm_env 是一个容器类,在这个容器类内实例化 driver、monitor、reference model、scoreboard 等。
5.1 加入 env 后 UVM 树的结构变化
因为通过 my_env 进行实例化 driver、monitor、reference model scoreboard 等组件,所以 UVM 的根变成 my_env ,my_driver 变成叶。
5.2 加入 env 后 top 的变化
未使用 env ,是在 top 中进行 my_driver 的实例化,在 build_phase 内通过 set 和 get 向 my_driver 中传递数据。
使用 env 例化后,变为在 top 中通过例化 env 间接例化 driver 。因此,set 用于指向传递目标的第二个参数也需要改变。
(1)未使用 env 的 top :
my_interface input_if; // 例化一个 my_interface 类型的对象 input_if
initial begin
run_test("my_driver"); // 直接例化 my_driver
end
initial begin
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top", "vif", input_if);
// 此时 UVM 树的根 uvm_test_top 指向 my_driver
end
(2)使用 env 后的 top :
my_interface input_if; // 例化一个 my_interface 类型的对象 input_if
initial begin
run_test("my_env"); // 通过 my_env 间接例化 my_driver
end
initial begin
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top.drv", "vif", input_if);
// 此时 UVM 树的根 uvm_test_top 指向 my_env
// uvm_test_top.drv 指向通过 my_env 实例化的 my_driver 类的对象 drv
end
set 的第二个参数指向要传递数据的实例。uvm_test_top 是自动创建的 UVM 树根的名字。
在没有使用 env 时,UVM 的根是 driver ,uvm_test_top 就指向 driver ,不用加后缀。
在使用 env 后,driver 在 env 中实例化。UVM 的根变为 env ,uvm_test_top 指向 env ,interface 的接收方还是 driver 不变,set 的第二个参数改为 uvm_tset_top.drv 。
5.3 my_env 定义
my_env.sv 如下(无 monitor ,只有 driver ):
class my_env extends uvm_env;
my_driver drv;
`uvm_component_utils(my_env)
extern function new(string name, uvm_component parent);
virtual function void build_phase(uvm_phase phase);
endclass
function my_env::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void my_env::build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this);
endfunction
6. monitor
6.1 monitor 的作用
收集 DUT 端口的数据传递,传递给 reference model 、scoreboard 等处理。
实际是将已有的 transaction 中的所有数据(从 dmac 到 crc 按顺序)压入 driver 中的临时队列,并通过 virtual interface 发送给 monitor。monitor 通过 virtual interface 接收数据再成一个新的 transaction 。
6.2 monitor 的特性
(1)所有 monitor 都派生自 uvm_monitor (class my_monitor extends uvm_monitor)。
(2)monitor 也需要 vitrual interface ,用于从 top 中获取数据。
(3)monitor 在整个仿真一直存在,所以使用 uvm_component_utils 宏注册 factory 。
(4)为了保证 uvm_monitor 在仿真中一直存在,在 my_monitor 的 main_phase 中使用 while(1) 循环来实现。
6.3 monitor 的定义
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor) // factory
function new(string name = "my_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual my_interface vif;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif);
endfunction
extern virtual task main_phase(uvm_phase phase);
extern task collect_one_pkt(my_transaction tr);
endclass
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr; // 实例化 my_transaction
while(1) begin // 使用 while(1) 保证 monitor 在仿真中始终存在
tr = new("tr"); // 为实例化的 tr 开辟新的空间
collect_one_pkt(tr); // 调用 collect_one_pkt 函数
end
endtask
// 因为 main_phase 是执行任务的主题,所以实例化 transaction 以及调用除 main_phase 之外的
// 其他 task 都在 main_phase 中完成
task my_monitor::collect_one_pkt(my_transaction tr);
bit [7:0] data_q[$]; // 每个元素 8 位的队列,临时存储来自 top 读取的端口数据
// 因为实际传送的是 transaction,所以将端口数据临时存储在 data_q 中
int psize;
// 即使 vif.valid=0 ,monitor 也一直存在
// 使用下面的循环保证 transaction 中队列为空时 monitor 不会结束
while(1) begin
@(posedge vif.clk);
if(vif.valid) break; // 当 vif.valid = 1,跳出循环,开始执行
end
// vif.valid=1 时,说明 transaction 非空,可以从中读取数据
// 将 transaction 传来的所有数据压入队列 data_q
// 新的数据压入队列末尾,队列后进先出
while(vif.valid) begin
data_q.push_back(vif.data);
#(posedge vif.clk);
end
// 1. dmac
// 先传送 dmac,从 data_q 中 pop 出数据给 tr 的 dmac
// 队列先进先出,为了保证顺序正确,从 data_q 头部 pop ,push 到 tr.dmac 尾部
// push 进 tr.dmac 尾部,实际是将 tr.dmac[39:0] 与 data_q 头部 pop 的元素拼接
// tr.dmac 是 48 位,等于 6 个 8 位 元素。data_q 每次 pop 出一个 8 位的元素,
// 所以 push 实际是把 tr.dmac 的低 40 位和 data_q 的首个元素拼接,形成新的 tr.dmac
for(i=0; i<6; i++) begin
tr.dmac = { tr.dmac[39:0], data_q.pop_front() };
end
// 2. smac
... ...
// 3. ether_type
... ...
// 4. payload
... ...
// 5. crc
... ...
endtask
monitor 工作流程就是用 interface 从 top 接收 DUT 端口的输入、输出数据,临时存储在队列中。再从队列头部 pop,依次 push 进 dmac、smac、ether_type、playload、crc 的尾部。这些数据组合成完整的 transaction,在 UVM 内传递。
上面的程序里设置临时存储数据的队列 data_q 中每个元素的大小是 8 位,transaction 中dmac = 48 位 = 8 位 × 6 。所以 data_q 每次 push 一个元素进 dmac 末尾,相当于将 dmac 的低 40 位左移,即 tr.dmac[39:0] 的元素放入 tr.dmac[47:8] ,将 data_q pop 的元素(8位)放入 tr.dmac[7:0] 。
6.4 在 env 中实例化 monitor
class my_env extends uvm_env;
`uvm_component_utils(my_env); // 在 factory 登记 my_env
my_driver drv; // 声明 driver 的例化对象 drv
my_monitor i_mon; // 声明输入端口 monitor 的例化对象 i_mon
my_monitor o_mon; // 声明输出端口的 monitor 的例化对象 o_mon
function new(string name = "my_env", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 例化 drv、i_mon、o_mon
drv = my_driver::type_id::create("drv", this);
i_mon = my_monitor::type_id::create("i_mon", this);
o_mon = my_monitor::type_id::create("o_mon", this);
endfunction
endclass
6.5 加入 monitor 后 UVM 树的结构
(1)monitor 与 driver 是并列;
(2)monitor 通过 env 实例化;
(3)monitor 贯穿仿真的始终。
7. agent
7.1 agent 的作用
因为 monitor 和 driver 类似,将 monitor 与 driver 共同封装在 agent 内,这样在 env 中就可以通过例化 agent 间接例化 monitor 和 driver 两个组件。
7.2 添加 agent 后的工作原理图
env 中只例化 my_agent 类的 i_agt 和 o_agt 。
i_agt 中例化 my_driver 类的 drv 、my_monitor 类的 mon 。drv 用于在 DUT 输入端口驱动信号,mon 用于监控 DUT 输入端口的信号。因为 i_agt 需要例化 driver ,所以在 env 中例化 i_agt 时,设置 i_agt 的自带参数:i_agt.is_active = UVM_ACTIVE; 。
o_agt 中例化 my_driver 类的 my_monitor 类的 mon (假设在 DUT 输出端不需要驱动数据)。mon 用于监控 DUT 输出端口的信号。因为 o_agt 不需要例化 driver ,所以在 env 中例化 o_agt 时,设置 o_agt 的自带参数:o_agt.is_active = UVM_PASSIVE; 。
7.3 添加 agent 后的 UVM 树
7.4 添加 agent 后 top 中 virtual interface 的路径变化
以 i_agt 例化的 driver 为例:top 例化 my_env (uvm_test_top)→ my_env 例化 i_agt → i_agt 例化 drv ,因此 top 中 virtual interface 的 set 的第二个参数为:uvm_test_top.i_agt_drv 。
... ...
my_interface input_if;
my_interface output_if;
... ...
initial begin
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top.i_agt.drv", "vif", input_if);
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top.i_agt.mon", "vif", input_if);
uvm_config_db#(virtual my_interface)::set(null, "uvm_test_top.o_agt.mon", "vif", output_if);
end
7.5 is_active 参数决定在 agent 中是否例化 driver
在 env 中例化 agent 时,设置 is_active = UVM_ACTIVE; ,那么在 agent 中例化 driver 和 monitor 。
在 env 中例化 agent 时,设置 is_active = UVM_PASSIVE; ,那么在 agent 中不例化 driver ,只例化 monitor 。
7.6 agent 的定义
// my_agent.sv
class my_agent extends uvm_agent;
my_driver drv; // 声明 my_driver 类的 drv
my_monitor mon; // 声明 my_monitor 类的 mon
`uvm_component_utils(my_agent)
function new(string name="my_agent",uvm_component parent=null);
super.new(name, parent);
endfunction
// 使用 function ,不是 task
extern virtual function void build_phase(uvm_phase phase);
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
// 如果 is.active==UVM_ACTIVE ,说明例化的是 i_agt ,需要例化 driver
// 如果 is.active==UVM_PASSIVE ,说明例化的是 o_agt ,不需要例化 driver
if(is.active == UVM_ACTIVE) begin
drv = my_driver::type_id::create("drv", this);
end
// 不管是 i_agt 还是 o_agt ,都需要 monitor
mon = my_monitor::type_id::create("mon", this);
endfunction
7.7 在 env 中例化 agent
注意,次代码假设 DUT 的输入端(i_agt)需要 driver 驱动数据,输出端(o_agt)不需要 driver 驱动数据。
class my_env extends uvm_env;
my_agent i_agt;
my_agent o_agt;
`uvm_component_utils(my_env)
function new(string name = "my_env", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt", this);
i_agt.is_active = UVM_ACTIVE;
o_agt = my_agent::type_id::create("o_agt", this);
o_agt.is_active = UVM_PASSIVE;
endfunction
8. reference model
8.1 reference model 的作用
reference model 用于完成和 DUT 相同的功能,其输出被传递到 scoreboard ,与 DUT 的输出相比较。
8.2 reference model 的定义
注意:(1)使用的 TLM 通信的接口在 class 内声明,在 build_phase 内例化。(2)main_phase 将 transaction 的数据复制给 my_transaction 类新例化的对象 new_tr ,tr 已经在 driver 中开辟了空间,所以在 my_model 中只需要给 new_tr 开辟空间。
class my_model extends uvm_component;
`uvm_conponent_utils(my_model)
uvm_blocking_get_port #(my_transaction) port;
uvm_analysis_port #(my_transaction) ap;
// uvm_blocking_get_port 用于接收从 i_agt 发送的 transaction
// uvm_analysis_port 用于发送 reference_model 中例化的 new_tr
extern function new(string name, uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
function my_model::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void my_model::build_phase(uvm_phase phase);
super.build_phase(phase);
port = new("port", this); // 在 build_phase 内对 port 例化
ap = new("ap", this);
endfunction
task my_model::main_phase(uvm_phase phase);
my_transaction tr;
my_transaction new_tr;
port.get(tr); // 这个 tr 是已经存在的,不需要再开辟空间生成
new_tr = new("new_tr");
new_tr.my_copy(tr);
ap.write(new_tr);
endtask
因为给 my_transaction 类新增了 copy 功能,所以将 my_transaction 进行改写:
class my_transaction extends uvm_sequence_item;
// 使用 uvm_object 宏实现 factory 。
`uvm_object_utils(my_transaction)
rand bit[47:0] dmac; // 48bit的以太网目的地址
rand bit[47:0] smac; // 48bit的以太网源地址
rand bit[15:0] ether_type; // 以太网类型
rand byte pload[]; // 携带数据的大小
rand bit[31:0] crc; // 所有数据的校验值
// 对 pload 添加约束函数,规定 pload 的大小
constraint pload_cons{
pload.size >= 46;
pload.size <= 1500;
}
extern function new(string name);
extern function bit[31:0] calc_crc();
extern function void post_randomize();
extern function void my_copy(transaction tr);
endclass
function my_transaction::new(string name = "my_transaction");
super.new(name);
endfunction
function bit[31:0] my_transaction::calc_crc();
return 32'h0;
endfunction
function void my_transaction::post_randomize();
crc = calc_rcr();
endfunction
function void my_transaction::my_copy(my_transaction tr);
if(tr == null)
`uvm_fatal("my_transaction", "is null");
dmac = tr.damc;
smac = tr.smac;
ether_typr = tr.ether_type;
pload = new[tr.polad.size()];
for(int i=0; i<pload.size(); i++) begin
pload[i] = tr.pload[i];
end
crc = tr.crc;
endfunction
9. transaction 的传递
9.1 传递路线
(1)i_agt 的 driver 驱动 transaction 。将已有的 transaction 中的所有数据,从 dmac 到 crc 按顺序压入队列临时保存,再从队列弹出到 vif.data ,通过 interface 发送。
(2)monitor 例化新的 transaction 。monitor 通过 interface 接收 driver 发送的数据,压入队列临时保存,再从队列弹出,送到 tr.dmac、tr.smac 等,拼接成一个新的 transaction 。
(3)在 monitor 中将新生成的 transaction 通过 uvm_analysis_port 端口发送。
(4)在 reference model 中通过 uvm_blocking_get_port 接收 i_agt 发送的 transaction 。
(5)在 env 中的 connect_phase 中,通过 uvm_tlm_analysis_fifo 将 monitor 的 uvm_analysis_port 和 reference model 的 uvm_blocking_get_port 连接。
9.2 i_agt 的 monitor 将 transaction 发出
(1)在类中声明 uvm_analysis_port 的端口 ap 。uvm_analysis_port 是一个参数化的类,其参数 A 是要发送的数据的类型(要发送本文中的transaction ,那此处 A 就是 my_transaction ),B 是 port 的实例名。格式为:uvm_analysis_port #(要发送的类型) 实例名;
(2)在类中声明 ap 后,在 build_phase 中例化。格式为: 实例名 = new("实例名", this);
(3)在 main_phase 中,创建完一个新的要发送的 transaction 后,将其写入 ap 。格式为:ap.write(要发送的实例名);
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor) // factory
function new(string name = "my_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual my_interface vif;
uvm_analysis_port #(my_transaction) ap; // 声明 analysis_port 端口 ap
extern virtual task main_phase(uvm_phase phase);
extern virtual function void build.phase(uvm_phase phase);
extern task collect_one_pkt(my_transaction tr);
endclass
function void my_monitor::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif);
ap = new("ap", this); // 将 ap 实例化
endfunction
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr;
while(1) begin
tr = new("tr");
collect_one_pkt(tr);
ap.write(tr); // 将新的要发送的 transaction 写入 ap
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;
end
while(vif.valid) begin
data_q.push_back(vif.data);
#(posedge vif.clk);
end
// 1. dmac
for(i=0; i<6; i++) begin
tr.dmac = { tr.dmac[39:0], data_q.pop_front() };
end
// 2. smac
... ...
// 3. ether_type
... ...
// 4. payload
... ...
// 5. crc
... ...
endtask
9.3 env 中连接 monitor 和 reference model
(1)在 class 中声明 uvm_tlm_analysis_port ;
(2)在 build_phase 中实例化;
(3)引入 connect::phase 。这是 UVM 内建的一个 phase ,在 build::phase 执行完毕后自动执行。其执行顺序是从叶到根,即先执行 driver 、monitor 的 connect_phase ,再执行 agent 的 connect_phase ,最后执行 env 的 connect_phase 。
(4)因为 env 中例化了 agent 和 reference model 而没有 monitor,所以可以在 agent 中定义一个指向 monitor 的 ap 的新的 ap ,方便在 env 中 连接 monitor 和 reference model 。agent 中的 ap 是一个指向 monitor 的 ap 的指针。
class my_agent extends uvm_agent;
my_driver drv;
my_monitor mon;
uvm_analysis_port #(my_transaction) ap;
`uvm_component_utils(my_agent)
function new(string name="my_agent",uvm_component parent=null);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if(is.active == UVM_ACTIVE) begin
drv = my_driver::type_id::create("drv", this);
end
mon = my_monitor::type_id::create("mon", this);
endfunction
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
ap = mon.ap;
endfunction
(5)env 定义如下:
env 中使用 uvm_tlm_analysis_port 定义的 agt_mdl_fifo 是连接 monitor 与 reference model 的通道名。
在 env 的 connect_phase 中,语句:i_agt.ap.connect(agt_mdl_info.analysis_export); 说明通道 agt_mdl_fifo 的发送端(analysis_export)指向 agt 的 ap ;语句:mdl.port.connect(agt_mdl_info.blocking_get_export); 说明通道 agt_mdl_fifo 的接收端(blocking_get_export)指向 mdl 的 port 。
class my_env extends uvm_env;
`uvm_component_utils(my_env)
uvm_tlm_analysis_port #(my_transaction) agt_mdl_info;
my_agent i_agt;
my_agent o_agt;
my_model mdl;
extern function new(string name, uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern function void connect_phase(uvm_phase phase);
endclass
function my_env::new(string name, uvm_component parent);
suprt.new(name,parent);
endfunction
function void my_env::build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt", this);
i_agt.is_avtive = UVM_ACTIVE;
o_agt = my_agent::type_id::create("o_agt", this);
o_agt.is_avtive = UVM_PASSIVE;
mdl = my_model::type_id::create("mdl", this);
agt_mdl_info = new("agt_mdl_info", this);
endfunction
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
i_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
endfunction
10. scoreboard
10.1 scoreboard 的作用和工作方式
scoreboard 接收 DUT 输入端和输出端的 transaction ,并进行比较。
10.2 scoreboard 的定义
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
my_transaction tmp_queue[$];
// 队列元素是 my_transaction 类,因为传递的 transaction 可能不止一个,所以用队列
uvm_blocking_get_port #(my_transaction) in_port;
// 接收 DUT 输入端的 transaction ,与 reference model 连接
uvm_blocking_get_port #(my_transaction) out_port;
// 接收 DUT 输出端的 transaction ,与 o_agt 的 monitor 连接
extern function new(string name, uvm_component parent);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
function my_scoreboard::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void my_scoreboard::build_phase(uvm_phase phase);
super.build_phase(uvm_phase phase);
in_port = new("in_port", this); // 例化 blocking_get_port 端口
out_port = new("out_port", this);
endfunction
task my_scoreboard::main_phase(uvm_phase phase);
my_transaction in_tr, out_tr, tmp_tr;
// in_tr 是 reference model 发送的 transaction
// out_tr 是 o_agt 的 monitor 发送的 transaction
// tmp_tr 保存从 tmp_queue 弹出的元素,将 tmp_tr 和 out_tr 进行比较
bit result;
// 保存比较结果
// 在 fork 内完成:
// 1. 接收 in_port 传递的 transaction 并存放在队列 tmp_queue 中
// 2. 接收 out_port 传递的 transaction
// 3. 比较值返给 result
fork
while(1) begin
in_port.get(in_tr); // 把从 DUT 输入端传入的 transaction 赋给 in_tr
tmp_queue.push_back(in_tr); // 把 in_tr 赋给队列 tmp_queue 临时保存
out_port.get(out_tr); // 把从 DUT 输出端传入的 transaction 赋给 out_tr
end
while(1) begin
if(tmp_queue.size() > 0) begin
tmp_tr = tmp_queue.pop_front(); // tmp_queue 的元素弹出放在 tmp_tr
result = out_tr.my_compare(tmp_tr);
// out_tr 调用 transaction 类包含的 my_compare 函数,与 tmp_tr 比较
if(result)
`uvm_info("my_scoreboard", "compare successfully", UVM_LOW)
else
`uvm_info("my_scoreboard", "compare failed", UVM_LOW)
end
else
`uvm_info("my_scoreboard", "No transaction transfered", UVM_LOW)
end
join
endtask