Tenstbench的基本组件包括generator、driver、monitor、checker。组件自底向上可以分为3个层次:
- signal layer
- driver:将抽象层次的数据变成带有时序的信号,送到DUT端口
- monitor:将带有时序信息的端口信号转换成抽象的数据,送到上层处理
- encapsulation layer
- generator:产生灌入DUT的激励,如随机激励、带约束的激励
- reference model:以更抽象的方式实现与DUT相同的功能
- checker:比较RM与DUT的输出,常用scoreboard来统计比较结果
- function layer
- testcase:各种可能场景的测试用例,可以认为其层次在testbench之上
Lab3关注的是上述组件中的monitor和checker,在Lab2的基础上通过自顶向下的方式完善整个testbench。
Top-level test env
Top-level的initial块调用所有待完成的任务,包括前面两个lab中的reset()、gen()、send()
,新增recv()
和check()
。这里要注意的是,send()
与recv()
是并发执行的进程。
……
logic [7:0] pkt2cmp_payload[$];
initial begin
reset();
repeat(run_for_n_packets) begin
gen();
fork // send and recv are parallel process
send();
recv();
join
check(); // compare results of recv and gen
end
end
……
Monitor
recv transactor
与send()
类似,recv()
也是transactor,即不直接与DUT通信,而是调用monitor来收集数据。
task recv();
get_payload(); // call monitor driver
endtask: recv
recv monitor
与DUT直接通信的monitor是get_payload
任务,任务可以分成以下两部分:
- 等待输出帧frameo_n有效,当frameo_n有效之后进入下一步,若输出帧长时间无效则退出程序。
- 输出帧有效后,检测valido_n是否有效,若有效则将输出结果缓存起来:1)假设frameo_n一直有效,则在收到8个数据以后结束for循环,将这一byte的数据添加到数据包中;2)假设frameo_n变成无效状态,若此时正好接收到8个数据,则同样把数据添加到数据包中并结束任务,否则打印错误信息并结束仿真。
- 若valido_n无效会出现什么样的结果? for-loop中所有的if都不满足的话,不采集输出端的数据,直接执行
@(rtr_io.cb)
即更新仿真时间。这也是 i 的递增必须发生在valido_n有效之后的原因,如果valido_n无效时 i 也递增的话,会出现接收的数据非byte对齐的情况。 - 何时跳出for-loop?i = 8即不满足for循环条件i < 8时,跳出for-loop。注意: 执行
return
结束了整个任务,肯定也跳出了for-loop,但执行return的必要条件是i = 8,仍然是在不满足for循环条件时跳出循环的。 - 何时跳出forever-loop?直到frameo_n无效且此时数据是byte对齐的,才能跳出forever-loop。注意:执行
$finish
是因为发生了error,不能认为正确跳出了循环。
task get_payload();
pkt2cmp_payload.delete();
// waiting for negedge of frameo_n
fork // limit the work domain of "disable fork"
begin: wd_timer_fork // keep fork...join_any and disable fork working squentially
fork: frameo_wd_timer
@(negedge rtr_io.cb.frameo_n[da]);
begin
repeat(1000) @(rtr_io.cb);
$display("\n%m\n [ERROR]%t Frame signal timed out!",$realtime);
$finish;
end
join_any: frameo_wd_timer
disable fork; // terminate the child-process above
end: wd_timer_fork
join
// sampling output data from DUT
forever begin
logic[7:0] datum;
for(int i=0; i<8; ) begin // warning hits: without for_step
if(!rtr_io.cb.valido_n[da])
datum[i++] = rtr_io.cb.dout[i];
if(rtr_io.cb.frameo_n[da])
if(i==8) begin
pkt2cmp_payload.push_back(datum);
return; // exit task
end
else begin
$display("\n%m\n [ERROR]%t Packet not byte aligned!",$realtime);
$finish; // exit simulation
end
@(rtr_io.cb);
end
pkt2cmp_payload.push_back(datum);
end
endtask: get_payload
- fork…join内的语句块称为子进程,执行这段fork…join块的代码称为父进程。
$dispaly
任务中第一个参数为打印格式,后续参数为输出列表。参考link- 这里的
%m
表示打印当前display语句所在的模块层次,%t
表示打印当前的仿真时间。 $time
返回一个64bit的整数来表示的当前仿真时刻值,该时刻以模块的仿真时间尺度(见timescale)为基准。$realtime
返回的时间数字是一个实型数,该数字也是以时间尺度为基准的。
- 这里的
- system verilog增加了
return
声明,用于从一个非void函数中返回数值或者从一个void函数或任务返回。- return声明可以在任务或函数执行流程的任意一点执行。
- 当return声明执行后,任务或者函数立即退出而不需要执行到任务或者函数的末尾。
Checker
compare function
创建任务check()
将收到的数据包与源数据包比较,其中调用了自定义函数compare()
比较两个数据包的尺寸和数据内容。只有当尺寸和内容都一致时,才会得到传输正确的结果,函数返回1;否则返回0,并打印错误信息。
function compare(ref string message0);
if(payload.size() != pkt2cmp_payload.size()) begin
message0 = "Payload Size Mismatch:\n";
message0 = { message0, $sformatf("payload.size() = %0d, pkt2cmp_payload.size() = %0d\n",
payload.size(), pkt2cmp_payload.size()) };
return (0);
end
if(payload == pkt2cmp_payload) ;
else begin
message0 = "Payload Content Mismatch:\n";
message0 = { message0, $sformatf("Packet Sent: %p \n Packet Received: %p",
payload, pkt2cmp_payload) };
return (0);
end
message0 = "Successfully Compared";
return(1);
endfunction: compare
- 加
ref
关键字采用标准的地址传递,即引用地址而非引用值!参考linkcompare(ref string message0)
的形参为字符串message,函数定义时为message开辟一段内存空间- 函数被调用时,实参将指向形参message所在的空间,并对空间中的内容进行修改
- 函数调用结束后,该内存空间中的值已经被修改了
$sformatf
的作用是整理字符串的打印格式,而非直接打印字符串。参考link%0d
中,d表示以十进制格式输出,在%和d之间加上0使输出数据时总是用最少的位数来显示表达式的当前值%p
可用于打印聚合表达式,例如解压缩结构、队列、数组和联合数组等。参考link
check task
在checker中定义一个字符串变量,用于比较结果的打印,以及一个计数器来统计对比结果。如果发现数据包发送不正确,则打印具体的错误信息,并结束仿真;如果对比结果显示数据包发送正确,则统计发送的数据包的个数或者编号。
task check();
string message1;
static int pkts_checked = 0;
if ( !compare(message1) ) begin
$display("\n%m\n[ERROR]%t Packet #%0d %s\n", $realtime, pkts_checked, message1);
$finish;
end
$display("[NOTE]%t Packet #%0d %s", $realtime, pkts_checked++, message1);
endtask: check
- message1即为实参,调用
compare
函数时,message1将指向前面的message0的内存空间,并对内存中的数据做修改。 - pkt_checked定义为
static
型变量,表示对所有对象都是可见的,被多个对象共享,在实例内外对它的修改都能生效。参考link
make bad
为了验证compare
方法的正确性,既要保证对于正确的rtl代码能得出successfully的结果,也要确保错误的rtl能够给出mismatch的结果。原教程中给出了一个bad rtl,通过make bad可以编译错误的代码。从仿真结果来看,当[sa]=15
作为输入端向[da]=8
端口输出数据时,输出端数据全为X
态。