System Verilog — 线程

本文详细介绍了线程的概念,包括线程的创建、控制和通信。重点讨论了`fork...join`的不同变体,如`join_any`和`join_none`,以及如何等待所有衍生线程和停止线程。同时,深入阐述了线程间通信的机制,如事件(event)、旗语(semaphore)和信箱(mailbox),强调了它们在同步和资源管理中的应用。
摘要由CSDN通过智能技术生成

一、线程的概念

  • 线程的执行轨迹是呈树状结构的,即任何的线程都应该有父线程。
  • 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程。
  • 当子线程终止时,父线程可以继续执行。
  • 当父线程终止时,其所开辟的所有子线程都会终止。

软件中的initial块对语句有两种分组方式:
(1)begin…end中的语句以顺序方式执行。
(2)fork…join中的语句以并发方式执行。

initial begin
	$display("@%0t: start fork...join example", $time);
	#10 $display("@%0t: sequential after #10", $time);
	fork
		$display("@%0t: parallel start", $time);
		#50 $display("@%0t: parallel after #50", $time);
		#10 $display("@%0t: parallel after #10", $time);
		begin
			#30 $display("@%0t: sequential after #30", $time);
			#10 $display("@%0t: sequential after #10", $time);
		end
	join
	$display("@%0t: after join", $time);
	#80 $display("@%0t: finish after #80", $time);
end

//输出结果
@0:  start fork...join example
@10: sequential after#10
@10: parallel start
@10: parallel after #10
@40: sequential after#30
@50: sequential after#10
@60: parallel after #50
@60: after join
@140:finish after #80

二、线程的控制

1.fork…join_any

在调度块内语句时,当并行块内的第一个语句完成后,父线程才继续执行,其他停顿的线程也得以继续。

initial begin
	$display("@%0t: start fork...join_any example", $time);
	#10 $display("@%0t: sequential after #10", $time);
	fork
		$display("@%0t: parallel start", $time);
		#50 $display("@%0t: parallel after #50", $time);
		#10 $display("@%0t: parallel after #10", $time);
		begin
			#30 $display("@%0t: sequential after #30", $time);
			#10 $display("@%0t: sequential after #10", $time);
		end
	join_any
	$display("@%0t: after join_any", $time);
	#80 $display("@%0t: finish after #80", $time);
end

//输出结果
@0:  start fork...join_any example
@10: sequential after#10
@10: parallel start
@10: after join_any
@20: parallel after#10
@40: sequential after#30
@50: sequential after#10
@60: parallel after #50
@90: finish after #80

2.fork…join_none

在调度块内语句时,父线程继续执行。

initial begin
	$display("@%0t: start fork...join_none example", $time);
	#10 $display("@%0t: sequential after #10", $time);
	fork
		$display("@%0t: parallel start", $time);
		#50 $display("@%0t: parallel after #50", $time);
		#10 $display("@%0t: parallel after #10", $time);
		begin
			#30 $display("@%0t: sequential after #30", $time);
			#10 $display("@%0t: sequential after #10", $time);
		end
	join_none
	$display("@%0t: after join_none", $time);
	#80 $display("@%0t: finish after #80", $time);
end

//输出结果
@0:  start fork...join_none example
@10: sequential after#10
@10: after join_none
@10: parallel start
@20: parallel after#10
@40: sequential after#30
@50: sequential after#10
@60: parallel after #50
@90: finish after #80

3.等待所有衍生线程

在SV中,当程序中的initial块全部执行完毕,仿真器就退出了。
如果我们希望等待fork块中的所有线程执行完毕再退出结束initial块,我们可以使用wait fork语句来等待所有子线程结束。

task run_theads;
	...
	fork
		check_trans(tr1);//线程1
		check_trans(tr2);//线程2
		check_trans(tr3);//线程3
	join_none
	...
	//等待所有fork中的线程结束再退出task
	wait fork;
endtask
	

4.停止线程

停止单个线程

使用disable来指定需要停止的线程。

parameter TIME_OUT = 1000;
task check_trans(Transaction tr);
	fork
		begin
			fork:timeout_block
				begin
					wait(bus.cb.addr == tr.addr);
					$display("@%0t: Addr match %d", $time, tr.addr);
				end
			#TIME_OUT $display("@%0t: Error: timeout", $time);
			join_any
			disable timeout_block;
		end
	join_none
endtask
	

停止多个线程

disable fork可以停止从当前线程中衍生出来的所有子线程。

initial begin
	check_trans(tr0);//线程0
	//创建一个线程来限制disable fork的作用范围
	fork//线程1
		begin
			check_trans(tr1);//线程2
			fork//线程3
				check_trans(tr2);//线程4
			join
			//停止线程1-4,单独保留线程0
			# (TIME_OUT/2) disable fork;
		end
	join
end

在这里插入图片描述

停止被多次调用的任务

如果给某一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过disable去禁止这个线程标号,所有衍生的同名线程都将被禁止。

三、线程间的通信

  • event:单一的通知功能,可以用来做事件的触发,也可以多个event组合起来用来做线程之间的同步。
  • semaphore:多线程间要对某一公共资源做访问时可以使用semaphore。
  • mailbox:在线程之间做数据通信或者内部数据缓存时,可以考虑使用mailbox。

1.事件event

运行下方代码块时,第一个初始化块启动,触发e1事件,然后阻塞在另一个事件上;第二个初始化块启动,触发e2事件(唤醒第一个块),然后阻塞在第一个事件上。但是,因为第一个事件是一个零宽度的脉冲,所以第二个线程会因为错过第一个事件而被锁住。

event e1, e2;
initial begin
	$display("@%0t: 1: before trigger", $time);
	-> e1;
	@ e2;
	$display("@%0t: 1: after trigger", $time);
end
initial begin
	$display("@%0t: 2: before trigger", $time);
	-> e2;
	@ e1;
	$display("@%0t: 2: after trigger", $time);
end

@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger

使用电平敏感的wait(e1.triggered())来替代边沿敏感的阻塞语句@e1。 如果事件在当前时刻已经被触发,则不会引起阻塞,否则,会一直等到事件被触发为止。

event e1, e2;
initial begin
	$display("@%0t: 1: before trigger", $time);
	-> e1;
	wait(e2.triggered());
	$display("@%0t: 1: after trigger", $time);
end
initial begin
	$display("@%0t: 2: before trigger", $time);
	-> e2;
	wait(e1.triggered());
	$display("@%0t: 2: after trigger", $time);
end

@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
@0: 2: after trigger

在运行上述代码时,第一个块启动,触发e1事件,然后阻塞在另外一个事件上;第二个初始化块启动,触发e2事件(唤醒第一个块),然后阻塞在第一个事件上,从而得到上述的输出。

2.旗语semaphore

  • semaphore可以实现对同一资源的访问控制。
  • semaphore的三种基本操作
  • new():创建一个带单个或多个钥匙的semaphore。
  • get():获取一个或多个钥匙。
  • put():返回一个或多个钥匙。
    3.如果试图获取一个semaphore而希望不被阻塞,可以使用try_get()函数。其返回1表示有足够多的钥匙,而返回0则表示钥匙不够。
    资源共享的需求:
    对于线程间共享资源的使用方式,应该遵循互斥访问原则。原因在于如果不对其访问做控制,可能会出现线程不安全。

3.信箱mailbox

  • 线程之间如果传递信息,可以使用mailbox。
  • mailbox和队列queue有相似之处。
  • mailbox是一种对象,因此也需要new()例化。例化时有一个可选的参数size来限定其存储的最大数量。如果size是0或者没有指定,则信箱是无限大的,可以容纳任意多的条目。
  • 使用put()可以把数据放入mailbox,使用get()可以从信箱移除数据。
  • 如果信箱为满,则put()会阻塞,如果信箱为空,则get()会阻塞。
  • peek()可以获取对象信箱里数据的拷贝而不移除它。
  • 线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法,即哪些是立即返回的,而哪些是需要等待时间的。

对于mailbox的用法,和FIFO的使用很相似。而两者在使用时的差异在于:

  • mailbox必须通过new()例化,而队列只需要声明。

  • mailbox在传递形式参数的时候,实际传递并拷贝的是mailbox的指针;而对于队列,需要考虑做的是引用还是拷贝,进而考虑端口声明的方向。

  • mailbox只能够用作FIFO,而队列除了FIFO,还可以用作LIFO(Last In First Out)。

  • mailbox的存取方法put()和get()是阻塞方法,即使用它们时,方法不一定会立即返回。而队列对应的存取方式push_back()和pop_front()是非阻塞的,会立即返回。
    因此在使用队列取数时,需额外填写wait(queue.size>0)才可以在其后对非空的queue做取数的操作。
    此外,还有关于mailbox的特性为:

  • mailbox在例化时,通过new(N)的方式可以使其变为定长容器。这样在负载到长度N之后,无法在对其写入,如果用new()的方式,则表示信箱容量不限大小。

  • 如果要显式地限定mailbox中元素的类型,可以通过mailbox #(type = T)的方式。例如存储的是int,可以声明为mailbox #(int)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值