AHBRAM框架
1.AHB传输
两个阶段
- 地址阶段,只有一个cycle
- 数据周期,由HREADY信号决定需要几个cycle
流水线传送
- 先是地址周期,然后是数据周期
2.AHB协议信号
3.工程架构
测试平台具体用的是RKV前缀的,VIP_LIB里面是lvc的前缀
利用AHB master VIP搭建验证环境:先实现基础的AHB master VIP,然后利用AHB VIP搭建验证环境并完成冒烟测试以及多个功能测试用例
- rkv用的是rkv的接口,lvc用的当然是lvc的接口,如rkv_ahbram_base_virtual_sequence 用的接口是rkv_ahbram_if。当然他们不是相互独立的,有联系,是基于lvc的VIP,然后有RKV的一些功能,搭建出验证平台
- lvc_driver里面按照协议的AHB时序和类型等要求实现了数据的读、写功能
由下图可知,工程中的elem_seqs和sequence_lib之间的关系,即elem_seqs把sequence_lib拿来用
4.接口方向
时钟块的方向
1.interface接口中信号的方向
首先记住一点:我们的目标都是围绕着验证DUT,因此都是接口中的信号方向是相对于DUT而言的
【以AHBRAM这个DUT来分析】
注:monitor中接受所有信号,全部有关的信号都可认为是输入信号
接口的时钟块分析是相对于DUT而言来分析的: cb_mst的input是DUT的反馈--即slave对master的反馈;output是TB对DUT的输入--即master对slave的操作 cb_slv则相反
为什么用时钟块clocking?
答:clocking块基于时钟周期对信号进行驱动或者采样的方式,使用clocking采样会比直接用接口中的clk更加稳定,但是在组合逻辑中不能用clocking。
目前是RTL仿真,只需解决RTL时序。同步逻辑或者组合逻辑都会插入延迟,因为是器件决定的。在门级仿真时真实的物理时序。
2.时钟块的用法
建议:接口信号并不是一定要用非阻塞赋值。因为你用了时钟块,所以非阻塞或者阻塞都没事。
5.代码解析
5.1 rkv_ahbram_if
这个文件的作用
因为与dut和大部分组件连接的接口是lvc_ahb_if,所以rkv_ahbram_if接口的主要功能并不是连接。
在rkv_ahbram_if中定义了reset信号,作为硬件的驱动,reset信号放在这里可以方便后续测试的调用,可测试reset信号是否有效清空memory里面的数据
5.2 lvc_ahb_if
`ifndef LVC_AHB_IF_SV `define LVC_AHB_IF_SV interface lvc_ahb_if; `include "lvc_ahb_defines.svh" import lvc_ahb_pkg::*; //这两行导入是由于代码直接有联系,必须要用到 //比如`LVC_AHB_MAX_DATA_WIDTH定义在lvc_ahb_defines.svh //比如response_type_enum定义在lvc_ahb_types.sv,而lvc_ahb_types.sv定义在lvc_ahb_pkg logic hclk; logic hresetn; logic hgrant; logic [(`LVC_AHB_MAX_DATA_WIDTH - 1):0] hrdata; logic hready; logic [1:0] hresp; logic [(`LVC_AHB_MAX_ADDR_WIDTH - 1):0] haddr; logic [2:0] hburst; logic hbusreq; logic hlock; logic [3:0] hprot; logic [2:0] hsize; logic [1:0] htrans; logic [(`LVC_AHB_MAX_DATA_WIDTH - 1):0] hwdata; logic hwrite; response_type_enum debug_hresp; trans_type_enum debug_htrans; burst_size_enum debug_hsize; burst_type_enum debug_hburst; xact_type_enum debug_xact; status_enum debug_status; //特殊的类型转换语法:<target_type>'(<expression>) //效果例如debug_hresp最终得到OKAY这个枚举元素 // debug signals assignment assign debug_hresp = response_type_enum'(hresp); assign debug_htrans = trans_type_enum'(htrans); assign debug_hsize = burst_size_enum'(hsize); assign debug_hburst = burst_type_enum'(hburst); //为什么没有xact status,xact信号对应的是hwrite 可以通过monitor观察 //status在此时只赋值了一个初值INITIAL(在transaction中),也不需要发送的数据更新 // the below signals to be assigned by monitor // debug_xact .. // debug_status .. clocking cb_mst @(posedge hclk); // USER: Add clocking block detail default input #1ps output #1ps; output haddr, hburst, hbusreq, hlock, hprot, hsize, htrans, hwdata, hwrite; input hready, hgrant, hrdata; endclocking : cb_mst clocking cb_slv @(posedge hclk); // USER: Add clocking block detail default input #1ps output #1ps; input haddr, hburst, hbusreq, hlock, hprot, hsize, htrans, hwdata, hwrite; output hready, hgrant, hrdata; endclocking : cb_slv clocking cb_mon @(posedge hclk); // USER: Add clocking block detail default input #1ps output #1ps; input haddr, hburst, hbusreq, hlock, hprot, hsize, htrans, hwdata, hwrite; input hready, hgrant, hrdata; endclocking : cb_mon endinterface `endif // LVC_AHB_IF_SV
5.3 lvc_ahb_master_driver
`ifndef LVC_AHB_MASTER_DRIVER_SV `define LVC_AHB_MASTER_DRIVER_SV class lvc_ahb_master_driver extends lvc_ahb_driver; lvc_ahb_agent_configuration cfg; virtual lvc_ahb_if vif; `uvm_component_utils(lvc_ahb_master_driver) ...... virtual task drive_transfer(REQ t); // TODO implementation in child class case(t.burst_type) SINGLE: begin do_atomic_trans(t); end INCR : begin `uvm_error("TYPEERR", "burst type not supported yet") end WRAP4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end INCR4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end WRAP8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end INCR8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end WRAP16: begin `uvm_error("TYPEERR", "burst type not supported yet") end INCR16: begin `uvm_error("TYPEERR", "burst type not supported yet") end default: begin `uvm_error("TYPEERR", "burst type not defined") end endcase endtask virtual task do_atomic_trans(REQ t); case(t.xact_type) READ : do_read(t); WRITE : do_write(t); IDLE_XACT : begin `uvm_error("TYPEERR", "trans type not supported yet") end default: begin `uvm_error("TYPEERR", "trans type not defined") end endcase endtask virtual task wait_for_bus_grant(); @(vif.cb_mst iff vif.cb_mst.hgrant === 1'b1); endtask virtual task do_write(REQ t); do_init_write(t); do_proc_write(t); endtask virtual task do_read(REQ t); do_init_read(t); do_proc_read(t); endtask virtual task do_init_write(REQ t); wait_for_bus_grant(); @(vif.cb_mst); vif.cb_mst.htrans <= NSEQ; vif.cb_mst.haddr <= t.addr; vif.cb_mst.hburst <= t.burst_type; vif.cb_mst.hsize <= t.burst_size; vif.cb_mst.hwrite <= 1'b1; @(vif.cb_mst); vif.cb_mst.hwdata <= t.data[0]; forever begin @(negedge vif.hclk); if(vif.hready === 1'b1) begin break; end else @(vif.cb_mst); end // update current trans status t.trans_type = NSEQ; t.current_data_beat_num = 0; // start beat from 0 to make consistence with data array index t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.hresp); endtask virtual task do_init_read(REQ t); wait_for_bus_grant(); @(vif.cb_mst); vif.cb_mst.htrans <= NSEQ; vif.cb_mst.haddr <= t.addr; vif.cb_mst.hburst <= t.burst_type; vif.cb_mst.hsize <= t.burst_size; vif.cb_mst.hwrite <= 1'b0; @(vif.cb_mst); // check ready with delay in current cycle forever begin @(negedge vif.hclk); if(vif.hready === 1'b1) begin break; end else @(vif.cb_mst); end t.data = new[t.current_data_beat_num+1](t.data); t.data[0] = vif.hrdata; // update current trans status t.trans_type = NSEQ; t.current_data_beat_num = 0; // start beat from 0 to make consistence with data array index t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.hresp); endtask virtual task do_proc_write(REQ t); // TODO implement for SEQ operations of other BURST types do_init_idle(t); endtask virtual task do_proc_read(REQ t); // TODO implement for SEQ operations of other BURST types do_init_idle(t); endtask virtual protected task do_init_idle(REQ t); @(vif.cb_mst); _do_drive_idle(); endtask virtual protected task _do_drive_idle(); vif.cb_mst.haddr <= 0; vif.cb_mst.hburst <= 0; vif.cb_mst.hbusreq <= 0; vif.cb_mst.hlock <= 0; vif.cb_mst.hprot <= 0; vif.cb_mst.hsize <= 0; vif.cb_mst.htrans <= 0; vif.cb_mst.hwdata <= 0; vif.cb_mst.hwrite <= 0; endtask virtual protected task reset_listener(); `uvm_info(get_type_name(), "reset_listener ...", UVM_HIGH) fork forever begin @(negedge vif.hresetn); // ASYNC reset _do_drive_idle(); end join_none endtask endclass `endif // LVC_AHB_MASTER_DRIVER_SV
【详细解读】
virtual task drive_transfer(REQ t); // TODO implementation in child class case(t.burst_type) SINGLE: begin do_atomic_trans(t); end INCR : begin `uvm_error("TYPEERR", "burst type not supported yet") end WRAP4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end INCR4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end WRAP8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end INCR8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end WRAP16: begin `uvm_error("TYPEERR", "burst type not supported yet") end INCR16: begin `uvm_error("TYPEERR", "burst type not supported yet") end default: begin `uvm_error("TYPEERR", "burst type not defined") end endcase endtask virtual task do_atomic_trans(REQ t); case(t.xact_type) READ : do_read(t); WRITE : do_write(t); IDLE_XACT : begin `uvm_error("TYPEERR", "trans type not supported yet") end default: begin `uvm_error("TYPEERR", "trans type not defined") end endcase endtask //在drvier_transfer函数中实现burst传输。 //burst的理解:如果你想要连续发送数据,那么你就会用到burst的这几个比特位。 //你的master能发送和slave能接受两种同时满足情况的时候。 //首先利用case语句确定burst传输的类型,在03版本中的设计仅有SIGNLE的传输类型。 //接着利用调用函数的case语句确定hwrite的传输状态(xact_type),在当前设计中仅有读或写的状态。
virtual task do_init_write(REQ t); wait_for_bus_grant(); @(vif.cb_mst); vif.cb_mst.htrans <= NSEQ; vif.cb_mst.haddr <= t.addr; vif.cb_mst.hburst <= t.burst_type; vif.cb_mst.hsize <= t.burst_size; vif.cb_mst.hwrite <= 1'b1; @(vif.cb_mst); vif.cb_mst.hwdata <= t.data[0]; forever begin @(negedge vif.hclk); if(vif.hready === 1'b1) begin break; end else @(vif.cb_mst); //这行作用未知,注释掉无影响。 //我认为可以注释,因为这个forever是为了等待hready信号,不满足直接继续循环时钟下降沿即可 //else条件的cb_mst触发可有可无 end // update current trans status t.trans_type = NSEQ; t.current_data_beat_num = 0; // start beat from 0 to make consistence with data array index t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.hresp); endtask
这是按照AHB协议实现的写操作。
如在T4时刻的上升沿到T5时刻的上升沿是地址周期,地址周期内给地址和初始化
在T5时刻的上升沿到T6时刻的上升沿是数据周期,数据周期内给数据,但是由HREADY信号决定需要几个cycle,图中的HREADY在此时拉低,因此需要等待HREADY拉高。
注意:按照协议的波形图,对于写操作而言,在数据周期已经将数据完成写操作;对于读操作而言,需要等待HREADY拉高后完成读操作
问:为什么@(negedge vif.hclk); 一定是在第二拍的下降沿去采样来获取hready的值?
答:因为我要得到的是hready的值为1才能发送数据,若不为0,则看下一拍的hready。
事实上,在T5当前的时钟沿,HREADY采样到的值只能是时钟沿左边的值1。从设计的逻辑来讲,hready信号的反馈和当前这一拍的时钟沿,它是在同一拍里边发生的,在同一拍发生的情况,要按照组合逻辑来处理,组合逻辑来处理的话,它是没有是时钟这样一个延时的关系的。(组合逻辑是瞬发的)而现在需要捕捉时钟上升沿右边的这部分值,才能实现:判断在这一时钟沿是hready信号的为高的功能。
如果你按照时钟上升沿捕捉的是hready这个值,那么它在T5时刻是1,T6时刻是0,T5-T6周期内可以发送数据。由于T6时刻是0,T7时刻是1,那么就是T6-T7周期内是hready为低,未就绪的。这样不仅会造成时序错误(不满足协议),还会使得验证代码延时时间冗余。
一般使用统一延时时钟下降沿。统一延时时钟下降沿其实就是为了捕捉的更准确,方便复用,因为一旦使用时钟的下降沿则不需要关心时钟的周期。当然直接延时1ps、1ns也是可以的,但是这样要考虑时钟周期,若原时钟周期1ns,直接延时2ns,则会出现延时了两个时钟周期,造成错误。
...... //task do_init_read t.data = new[t.current_data_beat_num+1](t.data); t.data[0] = vif.hrdata; ......
data是一个存放从bus总线上读数据或者写数据的动态数组
第一行先理解一个简单的例子,如 a=new[3] (b) ,a和b都是数组。 这行代码表示创建一个新的动态数组 a
,其长度为 3,并将数组 b
中的元素复制到新数组 a
中。 假设 b
是一个长度为 N 的数组,则代码 a=new[3](b)
将会执行以下操作:
- 创建一个长度为 3 的新数组
a
。 - 将数组
b
的前 3 个元素复制到新数组a
中。如果b
的长度小于 3,则只会复制全部可用的元素;如果b
的长度大于 3,则只会复制前 3 个元素,数组a
中其余位置的值将设置为默认值。 - 将新数组
a
的地址赋值给变量a
。 例如,如果b
数组为{1, 2, 3, 4, 5}
,则执行a=new[3](b)
后,a
数组将包含{1, 2, 3}
三个元素,其它两个元素的值将设置为默认值,而b
数组的内容不变。需要注意的是,新数组a
和原数组b
是独立的数组,它们占用不同的内存空间,对它们的修改互不影响。
第一行的含义,创建一个数组长度为t.current_data_beat_num +1 的数组,然后将原数组t.data或截取或复制到新数组t.data中。具体例子如当多次运行时,current_data_beat_num会增加,然后将增加后的数组中填充原来数组后形成新的数组。 而第二行,含义是将读数据总线中的数据放置到req类型的t的data动态数组的第0位。因此第一二行之间没有太多关系。
5.4 lvc_monitor
一般用interface中的cb_monitor去采样数据,但是一旦用了cb_monitor采样数据,基本就不能脱离,不能一会用一会不用这样。
采样逻辑(协议)在driver中,在lvc_monitor中也要实现类似driver的采样逻辑
5.5 rkv_ahbram_haddr_word_unaligned_virt_seq
`ifndef RKV_AHBRAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SV `define RKV_AHBRAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SV //这里只有写和读,比较不在这个seq里面做,真正的比较交给scoreboard去做 class rkv_ahbram_haddr_word_unaligned_virt_seq extends rkv_ahbram_base_virtual_sequence; `uvm_object_utils(rkv_ahbram_haddr_word_unaligned_virt_seq) function new (string name = "rkv_ahbram_haddr_word_unaligned_virt_seq"); super.new(name); endfunction virtual task body(); bit [31:0] addr, data; burst_size_enum bsize; super.body(); `uvm_info("body", "Entered...", UVM_LOW) for(int i=0; i<100; i++) begin std::randomize(bsize) with {bsize inside {BURST_SIZE_8BIT, BURST_SIZE_16BIT, BURST_SIZE_32BIT};}; std::randomize(addr) with {addr inside {['h1000:'h1FFF]}; bsize == BURST_SIZE_16BIT -> addr[0] == 0; bsize == BURST_SIZE_32BIT -> addr[1:0] == 0; //符合协议 3-25 页,第三段话 }; std::randomize(wr_val) with {wr_val == (i << 24) + (i << 16) + (i << 8) + i;};//每个byte位都有数,方便做debug data = wr_val; `uvm_do_with(single_write, {addr == local::addr; data == local::data; bsize == local::bsize;}) `uvm_do_with(single_read, {addr == local::addr; bsize == local::bsize;}) end `uvm_info("body", "Exiting...", UVM_LOW) endtask endclass `endif
5.6 rkv_ahbram_scoreboard
class rkv_ahbram_scoreboard extends rkv_ahbram_subscriber; ... function void store_data_with_hsize(lvc_ahb_transaction tr, int beat); case(tr.burst_size) //这么写的好处在于:低两位不care了,然后再给它补两位0 //这样不就可以实现字对齐的方式实现data了 BURST_SIZE_8BIT : mem[{tr.addr[31:2],2'b00}] = extract_current_beat_mem_data(tr, beat); ... function bit check_data_with_hsize(lvc_ahb_transaction tr, int beat); bit[31:0] tdata = extract_valid_data(tr.data[beat], tr.addr, tr.burst_size); //tdata是当前读的数据 tdata--active data bit[31:0] mdata = extract_valid_data(mem[{tr.addr[31:2],2'b00}], tr.addr, tr.burst_size); //mdata是写的数据,存储在那里 wdata--memory data check_data_with_hsize = tdata == mdata ? 1 : 0; cfg.scb_check_count++; if(check_data_with_hsize) `uvm_info("DATACHK", $sformatf("ahbram[%0x] data expected 'h%0x = actual 'h%0x", tr.addr, mdata, tdata), UVM_HIGH) else begin cfg.scb_check_error++; `uvm_error("DATACHK", $sformatf("ahbram[%0x] data expected 'h%0x != actual 'h%0x", tr.addr, mdata, tdata)) end endfunction //注意:monitor 既监测写,也监测读 //"写"的数据是seq中randomize的数据,不需要&掉x值 //"读"的数据需要&这个操作 //extract_current_beat_mem_data:提取当前节拍内存数据 //处理发送的数据,mdata返回 function bit [31:0] extract_current_beat_mem_data(lvc_ahb_transaction tr, int beat); bit [31:0] mdata = mem[{tr.addr[31:2],2'b00}]; bit [31:0] tdata = tr.data[beat]; case(tr.burst_size) BURST_SIZE_8BIT : mdata[(tr.addr[1:0]*8 + 7) -: 8] = tdata >> (8*tr.addr[1:0]);//难点 //按照片选,片选过来以后把数据抄过来要它的最低位 BURST_SIZE_16BIT : mdata[(tr.addr[1]*16 + 15) -: 16] = tdata >> (16*tr.addr[1]); BURST_SIZE_32BIT : mdata = tdata; BURST_SIZE_64BIT : begin `uvm_error("TYPEERR", "burst size not supported") end default : begin `uvm_error("TYPEERR", "burst size not supported") end endcase return mdata; endfunction ... //能寻找真正位移有效的data function bit [31:0] extract_valid_data([`LVC_AHB_MAX_DATA_WIDTH - 1:0] data ,[`LVC_AHB_MAX_ADDR_WIDTH - 1 : 0] addr ,burst_size_enum bsize); //为什么这里要做&? 因为我的data可能是一个高64位的,向右移8位,还有剩下的56位,但是我只取移位以后的低8位,所以要把剩下的&掉,&成0. case(bsize) BURST_SIZE_8BIT : return (data >> (8*addr[1:0])) & 8'hFF; BURST_SIZE_16BIT : return (data >> (16*addr[1]) ) & 16'hFFFF; BURST_SIZE_32BIT : return data & 32'hFFFF_FFFF; BURST_SIZE_64BIT : begin `uvm_error("TYPEERR", "burst size not supported") end default : begin `uvm_error("TYPEERR", "burst size not supported") end endcase endfunction ...