前言
把checker加入到环境中后,环境组件基本就搭建完成了,试着跑了跑出了发现之前的pkt_data有一些问题外,还发现仿真结束机制太不合理了,过于简单粗暴。于是把结束仿真的行为梳理一下,做的更合理一些。
参考了VMM的思路,做一个漏洞百出的结束机制:所有的组件维护各自的idle状态,当所有的组件都进入了idle状态后,则判定仿真可以结束;同时设置一个“看门狗”进行计时,如果数据总线上已经长时间没有动作而组件又不进入idle状态的话,则报仿真超时推出退出仿真。
之前相关博文:
【验证小白】只有SV+modelsim学验证(1)——把平台搭起来_尼德兰的喵的博客-CSDN博客_modelsim sv
【验证小白】只有SV+modelsim学验证(2)——加monitor到环境中_尼德兰的喵的博客-CSDN博客_sv中的monitor
【验证小白】只有SV+modelsim学验证(3)——加checker到环境中_尼德兰的喵的博客-CSDN博客
env_cfg
为了便于配置与管理,添加env_cfg类型数据在env中例化,并将其传入所有组件。
`ifndef ENV_CFG_SV
`define ENV_CFG_SV
class env_cfg;
bit chk_idle;
bit gen_idle;
bit drv_idle;
bit mon_idle;
int env_wait_pkt_time = 10000;
int mon_wait_pkt_time = 1000;
int drv_wait_pkt_time = 1000;
int chk_wait_pkt_time = 1000;
endclass:env_cfg
`endif
env_cfg中维护四个idle状态,分别由gen/drv/mon/chk来进行置位表征自己组件已经工作结束,同意结束仿真。
下面的wait_time为组件允许空转时间,一旦超过这个空转时间的话,组件就可以对xxx_idle进行职位。
env_wait_pkt_time为env中“看门狗”等待时间,超过这个时间不喂狗的话,仿真报超市退出。
env_cfg在env中例化,并且传入每一个组件(最后会附所有完整代码)。
gen = new(cfg, gen2drv_chan, gen2chk_chan);
drv = new(cfg, dif, gen2drv_chan);
mon = new(cfg, mif, mon2chk_chan);
chk = new(cfg, gen2chk_chan, mon2chk_chan);
gen结束仿真
gen结束仿真的机制最为简单,只要把所有要发的包都放进到chan中了那么就是完成了工作,代码如下。
task pkt_gen::run();
pkt_data send_pkt;
pkt_data chk_pkt;
$display("At %0t, [GEN NOTE]: send_num = %0d", $time, send_num);
repeat(send_num) begin
assert(pkt.randomize());
$cast(send_pkt, pkt.copy());
$cast(chk_pkt, pkt.copy());
gen2drv_chan.put(send_pkt);
gen2chk_chan.put(chk_pkt);
end
this.cfg.gen_idle = 1;
//$display("At %0t, [GEN NOTE]: gen over pkt", $time);
endtask
drv结束仿真
drv应该在把所有从gen中接收到的包发送出去后就可以结束仿真,但问题是drv并不止都一共要发多少包不知道会不会还有下一个包到来。因此我只好把drv的结束仿真机制设置为一段时间内drv总线上没有驱动新的数据,那么则判定drv工作已经完成(实际上我认为应该再加一条gen2drv_chan已经空了,不过我没加)。实现代码如下。
task pkt_drv::set_idle();
while(1)begin
@(negedge top.clk);
if(this.dif.vld == 1) idle_cnt = 0;
else idle_cnt++;
if(idle_cnt > this.cfg.drv_wait_pkt_time) this.cfg.drv_idle = 1;
else this.cfg.drv_idle = 0;
end
endtask:set_idle
mon结束仿真
mon结束仿真的方式和drv实现类似,其监视的总线上如果长时间没有动作,那么判定可以结束仿真。
else begin
wait_time++;
if(wait_time <= wait_pkt_time) begin
wait_time++;
end
else break;
end
end
this.cfg.mon_idle = 1;
chk结束仿真
chk结束仿真的思路是,每一拍都将idle_cnt加1,而如果两个chan中任意一个有收到数据,就将idle_cnt置0。这样当两个chan长时间没有没有新数据到来的话,就判定仿真结束。
task pkt_chk::set_idle();
while(1)begin
@(negedge top.clk);
idle_cnt++;
if(idle_cnt > this.cfg.chk_wait_pkt_time) this.cfg.chk_idle = 1;
else this.cfg.chk_idle = 0;
end
endtask:set_idle
“看门狗”结束仿真
如果仿真真的出现了问题,比如mon始终收不全一个包或者丢失了一个包导致组件始终无法结束仿真,那么就需要“看门狗”来动手结束仿真。实现的代码如下。
#100;
fork
begin
wait(cfg.gen_idle);
wait(cfg.drv_idle);
wait(cfg.mon_idle);
wait(cfg.chk_idle);
$display("At %0t, [ENV NOTE]: normal finish", $time);
end
begin
while(1)begin
@(negedge top.clk);
if(this.dif.vld) wait_time = 0;
else wait_time++;
if(wait_time > this.cfg.env_wait_pkt_time) break;
end
$display("At %0t, [ENV ERROR]: time out!!!!!", $time);
end
join_any
结束仿真的两个线程,一个线程是所有组件同意结束,另外的线程是看门狗杀死仿真,通过fork-join_any的方式实现二者有其一就可以结束仿真。当数据总线上有信元驱动时进行喂狗,否则看门狗持续加1。
不过这样做目前还是有很大问题的,比如drv真的做错了,一个包持续驱动vld始终不能降下来,那么环境就挂死在这里了事实上监控channel是更为合理的。因此之后这里的结束仿真机制可能是要再进行调整的,容我慢慢想下怎么才能在现有简陋条件下尽量完备些。
?,目前的结束仿真机制就是这样的,到此环境搭建的挺好的了,看一下仿真效果:
run 100000ns
# At 0, [ENV NOTE]: environment::build() start!
# At 0, [ENV NOTE]: environment::build() over!
# At 0, [GEN NOTE]: send_num = 100
# At 42570, [ENV NOTE]: normal finish
# At 43570, [ENV NOTE]: report start
# ----------------------------------------------------------------------------------------------------
# [CHECKER REPORT]expect pkt_num=100, actual pkt_num=100, match pkt_num=100, not match pkt_num=0
# ----------------------------------------------------------------------------------------------------
# At 44070, [ENV NOTE]: report over
# At 44070, [TESt NOTE]: simulation finish~~~~~~~~~~~~~~~~~~
# ** Note: $finish : C:/Users/gaoji/Desktop/sv test/test.sv(17)
# Time: 44070 ns Iteration: 1 Instance: /top/u_test
非常完美!
下一步准备加入覆盖率。
附目前为止所有文件
module top();
logic clk;
logic rst_n;
initial begin
#0ns clk = 0;
forever #5ns clk = ~clk;
end
initial begin
#0ns rst_n = 0;
#225ns rst_n = 1;
end
pkt_if u_if(clk, rst_n);
test u_test(u_if, u_if, clk, rst_n);
endmodule
`ifndef PKT_DATA_SV
`define PKT_DATA_SV
class pkt_data;
rand bit [7:0] payload_q[$];
rand int interval;
rand int pkt_len;
bit send_over;
bit [10:0] data[$];
constraint data_size_cons{
payload_q.size() == pkt_len;
};
constraint pkt_len_cons{
pkt_len inside {[1:50]};
//pkt_len == 5;
};
constraint interval_cons{
interval inside {[3:6]};
};
extern function new();
extern virtual function string psprintf(string preset = "");
extern virtual function bit compare(pkt_data to);
extern virtual function void pack();
extern virtual function void unpack();
extern virtual function pkt_data copy(pkt_data to=null);
endclass
function pkt_data::new();
endfunction
function bit pkt_data::compare(pkt_data to);
bit match = 1;
if(this.pkt_len != to.pkt_len) match = 0;
foreach(payload_q[i]) if(this.payload_q[i] != to.payload_q[i])
match = 0;
return match;
endfunction:compare
function void pkt_data::pack();
foreach (this.payload_q[i]) begin
if (i==0 & i==pkt_len-1) //modify!!!!!
this.data.push_back({1'b1, 1'b1, 1'b1, payload_q[i]});
else if (i==0)
this.data.push_back({1'b1, 1'b1, 1'b0, payload_q[i]});
else if (i==pkt_len-1)
this.data.push_back({1'b1, 1'b0, 1'b1, payload_q[i]});
else
this.data.push_back({1'b1, 1'b0, 1'b0, payload_q[i]});
end
for(int i=0; i<interval; i++)begin
this.data.push_front({'0});
end
endfunction
function string pkt_data::psprintf(string preset = "");
psprintf = {preset, $psprintf("pkt_len = %0d", pkt_len)};
foreach(payload_q[i])
psprintf = {psprintf, ",", $psprintf("payload[%0d] = 'h%0h", i, payload_q[i])};
endfunction
function pkt_data pkt_data::copy(pkt_data to=null);
pkt_data tmp;
if (to == null)
tmp = new();
else
$cast(tmp, to);
tmp.interval = this.interval;
tmp.pkt_len = this.pkt_len;
tmp.send_over= this.send_over;
foreach(this.payload_q[i])begin
tmp.payload_q.push_back(this.payload_q[i]);
end
return tmp;
endfunction
function void pkt_data::unpack();
this.pkt_len = payload_q.size();
endfunction
`endif
`ifndef ENV_CFG_SV
`define ENV_CFG_SV
class env_cfg;
bit chk_idle;
bit gen_idle;
bit drv_idle;
bit mon_idle;
int env_wait_pkt_time = 10000;
int mon_wait_pkt_time = 1000;
int drv_wait_pkt_time = 1000;
int chk_wait_pkt_time = 1000;
endclass:env_cfg
`endif
`include "pkt_if.sv"
`include "environment.sv"
program automatic test(
pkt_if.pkt_drv dif,
pkt_if.pkt_mon mif,
input clk, rst_n
);
environment env;
initial begin
env = new(dif, mif);
env.build();
env.gen.send_num = 100;
env.run();
$display("At %0t, [TESt NOTE]: simulation finish~~~~~~~~~~~~~~~~~~", $time);
$finish;
end
endprogram
`ifndef ENV_SV
`define ENV_SV
`include "pkt_gen.sv"
`include "pkt_drv.sv"
`include "pkt_mon.sv"
`include "pkt_chk.sv"
`include "pkt_if.sv"
`include "env_cfg.sv"
class environment;
pkt_gen gen;
pkt_drv drv;
pkt_mon mon;
pkt_chk chk;
env_cfg cfg;
mailbox gen2drv_chan;
mailbox gen2chk_chan;
mailbox mon2chk_chan;
vdrv dif;
vmon mif;
int send_pkt_num;
//for finish fimu
int wait_time;
extern function new(input vdrv dif,
input vmon mif
);
extern virtual task build();
extern virtual task run();
extern virtual task report();
endclass
function environment::new(input vdrv dif,
input vmon mif
);
this.dif = dif;
this.mif = mif;
this.cfg = new();
endfunction
task environment::build();
$display("At %0t, [ENV NOTE]: environment::build() start!", $time);
gen2drv_chan = new(1);
gen2chk_chan = new(1);
mon2chk_chan = new(1);
gen = new(cfg, gen2drv_chan, gen2chk_chan);
drv = new(cfg, dif, gen2drv_chan);
mon = new(cfg, mif, mon2chk_chan);
chk = new(cfg, gen2chk_chan, mon2chk_chan);
$display("At %0t, [ENV NOTE]: environment::build() over!", $time);
endtask
task environment::run();
fork
drv.run();
gen.run();
mon.run();
chk.run();
join_none
#100;
fork
begin
wait(cfg.gen_idle);
wait(cfg.drv_idle);
wait(cfg.mon_idle);
wait(cfg.chk_idle);
$display("At %0t, [ENV NOTE]: normal finish", $time);
end
begin
while(1)begin
@(negedge top.clk);
if(this.dif.vld) wait_time = 0;
else wait_time++;
if(wait_time > this.cfg.env_wait_pkt_time) break;
end
$display("At %0t, [ENV ERROR]: time out!!!!!", $time);
end
join_any
#1000;
report();
endtask
task environment::report();
$display("At %0t, [ENV NOTE]: report start", $time);
repeat(100) @top.clk;
chk.report();
$display("At %0t, [ENV NOTE]: report over", $time);
endtask
`endif
`ifndef PKT_GEN_SV
`define PKT_GEN_SV
`include "pkt_data.sv"
`include "env_cfg.sv"
class pkt_gen;
env_cfg cfg;
mailbox gen2drv_chan;
mailbox gen2chk_chan;
pkt_data pkt;
int send_num;
extern function new(env_cfg cfg, mailbox gen2drv_chan, mailbox gen2chk_chan);
extern virtual task run();
endclass
function pkt_gen::new(env_cfg cfg, mailbox gen2drv_chan, mailbox gen2chk_chan);
this.cfg = cfg;
this.gen2drv_chan = gen2drv_chan;
this.gen2chk_chan = gen2chk_chan;
this.pkt = new();
endfunction
task pkt_gen::run();
pkt_data send_pkt;
pkt_data chk_pkt;
$display("At %0t, [GEN NOTE]: send_num = %0d", $time, send_num);
repeat(send_num) begin
assert(pkt.randomize());
$cast(send_pkt, pkt.copy());
$cast(chk_pkt, pkt.copy());
gen2drv_chan.put(send_pkt);
gen2chk_chan.put(chk_pkt);
end
this.cfg.gen_idle = 1;
//$display("At %0t, [GEN NOTE]: gen over pkt", $time);
endtask
`endif
`ifndef PKT_DRV_SV
`define PKT_DRV_SV
`include "pkt_data.sv"
`include "pkt_if.sv"
`include "env_cfg.sv"
class pkt_drv;
env_cfg cfg;
mailbox gen2drv_chan;
vdrv dif;
//for finish simu
int idle_cnt;
int get_num;
extern function new(env_cfg cfg,
vdrv dif,
mailbox gen2drv_chan
);
extern virtual task run();
extern virtual task my_run();
extern virtual task rst_sig();
extern virtual task pkt_send(input pkt_data pkt);
extern virtual task set_idle();
endclass
function pkt_drv::new(env_cfg cfg,
vdrv dif,
mailbox gen2drv_chan
);
this.cfg = cfg;
this.dif = dif;
this.gen2drv_chan = gen2drv_chan;
this.get_num = 0;
endfunction
task pkt_drv::run();
fork
my_run();
set_idle();
join_none
endtask
task pkt_drv::my_run();
pkt_data send_pkt;
//$display("At %0t, [DRV NOTE]: pkt_drv run start!", $time);
rst_sig();
//$display("At %0t, [DRV NOTE]: after rst_n", $time);
while(1) begin
gen2drv_chan.peek(send_pkt);
//$display("At %0t, [DRV NOTE]: get no.%0d pkt from gen", $time, this.get_num++);
send_pkt.pack();
pkt_send(send_pkt);
gen2drv_chan.get(send_pkt);
rst_sig();
end
endtask:my_run
task pkt_drv::rst_sig();
wait(top.rst_n == 1'b1);
@(posedge top.clk);
this.dif.vld <= '0;
this.dif.sop <= '0;
this.dif.eop <= '0;
this.dif.data<= '0;
endtask
task pkt_drv::pkt_send(input pkt_data pkt);
foreach(pkt.data[i]) begin
@(posedge top.clk);
this.dif.vld <= pkt.data[i][10];
this.dif.sop <= pkt.data[i][9];
this.dif.eop <= pkt.data[i][8];
this.dif.data<= pkt.data[i][7:0];
end
endtask
task pkt_drv::set_idle();
while(1)begin
@(negedge top.clk);
if(this.dif.vld == 1) idle_cnt = 0;
else idle_cnt++;
if(idle_cnt > this.cfg.drv_wait_pkt_time) this.cfg.drv_idle = 1;
else this.cfg.drv_idle = 0;
end
endtask:set_idle
`endif
`ifndef PKT_MON_SV
`define PKT_MON_SV
`include "pkt_data.sv"
`include "pkt_if.sv"
`include "env_cfg.sv"
class pkt_mon;
env_cfg cfg;
vmon mif;
mailbox mon2chk_chan;
bit rec_status;//0:wait sop, 1:wait eop
int wait_pkt_time = 1000;
extern function new(env_cfg cfg,
vmon mif,
mailbox mon2chk_chan);
extern virtual task run();
extern virtual task send_chk(pkt_data pkt);
endclass
function pkt_mon::new(env_cfg cfg,
vmon mif,
mailbox mon2chk_chan
);
this.cfg = cfg;
this.mif = mif;
this.mon2chk_chan = mon2chk_chan;
this.wait_pkt_time = cfg.mon_wait_pkt_time;
this.rec_status = 0;
endfunction : new
task pkt_mon::run();
pkt_data rec_pkt;
int wait_time;
int i = 0;
//$display("At %0t, [MON NOTE]: run start!", $time);
while(1) begin
while(1) begin
@(posedge top.clk);
if(mif.vld == 1)begin
wait_time = 0;
if(rec_status == 0) begin//wait sop
rec_pkt = new();
if(mif.sop == 0) begin
$display("At %0t, [MON ERROR]: ERROR! The first pkt cycle is not sop!", $time);
break;
end
else begin
rec_pkt.payload_q.push_back(mif.data);
if(mif.eop == 1) begin
rec_status = 0;
//$display("At %0t, [MON NOTE]: get no.%0d pkt!", $time, i++);
send_chk(rec_pkt);
end
else
rec_status = 1;
end
end
else if(rec_status == 1) begin//wait eop
if(mif.sop == 1) begin
$display("At %0t, [MON ERROR]: ERROR! SOP????", $time);
break;
end
else begin
rec_pkt.payload_q.push_back(mif.data);
if(mif.eop == 1) begin
rec_status = 0;
//$display("At %0t, [MON NOTE]: get no.%0d pkt!", $time, i++);
send_chk(rec_pkt);
end
else
rec_status = 1;
end
end
end
else begin
wait_time++;
if(wait_time <= wait_pkt_time) begin
wait_time++;
end
else break;
end
end
this.cfg.mon_idle = 1;
//$display("At %0t, [MON NOTE]: mon run over!", $time);
break;
end
endtask : run
task pkt_mon::send_chk(pkt_data pkt);
pkt.unpack();
mon2chk_chan.put(pkt);
endtask : send_chk
`endif
`ifndef PKT_CHK_SV
`define PKT_CHK_SV
`include "pkt_data.sv"
`include "env_cfg.sv"
class pkt_chk;
env_cfg cfg;
pkt_data expect_q[$];
int in_expect, in_actual;
int match, not_match;
mailbox gen2chk_chan;
mailbox mon2chk_chan;
//for finish simu
int idle_cnt;
extern function new(env_cfg cfg,
mailbox gen2chk_chan,
mailbox mon2chk_chan);
extern virtual task run();
extern virtual task expect_gain();
extern virtual task actual_gain();
extern virtual task set_idle();
extern virtual function report();
endclass:pkt_chk
function pkt_chk::new(env_cfg cfg,
mailbox gen2chk_chan,
mailbox mon2chk_chan);
this.cfg = cfg;
this.gen2chk_chan = gen2chk_chan;
this.mon2chk_chan = mon2chk_chan;
endfunction:new
task pkt_chk::run();
fork
expect_gain();
actual_gain();
set_idle();
join_none
endtask:run
task pkt_chk::expect_gain();
pkt_data expect_data;
while(1) begin
gen2chk_chan.get(expect_data);
this.expect_q.push_back(expect_data);
in_expect++;
idle_cnt = 0;
//$display("At %0t, [CHK NOTE]: get a expect pkt", $time);
end
endtask:expect_gain
task pkt_chk::actual_gain();
pkt_data expect_data;
pkt_data actual_data;
while(1) begin
mon2chk_chan.get(actual_data);
in_actual++;
idle_cnt = 0;
if(this.expect_q.size == 0) $display("At %0t, [CHK ERROR]: expect_q==0???", $time);
else begin
expect_data = expect_q[0];
if(!expect_data.compare(actual_data)) begin
$display("At %0t, [CHK ERROR]: no match, \nexpect data:%s \nactual data:%s ", $time, expect_data.psprintf(), actual_data.psprintf());
expect_q.pop_front();
not_match++;
end
else begin
expect_q.pop_front();
//$display("At %0t, [CHK NOTE]: match, \nexpect data:%s \nactual data:%s ", $time, expect_data.psprintf(), actual_data.psprintf());
match++;
end
end
end
endtask:actual_gain
task pkt_chk::set_idle();
while(1)begin
@(negedge top.clk);
idle_cnt++;
if(idle_cnt > this.cfg.chk_wait_pkt_time) this.cfg.chk_idle = 1;
else this.cfg.chk_idle = 0;
end
endtask:set_idle
function pkt_chk::report();
$display("----------------------------------------------------------------------------------------------------");
$display("[CHECKER REPORT]expect pkt_num=%0d, actual pkt_num=%0d, match pkt_num=%0d, not match pkt_num=%0d", in_expect, in_actual, match, not_match);
$display("----------------------------------------------------------------------------------------------------");
endfunction:report
`endif
`ifndef PKT_IF_SV
`define PKT_IF_SV
interface pkt_if(input clk, rst_n);
logic [7:0] data;
logic sop;
logic eop;
logic vld;
clocking drv @(posedge clk);
output data;
output sop, eop, vld;
endclocking : drv
modport pkt_drv (clocking drv);
clocking mon @(posedge clk);
input data;
input sop, eop, vld;
endclocking : mon
modport pkt_mon (clocking mon);
endinterface
typedef virtual pkt_if.drv vdrv;
typedef virtual pkt_if.mon vmon;
`endif