经过一段时间的学习。大概了解了SystemVerilog的语法,以及一个验证平台的搭建流程。可以说,验证平台的搭建方法是相对固定的。因为验证组件是确定的,无非就是generator,driver,monitor,scoreboard等。不过要真正掌握搭建过程,还是需要不断的练习。
下面验证如下一个加法器。
验证平台的结构还是如下图所示。
`include "interface.sv"
`include "test.sv"
`include "adder.sv"
module top;
/* 最顶层的文件,用于连接DUT和验证平台;
包含DUT,测试以及接口实例;
接口能将DUT与验证平台相连 */
bit clk;
bit reset;
/* 实例化接口 */
intf tif(clk,reset);
/* 实例化DUT,并配置接口 */
adder DUT (
.clk(tif.clk),
.reset(tif.reset),
.a(tif.a),
.b(tif.b),
.valid(tif.valid),
.c(tif.c)
);
/* 连接测试接口 */
test t1(tif);
always #5 clk = ~clk;
initial begin
reset = 1;
#5 reset=0;
end
endmodule
`include"environment.sv"
program test(intf intf);
/* 创建环境,配置验证条件,初始化验证 */
environment env;
initial begin
/* 实例化环境类 */
env = new(intf);
/* 设置事务数目 */
env.gen.numtr = 10;
/* 启动环境 */
env.run();
end
endprogram
`include "trans.sv"
`include "generator.sv"
`include "driver.sv"
`include "monitor.sv"
`include "scoreboard.sv"
class environment;
generator gen;
driver drv;
monitor mon;
scoreboard scb;
mailbox gen2drv;
mailbox mon2scb;
/* generator结束生成 */
event gen_ended;
virtual intf vif;
function new(virtual intf vif);
this.vif = vif;
gen2drv = new();
mon2scb = new();
gen = new(gen2drv,gen_ended);
drv = new(vif,gen2drv);
mon = new(vif,mon2scb);
scb = new(mon2scb);
endfunction
/* 启动进行清零操作 */
task pre_test();
drv.reset();
endtask
/* 开始测试 */
task test();
fork
gen.main();
drv.main();
mon.main();
scb.main();
join_any
endtask
/* 检测generator,driver,scoreboard是否结束 */
task post_test();
wait(gen_ended.triggered);
wait(gen.numtr == drv.numtr);
wait(gen.numtr == scb.numtr);
endtask
/* 运行测试 */
task run;
pre_test();
test();
post_test();
$finish;
endtask
endclass
class driver;
/* 接收generator生成的数据包,并转化成接口信号,驱动给dut */
/* 生成虚接口 */
virtual intf vif;
/* 定义与driver通讯的信箱 */
mailbox gen2drv;
/* 总数据包 */
int numtr;
/* 构造函数,从上层获取接口以及信箱 */
function new(virtual intf vif, mailbox gen2drv);
this.vif = vif;
this.gen2drv = gen2drv;
endfunction
/* 重启任务 */
task reset;
/* 等待重启信号 */
wait(vif.reset);
vif.a <= 0;
vif.b <= 0;
vif.valid <= 0;
wait(!vif.reset);
$display("----------[driver] reset----------");
endtask
/* 主任务 */
task main;
forever begin
transaction tr;
/* 接收信箱中来自generator的transaction */
gen2drv.get(tr);
$display("----------[driver] no.%0d----------", numtr);
@(posedge vif.clk);
vif.valid <= 1;
vif.a <= tr.a;
vif.b <= tr.b;
$display("---------a = %0d; b = %0d----------", tr.a, tr.b);
@(posedge vif.clk);
vif.valid <= 0;
numtr++;
end
endtask
endclass
class generator;
/* generator用于生成随机的transaction,即数据包,
并通过mailbox发送给driver */
/* 定义一个随机的transaction句柄 */
rand transaction tr;
/* 与driver相通信的信箱 */
mailbox gen2drv;
/* 最大的transaction数目 */
int numtr;
/* transaction全部生成的标识 */
event ended;
function new(mailbox gen2drv, event ended);
this.gen2drv = gen2drv;
this.ended = ended;
endfunction
/* 主任务,生成随机的transaction,并放入信箱 */
task main();
repeat (numtr) begin
tr = new();
assert(tr.randomize());
gen2drv.put(tr);
end
/* 所有测试用数据包生成完毕,触发结束事件 */
->ended;
endtask
endclass
class monitor;
/* 监控dut的接口信号,并转换为transaction,通过信箱送往scoreboard */
virtual intf vif;
mailbox mon2scb;
function new(virtual intf vif, mailbox mon2scb);
this.vif = vif;
this.mon2scb = mon2scb;
endfunction
task main;
forever begin
transaction tr;
tr = new();
@(posedge vif.clk);
wait(vif.valid);
tr.a = vif.a;
tr.b = vif.b;
/* 等待dut计算结果 */
@(posedge vif.clk);
tr.c = vif.c;
mon2scb.put(tr);
end
endtask
endclass
class scoreboard;
/* 接收监视器发来的数据包,比较期望数据与实际数据 */
mailbox mon2scb;
int numtr;
/* 期望结果 */
bit [7:0] c;
function new(mailbox mon2scb);
this.mon2scb = mon2scb;
c = 7'b0;
endfunction
task main;
transaction tr;
forever begin
mon2scb.get(tr);
/* 计算期望结果 */
c = tr.a + tr.b;
if (c != tr.c)
$display("---ERROR!!!---a = %0d; b = %0d; c_dut = %0d; c_rm = %0d-",
tr.a, tr.b, tr.c, c);
else
$display("---SUCCESS!!---a = %0d; b = %0d; c_dut = %0d; c_rm = %0d-",
tr.a, tr.b, tr.c, c);
numtr++;
end
endtask
endclass
class transaction;
/* 一个transaction就是一个数据包,包含设计中的输入和输出 */
rand bit [3:0] a;
rand bit [3:0] b;
bit [6:0] c;
endclass
interface intf(input logic clk, reset);
/* 接口,连接端口信号,这个接口十分简单,没有设置modport*/
logic [3:0] a;
logic [3:0] b;
logic valid;
logic [6:0] c;
endinterface