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
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
//在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) 将会执行以下操作:

  1. 创建一个长度为 3 的新数组 a
  2. 将数组 b 的前 3 个元素复制到新数组 a 中。如果 b 的长度小于 3,则只会复制全部可用的元素;如果 b 的长度大于 3,则只会复制前 3 个元素,数组 a 中其余位置的值将设置为默认值。
  3. 将新数组 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
...

 

 

 

 

 

 

 

 

posted @ 2023-05-28 15:15  傅红雪a  阅读(519)  评论(0编辑  收藏  举报
Live2D