文后阅读原文附本文所有代码。
DUT:
是一个简单的memory。就六个信号,时钟信号clk,复位信号reset(高有效),读使能信号rd_en,写使能信号wr_en,写数据信号wdata,读数据信号rdata。
对于写操作:
address, wr_en和wdata 在同一时钟进行驱动。
对于读操作:
address和rd_en在同一时钟进行驱动,系统在下一时钟出现反应。
代码语言:javascript复制 //Memory
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH];
//Reset
always @(posedge reset)
for(int i=0;i<2**ADDR_WIDTH;i ) mem[i]=8'hFF;
// Write data to Memory
always @(posedge clk)
if (wr_en) mem[addr] <= wdata;
// Read data from memory
always @(posedge clk)
if (rd_en) rdata <= mem[addr];
Testbench:
对于验证来说,主要就是以下几个类组成:transaction,generator,driver,monitor,scoreboard,environment,top。
对于transaction:
主要是对生成激励中所需的字段进行声明,还可以用作DUT信号上监视器监视的活动的占位符。
- 编写transaction的步骤:
- 首先对域进行声明:
class transaction;
//declaring the transaction items
bit [1:0] addr;
bit wr_en;
bit rd_en;
bit [7:0] wdata;
bit [7:0] rdata;
bit [1:0] cnt;
endclass
- 对变量使用rand进行随机化
class transaction;
//declaring the transaction items
rand bit [1:0] addr;
rand bit wr_en;
rand bit rd_en;
rand bit [7:0] wdata;
bit [7:0] rdata;
bit [1:0] cnt;
endclass
- 对部分变量进行约束
//constaint, to generate any one among write and read
constraint wr_rd_c { wr_en != rd_en; };
- generator类的编写
主要是对transaction随机化产生激励,并且把产生的类发送给driver。
- 声明事务类句柄
class generator;
//declaring transaction class
rand transaction trans;
endclass
- 随机化事务类
class generator;
//declaring transaction class
rand transaction trans;
//main task, generates(create and randomizes) the packets and puts into mailbox
task main();
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
endtask
endclass
- 利用信箱把随机化后的事务类发送给driver
class generator;
//declaring transaction class
rand transaction trans;
//declaring mailbox
mailbox gen2driv;
//constructor
function new(mailbox gen2driv);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
endfunction
//main task, generates(create and randomizes) the packets and puts into mailbox
task main();
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
endtask
endclass
- 增加变量去控制事务产生的数量
class generator;
//declaring transaction class
rand transaction trans;
//declaring mailbox
mailbox gen2driv;
//repeat count, to specify number of items to generate
int repeat_count;
//constructor
function new(mailbox gen2driv);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
endfunction
//main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
task main();
repeat(repeat_count) begin
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
end
endtask
endclass
- 添加一个事件来指示生成过程的完成,该事件将在生成过程完成时触发。
class generator;
//declaring transaction class
rand transaction trans;
//declaring mailbox
mailbox gen2driv;
//repeat count, to specify number of items to generate
int repeat_count;
//event
event ended;
//constructor
function new(mailbox gen2driv,event ended);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
this.ended = ended;
endfunction
//main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
task main();
repeat(repeat_count) begin
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
end
-> ended;
endtask
endclass
- interface的编写
接口将对信号进行分组,指定方向(Modport)并同步信号(时钟块)。
- driver时钟块
//driver clocking block
clocking driver_cb @(posedge clk);
default input #1 output #1;
output addr;
output wr_en;
output rd_en;
output wdata;
input rdata;
endclocking
- monitor时钟块
//monitor clocking block
clocking monitor_cb @(posedge clk);
default input #1 output #1;
input addr;
input wr_en;
input rd_en;
input wdata;
input rdata;
endclocking
- 指定方向
//driver modport
modport DRIVER (clocking driver_cb,input clk,reset);
//monitor modport
modport MONITOR (clocking monitor_cb,input clk,reset);
- Driver类的编写
该类通过将事务类值分配给接口信号,接收从生成器生成的激励并驱动到DUT。
- 声明接口和信箱,通过构造函数获取接口和信箱句柄
//creating virtual interface handle
virtual mem_intf mem_vif;
//creating mailbox handle
mailbox gen2driv;
//constructor
function new(virtual mem_intf mem_vif,mailbox gen2driv);
//getting the interface
this.mem_vif = mem_vif;
//getting the mailbox handle from environment
this.gen2driv = gen2driv;
endfunction
- 添加复位任务,把接口信号初始化
//Reset task, Reset the Interface signals to default/initial values
task reset;
wait(mem_vif.reset);
$display("--------- [DRIVER] Reset Started ---------");
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
`DRIV_IF.addr <= 0;
`DRIV_IF.wdata <= 0;
wait(!mem_vif.reset);
$display("--------- [DRIVER] Reset Ended ---------");
endtask
- 添加驱动任务,把generator产生的事务驱动给DUT
//drive the transaction items to interface signals
task drive;
forever begin
transaction trans;
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
gen2driv.get(trans);
$display("--------- [DRIVER-TRANSFER:
] ---------",no_transactions);
@(posedge mem_vif.DRIVER.clk);
`DRIV_IF.addr <= trans.addr;
if(trans.wr_en) begin
`DRIV_IF.wr_en <= trans.wr_en;
`DRIV_IF.wdata <= trans.wdata;
$display("tADDR = %0h tWDATA = %0h",trans.addr,trans.wdata);
@(posedge mem_vif.DRIVER.clk);
end
if(trans.rd_en) begin
`DRIV_IF.rd_en <= trans.rd_en;
@(posedge mem_vif.DRIVER.clk);
`DRIV_IF.rd_en <= 0;
@(posedge mem_vif.DRIVER.clk);
trans.rdata = `DRIV_IF.rdata;
$display("tADDR = %0h tRDATA = %0h",trans.addr,`DRIV_IF.rdata);
end
$display("-----------------------------------------");
no_transactions ;
end
endtask
- 添加本地变量,记录传输的次数
//used to count the number of transactions
int no_transactions;
//drive the transaction items to interface signals
task drive;
------
------
no_transactions ;
endtask
- Monitor类的编写
- 声明接口和信箱,通过构造函数获取接口和信箱句柄。
//creating virtual interface handle
virtual mem_intf mem_vif;
//creating mailbox handle
mailbox mon2scb;
//constructor
function new(virtual intf vif,mailbox mon2scb);
//getting the interface
this.vif = vif;
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
- 采样逻辑并将采样的事务发送到记分板
task main;
forever begin
transaction trans;
trans = new();
@(posedge mem_vif.MONITOR.clk);
wait(`MON_IF.rd_en || `MON_IF.wr_en);
trans.addr = `MON_IF.addr;
trans.wr_en = `MON_IF.wr_en;
trans.wdata = `MON_IF.wdata;
if(`MON_IF.rd_en) begin
trans.rd_en = `MON_IF.rd_en;
@(posedge mem_vif.MONITOR.clk);
@(posedge mem_vif.MONITOR.clk);
trans.rdata = `MON_IF.rdata;
end
mon2scb.put(trans);
end
endtask
- Scoreboard类的编写
- 声明信箱和变量以保持事务计数,通过构造函数连接句柄
//creating mailbox handle
mailbox mon2scb;
//used to count the number of transactions
int no_transactions;
//constructor
function new(mailbox mon2scb);
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
- 存储wdata并将rdata与存储的数据进行比较的逻辑
//stores wdata and compare rdata with stored data
task main;
transaction trans;
forever begin
#50;
mon2scb.get(trans);
if(trans.rd_en) begin
if(mem[trans.addr] != trans.rdata)
$error("[SCB-FAIL] Addr = %0h,n t Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
else
$display("[SCB-PASS] Addr = %0h,n t Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
end
else if(trans.wr_en)
mem[trans.addr] = trans.wdata;
no_transactions ;
end
endtask
- Environment类的编写
- 声明句柄
//generator and driver instance
generator gen;
driver driv;
monitor mon;
scoreboard scb;
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb;
//event for synchronization between generator and test
event gen_ended;
//virtual interface
virtual mem_intf mem_vif;
- 在构造函数中创造信箱,driver,generator等
//constructor
function new(virtual mem_intf mem_vif);
//get the interface from test
this.mem_vif = mem_vif;
//creating the mailbox (Same handle will be shared across generator and driver)
gen2driv = new();
mon2scb = new();
//creating generator and driver
gen = new(gen2driv,gen_ended);
driv = new(mem_vif,gen2driv);
mon = new(mem_vif,mon2scb);
scb = new(mon2scb);
endfunction
- 为了更好的访问,使用三级任务
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
mon.main(); //---NEW CODE---
scb.main(); //---NEW CODE---
join_any
endtask
task post_test();
wait(gen.ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
wait(gen.repeat_count == scb.no_transactions); //---NEW CODE---
endtask
- 添加一个运行任务以调用上述方法,在post_test()之后调用$ finish以结束模拟。
task run;
pre_test();
test();
post_test();
$finish;
endtask
- 测试类的编写
测试负责,创造环境,配置测试平台,即设置要生成的事务的类型和数量,启动仿真。
- 声明并创建环境
//declaring environment instance
environment env;
initial begin
//creating environment
env = new(intf);
end
- 配置要生成的事务数
//setting the repeat count of generator as 10, means to generate 10 packets
env.gen.repeat_count = 10;
- 启动仿真
//calling run of env, it interns calls generator and driver main tasks.
env.run();
- 顶层测试的编写
这是最顶层的文件,用于连接DUT和TestBench。由DUT,Test和Interface实例组成。连接DUT和TestBench。
- 声明并生成时钟并设置
//clock and reset signal declaration
bit clk;
bit reset;
//clock generation
always #5 clk = ~clk;
//reset Generation
initial begin
reset = 1;
#5 reset =0;
end
- 创建接口实例
//creatinng instance of interface, inorder to connect DUT and testcase
mem_intf intf(clk,reset);
- 创建设计实例并连接接口信号
//DUT instance, interface signals are connected to the DUT ports
memory DUT (
.clk(intf.clk),
.reset(intf.reset),
.addr(intf.addr),
.wr_en(intf.wr_en),
.rd_en(intf.rd_en),
.wdata(intf.wdata),
.rdata(intf.rdata)
);
- 创建一个测试实例并通过接口句柄
//Testcase instance, interface handle is passed to test as an argument
test t1(intf);
End