👉最近在写代码时,常常忽略了field automation机制带来的便利性,这里做一个全面的总结。下面我将分为 object 和 component 类型的对象中做域的自动化。首先,常用的宏包括:
- uvm_field_int(arg,FLAG) ,需要传递:变量+ 标志位
- uvm_field_enum(T,arg,FLAG) ,需要传递:类型+ 变量+ 标志位
- uvm_field_object(arg,FLAG)
- uvm_field_string(arg,FLAG)
- uvm_field_array_int(arg,FLAG)
- uvm_field_queue_int(arg,FLAG)
一、Object 类型
首先注册的方式,如下:
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(data,UVM_ALL_ON )
...
`uvm_object_utils_end
对于 bject 类,将类中的变量注册后,可以使用UVM预定义好的函数,如下:
函数名 | 描述 |
---|---|
copy | 用于实例的复制 ,如B.copy(A),即将A的内容复制给B |
clone | 克隆一个对象,如果对象不存在则创建一个新对象 |
打印内容,如B.print( ),即打印 B 对象中所有在域中注册的变量、数组等 | |
pack_bytes | 将在域中注册的变量和数组等打包成byte流,输出到一个动态数组中 |
unpack_bytes | 传入一个动态数组,将所有内容根据域中注册的顺序,转换成类中的字段 |
pack | 打包成bit流 |
unpack | 将bit流注意恢复到类中 |
常使用的标志位,包括:
标志 | 描述 |
---|---|
UVM_COPY / UVM_NOCOPY | 打开/关闭 复制 |
UVM_PACK /UVM_NOPACK | 打开/关闭 byte流 |
UVM_ALL_ON | 所有功能都打开 |
UVM_PRINT / UVM_NOPRINT | 打开 / 关闭打印功能 |
1. 打包成byte流
这个是我用的最多的一个操作,比如我通过发一个以太网的包,数据需要一byte一byte的打到总线的RXD线上,那么你需要怎么造包?又怎么打包和采包?transaction如下:
`ifndef GMII_RX_TRANSACTION_SV
`define GMII_RX_TRANSACTION_SV
typedef enum { RANDOM,INCR,FIXED } payload_kind
class gmii_rx_transaction extends uvm_sequence_item;
rand bit [2:0] spa;
rand bit [47:0] dmac;
rand bit [47:0] smac;
rand bit [15:0] ether_type;
rand bit [7:0] payload[];
//用于组合 payload
rand int pkt_len;
rand bit [7:0] first_data;
rand payload_kind kind;
`uvm_object_utils_begin(gmii_rx_transaction)
`uvm_field_int(sap,UVM_ALL_ON | UVM_NOPACK)
`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(payload,UVM_ALL_ON)
`uvm_field_int(pkt_len,UVM_ALL_ON | UVM_NOPACK)
`uvm_field_int(first_data,UVM_ALL_ON | UVM_NOPACK)
`uvm_field_enum(payload_kind,kind,UVM_ALL_ON | UVM_NOPACK)
`uvm_object_utils_end
...
endclass
`endif
如果我才用打包成byte流的方式,那么在driver和monitor上,就比较容易实现,driver 中不需要挨个向总线上驱动dmac、smac、ether_type、payload…,如下:
class gmii_rx_driver extends uvm_driver#(gmii_rx_transaction);
byte unsigned data_a[]; #用于保存 byte 流
...
endclass
task gmii_rx_driver::run()
...
seq_item_port.get_next_item(req);
req.pack_bytes(data_a); #将byte流数据保存到data_a动态数组中
foreach( data_a[i] )begin
vif.drv_cb.RXD <= data_a[i]; //将数据按byte输出到总线上
end
...
endtask
byte流是怎么打入到总线的?
下面是发包的内容:
打包成byte流后,数组中的存储状态,如下:
综上,我们可以知道,pack_bytes( )时是按由高到低的顺序排列。举个例子,也就是假如你有一个[47:0] data ,data = 48‘h 55 44 33 22 11 00 ,当你发到总线上的,是先发 55 ,再发44 ,依次发到00。
同理,monitor的实现也会更加简洁,如下:
class gmii_rx_monitor extends uvm_monitor#(gmii_rx_transaction);
gmii_rx_transaction trans;
byte unsigned data_q[$]; //用于保存从总线上读取的数据
byte unsigned data_a[]; //用于转化 byte 流
...
endclass
task gmii_rx_driver::run();
@(posedge vif.rstn);
trans = new("trans");
while(vif.mon_cb.RX_DV )begin
data_q.push_back(vif.mon_cb.RXD); //读取总线上的数据
@(posedge vif.clk);
end
data_a = new[data_q.size()];
for(int i = 0;i <data_q.size( ) ; i++ )begin
data_a[i] = data_q.pop_front(); //将数据存放在动态数组中
end
trans.payload = new[data_a.size() - 14];
trans.pkt_len = trans.unpack_bytes(data_a) / 8; //将data_a中byte流转换成transaction中的各个字段
endtask
注意:
- byte流必须要用动态数组来转换和保存;
- byte流会根据field automation中域定义的顺序来打包和解包byte流;
为了更加明显的感受到byte流的便利,让大家看一下我之前写的很蠢的办法,惨痛教训。
monitor中的采包,如下:
transaction中的封包,如下:
说实话,我最近被这个 field_automation 中的pack_bytes 坑死了,因为经过UVM的封装,灵活性就大大降低了,在使用过程中也会有诸多不便(痛苦面具,这里就不细说了)。然后我将代码改了一版,采用SV中的流操作符(>> 或 <<)进行 pack 和 unpack,极大地提高了代码的灵活性,可以对任意长度的pkg_len的包进行组合。实现如下:
首先driver发包时,将transaction中的数据打包成byte流,保存在一个stream_q队列中:
其次monitor 收包时,将byte流还原到一个结构体中,再还原到对应的各个栏位中:
2. 宏与if相结合
这是白皮书总结的用法,我觉得挺实用,在这里也进行一下总结。可以先在transaction中预留一个使能位,如果需要打开这个使能,可以再随机时指定这个使能位的值,选择性的打开或关闭功能。如下:
typedef enum { RANDOM,INCR,FIXED } payload_kind
class gmii_rx_transaction extends uvm_sequence_item;
rand bit [2:0] spa;
rand bit [47:0] dmac;
rand bit [47:0] smac;
rand bit [15:0] ether_type;
rand bit [7:0] payload[];
//用于组合 payload
rand int pkt_len;
rand bit [7:0] first_data;
rand payload_kind kind;
//控制使能vlan帧
rand bit is_vlan;
rand bit[15:0] vlan_info1;
rand bit[2:0] vlan_info2;
`uvm_object_utils_begin(gmii_rx_transaction)
`uvm_field_int(sap,UVM_ALL_ON | UVM_NOPACK)
`uvm_field_int(dmac,UVM_ALL_ON)
`uvm_field_int(smac,UVM_ALL_ON)
if(is_vlan)begin
`uvm_field_int(vlan_info1,UVM_ALL_ON)
`uvm_field_int(vlan_info2,UVM_ALL_ON)
end
`uvm_field_int(ether_type,UVM_ALL_ON)
`uvm_field_array_int(payload,UVM_ALL_ON)
`uvm_field_int(pkt_len,UVM_ALL_ON | UVM_NOPACK)
`uvm_field_int(first_data,UVM_ALL_ON | UVM_NOPACK)
`uvm_field_enum(payload_kind,kind,UVM_ALL_ON | UVM_NOPACK)
`uvm_object_utils_end
...
endclass
二、Component类
首先注册的方式,如下:
`uvm_component_utils_begin(my_component)
`uvm_field_int(data,UVM_ALL_ON )
...
`uvm_component_utils_end
1. 从config_db中自动get值
在component中使用域的自动化的目的与Objec类有很大区别,其很重要的一个功能在于:经过component类中域的自动化注册后,并且在build_phase中调用了super.build_phase(phase),就可以省略config_db#(T)::get( )获取到变量。如下:
class gmii_rx_driver extends uvm_monitor#(gmii_rx_transaction);
bit mon_enable;
`uvm_component_utils_begin(my_component)
`uvm_field_int(mon_enable,UVM_ALL_ON )
`uvm_component_utils_end
function new(string name = "gmii_rx_driver",uvm_component patrent = null);
super.new(name,parent);
mon_enable = 1;
endfunction
virtual function void build( );
super.build( );
//uvm_config_db#(int)::get(this,"","mon_enable",mon_enable); //这段话可以省略
`uvm_info("gmii_rx_monitor",$sformatf("mon_enable is %0d",mon_enable),UVM_HIGH)
endfunction
virtual task run( );
if(mon_enable)begin
mon_trans( );
end
endtask
endclass
此时monitor中的mon_enable已经在通过域自动化注册,此时如果从顶层set,在monitor就不需要get了。如下:
class base_test extends uvm_test;
...
virtual function void build( );
super.build( );
//向monitor 发送mon_enable变量
uvm_config_db#(int)::set(this,"env.agt.mon","mon_enable",0);
endfunction
...
endclass
2.与域的自动化相关函数
,首先component类是在UVM的树形结构中,所以相应地函数上有所区别。如下:
- clone无法使用,因为parent参数没办法指定;
- copy可以使用,但要注意在相同parent下的实例名不能相同;
- 其他的区别不大。