SV-路科-V0实验-lab5使用类替换验证组件

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


学习目标

1.创建一个Generator transactor的类 2.创建一个Driver的类 3.创建一个Reciever的类 4.拓展测试平台,并将Driver和Monitor同时连接到所有的输入端和输出端
lab4我们将数据包封装到了类中。但同一个时间内只能把数据包从一个输入端驱动到另一个输出端。
本次实验,我们将验证组件产生器,驱动器,监视器和比较器都分别封装到相应的类中(Generator,Driver,Receiver,Scoreboard类)。同时创建testbench,将所有类容纳进去。
验证组件类的对象的传递需要使用信箱Mailbox。

实验结构
在这里插入图片描述

提示:以下是本篇文章正文内容,下面案例可供参考

一、实验步骤

任务一:创建DriverBase类并扩展

基类DriverBase

属性
1.首先声明驱动程序要驱动的接口信号interface,即例化接口router_io记得例化时带上插口TB
2.声明字符串name,作为对象的标识,区分对象
3.声明sa,da,payload[]和pkt2send类属性变量,这些都是在testbench中使用到的全局变量,会通过信箱将它们与类属性变量连接,使得类中的方法可以访问这些全局变量
4.在Driver类中的方法需要用到if-$display这有助于调试
5.最后别忘了类前面添加宏定义
方法
1.声明new方法,它有两个参数变量,用于区分对象的name和接口信号rtr_io
在方法内,需要将类属性的name和interface与参数的name和interface连接
2.将test.sv中用来驱动数据的任务(send,send_addrs,send_pad,send_payload)全部放在DriverBase中,依旧是使用外部任务的定义

  • 【注:在send系列的任务中的全局变量需要改为类属性的变量,原先的全局变量在类中是索引不到的】试过了,没影响
  • 对Driver里的任务声明virtual也没有影响

在这里插入图片描述
在这里插入图片描述

扩展类Driver

继承于基类,在Driver.sv中需要先导入基类的sv文件,同时别忘了宏定义。在 拓展类 中需要声明3个属性和两个方法;

  • in_mbox用来将Generator的数据包对象传递到Driver
  • out_mbox将Driver的数据包对象传递给Scoreboard
    这两个信箱都需要创建新的mailbox类型(typedef mailbox)以区别于其他的信箱,同时声明“#”信箱内存放的数据类型是Packet,可以命名pkt_mbox来表示这是用来存放数据包类的信箱。创建新的类型这部分代码需要放到router_test.svh中
    【注:记得也要创建数据类型(typedef classPacket,这样才能声明信箱存放的数据类型为Packet】
  • sem[ ]旗语用来防止多输入通道挤占同一输出通道。别忘了semaphore也是需要new()设置钥匙数量。旗语使用数组类型用来划分不同的输出通道sem[da]
  • 创建new()方法,该方法需要传递5个参数,首先是参数name,可以用来赋值对象的名称;port_id用来设置驱动器所驱动的输入端口;**sem[ ]**设置相应的值,以仲裁驱动器对输出端口的访问;in_box连接产生器和驱动器的信箱;out_box连接驱动器和计分板的信箱。
  • 创建start()任务,从in_box中取出数据包对象。在该方法中,如果目标地址端口还没有被另一驱动程序Driver使用,则会调用DUT中的send()方法驱动数据包。

在这里插入图片描述

任务二:扩展类new()方法

1.在扩展类的外部声明new()方法,在结构体内调用父类DriverBase的new(),并且传递参数name和rtr_io,port_id
2.在调用super.new()以后添加跟踪语句,用于追踪是谁的数据包对象在驱动,变量TRACE_ON是在test.sv中声明的全局变量。
3将port_id传递给类属性的sa
4.将相应参数的值传递给类属性的变量sem[ ],in_box,out_box

在这里插入图片描述

任务三:扩展类start()方法

  • 在test program中例化的每个数据包对象都需要一个启动机制,我们将它声明为start()方法。
  • 对于驱动器来说,start()方法需要执行无限循环。在循环的每次迭代中,都会从in_box中取出pkt2send数据包对象。这个数据包对象将通过调用DUT中的send()任务来驱动。一旦数据包对象被处理完成,将会通过out_box传递给Scoreboard。
  • 由于Driver对象在启动时应该与其他验证组件同时运行,因此在start()体中的所有语句应该放入非阻塞的fork-join内。

1.在start()方法体内添加追踪语句
2.在追踪语句后面,创建非阻塞fork-join这里应该是fork-join_none在调试过程,发现使用阻塞性并行线程会陷入循环
3.在fork-join内创建无限循环forever
4.循环中的每次迭代进行以下操作:
a) 从in_box中取出pkt2send数据包对象
b)如果取出数据包对象的sa与这个驱动类属性的sa不相等,则持续这个无限循环;
如果相等,则用数据包对象中的变量da和payload的值更新驱动类的da和payload
c) 使用sem[da ]分配仲裁对da指定输出端口的访问
d)一旦仲裁成功调用send()任务驱动数据包通过DUT
e)当完成数据包的发送send()后,将数据包对象存入out_box
d) 在循环的最后,将其余的钥匙放回去sem[da].put()
在这里插入图片描述

任务四:创建ReceiverBase类并扩展

基类ReceiverBase

属性
属性声明同DriverBase一样,例化的类的对象不同Packet pkt2cmp,且不需要声明sa,因为接受数据它不关心数据来源于哪个通道。存放数据的队列改为payload_pkt2cmp[$]
方法
1.new方法(),添加追踪语句然后连接类属性与参数的name和接口信号rtr_io,创建pkt2cmp对象。
2.recv()任务,将test program里的recv()内容剪切过来。同时在开头添加追踪语句。
3.get_payload()任务,同上
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

扩展类Receiver

属性
1.声明pkt_mbox类型信箱 out_box用来存放输出端得到的数据包对象,稍后要通过它把数据包对象传递给Scoreboard
方法
1.创建外部new()方法,需要传递3个参数变量,name,port_id,和out_box,与驱动器类的new方法不同,这里的port_id是接收数据的输出端口。
2.创建start()任务

在这里插入图片描述

任务五:扩展类-Receiver-new()方法

1.首先调用父类的new方法,super.new(),同时填写参数name 和接口rtr_io
2.在这之后添加跟踪语句
3.将port_id的值赋值给da
4.将参数传递的out_box赋值给类属性的out_box

在这里插入图片描述

任务六:扩展类-Receiver-start()任务

同驱动器类的start任务一样,也需要执行无限循环。在每个迭代循环中,都需要从DUT中获得类pkt2cmp的数据包对象。一旦得到数据包对象,将会通过out_box信箱传递给Scoreboard。
1.添加追踪语句
2.执行完追踪语句后。创建并行线程fork-join_none 为什么用fork-join_none?驱动器类使用的是fork-join
3.fork-join内创建无限循环块forever begin-end块
4.每个迭代的循环都需要执行以下操作:
a)调用recv()从DUT中取出数据包对象
b)复制pkt2cmp对象并将数据包对象存入out_box 不知道为什么要把对象复制后再存入信箱,直接存不可以吗?
【知识点:对象的复制,绿皮书p125】

在这里插入图片描述

任务七: Generator类

属性
1.声明字符串name
2.声明发送的数据包类pkt2send
3.声明数组信箱in_box,数组对应着存放16个端口地址,需要存放每个端口发送的数据包对象,每一个信箱对应16个driver
在这里插入图片描述

方法
封装之前test program中的gen()任务和start()任务
1.new()方法,参数传递name
a)添加跟踪语句
b)连接参数name与类属性的name
c)例化发送的数据包对象
d)使用new[ ]来设置数组信箱的大小,并对每个信箱开辟空间new()
在这里插入图片描述

2.将gen()剪切到类中
添加跟踪语句,需要注意的是gen()中不需要将数据包对象的值赋值给全局变量sa,da,payload了,因为数据传递的工作会在信箱中以对象的形式直接传递给其他验证组件。
在这里插入图片描述

3.start()任务,与之前Driver和Receiver中的start()有两个重要的区别:
a)start()方法内的循环由全局变量run_for_n_packets来控制,如果变量的值小于等于(<=)0,将执行无限循环;如果大于0,循环将在进行run_for_n_packets次后停止。
b)在调用gen()方法后,复制随机化后的数据包对象并将它通过信箱in_box传递给Driver。注意要通过句柄索引数据包对象中的输入端口sa来匹配对应的信箱
还是这个问题,不知道为什么要把对象复制后再存入信箱,直接存不可以吗?
在这里插入图片描述
在这里插入图片描述
这里的循环不能使用图一的forver begin-end块,否则无法跳出;

  • 或者可以在for begin-end的结尾处设置跳出forever的条件?
if(i == run_for_n_packets - 1) return;

不可以,返回语句不能用在并行线程内
在这里插入图片描述

任务七: Scoreboard类

计分板实际上就是比较器,用来封装之前写的check()程序。
它能实现数据包对象分别与Driver,Receiver的传递。驱动器将刚刚发送到DUT的数据包对象放入信箱driver_mbox,接收器将刚刚从DUT采样到的数据包对象放入receiver_mbox,比较的对象就是这两个信箱里的object。
当Scoreboard从receiver_mbox中找到一个数据包对象时,会将该对象句柄保存为pkt2cmp;然后将从driver_mbox中获得的数据包对象都推进 refPkt[$] 队列中;之后,根据pkt2cmp对象中的输出端口地址da,找到队列中相应位置的数据包Packet,然后比较它和pkt2cmp。如果找不到相应位置上的Packet,那么设置报错语句;当数据包对象的数量与全局变量run_for_n_packets相匹配时,事件event DONE触发。事件DONE将会允许仿真在合适的时间停止。

属性
1.声明字符串string
2.设置事件event DONE ,
3.声明数据包类pkt2send,pkt2cmp以及,存放类的队列refPkt[$]
4.声明存放来自Driver和Receiver的数据包对象的信箱driver_mbox,receiver_mbox
在这里插入图片描述

方法
一、new()方法
1.传递参数name,driver_mbox,receiver_mbox,并初始化字符串和信箱,信箱为null。
2.设置跟踪语句
3.将参数name与类属性的name连接
4.如果信箱driver_mbox为空,那么给信箱开辟空间new();参数信箱与类属性信箱连接
5.信箱receiver_mbox同上

在这里插入图片描述

二、start()任务
1.设置追踪语句
2.开辟并行线程fork-join_none,在块内执行无限循环forever begin-end,后续操作在循环体内执行
3.从receiver_mbox中取出数据包的句柄pkt2cmp
4.创建一个循环体,在driver_mbox信箱内的元素为空之前执行以下操作:使用while循环或者do-while比较好
a)从driver_mbox信箱内取出数据包,这个数据包类需要重新声明句柄

  • 和之前复制对象一样,为什么不直接用pkt2send?原因见调试过程

b)将取出的数据包从 refPkt[$] 队列末尾推入
5.调用check()任务。

在这里插入图片描述

三、check()任务
在之前编写的check()任务的基础上
1.声明整型int,队列,index[$],用来存放从driver_mbox信箱取出的对象与从receiver_mbox中取出数据包对象中的输出地址da相同的数据包对象的位置序列。这个队列里存放的是 refPkt[ $] 队列的位置信息,而非对象。
2.添加跟踪语句
3.定位 refPkt[ $] 队列对象中的da与pkt2cmp对象中的输出端口地址da相等的元素位置,将其赋值给队列index
【知识点:队列,动态数组的定位方法,这些方法的返回值是一个队列,绿皮书p35】
4.条件判断:如果在上述操作后队列index的长度是0,说明找不到输出端口匹配的对象。打印报错信息;并将输出端采样的数据打印出来,即执行pkt2cmp中的display()方法,记得传递字符串参数prefix ,ERROR即可;然后结束仿真。【注:打印语句参考之前的check()中编写的】
5.如果条件判定失败,说明有输出端口匹配的对象,接下来就需要比较对象的内容是否一致。首先将匹配得到的位置信息的第一位对应的对象赋值给pkt2send(这就是你所要比较的第一个对象,),然后delete这个对象,(删除这个对象是为了防止两次发送数据包具有相同的da这样下一次循环pkt2send就是下一个匹配到da相同的对象,不会重复),接着条件判断调用pkt2send的cpmpare()任务,传递参数为对象pkt2cmp,然后相应的打印语句【这部分就是之前check()的比较程序】
【 命令手册上说A queue is analogous to a one-dimensional unpacked array that grows and shrinks automatically.队列类似于一个一维未封装的数组。可以使用数组的定位方法】
在这里插入图片描述
6.条件判断:比较的的次数大于等于发送数据包的次数,触发事件DONE。

在这里插入图片描述

任务八: 修改test.sv使用这些类

对testbench进行修改,使它拥有一个generator,scoreboard,16个driver和16个receiver。
在这里插入图片描述
1.删除变量除了run_for_n_packets以外的其他全局变量
2.声明整型变量TRACE_ON并初始化为0(如果调试需要跟踪子程序则改为1即可)
3.在这之后,导入所有的头文件和类文件别忘了router_test.svh
4.然后添加以下全局变量:
semaphore sem[ ];设置旗语防止输出端口挤占
Driver drvr[ ];声明驱动器句柄,动态数组16个驱动器
Receiver rcvr[ ];声明接收器句柄,16个接收器
Generator gen;
Scoreboard sb;
5.删除initial块内容,保留仿真次数的设置,run_for_n_packets = 5
7.使用**new[ ]**设置动态数组的大小
6.所有类的例化都在initial块中进行。确保所有的信箱连接正确,即new()的参数传递正确。
发生器数据传递给所有的驱动器信箱(gen.in_box[i])
所有驱动器的数据传递到一个Scoreboard信箱(sb.driver_mbox)
所有接收器的数据传递到一个Scoreboard信箱(sb.receiver_mbox)
7.所有对象创建完后,启动所有的事务transaction,然后调用reset()复位DUT,reset()中添加跟踪语句
8.最后在程序结束前等待Scoreboard中事件DONE触发。

  • 考虑等待事件DONE是用wait还是@仿真结果没什么变化,两种都可?

【绿皮书p195,将事件作为仿真完成的标识信号,使用的是电平敏感的wait(DONE.triggered())

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

编译与仿真

编译

DriverBase.sv

1.条件编译的结尾*`endif*放在所有代码结尾
在这里插入图片描述
2.没有导入router_test.svh文件,以至于编译找不到Driver等类型
3.接口例化需要在声明前添加virtual否则会报错

  • 在interface定义时,如果不使用关键字 “virtual” 那么在多次调用该接口时,在其中的一个实例中对接口中某一信号的修改会影响其他实例接口;如果使用了 “virtual” 关键字,那么每个实例是独立的。
  • 习惯上在声明interface时均添加 "virtual"关键字
    在这里插入图片描述

Generator.sv

1.条件编译/宏定义命名有误,在相应的类前的宏定义名称要对应
在这里插入图片描述

编译test.sv

将新创建的类型文件的导入和声明的变量要放在其他类之前。否则编译时其他class找不到Packet,pkt_mbox,和变量run_for_n_packets,TRACE_ON
在这里插入图片描述

  • 直接使用da,索引不到
    在这里插入图片描述
    在这里插入图片描述
  • 加了this会报错对this的索引未解析不加this索引不到变量da和payload

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

**Driver.start()**里的变量全部用this索引,编译成功。在完成整个实验后,去掉了this发现编译依旧能成功没必要用this索引
在这里插入图片描述

仿真

变量打错了,修改后仿真成功
在这里插入图片描述
在这里插入图片描述

二、调试

仿真结果:一直运行,没有结果。
仿真器上显示pkt2send有随机值,但是波形上并没有采样到数据的发送。问题应该出在了generator和driver之间数据的传递。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
打印的跟踪语句
在这里插入图片描述

大概率是陷入了Driver.start的循环内
在这里插入图片描述
从验证代码来看,本应该是foreach同时调用16个drvr,以保证能有满足条件判断的drvr[sa]但是,并行线程采用了fork-join以至于第一个线程drvr[0]条件恰好成立执行了continue没办法完成后续语句,从而陷入了循环。
在这里插入图片描述


在这里插入图片描述
16个drvr都运行了start启动任务,但是只有sa = 13执行了send(),send_addrs()发送地址和数据的任务。
Generator.start()里数据包对象的复制
在这里插入图片描述
仿真结果,可以从追踪语句看到其他驱动器执行了send()任务。因此可能是对每次gen()中随机的pkt2send数据包对象都覆盖了上一次的pkt2send,这一点从正确的仿真结果的最后一次发送的数据就可以看出来。sa=13,da=1.恰好是之前的反正结果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将run_for_n_packet修改为2000,仿真会在#1984处即发送了1985次后停止,原因是在ReceiverBase.get_payload()内的fork-join内的fork-join_any里frame_n[4]等待了1000个cycles还没有等待下降沿。因此执行了$finish。将finish注释掉就能完整发送2000次。


总结

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值