话接上文UVM实战 卷I学习笔记2——为验证平台加入各个组件(1)
5、加入reference model
reference model用于完成和DUT相同的功能,其输出被scoreboard接收,用于和DUT的输出相比较。DUT如果很复杂,那么reference model也会相当复杂。本章的DUT很简单, 所以reference model也简单:
class my_model extends uvm_component;
uvm_blocking_get_port #(my_transaction) port;
uvm_analysis_port #(my_transaction) ap;
extern funciton new(string name, uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
`uvm_component_utils(my_model)
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);
ap = new("ap", this);
endfunction
task my_model::main_phase(uvm_phase phase);//复制一份从i_agt得到的tr并传递给后级scoreboard中
my_transaction tr;
my_transaction new_tr;
super.main_phase(phase);
while(1) begin
port.get(tr);
new_tr = new("new_tr");
new_tr.my_copy(tr);
`uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
new_tr.my_print();
ap.write(new_tr);
end
endtask
my_copy是一个在my_transaction中定义的函数, 实现两个my_transaction的复制,其代码为:
function void my_copy(my_transaction tr);
if(tr == null)
`uvm_fatal("my_transaction", "tr is null!!!!")
dmac = tr.dmac;
smac = tr.smac;
ether_type = tr.ether_type;
pload = new[tr.pload.size()];
for(int i = 0; i < pload.size(); i++) begin
pload[i] = tr.pload[i];
end
crc = tr.crc;
endfunction
完成my_model的定义后,需要将其在my_env中实例化。其实例化方式与agent、driver相似。加入my_model后,整棵UVM树如图所示:
- my_transaction的传递方式:my_model是从i_agt中得到my_transaction,并把
my_transaction传递给my_scoreboard。UVM通常使用TLM(Transaction Level Modeling)实现component之间transaction级别的通信。 - 要实现通信需要考虑两点:第一,数据是如何发送的?第二,数据是如何接收的?在UVM的transaction级别的通信中,数据的发送有多种方式,其中一种是使用uvm_analysis_port。在my_monitor中定义如下变量:uvm_analysis_port #(my_transaction) ap;
- uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据类型, 在例子中是my_transaction。声明了ap后,需要在monitor的build_phase中将其实例化:
virtual function void build_phase(uvm_phase phase);
...
ap = new("ap", this);
endfunction
在main_phase中,当收集完一个transaction后,需要将其写入ap中:
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr;
while(1) begin
tr = new("tr");
collect_one_pkt(tr);
ap.write(tr); //write是uvm_analysis_port的内建函数
end
endtask
- 至此,在my_monitor中需要为transaction通信准备的工作已经全部完成。
- UVM的transaction级通信的数据接收方式有多种,其中一种是用uvm_blocking_get_port。是参数化的类,其参数是要在其中传递的transaction的类型。在my_model中定义了一个端口,并在build_phase中对其进行实例化。在main_phase中,通过port.get任务来得到从i_agt的monitor中发出的transaction。
- 在my_monitor和my_model中定义并实现了各自的端口之后,通信的功能并没有实现,还需要在my_env中使用fifo将两个端口联系在一起。在my_env中定义一个fifo,并在build_phase中将其实例化:fifo的类型是uvm_tlm_analysis_fifo,它也是参数化的类,其参数是存储在其中的transaction的类型,这里是my_transaction。
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
...
agt_mdl_fifo = new("agt_mdl_fifo", this);
之后,在connect_phase中将fifo分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连:
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
- connect_phase也是UVM内建的一个phase,在build_phase执行完成之后马上执行。但与build_phase不同的是,它的执行顺序并不是从树根到树叶,而是从树叶到树根(自底向上)——先执行driver和monitor的connect_phase,再执行agent的connect_phase,最后执行env的connect_phase。
- 上例为何需要fifo?为何不能直接把my_monitor中的analysis_port和my_model中的blocking_get_port相连?由于analysis_port是非阻塞的,ap.write函数调用完成后马上返回,不会等待数据被接收。假如当write函数调用时,blocking_get_port正在忙而没有准备好接收新数据时,此时被write函数写入的my_transaction就需要一个暂存的位置——即fifo。
- 在上例的连接中用到了i_agt的一个成员变量ap,它的定义与my_monitor中ap的定义完全一样:uvm_analysis_port #(my_transaction) ap;
- 与my_monitor中的ap不同的是不需要对my_agent中的ap进行实例化,只需要在my_agent的connect_phase中将monitor的值赋给它, 相当于是一个指向my_monitor的ap的指针:
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
ap = mon.ap;
endfunction
my_agent的connect_phase的执行顺序早于my_env的connect_phase的执行顺序,从而保证执行到i_agt.ap.connect语句时,i_agt.ap不是一个空指针——connect_phase自底向上的意义所在。
6、加入scoreboard
在验证平台中加入了reference model和monitor之后,最后一步是加入scoreboard。my_scoreboard的代码如下:
class my_scoreboard extends uvm_scoreboard;
my_transaction expect_queue[$];
uvm_blocking_get_port #(my_transaction) exp_port;
uvm_blocking_get_port #(my_transaction) act_port;
`uvm_component_utils(my_scoreboard)
extern function new(string name, uvm_component parent = null);
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 = null);
super.new(name, parent);
endfunction
function void my_scoreboard::build_phase(uvm_phase phase);
super.build_phase(phase);
exp_port = new("exp_port", this);
act_port = new("act_port", this);
endfunction
task my_scoreboard::main_phase(uvm_phase phase);
my_transaction get_export, get_actual, tmp_tran;
bit result;
super.main_phase(phase);
fork
while(1) begin
exp_port.get(get_expect);
expect_queue.push_back(get_expect);
end
while(1) begin
act_port.get(get_actual);
if(expect_queue.size() > 0) begin
tmp_tran = expect_queue.pop_front();
result = get_actual.my_compare(tmp_tran);
if(result) begin
`uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW)
end
else begin
`uvm_error("my_scoreboard", "Compare FAILED")
$display("the expect pkt is");
tmp_tran.my_print();
$display("the actual pkt is");
get_actual.my_print();
end
end
join
endtask
//my_compare函数:逐字段比较两个my_transaction并给出最终的比较结果
function bit 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_type == tr.ether_type)
&& (crc == tr.crc));
if(pload.size() != tr.pload.size())
result = 0;
else
for(int i = 0; i < pload.size(); i++) begin
if(pload[i] != tr.pload[i])
result = 0;
end
return result;
endfunction
- my_scoreboard要比较的数据来源于reference model和o_agt的monitor,前者通过exp_port获取,后者通过act_port获取。
- 在main_phase中通过fork建立起了两个进程,一个处理exp_port的数据,收到数据后把数据放到expect_queue;另外一个处理act_port(DUT的输出)的数据,收到数据后调用my_transaction的my_compare函数比较两组收到的数据。
- 比较的前提是exp_port要比act_port先收到数据。由于DUT处理数据需要延时,而reference model是基于高级语言的处理,一般不需要延时,因此可保证这个前提的实现。
- 完成my_scoreboard的定义后,也需要在my_env中将其实例化。此时,整棵UVM树如图所示:
7、加入field_automation机制
-
前文引入my_mointor时在my_transaction中加入了my_print函数;引入reference model时加入my_copy
函数;引入scoreboard时加入my_compare函数。 -
上述三个函数虽然各自不同,但对于不同的transaction来说都是类似的: 它们都需要逐字段地对transaction进行某些操作。
-
field_automation机制可通过某些规则自动实现这些函数,通过使用uvm_field系列宏来实现:
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;
...
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(dmac, UVM_ALL_ON)
`uvm_field_int(smac, UVM_ALL_ON)
`uvm_field_int(ether_type, UVM_ALL_ON)
`uvm_field_array_int(ploadm, UVM_ALL_ON)
`uvm_field_int(crc, UVM_ALL_ON)
`uvm_object_utils_end
...
endclass
- 使用uvm_object_utils_begin和uvm_object_utils_end实现my_transaction的factory注册,在这两个宏中间使用uvm_field宏注册所有字段。uvm_field系列宏随着transaction成员变量的不同而不同。
- 使用上述宏注册之后,可以直接调用copy、 compare、 print等函数而无需自己定义。这极大简化了验证平台的搭建,提高了效率。例如下例:
task my_model::main_phase(uvm_phase phase);
my_transaction tr;
my_transaction new_tr;
super.main_phase(phase);
while(1) begin
port.get(tr);
new_tr = new("new_tr");
new_tr.copy(tr);
`uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
new_tr.print();
ap.write(new_tr);
end
endtask
- 引入field_automation机制的另外一大好处是简化了driver和monitor。
- 在前文my_driver的drv_one_pkt任务和my_monitor的collect_one_pkt任务代码很长,但几乎是一些重复性的代码。使用field_automation机制后,drv_one_pkt任务可以简化为:
task my_driver::drive_one_pkt(my_transaction tr);
byte unsigned data_q[];
int data_size;
data_size = tr.pack_bytes(data_q) / 8;//将tr所有字段变成byte流放入data_q中
`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW)
repeat(3) @(posedge vif.clk);
for(int i = 0; i < data_size; i++) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q[i];
end
@(posedge vif.clk);
vif.valid <= 1'b0;
`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask
- 在把所有字段变成byte流放入data_q中时,字段按照uvm_field系列宏书写的顺序排列。上述代码中是先放入dmac,再依次放入smac、ether_type、pload、crc。
- my_monitor的collect_one_pkt可以简化成:
task my_monitor::collect_one_pkt(my_transaction tr);
byte unsigned data_q[$];
byte unsigned data_array[];
logic [7:0] data;
logic valid = 0;
int data_size;
...
`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
while(vif.valid) begin
data_q.push_back(vif.data);
@(posedge vif.clk);
end
data_size = data_q.size();
data_array = new[data_size];
for ( int i = 0; i < data_size; i++ ) begin
data_array[i] = data_q[i];
end
tr.pload = new[data_size - 18]; //da sa, e_type, crc
data_size = tr.unpack_bytes(data_array) / 8;
`uvm_info("my_monitor", "end collect one pkt", UVM_LOW);
endtask
- 使用unpack_bytes函数将data_q中的byte流转换成tr中的各个字段。这个函数的输入参数必须是一个动态数组,所以需要先把收集到的、放在data_q中的数据复制到一个动态数组中。由于tr中的pload是一个动态数组,所以需要在调用unpack_bytes之前指定其大小, 这样unpack_bytes函数才能正常工作。