前言
【验证小白】只有SV+modelsim学验证(1)——把平台搭起来
【验证小白】只有SV+modelsim学验证(2)——加monitor到环境中
【验证小白】只有SV+modelsim学验证(3)——加checker到环境中
【验证小白】只有SV+modelsim学验证(4)——想办法合理的结束仿真后,准备好了所有代码
想想这个系列也是耗时最长的一个系列了。
从18年毕业4月份开始接触IC前端验证一筹莫展,看SV完全不知道在说什么,看《SystemVerilog 验证方法学》看的想撕书,加上对OOP组织风格完全陌生,简直可以用惨淡来形容那一段时间的学习。
后来到8月份时候重要感觉“我又行了!!”,于是想着要是还有一个我这样的没基础没方法没环境的孩子要学验证咋办,于是开始下笔写这个帖子。
陆陆续续写到了19年6月(18年8月两篇,19年6月两篇,中间我完全没有写东西,估计是项目最忙的那一段),写完4之后就放下了,一是我当时学习重心有变化在拼命的学脚本做效率提升,二是逐步有转回做设计的想法就没啥心情继续更,再加上当时沉迷于扣一些技术细节,宏观入门的东西就先放下了。
之后一年多偶尔能收到评论和私信,希望我把rm补全到环境中,这样作为一个完整的验证环境来辅助学习,于是我鼓了几次勇气想更完,但是没几分钟热情就过去了。
不过五一假期前后帮一个刚入门的朋友做了一个小的验证题发现很多随手就写的代码已经需要去想或者去查,这让我忽然意识到如果我再不写,那么可能我就会忘了应该怎么写,那这就永远是残篇了,环境少了精华所在。
因此这个周末花了一个下午,做了一个简单的rm添加到环境中,毕竟这样就是完善的验证环境。
环境结构
整体结构仍然是朴素思想下的验证环境,组件互联结构如下:
gen用来产生transaction激励,并通过channel发送到drv;
drv解析激励,拆分为时序级信息,发到interface上;
in_mon采样DUT入口,将采样信息恢复为transaction发送给RM;
DUT根据入口行为和硬件电路行为,给出输出;
RM根据输入的transaction预测输出,给到chker作为except transaction;
out_mon采样DUT输出,给到chker作为actual transaction;
chk在内部对except和actual transaction进行对比,如果比对不过,则报错。
整体思路跟之前并没有变化。
DUT实现调整
既然要做RM,那么DUT必然要有个行为,因此我定的行为是这样的:对于package输入,输入的前一个数据和后一个数据取异或作为输出数据,最后一拍数据没有下一拍了那么久输出本身,因此核心代码就是这样:
wire [DATA_WIDTH-1:0] data_in_merge = data_in_ff1 ^ data_in;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
data_in_vld_ff1 <= 1'b0;
sop_in_vld_ff1 <= 1'b0;
eop_in_vld_ff1 <= 1'b0;
data_in_ff1 <= {DATA_WIDTH{1'b0}};
end
else begin
data_in_vld_ff1 <= data_in_vld;
sop_in_vld_ff1 <= sop_in_vld;
eop_in_vld_ff1 <= eop_in_vld;
data_in_ff1 <= data_in;
end
end
wire pre_data_out_vld = data_in_vld_ff1;
wire pre_sop_out_vld = sop_in_vld_ff1;
wire pre_eop_out_vld = eop_in_vld_ff1;
wire [DATA_WIDTH-1:0] pre_data_out = {DATA_WIDTH{eop_in_vld_ff1}} & data_in_ff1 |
{DATA_WIDTH{~eop_in_vld_ff1}} & data_in_merge;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)begin
data_out_vld <= 1'b0;
sop_out_vld <= 1'b0;
eop_out_vld <= 1'b0;
data_out <= {DATA_WIDTH{1'b0}};
end
else begin
data_out_vld <= pre_data_out_vld;
sop_out_vld <= pre_sop_out_vld;
eop_out_vld <= pre_eop_out_vld;
data_out <= pre_data_out;
end
end
好的,我们暂且假定RTL是没有问题的好了。
实现RM
从结构图上可以看出,rm只需要两个new参数:in channel和out channel,分别连接in_mon和chk,因此规划rm如下:
`ifndef PKT_RM_SV
`define PKT_RM_SV
`include "pkt_data.sv"
`include "pkt_if.sv"
`include "env_cfg.sv"
class pkt_rm;
mailbox in_chan;
mailbox out_chan;
extern function new(mailbox in_chan, mailbox out_chan);
extern virtual task run();
extern virtual task my_run();
extern function pkt_data data_trans(pkt_data in_data);
endclass
`endif
new的时候,把channel指向好就可以了:
function pkt_rm::new(mailbox in_chan, mailbox out_chan);
this.in_chan = in_chan;
this.out_chan = out_chan;
endfunction:new
主方法run也很简单,就一个操作:
task pkt_rm::run();
fork
my_run();
join_none
endtask
mu_run是写核心操作的地方,分为以下几个事:
task pkt_rm::my_run();
pkt_data rcv_pkt;
pkt_data send_pkt;
while(1) begin
this.in_chan.get(rcv_pkt);
//$display("At %0t, [RM NOTE]: get a expect pkt", $time);
send_pkt = data_trans(rcv_pkt);
this.out_chan.put(send_pkt);
//$display("At %0t, [RM NOTE]: send a expect pkt", $time);
end
endtask:my_run
从in channel收transaction,预测输出,把预测的输出放到out channel。
data_trans用来做预测输出的事,这里我开始时候写错了,debug了半天才改成了下面那样,验证环境这代码啊真是一写就容易错,好多时候还得根据RTL来改成正确的行为,这就很离谱。
幸好设计不会提bug单,否则我这种就是一提一个准:
function pkt_data pkt_rm::data_trans(pkt_data in_data);
pkt_data tmp;
tmp = in_data.copy();
//foreach(in_data.payload_q[i])begin
// if(i == in_data.pkt_len - 1)begin
// tmp.payload_q[i] = in_data.payload_q[i];
// $display("At %0t, [RM NOTE]: tmp.payload_q[%0d] = %0h", $time, i, tmp.payload_q[i]);
// end
// else if (i >= 1)begin
// tmp.payload_q[i-1] = in_data.payload_q[i-1] ^ in_data.payload_q[i];
// $display("At %0t, [RM NOTE]: tmp.payload_q[%0d] = %0h, in_data.payload_q[i-1]=%0h, in_data.payload_q[i]=%0h", $time, i, tmp.payload_q[i], in_data.payload_q[i-1], in_data.payload_q[i]);
// end
//end
foreach(in_data.payload_q[i])begin
if (i >= 1)begin
tmp.payload_q[i-1] = in_data.payload_q[i-1] ^ in_data.payload_q[i];
end
end
tmp.payload_q[in_data.pkt_len - 1] = in_data.payload_q[in_data.pkt_len - 1];
return tmp;
endfunction:data_trans
OK,完成以上这几步,一个简单的RM其实完成了,下一项是在env中例化一下。
ENV中例化
全部代码最后再放吧,这里列一些主要的代码:
channel的例化:
gen2drv_chan = new();
in_mon2rm_chan = new();
rm2chk_chan = new();
out_mon2chk_chan = new();
几个组件的例化:
gen = new(cfg, gen2drv_chan);
drv = new(cfg, this.bus.pkt_in_bus, gen2drv_chan);
in_mon = new(cfg, this.bus.pkt_in_bus, this.in_mon2rm_chan, 0);
out_mon = new(cfg, this.bus.pkt_out_bus, this.out_mon2chk_chan, 1);
rm = new(this.in_mon2rm_chan, this.rm2chk_chan);
chk = new(cfg, this.rm2chk_chan, this.out_mon2chk_chan);
这样一搞,就把rm连到环境中了,对照着最上面的结构图,其实还是很清楚的。
测试下效果
在modelsim里跑一下,因为DUT和RM写的都很完美,所以结果也很完美:
run 100000ns
# Time scale of (top.u_test) is 10ns / 1ns
# At 0.000..ns.., [ENV NOTE]: environment::new() start!
# At 0.000..ns.., [ENV NOTE]: environment::build() start!
# At 0.000..ns.., [GEN NOTE]: send_num = 10
# At 545.000..ns.., [CHK NOTE]: get a expect pkt
# At 555.000..ns.., [CHK NOTE]: get a actual pkt
# At 555.000..ns.., [CHK NOTE]: match!!!!
# At 995.000..ns.., [CHK NOTE]: get a expect pkt
# At 1005.000..ns.., [CHK NOTE]: get a actual pkt
# At 1005.000..ns.., [CHK NOTE]: match!!!!
# At 1125.000..ns.., [CHK NOTE]: get a expect pkt
# At 1135.000..ns.., [CHK NOTE]: get a actual pkt
# At 1135.000..ns.., [CHK NOTE]: match!!!!
# At 1655.000..ns.., [CHK NOTE]: get a expect pkt
# At 1665.000..ns.., [CHK NOTE]: get a actual pkt
# At 1665.000..ns.., [CHK NOTE]: match!!!!
# At 1705.000..ns.., [CHK NOTE]: get a expect pkt
# At 1715.000..ns.., [CHK NOTE]: get a actual pkt
# At 1715.000..ns.., [CHK NOTE]: match!!!!
# At 1855.000..ns.., [CHK NOTE]: get a expect pkt
# At 1865.000..ns.., [CHK NOTE]: get a actual pkt
# At 1865.000..ns.., [CHK NOTE]: match!!!!
# At 2105.000..ns.., [CHK NOTE]: get a expect pkt
# At 2115.000..ns.., [CHK NOTE]: get a actual pkt
# At 2115.000..ns.., [CHK NOTE]: match!!!!
# At 2305.000..ns.., [CHK NOTE]: get a expect pkt
# At 2315.000..ns.., [CHK NOTE]: get a actual pkt
# At 2315.000..ns.., [CHK NOTE]: match!!!!
# At 2395.000..ns.., [CHK NOTE]: get a expect pkt
# At 2405.000..ns.., [CHK NOTE]: get a actual pkt
# At 2405.000..ns.., [CHK NOTE]: match!!!!
# At 2605.000..ns.., [CHK NOTE]: get a expect pkt
# At 2615.000..ns.., [CHK NOTE]: get a actual pkt
# At 2615.000..ns.., [CHK NOTE]: match!!!!
# At 12620.000..ns.., [ENV NOTE]: normal finish
# At 13620.000..ns.., [ENV NOTE]: report start
# ----------------------------------------------------------------------------------------------------
# [CHECKER REPORT]expect pkt_num=10, actual pkt_num=10, match pkt_num=10, not match pkt_num=0
# ----------------------------------------------------------------------------------------------------
# At 14120.000..ns.., [ENV NOTE]: report over
# At 14120.000..ns.., [TESt NOTE]: simulation finish~~~~~~~~~~~~~~~~~~
# ** Note: $finish : C:/Users/gaoji/Desktop/sv test/test.sv(19)
# Time: 14120 ns Iteration: 1 Instance: /top/u_test
对应的波形是这个样子的:
好了,自此一个五脏俱全版的验证环境全部完成。