SV(5)-线程的使用,控制和通信

一.线程的使用

创建线程有三种方式
(1)fork…join
(2)fork…join_any
(3)fork…join_none
在这里插入图片描述
从图中可以看出

  • fork…join表示一个并行执行语句,不同与begin…end的顺序执行,在数电中理解为与的概念
  • fork…join_any表示有一个执行完成就可以,在数电中可以理解为或的概念
  • fork…join_none表示一个都不执行,在数电中可以理解为非的概念

二.线程的控制

1.wait

  • wait语句可以等待所有语句执行完毕才退出程序,wait语句使用时加上等待线程名称
  • fork…join_any本身是只执行一句的,如果加入了wait语句,会将三个语句全部执行完毕才会退出整个initial语句,如下代码:
task run_threads;
	...
	fork
		check_trans(tr1);
		check_trans(tr2);
		check_trans(tr3);
		join_any
		...
		wait fork //等待三个check执行完毕再退出task
endtask

2.disable

  • disable可以停止单个线程也可以停止多个线程,都需要加上停止进行的名字
    (1)结束单个进程
  • 停止单个线程,dsiable timeout_block这个语句可以停止frok…join_any中的time_out进程
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
			//停止TIME_OUT语句
			disable timeout_block;
		end
	join_none
endtask

(2)停止多个进程

  • 在停止多个线程时候,需要加上名字以表区别比如:disable fork_a,如果只写fork,disable会将所有的fork都停止,所以写为disable fork_a以示区分,如下:
initial begin
   check_trans(tr0);
   	fork
   		begin
   			check_trans(tr1);
   			fork
   				check_trans(tr2);
   			join
   			//这里有两个fork,如果不指明,会将两个fork都需要停止,所以需要区分
   			#(TIME_OUT/2) disable fork; 
   		end
   	join
end

(3)停止被多次调用的任务

  • disable比想象的更强大,如果你给某一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过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
				$diaplay("@%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);//被停止
	wait_for_time_out(1);//被停止
	wait_for_time_out(2);//被停止
	#(TIME_OUT*2)$display("@%0t:All done",$time);
end
  • 任务wait_for_time_out被调用了三次,从而衍生了三个线程
  • 线程0在2#延时之后禁止了该任务,而由于三个线程均是“同名”线程,因此这些线程都被禁止了,最终也都没有完成。

三.线程的通信

1.事件

  • 作用:事件可以实现线程的同步
  • 用法:@用来等待事件,边沿敏感,所有总是阻塞的,等待事件的变化,->用来触发事件,用来解除线程的阻塞,wait用来等待事件,电平敏感。如:
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
  • 这里的打印输出缺少了2的after trigger,其实本身有两种情况,也有可能打印的少的是1的after trigger。事件开始前,首先会打印两个before,然后触发事件1和事件2,但是由于有delta cycle时间差,所有不能同一时刻进行初始化,有先后顺序,会导致有一个没有打印出来

使用triggered()方法,在事件触发后,会保留触发状态,使用wait就能保证只要是触发过,就能接收到触发信号。

event e1, e2;                                  //注意event 类不需要new
initial begin                                  //线程1
  $display("@%0t: (1)before trigger", $time);
  -> e1;                                       //触发事件e1
  wait(e2.triggered());                        //等待事件e2的触发
  $display("@%0t: (1)after trigger", $time);
end

initial begin                                   //线程2
  $display("@%0t: (2)before trigger", $time);
  -> e2;                                        //触发事件e2
  wait(e1.triggered());                         //等待事件e1触发
  $display("@%0t: (2)after trigger", $time);
end

2.旗语

  • 作用:可以实现不同的主体对同一资源的访问控制,但是同一个时间,只能是一个主体来控制。
  • 方法:旗语有3种基本操作,new()方法可以创建一个带单个或者多个钥匙的 semaphore ,使用 get() 方法可以获取一个或者多个钥匙,而put() 方法返回一个或者多个钥匙。使用get()如果拿不到钥匙,会被阻塞,而使用try_get()方法拿不到钥匙,会返回一个0,不会被阻塞。
program automatic test(bus.ifc.TB bus);
	semaphore sem;                       //创建一个旗语
	initila begin
		sem = new(1);                    //分配1个钥匙
		fork
			sequencer();                 //产生两个总线事务线程
			sequencer();
		join
end

task sequencer;
	repeat($urandom%10)                  //随机等待0-9个周期
		@bus.cb;  
	sendTrans();                         //执行总线任务
endtask

task sendTrans;
	sem.get(1);                          //获取总线钥匙
	@bus.cb;                             //把信号驱动到总线上
	bus.cb.addr <= t.addr;
	...
	sem.put(1);                          //处理完成把钥匙放回
	endtask
endprogram
  • 使用旗语时候,(1)返回的钥匙可能比你取出来的要多,(2)当你的程序需要获取和返回多个钥匙时,务必谨慎

3.信箱

  • 作用:信箱可以实现线程之间的信息传递
  • 方法:mailbox 类有以下方法,new() 创建一个mailbox,同时设置信箱大小,put() 方法,可以把数据放入到mailbox中,使用get() 方法可以从信箱中拿到数据,且移除数据。如果信箱满了,使用put() 方法会被阻塞住,如果信箱是空的,使用get() 方法会被阻塞住。peek() 方法可以获取信箱中的数据·,且不会移除数据。另外非阻塞方法有:try_put() try_get() try_peek()
program automatic bounded;
  mailbox mbx;
  initial begin
    mbx=new(1);                                   //信箱容量为1
    fork
      for(int i=1; i<4; i++) begin                //启动三个线程,这三个线程是顺序的,i依次是1 2 3
        $display("before 语句:%0d", i);
        mbx.put(i);                               //给信箱放数
        $display("after 语句:%0d", i);
      end
      repeat(4) begin
        int j;
        #ins mbx.get(j);                         //同时延迟1ns,从信箱中拿数
        $display("get data: %0d", j);
      end
    join
  end
endprogram

4.总结

  • event:最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个event组合起来用做线程的同步
  • semaphore:公共资源的安全卫士。如果多线程间要对某一个公共资源做访问,即可以使用这个元素
  • mailbox:精小的SV原生FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑使用此元素
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马志高

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值