前序
本篇文章主要参考《UVM实战:第二章》,虽说只是搭建了一个简单的验证平台,但从书本的零星知识到makefile成功并且dump到正确的波形,还是费了周折,有一定的参考意义。现将过程与学习IC验证的同学分享,也是防止自己日后遗忘。
验证平台拓扑结构(case调用report_phase中的uvm_top.print_topology()函数来打印整个验证平台的拓扑结构):
笔者的文件目录:
DUT
wrapper.v
DUT很简单,就是将接收到的数据在下一拍输出,输入输出都由data和valid构成。
module wrapper(
input clk,
input rst_n,
input [7 : 0] rxd,
input rx_dv,
output reg [7 : 0] txd,
output reg tx_en
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n) begin
txd <= 8'd0;
tx_en <= 1'b0;
end
else begin
txd <= rxd;
tx_en <= rx_dv;
end
end
endmodule
UVM验证平台(自上而下的顺序,主要组件实现的功能)
case0.sv:
1. 首先定义了此case对应的sequence,在sequence中产生transaction,如何产生取决于sequence中的body()任务,此case中的sequence用`uvm_do系列宏产生了10个transaction,关于uvm_do系列宏,参考笔者之前的一片博文,应该会有一些了解。
2. 并用starting_phase指针控制sequencer.main_phase平台的raise/drop_objection:参考书中原话,在uvm_sequence这个基类中,有一个变量名为starting_phase,它的类型是uvm_phase,sequencer在启动default_sequence时,将seq.starting_phase指向了sequencer的main_phase。
3. 在build_phase中实例化一个env,并通过uvm_config_db把sequence寄信给sqr.main_phase。
4. 打印此case的拓扑结构以及最终的比较结果。
class case0_sequence extends uvm_sequence#(transaction_dut);
transaction_dut m_trans;
function new(string name = "case0_sequence");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat(10) begin
`uvm_do_with(m_trans, {m_trans.pload.size() == 10;})
end
#100;
if(starting_phase != null) begin
starting_phase.drop_objection(this);
end
endtask
`uvm_object_utils(case0_sequence)
endclass
class case0 extends uvm_test;
my_env env;
function new(string name = "case0", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("case0", "case0 build_phase", UVM_LOW)
env = my_env::type_id::create("env", this);
//use default_sequence
uvm_config_db#(uvm_object_wrapper)::set(this, "env.in_agt.sqr.main_phase", "default_sequence", case0_sequence::type_id::get());
endfunction
extern virtual function void report_phase(uvm_phase phase);
`uvm_component_utils(case0)
endclass
function void case0::report_phase(uvm_phase phase);
uvm_report_server server;
int err_num;
super.report_phase(phase);
uvm_top.print_topology();
server = get_report_server();
err_num = server.get_severity_count(UVM_ERROR);
if(err_num != 0) begin
$display("----------------------");
$display("----- TEST CASE FAILED ----");
$display("----------------------");
end
else begin
$display("----------------------");
$display("----- TEST CASE PASSED ----");
$display("----------------------");
end
endfunction
env.sv
1. build_phase中创建四个组件,以及连接他们的三个fifo,以及agent的参数。
2. connect_phase中将他们连接起来。
class my_env extends uvm_env;
my_agent in_agt;
my_agent out_agt;
my_model mdl;
my_scoreboard scb;
uvm_tlm_analysis_fifo #(transaction_dut) agt_scb_fifo;
uvm_tlm_analysis_fifo #(transaction_dut) agt_mdl_fifo;
uvm_tlm_analysis_fifo #(transaction_dut) mdl_scb_fifo;
extern function new(string name = "my_env", uvm_component parent = null);
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_env)
endclass
function my_env::new(string name = "my_env", uvm_component parent = null);
super.new(name, parent);
endfunction
function void my_env::build_phase(uvm_phase phase);
super.build_phase(phase);
in_agt = my_agent::type_id::create("in_agt", this);
out_agt = my_agent::type_id::create("out_agt", this);
in_agt.is_active = UVM_ACTIVE;
out_agt.is_active = UVM_PASSIVE;
mdl = my_model::type_id::create("mld", this);
scb = my_scoreboard::type_id::create("scb", this);
agt_scb_fifo = new("agt_scb_fifo", this);
agt_mdl_fifo = new("agt_mdl_fifo", this);
mdl_scb_fifo = new("mdl_scb_fifo", this);
endfunction
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
in_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
mdl.ap.connect(mdl_scb_fifo.analysis_export);
scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
out_agt.ap.connect(agt_scb_fifo.analysis_export);
scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction
agent.sv
1. build_phase中判断参数创建组件,以及连接fifo的port。
2. connect_phase中将指针指向monitor的port,实质上是monitor 向fifo写数据;再将driver和sequencer的port连接
class my_agent extends uvm_agent;
my_sequencer sqr;
driver_dut drv;
my_monitor mon;
uvm_analysis_port #(transaction_dut) ap;
extern function new (string name, uvm_component parent);
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 my_agent::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if(is_active == UVM_ACTIVE) begin
sqr = my_sequencer::type_id::create("sqr", this);
drv = driver_dut::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);
if(is_active == UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
ap = mon.ap;
endfunction
model.sv
1. build中实例化连接fifo的两侧端口
2. main_phase中将接收到的数据复制发送
class my_model extends uvm_component;
uvm_blocking_get_port #(transaction_dut) port;
uvm_analysis_port #(transaction_dut) ap;
extern function new(string name = "my_model", uvm_component parent = null);
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 = "my_model", uvm_component parent = null);
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);
transaction_dut tr;
transaction_dut 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
scoreboard.sv
1. build_phase中创建连接两个fifo的接口
2. main_phase中通过队列缓存来自in_agent的数据,得到来自DUT的数据后,比较后打印结果(缓存来自in_agent的数据是因为来自DUT的数据有时钟的概念,迟于来自in_agent的数据)
class my_scoreboard extends uvm_scoreboard;
transaction_dut expect_queue[$];
uvm_blocking_get_port #(transaction_dut) exp_port;
uvm_blocking_get_port #(transaction_dut) act_port;
`uvm_component_utils(my_scoreboard)
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(phase);
exp_port = new("exp_port", this);
act_port = new("act_port", this);
endfunction
task my_scoreboard::main_phase(uvm_phase phase);
transaction_dut get_expect, 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.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.print();
$display("the actual pkt is");
get_actual.print();
end
end
else begin
`uvm_error("my_scoreboard", "Received from DUT, while Expect Queue is empty")
$display("the unexpected pkt is");
get_actual.print();
end
end
join
endtask
driver_dut.sv
1. build_phase中config_db虚接口
2. main_phase中,把请求到的tranaction通过tr.pack_bytes任务打包成DUT需要的数据,最后调用seq_item_port.item_done()任务,告诉sequence这次传输完成,即`uvm_do宏的一次完成。
class driver_dut extends uvm_driver#(transaction_dut);
virtual interface_dut vif;
`uvm_component_utils(driver_dut)
extern function new(string name, uvm_component parent);
extern function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
extern task drive_one_pkt(transaction_dut tr);
endclass
function driver_dut::new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void driver_dut::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual interface_dut)::get(this, "", "vif", vif))
`uvm_fatal("driver_dut", "virtual interface must be set for vif!!!")
endfunction
task driver_dut::main_phase(uvm_phase phase);
vif.data <= 8'd0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
task driver_dut::drive_one_pkt(transaction_dut tr);
byte unsigned data_q[];
int data_size;
data_size = tr.pack_bytes(data_q) / 8;
`uvm_info("driver_dut", "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("driver_dut", "end drive one pkt", UVM_LOW)
endtask
driver_dut.sv
1. build_phase中实例化port,并config虚接口
2. main_phase中收集数据并转化为transaction通过端口广播出去。
class my_monitor extends uvm_monitor;
virtual interface_dut vif;
uvm_analysis_port#(transaction_dut) ap;
`uvm_component_utils(my_monitor);
function new (string name = "my_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
extern task collect_one_pkt(transaction_dut tr);
endclass
function void my_monitor::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual interface_dut)::get(this, "", "vif", vif))
`uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
ap = new("ap", this);
endfunction
task my_monitor::main_phase(uvm_phase phase);
transaction_dut tr;
while(1) begin
tr = new("tr");
collect_one_pkt(tr);
ap.write(tr);
end
endtask
task my_monitor::collect_one_pkt(transaction_dut tr);
byte unsigned data_q[$];
byte unsigned data_array[];
logic [7 : 0] data;
logic valid = 0;
int data_size;
while(1) begin
@(posedge vif.clk);
if(vif.valid) break;
end
`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];//-18 byte
data_size = tr.unpack_bytes(data_array) / 8;
`uvm_info("my_monitor", "end collect one pkt", UVM_LOW)
endtask
sequencer.sv
class my_sequencer extends uvm_sequencer#(transaction_dut);
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
`uvm_component_utils(my_sequencer)
endclass