1.线程的使用
- 从硬件角度,Verilog通过always、initial过程块和信号数据连接实现进程间通信。
- 可将不同module作为独立的程序块,其同步通过信号的变化(event触发)、等待特定事件(时钟周期)或者时间(固定延时)来完成。
- 线程即独立运行的程序,需要被触发,可以结束或者不结束。
- 验证环境中的对象可以创建和销毁,软件测试端的资源占用是动态的。
- 软件环境中的initial块有两种分组方式,
begin...end
串行执行或者fork..join/join_any/join_none
并行执行。
2.线程的控制
fork...join
: 所有线程均执行完才会继续向下执行。fork...join_any
: 任何一个线程执行完就可继续向下执行。fork...join_none
: 无需任何一个线程执行完就可继续向下执行,实际上是不等线程执行就已经退出了。
⭐在sv中,initial块全部执行完毕,仿真器就退出了。如果希望等待所有线程执行完毕,可以使用wait fork...
语句来等待所有子线程结束。
⭐停止线程:在使用了fork...join_any
或者fork...join_none
之后可以使用disable xx
停止指定的线程;注意在这种情况下所有衍生的同名线程都将被禁止。
3.线程间的通信
※线程间的通信:测试平台中所有的线程都需要同步并交换数据。等待另外的线程;多个线程可能同时访问同一个资源;线程之间可能需要交换数据。
-
event事件。
1)@
边沿敏感,阻塞、等待事件的变化。可通过->
操作符来触发事件。无需new
,但可以看作一个对象。
2)可用电平敏感的wait(xx.triggered())
来替代边沿敏感的阻塞语句@
。比起@
,可以避免在相同时刻出发event而带来的竞争问题,但同样无法捕捉已经被触发但后续才等待的事件。
3)最简单的也可用1bit的flag实现。 -
semaphore旗语
1)semaphore可以实现对同一资源的访问控制。
2)基本操作:new()
创建带单个或多个钥匙的semaphore;get()
获取一个或多个钥匙;put
返回一个或者多个钥匙。此外还有try_get()
函数可以试图获取钥匙且不被阻塞,返回1
且拿到钥匙或者返回0
表示钥匙不够。 -
mailbox信箱
1)线程之间传递信息,可以使用mailbox。与队列有相似之处。
2)基本操作:使用new()
进行例化,可以指定size,若无指定则size可以是无限大;put()
把数据放入mailbox;get()
从信箱移除数据(如果信箱为满或者空,put()
或者get()
会阻塞)。peek()
可以获取对信箱里数据的拷贝而不移除数据。相应地,try_put()
、try_get()
、try_peek()
为非阻塞方法。
3)使用队列也可实现类似mailbox的操作。区别在于queue
的push_back()
和pop_front()
方法是非阻塞的,所以在取数时需要格外判断wait(queue.size()>0)
。需要注意的是只可在task中调用阻塞方法(耗时),而非阻塞方法既可在task也可在function中调用。
4)此外,注意形式参数的声明是否应为ref
方向,如果默认input
方向,则在传递过程中发生的是数组的拷贝,内部的操作并不会影响外部数组本身。
5)如果需要显式地限定mailbox中的元素类型,则可以通过mailbox #(type = T)
的方式来声明。
※总的来说,如果要在同步事件的同时,完成一些数据传输,那么更合适的是mailbox,因为它可以用来存储一些数据;而event和semaphore更偏向于小信息量的同步,即不包含更多的数据信息。