最近在做自研总线相关的验证,遇到的情景是2048个master和2048个slave同时并行发送和接收数据,这部分必然会用到systemverilog中fork相关的语法,而在这个过程中,遇到了从未见过的复杂组合,包括wait fork及disable fork作用域的问题。很多的理解需要做简单的仿真来进行实验,进而总结结论,加深印象,现总结如下。
例子1
多个initial块之间是并行执行的
在initial块内部,可以写begin……end的顺序结构,也可以写fork的并行结构。(包括fork……join,fork……join_none,fork……join_any)
如果是多个begin……end,是从上到下顺序执行的。
不太常见的写法,initial fork …… join结构
因此,了解这些语句执行顺序的关键首先要明白四种过程块结构的规则
- begin……end 内部的语句依次按顺序执行
- fork……join 内部语句同时开始执行,最晚的线程结束后,才会退出fork……join过程块,继续执行之后的语句
- fork……join_any 内部语句同时开始执行,最早的线程结束后,即可走出fork……join_any过程块,继续执行之后的语句,与此同时,fork……join_any内未执行完的线程仍在继续执行,多个线程在并行执行。
- fork……join_none 内部语句同时开始执行,但是并不会阻塞后面的线程,fork……join_none之后的线程会同时执行,多个线程同时并行执行。
sv绿皮书上很经典的一张图。对于fork……join_any和fork_join_none而言,跳出fork块,并不意味着fork块内还没有执行完的线程就会结束终止,而是仍在并行执行。
fork……join
- 仿真开始,运行10ns,然后进入fork块,四条语句同时开始执行
- 第10ns,01 print,02 print
- 第20ns,fork join块结束
- 第30ns,03 print
- 第40ns,04 print
fork……join_any
- 仿真开始,运行10ns,进入fork块,四条语句同时开始执行
- 第15ns,02 print,语句执行完成,走出fork块,其他线程继续执行 ,后续线程开始执行
- 第20ns,01 print,fork块内所有线程执行完成
- 第25ns,03 print
- 第35ns,04 print
fork……join_none
- 仿真开始,运行10ns,进入fork块,四条语句同时开始执行,同时,fork块之后的语句同时开始执行
- 第15ns,02 print
- 第20ns,01 print, 03 print,fork块内所有线程执行完成
- 第30ns,04 print
当fork块和begin end块连用和嵌套时,分析的基本原则是,从外到内,层层分析。遇到嵌套时,先把过程块当成一个整体。
fork……join中嵌套begin……end
- 仿真开始,运行10ns,进入fork块,在fork块内部,有四条语句,还有一个begin……end块,先把begin……end块当成一个整体,所以,begin end块和四条语句是并行执行的,而在begin end块内部,有三条语句,这三条语句是顺序执行的
- 第10ns,进入fork块,四条语句和begin……end块同时开始执行
- 第15ns,02 print
- 第20ns,01 print,03 print
- 第30ns,在begin end块中,74行的延时执行完成
- 第35ns,04 print,begin……end块执行完毕,走出fork join块
- 第45ns,05 print
- 第55ns,06 print
fork……join_none中很容易犯的书写错误
忘记在fork……join_none中添加 begin end,导致fork……join_none块里的语句都是并行的,这会导致很奇怪的现象,不容易debug出来。
fork……join_any中嵌套begin……end
- 仿真开始,运行10ns,进入fork块,在fork块内部,有四条语句,还有两个begin……end块,先把begin……end块当成一个整体,所以,两个begin end块和四条语句是并行执行的,而在begin end块内部,有三条语句,这三条语句是顺序执行的
- 第10ns,进入fork块,四条语句和两个begin end块同时并行执行
- 第15ns,02 print,此时fork……join_any块中最早的线程完成,走出fork块
- 第20ns,01 print,03 print,05 print
- 第25ns,07 print
- 第30ns,无打印
- 第35ns,04 print,06 print, 08 print
fork……join_none中嵌套begin……end
- 仿真开始,运行10ns,进入fork块,在fork块内部,有四条语句,还有两个begin……end块,先把begin……end块当成一个整体,所以,两个begin end块和四条语句是并行执行的,而在begin end块内部,有三条语句,这三条语句是顺序执行的
- 第10ns,进入fork块,四条语句和两个begin end块同时并行执行,同时开始执行fork块之后的语句
- 第15ns,02 print
- 第20ns,07 print,01 print,03 print, 05 print
- 第30ns,08 print
- 第35ns,04 print,06 print
不管有多少层级嵌套,都要遵守分析规则,由外到内,整体法层层分析
对自己狠一点,整一个复杂的嵌套,
从外到内,层层分析
在initial块的最上层,是begin end,这一层begin end中,可以看做四部分,第130行,第131~157行fork块,第158行,第159行。这四部分是顺序执行的。
进入fork块内部 ,fork……join_none内部,可以看做四部分,第132~137行fork join块,第138~144行fork join_any块,第145~151行begin end块,第152~156行begin end块,这四部分在fork……join_none块中是并行执行的。
第10ns,进入fork块,与此同时,fork块之后的语句也开始执行
第15ns,02 print
第20ns,09 print,01 print, 03 print,05 print,07 print,此时第132~137行fork join块结束
第25ns,04 print,06 print
第30ns,10 print
第35ns,08 print
利用循环产生多个并发线程
一种错误的写法
详细原因可以参考绿皮书,4次for循环,产生4个fork join_none,这4个同时开始,并发执行,但是产生这四个的时候,循环变量i已经到4了,因此最终打印的都是4
正确写法应该是写一个自动变量,将每次循环的值copy下来
把自动化变量的赋值,写在fork……join_none内部也是可以的
将这种写法等价于再begin……end块中,有多个fork……join_none块
此外,还有另一种写法,把for循环写到fork……join_none内部,注意这种写法和上面的写法结果是有本质差别的。
把foreach块当作一个整体,fork join_none只有一个线程,且会立即执行fork join_none之后的代码,但是之后for循环的执行,会按照begin end来顺序执行
按照deepseek的说法:
- 执行逻辑:
fork join_none会将foreach循环当作一个整体来并发执行。也就是说,foreach循环的所有迭代会被一次性地提交到事件队列里,接着fork join_none块会立刻退出,后续的代码会继续执行。- 时间顺序:由于
foreach循环的所有迭代是一次性提交的,各个迭代里的延迟时间会决定它们执行的先后顺序。在上述示例中,array[0]会在0时刻显示,array[1]会在10时刻显示,依此类推。- 并发情况:
foreach循环的所有迭代是并发执行的,不过它们的显示顺序会由各自的延迟时间来决定。
再看下面一个例子
在begin end中有四个fork join_none,还有两个语句,这些都是顺序执行的,而fork join_none不会阻塞之后的线程
第5ns,05 print
第7ns,06 print
第10ns,01 print,02 print,03 print,04 print
begin end块中,有一个fork join,有一个语句,这两个是顺序执行的,需要先执行完fork join
在fork join块中,有四个fork join_none,还有一个打印语句,这五个线程在fork join中是并行的,fork join_none可以认为立即执行完毕,并不是fork join_none中的语句执行完毕,才会退出fork join
第5ns,05 print,走出fork join
第7ns,06 print
第10ns,01 print,02 print,03 print,04 print
wait fork的用法
作用域是什么?到底等的是哪些线程结束
disable fork的用法