Systemverilog中fork...join等线程的使用
Systemverilog中fork...join等线程的使用
- 前言
- fork...join 三个变体的区别
- fork...join 三个变体的典型应用场景
- fork...join示例
- fork...join_any示例
- fork...join_none示例
- 线程控制方法
- wait fork
- disable fork
- 注意事项
- 循环中的自动变量
- join_none与#0延迟
前言
在 SystemVerilog 中,代码总是以initial块启动,从0时刻开始执行,并且顺序执行代码块。但在某些场景下,必须需要某些代码块并行执行,这时候就需要用到fork...join了。
fork...join及其变体是创建并发线程(并发进程)的核心机制,是测试平台(testbench)中实现多线程行为的核心结构之一,其是构建测试平台(Testbench)、实现并行激励和检查的关键。它允许在仿真中同时运行多个语句或任务,而不是像传统软件那样顺序执行。
fork…join 三个变体的区别
如上图所示,fork...join还存在fork...join_any、fork...join_none两个变体,这三者的区别在于何时解除对父线程的阻塞。具体区别如下:
| 进程类型 | 行为描述 | 类比 |
|---|---|---|
| fork…join | 等待所有的子线程完成之后才可以继续执行后续代码 | 整个部队全队集合完毕才可以出发 |
| fork…join_any | 任意一个子线程完成之后,就可以继续执行后续代码,其余未完成的线程会继续执行 | 只要有一个人都到达终点,教练就继续下一个项目 |
| fork…join_none | 立刻执行后续的代码,不等待任何子线程,所有子线程在后台并发执行 | 发令枪响,运动员开跑,裁判立刻去忙别的事 |
fork…join 三个变体的典型应用场景
- fork…join的典型应用场景:
- 并行测试:同时驱动多个接口,且等待全部发送完成后检查结果;例如在验证环境中同时发送多笔事务,所有事务处理完成后再统一进行对比;
- 多通道数据采集:从多个接口信号采集数据,要确保所有接口均采集完成后再进行下一步处理;
- fork…join_any的典型应用场景:
- 超时保护:当前执行的任务和延迟计数器并行运行,当前任务和延迟计数器哪个先完成就退出等待;
- 竞争仲裁:若多个模块同时请求资源,哪个模块先完成就先获取到资源;
- fork…join_none的典型应用场景:
- 看门狗:启动一个信号状态监视的进程,该进程用于看门狗,不影响主进程的运行;
- 动态线程池:可在循环中不断启动子线程,而主线程可以立刻执行其他操作,无需等待每个子线程结束;
fork…join示例
initial begin $display("@%0t UVM_Frequency: fork...join Start",$time);fork #10$display("@%0t UVM_Frequency: process A",$time);#5$display("@%0t UVM_Frequency: process B",$time);#15$display("@%0t UVM_Frequency: process C",$time);join $display("@%0t UVM_Frequency: fork...join end",$time);end在上述示例中,最终的打印信息为:
@0UVM_Frequency:fork...join Start @5ns UVM_Frequency:process B @10ns UVM_Frequency:process A @15ns UVM_Frequency:process C @15ns UVM_Frequency:fork...join end在UVM环境中的运行结果如下:
fork…join_any示例
initial begin $display("@%0t UVM_Frequency: fork...join_any Start",$time);fork #10$display("@%0t UVM_Frequency: process A",$time);#5$display("@%0t UVM_Frequency: process B",$time);#15$display("@%0t UVM_Frequency: process C",$time);join_any $display("@%0t UVM_Frequency: fork...join_any end",$time);end在上述示例中,最终的打印信息为:
@0ns UVM_Frequency:fork...join_any Start @5ns UVM_Frequency:process B @5ns UVM_Frequency:fork...join_any end @10ns UVM_Frequency:process A @15ns UVM_Frequency:process C在UVM环境中的运行结果如下:
fork…join_none示例
initial begin $display("@%0t UVM_Frequency: fork...join_none Start",$time);fork #10$display("@%0t UVM_Frequency: process A",$time);#5$display("@%0t UVM_Frequency: process B",$time);#15$display("@%0t UVM_Frequency: process C",$time);join_none $display("@%0t UVM_Frequency: fork...join_none end",$time);end在上述示例中,最终的打印信息为:
@0ns UVM_Frequency:fork...join_none Start @0ns UVM_Frequency:fork...join_none end @5ns UVM_Frequency:process B @10ns UVM_Frequency:process A @15ns UVM_Frequency:process C在UVM环境中的运行结果如下:
线程控制方法
为了管理好fork...join的并发线程,systemverilog提供了两个重要的控制语句:wait fork和disable fork。
wait fork
该语句用于等待当前进程中的所有子进程全部结束后才会结束仿真,包括fork...join_none产生的进程,具体如下:
- 等待所有子进程结束:
wait fork会等待当前进程直接或间接创建的所有子进程都终止(包括执行完成或者被disable fork强行终止); - 灵活的同步点:与
fork...join必须在同一个fork中等待所有子进程结束不一样,wait fork可以在后续任意时刻执行,其允许先启动一部分进程,执行一些其他操作,最后统一等待他们完成;即wait fork可跨多个fork块,等待调用者所有活跃的子进程结束; - 避免进程泄漏:若不使用
wait fork,则有可能在父进程执行结束之后,尚未完成的子进程仍在后台运行,有可能会导致信号意外被驱动或者资源占用; - 注意事项:
- 只能作用于
initial、always、task、function等过程块; - 不关心其他进程,只关心当前进程的后代;
- 若当前进程没有子进程,则
wait fork会立即返回,不阻塞执行其他进程;
- 只能作用于
- wait fork代码示例:
initial begin $display("[%0t]UVM_Frequency: paraent process start",$time);fork begin #10;$display("[%0t]UVM_Frequency: son process A end",$time);end begin #20;$display("[%0t]UVM_Frequency: son process B end",$time);end join_none// 父进程不阻塞$display("[%0t]UVM_Frequency: paraent process continue",$time);#5;$display("[%0t]UVM_Frequency: paraent process wait son process",$time);wait fork;// 等待所有子进程结束$display("[%0t]UVM_Frequency: all son process end,paraent process continue",$time);#10$finish;end上述代码的运行结果为:
[0]UVM_Frequency:paraent process start[0]UVM_Frequency:paraent processcontinue[5]UVM_Frequency:paraent process wait son process[10]UVM_Frequency:son process A end[20]UVM_Frequency:son process B end[20]UVM_Frequency:all son process end,paraent processcontinue在UVM环境中的运行结果如下:
disable fork
该语句用于终止当前进程中的所有子进程,强行停止这些进程。常用于超时控制、异常退出等,具体如下:
- 终止所有由当前进程派生的子进程:
disable fork会终止包含fork直接创建的进程及子进程内部再次fork产生的后代进程,并将其递归终止; - 释放资源:
disable fork可以停止一些不需要的后台监控、超时计数器等,避免其产生意外的副作用; - 与wait…fork一起配合完成精准控制:可以先用
disable fork终止一些不需要的进程,再用wait fork等待剩余进程自然结束,确保完成同步; - 注意事项:
- 只能作用于
initial、always、task、function等过程块; - 其作用于调用它的那个进程所拥有的子进程,而不会影响兄弟进程或父进程的其他分支;
- 谨慎使用
disable fork,若滥用disable fork则会有概率导致难以调试的竞态条件,其会突然终止正在运行的进程,从而导致某些信号处于不确定状态;
- 只能作用于
- disable fork代码示例:
initial begin fork begin:outer $display("[%0t]UVM_Frequency: outer start",$time);fork begin:innerA #20;$display("[%0t]UVM_Frequency: inner A end",$time);end begin:innerB #40;$display("[%0t]UVM_Frequency: inner B end",$time);end join_none #10;$display("[%0t]UVM_Frequency: outer end",$time);end join_none #5;$display("[%0t]UVM_Frequency: paraent process continue executing disable_fork",$time);disable fork;// 终止外层及其所有内层子进程#10$finish;end上述代码的运行结果为:
[0]UVM_Frequency:outer start[5]UVM_Frequency:paraent processcontinueexecuting disable_fork在UVM环境中的运行结果如下:
即在5ns的时候disable fork终止了外层进程outer,进而递归终止了其内部的内层进程innerA和innerB,因此innerA、innerB、outer的结束打印信息均不会出现。
注意事项
循环中的自动变量
在fork循环中,如果使用静态变量(如下面示例中的i),那么产生的所有线程将会共享该变量,从而会导致结果有误,错误示例如下:
for(inti=0;i<10;i++)begin fork $display("UVM_Frequency: i = %0d",i);// 危险!所有线程看到的i都是最终值10join_none end上述代码在UVM环境中的运行结果如下,所有线程看到的i都是最终值10。
正确做法是使用automatic,具体如下:
for(inti=0;i<10;i++)begin automaticintj=i;fork $display("UVM_Frequency: j = %0d",j);join_none end上述代码在UVM环境中的运行结果如下:
join_none与#0延迟
当采用fork...join_none时,其启动的线程虽然立刻开始调度,但是在#0内(即零延时),父线程的后续代码会优先执行。
若需要子线程先执行的话,可以采用mailbox等同步机制。