System verilog中事件和信箱

事件

Verilog事件可以实现线程的同步。在Verilog中,一个线程总是要等待一个带操作符的事件。这个操作符是边沿敏感的,所以它总是阻塞着,等待事件的变化。其他的线程可以通过->操作符来触发事件,解除对第一个线程的阻塞。
在System Verilog,事件成为了同步对象的句柄,可以传递给子程序。这个特点允许在对象间共享事件,而不用把事件定义成全局的。最常见的方式是把事件传递到一个对象的构造器中。
在Verilog中,当一个线程在一个事件上发生阻塞的同时,正好另一个线程触发了这个事件,则竞争的可能性便出现了。如果触发线程先于阻塞线程执行,则触发无效。System Verilog引入triggered()函数,可用于查询某个事件是否已被触发,包括在当前时刻。线程可以等待这个函数的结果,而不用在@操作符上阻塞。

在事件的边沿阻塞

event el,e2; 
initial begin
	$display("@% 0t:1:before trigger",$time);
	->el;
	@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

在一个事件上阻塞以后输出结果如下所示:

00:1:before trigger
00:2:before trigger
00:1:after trigger

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

等待事件的触发

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

event el,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", Stime);
	->e2; 
	wait(el. triggered());
	$diaplay("@ %0t: 2: after. trigger". $time); 
end

第一个初始化块启动,触发e1事件,然后阻塞在另外一个事件上。第二个初始化块启动,触发e2事件(唤醒第一个块),然后阻塞在第一个事件上。从而得到如下输出:

00:1:before trigger
00:2:before trigger
e0:1:after trigger
00:2:after trigger

上述几个例子都存在竞争的条件,它们在不同仿真器上的执行结果可能并不完全一致。比如,上例的输出是假定当第二个块触发e2后,程序的执行便跳回到了第二个块上。而下面的假定同样也是合理的:第二个块触发e2后,开始等待e1,接着打印出一个信息以后,控制权才回到第一个块上。

信箱

如何在两个线程之间传递信息呢?考虑发生器需要创建很多事务并传递给驱动器的情况.。可能会仅仅使用发生器线程去调用驱动器中的任务便可以了。但如果这样做,发生器需要知道到达驱动器任务的层次化路径,这会降低代码的可重用性。此外,这种代码风格还会迫使发生器与驱动器以同一速率运行,在一个发生器需要控制多个驱动器的情况下会引发同步问题。

把发生器和驱动器想象成具备自治能力的事务处理器对象,它们通过信道交换数据。每个对象从它的上游对象中得到事务(如果对象本身是发生器,则创建事务),进行一些处理,然后把它们传递给下游对象。这里的信道必须允许驱动器和接收器异步操作。

解决的办法是使用System Verilog中的信箱。从硬件角度出发,对信箱的最简单的理想是把它看成一个具有源端和收端的FIFO。源端把数据放进信箱,收端则从信箱中获取数据。
System verilog中事件和信箱
**信箱可以有容量上的限制,也可以没有。**当源端线程试图向一个容量固定并且已经饱和的信箱里放入数值时,会发生阻塞直到信箱里的数据被移走。同样地,如果收端线程试图从一个空信箱里移走数据,它也会被阻塞直到有数据放入信箱里。
信箱是一种对象, 必须调用new函数来进行实例化。例化时有一个可选的参数size,用以限制信箱中的条目。如果size是0或者没有指定,则信箱是无限大的,可以容纳任意多的条目。

使用put任务可以把数据放入信箱里,而使用get任务则可以移除数据。如果信箱为满,则put会阻塞;而如果信箱为空,则get会阻塞。peek任务可以获取对信箱里数据的拷贝而不移除它。

这里说的数据可以是单个的值,例如一个整数,或者是任意宽度的logic。可以在信箱中放入句柄,但不能是对象。 缺省情况下,信箱没有类型,所以允许在其中放入任何混合类型的数据。务必在一个信箱里只放一种类型的数据。

一个典型的例子:
在循环外面构造一个对象,然后使用循环对对象进行随机化并把它们放到信箱里。因为实际上只有一个对象,它被一次又一次地随机化。下图显示了所有指向同一个对象的句柄。信箱里保存的只是句柄而非对象,所以最终得到的是一个含有多个句柄的信箱,所有句柄都指向同一个对象。从信箱里获取句柄的代码实际上只能见到最后一组随机值。
System verilog中事件和信箱

//只创建一个对象的不良发生器
task generator bad(int n,mailbox mbx);
	Transactiont;
	t=new();//只创建一个事务
	repeat(n)begin 
		assert(t.randomize());//对变量进行随机化
		$display("GEN:Sending addr=%h",t.addr);
		mbx.put(t);//把事务发送给驱动器
	end 
endtask

解决的办法如下例所示,就是确保每个循环都含有构造对象、把对象随机化并放入信箱这样三个完整的步骤。

//创建多个对象的良性发生器
task generator_good(int n,mailbox mbx);
	Transaction t;
	repeat(n)begin 
		t=new();//创建一个新的事务
		assert(t.randomize());//对变量进行随机化
		$display("GEN:Sending addr=s h",t.addr);
		mbx.put(t);//把事务发送给驱动器
	end 
endtask

下图为,一个信箱带有多个指向不同对象的句柄:
System verilog中事件和信箱

下例所示范的驱动器,正在等待来自发生器的事务。

//接收来自信箱的事务的良性驱动器
task driver(mailbox mbx);
	Transaction t;
	forever begin 
		mbx.get(t);//获取来自信箱的事务
		$display("DRV:Received addr=8 h",t.addr);
		//驱动事务到待测设计中
	end 
endtask

**如果不希望代码在访问信箱时出现阻塞,可以使用try_get()和try_peek函数,如果函数执行成功,它们会返回一个非零值,否则返回0。**这比使用num函数可靠一些.因为在对信箱实施测量直到下一次访问信箱的这段时间里,信箱中条目的数量可能会发生变化。

上一篇:Verilog标识符与关键字


下一篇:impdp参数TABLE_EXISTS_ACTION