路科V0P23P25线程控制-线程同步-线程通信
线程控制
什么是线程
- 线程是独立运行的程序;
- 需要被触发,可以结束或者不结束;
- 在module中的initial和always,都可以看做独立的线程,会在仿真0时刻开始,选择结束或者不结束;
- 硬件模型中,由于都是always语句块,所以可以看成是多个独立运行的线程,他们一直占用着仿真资源,因为不会结束;
- 验证环境中需要由initial语句块去创建,在仿真过程中,验证环境中的对象可以创建和销毁,因此验证环境中的资源是动态的。
- 验证环境中的initial语句块有两种分组方式,begin..end和fork..join
- begin..end : 顺序方式执行
- fork..join : 并发方式执行
- 与fork..join类似的,还有fork..join_any和fork..join_none
- 线程的执行轨迹是树状结构,任何线程都有父线程;
- 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程;
- 子线程终止时,父线程可以继续执行;
- 父线程终止时,它的所有子线程都会终止;
并行线程
- fork..join : 等待所有子线程结束,才会继续执行后面的;
- fork..join_any : 只要有一个子线程结束(最短的先结束),就会继续执行后面的;
- fork..join_none : 不需要等待任何子线程结束,就可以继续执行后面的;
- 注意 : 虽然无需等待,但是fork..join_any和fork..join_none执行后面的时候,前面的子线程还在执行;
- 如果要等待这些子线程都完成,或者停止这些子线程,可以使用
wait fork
或者disable fork
;
1 | fork |
时序控制
- SV中可以通过延迟控制或者事件等待来完成时序控制;
- 延迟控制通过
#
完成;#10 rega = regb;
- 事件控制(event)通过
@
完成;@r rega = regb;
@(posedge clock) rega = regb;
wait
语句可以与事件或者表达式结合,来完成时序控制;real AOR[]; initial wait(AOR.size() > 0) ...;
线程同步
概述
- 测试平台中所有线程都需要同步并交换数据;
- 一个线程等待另外一个,比如验证环境需要等待所有激励结束,比较结束才能结束仿真;
- 比如监测器需要将数据发送到检查器,检查器又需要从不同的缓存获取数据进行比较;
线程同步的第一个类型:event事件
- 可以通过event声明一个event变量,并且触发它;
- 这个event变量可以用来控制多个线程间的同步;一端触发,另一端阻塞等待;
- 通过
->
操作符触发事件; - 其他等待该事件的线程可以通过
@
操作符或者wait()
来检查event的触发状态; wait_order()
方法: 可以使线程保持等待,直到在参数列表中的事件event按照从左到右的顺序依次完成;- 如果参数列表中的事件被触发,但是没有按照要求顺序,也会失败;
1 | event done, blast; |
线程同步的第二个类型:旗语semaphore
- 旗语可以看做打开共享资源大门的钥匙;用于访问控制保护;
- 创建旗语时,会为其分配固定的钥匙数量;
- 使用旗语时,必须先获得钥匙,才能继续执行;
- 旗语的钥匙数量可以有多个,等待旗语钥匙的线程也可以有多个;
- 旗语通常用于互斥,对共享资源的访问控制,以及基本的同步;
旗语的创建
- 创建旗语,并为他分配钥匙的方式:
semaphore sm;
sm = new();
- 创建一个固定钥匙数量的旗语,
new(N)
- 从旗语那里获取一个或者多个钥匙(阻塞型):
get(N=1)
- 将一个或者多个钥匙返回到旗语中:
put(N=1)
- 尝试获取一个或者多个钥匙,而不阻塞:
try_get(N=1)
- new函数默认为0,但是可以put超过开始的数量值;
- new()返回旗语的句柄;
- put() : 如果其他进程在等待旗语,则应该在该进程有足够数量钥匙的情况下返回;
- get() : 如果指定数量的钥匙可用,则方法返回并继续执行,如果不足,进程阻塞直到钥匙数目充足;
- 旗语的等待队列是FIFO的,先排队的先获得;
- try_get() : 如果指定数目可用,返回正数并继续执行,否则,返回0;
线程通信
除了event、semaphore之外,还有mailbox信箱
信箱可以放置任何类型,可以设置尺寸大小,防止存储数据过多占用资源,信箱也是FIFO的
信箱的内建方法
- new() : 创建信箱,默认尺寸bound为0,表示不限制大小,否则限制为最大值N;
- put() : 将信息写入信箱,如果信箱已满,则put被挂起,直到可以有新的空间;
- try_put() : 尝试写入信箱,不发生阻塞;如果满,不阻塞,返回0,否则成功返回1;
- get() : 从信箱中获取信息,并取出
- peek() : 获取信息,但是不取出,只是拷贝,如果信箱为空,peek会挂起,直到有消息;
- try_get() try_peek() : 非阻塞取出
- num() : 获取信箱信息的数目;
参数化信箱
- 虽然信箱可以存放各种数据类型,但是为了之后用的方便,在声明时最好指定存储类型;
- 这种参数化信箱的方式可以在编译时就能检查出类型不匹配的情况;
1 | typedef mailbox #(string) s_box; |
信箱和队列的区别
- 信箱必须通过new例化,但是队列只需要声明即可;
- 信箱的存取方法put()和get()是阻塞方法,但是队列的存取方法,push_back()和pop_front()方法是非阻塞的,会立即返回;
- 在传递形参时,如果是input方向,信箱类型传递到是句柄,而队列类型则是完成的队列内容的拷贝;
线程通信的比较
- event : 最小信息量触发,即单一通知功能,可以用来做事件的触发,也可以多个事件组合起来用作线程同步;
- semaphore : 共享资源的卫士,如果多线程对某一共享资源做访问,则可以使用这个要素;
- mailbox : 精小的SV原生FIFO,在线程之间做数据通信或者内部数据缓存时考虑使用这个元素;
路科V0P23P25线程控制-线程同步-线程通信
https://dustofstars.github.io/IC验证/路科V0/路科v0p23p25线程控制-线程同步-线程通信/