目录
0.前言
该lab的目标有以下2点:
1.写一个generator产生随机激励
2.将generator产生的随机激励驱动给DUT,按照协议的时序
3.通过波形观察结果
该lab新添加的所有的操作都是在lab1中的test.sv文件中添加。
1.全局变量声明
我们要给DUT发送激励,需要知道发给哪个端口,还要告诉DUT从哪个端口输出,所以首先我们要定义3个变量,如下:
bit [3:0] sa; //输入端口,16个端口,用4bits来声明
bit[3:0] da; // 输出端口
logic[7:0] payload[$];//声明一个队列,队列中每个元素为8bits,用来存储激励
2.定义generator产生随机激励
在lab1中,我们首先利用reset()函数对DUT进行了初始化,现在需要产生随机激励,为了代码的简洁及可复用,我们通过在reset()后调用gen()函数来执行这个操作:
program automatic test (router__io.TB rtr__io);
initial begin
$display("Hello World!");
$vcdpluson;
reset ();
gen ();//调用gen函数产生随机激励
end
/*>=====================================task==========================================<*/
task reset();
router.reset_n=1'b0;
router.cb.frame_n.<='b1;
router.cb.valid_n<='b1;
##2 router.cb.reset_n<=1'b1;
repeat(15) @(router.cb);
endtask
task gen();
sa=3;//按照lab要求从3号接口输入
da=7;//按照lab要求从7号接口输出
payload.delete();//删除队列中所有的元素,用来将上一次产生的激励全部删除
repeat($urandom_range(2,4)) payload.push_back($urandom);//在队列中插入2-4个8bits的随机数据
endtask
endprogram
这里有几点需要注意:
1.payload.delete()的作用是将上一次产生的激励删除,由于验证要随机产生无数次激励,如第一次产生了payload={1,2,3},将它们驱动到DUT上后;payload中的这些数据不会自己删除,因此第二次再产生激励前,我们要先把第一次产生的数据删除
2.$urandom_range(2,4)表示返回2-4之间一个无符号随机整数,$urandom表示返回一个32bits的无符号随机整数,这行代码就完成了一次激励的生成,即在队列中插入2-4个8bits的随机数据。当然插入2-4个数是lab2中要求的。
3.将随机激励驱动到DUT上
数据产生之后,我们需要将数据drive到DUT的端口上,依然采用函数调用的方式,代码及讲解如下:
program automatic test (router__io.TB rtr__io);
initial begin
$display("Hello World!");
$vcdpluson;
reset ();
repeat (21) begin//产生21次激励,产生多少次激励可按照需要改变
gen ();//调用gen函数产生随机激励
send();//调用send函数按照协议把激励发送给DUT
end
end
/*>=====================================task==========================================<*/
task reset();
router.reset_n=1'b0;
router.cb.frame_n.<='b1;
router.cb.valid_n<='b1;
##2 router.cb.reset_n<=1'b1;
repeat(15) @(router.cb);
endtask
task gen();
sa=3;
da=7;
payload.delete();
repeat($urandom_range(2,4)) payload.push_back($urandom);数据
endtask
task send();//数据发送函数:由发送地址,隔离,数据3个函数组成
send_addrs();//传输地址
send_pad();//传输隔离带
send_payload();//传输数据
endtask
task send_addrs();//根据协议写
rtr_io.cb.frame_n[sa]<=1'b0;//根据协议,传输地址开始时,frame_n同时拉低
for(int i=0;i<4;i++)begin//由于单个端口是单bit,因此要发4次构成4为地址
rtr_io.cb.din[sa]<=da[i];
@(rtr_io.cb); //作用后面讲解
end
endtask
task send_pad();//根据协议写
rtr_io.cb.frame_n[sa]<=1'b0;//隔离区frame_n继续拉低
rtr_io.cb.valid_n[sa]<=1'b1;//隔离区valid_n拉高
rtr_io.cb.din[sa]<=1'b1;//隔离区din拉高
repeat(5) @(rtr_io.cb);//隔离5个5个周期
endtask
task send_payload();//根据协议写
foreach(payload[index])//遍历队列中每个数据
for(int i=0;i<8;i++)begin//将每个数据的bit挨个发送出去
rtr_io.cb.din[sa]<=payload[index][i];//一个数据的单个bit
rtr_io.cb.valid_n[sa]<=1'b0;//发数据的时候valid_n拉低
rtr_io.cb.frame_n[sa]<=(index==(payload.size()-1))&&(i==7);//frame_n在队列中最后一个数据的最后一位发送时拉高,其余时候保持前面的0状态
@(rtr_io.cb)
end
endtask
/*>====================================endtask========================================<*/
endprogram
4.仿真波形:
5.值得注意的细节
在send_addrs和send_payload函数中,for循环里都有一个 @(rtr_io.cb),那这句话的作用是什么?有和没有对波形会有什么影响?
task send_addrs();//根据协议写
rtr_io.cb.frame_n[sa]<=1'b0;//根据协议,传输地址开始时,frame_n同时拉低
for(int i=0;i<4;i++)begin//由于单个端口是单bit,因此要发4次构成4为地址
rtr_io.cb.din[sa]<=da[i];
@(rtr_io.cb); //作用是什么?
end
endtask
task send_payload();//根据协议写
foreach(payload[index])//遍历队列中每个数据
for(int i=0;i<8;i++)begin//将每个数据的bit挨个发送出去
rtr_io.cb.din[sa]<=payload[index][i];//一个数据的单个bit
rtr_io.cb.valid_n[sa]<=1'b0;//发数据的时候valid_n拉低
rtr_io.cb.frame_n[sa]<=(index==(payload.size()-1))&&(i==7);//frame_n在队列中最后一个数据的最后一位发送时拉高,其余时候保持前面的0状态
@(rtr_io.cb)//作用?
end
endtask
下面以send_addrs中的@(rtr_io.cb)为例,观察有和没有的波形:
有 @(rtr_io.cb)
可见波形按照预期的时间顺序1110产生
无@(rtr_io.cb)
可见数据段直接没了,上来就干到隔离段,这是因为没有@(rtr_io.cb)等待一个clk,使得在din在一开始的posedge clk时,完成了send_addrs中所有数的赋值,而缺少了赋一个值等待一个clk,所以其不是没执行而是在posedge clk瞬间执行完了,因此在波形上看不出来,导致地址段的缺少。