SV基础知识-----线程间的控制与通信

前言:

  • 线程间的同步,用event。
  • 多个线程访问一个共享资源,用semaphore(旗语)的钥匙。
  • 线程间传递信息,用mailbox。(信箱:放置缓存)

一、程序和模块

  • module除了作为RTL模型的外壳包装和实现硬件行为,在更高层的集成层面,模块之间也需要通信和同步
  • 对于硬件的过程块,它们之间的通信可理解为不同逻辑/时序块之间的通信或者同步,是通过信号的变化来完成的。
  • 从硬件实现的角度来看,Verilog通过always,initial过程语句块和信号数据连接实现进程间通信。
  • 可以将不同的module作为独立的程序块,他们之间的同步通过信号的变化event触发、等待特定事件(时钟周期)或者时间(固定延时)来完成。
  • 模块(线程)在仿真一开始便并行执行,除了每个线程会依照自身内部产生的事件来触发过程语句块之外,也同时依靠相邻模块间的信号变化来完成模块之间的线程同步

二、什么是线程

  • 线程即独立运行的程序。
  • 线程需要被触发,可以结束或者不结束
  • 在module中的initialalways,都可以看做独立的线程,它们会在仿真0时刻开始,而选择结束或者不结束。
  • 硬件模型中由于都是always语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源,因为它们并不会结束
  • 软件测试平台中的验证环境都需要由initial语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此资源占用是动态的。
  • 软件环境中的initial块对语句有两种分组方式,使用begin...endfork...joinbegin...end中的语句以顺序方式执行,而fork...join中的语句则以并发方式执行。并行方式还包括fork...join_anyfork...join_none
  • 线程的执行轨迹是呈树状结构的,任何的线程都应该有父线程。
  • 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程。当子线程终止时,父线程可以继续执行,当父线程终止时,其子线程都应当终止。

三、线程的控制

fork并行线程语句块(下图分别开辟了三个子线程)

  • fork...join需要所有线程都执行完,才能继续执行。
  • fork...join_any只需要任何其中一个线程执行完,就可以继续执行
  • fork...join_none无需等待正在执行的子线程,直接继续执行接下来的程序。

等待所有衍生线程

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

停止单个或者多个线程

  • 在使用了fork...join_any或者fork...join_none以后,可以使用disable来指定需要停止的线程。
parameter TIME_OUT = 1000;
task check_trans(Transaction tr);
	fork
		begin
			//等待回应,或者达到某个最大时延
			fork : timeout_block			//这个fork块里面有两个并行线程,但只希望有一个执行完就可以退出
				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去禁止这个线程标号,所有衍生的同名线程都将被禁止
task wait_for_time_out(int id);
	if(id == 0)
		fork
			begin
				#2;
				$display("%0t: disable wait_for_time_out", $time);
				disable wait_for_time_out;       //diable task名
			end
		join_none
	fork : just_a_little
		begin
			$display("@%0t: %m: %0d entering thread", $time, id);
			#TIME_OUT;
			$display("@%0t: %m: %0d done", $time, id);
		end
	join_none
endtask

initial begin
	//任务被调用三次,从而衍生了三个线程
	wait_for_time_out(0);				//线程0在#2延时之后禁止了该任务,而由于三个线程均是同名线程,因此这些线程都被禁止了,最终也都没有完成
	wait_for_time_out(1);
	wait_for_time_out(2);
	#(TIME_OUT * 2) $display("@%0t: All done", $time);
end

四、线程间的通信

  • 测试平台中的所有线程都需要同步交换数据
  • 一个线程需要等待另一个线程。
  • 多个线程可能同时访问同一个资源。
  • 线程之间可能需要交换数据。(如:generator和simulator直接的数据交换通过maibox)
  • 所有数据交换和同步称之为线程间的通信(IPC)
  • 线程间共享资源的使用方式,应该遵循互斥访问原则。

event事件

  • Verilog中一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,所以总是阻塞着、等待事件的变化。
  • SV中,其它线程可以通过 ->操作符来触发事件,结束对第一个线程的阻塞。
  • 可以使用电平敏感wait(e1.triggered())来替代边沿敏感的阻塞语句@e1。如果事件在当前时刻已经被触发则不会引起阻塞,否则会一直等到事件被触发为止。而使用电平触发,只要event被触发过,就可以防止引起阻塞
  • 适用于最小信息量的触发,即单一的通知功能。(如但bit  0 -> 1)

 semaphore旗语

  • semaphore可以实现对同一资源访问控制
  • semephore有三种基本操作。new()方法可以创建一个带单个或者多个钥匙的semaphore,使用get()方法可以获取一个或者多个钥匙,而put()方法可以返回一个或者多个钥匙。
  • 如果想要获取一个semaphore而不希望被阻塞,可以使用try_get()函数,返回1表示有足够多的钥匙,返回0则表示钥匙不够。
  • 适用于共享资源访问控制。

mailbox信箱

  • 线程之间如果传递信息,可以使用mailbox信箱。mailbox和队列queue相似。
  • mailbox是一种对象,因此也需要使用new()来例化,参数size为0或者没有指定时,信箱则表示无限大。
  • 使用put()可以把数据放入mailbox,使用get()可以从信箱移除数据。peek()可以获取对信箱里数据的拷贝而不移除它。
  • 线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法。
  • mailbox必须通过new()例化,而队列只需要声明。
  • mailbox可以将不同的数据类型同时存储,队列存储的元素类型必须一致。
  • mailbox的存取方法put()get()阻塞方法,队列的push_back()pop_front()方法是非阻塞的。调用阻塞方法只能在task中调用,因此阻塞方法是耗时的。
  • 显式地限定mailbox中元素的类型,可以通过mailbox #(type = T)的方式来声明。
  • 精小的SV原生FIFO,适用于在线程之间做数据通信或者内部数据缓存

猜你喜欢

转载自blog.csdn.net/Arvin_ing/article/details/127962463
今日推荐