[转]《SystemVerilog验证测试平台编写指南》学习笔记——线程以及线程间的通信(一)

在实际硬件中,时序逻辑通过时钟沿来激活,组合逻辑的输出则是随着输入的变化而变化。所有这些并发的活动在Verilog的寄存器传输级上是通过initial和always块语句、实例化和连续赋值语句来模拟的。为了模拟和检验这些语句块,测试平台使用许多并发执行的线程。在测试平台的环境里,大多数语句块被模拟成事务处理器,并运行在各自的线程里。

测试平台环境的组成模块:
在这里插入图片描述
发生器把激励传递给代理,环境类需要知道发生器什么时候完成任务,以便及时终止测试平台还在运行的线程。这个过程需要借助线程间的通信(IPC)来完成。

一、线程的使用

标准的Verilog中对语句分为两种方式——使用begin…end和fork…join。
begin…end顺序方式执行,fork…join以并发方式执行,后者的不足在于必须等fork块内的所有语句都执行完后才能继续块内后续的处理。
SV中引入了两种新的创建线程的方法——使用fork…join_none和fork…join_any
在这里插入图片描述
测试平台通过已有的结构如事件、@事件控制、wait和disable语句,以及新的语言元素如旗语和信箱,来实现线程间的通信、同步以及对程序的控制。

1、使用fork…join和begin…end

fork…join和begin…end的相互作用

initial begin
	$display("@ % 0t: start fork...join example", $time);
	# 10 $display("@ % 0t: sequence 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: sequence after # 30", $time);
			# 10 $display("@ % 0t: sequence after # 10", $time);
		end
	join
	$display("@ % 0t: after join", $time);
	# 80 $display("@ % 0t: finish after # 80", $time);
end

在这里插入图片描述
仿真结果:
在这里插入图片描述

2、使用fork…join_none来产生线程

fork…join_none块在调度其块内语句时,父线程继续执行

initial begin
	$display("@ % 0t: start fork...join_none example", $time);
	# 10 $display("@ % 0t: sequence 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: sequence after # 30", $time);
			# 10 $display("@ % 0t: sequence after # 10", $time);
		end
	join_none
	$display("@ % 0t: after join", $time);
	# 80 $display("@ % 0t: finish after # 80", $time);
end

仿真结果:
在这里插入图片描述

3、使用fork…join_any实现线程同步

fork…join_any块对块内语句进行调度,当第一个语句完成后,父进程才继续执行,其他停顿的线程也得以继续。

 initial 
    begin
      $display("@%0t: start fork...join_any example", $time);
      # 10 $display("@%0t: sequence 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: sequence after # 30", $time);
              # 10 $display("@%0t: sequence after # 10", $time);
          end
      join_any
      $display("@%0t: after join", $time);
      # 80 $display("@%0t: finish after # 80", $time);
  	end

仿真结果:
在这里插入图片描述

4、动态线程

动态线程的创建

program automatic test(bus_ifc.TB bus);

	task check_trans(Transaction tr);		//当被调用时,它便产生一个线程用来检测总线以获取匹配的事务地址
		fork
			begin
				wait(bus.cb.addr == tr.addr);
				$display("@%0t": Addr match %d, $time, tr.addr);
			end
		join_none
	endtask

	Transaction tr;
	initial begin
		repeat(10) begin
			tr = new();		//创建一个随机事务
			assert(tr.randomize());
			transmit(tr);		//把事务发送到被测设计中
			check_trans(tr);		//等待被测设计的回复
		end
		# 100;		//等待事务的最终完成
endprogram

5、线程中的自动变量

fork…join_none里的自动变量

initial begin
	for(int j = 0; j < 3; j++)
		fork
			automatic int k = j;		//创建索引的拷贝
			$write(k);
		join_none
	
	# 0 $display;
end
/*
在每轮循环中,k的一个拷贝被创建并且被设置为j的当前值,
然后fork...join_none($write)被调度,包括k的拷贝,
在循环完成后,#0阻塞了当前进程,因此三个进程一起运行,打印出各自拷贝值k,
当线程运行完成后,在当前时间片已经没有其他事件残留,这时SV就会前进到下一个语句执行$display
*/

6、等待所有衍生线程

在SV中,当程序中的initial块全部执行完毕,仿真器也就退出了。有些线程运行时间比较长时,可以使用wait fork语句来等待所有子线程结束。

task run_threads;
	...		//创建一些事务
	fork
		check_trans(tr1);		//产生第一个线程
		check_trans(tr2);		//产生第二个线程
		check_trans(tr3);		//产生第三个线程
	join_none
	...
	//在这里等待上述线程结束
	wait fork;
endtask

二、停止线程

1、停止单个线程

/*
fork...join_any里包含了两个线程:一个是简单的wait,另一个是带时延的显示,这两个线程并发执行。
如果正确的总线地址来得足够早,则wait结构先完成,fork...join_any得以执行,之后的disable结束剩余的线程。
但是如果在TIME_OUT时延完成前总线地址还没有得到正确值,那么错误警告的信息就会被打印出来,join_any被执行,而后的disable将结束wait线程。
*/
parameter TIME_OUT = 1000;
task check_trans(Transaction tr);
	fork
		begin
			//等待回应,或者达到某个最大时延
			fork : timeout_block		//用disable停止的署名块
				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 

2、停止多个线程

Verilog中disable来停止一个署名块中的所有线程,而SV中disable fork语句可以停止从当前线程中衍生出来的所有子线程。

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

3、禁止被多次调用的任务

如果在某个任务内部禁止该任务,这就像时任务的返回语句,但是这也会停止所有由该任务启动的线程。如果该任务已经被多个线程调用,禁止其中的一个将导致它们全部被禁止

使用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;
			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
	wait_for_time_out(1);		//衍生线程1
	wait_for_time_out(2);		//衍生线程2
	# (TIME_OUT * 2) $display("@%0t: All done", $time);
end
/*
任务被调用三次,衍生了三个线程,线程0在#2延时之后禁止了该任务,导致三个线程最终都没完成。
*/

---------------------
作者:煎丶包
来源:CSDN
原文:https://blog.csdn.net/qq_39794062/article/details/113090988
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值