fork join和for循环结合的实际应用

本文详细介绍了Verilog中的fork...join、fork...join_any和fork...join_none三种并行结构的工作原理及其在实际应用中的区别。通过示例代码展示了如何在并行执行任务中处理变量共享和同步问题,强调了在for循环中使用fork...join-none配合动态变量和waitfork来实现并行进程的正确同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

fork join 结构中,父进程会被阻塞直到所有的子进程结束;
fork...join_any 结构中,父进程会被阻塞直到其中任意一个子进程结束;
fork.... join_none 父进程会立即与产生的所有进程并发执行。
fork中每条语句并行执行 ,如果将多个语句封装在一个 begin end 块中会将整个块 作为单个进程执行,其中每条语句顺序地执行。
实际应用:
实际应用中,我们通常会使用了for循环和fork join_none来并行启动多个进程(这样不用写哪些重复的代码)。
for+fork...join
首先说明使用for+fork...join是 不能实现并行启动多个进程,因为fork...join是会阻塞父进程的,外部的for循环是主程序一部分,所以fork...join也会阻塞for循环的进行。如下代码的结果·中,从打印的时间,看三次for循环并没有并行(即使使用automatic也一样的,这是fork...join阻塞父进程决定的):
`timescale 1ns/1ns

module top();
	int a;
	int i;

	task lab(int i);
		a<=i;
		#1;
		$display("a=%0d,time=%0t",i,$time);
	endtask
	initial begin

		for(int j=0;j<3;j++) begin
			fork
				$display("j=%0d,time=%0t",j,$time);
				lab(j);
			join
		end
		$display("main,time=%0t",$time);
		#10;
		$finish;
	end
endmodule

结果:

*Verdi* : Begin traversing the scopes, layer (0).
*Verdi* : Enable +all dumping.
*Verdi* : End of traversing.
ucli% run 1000ns
j=0,time=0
a=0,time=1
j=1,time=1
a=1,time=2
j=2,time=2
a=2,time=3
main,time=3
$finish called from file "lab13_fork.sv", line 22.
$finish at simulation time                   13
           V C S   S i m u l a t i o n   R e p o r t 

for+fork...join_none

fork...join_none则不会阻塞父进程,因此使用for+fork...join_none才能实现并行启动多个进程。当我们将上面代码改为fork...join_none:

代码:

`timescale 1ns/1ns

module top();
	int a;
	int i;

	task lab(int i);
		a<=i;
		#1;
		$display("a=%0d,time=%0t",i,$time);
	endtask
	initial begin

		for(int j=0;j<3;j++) begin
			fork
				$display("j=%0d,time=%0t",j,$time);
				lab(j);
			join_none
		end
		$display("main,time=%0t",$time);
		#10;
		$finish;
	end
endmodule

结果:

*Verdi* : Begin traversing the scopes, layer (0).
*Verdi* : Enable +all dumping.
*Verdi* : End of traversing.
ucli% run 1000ns
main,time=0
j=3,time=0
j=3,time=0
j=3,time=0
a=3,time=1
a=3,time=1
a=3,time=1
$finish called from file "lab13_fork.sv", line 22.
$finish at simulation time                   10
           V C S   S i m u l a t i o n   R e p o r t 

可以看到打印的三个时间是一样的了说明是并行,但是a的值是一样的,这是因为SV中function和task默认都是静态的(注意:在module或initial中是默认静态的,所以这里表现出来是静态的。来自路桑的提醒),这些静态的function和task里面的局部变量会使用共享的静态存储区,所以不同的线程之间会窜用这些变量,可以在unction和task加入automatic改做动态的,此外还需要一个动态变量来接收for循环的j,不然结果依然,这是因为并行启动时j已经等于3了,此时各个进程会共用一个j。改变后代码如下:

`timescale 1ns/1ns

module top();
	int a;
	int i;

	task automatic lab(int i);
		a<=i;
		#1;
		$display("a=%0d,time=%0t",i,$time);
	endtask
	initial begin

		for(int j=0;j<3;j++) begin
			fork
				automatic int k=j;
				$display("k=%0d,time=%0t",k,$time);
				lab(k);
			join_none
		end
		$display("main,time=%0t",$time);
		#10;
		$finish;
	end
endmodule

结果:

*Verdi* : Begin traversing the scopes, layer (0).
*Verdi* : Enable +all dumping.
*Verdi* : End of traversing.
ucli% run 1000ns
main,time=0
k=0,time=0
k=1,time=0
k=2,time=0
a=0,time=1
a=1,time=1
a=2,time=1
$finish called from file "lab13_fork.sv", line 23.
$finish at simulation time                   10
           V C S   S i m u l a t i o n   R e p o r t 

但是上面结果有另外一个问题,如果for循环后面display这个语句执行是和整个循环并行的(打印的time是0),因为fork...join_none并没有阻塞父进程。如果for后面代码依赖for循环内的并行进程时,我们就希望在for内所有进程都执行完再执行for后面的父进程。此时,我们就需要加上一个隔离进程。

for+fork...join_none+fork..join

因为fork...join可以阻塞父进程,所以我们可以在for外面在套一个fork...join作为隔离,此外我们需要使用wait fork来等待子进程执行完成。如下结果中我们可以看到display的打印已经在time=1。说明for后面主进程是在并行进程执行完之后执行的。

代码:

`timescale 1ns/1ns

module top();
	int a;
	int i;

	task automatic lab(int i);
		a<=i;
		#1;
		$display("a=%0d,time=%0t",i,$time);
	endtask
	initial begin
		fork 
			begin
				for(int j=0;j<3;j++) begin
					fork
						automatic int k=j;
						$display("k=%0d,time=%0t",k,$time);
						lab(k);
					join_none
				end
				wait fork;
			end
		join
		$display("main,time=%0t",$time);
		#10;
		$finish;
	end
endmodule

结果:

*Verdi* : Enable +all dumping.
*Verdi* : End of traversing.
ucli% run 1000ns
k=0,time=0
k=1,time=0
k=2,time=0
a=0,time=1
a=1,time=1
a=2,time=1
main,time=1
$finish called from file "lab13_fork.sv", line 27.
$finish at simulation time                   11
           V C S   S i m u l a t i o n   R e p o r t 

automatic int k=j;这句和其余两句如果放在begin ... end中,比如:

结果是:

 结果会是k都一样都为最后一个(这个的原因:我自己的理解是automatic也在begin end中那么对于父进程(fork join_none)就是不可见的,因为是局部的,这样相当于又隔了一层,而automatic如果是在begin end外面那么对于父进程(fork join_none)就是可见的,对于父进程来说他就是父进程的automatic 变量,并且对父进程的子进程可见)。如果automatic的这句单独用begin end包起来,这样k属于局部变量在第二个begin begin中会不能使用。

在实际代码中,我们可能需要同时送不同的item给不同的sequencer,而这不同的item之间又需要同步发送,那么就需要使用这样的组合。比如下面代码:

		fork
			begin
			for(int j=0;j<seq_id.size();j++) begin
				fork 
					automatic int k=j;
					begin
						string tr_name=$sformatf("px_tr_seq%0d",k);
						px_tr_img[k]=pixel_transaction::type_id::create(tr_name, ,get_full_name());
						`uvm_info("image_gen_sequence",$sformatf("Send sequence%0d's px_tr_img ...",seq_id[k]),UVM_LOW);
						start_item(px_tr_img[k],-1,p_sequencer.px_sqr[k]);
						px_tr_img[k].copy(px_tr);
						for(int i=0;i<(2+1);i++) begin//parameter from ghl
							px_tr_img[k].data_in0[i]=block_img[k][0][i];
							px_tr_img[k].data_in1[i]=block_img[k][1][i];
						end
						finish_item(px_tr_img[k]);
						`uvm_info("image_gen_sequence",$sformatf("Finish send sequence%0d's px_tr_img ...",seq_id[k]),UVM_LOW);
					end
				join_none
			end

			wait fork;
			end
		join

注意px_tr_img一定不能申明为队列,因为是并行,而队列中如果前一个元素没有创建,后一个元素是不能创建的,所以px_tr_img只能是定数组或动态数组。(提醒自己)

<think>好的,用户想了解SystemVerilog中的fork join结构的使用方法支持情况。我需要先回忆一下相关的知识点,然后结合提供的引用内容来组织答案。 首先,fork joinSystemVerilog中是用于并发执行多个线程的结构。根据引用[1][2],fork-join允许同时生成多个过程块,主线程会在所有子线程完成后继续执行。不过还有其他的变体,比如join_anyjoin_none,这些也需要提到。 接下来,我需要说明基本使用方法。根据示例中的代码,比如引用[4]中的例子,可以展示如何使用fork join_none来启动多个forever循环。同时,要注意自动变量的使用,如引用[5]提到的automatic关键字,避免多个线程共享变量导致的问题。 然后,不同的fork join变体需要分别解释。比如,fork...join会等待所有线程完成,fork...join_any等待任意一个完成,而fork...join_none则不等待直接继续。这部分可以引用[1][2]的内容。 应用场景方面,引用[3]提到在验证中需要并行发送激励,这时候fork join就很有用。比如同时挂起多个任务,可以用循环fork join结合,提高代码的可维护性。同时,引用[4]中的例子展示了如何用for循环fork join_none来简化代码,避免重复。 注意事项部分,自动变量的使用是关键。引用[5]的例子说明了如果不使用automatic,多个线程可能会共享同一个变量,导致错误的结果。所以必须强调在循环内声明自动变量来捕获每次迭代的值。 最后,生成相关问题需要考虑用户可能进一步想了解的内容,比如三种join的区别、自动变量的必要性、并发线程的问题解决以及应用场景举例。这样能帮助用户更深入地理解fork join的使用。 现在整理这些点,确保结构清晰,引用正确,并且使用中文回答。同时,按照要求使用行内公式独立公式的格式,但在这个问题中可能不需要数学表达式,所以可以忽略。检查是否有遗漏的引用内容,确保覆盖所有相关的使用方法、变体注意事项。</think>### SystemVerilogfork join结构的使用方法及支持情况 SystemVerilog **支持**使用`fork join`结构实现多线程并发控制,其核心功能是通过并行执行多个线程提升验证效率[^1][^2]。以下是具体使用方法及注意事项: --- #### 一、基本使用方法 1. **基础语法结构** ```systemverilog fork // 线程1 begin ... end // 线程2 begin ... end join // 或 join_any、join_none ``` - `fork...join`:主线程等待**所有子线程完成**后继续执行[^1]。 - `fork...join_any`:主线程等待**任意一个子线程完成**后继续[^1]。 - `fork...join_none`:主线程**不等待子线程**,直接继续执行[^1][^4]。 2. **并发线程示例** 以下代码使用`fork...join_none`并行启动4个无限循环任务: ```systemverilog initial begin for (int i = 1; i <= 4; i++) begin fork automatic int task_id = i; // 关键:声明automatic变量 forever begin @(posedge clk); task_(task_id); // 调用任务 end join_none end end ``` - **关键点**:使用`automatic`关键字为每个线程创建独立变量副本,避免共享冲突[^4][^5]。 --- #### 二、主要应用场景 1. **验证环境中的并行激励生成** 例如:多个接口同时发送数据包[^3]。 ```systemverilog fork send_packet(port0); send_packet(port1); monitor_response(); join ``` 2. **动态线程管理** 通过循环嵌套`fork join`批量生成线程: ```systemverilog initial begin for (int i = 0; i < 8; i++) begin fork automatic int idx = i; begin @(posedge clk); drive_transaction(idx); end join_none end wait fork; // 等待所有子线程完成 end ``` --- #### 三、注意事项 1. **变量作用域问题** 在循环内使用`fork`时,若未声明`automatic`变量,所有线程可能共享同一变量值[^5]: ```systemverilog // 错误示例:所有线程共享i的最终值 for (int i = 0; i < 4; i++) begin fork $display("i=%0d", i); // 输出全为4 join_none end ``` 2. **线程同步机制** 使用`wait fork`或事件(`event`)控制线程执行顺序,避免竞争条件。 ---
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值