10.1 任务
一个任务就像一个过程,它可以从描述的不同位置执行共同的代码段。共同的代码段用
任务定义编写成任务,这样它就能够从设计描述的不同位置通过任务调用被调用。任务可以
包含时序控制,即时延控制,并且任务也能调用其它任务和函数。
10.1.1 任务定义
任务定义的形式如下:
t a s k t a s k _ i d;
[d e c l a r a t i o n s]
p r o c e d u r a l _ s t a t e m e n t
e n d t a s k
任务可以没有或有一个或多个参数。值通过参数传入和传出任务。除输入参数外(参数
从任务中接收值),任务还能带有输出参数(从任务中返回值)和输入输出参数。任务的定义
在模块说明部分中编写。例如:
m o d u l e H a s _ T a s k;
p a r a m e t e r MAXBITS = 8;
t a s k R e v e r s e _ B i t s;
i n p u t [MAXBITS -1:0] D i n;
o u t p u t [MAXBITS -1:0] D o u t;
i n t e g e r K;
b e g i n
f o r (K = 0; K < M A X B I T S; K = K + 1)
D o u t [M A X B I T S-K] = D i n[K] ;
e n d
e n d t a s k
. . .
e n d m o d u l e
任务的输入和输出在任务开始处声明。这些输入和输出的顺序决定了它们在任务调用中
的顺序。下面是另一个例子:
t a s k R o t a t e _ L e f t;
i n o u t [1:16] I n _ A r r;
i n p u t [0:3] S t a r t _ B i t,S t o p _ B i t,R o t a t e _ B y;
r e g F i l l _ V a l u e;
i n t e g e r M a c 1,M a c 3;
b e g i n
f o r (M a c 3 = 1; Mac3 <= R o t a t e _ B y; M a c 3 = M a c 3 + 1)
b e g i n
F i l l _ V a l u e = I n _ A r r[S t o p _ B i t] ;
f o r (M a c 1 = S t o p _ B i t; M a c 1 >= S t a r t _ B i t + 1;
M a c 1 = M a c 1 - 1 )
I n _ A r r[M a c 1] = I n _ A r r [M a c 1 - 1];
I n _ A r r[S t a r t _ B i t] = F i l l _ V a l u e;
e n d
e n d
e n d t a s k
F i l l _ Va l u e是任务的局部寄存器,只能在任务中直接可见。任务的第 1个参数是输入输出数组I n _ A rr,随后是3个输入,S t a rt _ B i t、S t o p _ B i t和R o t a t e _ B y。除任务参数外,任务还能够引用说明任务的模块中定义的任何变量。在下一节将举例说明这种情况。
10.1.2 任务调用
一个任务由任务调用语句调用。任务调用语句给出传入任务的参数值和接收结果的变量
值。任务调用语句是过程性语句,可以在 always 语句或initial 语句中使用。形式如下:
t a s k _ i d [ (e x p r 1 , e x p r 2 , . . . , e x p r N) ] ;任务调用语句中参数列表必须与任务定义中的输入、输出和输入输出参数说明的顺序匹配。此外,参数要按值传递,不能按地址传递。在其它高级编程语言中,例如 P a s c a l,任务与过程的一个重要区别是任务能够被并发地调用多次,并且每次调用能带有自己的控制,最重要的一点是在任务中声明的变量是静态的,即它决不会消失或重新被初始化。因此一个任务调用能够修改被其他任务调用读取的局部变量的值。
下面是调用任务R e v e r s e _ B i t s的实例,该任务定义已在前面章节中给出,
/ /寄存器说明部分:
r e g [M A X B I T S-1:0] R e g _ X , N e w _ R e g;
R e v e r s e _ B i t s(R e g _ X , N e w _ R e g); //任务调用。
R e g _ X的值作为输入值传递,即传递给 D i n。任务的输出D o u t返回到N e w _ R e g。注意因为任务能够包含定时控制,任务可在被调用后再经过一定时延才返回值。因为任务调用语句是过程性语句,所以任务调用中的输出和输入输出参数必须是寄存器类型的。在上面的例子中,N e w _ R e g必须被声明为寄存器类型。
下面的例子不通过参数表向任务调用传入变量。尽管引用全局变量被认为是不良的编程
风格,它有时却非常有用。
m o d u l e G l o b a l _ V a r;
reg [0:7] R a m Q [ 0 : 6 3 ] ;
i n t e g e r I n d e x;
r e g C h e c k B i t;
t a s k G e t P a r i t y;
i n p u t A d d r e s s;
o u t p u t P a r i t y B i t;
P a r i t y B i t = ^R a m Q[A d d r e s s] ;
e n d t a s k
i n i t i a l
f o r (I n d e x = 0; I n d e x <= 63; I n d e x = I n d e x+ 1 )b e g i n
G e t P a r i t y(I n d e x , C h e c k B i t) ;
$d i s p l a y("Parity bit of memory word %d is %b.",
Index, CheckBit ) ;
e n d
e n d m o d u l e
存储器R a m Q的地址被作为参数传递,而存储器本身在任务内直接引用。任务可以带有时序控制,或等待特定事件的发生。但是,输出参数的值直到任务退出时才传递给调用参数。例如:
m o d u l e T a s k W a i t;
r e g N o C l o c k;
t a s k G e n e r a t e W a v e f o r m;
o u t p u t C l o c k Q;
b e g i n
C l o c k Q = 1;
#2 C l o c k Q = 0;
#2 C l o c k Q = 1;
#2 C l o c k Q = 0;
e n d
e n d t a s k
i n i t i a l
G e n a r a t e W a v e f o r m (N o C l o c k) ;
e n d m o d u l e
任务G e n e r a t e Wa v e f o r m对C l o c k Q的赋值不出现在N o C l o c k上,即没有波形出现在 N o C l o c k上;只有对C l o c k Q的最终赋值0在任务返回后出现在 N o C l o c k上。为避免这一情形出现,最好将C l o c k Q声明为全局寄存器类型,即在任务之外声明它。
10.2 函数
函数,如同任务一样,也可以在模块不同位置执行共同代码。函数与任务的不同之处是函数只能返回一个值,它不能包含任何时延或时序控制(必须立即执行),并且它不能调用其它的任务。此外,函数必须带有至少一个输入,在函数中允许没有输出或输入输出说明。函数可以调用其它的函数。
10.2.1 函数说明部分
函数说明部分可以在模块说明中的任何位置出现,函数的输入是由输入说明指定,形式
如下:
f u n c t i o n [r a n g e] f u n c t i o n _ i d;
i n p u t _ d e c l a r a t i o n
o t h e r _ d e c l a r a t i o n s
p r o c e d u r a l _ s t a t e m e n t
e n d f u n c t i o n
如果函数说明部分中没有指定函数取值范围,则其缺省的函数值为 1位二进制数。函数实
例如下:
m o d u l e F u n c t i o n _ E x a m p l e
p a r a m e t e r MAXBITS = 8;
f u n c t i o n [M A X B I T S-1:0] R e v e r s e _ B i t s;
i n p u t [M A X B I T S-1:0] D i n;
i n t e g e r K;
b e g i n
f o r (K=0; K < M A X B I T S; K = K + 1)
R e v e r s e _ B i t s [M A X B I T S-K] = D i n [K] ;
e n d
e n d f u n c t i o n
. . .
e n d m o d u l e
函数名为R e v e r s e _ B i t s。函数返回一个长度为 M A X B I T S的向量。函数有一个输入 D i n.K,是局部整型变量。函数定义在函数内部隐式地声明一个寄存器变量,该寄存器变量与函数同名并且取值范围相同。函数通过在函数定义中显式地对该寄存器赋值来返回函数值。对这一寄存器的赋值必须出现在函数定义中。下面是另一个函数的实例。
f u n c t i o n P a r i t y;
i n p u t [0:31] S e t;
r e g [0:3] R e t;
integer J;
b e g i n
R e t = 0;
f o r (J = 0;J <= 31; J = J + 1)
if (S e t[J] = = 1 )
R e t = R e t + 1;
P a r i t y = R e t % 2;
e n d
e n d f u n c t i o n
在该函数中,P a r i t y是函数的名称。因为没有指定长度,函数返回 1位二进制数。R e t和J
是局部寄存器变量。注意最后一个过程性赋值语句赋值给寄存器,该寄存器从函数返回值(与函数同名的寄存器在函数中被隐式地声明)。
10.2.2 函数调用
函数调用是表达式的一部分。形式如下:
f u n c _ i d(e x p r 1 , e x p r 2 , . . . , e x p r N)
以下是函数调用的例子:
r e g [M A X B I T S-1:0] N e w _ R e g , R e g _ X; //寄存器说明。
N e w _ R e g = R e v e r s e _ B i t s(R e g _ X); //函数调用在右侧表达式内。
与任务相似,函数定义中声明的所有局部寄存器都是静态的,即函数中的局部寄存器在函数的多个调用之间保持它们的值。
10.3 系统任务和系统函数
Verilog HDL提供了内置的系统任务和系统函数,即在语言中预定义的任务和函数。它们
分为以下几类:
1) 显示任务(display task)
2) 文件输入/输出任务 (File I/O task)
3) 时间标度任务 (timescale task)
4) 模拟控制任务 (simulation control task)
5) 时序验证任务 (timing check task)
6) PLA建模任务 (PLA modeling task)
7) 随机建模任务 (stochastic modeling task)
8) 实数变换函数 (conversion functions for real)
9) 概率分布函数 (probabilistic distribution function)
P L A建模任务和随机建模任务不在本书的讨论范围内。
10.3.1 显示任务
显示系统任务用于信息显示和输出。这些系统任务进一步分为:
• 显示和写入任务
• 探测监控任务
• 连续监控任务
1. 显示和写入任务
语法如下:
t a s k _ n a m e (f o r m a t _ s p e c i f i c a t i o n 1 , a r g u m e n t _ l i s t 1 ,
f o r m a t _ s p e c i f i c a t i o n 2 , a r g u m e n t _ l i s t 2 ,
. . . ,
f o r m a t _ s p e c i f i c a t i o n N , a r g u m e n t _ l i s t N) ;
t a s k _ n a m e是如下编译指令的一种:
$display $displayb $displayh $d i s p l a y o
$write $writeb $writeh $w r i t e o
显示任务将特定信息输出到标准输出设备,并且带有行结束字符;而写入任务输出特定
信息时不带有行结束符。下列代码序列能够用于格式定义:
%h 或 %H : 十六进制
%d 或 %D : 十进制
%o 或 %O : 八进制
%b 或 %B : 二进制
%c 或 %C : ASCII字符
%v 或 %V : 线网信号长度
%m 或 %M : 层次名
%s 或 %S : 字符串
%t 或 %T : 当前时间格式
如果没有特定的参数格式说明,缺省值如下:
$ d i s p l a y与$ w r i t e :十进制数
$ d i s p l a y b与$ w r i t e b :二进制数
$ d i s p l a y o与$ w r i t e o :八进制数
$ d i s p l a y h与$ w r i t e h :十六进制数
可以用如下代码序列输出特殊字符:
\n 换行
\t 制表符
\\ 字符\
\" 字符”
\OOO 值为八进制值O O O的字符
%% 字符%
例如:
$d i s p l a y("Simulation time is %t",$ t i m e) ;
$d i s p l a y( $t i m e,":R=%b,Q=%b,QB=%b", R,S,Q,QB);
/ /因为没有指定格式,时间按十进制显示。
$w r i t e("Simulation time is:);
$w r i t e( " % t \ n " , $t i m e) ;
上述语句输出$ t i m e、R、S、Q和Q B等值的执行结果如下:
Simulation time is 10
10:R=1, S=0, Q=0, QB=1
Simulation time is 10
2. 探测任务
探测任务有:
$s t r o b e $s t r o b e b $s t r o b e h $s t r o b e o
这些系统任务在指定时间显示模拟数据,但这种任务的执行是在该特定时间步结束时才
显示模拟数据。“时间步结束”意味着对于指定时间步内的所有事件都已经处理了。
a l w a y s
@ (p o s e d g e R s t )
$s t r o b e("the flip-flop value is %b at time %t",Q,$ t i m e) ;
当R s t有一个上升沿时,$s t ro b e任务输出Q的值和当前模拟时间。下面是 Q和$t i m e的一些值的输出。这些值在每次R s t的上升沿时被输出。
The flip-flop value is 1 at time 17
The flip-flop value is 0 at time 24
The flip-flop value is 1 at time 26
其格式定义与显示和写入任务相同。
探测任务与显示任务的不同之处在于:显示任务在遇到语句时执行,而探测任务的执行
要推迟到时间步结束时进行。下面的例子有助于进一步区分这两种不同任务。
i n t e g e r C o o l;
i n i t i a l
b e g i n
Cool = 1;
$d i s p l a y("After first assignment,Cool has value %d", C o o l) ;
$s t r o b e("When strobe is executed,Cool has value %d", C o o l) ;
C o o l = 2;
$d i s p l a y("After second assignment,Cool has value %d", C o o l) ;
e n d
产生的输出为:
After first assignment,Cool has value 1
When strobe is executed,Cool has value 2
After second assignment,Cool has value 2
第一个$d i s p l a y任务输出 C o o l的值1(C o o l的第一个赋值)。第二个 $d i s p l a y任务输出C o o l的值2(C o o l的第二个赋值)。$s t ro b e任务输出 C o o l的值2,这个值保持到时间步结束。
3. 监控任务
监控任务有:
$m o n i t o r $m o n i t o r b $m o n i t o r h $m o n i t o r o
这些任务连续监控指定的参数。只要参数表中的参数值发生变化,整个参数表就在时间
步结束时显示。例如:
i n i t i a l
$m o n i t o r ("At %t,D = %d, Clk = %d"),
$t i m e, D , C l k,"and Q is %b",Q);
当监控任务被执行时,对信号 D、C l k和Q的值进行监控。若这些值发生任何变化,则显
示整个参数表的值。下面是D、C l k和Q发生某些变化时的输出样本。
监控任务的格式定义与显示任务相同。在任意时刻对于特定的变量只有一个监控任务可
以被激活。
可以用如下两个系统任务打开和关闭监控。
$m o n i t o r o f f; //禁止所有监控任务。
$m o n i t o r o n; //使能所有监控任务。
这些提供了控制输出值变化的机制。$m o n i t o ro ff任务关闭了所有的监控任务,因此不再
显示监控更多的信息。$m o n i t o ro n任务用于使能所有的监控任务。
10.3.2 文件输入/输出任务
1. 文件的打开和关闭
系统函数$f o p e n用于打开一个文件。
i n t e g e r f i l e _ p o i n t e r = $ f o p e n(f i l e _ n a m e);
/ /系统函数$f o p e n返回一个关于文件的整数(指针)。
而下面的系统任务可用于关闭一个文件:
$f c l o s e(f i l e _ p o i n t e r);
这是一个关于它的用法的例子。
i n t e g e r T q _ F i l e;
i n i t i a l
b e g i n
T q _ F i l e = $ f o p e n("~/ j b / d i v . t q ") ;
. . .
$f c l o s e(T q _ F i l e) ;
e n d
2. 输出到文件
显示、写入、探测和监控系统任务都有一个用于向文件输出的相应副本,该副本可用于
将信息写入文件。这些系统任务如下:
$f d i s p l a y $f d i s p l a y b $f d i s p l a y h $f d i s p l a y o
$f w r i t e $f w r i t e b $f w r i t e h $f w r i t e o
$f s t r o b e $f s t r o b e b $f s t r o b e h $f s t r o b e o
$f m o n i t o r $f m o n i t o r b $f m o n i t o r h $f m o n i t o r o
所有这些任务的第一个参数是文件指针,其余的所有参数是带有参数表的格式定义序列。
下面的实例将作进一步解释说明。
i n t e g e r V e c _ F i l e;
i n i t i a l
b e g i n
Vec_File = $fopen (" d i v . v e c ") ;
. . .
$f d i s p l a y(V e c _ F i l e,"The simulation time %t",$time);
/ /第一个参数V e c _ F i l e是文件指针。
$f c l o s e(V e c _ f i l e) ;
e n d
等到$f d i s p l a y任务执行时,文件“d i v. v e c”中出现下列语句:
The simulation time is 0
3. 从文件中读取数据
有两个系统任务能够用于从文件中读取数据,这些任务从文本文件中读取数据并将数据
加载到存储器。它们是:$readmemb $readmemh
文本文件包含空白空间、注释和二进制(对于 $re a d m e m b)或十六进制(对于
$re a d m e m h)数字。每个数字由空白空间隔离。当执行系统任务时,每个读取的数字被指派给存储器内的一个地址。开始地址对应于存储器最左边的索引。
r e g [0:3] Mem_A [0:63];
i n i t i a l
$r e a d m e m b( " o n e s _ a n d _ z e r o . v e c " , M e m _ A ) ;
/ /读入的每个数字都被指派给从0开始到6 3的存储器单元。
显式的地址可以在系统任务调用中可选地指定,例如:
$r e a d m e m b( " r x . v e x " , M e m _ A , 1 5 , 3 0 ) ;
/ /从文件“r x . v e c”中读取的第一个数字被存储在地址1 5中,下一个存储在地址
/ / 1 6,并以此类推直到地址3 0。
也可以在文本文件中显式地给出地址。形式如下:
@ a d d r e s s _ i n _ h e x a d e c i m a l
在这种情况下,系统任务将数据读入指定地址。后续的数字从指定地址开始向后加载。
10.3.3 时间标度任务
系统任务
$p r i n t t i m e s c a l e
给出指定模块的时间单位和时间精度。若 $p r i n t t i m e s c a l e任务没有指定参数,则用于输出包含该任务调用的模块的时间单位和精度。如果指定到模块的层次路径名为参数,则系统任务输出指定模块的时间单位和精度。
$p r i n t t i m e s c a l e ;
$p r i n t t i m e s c a l e(h i e r _ p a t h _ t o _ m o d u l e) ;
下面是这些系统被调用时输出的样本。
Time scale of (C10) is 100ps/100ps
Time scale of (C10.INST) is lus/100ps
系统任务
$t i m e f o r m a t
指定% t格式定义如何报告时间信息,该任务形式如下:
$t i m e f o r m a t(u n i t s _ n u m b e r , p r e c i s i o n ,
s u f f i x , n u m e r i c _ f i e l d _ w i d t h) ;
其中u n i t s _ n u m b e r为:
0 :1 s
-1 :100 ms
-2 :10 ms
-3 :1 ms
-4 :100 us
-5 :10 us
-6 :1 us
-7 :100 ns
-8 :10 ns
-9 :1 ns
-10 :100 ps
-11 :10 ps
-12 :1 ps
-13 :100 fs
-14 :10 fs
-15 :1 fs
系统任务调用
$t i m e f o r m a t(-4, 3, "ps", 5);
$d i s p l a y("Current simulation time is %t",$time);
将显示$d i s p l a y任务中% t说明符的值,如下:
Current simulation time is 0.051 ps
如果没有指定$t i m e f o r m a t,% t按照源代码中所有时间标度的最小精度输出。
10.3.4 模拟控制任务
系统任务
$f i n i s h;
使模拟器退出,并将控制返回到操作系统。
系统任务
$s t o p
使模拟被挂起。在这一阶段,交互命令可能被发送到模拟器。下面是该命令使用方法的例子。
i n i t i a l #500 $s t o p;
5 0 0个时间单位后,模拟停止。
10.3.5 定时校验任务
系统任务:
$s e t u p(d a t a _ e v e n t , r e f e r e n c e _ e v e n t , l i m i t) ;
如果
(time_of_reference_event - time_of_data_event ) < limit
则报告时序冲突(timing violation);
系统调用实例如下:
$s e t u p(D, p o s e d g e Ck, 1, 0);
系统任务:
$h o l d(r e f e r e n c e _ e v e n t , d a t a _ e v e n t , l i m i t) ;
如果
(time_of_data_event - time_of_reference_event ) < limit,
则报数据保持时间时序冲突。
例如:
$h o l d(p o s e d g e C k , D , 0 . 1) ;
系统任务$s e t u p h o l d是$s e t u p和$h o l d任务的结合:
$s e t u p h o l d(r e f e r e n c e _ e v e n t , d a t a _ e v e n t , s e t u p _ l i m i t , h o l d _ l i m i t) ;
而系统任务:
$w i d t h(r e f e r e n c e _ e v e n t , l i m i t , t h r e s h o l d) ;
则检查信号的脉冲宽度限制,如果
threshold < (t i m e _ o f _ d a t a _ e v e n t - t i m e _ o f _ r e f e r e n c e _ e v e n t) < limit
则报告信号上出现脉冲宽度不够宽的时序错误。
数据事件来源于基准事件:它是带有相反边沿的基准事件,例如:
$w i d t h(n e g e d g e C k ,0 . 0 , 0 ) ;
系统任务:
$p e r i o d(r e f e r e n c e _ e v e n t , l i m i t)
检查信号的周期,若
( time_of_data_event - time_of_reference_event ) < limit
则报告时序错误。
基准事件必须是边沿触发事件。数据事件来源于基准事件:它是带有相同边沿的基准事
件。
系统任务:
$s k e w(r e f e r e n c e _ e v e n t , d a t a _ e v e n t , l i m i t)
检查信号之间(尤其是成组的时钟控制信号之间)的偏斜( s k e w)是否满足要求,若
time_of_data_event - time_of_reference_event > limit则报告信号之间出现时序偏斜太大的错误。如果 d a t a _ e v e n t的时间等于r e f e r e n c e _ e v e n t的时间,
则不报出错。
系统任务:
$r e c o v e r y(r e f e r e n c e _ e v e n t , d a t a _ e v e n t , l i m i t) ;
主要检查时序状态元件(触发器、锁存器、 R A M和R O M等)的时钟信号与相应的置 /复位信号之间的时序约束关系,若
( time_of_data_event - time_of_reference_event ) < limit则报告时序冲突。该系统任务的基准事件必须是边沿触发事件。该系统任务在执行定时校检前记录新基准事件时间;因此,如果数据事件和基准事件在相同的模拟时间同时发生,就报告时序冲突错误。
系统任务:
$n o c h a n g e(r e f e r e n c e _ e v e n t , d a t a _ e v e n t , s t a r t _ e d g e _ o f f s e t ,
e n d _ e d g e _ o f f s e t) ;
如果在指定的基准事件区间发生数据变化,就报告时序冲突错误。基准事件必须是边沿触发
事件。例如:
$n o c h a n g e(n e g e d g e C l e a r , P e r s e t, 0 , 0 ) ;
如果在C l e a r为低时P re s e t发生变化,将报告时序冲突错误。
上述每个系统任务均可以带有一个可选参数 n o t i f i e r。当发生时序冲突时,系统任务根据
下列c a s e语句改变自身的值来更新参数n o t i f i e r。
c a s e (n o t i f i e r)
'bx : notifier= 'b0;
'b0 : notifier= 'b1;
'b1 : notifier = 'b0;
'bz : notifier= 'bz;
e n d
n o t i f i e r参数可提供关于时序冲突或传播 x到输出的时序冲突。使用参数 n o t i f i e r的例子如下:
r e g N o t i f y D i n ;
. . .
$s e t u p h o l d (n e g e d g e C l o c k , D i n , t S E T U P , t H O L D , N o t i f y D i n) ;
在这一实例中,N o t i f y D i n是参数n o t i f i e r。如果时序冲突发生,寄存参数 N o t i f y D i n根据前面对参数n o t i f i e r描述的c a s e语句改变其值。
10.3.6 模拟时间函数
下列系统函数返回模拟时间。
• $time:返回6 4位的整型模拟时间给调用它的模块。
• $stime:返回3 2位的时间。
• $realtime:向调用它的模块返回实型模拟时间。
例如
` t i m e s c a l e 1 0 n s / 1 n s
m o d u l e T B ;
. . .
i n i t i a l
$ m o n i t o r ("Put_A=%d Put_B=%d",Put_A,Put_B,
"Get_O=%d",Get_O,"at time %t",$time ) ;
e n d m o d u l e
该例产生的输出如下:
Put_A=0 Put_B=0 Get_O=0 at time 0
Put_A=0 Put_B=1 Get_O=0 at time 5
Put_A=0 Put_B=0 Get_O=0 at time 16
$t i m e按模块T B的时间单位比例返回值,并且被四舍五入。注意 $t i m e f o r m a t描述了时间
值如何被输出。下面是另一例子及其输出。
i n i t i a l
$m o n i t o r ( "Put_A=%d Put_B=%d",Put_A,Put_B,
"Get_O=%d",Get_O,"at time %t",$realtime ) ;
Put_A=0 Put_B=1 Get_O=0 at time 5.2
Put_A=0 Put_B=0 Get_O=0 at time 15.6
10.3.7 变换函数
下列系统函数是数字类型变换的功能函数:
• $rt o i(re a l _ v a l u e):通过截断小数值将实数变换为整数。
• $i t o r(i n t e g e r _ v a l u e):将整数变换为实数。
• $re a l t o b i t s(re a l _ v a l u e):将实数变换为6 4位的实数向量表示法(实数的IEEE 745表示法)
• $b i t s t o re a l(b i t _ v a l u e):将位模式变换为实数(与$re a l t o b i t s相反)
10.3.8 概率分布函数
函数:
$r a n d o m [ (s e e d) ]
根据种子变量( s e e d)的取值按3 2位的有符号整数形式返回一个随机数。种子变量(必
须是寄存器、整数或时间寄存器类型)控制函数的返回值,即不同的种子将产生不同的随机
数。如果没有指定种子,每次$r a n d o m函数被调用时根据缺省种子产生随机数。
例如,
i n t e g e r Seed, Rnum;
w i r e C l k ;
i n i t i a l S e e d = 12;
a l w a y s
@ (Clk) Rnum= $random (S e e d) ;
在C l k的每个边沿,$r a n d o m被调用并返回一个3 2位有符号整型随机数。
如果数字在取值范围内,下述模运算符可产生- 1 0~+ 1 0之间的数字。
Rnum = $r a n d o m(S e e d) % 11;
下面是没有显式指定种子的例子。
R n u m = $r a n d o m / 2; //种子变量是可选的。
注意数字产生的顺序是伪随机排序的,即对于一个初始种子值产生相同的数字序列。
表达式:{r a n d o m} % 11
产生0~1 0之间的一个随机数。并置操作符( { })将$r a n d o m函数返回的有符号整数变换为无符号数。
下列函数根据在函数名中指定的概率函数产生伪随机数。
$d i s t _ u n i f o r m ( s e e d , s t a r t , e n d)
$d i s t _ n o r m a l ( s e e d , m e a n , s t a n d a r d _ d e v i a t i o n , u p p e r)
$d i s t _ e x p o n e n t i a l ( s e e d , m e a n)
$d u s t _ p o i s s o n ( s e e d , m e a n)
$d i s t _ c h i _ s q u a r e ( s e e d , d e g r e e _ o f _ f r e e d o m)
$d i s t _ t ( s e e d , d e g r e e _ o f _ f r e e d o m)
$d i s t _ e r l a n d ( s e e d , k _ s t a g e , m e a n)
这些函数的所有参数都必须是整数。
10.4 禁止语句
禁止语句是过程性语句(因此它只能出现在 a l w a y s或i n i t i a l语句块内)。禁止语句能够在任
务或程序块没有执行完它的所有语句前终止其执行。它能够用于对硬件中断和全局复位的建
模。其形式如下:
d i s a b l e t a s k _ i d ;
d i s a b l e b l o c k _ i d ;
在禁止语句执行后,继续执行任务调用或被禁止的程序块的下一条语句。
b e g i n: B L K _ A
/ /语句1。
/ /语句2。
d i s a b l e B L K _ A;
/ /语句3。
/ /语句4。
e n d
/ /语句5。
语句3和语句4从未被执行。在禁止语句被执行后,执行语句 5。又如:
t a s k B i t _ T a s k;
begin
/ /语句6。
d i s a b l e B i t _ T a s k;
/ /语句7。
e n d
e n d t a s k
/ /语句8。
Bit_Task; //任务调用
/ /语句9。
当禁止语句被执行时,任务被放弃,即语句 7永远不会被执行。紧跟在任务调用后面继续
执行的语句,在此例中是语句9。建议最好在任务定义中不要使用 d i s a b l e禁止语句,尤其是当任务具有一定的返回值时更是如此。这是因为当任务被禁止时, Ve r i l o g语言给出的输出和输入参数值是不确定的。如果必须在任务中这样做,一种比较稳妥的方法是:如果有,就在任务中禁止顺序程序块,例如,
t a s k E x a m p l e ;
o u t p u t [ 0 : 3 ] C o u n t;
b e g i n:L O C A L _ B L K
/ /语句1 0。
Count = 10;
d i s a b l e L O C A L _ B L K;
/ /语句1 1。
e n d
e n d t a s k
当禁止语句开始执行时,禁止语句促使顺序程序块 L O C A L _ B L K退出。由于这是任务中的
唯一语句,因此任务退出,并且C o u n t的值为1 0。如果禁止语句被替换为:
d i s a b l e E x a m p l e;
那么在禁止语句开始执行后,C o u n t的值不确定。
10.5 命名事件
考虑下述两个a l w a y s语句。
r e g Ready ,Done;
/ /获取a l w a y s语句的交互:
i n i t i a l
b e g i n
D o n e = 0;
#0 D o n e = 1;
e n d
a l w a y s
@ (D o n e) b e g i n
. . .
/ /完成处理这个a l w a y s语句。
/ /触发下一个a l w a y s语句。
/ /在R e a d y信号上创建一个事件。
R e a d y = 0;
#0 R e a d y = 1;
e n d
a l w a y s
@ (R e a d y) b e g i n
. . .
/ /完成处理这个a l w a y s语句。
/ /创建事件触发前面的a l w a y s语句。
D o n e = 0;
# 0 D o n e = 1;
e n d
每个a l w a y s语句中的两个赋值必须要确认在 R e a d y和D o n e上创建一个事件。这表明 R e a d y
和D o n e的目的是作为两个a l w a y s语句间的握手信号。
Verilog HDL提供一种替代机制实现这一功能 — 使用命名事件。命名事件是Verilog HDL
语言的另外一种数据类型(Ve r i l o g语言中的其它两类数据类型是寄存器类型和线网数据类型)。命名事件必须在使用前声明。声明形式如下:
e v e n t Ready, Done;事件声明说明R e a d y和D o n e为两个命名事件。声明命名事件后,可以使用事件触发语句创建事件。形式如下:
- > R e a d y ;
- > D o n e ;
命名事件上的事件能够同变量上的事件一样被监控,即使用 @机制,例如:
@ ( D o n e)
所以只要D o n e 上的事件触发语句被执行,一个事件就在 D o n e上发生,这使执行。
可以用命名事件重写两类always 语句的简例:
e v e n t Ready, Done
i n i t i a l
- >D o n e;
a l w a y s
@( D o n e) b e g i n
. . .
/ /触发下一个a l w a y s语句。
/ /在R e a d y上创建一个事件。
- >R e a d y ;
end
a l w a y s
@ (R e a d y)b e g i n
. . .
/ /创建事件来触发前面的a l w a y s语句。
- > D o n e;
e n d
也可以使用事件描述状态机。下面是一个异步状态机实例:
e v e n t State1, State2, State3 ;
/ /状态复位
i n i t i a l
b e g i n
/ /复位状态逻辑。
- > S t a t e 1;
e n d
a l w a y s
@ (S t a t e 1) b e g i n
// State1 逻辑。
->State2; / /在State2 上创建事件。
e n d
a l w a y s
@ (S t a t e 2) b e g i n
/ / S t a t e 2 逻辑。
->State3; // 在State3 上创建事件。
e n d
a l w a y s
@ (S t a t e 3) b e g i n
/ / S t a t e 3 逻辑。它可以有如下语句:
i f (I n p u t A)
->State2; // 在State2 上创建事件。
e l s e
->State1; // 在State1 上创建事件。
e n d
i n i t i a l语句描述复位逻辑。在initial 语句执行结束时,触发第2条always 语句,该语句中最后一条语句的执行促使在 S t a t e 2上发生一个事件;这促使第 3条always 语句执行,然后第 4条always 语句执行。在最后一条 always 语句中,根据 Input A的值决定事件发生在 S t a t e 2还是S t a t e 1上。
10.6 结构描述方式和行为描述方式的混合使用
在前面的章节中,我们描述了硬件建模的几种不同方式。Verilog HDL允许所有这些不同
风格的建模在单一模块中结合使用。模块语法如下:
m o d u l e m o d u l e _ n a m e(p o r t _ l i s t) ;
D e c l a r a t i o n s :
Input ,ouput and inout declarations.
Net declarations.
Reg declarations.
Parameter declarations.
Initial statement.
Gate instantiation statement.
Module instantiation statement.
UDP instantiation statement.
Always statement.
Continuous assignment.
e n d m o d u l e
下面的实例采用不同的风格进行硬件建模:
m o d u l e MUX2x1 (C t r l , A , B , E n a , Z)
/ /输入说明:
i n o u t C t r l , A , B , E n a;
/ /输出说明:
o u t p u t Z;
/ /线网说明:
w i r e M o t , N o t _ C t r l ;
/ /带赋值的线网说明:
w i r e Z = Ena == 1 ? Mot : 'bz;
/ /门实例语句:
n o t (N o t _ C t r l , C t r l) ;
o r (M o t , T a , T b) ;
/ /连续赋值
a s s i g n T a = A & Ctrl;
a s s i g n T b = B & Not_Ctrl;
e n d m o d u l e
模块包含内置逻辑门(结构化组件)和连续赋值(数据流方式)的混合描述形式。
10.7 层次路径名
Verilog HDl中的标识符具有一个唯一的层次路径名。层次路径名通过由句点(.)隔开的
名字组成。新层次由以下定义:
1) 模块实例化
2) 任务定义
3) 函数定义
4) 命名程序块
任何标识符的全称路径名由顶层模块(不被任何其它模块实例化的模块)开始。这一路
径名可在描述的任何层次使用。实例如下。图 1 0 - 1显示了层次。
图10-1 模块层次
m o d u l e T o p;
w i r e S b u s;
f u n c t i o n F u n c .. .
. . .
e n d f u n c t i o n
t a s k P r o c
. . .
r e g A r t;
b e g i n : B L A
i n t e g e r D o t;
第10章 其 他 论 题 107
下载
程序块 BLA
integer Dot
程序块 BLB
reg Art,Cit
. . .
e n d
b e g i n : B L B
reg Art, Cit;
. . .
e n d
e n d t a s k
Chil C1(...) ; // 一个模块实例。
e n d m o d u l e / /模块T o p。
m o d u l e C h i l ;
r e g A r t ;
. . .
e n d m o d u l e
本例中的层次名为:
T o p . C 1 . A r t
T o p . P r o c . A r t
T o p . P r o c . B L B . A r t
T o p . P r o c . B L A . D o t
T o p . P r o c . B L B . C i t
T o p . S b u s
这些层次名允许自由访问层次结构中任一层次的任一数据项。数据不仅可读,而且可以
通过路径名更新任何层次中的数据项的值。
较低层模块能够通过使用模块实例名限定变量引用高层 (称为向上引用)或低层(称为向下
引用)模块。形式如下:
m o d u l e _ i n s t a n c e _ n a m e . v a r i a b l e _ n a m e
对于向下路径引用,模块实例必须与较低层模块在同一层。例如:
m o d u l e T o p;
w i r e S b u s ;
Chil Cl (. . .); // 一个模块实例。
$d i s p l a y (C l . A r t); //向下引用。
e n d m o d u l e
m o d u l e C h i l;
r e g A r t;
. . .
e n d m o d u l e
10.8 共享任务和函数
一种在不同模块间共享任务和函数的方法是在文本文件中编写共享任务和函数的定义,
然后使用` i n c l u d e编译指令在需要的模块中包含这些定义。假设我们在文件“ s h a r e . h”中有如
下函数和任务定义:
f u n c t i o n S i g n e d P l u s;
. . .
e n d f u n c t i o n
function S i g n e d M i n u s;
. . .
e n d f u n c t i o n
t a s k P r e s e t C l e a r ;
. . .
e n d t a s k
下面是在模块中使用文件的方式:
module SignedAlu (A, B, Operation, Z ) ;
i n p u t [0:3] A, B;
i n p u t O p e r a t i o n ;
o u t p u t [0:3] Z;
r e g [0:3] Z;
/ /包含共享函数的定义。
` i n c l u d e “s h a r e . h”
a l w a y s
@ (A o r B o r O p e r a t i o n)
i f (O p e r a t i o n)
Z = S i g n e d P l u s (A, B) ;
e l s e
Z = S i g n e d M i n u s (A, B) ;
e n d m o d u l e
注意因为文件“S h a r e . h”中任务和函数定义没有被模块说明限定, ` i n c l a d e编译指令必须
在模块说明内出现。
有一种可选的方法是在模块内定义共享任务和函数,然后用层次名在不同的模块中引用
需要的任务或函数。下面的实例与前面相同,但是这一次任务和函数定义在模块说明内出现。
module S h a r e ;
f u n c t i o n S i g n e d P l u s ;
. . .
e n d f u n c t i o n
f u n c t i o n S i g n e d M i n u s;
. . .
e n d f u n c t i o n
t a s k P r e s e t C l e a r ;
. . .
e n d t a s k
e n d m o d u l e
下面是在其它模块中引用共享函数的方法。
m o d u l e S i g n e d A l u 2 (A, B, Operation, Z ) ;
i n p u t [0:3] A, B;
i n p u t O p e r a t i o n;
o u t p u t [0:3] Z;
r e g [0:3] Z;
a l w a y s
@ (A o r B o r O p e r a t i o n)
i f (O p e r a t i o n)
Z = S h a r e . S i g n e d P l u s (A, B) ;
e l s e
Z = S h a r e . S i g n e d M i n u s (A, B) ;
e n d m o d u l e
10.9 值变转储文件
值变转储(V C D)文件包含设计中指定变量取值变化的信息。它的主要目的是为其它后
处理工具提供信息。
下面的系统任务用于创建和将信息导入 V C D文件。
1) $d u m p f i l e:本系统任务指定转储文件名。
例如:
$d u m p f i l e (“u a r t . d u m p”) ;
2) $dumpvars:本系统任务指定哪些变量值变化时转储进转储文件。
$d u m p v a r s ;
/ /无参数,它指定在设计中转储所有变量。
$d u m p v a r s (level, module_name ) ;
/ /在指定模块和所有指定层次下面的模块中的转储变量。
$d u m p v a r s (1, UART) ;
/ /只转储模块U A R T中的变量。
$d u m p v a r s (2, UART) ;
/ /转储U A R T及其下一层模块中的所有变量。
$d u m p v a r s (0, UART) ;
/ /第0层导致U A R T模块及其下面层次中所有模块中的各个变量被转储。
$d u m p v a r s (0, P_State, N_State ) ;
/ /转储关于P _ S t a t e和N _ S t a t e的变量信息。层次数与此例无关,但是必须给出/ /层次数。
$d u m p v a r s (3, Div.Clk, UART ) ;
/ /层次数只作用于模块本身及其下面两个层次中的所有变量。在此例中,只作用于模块 U A R T,即U A R T中所有变量及U A R T下面两个层次中各模块的所有变量,同时转储变量D i v . C l k上值的变化。
3) $d u m p o ff:本系统任务促使转储任务被挂起。
$d u m p o f f ;
4) $d u m p o n:本系统任务促使所有转储任务被挂起。语法如下:
$d u m p o n;
5) $d u m p a l l:本系统任务执行时转储所有当前指定的变量值。语法如下:
$d u m p a l l
6) $d u m p l i m i t:本系统任务为V C D文件指定最大长度(字节)。转储在到达此界限时停止。例如,
$d u m p l i m i t (1024); //VCD 文件的最大值为1 0 2 4字节。
7) $ d u m p f l u s h:本系统任务刷新操作系统 V C D文件缓冲区中的数据,将数据存到 V C D
文件中。执行此系统任务后,转储任务处于唤醒状态。
$d u m p f l u s h ;
10.9.1 举例
下面是在5~1 2之间计数的可逆计数器的例子:
m o d u l e C o u n t U p D o w n (Clk, Count, Up_Down ) ;
i n p u t Clk, Up_Down;
o u t p u t [0:3] C o u n t ;
r e g [0:3] C o u n t ;
i n i t i a l C o u n t = ‘d 5 ;
a l w a y s
@ (p o s e d g e C l k) b e g i n
i f (U p _ D o w n)
b e g i n
C o u n t = C o u n t + 1;
if (C o u n t > 12)
C o u n t = 12;
e n d
e l s e
b e g i n
C o u n t = C o u n t -1 ;
i f (C o u n t < 5)
Count = 5;
e n d
e n d
e n d m o d u l e
m o d u l e T e s t;
r e g Clock, UpDn;
w i r e [0:3] C n t _ O u t;
p a r a m e t e r O N _ D E L A Y = 1, O F F _ D E L A Y = 2;
CountUpDown C1(Clock, Cnt_Out, UpDn ) ;
a l w a y s
b e g i n
C l o c k = 1;
# O N _ D E L A Y;
C l o c k = 0;
# O F F _ D E L A Y ;
e n d
i n i t i a l
b e g i n
U p D n = 0;
#50 UpDn = 1;
#100 $d u m p f l u s h;
$s t o p; //停止模拟。
e n d
i n i t i a l
b e g i n
$d u m p f i l e (“c o u n t . d u m p”) ;
$d u m p l i m i t ( 4 0 9 6 ) ;
$d u m p v a r s (0, T e s t ;)
$d u m p v a r s (0, Cl.Count, Cl.Clk, C1.Up_Down ) ;
e n d
e n d m o d u l e
10.9.2 VCD文件格式
V C D文件是A S C I I文件。V C D文件包含如下信息:
• 文件头信息:提供日期、模拟器版本和时间标度单位。
• 节点信息:定义转储作用域和变量类型。
• 取值变化:实际取值随时间变化。记录绝对模拟时间。
V C D文件实例如图1 0 - 2所示。
图10-2 VCD文件
10.10 指定程序块
迄今为止,我们讨论的时延,如门时延和线网时延,都是分布式时延。模块中关于路径
的时延,称为模块路径时延,可以使用指定程序块来指定。通常,指定程序块有如下用途:
1) 指定源和目的之间的通路。
2) 为这些通路分配时延。
3) 对模块进行时序校验。
指定程序块出现在模块说明内。形式如下:
s p e c i f y
spec_param_declarations / /参数说明
path_declarations / /通路说明
s y s t e m _ t i m i n g _ c h e c k s / /系统时序校验说明
e n d s p e c i f y
s p e c p a r a m (或指定参数)说明指定程序块内使用的参数。实例如下:
s p e c p a r a m t S E T U P = 20, tHOLD = 25;
在指定程序块内能够描述下述三类模块通路:
• 简单通路
• 边沿敏感通路
• 与状态有关的通路
可以使用如下两种形式说明简单通路。
source *> destination
/ /指定一个完全连接:源参数上的每一位都与目的参数的所有位相连接。
source => destination
/ /指定一个并行连接:源参数上的每一位分别与目的参数的位一一连接。
以下是一些实例。
i n p u t C l o c k;
i n p u t [7:4] D;
o u t p u t [4:1] Q;
(C l o c k => Q) = 5;
/ /从输入C l o c k到Q的每位延迟为5。
(D *> Q) = ( tRISE, tFALL) ;
/* 包括下述通路:
D[7] 到 Q [ 4 ]
D[7] 到 Q [ 3 ]
D[7] 到 Q [ 2 ]
D[7] 到 Q [ 1 ]
D[6] 到 Q [ 4 ]
. . .
D[4] 到 Q [ 1 ]
* /
在边沿敏感通路中,通路描述与源的边沿相关。例如,
(p o s e d g e C l o c k => (Qb +: Da)) = (2:3:2);
/ *通路时延从C l o c k的正沿到Q b。数据通路从Q b到D a, 当D a传播到Q b时,D a不翻转。* /与状态有关的通路在某些条件为真时指定通路时延。例如,
i f (C l e a r)
(D => Q) = (2.1, 4.2);
/ /只在C l e a r为高电平时,为指定通路时延。
下面是可在指定程序块内使用的时序验证系统任务。
$s e t u p $h o l d
$s e t h o l d $p e r i o d
$s k e w $r e c o v e r y
$w i d t h $n o c h a n g e
下面是指定程序块的实例。
s p e c i f y
/ /指定参数:
s p e c p a r a m t C L K _ Q = (5:4:6);
s p e c p a r a m t S E T U P = 2.8, t H O L D = 4.4;
/ /指定通路的路径时延:
(Clock *> Q) = tCLK_Q;
(Data *> Q) = 12;
(Clear, Preset *> Q ) = (4,5);
/ /时序验证:
$s e t u p h o l d (n e g e d g e Clock, Data, tSETUP, tHOLD ) ;
e n d s p e c i f y
沿着模块通路,只有长度大于通路时延的脉冲才能传播到输出。但是,还可以通过用称
为PAT H P U L S E$的专门指定程序块参数进行控制。除用于指定被舍弃的脉冲宽度范围外,还
可用于指定促使通路结束处出现x的脉冲宽度范围。参数说明的简单形式如下:
P A T H P U L S E $= (reject_limit, [, error_limit] ) ;
如果脉冲宽度小于 re j e c t _ l i m i t,那么脉冲就不会传播到输出。如果脉冲宽度小于
e rro r _ l i m i t(如果未指定就与re j e c t _ l i m i t相同)。但是大于re j e c t _ l i m i t,在通路的目标处产生x。
也可以按如下形式使用:
P A T H P U L S E$ i n p u t _ t e r m i n a l $ o u t p u t _ t e r m i n a l
PAT H P U L S E $参数为特殊通路指定脉冲界限。
下面是指定程序块的实例。
s p e c i f y
specparam PATHPULSE $= (1, 2);
// Reject limit = 1, Error limit = 2.
specparam PATHPULSE $Data$Q = 6;
// Reject limit = Error limit = 6, 在从 D a t a 到 Q 的路径上。
e n d s p e c i f y
10.11 强度
在Verilog HDL中除了指定四个基本值0、1、x和z外,还可以对这些值指定如驱动强度和
电荷强度等属性。
10.11.1 驱动强度
驱动强度可以在如下情况中指定:
1) 在线网说明中带赋值的线网。
2) 原语门实例中的输出端口。
3) 在连续赋值语句中。
驱动强度定义有两个值:一个是线网被赋值为 1时的强度值;另一个是线网被赋值为 0时
的强度值。形式如下:
(strength_for_1, strength_for_0 )
值的顺序并不重要。对于值1的赋值,只允许如下的信号驱动强度:
• supply1
• stro n g 1
• pull1
• weak1
• highz1(禁止对门级原语使用)
对于值0的赋值,允许如下的信号驱动强度:
• supply0
• stro n g 0
• pull0
• weak0
• highz0 (禁止对门级原语使用)
缺省的信号驱动强度定义为(s t ro n g 0, s t ro n g 1)。例如:
/ /线网的强度:
w i r e (pull1, weak0 ) # (2,4) L r k = Pol && Ord;
/ /信号的驱动强度定义仅适用于标量类型的信号,如:w i r e、w a n d、w o r、t r i、t r i a n d、t r i o r、
t r i r e g、/ /t r i 0和t r i 1。
/ /门级原语输出端口的驱动强度定义:
nand (pull1, strong0 ) # (3:4:4) A l (Mout, MinA, MinB, MinC ) ;
/ /信号驱动强度仅适用于定义下列门级原语的外部端口:a n d、o r、x o r、n a n d、n o r、x n o r、/ /b u f
b u f i f 0、b u f i f 1、n o t、n o t i f 0、n o t i f 1、p u l l d o w n和p u l l u p。
/ /连续赋值语句中的强度定义:
a s s i g n (weak1, pull0) #2.56 Wrt = Ctrl;
线网的驱动强度可以在显示任务中用 % v格式定义输出。例如:
$d i s p l a y ( " Prq is %v", Prq ) ;
结果为:Prq is Wel
10.11.2 电荷强度
三态寄存器线网也能有选择地规定其存储的电荷强度。三态寄存器线网存储的电荷强度
与三态线网相关的电容大小有关。电荷强度分为三类:
• 小型
• 中型(如果没有特别强调,则为缺省值)
• 大型
此外,三态寄存器线网存储的电荷衰退时间也可被指定。实例如下:
trireg (s m a l l ) # (5,4,20) T r o;
三态寄存器线网Tro有一个小型电容。上升时延是5个时间单位,下降时延是4个时间单位,
并且放电时间(当线网处于高阻状态时的电容器放电 )是2 0个时间单位。
10.12 竞争状态
如果在连续赋值或always 语句中未使用时延,即是零时延时,会产生竞争状态。这是因
为Verilog HDL没有定义同时发生的事件的模拟顺序。
下面用一个简例解释说明使用非阻塞性赋值的零时延情况。
b e g i n
S t a r t <= 0;
S t a r t <= 1;
end
在时间步结束时,值 0和值1都被调度为S t a rt赋值。根据事件的排序 (由模拟器内部决定 ),
S t a rt上的结果可以是0,也可以是1。
下面的另一例子显示由于事件排序产生的竞争状态。
i n i t i a l
b e g i n
P a l = 0;
C t r l = 1;
#5 Pal = 1;
C t r l = 0;
end
a l w a y s
@ (C o t o r C t r l) b e g i n
$d i s p l a y ("The value of Cot at time", $ t i m e, "is", C o t) ;
e n d
a s s i g n Cot = Pal;
在时刻0,当P a l和C t r l在i n i t i a l语句内被赋值时,连续赋值语句和 a l w a y s语句都已为执行做好准备。那么哪一个先执行呢 ? Verilog HDL语言没有定义这种顺序。如果连续赋值语句首先执行,C o t赋值为0,反过来触发a l w a y s语句。但是因为它已为执行做好准备,所以没有发生任何改变。a l w a y s语句开始执行,C o t的值显示为0。
如果我们假设首先执行 a l w a y s语句,C o t的当前值被输出(连续赋值语句还没有执行 ),然
后连续赋值语句开始执行,更新C o t的值。因此,在处理零时延赋值时要格外注意。下面是竞争状态的另一实例。
a l w a y s @ (p o s e d g e G l o b a l C l k)
RegB = RegA;
a l w a y s @ (p o s e d g e G l o b a l C l k)
RegC = RegB;
语言没有指出在 G l o b a l C l k上有正沿时,哪一条 always 语句首先执行。如果执行第1条
always 语句,R e g B将立即获得R e g A的值;随后第2条a l w a y s语句执行,R e g C将获得R e g B最新的取值(第1条a l w a y s语句中的赋值)。
如果首先执行第 2条always 语句,R e g C将获得R e g B的旧值(R e g B还没有被赋值 ),随后R e g B将被赋于R e g A的值。所以根据首先执行哪一条 a l w a y s语句,R e g C取不同的值。因为过程性赋值立即发生,即没有任何时延,所以会产生一些问题。避免这种问题的一种方法是插入语句内时延。但最好是使用非阻塞性赋值语句。如:
a l w a y s @ (p o s e d g e G l o b a l C l k)
RegB < = RegA;
a l w a y s @ (p o s e d g e G l o b a l C l k)
RegC <= RegB;
当a l w a y s语句通过变量通信时,对变量赋值时使用非阻塞性赋值可以避免产生竞争状态。

欢迎关注FPGA设计论坛,实时获取更多FPGA相关资讯


本文详细介绍了 Verilog HDL 的核心概念,包括任务和函数的定义与调用、系统任务和函数的应用、时序校验、命名事件的使用、结构描述与行为描述的混合使用等。此外,还探讨了驱动强度、电荷强度的概念以及如何避免竞争状态等问题。

397

被折叠的 条评论
为什么被折叠?



