Testbench由硬件环境转变为软件环境。
实验一
- Testbench结构框图
- 时钟信号和复位信号放在task内由两个并行initial块调用,时钟周期可传参指定
- 为发送更多数据采用动态数组产生数据;为实现更紧凑高速的数据发送采用并行发送
- 将原有的激励方法chnl_write()、chnl_idle() 等封装在新的模块chnl_initiator中
module chnl_initiator(
input clk,
input rstn,
output logic [31:0] ch_data,
output logic ch_valid,
input ch_ready,
input [ 5:0] ch_margin
);
string name;
function void set_name(string s); // 设置实例名称,方便仿真时打印信息和调试
name = s;
endfunction
task chnl_write(input logic[31:0] data); // 实现一次有效写数据,不需再传id参数
@(posedge clk);
ch_valid <= 1;
ch_data <= data;
@(negedge clk);
wait(ch_ready === 'b1); // 等待ch_ready拉高
$display("%t channel initial [%s] sent data %x", $time, name, data);
chnl_idle();
endtask
task chnl_idle(); // 实现一个时钟周期的idle
@(posedge clk);
ch_valid <= 0;
ch_data <= 0;
endtask
endmodule
实验二
- Testbench结构框图
软件和硬件分开,采用interface连接;软件部分封装进package,硬件封装进library。
- 采用interface连接验证组件和DUT
interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_valid;
logic ch_ready;
logic [ 5:0] ch_margin;
clocking drv_ck @(posedge clk); // 声明时钟块控制时钟偏移,消除可能的竞争问题
default input #1ns output #1ns;
output ch_data, ch_valid;
input ch_ready, ch_margin;
endclocking
endinterface
- 将module initiator和generator改造为class,并定义了一个用来封装发送数据的类chnl_trans
class chnl_trans;
int data;
int id;
int num;
endclass: chnl_trans
class chnl_initiator;
local string name; // SV类中成员默认public
local int idle_cycles;
local virtual chnl_intf intf; // class无端口,interface作为成员变量加virtual声明为虚接口
function new(string name = "chnl_initiator"); // class内无需声明automatic
this.name = name;
this.idle_cycles = 1;
endfunction
function void set_idle_cycles(int n);
this.idle_cycles = n;
endfunction
function void set_name(string s);
this.name = s;
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task chnl_write(input chnl_trans t);
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1; // 用interface的clocking块drv_ck驱动数据
intf.drv_ck.ch_data <= t.data;
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
$display("%t channel initiator [%s] sent data %x", $time, name, t.data);
repeat(this.idle_cycles) chnl_idle(); // idle_cycles定义了插入数据间的idle周期
endtask
task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass: chnl_initiator
class chnl_generator;
chnl_trans trans[$];
int num;
int id;
function new(int n);
this.id = n; // 每个initiator的id不同,从generator中拿到的数据也不同
this.num = 0;
endfunction
function chnl_trans get_trans();
chnl_trans t = new();
t.data = 'h00C0_0000 + (this.id<<16) + this.num;
t.id = this.id;
t.num = this.num;
this.num++;
this.trans.push_back(t);
return t;
endfunction
endclass: chnl_generator
- 引入agent、root_test、basic_test、burst_test和fifo_full_test等class,封装进package编译
package chnl_pkg;
class chnl_agent;
chnl_generator gen;
chnl_initiator init;
local int ntrans;
virtual chnl_intf vif;
function new(string name = "chnl_agent", int id = 0, int ntrans = 1);
this.gen = new(id); // 在agent内例化generator和initiator
this.init = new(name);
this.ntrans = ntrans;
endfunction
function void set_ntrans(int n);
this.ntrans = n;
endfunction
function void set_interface(virtual chnl_intf vif);
this.vif = vif;
init.set_interface(vif);
endfunction
task run(); // 运行各组件
repeat(this.ntrans) this.init.chnl_write(this.gen.get_trans());
this.init.chnl_idle(); // set idle after all data sent out
endtask
endclass: chnl_agent
class chnl_root_test;
chnl_agent agent[3];
protected string name;
function new(int ntrans = 100, string name = "chnl_root_test");
foreach(agent[i]) begin
this.agent[i] = new($sformatf("chnl_agent%0d",i), i, ntrans);
end
this.name = name;
$display("%s instantiate objects", this.name);
endfunction
task run();
$display("%s started testing DUT", this.name);
fork
agent[0].run();
agent[1].run();
agent[2].run();
join
$display("%s waiting DUT transfering all of data", this.name);
fork // 3个anent并行运行
wait(agent[0].vif.ch_margin == 'h20);
wait(agent[1].vif.ch_margin == 'h20);
wait(agent[2].vif.ch_margin == 'h20);
join
$display("%s: 3 channel fifos have transferred all data", this.name);
$display("%s finished testing DUT", this.name);
endtask
function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif);
agent[0].set_interface(ch0_vif);
agent[1].set_interface(ch1_vif);
agent[2].set_interface(ch2_vif);
endfunction
endclass
// each channel send data with idle_cycles inside [1:3]
// each channel send out 200 data
class chnl_basic_test extends chnl_root_test; // 继承实现代码复用
function new(int ntrans = 200, string name = "chnl_basic_test");
super.new(ntrans, name);
foreach(agent[i]) begin
this.agent[i].init.set_idle_cycles($urandom_range(1, 3));
end
$display("%s configured objects", this.name);
endfunction
endclass: chnl_basic_test
//省略其他test子类...
endpackage: chnl_pkg
- 接口句柄由set_interface() 获取并经由test->agent->initiator层层传递;理解组件连接、环境运行的层次结构。
import chnl_pkg::*; //导入package中定义的class
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
chnl_basic_test basic_test;
chnl_burst_test burst_test;
chnl_fifo_full_test fifo_full_test;
initial begin
// 测试环境例化
basic_test = new();
burst_test = new();
fifo_full_test = new();
// set_interface()收集各interface句柄并传递给各initiator对象
basic_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
burst_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
fifo_full_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
// 开始定向测试序列
basic_test.run();
burst_test.run();
fifo_full_test.run();
$display("*****************all of tests have been finished********************");
$finish();
end
吐槽一下csdn不支持Verilog语法高亮,所以就先用Java替代啦。