前言:在实验3添加了随机约束来随机化产生的数据,使用了信箱来完成类之间的通信,添加了monitor、checker让验证的组件越来越完整。
种一棵树最好的时间是十年前,其次是现在。不是吗?
实验3需要用到随机化、信箱、类的多态这几个主要知识,大家可以先去学习哦!
将package和tb文件分开成两个文件。
在package包括了 chnl_trans、chnl_initiator、chnl_generator、chnl_monitor、chnl_checker和chnl_root_test。
package chnl_pkg3;
// static variables shared by resources
semaphore run_stop_flags = new();
chnl_trans在实验2用来存放数据,内部结构很简单。在实验3给他添加了随机约束,让产生的数据更复杂。
添加了控制数据传输间隔的变量,声明为了随机变量,trans中的数据从一个数据变成了多个数据。
function new:
变量声明前加rand,在constraint中添加约束。
新添加了pkt_nidles,代表数据包之间的间隔。假设一共有5批货,每批货100件,1件与1件的间隔是data_nidles,当发完第一批货的100件,与第二批货之间的间隔是pkt_nidles。
通过foreach遍历的方法,随机产生数据。简单的分析下产生的数据:ch_id左移了24位,pkt_id左移了8位,剩下的八位留给了变量i。假设有三个发送数据的端口,把3个ch_id赋值为00、01、02,这样传出的数据来自于哪个端口就一目了然。
class chnl_trans;
rand bit[31:0] data[];//rand随机
rand int ch_id;
rand int pkt_id;//数据包的id
rand int data_nidles;//单个数据之间的间隔
rand int pkt_nidles;//数据包之间的间隔
bit rsp;
local static int obj_id = 0; //local只能在本类中使用的变量obj_id
constraint cstr{ //约束内容
soft data.size inside {[4:8]};//数据是4到8位
foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
soft ch_id == 0; //soft软约束
soft pkt_id == 0;
data_nidles inside {[0:2]};
pkt_nidles inside {[1:10]};
};
每例化一次trans,让obj_id加1,用来统计总共例化了多少次。
function new(); //new 函数,每例化一次trans,obj_id++
this.obj_id++;
endfunction
让trans具有克隆的功能,创建了句柄c。并例化,把上边的所有变量全部复制给句柄c指向的对象。最后返回值为句柄c。
function chnl_trans clone();
chnl_trans c = new();
c.data = this.data;
c.ch_id = this.ch_id;
c.pkt_id = this.pkt_id;
c.data_nidles = this.data_nidles;
c.pkt_nidles = this.pkt_nidles;
c.rsp = this.rsp;
return c;
endfunction
一个用来打印的function。
function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_trans object content is as below: \n")};
s = {s, $sformatf("obj_id = %0d: \n", this.obj_id)};
foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction
endclass: chnl_trans
与之前的实验相比,init的功能主要还是chnl_write,不过是从信箱中获取数据,并且会告诉信箱,它有没有把数据消化完毕。
class chnl_initiator;
local string name;
local virtual chnl_intf intf;//接口
mailbox #(chnl_trans) req_mb; //信箱req 和 rsp
mailbox #(chnl_trans) rsp_mb;
function new(string name = "chnl_initiator");
this.name = name;
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
当调用run时,会执行dirve这个任务。
第一行调用类chnl_trans,生成了两个句柄req、rsp。当上升沿来临。开始死循环,不断从信箱中取数据req_mb.get(req),假如信箱中放入了req,执行chnl_write(req)。并把req的值克隆给句柄rsp,并把其中的rsp改为1。
task run();
this.drive();
endtask
task drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin//死循环,不断要求从mb中取数据。
this.req_mb.get(req);//initiator从信箱中取出gen放的数据req,此时mb里边就没有东西了
this.chnl_write(req);//通过chnl_write把req中的data,写入interface中
rsp = req.clone();//使用克隆
rsp.rsp = 1;//trans类中定义了bit型的rsp,默认初始值为0,此时改变变量rsp的值
this.rsp_mb.put(rsp);//把此时的rsp再传入gen,类似于给gen一个反馈,告诉gen自己已经消化完毕。
end
endtask
task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
@(negedge intf.clk);
wait(intf.ch_ready === 'b1);
$display("%0t channel initiator [%s] sent data %x", $time, name, t.data[i]);
repeat(t.data_nidles) chnl_idle();
end
repeat(t.pkt_nidles) chnl_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;
rand int pkt_id = -1;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;
mailbox #(chnl_trans) req_mb;//两个信箱
mailbox #(chnl_trans) rsp_mb;
constraint cstr{
soft ch_id == -1;
soft pkt_id == -1;
soft data_size == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft ntrans == 10;
}
在new函数中将两个信箱例化,现在信箱可以使用。
function new();
this.req_mb = new();
this.rsp_mb = new();
endfunction
重复ntrans次,task send_trans。 把数据传入到信箱给init,init取到数据后会给gen一个反馈。发送完数据后,会给semaphore 放入一把钥匙,在test中会使用到。
task run();
repeat(ntrans) send_trans();
run_stop_flags.put(); //
endtask
随机化trans中的变量,并通过mailbox(mb)发送给initiator。
asset断言类似于if_else。
chnl_trans中的有bit型的rsp,默认初始值为0,在initiator中将rsp的值赋为了1。
local::ch_id >= 0 -> ch_id == local::ch_id;
// generate transaction and put into local mailbox
task send_trans();
chnl_trans req, rsp;
req = new();
assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
})
else $fatal("[RNDFAIL] channel packet randomization failure!");
this.pkt_id++;
$display(req.sprint());//req为trans,里边含有sprint打印函数
this.req_mb.put(req);
this.rsp_mb.get(rsp);
$display(rsp.sprint());//rsp为trans,里边含有sprint打印函数
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask
function string sprint();
string s;
s = {s, $sformatf("=======================================\n")};
s = {s, $sformatf("chnl_generator object content is as below: \n")};
s = {s, $sformatf("ntrans = %0d: \n", this.ntrans)};
s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
s = {s, $sformatf("data_size = %0d: \n", this.data_size)};
s = {s, $sformatf("=======================================\n")};
return s;
endfunction
function void post_randomize();
string s;
s = {"AFTER RANDOMIZATION \n", this.sprint()};
$display(s);
endfunction
endclass: chnl_generator
class chnl_agent;
local string name;
chnl_initiator init;
chnl_monitor mon;
// USER TODO 3.2
// Refer to how we create, set virtual interface and run the initiator
// object, use do the similar action to the monitor object
virtual chnl_intf vif;
//初始化
function new(string name = "chnl_agent");
this.name = name;
this.init = new({name, ".init"});
this.mon = new({name, ".mon"});
endfunction
function void set_interface(virtual chnl_intf vif);
this.vif = vif;
init.set_interface(vif);
mon.set_interface(vif);
endfunction
//运行
task run();
fork
init.run();
mon.run();
join
endtask
endclass: chnl_agent
在test中创建了task do_config,来专门随机化gen。 在test随机化gen,依靠gen被随机化的成员变量进一步随机gen中的trans,这样可以实现test灵活控制全局。
class chnl_root_test;
chnl_generator gen[3];
chnl_agent agents[3];
mcdt_monitor mcdt_mon;//monitor
chnl_checker chker;//checker
protected string name;//子类可以使用,但是外界无法调用
event gen_stop_e;//事件event
//初始化
function new(string name = "chnl_root_test");
this.name = name;
this.chker = new();
foreach(agents[i]) begin //使用foreach全部遍历
this.agents[i] = new($sformatf("chnl_agent%0d",i));
this.gen[i] = new();
// initior中的mb与gen中的mb相连接
this.agents[i].init.req_mb = this.gen[i].req_mb;
this.agents[i].init.rsp_mb = this.gen[i].rsp_mb;
this.agents[i].mon.mon_mb = this.chker.in_mbs[i];
end
this.mcdt_mon = new();
this.mcdt_mon.mon_mb = this.chker.out_mb;
$display("
%s instantiated and connected objects", this.name);
endfunction
virtual task gen_stop_callback();
endtask
virtual task run_stop_callback(); //控制仿真停止
$display("run_stop_callback enterred");
// by default, run would be finished once generators raised 'finish'
// flags
$display("%s: wait for all generators have generated and tranferred transcations", this.name);
run_stop_flags.get(3); //当钥匙数量为3时,表示有三个gen完成了发送数据。
$display($sformatf("*****************%s finished********************", this.name));
$finish();
endtask
virtual task run(); //首先do_config生成随机数,分别运行三个agents,在agents的task run中再运行initiator
$display($sformatf("*****************%s started********************", this.name));
this.do_config();
fork
agents[0].run();
agents[1].run();
agents[2].run();
mcdt_mon.run();
chker.run();
join_none
// run first the callback thread to conditionally disable gen_threads
fork
this.gen_stop_callback();//当event触发时,停止gen的运行。
@(this.gen_stop_e) disable gen_threads;
join_none
fork : gen_threads//控制产生数据的gen。
gen[0].run();
gen[1].run();
gen[2].run();
join
run_stop_callback(); // wait until run stop control task finished
endtask
virtual function void set_interface(virtual chnl_intf ch0_vif
,virtual chnl_intf ch1_vif
,virtual chnl_intf ch2_vif
,virtual mcdt_intf mcdt_vif
);
agents[0].set_interface(ch0_vif);
agents[1].set_interface(ch1_vif);
agents[2].set_interface(ch2_vif);
mcdt_mon.set_interface(mcdt_vif);
endfunction
用来随机化gen,gen再随机化trans。
virtual function void do_config();
assert(gen[0].randomize() with {ntrans==100; data_nidles==0; pkt_nidles==1; data_size==8;})
else $fatal("[RNDFAIL] gen[0] randomization failure!");
assert(gen[1].randomize() with {ntrans==50; data_nidles inside {[1:2]}; pkt_nidles inside {[3:5]}; data_size==6;})
else $fatal("[RNDFAIL] gen[1] randomization failure!");
assert(gen[2].randomize() with {ntrans==80; data_nidles inside {[0:1]}; pkt_nidles inside {[1:2]}; data_size==32;})
else $fatal("[RNDFAIL] gen[2] randomization failure!");
endfunction
endclass
修改了root_test里的do_config。
class chnl_basic_test extends chnl_root_test;
function new(string name = "chnl_basic_test");
super.new(name);
endfunction
virtual function void do_config();
super.do_config();
assert(gen[0].randomize() with {ntrans==100; data_nidles==0; pkt_nidles==1; data_size==8;})
else $fatal("[RNDFAIL] gen[0] randomization failure!");
assert(gen[1].randomize() with {ntrans==50; data_nidles inside {[1:2]}; pkt_nidles inside {[3:5]}; data_size==6;})
else $fatal("[RNDFAIL] gen[1] randomization failure!");
assert(gen[2].randomize() with {ntrans==80; data_nidles inside {[0:1]}; pkt_nidles inside {[1:2]}; data_size==32;})
else $fatal("[RNDFAIL] gen[2] randomization failure!");
endfunction
endclass: chnl_basic_test
// USER TODO 2.4
// each channel send data packet number inside [80:100]
// data_nidles == 0, pkt_nidles == 1, data_size inside {8, 16, 32}
class chnl_burst_test extends chnl_root_test;
function new(string name = "chnl_burst_test");
super.new(name);
endfunction
virtual function void do_config();
super.do_config();
assert(gen[0].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
else $fatal("[RNDFAIL] gen[0] randomization failure!");
assert(gen[1].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
else $fatal("[RNDFAIL] gen[1] randomization failure!");
assert(gen[2].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
else $fatal("[RNDFAIL] gen[2] randomization failure!");
endfunction
endclass: chnl_burst_test
// USER TODO 2.5
// keep channel sending out data packet with number, and please
// let at least two slave channels raising fifo_full (ready=0) at the same time
// and then to stop the test
首先是初始化,do_config。
class chnl_fifo_full_test extends chnl_root_test;
function new(string name = "chnl_fifo_full_test");
super.new(name);
endfunction
virtual function void do_config();
super.do_config();
assert(gen[0].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
else $fatal("[RNDFAIL] gen[0] randomization failure!");
assert(gen[1].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
else $fatal("[RNDFAIL] gen[1] randomization failure!");
assert(gen[2].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
else $fatal("[RNDFAIL] gen[2] randomization failure!");
endfunction
// get all of 3 channles slave ready signals as a 3-bits vector
local function bit[3] get_chnl_ready_flags();
return {agents[2].vif.mon_ck.ch_ready
,agents[1].vif.mon_ck.ch_ready
,agents[0].vif.mon_ck.ch_ready
};
endfunction
virtual task gen_stop_callback();
bit[3] chnl_ready_flags;
$display("gen_stop_callback enterred");
@(posedge agents[0].vif.rstn);
forever begin
@(posedge agents[0].vif.clk);
chnl_ready_flags = this.get_chnl_ready_flags();
if($countones(chnl_ready_flags) <= 1) break;
end
$display("%s: stop 3 generators running", this.name);
-> this.gen_stop_e;
endtask
等数据传输完毕,结束仿真。
virtual task run_stop_callback();
$display("run_stop_callback enterred")
$display("%s: waiting DUT transfering all of data", this.name);
fork
wait(agents[0].vif.ch_margin == 'h20);
wait(agents[1].vif.ch_margin == 'h20);
wait(agents[2].vif.ch_margin == 'h20);
join
$display("%s: 3 channel fifos have transferred all data", this.name);
$display($sformatf("*****************%s finished********************", this.name));
$finish();
endtask
endclass: chnl_fifo_full_test
endpackage
最后在checker做了数据比对。
//声明了结构体,储存检测到的id和data。
typedef struct packed {
bit[31:0] data;
bit[1:0] id;
} mon_data_t;
声明了信箱,但没有例化,最后在test中与checker连接。
class chnl_monitor;
local string name;
local virtual chnl_intf intf;
mailbox #(mon_data_t) mon_mb;//声明信箱mon_mb只能存放结构体mon_data_t中的变量
初始化、连接interface会在agent里完成。
function new(string name="chnl_monitor");//初始化name
this.name = name;
endfunction
function void set_interface(virtual chnl_intf intf); //连接interface
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
死循环,持续监视数据,当总线上的时序变化,符合有效数据的时序,把数据通过句柄写入checker。monitor和initiator都是死循环无法结束,结束只能停止发送数据,也就是停止agent。
task run();//run
this.mon_trans();
endtask
task mon_trans();
mon_data_t m; //结构体m
forever begin
@(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1)); //时序对把数据传入m.data,并放入信箱。
m.data = intf.mon_ck.ch_data;
mon_mb.put(m);
$display("%0t %s monitored channle data %8x", $time, this.name, m.data);
end
endtask
endclass
监视arbiter发送出来的数据,首先连接mcdt的interface,接着获取数据,并赋值给结构体,放入checker的信箱。与输入的数据在checker作比对。
mcdt_monitor的例化在root_test里,在root_test里将checker的信箱与mcdt_monitor的信箱建立了连接。
class mcdt_monitor;
local string name;
local virtual mcdt_intf intf;
mailbox #(mon_data_t) mon_mb; //信箱:监视mcdt输出的数据
function new(string name="mcdt_monitor");
this.name = name;
endfunction
task run();
this.mon_trans();
endtask
function void set_interface(virtual mcdt_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task mon_trans();
mon_data_t m;
forever begin
@(posedge intf.clk iff intf.mon_ck.mcdt_val==='b1);
m.data = intf.mon_ck.mcdt_data;
m.id = intf.mon_ck.mcdt_id;
mon_mb.put(m);
$display("%0t %s monitored mcdt data %8x and id %0d", $time, this.name, m.data, m.id);
end
endtask
endclass
class chnl_checker;
local string name;
local int error_count;//有一次错误加1
local int cmp_count; //比较一次加1
mailbox #(mon_data_t) in_mbs[3];//共4个信箱,3个输入1个输出,都完成了例化
mailbox #(mon_data_t) out_mb;
function new(string name="chnl_checker");
this.name = name;
foreach(this.in_mbs[i]) this.in_mbs[i] = new();
this.out_mb = new();
this.error_count = 0;
this.cmp_count = 0;
endfunction
task run();
this.do_compare();
endtask
task do_compare();
mon_data_t im, om;
forever begin
// USER TODO 3.3
// compare data once there is data in in_mb0/in_mb1/in_mb2 and out_mb
// first, get om from out_mb, and im from one of in_mbs
out_mb.get(om);
case(om.id)
0: in_mbs[0].get(im);
1: in_mbs[1].get(im);
2: in_mbs[2].get(im);
default: $fatal("id %0d is not available", om.id);
endcase
if(om.data != im.data) begin
this.error_count++;
$error("[CMPFAIL] Compared failed! mcdt out data %8x ch_id %0d is not equal with channel in data %8x", om.data, om.id, im.data);
end
else begin
$display("[CMPSUCD] Compared succeeded! mcdt out data %8x ch_id %0d is equal with channel in data %8x", om.data, om.id, im.data);
end
this.cmp_count++;
end
endtask
endclass
在tb文件与之前最大的区别是加入了$value$plusargs("TESTNAME=%s", name)
通过在仿真器输入vsim -novopt -solvefaildebug -sv_seed random
+TESTNAME=chnl_root_test
work.tb2,来运行相应的test。
传递不同的随机种子,和测试名称即可完成响应的测试,是回归测试的雏形。
`timescale 1ns/1ps
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
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input ch_data, ch_valid, ch_ready, ch_margin;
endclocking
endinterface
interface mcdt_intf(input clk, input rstn);
logic [31:0] mcdt_data;
logic mcdt_val;
logic [ 1:0] mcdt_id;
clocking mon_ck @(posedge clk);
default input #1ns output #1ns;
input mcdt_data, mcdt_val, mcdt_id;
endclocking
endinterface
module tb3_ref;
logic clk;
logic rstn;
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_if.mcdt_data )
,.mcdt_val_o (mcdt_if.mcdt_val )
,.mcdt_id_o (mcdt_if.mcdt_id )
);
// clock generation
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
// reset trigger
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
import chnl_pkg3::*;
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
mcdt_intf mcdt_if(.*);
chnl_basic_test basic_test;
chnl_burst_test burst_test;
chnl_fifo_full_test fifo_full_test;
chnl_root_test tests[string];
string name;
initial begin
basic_test = new();
burst_test = new();
fifo_full_test = new();
tests["chnl_basic_test"] = basic_test;
tests["chnl_burst_test"] = burst_test;
tests["chnl_fifo_full_test"] = fifo_full_test;
if($value$plusargs("TESTNAME=%s", name)) begin
if(tests.exists(name)) begin
tests[name].set_interface(chnl0_if, chnl1_if, chnl2_if, mcdt_if);
tests[name].run();
end
else begin
$fatal($sformatf("[ERRTEST], test name %s is invalid, please specify a valid name!", name));
end
end
else begin
$display("NO runtime optiont +TESTNAME=xxx is configured, and run default test chnl_basic_test");
tests["chnl_basic_test"].set_interface(chnl0_if, chnl1_if, chnl2_if, mcdt_if);
tests["chnl_basic_test"].run();
end
end
endmodule
关于实验代码、验证相关的资料,你找的到的找不到的都可以从公众号"袁小丑"免费获取!