一、构建组件
在driver的基础上加入transaction、env、monitor,agent组件。
1、transaction
transaction指交易、事务,在协议中数据一般以包、帧形式传递而不是byte、bit的形式, transaction就表示一笔数据信息
`ifndef MY_TRANSACTION__SV
`define MY_TRANSACTION__SV
class my_transaction extends uvm_sequence_item;//所有的transaction都要从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;
//pload_cons约束
constraint pload_cons{
pload.size >= 46;
pload.size <= 1500;
}
function bit[31:0] calc_crc();
return 32'h0;
endfunction
function void post_randomize();//randomize函数被调用后,post_randomize会紧随其后无条件地调用
crc = calc_crc;
endfunction
`uvm_object_utils(my_transaction)//生命周期不是一直存在的所以使用了uvm_object_utils宏来实现工厂机制
function new(string name = "my_transaction");
super.new();
endfunction
function void my_print();
$display("dmac = %0h", dmac);
$display("smac = %0h", smac);
$display("ether_type = %0h", ether_type);
for(int i = 0; i < pload.size; i++) begin
$display("pload[%0d] = %0h", i, pload[i]);
end
$display("crc = %0h", crc);
endfunction
endclass
`endif
说明:
- 有生命周期的类都是派生自uvm_object或者uvm_object的派生类,uvm_sequence_item的祖先就是uvm_object。UVM中具有这种特征的类都要使用uvm_object_utils宏来实现
- 只有从uvm_sequence_item派生的transaction才可以使用UVM的sequence机制
2、更新后的driver
`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);//完成一些uvm必要的操作
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);//提起objection
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;});//随机化tr
drive_one_pkt(tr);//打包驱动到DUT
end
repeat(5) @(posedge vif.clk);
phase.drop_objection(this);//撤销objection
endtask
task my_driver::drive_one_pkt(my_transaction tr);
bit [47:0] tmp_data;
bit [7:0] data_q[$]; //打包成一个byte流的对列
//push dmac to data_q
tmp_data = tr.dmac;
for(int i = 0; i < 6; i++) begin
data_q.push_back(tmp_data[7:0]);//从对列后往前压入tmp_data
tmp_data = (tmp_data >> 8);
end
//push smac to data_q
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
//push ether_type to data_q
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
//push payload to data_q
for(int i = 0; i < tr.pload.size; i++) begin
data_q.push_back(tr.pload[i]);
end
//push crc to 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
`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
repeat(3) @(posedge vif.clk);
while(data_q.size() > 0) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q.pop_front(); //传递数据到dut
end
@(posedge vif.clk);
vif.valid <= 1'b0;
`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask
`endif
3、monitor
`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
class my_monitor extends uvm_monitor;//所有的monitor类应该派生自uvm_monitor
virtual my_if vif;//定义虚接口
`uvm_component_utils(my_monitor)//使用uvm_component_utils宏注册
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))//接收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;
while(1) begin //while(1)表示monitor需要时刻收集数据
tr = new("tr");
collect_one_pkt(tr);//收集事务包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;//表示driver传递数据到dut时跳出循环,开始监测数据
end
`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
while(vif.valid) begin
data_q.push_back(vif.data);//收集dut输出的数据
@(posedge vif.clk);
end
//pop dmac
for(int i = 0; i < 6; i++) begin
tr.dmac = {tr.dmac[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
4、agent
UVM中通常将driver、monitor二者封装在一起,成为一个agent,不同的agent就代表了不同的协议
`ifndef MY_AGENT__SV
`define MY_AGENT__SV
class my_agent extends uvm_agent ;//所有的agent都要派生自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)//agent本身是一个component,使用uvm_component_utils宏来注册factory
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if (is_active == UVM_ACTIVE) begin//根据is_active这个变量的值来决定是否创建driver的实例
drv = my_driver::type_id::create("drv", this);//前者drv只是一个句柄变量只是指向了该对象,后者drv才是真正的实例化对象名字
end
mon = my_monitor::type_id::create("mon", this);
endfunction
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
`endif
说明:
- 在这儿由于my_driver在uvm_agent中实例化,所以my_driver的父结点(parent)就my_agent就是this。
- is_active是uvm_agent的uvm_active_passive_enum定义的一个枚举类型的成员变量
- 枚举变量仅有两个值:UVM_PASSIVE和UVM_ACTIVE。在uvm_agent中,is_active的值默认为UVM_ACTIVE
- 在输出端口上不需要驱动任何信号,只需要监测信号,端口上是只需要monitor的,所以driver不用实例化
5、env
只使用run_test进行实例化显然是不行的,因为run_test函数虽然强大,但也只能实例化一个实例,要想例化driver、monitor、reference model和scoreboard等多个组件时定义一个env容器更方便,但在run_test传参时用env的参数,这样就只用run_test例化env
`ifndef MY_ENV__SV
`define MY_ENV__SV
class my_env extends uvm_env;//所有的env应该派生自uvm_env
my_agent i_agt;
my_agent o_agt;
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);
i_agt = my_agent::type_id::create("i_agt", this);//创建输入的agent
o_agt = my_agent::type_id::create("o_agt", this);//创建输出的agent
i_agt.is_active = UVM_ACTIVE;
o_agt.is_active = UVM_PASSIVE;//设置输出端口不实例化driver
endfunction
`uvm_component_utils(my_env)//容器类在仿真中也是一直存在的,使用uvm_component_utils宏来实现factory注册
endclass
`endif
说明:
- type_name::type_id::create(name,parent):type_name表示实例化对象的类名字,两个参数name表示实例化对象的名字,parent表示该实例化对象的父结点
- 在树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字(实例化名字)是固定的,为uvm_test_top,从树根长出枝叶就是指其他组件的实例化对象
- 无论是树根还是树叶,都必须由uvm_component或者其派生类继承而来,uvm_component的派生类会在uvm_component基础上增加一些功能,不同组件增加不同的功能
- UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,即先执行my_env的build_phase,再执行my_agent的build_phase,再执行my_driver的build_phase。当把整棵树的build_phase都执行完毕后,再执行后面的phase。
- 不可以在build_phase外的phase实例化如main_phase等。
- 还可以在new函数中执行实例化的动作比如driver和monitor,但会导致无法通过直接赋值的方式向uvm_agent传递is_active的值,要解决这个问题,可以在my_agent实例化之前使用config_db语句传递is_active的值,但UVM中约定俗成的在build_phase中完成实例化
6、top_tb
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "my_if.sv"
`include "my_transaction.sv"
`include "my_driver.sv"
`include "my_monitor.sv"
`include "my_agent.sv"
`include "my_env.sv"
module top_tb;
reg clk;
reg rst_n;
reg[7:0] rxd;
reg rx_dv;
wire[7:0] txd;
wire tx_en;
my_if input_if(clk, rst_n);
my_if output_if(clk, rst_n);
dut my_dut(.clk(clk),
.rst_n(rst_n),
.rxd(input_if.data),
.rx_dv(input_if.valid),
.txd(output_if.data),
.tx_en(output_if.valid));
initial begin
clk = 0;
forever begin
#100 clk = ~clk;
end
end
initial begin
rst_n = 1'b0;
#1000;
rst_n = 1'b1;
end
initial begin
run_test("my_env");//传递参数表示了树根,要被实例化的类
end
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.drv", "vif", input_if);//路径修改drv是在my_env的build_phase中实例化drv时传递过去的名字
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.mon", "vif", input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_agt.mon", "vif", output_if);
end
endmodule
- 由run_test自动创建的实例是树根(这里是my_env,但也并不是UVM真正的树根,真正树根是__top__暂时忽略这点),并且树根的名字(实例化名字)是固定的,为uvm_test_top。