本文章是优快云博客上收集信息集合
1. event
event事件是静态的同步对象句柄(可以像参数一样在子程序中传递),它用来同步多个并发的进程,比如某个进程等待着事件,而另一个进程则触发这个事件。
几个特征:
- 可以被赋值成null
- 可以传给队列,函数和任务
可以被赋值给其它事件,这样两个事件变量(句柄)会指向同一个同步化对象,触发任意一个变量就触发这个事件, 如下所示
event eve;
event tmp;
initial begin
tmp = eve;
#10 -> tmp;
end
initial begin
wait(eve.triggered());
$display("@%0t : event eve receive trigger",$time);
end
------------------------------------
@10 : event eve receive trigger //执行结果
-------------------------------------
事件的触发和阻塞等待
-
触发: -> (or ->>) event_handle
-
等待:@ or wait(event_handle.triggered)
两种等待条件的区别以及竞争条件的发生
首先分析竞争的产生。如下代码所示:
module tb;
// Create an event variable that processes can use to trigger and wait
event event_a;
// Thread1: Triggers the event using "->" operator at 20ns
initial begin
#20 ->event_a;
$display ("[%0t] Thread1: triggered event_a", $time);
end
// Thread2: Starts waiting for the event using "@" operator at 20ns
initial begin
$display ("[%0t] Thread2: waiting for trigger ", $time);
#20 @(event_a);
$display ("[%0t] Thread2: received event_a trigger ", $time);
end
// Thread3: Starts waiting for the event using ".triggered" at 20ns
initial begin
$display ("[%0t] Thread3: waiting for trigger ", $time);
#20 wait(event_a.triggered);
$display ("[%0t] Thread3: received event_a trigger", $time);
end
endmodule
上面module的运行结果是:
[0] Thread2: waiting for trigger
[0] Thread3: waiting for trigger
[20] Thread1: triggered event_a
[20] Thread3: received event_a trigger
发现使用@的阻塞等待没有收到trigger。分析如下:事件的触发状态只会在当前时间步长保持,一但仿真开始向前运行,就会失去这个状态。也就是说,在仿真时间20的这个时间步长中(可以把它想象成1ns或1ps),事件是处于触发状态的,到21仿真时间后触发状态消失。在上面的代码中,两个阻塞等待@和.triggered都是和事件触发同时发生的,出现了竞争条件,结果是虽然触发和等待发生在同一个仿真时间,但是还有执行顺序,根据代码顺序,先执行事件的触发,由于@事件控制是边沿敏感的,等到执行@语句时,事件的触发的“上升沿”已经消失,所以@并没有被触发,而@和.triggered的不同就在于此,@是边沿敏感,后者是电平敏感,所以在整个20仿真时间步长中,wait语句都能被触发。
触发和等待在同一时间开始的话就不要用@改用.triggered,当然如果触发进程在等待进程(的仿真时间)之前发生,那这两种等待都不会收到trigger。
其实将Thread2initial块移动到Thread1 initial块之前,也能让@阻塞等待被触发,这就是因为@的执行顺序在触发事件之前。
wait_order的使用
wait_order阻塞等待多个事件的触发,并且要求这多个事件按照用户决定顺序触发。wait_order可以和else一同使用,当多个事件按顺序触发时,执行wait_order后的语句,否则执行else后的语句。
module tb;
// Declare three events that can be triggered separately
event a, b, c;
// This block triggers each event one by one
initial begin
#10 -> a;
#10 -> b;
#10 -> c;
end
// This block waits until each event is triggered in the given order
initial begin
wait_order (a,b,c)
$display ("Events were executed in the correct order");
else
$display ("Events were NOT executed in the correct order !");
end
endmodule
--------------------------------------------------------
Events were executed in the correct order //执行结果
--------------------------------------------------------------
事件的合并
之前提到一个事件可以赋值给其它的事件变量,这样两个事件变量实际指向同一个事件对象,触发任意一个变量就是触发了这个对象
module tb;
// Create event variables
event event_a, event_b;
initial begin
fork
// Thread1: waits for event_a to be triggered
begin
wait(event_a.triggered);
$display ("[%0t] Thread1: Wait for event_a is over", $time);
end
// Thread2: waits for event_b to be triggered
begin
wait(event_b.triggered);
$display ("[%0t] Thread2: Wait for event_b is over", $time);
end
// Thread3: triggers event_a at 20ns
#20 ->event_a;
// Thread4: triggers event_b at 30ns
#30 ->event_b;
// Thread5: Assigns event_b to event_a at 10ns
begin
// Comment code below and try again to see Thread2 finish later
#10 event_b = event_a;
end
join
end
endmodule
--------------------------------------------------------------------
[20] Thread1: Wait for event_a is over
[20] Thread2: Wait for event_b is over
---------------------------------------------------------------------
上面结果可见,触发event_a后也触发了event_b,因为这两个句柄指向同一个事件对象。
2. fork
fork jion
fork...join内部的线程都会并行执行,直至处理完内部所有线程后才会结束块语句。fork join 例子:
module fork_join;
initial begin
$display("-----------------------------------------------------------------");
fork
//-------------------
//Process-1
//-------------------
begin
$display($time,"\tProcess-1 Started");
#5;
$display($time,"\tProcess-1 Finished");
end
//-------------------
//Process-2
//-------------------
begin
$display($time,"\tProcess-2 Started");
#20;
$display($time,"\tProcess-2 Finished");
end
join
$display($time,"\tOutside Fork-Join");
$display("-----------------------------------------------------------------");
$finish;
end
endmodule
Process-1和Process-2将会同时开始运行,Process-1在5ns完成,Process-2在20ns完成.运行结果如下:
fork join_any
fork...join_any语句块会阻塞statement-3的执行,直至fork..join_any语句块中的任意一个线程执行完(如上图中的proceess-2)后才能开始执行statement-3。
fork join any 例子:
module fork_join;
initial begin
$display("-----------------------------------------------------------------");
fork
//Process-1
begin
$display($time,"\tProcess-1 Started");
#5;
$display($time,"\tProcess-1 Finished");
end
//Process-2
begin
$display($time,"\tProcess-2 Started");
#20;
$display($time,"\tProcess-2 Finished");
end
join_any
$display($time,"\tOutside Fork-Join");
$display("-----------------------------------------------------------------");
end
endmodule
Process-1和Process-2将会同时开始运行,Process-1在5ns完成,Process-2在20ns完成.在执行fork..join_any语句块的同时,语句块后的$display语句也在执行。运行结果如下:
fork join_none
fork...join_none语句块不会阻塞statement-3的执行,即在执行fork...join_none语句块的同时也在执行statement-3.
fork join none 例子:
module fork_join_none;
initial begin
$display("-----------------------------------------------------------------");
fork
//Process-1
begin
$display($time,"\tProcess-1 Started");
#5;
$display($time,"\tProcess-1 Finished");
end
//Process-2
begin
$display($time,"\tProcess-2 Startedt");
#20;
$display($time,"\tProcess-2 Finished");
end
join_none
$display($time,"\tOutside Fork-Join_none");
$display("-----------------------------------------------------------------");
end
endmodule
在执行fork块语句的同时,语句块后面的$display语句也在执行,运行结果如下:
wait fork
直至处理完fork块中的线程,程序才会往下执行,也就是将程序阻塞在fork块这里,直到fork块执行完。例子如下:
module wait_fork;
initial begin
$display("-----------------------------------------------------------------");
fork
//Process-1
begin
$display($time,"\tProcess-1 Started");
#5;
$display($time,"\tProcess-1 Finished");
end
//Process-2
begin
$display($time,"\tProcess-2 Started");
#20;
$display($time,"\tProcess-2 Finished");
end
join_any
$display("-----------------------------------------------------------------");
$finish; //ends the simulation
end
endmodule
上面的例子在5ns执行完process-1后,fork块将不会阻塞,在执行fork块内的process-2的同时,开始执行块后的$display语句,当遇到$finish时会结束程序的执行,导致 process-2被迫中途停止执行。运行结果如下:
解决上述问题的办法就是在fork块语句后添加wait fork语句,它会等待fork语句块内的所有线程执行完毕后,才会继续往下执行。修改后的代码如下:
module wait_fork;
initial begin
$display("-----------------------------------------------------------------");
fork
//Process-1
begin
$display($time,"\tProcess-1 Started");
#5;
$display($time,"\tProcess-1 Finished");
end
//Process-2
begin
$display($time,"\tProcess-2 Started");
#20;
$display($time,"\tProcess-2 Finished");
end
join_any
wait fork; //waiting for the completion of active fork threads
$display("-----------------------------------------------------------------");
$finish; //ends the simulation
end
endmodule
运行结果如下:
disable fork
终止正在执行的fork语句块。看下面的例子:
module disable_fork;
initial begin
$display("-----------------------------------------------------------------");
//fork-1
fork
//Process-1
begin
$display($time,"\tProcess-1 of fork-1 Started");
#5;
$display($time,"\tProcess-1 of fork-1 Finished");
end
//Process-2
begin
$display($time,"\tProcess-2 of fork-1 Started");
#20;
$display($time,"\tProcess-2 of fork-1 Finished");
end
join_any
//fork-2
fork
//Process-1
begin
$display($time,"\tProcess-1 of fork-2 Started");
#5;
$display($time,"\tProcess-1 of fork-2 Finished");
end
//Process-2
begin
$display($time,"\tProcess-2 of fork-2 Started");
#20;
$display($time,"\tProcess-2 of fork-2 Finished");
end
join_none
disable fork;
$display("-----------------------------------------------------------------");
$display($time,"\tAfter disable-fork");
$display("-----------------------------------------------------------------");
end
endmodule
在执行完fork-1的process-1后开始执行后面的fork-2,由于fork-2为fork...join_none语句,不会发生阻塞,所以会继续执行下面的disable fork语句,在执行此语句后,中断所有的fork块,包括 fork-1的process-2和fork-2的process-1以及process-1的执行都被终止。运行结果如下:
3. TASK
1.任务定义
任务定义的形式如下:
task task_id;
[declaration]
procedural_statement
endtask
其中,关键词 task 和 endtask 将它们之间的内容标志成一个任务定义,task 标志着一个
任务定义结构的开始;task_id 是任务名;可选项 declaration 是端口声明语句和变量声明语
句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;procedural_statement
是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;
endtask 为任务定义结构体结束标志。下面给出一个任务定义的实例。
:定义一个任务。
task task_demo; //任务定义结构开头,命名为 task_demo
input [7:0] x,y; //输入端口说明
output [7:0] tmp; //输出端口说明
if(x>y) //给出任务定义的描述语句
tmp = x;
else
tmp = y;
endtask
上述代码定义了一个名为“task_demo”的任务,求取两个数的最大值。在定义任务时,
有下列六点需要注意:
(1)在第一行“task”语句中不能列出端口名称;
(2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及
双向端口。
(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频繁
的就是延迟控制语句) ,但这样会造成该任务不可综合。
(4)在任务中可以调用其他的任务或函数,也可以调用自身。
(5)在任务定义结构内不能出现 initial和 always过程块。
(6)在任务定义中可以出现“disable 中止语句” ,将中断正在执行的任务,但其是不
可综合的。当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
2.任务调用
虽然任务中不能出现 initial 语句和 always 语句语句, 但任务调用语句可以在 initial 语句
和 always 语句中使用,其语法形式如下:
task_id[(端口1, 端口 2, ........, 端口 N)];
其中 task_id是要调用的任务名,端口 1、端口 2,…是参数列表。参数列表给出传入任
务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果) 。
任务调用语句中,参数列表的顺序必须与任务定义中的端口声明顺序相同。任务调用语句是
过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。下面给出一个任务调
用实例。
例:通过 Verilog HDL 的任务调用实现一个 4 比特全加器。
module EXAMPLE (A, B, CIN, S, COUT);
input [3:0] A, B;
input CIN;
output [3:0] S;
output COUT;
reg [3:0] S;
reg COUT;
reg [1:0] S0, S1, S2, S3;
task ADD;
input A, B, CIN;
output [1:0] C;
reg [1:0] C;
reg S, COUT;
begin
S = A ^ B ^ CIN;
COUT = (A&B) | (A&CIN) | (B&CIN);
C = {COUT, S};
end
endtask
always @(A or B or CIN) begin
ADD (A[0], B[0], CIN, S0);
ADD (A[1], B[1], S0[1], S1);
ADD (A[2], B[2], S1[1], S2);
ADD (A[3], B[3], S2[1], S3);
S = {S3[0], S2[0], S1[0], S0[0]};
COUT = S3[1];
end
endmodule
在调用任务时,需要注意以下几点:
(1)任务调用语句只能出现在过程块内;
(2)任务调用语句和一条普通的行为描述语句的处理方法一致;
(3)当被调用输入、输出或双向端口时,任务调用语句必须包含端口名列表,且信号
端口顺序和类型必须和任务定义结构中的顺序和类型一致。需要说明的是,任务的输出端口
必须和寄存器类型的数据变量对应。
(4)可综合任务只能实现组合逻辑,也就是说调用可综合任务的时间为“0” 。而在面
向仿真的任务中可以带有时序控制,如时延,因此面向仿真的任务的调用时间不为“0”