上一章已经建立了一个完整的agent,后续会添加scoreboard,那么scoreboard的对比数据来源何处? 答案是来源于读agent中monitor的输出 和 写agent中的monitor输出,对比这两个数是否相同。
那么还需不需要再建立一个新的agent呢,答案是否定的,我们可以直接例化同一个agent但是取不同名字就行了,这也是UVM的一个非常方便的功能。
这里借用一下这篇文章中的平台图,write_agent read_agent都是从同一个agent扩展出来的
异步FIFO的UVM验证(VCS+Verdi 附源代码)_异步fifo的验证代码-CSDN博客
那么如果是从同一个agent扩展出来的,他又如何判断是读还是写呢?
首先在不同的agent中会配置一个cmd参数,cmd=1就是写,cmd=0就是读,其次我们会在agent所有组件中都加入判断语句,cmd=1的话...... ;cmd=0的话执行另一条语句。两个agent并行执行。
1.env的配置文件中也例化了两个agent的配置参数
class my_env extends uvm_env;
`uvm_component_utils(my_env)
master_agent m_agent;
master_agent r_agent;
asyn_fifo_env_config m_env_cfg;
function new (string name="" ,uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_agent=master_agent::type_id::create("m_agent",this);
r_agent=master_agent::type_id::create("r_agent",this);
if(!uvm_config_db#(asyn_fifo_env_config)::get(this,"","asyn_fifo_env_config",m_env_cfg)) begin
`uvm_fatal("CONFIG_FATAL","ENV can not get the configuration")
end
uvm_config_db#(fifo_agent_config)::set(this,"m_agent*","fifo_agent_config",m_env_cfg.m_wf_cfg);
uvm_config_db#(fifo_agent_config)::set(this,"r_agent*","fifo_agent_config",m_env_cfg.m_rf_cfg);
/* if(m_env_cfg.is_coverage) begin
`uvm_info("COVERAGE_ENABLE","The function coverage is enable for this testcase",UVM_MEDIUM)
end
if(m_env_cfg.is_check) begin
`uvm_info("CHECK_ENABLE","The check function is enable for this testcase",UVM_MEDIUM)
end*/
endfunction
//virtual function void connect_phase(uvm_phase phase)
endclass
2.agent的配置参数中还是例化了interface,在class中例化interface需要加virtual,在module中才可以不加。可参考这篇文章中提到的interface的知识
interface、virtual interface 与 config_db之间的关系?_interface和virtual interface-CSDN博客
class master_agent extends uvm_agent;
`uvm_component_utils(master_agent)
my_sequencer m_seqr;
my_driver m_driv;
my_monitor m_moni;
fifo_agent_config m_cfg;
uvm_analysis_port #(my_transaction) ap;
function new(string name="",uvm_component parent);
super.new(name,parent);
ap=new("ap",this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(fifo_agent_config)::get(this,"","fifo_agent_config",m_cfg)) begin
`uvm_fatal("CONFIG_FATAL","master_agent can not get the configuration")
end
uvm_config_db#(virtual dut_interface)::set(this,"m_driv","vif",m_cfg.m_vif);
uvm_config_db#(virtual dut_interface)::set(this,"m_moni","vif",m_cfg.m_vif);
if(m_cfg.is_active==UVM_ACTIVE) begin
m_seqr=my_sequencer::type_id::create("m_seqr",this);
m_driv=my_driver::type_id::create("m_driv",this);
end
m_moni=my_monitor::type_id::create("m_moni",this);
endfunction
virtual function void connect_phase(uvm_phase phase);
if(is_active==UVM_ACTIVE)
m_driv.seq_item_port.connect(m_seqr.seq_item_export);
ap=m_moni.ap;
endfunction
endclass
3.top层包含了所有用到的子文件,并启动test,在这里我们还通过sequence配置了读写时钟,分别通过读写agent中的m_sequencer 启动相应的时钟配置sequence,来对顶层文件进行配置。
`include "./rtl/defines.v"
`include "./rtl/dual_port_dram.v"
`include "./rtl/ASFIFO.v"
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "fifo_agent_config.sv"
`include "asyn_fifo_env_config.sv"
`include "my_transaction.sv"
`include "asyn_fifo_seq_base.sv"
`include "rclk_config_seq.sv"
`include "wclk_config_seq.sv"
`include "wrstn_seq.sv"
`include "rdstn_seq.sv"
`include "read_seq.sv"
`include "write_seq.sv"
`include "dut_interface.sv"
`include "my_sequencer.sv"
//`include "my_sequence.sv"
`include "asyn_fifo_vseq_base.sv"
`include "write_fast_vseq.sv"
`include "write_with_reset_vseq.sv"
`include "my_driver.sv"
`include "my_monitor.sv"
`include "master_agent.sv"
`include "my_env.sv"
`include "asyn_fifo_test_base.sv"
`include "write_fast_test.sv"
`include "write_with_reset_test.sv"
module top_tb;
parameter WIDTH = 16;
parameter PTR = 4 ;
int wclk_half_period;
int rclk_half_period;
bit wrclk_sys,rdclk_sys;
dut_interface inf(wrclk_sys,rdclk_sys);
ASFIFO
uASFIFO
(
.wrclk(inf.wrclk),
.rdclk(inf.rdclk),
.rd_rst_n(inf.rd_rst_n),
.wr_rst_n(inf.wr_rst_n),
.wr_en(inf.wr_en),
.rd_en(inf.rd_en),
.wr_data(inf.wr_data),
.rd_data(inf.rd_data),
.wr_full(inf.wr_full),
.rd_empty(inf.rd_empty)
);
initial begin
wrclk_sys = 0;
#10
if(uvm_config_db#(int)::get(uvm_root::get(),"uvm_test_top.m_env.m_agent.m_seqr","wclk_half_period",wclk_half_period))
`uvm_info("WCLK",$sformatf("Configure the wclk_half_period = [%0d]",wclk_half_period),UVM_NONE)
else begin
`uvm_info("WCLK","Can't configure wclk with config_db correctly,will use default value:10",UVM_MEDIUM)
wclk_half_period = 10;
end
forever begin
#wclk_half_period wrclk_sys= ~wrclk_sys;
end
end
initial begin
rdclk_sys = 0;
#10
if(uvm_config_db#(int)::get(uvm_root::get(),"uvm_test_top.m_env.r_agent.m_seqr","rclk_half_period",rclk_half_period))
`uvm_info("RCLK",$sformatf("Configure the rclk_half_period = [%0d]",rclk_half_period),UVM_NONE)
else begin
`uvm_info("RCLK","Can't configure rclk with config_db correctly,will use default value:10",UVM_MEDIUM)
rclk_half_period = 10;
end
forever begin
#rclk_half_period rdclk_sys = ~rdclk_sys;
end
end
initial begin
uvm_config_db#(virtual dut_interface)::set(null,"uvm_test_top","w_top_if",inf);
uvm_config_db#(virtual dut_interface)::set(null,"uvm_test_top","r_top_if",inf);
run_test("write_fast_test");
end
initial begin
#5000;
$finish;
end
endmodule
4.时钟的配置文件如下:分别为写时钟和读时钟。分别在body任务中配置了半周期的数值,通过sequencer来传递。那么如何启动这些sequence,又如何配置到相关agent中呢。我们在面对多高sequence并且需要配置到不同agent时,需要用到virtual sequence机制,他来控制目标agent和启动不同sequence的先后顺序。
该图出自B站的UVM基础视频
在virtual sequence中启动方式如下,使用了`uvm_do_on_with语句,on指定哪一个agent的sequencer,with指定了参数的约束。在这里我们使用了virtual sequence中自定义的时钟周期,就没有用读写时钟sequence中的rand值,如果想产生随机时钟的同学,可以把with部分的参数删掉。
class wclk_config_seq extends uvm_sequence;
`uvm_object_utils(wclk_config_seq)
rand int wclk_half_period;
extern function new(string name = "wclk_config_seq");
extern task body();
endclass : wclk_config_seq
function wclk_config_seq::new(string name = "wclk_config_seq");
super.new(name);
endfunction : new
task wclk_config_seq::body();
uvm_config_db#(int)::set(m_sequencer,"","wclk_half_period",wclk_half_period);
`uvm_info("WCLK",$sformatf("Configure the wclk half period = [%0d]",wclk_half_period),UVM_MEDIUM)
endtask : body
class rclk_config_seq extends uvm_sequence;
`uvm_object_utils(rclk_config_seq)
rand int rclk_half_period;
extern function new(string name = "rclk_config_seq");
extern task body();
endclass : rclk_config_seq
function rclk_config_seq::new(string name = "rclk_config_seq");
super.new(name);
endfunction : new
task rclk_config_seq::body();
uvm_config_db#(int)::set(m_sequencer,"","rclk_half_period",rclk_half_period);
`uvm_info("rclk",$sformatf("Configure the rclk half period = [%0d]",rclk_half_period),UVM_MEDIUM)
endtask : body
5. 接下来就是testcase,他负责启动virtual sequence,在这里我没有采用之前default sequence的自动启动方法啊,而是采用的手动启动sequence,启动的是virtual sequence。
`ifndef WRITE_FAST_TEST_SV
`define WRITE_FAST_TEST_SV
class write_fast_test extends asyn_fifo_test_base;
`uvm_component_utils(write_fast_test)
extern function new(string name = "write_fast_test",uvm_component parent = null);
extern task run_phase(uvm_phase phase);
extern function void build_phase(uvm_phase phase);
endclass : write_fast_test
function write_fast_test::new(string name = "write_fast_test",uvm_component parent = null);
super.new(name,parent);
endfunction : new
function void write_fast_test::build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction : build_phase
task write_fast_test::run_phase(uvm_phase phase);
write_fast_vseq vseq = write_fast_vseq::type_id::create("vseq");
phase.raise_objection(this);
init_vseq(vseq);//asyn_fifo_test_base
vseq.start(null);
phase.drop_objection(this);
endtask : run_phase
`endif // write_fast_test_SV
6.my_driver是本代码的核心,负责把transaction中的值赋给DUT能收到的PIN级信号
class my_driver extends uvm_driver#(my_transaction);
`uvm_component_utils(my_driver)
virtual dut_interface m_vif;
fifo_agent_config cfg;
bit has_trans;
bit rd_has_reset,wr_has_reset;
bit req_status;
bit [`WIDTH-1:0] data_t;
//int unsigned pad_cycles;
function new (string name="my_driver",uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
//cfg=fifo_agent_config::type_id::create("cfg");
if(!uvm_config_db#(virtual dut_interface)::get(this,"","vif",m_vif)) begin
`uvm_fatal("CONFIG_FATAL","driver can not get the interface")
end
if(!uvm_config_db#(fifo_agent_config)::get(this,"","fifo_agent_config",cfg))
`uvm_fatal(get_type_name(),"Can't get the fifo_agent_config!")
endfunction
virtual task configure_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("DRV_CONFIGURE_PHASE", "Now driver config the DUT...", UVM_MEDIUM)
phase.drop_objection(this);
endtask
//`uvm_register_cb(my_driver,driver_base_callback)
virtual task reset_wr_rd();
forever begin:reset
if(cfg.cmd) begin//write
@(negedge m_vif.wr_rst_n);
m_vif.drv_wr_cb.wr_en<=0;
m_vif.drv_wr_cb.wr_data<=0;
wr_has_reset=0;
// `uvm_info("wr_has_reset",$sformatf("wr_has_reset=%0d",wr_has_reset),UVM_NONE)
end
else begin
@(negedge m_vif.rd_rst_n);
m_vif.rd_en<=0;
rd_has_reset=0;
end
end
endtask
virtual task reset_flag();
forever begin:reset_flag
if(cfg.cmd) begin//write
@(posedge m_vif.wr_rst_n);
wr_has_reset=1;
// $display("wr_has_reset=%0d",wr_has_reset);
end
else begin
@(posedge m_vif.rd_rst_n);
rd_has_reset=1;
//`uvm_info("RESET_FLAG",$sformatf("rd_has_reset=%0d",rd_has_reset),UVM_NONE)
end
end
endtask
virtual task check_req();
forever begin
if(cfg.cmd) begin
@(negedge m_vif.wrclk);
if(req_status==0) begin
@(posedge m_vif.wrclk);
#1;
m_vif.drv_wr_cb.wr_en <= (req_status==0)?0:1;
end
end
else begin
@(negedge m_vif.rdclk);
if(req_status==0) begin
@(posedge m_vif.rdclk);
#1;
m_vif.drv_rd_cb.rd_en <= (req_status==0)?0:1;
end
end
end
endtask
virtual task get_and_drive();
seq_item_port.get_next_item(req);//transaction request,blocking until get
`uvm_info("DRV_RUN_PHASE", {"\n",req.sprint()}, UVM_MEDIUM)
req_status=1;
if(cfg.cmd) begin
if(!m_vif.wr_full && wr_has_reset &&req_status) begin //if write
@(posedge m_vif.wrclk);
m_vif.drv_wr_cb.wr_en <= 1;
m_vif.drv_wr_cb.wr_data <= req.data;
end
@(m_vif.drv_wr_cb);
/* else if(!m_vif.wr_full && !wr_has_reset) begin //if write
@(posedge m_vif.wrclk);
m_vif.drv_wr_cb.wr_en <= 0;
m_vif.drv_wr_cb.wr_data <= 0;
$display("wr_has_reset=%0d",wr_has_reset);
end*/
end
else begin
if(!m_vif.rd_empty&& rd_has_reset &&req_status) begin
@(posedge m_vif.rdclk);
m_vif.drv_rd_cb.rd_en <= 1;
`uvm_info("RESET_FLAG",$sformatf("rd_has_reset=%0d",rd_has_reset),UVM_NONE)
end
@(m_vif.drv_rd_cb);
end
req_status=0;
seq_item_port.item_done();
endtask : get_and_drive
virtual task write_one_pkt(my_transaction req);
@(posedge m_vif.wrclk);
m_vif.drv_wr_cb.wr_en <= 1;
m_vif.drv_wr_cb.wr_data <= req.data;
repeat(req.pkt_idles)
drive_idle(req.cmd);
endtask : write_one_pkt
virtual task read_one_pkt(my_transaction req);
@(posedge m_vif.rdclk);
m_vif.drv_rd_cb.rd_en <= 1;
repeat(req.pkt_idles)
drive_idle(req.cmd);
endtask : read_one_pkt
virtual task run_phase(uvm_phase phase);
my_transaction req;
m_vif.wr_data <= '0;
m_vif.wr_en <= 0;
m_vif.rd_en <= 0;
fork
reset_wr_rd();
reset_flag();
//repeat(300) begin
check_req();
//end
forever begin
get_and_drive();
end
//check_req();
join_none
endtask : run_phase
virtual task write_dile();
forever begin
@(posedge m_vif.wrclk);
if(has_trans & cfg.cmd) m_vif.drv_wr_cb.wr_en <= 0;
end
endtask : write_dile
virtual task read_dile();
forever begin
@(posedge m_vif.rdclk);
if(!has_trans & (!cfg.cmd)) m_vif.drv_rd_cb.rd_en <= 0;
end
endtask : read_dile
virtual task drive_idle(bit cmd);
if(cmd) begin
@(m_vif.drv_wr_cb);
m_vif.wr_data <= '0;
m_vif.wr_en <= 0;
end
else begin
@(m_vif.drv_rd_cb);
m_vif.rd_en <= 0;
end
endtask : drive_idle
endclass
7.写sequence,cmd=1即可
typedef class my_transaction;
class write_seq extends uvm_sequence #(my_transaction);
`uvm_object_utils(write_seq)
rand int pkt_idles;
constraint cstr{
soft pkt_idles==0;
}
function new (string name ="write_seq");
super.new(name);
endfunction:new
task body();
my_transaction req;
`uvm_do_with(req,{cmd == 1;pkt_idles == local::pkt_idles;})
endtask:body
endclass:write_seq
8.读sequence
class read_seq extends uvm_sequence#(my_transaction);
`uvm_object_utils(read_seq)
function new (string name ="write_seq");
super.new(name);
endfunction
virtual task body();
my_transaction req;
`uvm_do_with(req,{cmd == 0;})
endtask
endclass
7.virtual sequence的配置,其中包含了时钟配置的启动和读写sequence的启动,他们共同构成了这个virtual sequence
// ---------------------------------------------------------------------------------
// Copyright (c) 2022
// ALL RIGHTS RESERVED
// ---------------------------------------------------------------------------------
// Filename : write_fast_vseq.sv
// Author : AiF
// Created On : 2022-05-19 11:14
// Last Modified : 2022-05-19 16:53
// ---------------------------------------------------------------------------------
// Description :
//
//
// ---------------------------------------------------------------------------------
`ifndef WRITE_FAST_VSEQ_SV
`define WRITE_FAST_VSEQ_SV
typedef class write_seq;
class write_fast_vseq extends asyn_fifo_vseq_base;
`uvm_object_utils(write_fast_vseq)
int wclk_half_period = 10;
int factor = 2;
extern function new(string name = "write_fast_vseq");
extern task body();
endclass : write_fast_vseq
function write_fast_vseq::new(string name = "write_fast_vseq");
super.new(name);
endfunction : new
task write_fast_vseq::body();
write_seq wr_sq;
read_seq rd_sq;
// wrstn_config_seq wrstn_cfg_seq;
// rrstn_config_seq rrstn_cfg_seq;
wclk_config_seq wclk_cfg_seq;
rclk_config_seq rclk_cfg_seq;
// my_sequence m_seq;
wrstn_seq wrreset_seq;
rdstn_seq rdreset_seq;
fork
// `uvm_do_on(wrstn_cfg_seq,wf_sqr)
// `uvm_do_on(rrstn_cfg_seq,rf_sqr)
//`uvm_do_on(m_seq,wf_sqr)
// `uvm_do_on(m_seq,rf_sqr)
`uvm_do_on_with(wclk_cfg_seq,wf_sqr,{wclk_half_period == local::wclk_half_period;})
`uvm_do_on_with(rclk_cfg_seq,rf_sqr,{rclk_half_period == local::wclk_half_period*factor; })
`uvm_do_on(wrreset_seq,wf_sqr)
`uvm_do_on(rdreset_seq,rf_sqr)
join_none
repeat(5)
`uvm_do_on(wr_sq,wf_sqr)
#50
repeat(5)
`uvm_do_on(rd_sq,rf_sqr)
#100
fork
repeat(100)
`uvm_do_on_with(wr_sq,wf_sqr,{pkt_idles == 0;})
repeat(100)
`uvm_do_on(rd_sq,rf_sqr)
join
#500;
endtask : body
`endif // write_fast_VSEQ_SV
时钟配置
结构树,可以看见有两个从masteragent扩展出来的agent
monitor和sequencer、sequence等等不变
这也是我做验证的中间产物,我都是一步一步添加功能的,可能这里面在功能上有一些bug,但是在最后一版中我会对功能做检查,确保没有问题