Lab2主要是使用之前学习的接口、仿真开始和结束、类以及包的使用,来优化Lab1的验证结构。逐渐从使用硬件盒子过渡到使用接口和软件盒子(class)来验证设计。
一、接口的使用
- 将验证组件和DUT之间通过接口来连接,所以验证组件
chnl_initiator
的端口变得非常干净,即chnl_intf
。在使用接口之前需要定义接口chnl_intf
和内部的接口,同时也要声明一个时钟块,它的功能是为了消除可能存在的竞争问题,确保时钟驱动数据之间有一定的延迟,以便于DUT顺利采样。 - 引入接口后的代码,例化的实例包括了只产生数据的
channel_generator
,只负责发送数据的chnl_initiator
以及作为验证组件和DUT之间的接口chnl_intf
。
实验要求:
chnl_initiator
发送的数据例如valid
和data
与时钟clk
均在同一个变化沿,没有任何延迟。这种0延迟的数据发送不利于波形的查看,要在之前的代码基础上使用intf.ck的方式来做数据驱动,再观察波形,查看驱动的数据与时钟上升沿的延迟。- 为了更好地控制相邻数据之间的空闲间隔,引入了一个变量
idle_cycles
,它表示相邻有效数据之间的间隔。之前的代码会使得有效数据之间保持固定的一个空闲周期,需要使用idle_cycles
来灵活控制有效数据之间的空闲周期。
二、仿真的开始和结束
使用fork-join语句来实现chnl_initiator
同时发送数据。还将不同的test也组装到task中,以此来区分不同的测试内容,这是由于每一个测试任务的测试目的和要求都不相同。
实验要求:
- 实现
burst_test()
方法,使得每个chnl_initiator
的idle_cycles
设置为0,同时发送500个数据,最后结束测试。 - 实现
fifo_full_test()
方法,使得无论采取什么数值的idle_cycles
也无论发送多少个数据,只要各个chnl_initiator
不停发送使得对应的channel缓存变为满标志(ready拉低),那么可以在三个channel都拉低ready时(不用同时拉低,可以先后拉低),便可以立即结束测试。
三、类的例化和类的成员
将之前用来封装验证功能的硬件盒子(module)中的数据和内容移植到软件盒子(class)中来。
实验要求:
- 将module的
chnl_initiator
和chnl_generator
改成class的chnl_initiator
和chnl_generator
,同时定义一个用来封装发送数据的类chnl_trans
。 - 由于每一个
chnl_initiator
都需要使用接口chnl_intf
来发送数据,在发送数据之前需要确保chnl_initiator
中的接口不是悬空的,即需要由外部被传递。所有需要通过调用chnl_initiator
的方法来完成接口的传递。
四、包的定义和类的继承
进一步引入新的类chnl_agent
、chnl_root_test
、chnl_basic_test
、chnl_burst_test
和chnl_fifo_full_test
,同时将所有的类(都是与channel相关的验证组件类)封装到专门包裹软件类的容器packagechnl_pkg
中,且完成编译。编译后的chnl_pkg
会被默认编译到work库中,与其他的module是并列放置的。
chnl_agent
:将它作为一个标准组件单元,包括generator、
- driver(initiator)和monitor。所以
chnl_initiator
和chnl_generator
应该放在agent中例化。 - task实现的测试任务由类来实现:父类
chnl_root_test
,子类chnl_basic_test
、chnl_burst_test
和chnl_fifo_full_test
。
五、验证结构框架图
六、代码实现
接口的使用
接口chnl_intf
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
例化接口
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
将接口里面的各个信号连接到mcdt上面
mcdt dut(
.clk_i (clk )
,.rstn_i (rstn )
,.ch0_data_i (chnl0_if.ch_data )
,.ch0_valid_i (chnl0_if.ch_valid )
,.ch0_ready_o (chnl0_if.ch_ready )
,.ch0_margin_o(chnl0_if.ch_margin )
,.ch1_data_i (chnl1_if.ch_data )
,.ch1_valid_i (chnl1_if.ch_valid )
,.ch1_ready_o (chnl1_if.ch_ready )
,.ch1_margin_o(chnl1_if.ch_margin )
,.ch2_data_i (chnl2_if.ch_data )
,.ch2_valid_i (chnl2_if.ch_valid )
,.ch2_ready_o (chnl2_if.ch_ready )
,.ch2_margin_o(chnl2_if.ch_margin )
,.mcdt_data_o (mcdt_data )
,.mcdt_val_o (mcdt_val )
,.mcdt_id_o (mcdt_id )
);
chnl_initiator
激励
module chnl_initiator(chnl_intf intf);
string name;
int idle_cycles = 1;
function automatic void set_idle_cycles(int n);
idle_cycles = n;
endfunction
function automatic void set_name(string s);
name = s;
endfunction
task automatic chnl_write(input logic[31:0] data);
@(posedge intf.clk);
// Please use the clocking drv_ck of chnl_intf to drive data
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= data;
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
$display("%t channel initiator [%s] sent data %x", $time, name, data);
// Apply variable idle_cycles and decide how many idle cycles to be
// inserted between two sequential data
repeat(idle_cycles) chnl_idle();
endtask
task automatic chnl_idle();
@(posedge intf.clk);
// Please use the clocking drv_ck of chnl_intf to drive data
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endmodule
例化initiator,需要接口做激励,将接口作为参数
chnl_initiator chnl0_init(chnl0_if);
chnl_initiator chnl1_init(chnl1_if);
chnl_initiator chnl2_init(chnl2_if);
chnl_generator产生数据
module chnl_generator;
int chnl_arr[$];
int num;
int id;
function automatic void initialize(int n);
id = n;
num = 0;
endfunction
function automatic int get_data();
int data;
data = 'h00C0_0000 + (id<<16) + num;
num++;
chnl_arr.push_back(data);
return data;
endfunction
endmodule
例化chnl_generator
chnl_generator chnl0_gen();
chnl_generator chnl1_gen();
chnl_generator chnl2_gen();
通过intf.ck
来做数据驱动
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= data;
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
idle_cycles
控制空闲间隔
int idle_cycles = 1;
function automatic void set_idle_cycles(int n);
idle_cycles = n;
endfunction
task automatic chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
// inserted between two sequential data
repeat(idle_cycles) chnl_idle();
验证组件初始化
initial begin
// verification component initializationi
chnl0_gen.initialize(0);
chnl1_gen.initialize(1);
chnl2_gen.initialize(2);
chnl0_init.set_name("chnl0_init");
chnl1_init.set_name("chnl1_init");
chnl2_init.set_name("chnl2_init");
chnl0_init.set_idle_cycles(0);
chnl1_init.set_idle_cycles(0);
chnl2_init.set_idle_cycles(0);
end
复位后连续发送数据
initial begin
@(posedge rstn);
repeat(5) @(posedge clk);
repeat(100) begin
chnl0_init.chnl_write(chnl0_gen.get_data());
end
chnl0_init.chnl_idle();
end
仿真的开始和结束
fork...join
连续并行发送数据
fork
repeat(100) chnl0_init.chnl_write(chnl0_gen.get_data());
repeat(100) chnl1_init.chnl_write(chnl1_gen.get_data());
repeat(100) chnl2_init.chnl_write(chnl2_gen.get_data());
join
basic_test
方法
// each channel send data with idle_cycles inside [1:3]
// each channel send out 100 data
// then to finish the test
task automatic basic_test();
// verification component initializationi
chnl0_gen.initialize(0);
chnl1_gen.initialize(1);
chnl2_gen.initialize(2);
chnl0_init.set_name("chnl0_init");
chnl1_init.set_name("chnl1_init");
chnl2_init.set_name("chnl2_init");
chnl0_init.set_idle_cycles($urandom_range(1, 3));
chnl1_init.set_idle_cycles($urandom_range(1, 3));
chnl2_init.set_idle_cycles($urandom_range(1, 3));
$display("basic_test initialized components");
wait (rstn === 1'b1);
repeat(5) @(posedge clk);
$display("basic_test started testing DUT");
// Please check the SV book for fork-join basic knowledge
// and get understood it is for parallel thread running
fork
repeat(100) chnl0_init.chnl_write(chnl0_gen.get_data());
repeat(100) chnl1_init.chnl_write(chnl1_gen.get_data());
repeat(100) chnl2_init.chnl_write(chnl2_gen.get_data());
join
$display("basic_test finished testing DUT");
endtask
burst_test
方法
// each channel send data with idle_cycles == 0
// each channel send out 500 data
// then to finish the test
task automatic burst_test();
// verification component initializationi
chnl0_gen.initialize(0);
chnl1_gen.initialize(1);
chnl2_gen.initialize(2);
chnl0_init.set_name("chnl0_init");
chnl1_init.set_name("chnl1_init");
chnl2_init.set_name("chnl2_init");
chnl0_init.set_idle_cycles(0);
chnl1_init.set_idle_cycles(0);
chnl2_init.set_idle_cycles(0);
$display("basic_test initialized components");
wait (rstn === 1'b1);
repeat(5) @(posedge clk);
$display("basic_test started testing DUT");
// Please check the SV book for fork-join basic knowledge
// and get understood it is for parallel thread running
fork
begin
repeat(500) chnl0_init.chnl_write(chnl0_gen.get_data());
chnl0_init.chnl_idle();
end
begin
repeat(500) chnl1_init.chnl_write(chnl1_gen.get_data());
chnl1_init.chnl_idle();
end
begin
repeat(500) chnl2_init.chnl_write(chnl2_gen.get_data());
chnl2_init.chnl_idle();
end
join
fork
wait(chnl0_init.intf.ch_margin == 'h20);
wait(chnl1_init.intf.ch_margin == 'h20);
wait(chnl2_init.intf.ch_margin == 'h20);
join
$display("basic_test finished testing DUT");
endtask
fifo_full_test
方法
// The test should be immediately finished when all of channels
// have been reached fifo full state, but not all reaching
// fifo full at the same time
task automatic fifo_full_test();
// verification component initializationi
chnl0_gen.initialize(0);
chnl1_gen.initialize(1);
chnl2_gen.initialize(2);
chnl0_init.set_name("chnl0_init");
chnl1_init.set_name("chnl1_init");
chnl2_init.set_name("chnl2_init");
chnl0_init.set_idle_cycles(0);
chnl1_init.set_idle_cycles(0);
chnl2_init.set_idle_cycles(0);
$display("fifo_full_test started testing DUT");
fork: fork_all_run
forever chnl0_init.chnl_write(chnl0_gen.get_data());
forever chnl1_init.chnl_write(chnl1_gen.get_data());
forever chnl2_init.chnl_write(chnl2_gen.get_data());
join_none
$display("fifo_full_test: 3 initiators running now");
$display("fifo_full_test: waiting 3 channel fifos to be full");
fork
wait(chnl0_init.intf.ch_margin == 0);
wait(chnl1_init.intf.ch_margin == 0);
wait(chnl2_init.intf.ch_margin == 0);
join
$display("fifo_full_test: 3 channel fifos have reached full");
$display("fifo_full_test: stop 3 initiators running");
disable fork_all_run;
$display("fifo_full_test: set and ensure all agents' initiator are idle state");
fork
chnl0_init.chnl_idle();
chnl1_init.chnl_idle();
chnl2_init.chnl_idle();
join
$display("fifo_full_test waiting DUT transfering all of data");
fork
wait(chnl0_init.intf.ch_margin == 'h20);
wait(chnl1_init.intf.ch_margin == 'h20);
wait(chnl2_init.intf.ch_margin == 'h20);
join
$display("fifo_full_test: 3 channel fifos have transferred all data");
$display("fifo_full_test finished testing DUT");
endtask
初始化测试任务
initial begin
basic_test();
burst_test();
fifo_full_test();
$display("*****************all of tests have been finished********************");
$finish();
end
类的例化和类的成员
将chnl_initiator
从硬件盒子module转换成软件盒子class,chnl_initiator
里面的接口参数以及端口,转变成了class里面的成员变量,注意class里面声明接口的指针必须要加上virtual
关键字。
class chnl_initiator;
local string name;
local int idle_cycles;
virtual chnl_intf intf;
function new(string name = "chnl_initiator");
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;
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();
endtask
task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass
chnl_trans
类封装发送的数据
class chnl_trans;
int data;
int id;
int num;
endclass
将chnl_generator
从硬件盒子module转换成软件盒子class
class chnl_generator;
chnl_trans trans[$];
int num;
int id;
chnl_trans t;
function new(int n);
this.id = n;
this.num = 0;
endfunction
function chnl_trans get_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_initiator
、chnl_generator
句柄的声明
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
chnl_initiator chnl0_init;
chnl_initiator chnl1_init;
chnl_initiator chnl2_init;
chnl_generator chnl0_gen;
chnl_generator chnl1_gen;
chnl_generator chnl2_gen;
chnl_initiator
、chnl_generator
的例化
initial begin
// instantiate the components chn0/1/2_init chnl0/1/2_gen
chnl0_init = new("chnl0_init");
chnl1_init = new("chnl1_init");
chnl2_init = new("chnl2_init");
chnl0_gen = new(0);
chnl1_gen = new(1);
chnl2_gen = new(2);
// assign the interface handle to each chnl_initiator objects
chnl0_init.set_interface(chnl0_if);
chnl1_init.set_interface(chnl1_if);
chnl2_init.set_interface(chnl2_if);
// START TESTs
$display("*****************all of tests have been finished********************");
basic_test();
burst_test();
fifo_full_test();
$finish();
end
包的定义和类的继承
相比于之前的代码,新添加了一个chnl_agent
类,该类包括了chnl_initiator
、chnl_generator
两个类,之前的代码chnl_initiator
、chnl_generator
直接在TB里面例化,而有了chnl_agent
类之后,可以直接例化一个chnl_agent
类,例化后就可以包括那两个类。
class chnl_agent;
chnl_generator gen;
chnl_initiator init;
local int ntrans;
local virtual chnl_intf vif;
function new(string name = "chnl_agent", int id = 0, int ntrans = 1);
this.gen = new(id);
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());
endtask
endclass: chnl_agent
chnl_root_test
类,从顶层的test里面例化3个agent,也就是例化了3个initiator和3个generator。其中例化时传递的参数ntrans
会供agent的run()
来使用,进而run()
会调用initiator的chnl_write()
从generator中拿到发送数据。这样,chnl_root_test
和chnl_agent()
之间建立了联系,chnl_agent()
和chnl_initiator
、chnl_generator
之间也建立了联系。而chnl_root_test
类中也有run()
,它的run()
作用是调用agent的run()
,并且通过fork…join并行运行3个agent。
new函数构建了整个验证环境的层次,而run函数使得层次之间可以互动起来。
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 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
chnl_basic_test
类继承chnl_root_test
类,在new函数里调用super.new()
,否则将无法执行父类的new,也就无法例化父类中的agent。同时继承还可以减少重复的代码量。
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
chnl_burst_test
类继承chnl_root_test
类
class chnl_burst_test extends chnl_root_test;
function new(int ntrans = 500, string name = "chnl_burst_test");
super.new(ntrans, name);
foreach(agent[i]) begin
this.agent[i].init.set_idle_cycles(0);
end
$display("%s configured objects", this.name);
endfunction
endclass: chnl_burst_test
chnl_fifo_full_test
类继承chnl_root_test
类
class chnl_fifo_full_test extends chnl_root_test;
function new(int ntrans = 1_000_000, string name = "chnl_fifo_full_test");
super.new(ntrans, name);
foreach(agent[i]) begin
this.agent[i].init.set_idle_cycles(0);
end
$display("%s configured objects", this.name);
endfunction
task run();
$display("%s started testing DUT", this.name);
fork: fork_all_run
agent[0].run();
agent[1].run();
agent[2].run();
join_none
$display("%s: 3 agents running now", this.name);
$display("%s: waiting 3 channel fifos to be full", this.name);
fork
wait(agent[0].vif.ch_margin == 0);
wait(agent[1].vif.ch_margin == 0);
wait(agent[2].vif.ch_margin == 0);
join
$display("%s: 3 channel fifos have reached full", this.name);
$display("%s: stop 3 agents running", this.name);
disable fork_all_run;
$display("%s: set and ensure all agents' initiator are idle state", this.name);
fork
agent[0].init.chnl_idle();
agent[1].init.chnl_idle();
agent[2].init.chnl_idle();
join
$display("%s waiting DUT transfering all of data", this.name);
fork
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
endclass: chnl_fifo_full_test
从包chnl_pkg
中引入定义的类
import chnl_pkg::*;
发送激励之前,还需要传递接口。TB看到的最高的验证环境的层次是软件的对象test,test裸露在TB环境中,想要把interface传递进来,那么test里面要有set_interface()
,然后test拿到指针,这些指针通过agent再次调用agent的set_interface()
,agent拿到指针后再次调用init.set_interface()
,进而发送激励。整个interface的流转是从test->agent->initiator层层递进传递。最顶层test传递完接口之后,调用run()
运行起来,test的run()
又调用agent的run()
,一层层调用各个组件的run()
。test里面的new()
是调用了agent里面的new()
,agent里面的new()
又调用了generator和initiator里面的new()
,这样从顶层往下一层一层创建实例。
test继承的是构建环境的层次结构,变化的是激励发送的内容。
initial begin
// Instantiate the three test environment
basic_test = new();
burst_test = new();
fifo_full_test = new();
// assign the interface handle to each chnl_initiator objects
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);
// START TESTs
basic_test.run();
burst_test.run();
fifo_full_test.run();
$display("*****************all of tests have been finished********************");
$finish();
end