文章目录
一、验证平台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