Lab1介绍了DUT的基本情况,学习了基本的验证流和基础的验证框架。Lab2在此基础上对test program进行功能的拓展,新增发送数据包的任务;其他模块保持不变。
Test Program
test generator
简单来说,这部分的功能就是产生激励,需要明确两点:一是数据输入和输出的端口,二是待传输的数据内容。因而,首先定义如下的全局变量,其次将地址和数据赋给全局变量。
bit[3:0] sa; // source port
bit[3:0] da; // destination port
logic[7:0] payload[$]; // packet data array
- payload是一个队列
queue
,队列中存储的数据类型是8位的logic数据。- 队列结合了链表和数组的优点,可在任何地方添加或删除元素,并通过index实现对任一元素的访问
- 声明时使用美元符号
[$]
,队列的存储空间无限大,理论上为物理内存的最大空间,从0
到$
- 不需要使用
new[ ]
来创建空间,一开始的空间为0,使用队列的方法可以实现元素的增减 - 队列自带的方法有:
insert、delete、push_front、push_back、pop_front、pop_back
(通过push_back
和pop_front
的结合来可以实现FIFO的用法)
task gen(); // specifying input and output ports and packet data
sa = 3;
da = 7;
payload.delete();
repeat($urandom_range(2,4))
payload.push_back($urandom);
endtask: gen
- 队列方法:参考link
payload.delete()
方法:删除整个queue;若在括号中指定index,则删除该元素。payload.push_back()
方法:在队尾插入元素,括号里为待插入的元素。
- 随机化系统函数:参考link
$urandom_range()
: 返回指定范围内的无符号随机整数。$urandom
:返回32bit无符号数。payload是8bit,32bit无符号数高位截断,取低8位赋值给payload。参考link
transactor & device driver
这里的transactor,无其他具体的动作,只是调用device driver完成事务;device driver才是直接与硬件交互的模块。根据DUT发送数据的时序来看,完成一次事务需要发送address、padding bits、payload。
send transactor
task send(); // test transactor
send_address();
send_pad();
send_payload();
endtask: send
send drivers
task send_address(); // device driver 1
rtr_io.cb.frame_n[sa] <= 1'b0;
for(int i=0; i<4; i++) begin
rtr_io.cb.din[sa] <= da[i];
@(rtr_io.cb); // dont forget clock !!!
end
endtask: send_address
task send_pad(); // device driver 2
rtr_io.cb.frame_n[sa] <= 1'b0;
rtr_io.cb.valid_n[sa] <= 1'b1;
rtr_io.cb.din[sa] <= 1'b1;
repeat(5) @(rtr_io.cb);
endtask: send_pad
task send_payload(); // device driver 3
rtr_io.frame_n[sa] <= 1'b0;
rtr_io.valid_n[sa] <= 1'b0;
for(int index=0; index<payload.size(); index++) begin // -> foreach(payload[index])
for(int i=8; i<8; i++) begin
rtr_io.cb.din[sa] <= payload[index][i];
rtr_io.cb.valid_n[sa] <= 1'b0;
rtr_io.cb.frame_n[sa] <= (index == payload.size - 1) && (i==7);
@(rtr_io.cb); // dont forget clock !!!
end
end
rtr_io.cb.valid_n[sa] <= 1'b1;
endtask: send_payload
- 特别注意时钟推进的位置!要特别注意如何把时钟拨快。如果在一个子程序中,你在退出子程序时推进时钟,然后在另一个子程序中,你在进入子程序时推进时钟,可能会导致错误的计时。观察
@(rtr_io.cb)
的位置发现,这三个diver都在退出子程序之时推进时钟。
task integration
在initial过程块中调用上述task,即可完成Lab2中测试平台的搭建。
initial begin
run_for_n_packets = 21; // dont forget define it as "int"
reset();
repeat(run_for_n_packets) begin
gen();
send();
end
repeat(10) @(rtr_io.cb);
end
send()
后增加10个额外的时钟周期,目的是便于观察最后一轮发送的输出数据;不加的话,不便于观察而已,不影响结果。
一些疑惑
- 创建队列元素之前为何要先使用
delete()
方法将整个队列清空? - 因为test中重复发送packets多次,在新一轮的发送之前必须将旧的内容清除,创建新的待发送数据。假设只发送一次,不清空队列不会产生影响。