7.1 搭建事件触发器框架
事件触发器subscriber的目的就是在agent_monitor一侧监听数据,无论是scoreboard检查器还是覆盖率收集都需要基于一些事件的触发来进行功能比较与覆盖率收集。因此比较方便的做法是定义一个subscriber来完成事件的定义与一些事件的触发,而scoreboard与coverage模块继承于此父类,从而可以使用其中的事件与方法。 :::info
- 在env下创建scoreboard;
- 添加event:
- 关键的event触发送到covergroup;
- 送到scoreboard做数据检查的关键实现。
- 在env中新建apb_watchdog_subscriber:
- 从monitor捕捉信息(事件)进行分析;
在cov下创建apb_watchdog_cov.sv文件并包含在.svh文件中 :::
``verilog
ifndef APB_WATCHDOG_COV_SVH `define APB_WATCHDOG_COV_SVH`include “apb_watchdog_cov.sv”
`endif // APB_WATCHDOG_COV_SVH
:::info
5. 搭建subscriber的结构:
1. 在subscriber中声明端口的名称并定义;
1. 在subscriber中导入可能用到的一些变量(声明成句柄config,RGM,vif);
1. 由于在subscriber中声明了一个import,所以必须在sunscriber中实现对应的方法:write_apb()
1. 在build_phase中例化端口,getconfig,连接句柄;
:::
```verilog
`ifndef APB_WATCHDOG_SUBSCRIBER_SV
`define APB_WATCHDOG_SUBSCRIBER_SV
`uvm_analysis_import_decl(_apb)
class apb_watchdog_subscriber extends uvm_component;
// analysis import
uvm_analysis_import_apb #(apb_transfer, apb_watchdog_subscriber) apb_trans_observed_imp;
apb_watchdog_config cfg;
apb_watchdog_rgm rgm;
virtual apb_watchdog_if vif;
`uvm_component_utils(apb_watchdog_subscriber)
function new(string name = "apb_watchdog_subscriber", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
apb_trans_observed_imp = new("apb_trans_observed_imp", this)
if(!uvm_config_db#(apb_watchdog_config)::get(this, "", "cfg", cfg)) begin
`uvm_fatal("GETCFG", "cannot get conifg object from config db")
end
rgm = cfg.rgm;
vif = cfg.vif;
endfunction
endclass
`endif // APB_WATCHDOG_subscriber_SV
:::info
- 搭建scoreboard的框架:
:::
``verilog
ifndef APB_WATCHDOG_SCOREBOARD_SV `define APB_WATCHDOG_SCOREBOARD_SV
class apb_watchdog_scoreboard extends apb_watchdog_subscriber;
`uvm_component_utils(apb_watchdog_scoreboard)
function new(string name = “apb_watchdog_scoreboard”, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
endfunction endclass
`endif // APB_WATCHDOG_SCOREBOARD_SV
:::info
7. 在env中添加声明的组件并在build_phase中例化
1. 声明scoreboard和cov并例化;
1. 在build_phase中将config向下面的cov和scb中都进行传递;
1. 在connect_phase中将apb_master_monitor的端口和scb以及cov中的端口进行连接。
:::
```verilog
`ifndef APB_WATCHDOG_ENV_SV
`define APB_WATCHDOG_ENV_SV
class apb_watchdog_env extends uvm_env;
apb_master_agent apb_mst;
apb_watchdog_config cfg;
apb_watchdog_virtual_sequencer virt_sqr;
apb_watchdog_rgm rgm;
apb_watchdog_adapter adapter;
apb_watchdog_scoreboard scb;
apb_watchdog_cov cov;
uvm_reg_predictor #(apb_transfer) predictor;
`uvm_component_utils(apb_watchdog_env)
function new (string name = "apb_watchdog_env", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Get configuration from test layer
if(!uvm_config_db#(apb_watchdog_config)::get(this,"","cfg", cfg)) begin
`uvm_fatal("GETCFG","cannot get config object from config DB")
end
uvm_config_db#(apb_watchdog_config)::set(this, "virt_sqr", "cfg", cfg);
uvm_config_db#(apb_watchdog_config)::set(this, "scb", "cfg", cfg);
uvm_config_db#(apb_watchdog_config)::set(this, "cov", "cfg", cfg);
uvm_config_db#(apb_config)::set(this, "apb_mst", "cfg", cfg.apb_cfg);
apb_mst = apb_master_agent::type_id::create("apb_mst", this);
virt_sqr = apb_watchdog_virtual_sequencer::type_id::create("virt_sqr", this);
if(!uvm_config_db#(apb_watchdog_rgm)::get(this,"","rgm", rgm)) begin
rgm = apb_watchdog_rgm::type_id::create("rgm", this);
rgm.build();
end
adapter = apb_watchdog_adapter::type_id::create("adapter");
predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("predictor", this);
scb = apb_watchdog_scoreboard::type_id::create("scb", this);
cov = apb_watchdog_cov::type_id::create("cov", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
virt_sqr.apb_mst_sqr = apb_mst.sequencer;
rgm.map.set_sequencer(apb_mst.sequencer, adapter);
apb_mst.monitor.item_collected_port.connect(predictor.bus_in);
predictor.map = rgm.map;
predictor.adapter = adapter;
apb_mst.monitor.item_collected_port.connect(scb.apb_trans_observed_imp);
apb_mst.monitor.item_collected_port.connect(cov.apb_trans_observed_imp);
endfunction
function void report_phase(uvm_phase phase);
string reports = "\n";
super.report_phase(phase);
reports = {reports, $sformatf("================================================= \n")};
reports = {reports, $sformatf("CURRENT TEST SUMMARY \n")};
reports = {reports, $sformatf("SEQUENCE CHECK COUNT : %0d \n", cfg.seq_check_count)};
reports = {reports, $sformatf("SEQUENCE CHECK ERROR : %0d \n", cfg.seq_check_error)};
reports = {reports, $sformatf("SCOREBOARD CHECK COUNT : %0d \n", cfg.scb_check_count)};
reports = {reports, $sformatf("SCOREBOARD CHECK COUNT : %0d \n", cfg.scb_check_error)};
reports = {reports, $sformatf("=================================================")};
`uvm_info("TEST_SUMMARY",reports, UVM_LOW);
endfunction
endclass
`endif
【调试及结果】
make elab
make run GUI=1 TESTNAME=apb_watchdog_countdown_test &
点击object选择uvm_components,可以看到顶层的环境中已经搭建好了cov和scb。
7.2 添加计数检查器
7.2.1 在subscriber中添加事件
subscriber捕捉了来自apb_monitor中的信息:主要是访问了什么寄存器,做了哪些读取操作。 :::danger 【思考】
- 如何抓去event,抓取哪些event?
阅读功能描述文档,在event declare中将一些关键的事件进行抓取。
- 如何触发这些事件?
观察每次捕捉到的apb_transfer进行了哪些访问,读写的寄存器是什么,内容是什么? ::: :::info
- 对event进行声明并与资源池中的句柄连接;
- 编辑write_apb函数,将拿到的apb_transfer广播出去使得rgm.map能够查找到;
- 将拿到的寄存器采用前门访问的方式再次广播;
- 在subscriber中添加enble的使能信号,在scoreboard和coverage中分别将enable和cfg中的enable信号连接。
:::
``verilog
ifndef APB_WATCHDOG_SUBSCRIBER_SV `define APB_WATCHDOG_SUBSCRIBER_SV
`uvm_analysis_imp_decl(_apb)
class apb_watchdog_subscriber extends uvm_component;
// analysis import uvm_analysis_imp_apb #(apb_transfer, apb_watchdog_subscriber) apb_trans_observed_imp;
// Declare events uvm_event wdg_regacc_fd_e; uvm_event wdg_regacc_bd_e; uvm_event wdg_reg_inten_e; uvm_event wdg_reg_resen_e; uvm_event wdg_reg_load_e; uvm_event wdg_reg_intr_assert_e;
local uvm_event_pool _ep;
apb_watchdog_config cfg; apb_watchdog_rgm rgm; virtual apb_watchdog_if vif;
bit enable;
`uvm_component_utils(apb_watchdog_subscriber)
function new(string name = “apb_watchdog_subscriber”, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase); super.build_phase(phase); apb_trans_observed_imp = new(“apb_trans_observed_imp”, this); // Get configuration from test layer if(!uvm_config_db#(apb_watchdog_config)::get(this, “”, “cfg”, cfg)) begin `uvm_fatal(“GETCFG”, “cannot get conifg object from config db”) end vif = cfg.vif; rgm = cfg.rgm; // Local event pool and events creation _ep = new (“_ep”); wdg_regacc_fd_e = _ep.get(“wdg_regacc_fd_e”); wdg_regacc_bd_e = _ep.get(“wdg_regacc_bd_e”); wdg_reg_inten_e = _ep.get(“wdg_reg_inten_e”); wdg_reg_resen_e = _ep.get(“wdg_reg_resen_e”); wdg_reg_load_e = _ep.get(“wdg_reg_load_e”); wdg_reg_intr_assert_e = _ep.get(“wdg_reg_intr_assert_e”); endfunction
function void end_of_elaboration_phase(uvm_phase phase); super.end_of_elaboration_phase(phase); endfunction
task run_phase(uvm_phase phase); super.run_phase(phase); do_events_trigger(); endtask
virtual function void write_apb(apb_transfer tr); uvm_reg r; r = rgm.map.get_reg_by_offset(tr.addr); wdg_regacc_fd_e.trigger(r); endfunction
virtual task do_events_trigger(); uvm_object tmp; uvm_reg r; fork forever begin fork wdg_regacc_fd_e.wait_trigger_data(tmp); wdg_regacc_bd_e.wait_trigger_data(tmp); join_any disable fork; void’($cast(r, tmp));
#1ps;
if(r.get_name() == "WDOGCONTROL") begin
if(rgm.WDOGCONTROL.INTEN.get() == 1'b1) wdg_reg_inten_e.trigger();
if(rgm.WDOGCONTROL.RESEN.get() == 1'b1) wdg_reg_resen_e.trigger();
end
else if(r.get_name() == "WDOGLOAD") begin
if(rgm.WDOGLOAD.LOADVAL.get() != 0) wdg_reg_load_e.trigger();
end
end
join_none
endtask
endclass
`endif // APB_WATCHDOG_subscriber_SV
> - apb_mst.monitor的端口在监测到apb_transfer以后就会将这个事物广播出去,同时交给了predictor和subscriber。subscriber在拿到这样的一个事物后就会通过地址在RGM的map中查找对应的寄存器,查找到以后又会将这个事物再次广播出去。
> - 在下面的耗时语句task中会通过前门或后门的方式来等待事件,等到以后就会终止线程,进一步查找是对哪一个寄存器进行了操作,最后当确认到信号发生了变化以后就触发这个对应的事件。
> 将apb_transfer广播出去是一个函数,是非耗时的。如果想要构建耗时语句就需要再新建一个task。
:::danger
【思考】<br />为什么一定要一个采用一个耗时的task来完成等待事物的操作?<br />subscriber希望读取的数据是寄存器模型更新以后的数值。所以希望在一定时间以后来等待,这个时间就是1ps,因此这个时间是非常重要的。
:::
<a name="EZp5O"></a>
## 7.2.2 在scoreboard中递减计数测试功能
:::info
1. scoreboard中需要完成对计数的检查,因此声明do_countdown_check();
1. 根据功能描述文档,当计数递减为0以后,INT信号会拉高
:::
> 首先根据功能描述文档写出计数递减的功能,也就是INTEN拉高同时由数读进来;
> 将读进来的数据和当前的数据分别赋值给cur_load和cur_value;
> 在循环中实现:只要cur_value的值不为0就一直递减;
> 递减到0以后拉高INT信号,并判断如果INT信号没有被拉高,报错:需要将INT信号拉高。
```verilog
`ifndef APB_WATCHDOG_SCOREBOARD_SV
`define APB_WATCHDOG_SCOREBOARD_SV
class apb_watchdog_scoreboard extends apb_watchdog_subscriber;
`uvm_component_utils(apb_watchdog_scoreboard)
function new (string name = "apb_watchdog_scoreboard", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
enable = cfg.scb_enable;
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
do_countdown_check();
endtask
virtual task do_countdown_check();
bit [31:0] cur_load;
bit [31:0] cur_count;
fork
forever begin
fork
wdg_reg_inten_e.wait_trigger();
wdg_reg_load_e.wait_trigger();
join
@(posedge vif.wdg_clk);
cur_load = rgm.WDOGLOAD.LOADVAL.get();
cur_count = cur_load;
do begin
@(posedge vif.wdg_clk);
cur_count--;
end while(cur_count != 0);
wdg_reg_intr_assert_e.trigger();
// From logic timing after count reach zero
if(vif.wdog_int != 1'b1) begin
cfg.scb_check_error++;
`uvm_error("COUNTDOWN_CHECK", "Wdog interrupt signal should be asserted!")
end
cfg.scb_check_count++;
end
join_none
endtask
endclass
`endif // APB_WATCHDOG_SCOREBOARD_SV
在scoreboard中完成计数递减的功能检查:
- 等待INTEN和LOADEN都被触发;
- 将LOAD的值赋给两个变量;
- 在循环体重作递减计数的功能,递减到0以后将INT信号拉高;
【调试及结果】
当断点停在int_assert事件trigger的时候,定向测试一侧中寄存器的值正好减为0。
:::danger
【思考】
当计数递减到0以后,在多长时间内INT信号会被拉起呢?如何对这个时间进行检查?
根据波形来看是在下一个时钟信号的上升沿,INT信号会被拉起,但是不能在这个地方进行检查因为存在delta_cycle的问题,因此要等两个时钟信号的下降沿再进行检查。
:::
在int拉起的事件触发以后继续添加若干个时钟信号的下降沿,可以看到在计数到0以后的一拍后就将INT信号拉高了。