文章目录
记录一下《UVM实战(卷I)》学习笔记。
这节的 dut 与第一节是一样的,直接对输入打拍输出:
module dut(clk,
rst_n,
rxd,
rx_dv,
txd,
tx_en);
input clk;
input rst_n;
input[7:0] rxd;
input rx_dv;
output [7:0] txd;
output tx_en;
reg[7:0] txd;
reg tx_en;
always @(posedge clk) begin
if(!rst_n) begin
txd <= 8'b0;
tx_en <= 1'b0;
end
else begin
txd <= rxd;
tx_en <= rx_dv;
end
end
endmodule
1 加入 transaction
一般物理协议传输都是以帧或包为单位,很少以bit或byte为单位进行数据交换。
transaction 用于模拟这种实际情况,一笔 transaction 就是一个包。
注意:
- my_transaction的基类是uvm_sequence_item。只有从uvm_sequence_item派生的transaction才可以使用UVM中强大的sequence机制。
- 没有使用uvm_component_utils宏来实现factory机制,而是使用了uvm_object_utils。
书本例程 my_transaction.sv 定义了一个简单的以太网的 transaction 。
在my_driver中实现基于transaction的驱动。
my_driver中定义的 main_phase 实现 transaction 的例化,然后 drive_one_pkt 任务的作用是将这个包按 byte 传输出去。
添加相关源文件,然后定义 modelsim do文件:
cd D:/prj/uvm_prj/demo2.3.1
vlib work
set UVM_PATH C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src
set SRC_PATH .
vlog +incdir+$UVM_PATH $UVM_PATH/uvm_pkg.sv $SRC_PATH/top_tb.sv $SRC_PATH/dut.sv
vsim -novopt -sv_lib C:/software/FPGA/modeltech64_10.5/uvm-1.1d/win64/uvm_dpi work.top_tb
add wave -position insertpoint sim:/top_tb/*
run 2us
定义批处理文件 :
vsim -do demo.do
文件列表:
仿真结果:
在 my_driver 的 main_phase 可以看到,产生了两个随机包,然后发送出去,这与仿真结果相符合。
2 加入 env
定义好了组件,验证平台的什么位置对它们进行实例化?
引入一个容器类,在这个容器类中实例化 driver、monitor、reference model 和 scoreboard 等。在调用 run_test 时,传递的参数不再是 my_driver ,而是这个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为 uvm_env。
注意:
- 所有的env应该派生自uvm_env, 且与my_driver一样, 容器类在仿真中也是一直存在的, 使用uvm_component_utils宏来实现factory的注册。
- build_phase的执行遵照从树根到树叶的顺序。
`ifndef MY_ENV__SV
`define MY_ENV__SV
class my_env extends uvm_env;
my_driver drv;
//
function new(string name = "my_env", uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 例化是 type_name::type_id::create 的方式 而不是 调用 my_driver 的 new 函数。只有使用factory机制注册过的类才能使用这种方式实例化,只有使用这种方式实例化的实例, 才能使用后文要讲述的factory机制中最为强大的重载功能。
drv = my_driver::type_id::create("drv", this);
endfunction
// 容器类在仿真过程中一直存在,所以用 uvm_component_utils 宏实现 factory 的注册。
`uvm_component_utils(my_env)
endclass
`endif
由于my_driver在uvm_env中实例化,所以my_driver的父结点(parent)就是my_env。所以例化的时候传递的参数为 this 指针。通过parent的形式,UVM建立起了树形的组织结构。在这种树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字是固定的,为uvm_test_top,在树根之后会生长出枝叶(这里只有my_driver),长出枝叶的过程需要在my_env的build_phase中手动实现。无论是树根还是树叶,都必须由uvm_component或者其派生类继承而来。
修改 Modelsim do文件,如果像我这样吧批处理文件放在工程中,则第一行切换工作路径可以删除,这也就可以通用了。另外,作者的代码都有$finish,所有仿真时间可以改为 run -all
cd .
vlib work
set UVM_PATH C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src
set SRC_PATH .
vlog +incdir+$UVM_PATH $UVM_PATH/uvm_pkg.sv $SRC_PATH/top_tb.sv $SRC_PATH/dut.sv
vsim -novopt -sv_lib C:/software/FPGA/modeltech64_10.5/uvm-1.1d/win64/uvm_dpi work.top_tb
add wave -position insertpoint sim:/top_tb/* sim:/top_tb/input_if/*
run -all
文件结构:
仿真结果:
3 加入 monitor
driver 把 transaction 级数据转变成 DUT 的输入数据。
monitor 把 DUT 的输出数据 转变成 transaction 数据。
monitor 输出的 transaction 数据交给后续组件(如referance model、scoreboard等)处理。
注意:
- 所有的monitor类应该派生自uvm_monitor。
- uvm_monitor在整个仿真中是一直存在的, 所以它是一个component, 要使用uvm_component_utils宏注册。
- 由于monitor需要时刻收集数据, 永不停歇, 所以在main_phase中使用while( 1) 循环来实现这一目的。
monitor的定义:
`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
// 所有的 monitor 派生自 uvm_monitor
class my_monitor extends uvm_monitor;
virtual my_if vif;
// uvm_monitor在整个仿真中是一直存在的
`uvm_component_utils(my_monitor)
function new(string name = "my_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
// 当UVM启动后, 会自动执行 build_phase。 build_phase在new函数之后main_phase之前执行。
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 phase);
extern task collect_one_pkt(my_transaction tr);
endclass
task my_monitor::main_phase(uvm_phase phase);
my_transaction tr;
// monitor需要时刻收集数据, 永不停歇
while(1) begin
tr = new("tr");
collect_one_pkt(tr);
end
endtask
task my_monitor::collect_one_pkt(my_transaction tr);
// ...
endtask
`endif
在env对monitor实例化:
// ...
class my_env extends uvm_env;
my_driver drv;
my_monitor i_mon;
my_monitor o_mon;
// ...
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = my_driver::type_id::create("drv", this);
// 同样的,要传递 this 指针作为 monitor 的 parent。
i_mon = my_monitor::type_id::create("i_mon", this);
o_mon = my_monitor::type_id::create("o_mon", this);
endfunction
`uvm_component_utils(my_env)
endclass
// ...
DUT 输入口添加 monitor 的原因:
- driver根据某一协议发送数据, 而monitor根据这种协议收集数据, 如果driver和monitor由不同人员实现, 那么可以大大减少其中任何一方对协议理解的错误。
- 实现代码重用。
文件结构:
仿真结果:
4 封装成agent
driver和monitor处理同一种协议,代码高度相似,UVM通常将二者封装在一起,称为一个agent。
因此,不同agent 代表不同协议。
注意:
- 所有的agent都要派生自uvm_agent类。
- 其本身是一个component, 应该使用uvm_component_utils宏来实现factory注册。
//...
// 所有 agent 都要派生自 uvm_agent
class my_agent extends uvm_agent ;
my_driver drv;
my_monitor mon;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_agent)
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
// is_active是uvm_agent的一个成员变量
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);
endfunction
//...
在env例化agent 而不需要直接例化 driver 和 monitor。
UVM树层次结构:
5 加入 reference model
reference model用于完成和DUT相同的功能。
reference model的输出被scoreboard接收,用于和DUT的输出相比较。
reference model 单纯地复制一份从i_agt得到的tr, 并传递给后级的scoreboard中。在UVM中,通常使用TLM( Transaction Level Modeling)实现component之间transaction级别的通信。
在UVM的transaction级别的通信中,数据的发送有多种方式,其中一种是使用uvm_analysis_port。这个是在monitor中发送的;数据接收方式也有多种,其中一种就是使用uvm_blocking_get_port,在model中接收。最后还要在env中使用fifo将两个端口连接在一起。
连接需要在env使用UVM内建 phase :connect_phase。在build_phase执行完成之后马上执行。执行顺序:从树叶到树根。
6 加入 scoreboard
my_scoreboard要比较的数据一是来源于reference model,二是来源于o_agt的monitor。都可以通过 uvm_blocking_get_port 来实现。
收到参考模型的数据先存到队列中,等DUT输出数据,再弹出来进行比对。
7 加入 field_automation 机制
引入my_mointor时, 在my_transaction中加入了my_print函数;引入reference model时, 加入了my_copy
函数;引入scoreboard时, 加入了my_compare函数。函数不同,但对于不同 transaction 来说类似,都是逐字段处理。
UVM中的field_automation机制, 使用uvm_field系列宏实现。通过定义某些规则自动实现这三个函数。
my_transaction.sv:
// ...
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(pload, UVM_ALL_ON)
`uvm_field_int(crc, UVM_ALL_ON)
`uvm_object_utils_end
//...
endclass
//...