来源| 杰瑞IC验证(ID:Jerry_IC)
|原创作者| Jerry
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个$display(“No%0d,My face_grade is %0d”, i ,i ); 才并行的执行完!!在打印的时候得到的i值就是最后的10了。换句话理解:这10个并行的$display里面的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
f
这段代码打印的正是我们期望的:
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的一开始定义,并且赋值。
今天这个知识点很常见,很重要哦,希望各位初学者不要着急,像这样的知识点,jerry聊一个就记住一个就好,不怕慢只怕站~哈哈,继续关注jerry后面的文章,一起成长~加油!
——The End——