AHB—SRAMC基于SV的Testbench之一(interface、transaction、generator、agent)

一、验证平台Testbench介绍

通常基于systemverilog的验证平台主要包括以下几个部分:

最顶层的Testbench_Top:包括DUT(design under test)待测的设计文件,TEST验证的平台,以及二者之间的interface接口

TEST验证平台:包括env环境

env环境:包括generator数据发生器,driver数据驱动器,scoreboard比对器,monitor采样,coverage覆盖率收集,transaction数据包等组件

整个验证平台如下图所示:
在这里插入图片描述
注:基于SV的验证平台中,当数据由agent发送去Scoreboard时,在激励前就不在需要用(左侧)monitor去采集进入DUT的数据,但由DUT送出的数据依然会由monitor采集,送入Scoreboard,然后进行比对。

二、代码

2.1.接口interface:ahb_slv_if.sv

`ifndef AHB_SLV_IF_SV
`define AHB_SLV_IF_SV

interface ahb_slv_if(input hclk);          //时钟信号一般声明在接口名后
  logic           hresetn;               //低电平有效复位信号
  logic           hsel;                  //slave选择信号
  logic           hwrite;               //读/写命令(控制信号)
  logic           hready;               //由ater发给slave(状态信号),高:有效;否则,无效
  logic  [1:0]    htrans;                //指示命令是否有效(控制信号)
  logic  [2:0]    hsize;                 //传输总线的有效数据位(控制信号)
  logic  [2:0]    hburst;                //是否连续传输(控制信号)
  logic  [31:0]   haddr;                 //32位系统总线地址信号
  logic  [31:0]   hwdata;                //写数据总线信号
  logic           hready_resp;          //hready_out(状态信号),/slave输出给master,表明slave是否OK
  logic  [1:0]    hresp;                 //hrdata信号(状态信号),表明传输是否OK,00:OKAY,01:ERROR
  logic  [31:0]   hrdata;                //读数据总线信号

  clocking  drv_cb@(posedge hclk);        //主要用于显式同步时钟域
    output      hsel;           
    output      hready;        
    output      haddr;       //对于driver而言,他是一个master,通过接口interface按照AHB协议与DUT相连,  
    output      htrans;      //所以,其信号的输入输出应以DUT为依据;
    output      hsize;       //因此,driver输出信号除了常规AHB的地址信号、读/写数据信号,控制信号,
    output      hwrite;      //还有hsel选择信号,和hready(master输出)状态信号
    output      hwdata;
    input       hrdata;                   //输入—读数据总线信号
  endclocking

  clocking  mon_cb@(posedge hclk);        //主要用于显式同步时钟域
    input      hsel;
    input      hready;
    input      haddr;         //对于monitor而言,他通过DUT、接口和driver相连接,他会采集所有接口上的信息,
    input      htrans;        //然后通过邮箱mailbox将数据送到Scoreboard进行比对,由于数据传输不是burst传输,
    input      hsize;         //所以,控制信号不需要定义burst信号,
    input      hwrite;        //此外monitor和driver中的hrdata信号都为input类型,他们都是有DUT输入。
    input      hwdata;
    input      hrdata;                   //输入—读数据总线信号
  endclocking

  modport driver(clocking drv_cb);       //modport时module port模块端口的简写,它为接口内部提供不同的视图,
  modport monitor(clocking mon_cb);      //这里的modport基于clocking的方式驱动信号,clocking中只需声明方向。

endinterface

`endif

2.2.数据包transaction:transaction.sv

`ifndef TRANSACTION_SV
`define TRANSACTION_SV

class transaction;                  //对数据激励进行建模,以便产生随机化数据包
  rand bit  [31:0]  haddr;
  rand bit          hsel;           //需要注意,并不是将所有接口interface中的信号都放在数据包transaction中发出,
  rand bit  [1:0]   htrans;         //因为有些信号是固定接死的,并没有必要去采集,看他们的覆盖率,
  rand bit  [1:0]   hsize;          //或者发送出去做一些随机化处理。
  rand bit          hwrite;
  rand bit  [2:0]   hburst;         //常用于随机化的数据:地址信号、控制信号、读写数据信号、hsel选择信号等。
  rand bit  [31:0]  hwdata;         //一般在发送非定向用例时,便会随机化这些信号。
  rand bit  [31:0]  hrdata;

  constraint c1{
                   haddr inside {[32'h0:32'h0001_FFFF]};    //系统地址:0-64K;sram地址0-16K
                   }      //这里的地址是系统地址64K=2^16, 地址范围:0—2^16,先化为二进制,在变化为十六进制写法
                          //32'b0:32'b0000_0000_0000_0000_1111_1111_1111_1111_ = 32'h0:32'h0000_FFFF
endclass

`endif

2.3.数据包生成器generator:generator.sv

`ifndef GENERATOR_SV
`define GENERATOR_SV

class generator;
  int            tr_num;            //定义了要发送的激励的数量(不同的testcase发送的激励命令数量不一样)
  transaction    tr;                //产生对象tr(tr的产生由顶层testcase告知)
  mailbox        mbx=new();         //将tr对象放入邮箱mbx中,负责数据的传递
  event          gen_data;          //定义时间,以便于内部通信
  
  extern function new(mailbox mbx,int tr_num);
  extern function build();
  extern task write_data32(logic [31:0] addr, logic [31:0] wdata);     //只写32/16/8位数据,
  extern task write_data16(logic [31:0] addr, logic [31:0] wdata);     //testcase告诉地址addr和数据wdata,
  extern task write_data8(logic [31:0] addr, logic [31:0] wdata);      //就会产生tr对象。
  extern task write_data32_random(logic [31:0] addr);           //写32位随机数据,随机产生tr。
  extern task write_data16_random(logic [31:0] addr);           //写16位随机数据,随机产生tr。
  extern task write_data8_random(logic [31:0] addr);            //写8位随机数据,随机产生tr。
  extern task write_addr_random(logic [31:0] wdata);
  extern task read_data32(logic [31:0] addr);
  extern task read_data16(logic [31:0] addr);
  extern task read_data8(logic [31:0] addr);
  extern task read_addr_random();          //读地址随机
  extern task read_write_random();         //读/写都随机,即地址、数据和类型hwrite都随机
  extern task all_random();          //所有的tr数据都随机,包括hsel、hwrite等信号
  extern task no_op();            //无操作,空命令
  extern task run();

endclass

function generator::new(mailbox mbx,int tr_num);
  this.mbx = mbx;       //将外部邮箱赋给内部邮箱,使两者相连接,generator的mbx会放在公共邮箱里,等待下一级组件去取
  this.tr_num = tr_num; //不通testcase发送的tr_num激励数量不一样
endfunction

function generator::build();         //在运行testcase之前的准备工作
  tr = new;                          //对象tr实例化,分配空间
  if(!tr.randomize())begin           //随机化transaction中的数据
    $display("@%0t ERROR::generator::build randomize failed",$time);
  end
endfunction

task generator::write_data32(logic [31:0] addr, logic [31:0] wdata);
  tr = new;                 //对象tr实例化分配空间
  tr.haddr  = addr;         //testcase传入地址,对数据进行地址分配地址
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b10;        //2'b10:表示有效数据传输位为32bit
  tr.hburst = 2'b00;        //single操作,非连续传输(可省略)
  tr.hwdata = wdata;        //写入数据
  ->gen_data;     //触发事件,在后边run();的时刻,等待事件在收到触发时,就会把tr放入邮箱
endtask

task generator::write_data16(logic [31:0] addr, logic [31:0] wdata);
  tr = new;                 //对象tr实例化,分配空间
  tr.haddr  = addr;         //testcase传入地址,对写数据进行地址分配地址
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b01;        //2'b01:表示有效数据传输位为16bit
  tr.hburst = 2'b00;        //(可省略)
  tr.hwdata = wdata;        //写数据
  ->gen_data;      //触发事件,在后边run();的时刻,等待事件在收到触发时,就会把tr放入邮箱
endtask

task generator::write_data8(logic [31:0] addr, logic [31:0] wdata);
  tr = new;
  tr.haddr  = addr;         //testcase传入地址,对写数据进行地址分配地址
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b00;        //2'b00:表示有效数据传输位为8bit
  tr.hburst = 2'b00;        
  tr.hwdata = wdata;        
  ->gen_data;
endtask

task generator::write_data32_random(logic [31:0] addr);
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::write_data_random.randomize failed",$time);
  end
  tr.haddr  = addr;         //testcase传入地址,对随机化数据进行地址分配
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b10;        //2'b10:表示有效数据传输位为32bit
  ->gen_data;
endtask

task generator::write_data16_random(logic [31:0] addr);
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::write_data_random.randomize failed",$time);
  end
  tr.haddr  = addr;         //testcase传入地址,对随机化数据进行地址分配
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b01;        //2'b01:表示有效数据传输位为16bit
  ->gen_data;
endtask

task generator::write_data8_random(logic [31:0] addr);
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::write_data_random.randomize failed",$time);
  end
  tr.haddr  = addr;         //testcase传入地址,对随机化数据进行地址分配
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b00;        //2'b00:表示有效数据传输位为32bit
  ->gen_data;
endtask

task generator::write_addr_random(logic [31:0] wdata);
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::write_addr_random.randomize failed",$time);
  end
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b1;         //1'b1:表示写数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示写传输命令有效:NONSEQ
  tr.hsize  = 2'b10;        //2'b10:表示有效数据传输位为32bit(支持写8/16/32bit)
  tr.hwdata = wdata;        //随机写入数据
  ->gen_data;
endtask

task generator::read_data32(logic [31:0] addr);
  tr = new;
  tr.haddr  = addr;       
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b0;         //1'b0:表示读数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示传输命令有效:NONSEQ
  tr.hsize  = 2'b10;        //2'b10:表示有效数据传输位为32bit
  ->gen_data;
endtask

task generator::read_data16(logic [31:0] addr);
  tr = new;
  tr.haddr  = addr;       
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b0;         //1'b0:表示读数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示传输命令有效:NONSEQ
  tr.hsize  = 2'b01;        //2'b10:表示有效数据传输位为16bit
  ->gen_data;
endtask

task generator::read_data8(logic [31:0] addr);
  tr = new;
  tr.haddr  = addr;       
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b0;         //1'b0:表示读数据传输模式
  tr.htrans = 2'b10;        //2'b10:表示指示传输命令有效:NONSEQ
  tr.hsize  = 2'b00;        //2'b00:表示有效数据传输位为8bit
  ->gen_data;
endtask

task generator::read_addr_random();   //读数据只需要地址即可,地址随机化,故不再需要参数
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::read_addr_random.randomize failed",$time);
  end
  tr.hsel   = 1'b1;         //选中该slave
  tr.hwrite = 1'b0;         //1'b0:表示读数据传输命令模式
  tr.htrans = 2'b10;        //2'b10:表示指示传输命令有效:NONSEQ
  tr.hsize  = 2'b10;        //2'b10:表示有效数据传输位为32bit(支持读8/16/32bit)
  ->gen_data;
endtask

task generator::read_write_random();  //读/写都随机,hwrite也随即化,不再需要赋值
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::read_addr_random.randomize failed",$time);
  end
  tr.hsel   = 1'b1;         //选中该slave
  tr.htrans = 2'b10;        //2'b10:表示指示传输命令有效:NONSEQ
  tr.hsize  = 2'b10;        //2'b10:表示有效数据传输位为32bit
  ->gen_data;
endtask

task generator::all_random();       //所有都随机,不再对各个信号再进行赋值
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::read_addr_random.randomize failed",$time);
  end
  ->gen_data;
endtask

task generator::no_op();            //无操作命令
  tr = new;
  if(!tr.randomize())begin  //随机化transaction包中的数据
    $display("@%0t ERROR::generator::read_addr_random.randomize failed",$time);
  end
  tr.hsel   = 'h0;         //未选中slave
  tr.htrans = 'h0;        //无效命令指示,  无效操作
  ->gen_data;
endtask

task generator::run();        //前面产生激励的task,如果产生数据的话,便会同时触发事件gen_data,告诉run();
  reteat(tr_num)begin         //tr数据包已产生了数据,然后run会把数据包放入邮箱mbx,等待下一个组件去取。
     @(gen_data);             //testcase每发一个包命令,run()就等待tr产生数据,然后放入邮箱
     mbx.put(tr);             //这里的邮箱就相当于一个FIFO
  end
endtask

`endif

2.4.代理agent:agent.sv

复杂的验证环境一般会有agent组件,但agent组件不是必须的;agent组件在项目中相当于一个数据中转站,负责将数据包传送到不同地方。agent通过邮箱mailbox,一方面从generator获取(get)数据包到agent;另一方面又通过邮箱mailbox同时将数据包发送(put)到driver和Scoreboard。

`ifndef AGENT_SV
`define AGENT_SV

class agent;
  int          tr_num;               //传输数据包的总个数,测试用例会告知每次传输多少个包
  mailbox      gen2agt_mbx=new();    //创建邮箱,邮箱深度不限
  mailbox      agt2drv_mbx=new();
  mailbox      agt2scb_mbx=new();
  
  transaction  tr;                    //在tr的传输过程中,数据内容都是一样的来源于transaction

  extern function new(mailbox gen2agt_mbx,agt2drv_mbx,agt2scb_mbx, int tr_num);
  extern function build();
  extern task run();
  
endclass

function agent::new(mailbox gen2agt_mbx,gen2drv_mbx,gen2scb_mbx, int tr_num);
  this.gen2agt_mbx = gen2agt_mbx;      //generator的外层(env层)会定义公共邮箱(形参),并将其
  this.agt2drv_mbx = agt2drv_mbx;      //同时传递给agent、generator,那么这两个邮箱就成了一个邮箱
  this.agt2scb_mbx = agt2scb_mbx;      //new函数的作用就是在创建agent对象时,通过公共邮箱,
  this.tr_num = tr_num;                //将generator、driver和Scoreboard的邮箱连接起来
endfunction

function agent::build();
endfunction

task agent::run();
  repeat(tr_num)begin        //tr_num为发包数目
    tr = new();              //创建对象实体
    gen2agt_mbx.get(tr);     //从generator处获得(get)一个 实体数据包
    agt2drv_mbx.put(tr);     //将tr实体数据包发送(put)到driver
    agt2scb_mbx.put(tr);     //将tr实体数据包发送(put)到Scoreboard
  end
endtask

`endif
发布了21 篇原创文章 · 获赞 10 · 访问量 1834

猜你喜欢

转载自blog.csdn.net/weixin_46022434/article/details/104940174