SV中,fork-join,fork-join_any、fork-join_none的理解_fork join_any-程序员宅基地

技术标签: SV知识点  

fork join的用法

我们早在学习Verilog语言时就学过:相对于begin-end顺序执行的语句块,还存在fork-join并行执行的语句块。这些知识用起来已经很顺手了,但是当学习到SystemVerilog语言的时候,突然告诉你:其实fork-join还有两个失散多年的亲兄弟活着!那就是fork-join_any和fork-join_none!!!

这三个兄弟虽然长的比较像,但是其实性格是不一样的!他们的主要性格区别是他们对待称为“线程”的小朋友的态度上。“线程”小朋友是轻量级的“进程”,是程序调度的基本单位。假如某一时段同时来了好几个线程小朋友去他们家吃饭,三兄弟性格可以表现的非常明显。其中fork-join的性格是最温和耐心的,他会静静等待所有线程小朋友全部吃完饭才去做别的事情。而fork-join_any性格是最健忘和丢三落四的,当他看到其中某个线程小朋友吃完后会直接忘了别的小朋友还在吃,以为都完成了,直接去做自己的事情。至于fork-join_none则是脾气最暴躁的,他不会等待任何一个线程小朋友吃饭,会直接去搞自己的事情!

在这里插入图片描述

那fork-join_any、fork-join_none一个健忘症一个暴脾气,他们是猴子请来搞笑的吗?除了增加我们的概念记忆还有什么作用?

   考虑遇到这样一种情况:当某些线程小朋友是无限循环的,永远吃不完,而下面我们就想结束所有的程序,那一直等下去有时就会出问题。 这时对于fork-join_any这个健忘症,就可以有用武之地了。我们常常设立一个吃的最快的线程小朋友作为“组长”,而fork-join_any通过等待“组长”来控制这段程序运行结束。

   实际中的一个常见经典用法如下,(初学者不需要了解代码中每行的含义,只需要对fork-join_any的应用有一个直观认识即可)想在记分板中控制验证平台的结束时,通过配置num参数来控制想收到的实际数据包数量,运行完for循环就会执行drop_objection,结束平台运行。这里面的for循环就是我们前面提到的“组长”,如果没有fork-join_any,单纯的使用fork-join便会一直停不下来,是不能实现这个功能的了。

在这里插入图片描述

对于fork-join_none这个暴脾气,其实也是很有用处的。比如有这样一个需求:把某个相同的线程并行启动运行100个。这个需求用fork-join可以实现,但是你要在其中罗列100次这个线程:
在这里插入图片描述
这样写显然不合理,太麻烦了,如果更大的数那就是不可能完成的任务了。这时候fork-join_none就显示出了很好的作用,如下,配合for循环几行就可以达到启动的100个的目的。
在这里插入图片描述
值得一提的是,这两段代码作用其实是不等价的,通过fork-join_none运行的100个线程,是并行启动了,但是不等他们全部结束程序就会进行到后面的程序中去,如果想要等价可以在后面使用wait fork语句来等待所有线程结束,如下代码就与fork-join控制的完全等价了。
在这里插入图片描述

fork join none的坑

1. 回忆下fork-join_none

fork-join_none相信大家应该熟悉了,新来的朋友可以回顾下jerry之前的文章,就是之前jerry提到的那个“暴脾气”的哥们,他不会去等别人,直接会着急做自己的事情。
前文回顾(点击查看):fork-join挺好用的了,fork-join_any、fork-join_none有什么用?
回顾下那篇文章中我们举的一个例子,这个暴脾气可以这么用:

fork
aa( );
aa( );
aa( );
……
aa( );
aa( );
join

如上代码,我们想要并行的执行100个 aa( )这个函数进程。通过fork-join要写到手软,用这个暴脾气,几句话就搞定:

for(int i=0; i<100; i++)
fork
aa( );
join_none

但是,今天jerry告诉各位初学者,这个暴脾气有不好驾驭的那一面的哦,弄不好就很容易翻车!!

2. fork-join_none翻车现场

什么情况下容易翻车呢?

大家仔细看看上面的例子,并行运行的aa( ),都是一样的内容,放在for循环中,却并没有使用for循环的循环因子 i 啊~

有人说,这有什么关系吗?
好的,来,看看jerry今天准备的代码,逼出它的邪恶面!

我们还是通过暴脾气fork-join_none,外加for循环,这次我们用上for的循环因子i,
怎么用i呢?我们直接通过$display来打印,打出10个选美者的编号和颜值等级:

for (int i=0 ; i<10; i++)
fork
$display(“No%0d,My face_grade is %0d”, i ,i );
join_none

大家先不看答案,先猜猜,这个会怎么打?

算了,jerry先猜猜你们是怎么想的?是不是打印出下面这样?

No0,My face_grade is 0
No1,My face_grade is 1
No2,My face_grade is 2
No3,My face_grade is 3
……
No9,My face_grade is 9

大错特错!!太天真了!这个时候这个暴脾气只会在电脑的某个角落里看着你笑着说“愚蠢的人类”!!
jerry告诉你打印出来的是什么:

No10,My face_grade is 10
No10,My face_grade is 10
No10,My face_grade is 10
……
No10,My face_grade is 10

太阴险了!怎么会是这样呢?我0-9怎么还出来10了?

3. 再认识下for循环

先解释下这个for循环范围0-9,怎么打出来10了?
看下这段代码:

int apple_num;
for (apple_num=0 ; apple_num<10; apple_num++);
$display(“i have %0d apples”, apple_num );

直接告诉大家,这个代码打印出来的是:

i have 10 apples

这个代码,for循环是执行的一个空语句,for结束后才进行打印循环因子。让不注重细节的伙伴们再认识下for,for在最后执行完成他的值是还要再走apple_num++的,正是因为加到了10,才不满足apple_num<10的条件不再进行循环下去了。
我们再回头看看这个代码:

for (int i=0 ; i<10; i++)
fork
$display(“No%0d,My face_grade is %0d”, i ,i );
join_none

现在知道这个打印出来10是怎么来的了,是for循环执行完了以后循环因子i的值啊!!

好像差不多理解了:for循环的时候依次创建了10个进程,然后等for循环结束后,才并行执行10个fork进程。

因为fork-join_none,for全部循环完了以后, 10个 d i s p l a y ( “ N o display(“No%0d,My face_grade is %0d”, i ,i ); 才并行的执行完!!在打印的时候得到的i值就是最后的10了。换句话理解:这10个并行的 display(Nodisplay里面的i其实是同一个int i,i++是会改变它的。

4. 怎么防止它的翻车

来,jerry先直接告诉各位怎么解这个问题:

for (int i=0 ; i<10; i++)
fork
automatic int j=i;
$display(“No%0d,My face_grade is %0d”, j ,j );
join_none

这段代码打印的正是我们期望的:
No0,My face_grade is 0
No1,My face_grade is 1
No2,My face_grade is 2
No3,My face_grade is 3
……
No9,My face_grade is 9

为什么呢?我们来分析一下:

如上代码,我们加了一个automatic int j=i 转了一下,把i给j,我们打印j。

此处automatic类型,意味着进入fork进程被创建,结束被撤销。保证了10个并行的display语句,每个进程中的j是它自己的,是独一无二的(不清楚automatic和static的区别的可以自己查或者关注jerry后面的文章哈)。

先不要恍然大悟,仔细想想,仅仅保证了独一无二,就行了?automatic int j=i;

这句话还没执行,for不就应该执行完了?那这里的i岂不是还是应该是10??

是啊!除非……?

没错!
automatic int j=i;在i++之前就执行了!!!

我们来验证下,假如这么写:

for (int i=0 ; i<10; i++)
fork
#0;
begin
automatic int j=i;
$display(“No%0d,My face_grade is %0d”, j ,j );
end
join_none

果然就又出错打印成下面这样了!!!

No10,My face_grade is 10
No10,My face_grade is 10
No10,My face_grade is 10
……
No10,My face_grade is 10

其实不要说那样,就即便如下这样都是不对的!!

for (int i=0 ; i<10; i++)
fork
automatic int j;
j=i;
$display(“No%0d,My face_grade is %0d”, j ,j );
join_none

看来除了保证独一无二,更关键的原因是执行顺序!!

什么执行顺序呢?

简单的说,如果把我们这段代码理解为两个过程:“创建进程”、“执行进程”。

创建进程的时候,创建10个并行的进程,然后统一执行。

这句神奇的automatic int j=i;偏偏就是在创建进程的过程中就执行了!

大家可以看一下下面的视频,DVE上的断点单步调试,上面提到的两种代码执行顺序对比:

先执行的94行for进入第一段代码“创建线程”阶段,然后马上95行神奇的automatic int j=i;可见它也是第一段代码“创建线程”阶段执行的!然后并没有接着执行96行的display,而是101行的for!进入了第二段代码的“创建线程”阶段!线程都创建完成之后才再回去96行进入执行进程阶段,执行了display,最后执行了102行的display。

各位初学者可以这样简单的理解这段代码,但是其实呢要更进一步探究就涉及到了
sv的仿真调度机制!!!

先简单看一眼,就是这些个东西啦:

我擦,短短几句代码需要想到这么多知识吗?这里这个调度机制我们就先不深究了,大家先擦擦汗,jerry后面的文章会娓娓道来的~

我们回到今天要讲的重点:“for循环+fork-join_none结构”的坑,怎么处理呢?最简单的一种方式就是用一个automatic int j=i 转一下,一定要在fork的一开始定义,并且赋值。

disable fork用法

SystemVerilog允许大家在使用fork + join/join_any/join_none创建进程之后,通过disable fork来提前结束这些进程。

例如下面的代码片段1,fork + join_any产生了两个并行的子进程:

第一个子进程等待valid信号,然后打印第12行的信息;

第二个子进程等待max_delay个ns,然后打印第16行的信息。

不论是哪一种结果,都会导致join_any跳出fork,接着执行disable fork来结束这个fork进程及其子进程。

在这里插入图片描述

代码片段1

这个task在等待valid的同时,为了避免长时间等待,加了一个超时机制。不论是等到valid,还是超时了,都不必再等待另一个子进程继续执行下去。这段代码乍一看好像没什么问题啊?

别急,继续往下看。

假如还有另一个task B,需要在启动task A之前启动,常见的做法就是先fork + join_none的方式启动B,再启动A。

如下面代码片段2所示。
在这里插入图片描述
代码片段2

执行task C,会惊奇的发现:不论task A里面是否wait valid成功,当执行后面的disable fork之后,task B始终都没有打印第27行的信息?

为什么会这样?是不是开始怀疑人生了?

别急,这是因为当disable fork的时候,不仅杀掉了task A里面的fork进程,连task C里面的fork/join_none进程也杀死了。disable的杀伤力,远远超出了想象,有没有?

不是我不小心,只是……

要避免这样的误杀,办法其实很多。最常见的做法是添加所谓的guard fork,来限制disable fork的作用范围。

如下面的代码片段3所示:
在这里插入图片描述

代码片段3

还有一种不太好做法是给fork的进程添加别名,然后disable这个指定的进程,如下面的代码片段4所示:

在这里插入图片描述

代码片段4

这种做法看似也OK,但是会引入另外一种风险。思考一下,不知道你是否猜到了?

Q哥带你揭晓答案。

如下面所示的代码片段5,task D里面通过fork join同时启动了两个调用task A的子进程并行执行。当调用A(1000)执行到disable p1的时候,会惊奇地发现,A(2000)也被意外地终结了。

在这里插入图片描述

代码片段5

给fork进程命名,弄巧成拙了。推荐大家还是使用guard fork,这是一种良好的coding style。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44969124/article/details/108175684

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf