目录
APB_Timer模块解读
APB_Timer定时器cmsdk_apb_timer.v是一个32位的下行计数器,具有以下能力:
1.当计数器达到0时,你可以生成一个中断请求信号TIMERINT。中断请求被保持,直到它被写入INTCLEAR寄存器。
2.你可以使用外部输入信号的0到1转换,EXTIN,作为定时器启用
3.如果APB定时器计数达到0,同时软件清除之前的一个中断状态,则中断状态设置为1
4.外部时钟EXTIN必须慢于外部时钟的一半,因为它由双触发器采样,然后在外部输入作为时钟时通过边缘检测逻辑。参见4-6页的程序员模型。
5.一个单独的时钟引脚,PCLKG,用于APB寄存器的读或写逻辑,当没有APB活动时,允许时钟到外设寄存器逻辑停止。(应该再注意一下时钟的使用,对CTRL和RELOAD寄存器的写入用的时钟是PCLKG,然后对寄存器读使用的时钟也是PCLKG。除此之外都用的是PCLK,好处是当没有提供PCLK信号,也就是计数器没法工作时,仍然可以配置寄存器和读取寄存器信息,将计数器的功能与配置寄存器的操作解耦,使可以先配置寄存器,然后提供PCLK信号控制计数器。)
而APB_Timer的几大特性是:生成中断,中断清除,引入外部控制信号和外部时钟等特性。
首先是寄存器信息,RELOAD寄存器和Current VALUE寄存器和一个各个位对应不同控制信号的CTRL控制寄存器,一个中断寄存器。
APB协议
APB主要用于低带宽的周边外设之间的连接,例如UART、1284等,它的总线架构不像AHB支持多个主模块,在APB里面唯一的主模块就是APB 桥。其特性包括:两个时钟周期传输;无需等待周期和回应信号;控制逻辑简单,只有四个控制信号。在APB_Timer中:
1、PCLK(总线时钟);
2、PRESETn(APB复位);
3、PSEL(选择从设备);
4、PADDR/PWRITE/PWDATA/PRDATA(地址和控制信号,读写数据);PREADY/PENABLE(APB 传输控制);PSLVERR(PSLVERR是表示当前传输的数据是否有误)。
AHB_Timer验证结构图
tb中主要包括APB_Timer模块的例化、模块与验证平台接口的连接,config_db机制将ahb_interface传递于env的agent里,将ahbram_interface传递到base_test层里面例化的config里。
在base_test中例化了enviroment, config,与寄存器模型,在顶层test中配置config组件,通过config_db机制将config组件set到底层的env组件中,config组件中的rgm句柄指向寄存器模型实例,这样底层的组件通过调用cfg.rgm就可以索引到寄存器模型,方便后期代码修改。
验证环境env中例化了ahb_mst_agent, virtual_sequencer, scorboard, coverage, adapter和predictor, ahb_mst_agent包括driver, sequencer, monitor,同時將monitor的uvm_analysis_port连接到scoreboard和coverage_model的uvm_analysis_imp中。
测试序列
(1)base_virtual_sequence :
定义了compare()一些基本方法,其他的virtual_sequence继承于base_virtual_sequence;
(2)integration_virt_seq:写入数据在读取数据,比较;
(3) counter_boundary_and_recount_virt_seq:
1.32bit的边界检查;2.一旦重新把数写入VALUE寄存器中,计数器会重新计时;3.RELOAD 寄存器写入数据,等到计数为0的时候,才开始下一次计时;
(4)reload_and_interrupt_virt_seq:检验interrupt拉起后是否能够被清除;
(5)external_source_as_clock_virt_seq/external_source_as_enable_virt_seq;外部信号和时钟能否控制计时。
覆盖率收集
定义了6个covergroup,分别关心”CTRL”的四个小寄存器域,“RELOAD”,”INSTSTAT”采样,对中断信号timerint以及计数的数据counter采样。
覆盖率收集
定义6个covergroup,分别关心RELOAD寄存器、VALUE寄存器、CTRL控制寄存器,中断信号,计数:
使用shell脚本,固定种子seed,随机种子跑完以上测试,可以得到合并后的代码覆盖率情况如下:
Shell 脚本:
分析覆盖率收集情况:
功能覆盖率:
CTRL.ENABLE翻转没覆盖,
reloadd的边界值没覆盖,
代码覆盖率:PRDATA的翻转,PREADY,PSLVERR
添加新的测试用例:
class rkv_timer_start_end_virt_seq extends rkv_timer_base_virtual_sequence;
`uvm_object_utils(rkv_timer_start_end_virt_seq)
function new (string name = "rkv_timer_base_virtual_sequence");
super.new(name);
endfunction
task body();
super.body();
`uvm_info("SEQ", "test g continue to take count once reached 0", UVM_MEDIUM)
rgm.CTRL.mirror(status);
// enable timer
rgm.CTRL.ENABLE.set(1);
rgm.CTRL.update(status);
//enable 0
rgm.CTRL.ENABLE.set(0);
rgm.CTRL.update(status);
// enable timer
rgm.CTRL.ENABLE.set(1);
rgm.CTRL.update(status);
// enable interrupt CTRL[3] = 1
rgm.CTRL.INTREN.set(1);
rgm.CTRL.update(status);
// enable interrupt CTRL[3] = 0
rgm.CTRL.INTREN.set(0);
rgm.CTRL.update(status);
// enable interrupt CTRL[3] = 1
rgm.CTRL.INTREN.set(1);
rgm.CTRL.update(status);
// set reload count
wr_val = 32'hFFFF_FFFF;
rgm.RELOAD.write(status, wr_val);
rgm.RELOAD.read(status, wr_val);
wait_apb_cycles(100);
wr_val = 32'h0000_0000;
rgm.RELOAD.write(status, wr_val);
rgm.RELOAD.read(status, wr_val);
wait_apb_cycles(100);
rgm.INTSTAT.read(status, rd_val);
// clear interrupt
rgm.INTSTAT.write(status, 1'b1);
`uvm_info("SEQ", "Clear the interrupt status register", UVM_MEDIUM)
// check interrupt status reg and pin
rgm.INTSTAT.read(status, rd_val);
endtask
endclass
加入新的定向测试后的覆盖率:
功能覆盖率:CTRL.ENABLE翻转覆盖,RELOAD边界值覆盖,
代码覆盖率:PRDATA的翻转已覆盖
覆盖率分析:
功能覆盖率分析:APB_timer Single传输,功能覆盖率部分可以exclude
断言覆盖率分析:APB_timer Single传输,覆盖率部分可以exclude
代码覆盖率:PREADY为1,PSLVERR是表示当前传输的数据是否有误,代码覆盖率部分也可先exclude:
最终覆盖率:
项目难点
验证的难点是要保证验证的完备性和正确性;
完备性是指设计规格中的所有功能覆盖率都必须是100%,这个就需要熟悉设计spec与RTL代码制定相应的验证计划与测试案例,并且要保证功能点划分清晰;所以你的covermodel怎么去正确的收集数据就很重要,在timer中,covermodel要收集counter的数据,在covergroup_new (vif.countert) 的时候收集vif.counter的数据,但是问题是,function_new还没有拿到vif,所以会报错。
比如验证的正确性是指测试案例能否正确反映RTL的功能是否正常,它要通过波形检查、模型比较的方式。在timer中,subscriber中通过monitor的监听传递了transaction tr,利用rgm.map.get_reg_by_offset(tr.addr)得到了寄存器的信息,在task中的do_event_trigger()中case语句获知哪一个寄存器被拉起,通过uvm_event把信息传递给scoreboard后,获知dut的信息,比如counter(随时计数的信息),我们会把它和vif.counter比较,用diff = counter >= vif.counter ? counter - vif.counter : vif.counter - counter;因为我们用的是foever begin,在开始我们将reload value的值写入dut的时候,此时monitor会监测reload的值,发给scoreboard, scoreboard的counter的值会马上更新,dut的值(vif.counter的值)的更新会比scoreboard的值慢,diff就会很大,所以 if(!(diff <= cfg.counter_diff_max || (reload - diff) <= cfg.counter_diff_max)),保证开始的时候是正确的;
2.rkv_timer_external_source_as_enable_virt_seq
如果extin为0,scoreboard的counter一直在计数,而实际上硬件的counter并没有计数:
比较报错:
修改subscriber和scoreboard:
subscriber中,添加两个uvm_evevt,
在scoreboard中添加变量count_enable控制计数的开始, 运行rkv_timer_external_source_as_enable_test, extin_as_enable=1,一开始scoreboard的count_enable=1, 如果vif.extin=0,所以count_enable=0,do_counting不计数;如果vif.extin=1,count_enable=1,do_counting开始计数;
如果运行其他test,extin_as_enable=0,vif.extin=0,所以count_enable=1,开始计数,
scoreboard中: