Verilog HDL
数据类型
四值逻辑
上面列出了Verilog采用的具有八种
信号强度
的四值逻辑(four-valued logic),数字电路中的信号可以用
逻辑值
、信号强度加以描述。当系统遇到信号之间的竞争时,需要考虑各组信号的状态和强度。如果驱动统一线网的信号强度不同,则输出结果是信号强度高的值;如果两个强度相同的信号之间连接到同一个线网,将会发生竞争,结果为不确定值x。
线网与寄存器
Verilog所用到的所有变量都属于两个基本的类型:线网类型和
寄存器
类型。
线网与我们实际使用的电线类似,它的数值一般只能通过连续赋值(continuous assignment),由赋值符右侧连接的驱动源决定。线网在初始化之前的值为x(trireg类型的线网是一个例外,它相当于能够储存电荷的电容器)。如果未连接驱动源,则该线网变量的当前数值为z,即高阻态。线网类型的变量有以下几种:wire、tri、wor、trior、wand、triand、tri0、tri1、supply0、supply1、trireg,其中wire作为一般的电路连线使用最为普遍,而其他几种用于构建总线,即多个驱动源连接到
一条线
网的情况,或搭建电源、接地等。当进行模块的端口声明时,如果没有明确指出其类型,那么这个端口会被隐含地声明为wire类型。因此,在声明输出端口时应该注意是否有必要加上reg关键字。以下面的代码片段为例:
module my_moule (out1, out2, in1, in2); //该模块具有两个输出端口
output reg out1; //out1端口被声明为为reg类型,它可以保存
当前值
output out2; //out2端口隐含地被声明为为wire类型,它的数值必须依赖连续赋值语句维持
endmodule
寄存器与之不同,它可以保存当前的数值,直到另一个数值被赋值给它。在保持当前数值的过程中,不需要驱动源对它进行作用。如果未对
寄存器变量
赋值,它的初始值则为x。Verilog中所说的寄存器类型变量与真实的
硬件寄存器
是不同的,它是指一个储存数值的变量。如果要在一个过程(initial过程或always过程)里对
变量赋值
,这个变量必须是寄存器类型的。寄存器类型的变量有以下几种:reg(普通寄存器)、integer(整数)、time(时间)、real(实数),其中reg作为一般的寄存器使用最为普遍。利用寄存器变量的数组,还可以对ROM进行建模。
关于选择线网类型还是寄存器类型,需要符合一定的规定。模块的输入端口可以与外界的线网或寄存器类型的变量连接,但是这个模块输出端口只能连接到外界的线网。再简单点,就是在两个模块的信号
连接点
,提供信号的一方可以是寄存器或者线网,但是接受信号的一方只能是线网。此外,在initial、always过程代码块中赋值的变量必须是寄存器类型的,而连续赋值的对象只能是线网类型的变量。
数字的表示
在Verilog里,当一个变量的类型确定,即已经知道它是寄存器类型或者是线网类型,当把具体的数值赋值给它时,需要利用下面所述的数字表示方法。数字表示的基本语法结构为<位宽>'<
数制
的符号><数值>。其中,位宽是与数据大小相等的对应
二进制数
的位数加上占位所用0的位数,这个位数需要使用
十进制
来表示。位宽是可选项,如果没有指明位宽,则默认的数据位宽与仿真器有关(最小32位);数制需要用字母来表示,h对应
十六进制
,d对应十进制,o对应
八进制
,b对应
二进制
。如果没有指明数制,则默认数据为
十进制数
。例如:
如果某个数的最高位为x或z,那么系统会自动使用x或z来填充没有占据的更高位。如果最高位为其他情况,系统会自动使用0来填充没有占据的更高位。
另外,如果需要使用reg表示负数,可以在位宽之前添加一个
负号
,但是需要注意后面的数值为所需负数的二进制
补码
。为了防止出错,可以直接使用整数integer或实数real,二者都是
带符号
数,再利用省略位宽和数制的十进制数来表示负数。
向量
向量形式的数据是Verilog相对C语言较为特殊的一种数据,但是这种数据在硬件描述语言中十分重要。在Verilog中,
标量
的意思是只具有一个
二进制位
的变量,而向量表示具有多个二进制位的变量。如果没有特别指明位宽,系统默认它为标量。
在真实的数字电路,例如将两个四位二进制数相加的进位
加法器
中,我们可以发现,其中一个数是通过四条电线(每条线表示四位中的某一位)连接到加法器上的。我们可以用一个向量来表示这个
多位数
,分别用这个向量的各个分量来表示“四条电线”,即四位中的某一位。这样做的好处是,可以方便地在Verilog代码的其他地方选择其中的一位(位选)或多位(域选)。当然,如果没有进行位选或域选,则这个多位数整体被选择。
向量的表示需要使用
方括号
,方括号里的第一个数字为向量第一个分量的序号,第二个数字为向量最后一个分量的序号,中间用
冒号
隔开。向量分量的序号不像C语言的数组一样必须从0开始,不过为了和数字电路里二进制数高低位的表示方法一致,我们常常让最低位为0(即对于四位二进制数,其最高位为第3位,次高位为第2位,次低位为第1位,最低位为第0位),当然这只是一种习惯。例如,上面提到的四位二进制数用向量表示为:
wire [3:0] input_add; //声明名为input_add的4位wire型向量
wire [4:1] input_add1; //也是4位wire型向量,但是分量序号从4到1
wire [0:3] input_
add2
; //也是4位wire型向量,但是分量序号从0到3
上面的向量声明之后,我们就可以方便地选择其中的某几个分量进行操作。请注意用于域选的方括号的位置在向量名称之后,方括号内的数字为所需的位数。例如我们可以进行以下操作:
input_add [3] = 1'b1; //将1赋值给input_add向量的第三位(最高位)
input_add [1:0] = 2'b01; //将0和1分别赋值给input_add向量的第1、0位(最低两位)
当对向量进行赋值时,如果右边的数值位宽大于左边的变量,则多出来的位被丢弃;如果右边的数值位宽小于左边的变量,则不够的位用0填补。
数组
Verilog中的几种寄存器类型的数据,包括reg、integer、time、real,以及由这几种数据构成的向量,都可以构成数组。
声明数组
时,方括号位于数组名的后面,括号内的第一个数字为第一个元素的序号,第二个数字为最后一个元素的序号,中间用冒号隔开。如果数组是由向量构成的,则数组的其中某个元素是向量。同样,出于习惯考虑,我们一般让数组第一个元素的序号为0,后面元素的序号依次递增。此外,和C语言类似,用户可以声明多维数组。例如:
integer number [0:100]; //声明一个有101个元素的整数数组
number [25] = 1234; //将1234赋值给25号(第26个)元素
reg [7:0] my_input [65535:0]; //声明一个有65536个元素的8位向量寄存器
my_input [97] = 8'b10110101; //将10110101分别赋值给97号(第2个)元素的7至0位
reg my_reg [0:3][0:4]; //声明一个具有20个元素的二维寄存器数组
my_reg [1][2] = 1'b1; //将1赋值给上述
二维数组
的第2行、第3列元素
由于数组和向量的表示都使用了方括号,因此使用时需要注意这个变量或向量的名称在最初被声明为何种类型的数据。上面第三行的例子是65536个8位向量组成的向量数组,它可以描述一个64KB的
存储器
。
表示数组某个元素时,允许使用变量来表示元素的索引(如number [i] = 1234;),但是表示一个向量的一位或者几位时,只允许使用数字来表示位的索引;此外,使用数组时一次只能对一个元素进行操作,而不能像向量那样同时对连续的几个位进行操作,例如my_input [65535][7:4] = 4'b1010;将一个四位二进制数赋值给第65536个元素的高四位。
参数
可以通过parameter关键字声明参数。参数与常数的意义类似,不能够通过赋值运算改变它的数值。在模块进行实例化时,可以能够通过defparam,即参数重载语句块来改变模块实例的参数。另一种方法是在模块实例化时,使用#()将所需的实例参数覆盖模块的
默认参数
。
局部参数
可以用localparam关键字声明,它不能够进行参数重载。
在设计中使用参数,可以使得模块代码在不同条件下被重复利用,例如四位数
全加器
和十六位数全加器可以通过参数实例化同一个通用全加器模块。
字符串
Verilog中的字符串总体来说与C语言中的字符串较为类似,其中每个字符以
ASCII
表示,占8位。字符串存储在位宽足够的向量寄存器中。字符串中的空格、换行等特殊内容,以转义标识符(参见前面提到过的转义标识符)的形式表示。
Verilog HDL
流程控制
为了使设计人员方便地使用
寄存器传输级
描述,Verilog提供了多种流程
控制结构
,包括if、if...else、if...
else if
...else等形式的条件结构,case
分支结构
,for、while循环结构。这些流程控制结构与C语言有着相似的用法。不同的循环结构可能造成不同的逻辑综合结果。
Verilog也提供了一些C语言中没有的流程控制结构以适应硬件描述语言的需要,例如casex、casez两种选择结构,前者可以条件数值中的x、z均作为无关值,后者仅将z作为无关值;此外还提供了forever、repeat两种循环结构,分别用于
无限循环
和指定次数循环。数字电路的逻辑功能描述常常使用到这些流程控制结构,例如,case结构可以清晰地描述一个
数据选择器
。
Verilog HDL
运算符
Verilog的许多运算符和C语言类似,但是有一部分运算符是特有的,例如拼接运算符、缩减运算符、带有无关位的
相等运算符
等。
Verilog的常见运算符隐藏▲
-
按位
-
按位取反(~):1个多位操作数按位取反。例如:a=4'b1011,则~a的结果为4'b0100
-
按位与(&):2个多位操作数按位进行与运算,各位的结果按顺序组成一个新的多位数。例如:a=2'b10,b=2'b11,则a&b的结果为2'b10
-
按位或(|):2个多位操作数按位进行或运算,各位的结果按顺序组成一个新的多位数。例如:a=2'b10,b=2'b11,则a|b的结果为2'b11
-
按位异或(^):2个多位操作数按位进行异或运算,各位的结果按顺序组成一个新的多位数。例如:a=2'b10,b=2'b11,则a^b的结果为2'b01
-
按位同或(~^或^~):2个多位操作数按位进行同或运算,各位的结果按顺序组成一个新的多位数。例如:a=2'b10,b=2'b11,则a~^b的结果为2'b10
-
逻辑
-
逻辑取反(!):对1个操作数进行逻辑取反,如果这个操作数为0,则结果为1;如果这个操作数不为0,则结果为0
-
逻辑与(&&):对2个操作数进行逻辑与,如果二者同为0或同不为0,则结果为1,否则为0。例如:3 && 0的结果为0。
-
逻辑或(||):对2个操作数进行逻辑或,如果二者其中至少有一个不为0,则结果为1,否则为0。例如:3||0的结果为1。
-
缩减
-
缩减与(&):对一个多位操作数进行缩减与操作,先将它最高位与次高位进行与操作,其结果再与第二次高位进行与操作,直到最低位。例如:&(4'b1011)的结果为0
-
缩减与非(~&):对一个多位操作数进行缩减与非操作,先将它最高位与次高位进行与非操作,其结果再与第二次高位进行与非操作,直到最低位。例如:~&(4'b1011)的结果为1
-
缩减或(|):对一个多位操作数进行缩减或操作,先将它最高位与次高位进行或操作,其结果再与第二次高位进行或操作,直到最低位。例如:|(4'b1011)的结果为1
-
缩减或非(~|):对一个多位操作数进行缩减或非操作,先将它最高位与次高位进行或非操作,其结果再与第二次高位进行或非操作,直到最低位。例如:|(4'b1011)的结果为0
-
缩减异或(^):对一个多位操作数进行缩减异或操作,先将它最高位与次高位进行异或操作,其结果再与第二次高位进行异或操作,直到最低位。例如:^(4'b1011)的结果为1
-
缩减同或(~^or^~):对一个多位操作数进行缩减同或操作,先将它最高位与次高位进行同或操作,其结果再与第二次高位进行同或操作,直到最低位。例如:~^(4'b1011)的结果为0
-
算术
-
关系
-
大于(>):比较2个操作数,如果前者大于后者,结果为真
-
小于(<):比较2个操作数,如果前者小于后者,结果为真
-
大于或等于(>=):比较2个操作数,如果前者大于或等于后者,结果为真
-
小于或等于(<=):比较2个操作数,如果前者小于或等于后者,结果为真
-
逻辑相等(==):2个操作数比较,如果各位均相等,结果为真。如果其中任何一个操作数中含有x或z,则结果为x
-
逻辑不等(!=):2个操作数比较,如果各位不完全相等,结果为真。如果其中任何一个操作数中含有x或z,则结果为x
-
case相等(===):2个操作数比较,如果各位(包括x和z位)均相等,结果为真
-
case不等(!==):2个操作数比较,如果各位(包括x和z位)不完全相等,结果为真
-
移位
-
逻辑右移(>>):1个操作数向右移位,产生的空位用0填充
-
逻辑左移(<<):1个操作数向左移位,产生的空位用0填充
-
算术右移(>>>):1个操作数向右移位。如果是无符号数,则产生的空位用0填充;有符号数则用其符号位填充
-
算术左移(<<<):1个操作数向左移位,产生的空位用0填充
-
拼接({,}):2个操作数分别作为高低位进行拼,例如:{2'b10,2'b11}的结果是a'b1011
-
重复({n{m}}):将操作数m重复n次,拼接成一个多位的数。例如:A=2'b01,则{2{A}}的结果是4'b0101
-
条件(?:):根据?前的表达式是否为真,选择执行后面位于:左右两个语句。例如:(a>b)?(a=a-1):(b=b-2),如果a大于b<code>,则将a-1的值赋给a,否则将b-2的值赋给b
Verilog HDL
两种过程
在Verilog中,可以声明两种不同的过程:always过程和initial过程。过程可以是包含时序的
过程描述
,而不包含时序的过程还可以表达组合逻辑。always过程从关键字always开始,可以连续多次运行,当过程的最后一行代码执行完成后,再次从第一行代码开始执行。如果没有使用系统任务$finish,always过程将不断循环执行。initial过程从关键字initial开始,它只能执行一次。
一个模块中可以包含多个过程,各个过程相互之间是并发执行的。不过,过程不能够嵌套使用。如果过程中有多个语句,则需要使用关键字begin、end或
fork
、join将它们组成一个代码块。这两种关键字组合代表着顺序代码块和并行代码块,后面的部分会讲述这两种结构。
例如,利用always过程循环执行的特点,可以为模块提供一个时间脉冲(注意第一个initial过程为时钟的初始化,这个过程只需要进行一次):
initial a = 1'b0;
always #1 a=~a;
end
虽然,always代码块和
while
语句、forever语句都能提供循环功能,但是alway代码块的循环更侧重过程的循环执行,而后二者更侧重代码的循环执行。因此,为了使代码更具条理,过程的循环应当用always语句描述。当然,在实际使用过程中,强制使用其中的某一种在功能实现上都是可行的。
Verilog HDL
寄存器变量的过程赋值
在Verilog中,有两种赋值运算,一种叫做
阻塞赋值
(blocking assignment),其运算符为=;另一种叫做
非阻塞赋值
(non-blocking assignment),其运算符为<=。在顺序代码块中使用阻塞赋值语句,如果这一句没有执行完成,那么后面的语句不会执行;如果在顺序代码块中使用非阻塞赋值,则执行这一句的同时,并不会阻碍下一句代码的执行。而且,如果后一个语句涉及前面一个非阻塞赋值语句中的变量,由于这两个语句“同时”执行,因此后一个语句所用到的是前面一个语句执行前变化的数值。非阻塞赋值是Verilog作为硬件描述语言与普通编程语言的一个重大区别。
always @ (posedge reset or posedge clock)
begin
a <= b;
b <= a;
end
endmodule
上面的例子如果没有使用非阻塞
赋值
,而使用阻塞赋值,那么a和b的数值就不能被交换。a和b在执行完毕后的数值都与之前b的数值相同。在传统的编程语言中,可能需要一个临时的变量,或者使用指针,才能够达到交换两个变量的目的。这里使用了非阻塞赋值,相当于引入了一个隐含的临时变量。第二个非阻塞赋值右边的a是第一句赋值之前的数值,变量交换的目的得以实现。信号边缘敏感的过程
语句块
内常使用非阻塞赋值,使语句块的诸赋值语句同时进行,虽然功能上似乎可以用阻塞赋值实现,但是仿真时会产生不正常的结果。
通常的过程赋值语句往往只有在触发或循环等情况,即赋值语句被执行到时候,才会使左边的
寄存器变量
改变一次;而线网变量的连续赋值则一直“监视”右边表达式的变化,一旦其结果发生变化,立即会左边的线网变量更新为此结果。如果需要对寄存器变量进行过程连续赋值,则可以使用Verilog提供的assign或force关键字“强制地”将
赋值运算符
右边表达式的结果连续不断地施加在左边的寄存器变量上。
Verilog HDL
线网变量的连续赋值
对线网类型变量的连续赋值是
数字电路
数据流
建模的重要步骤,
数字系统
不含时的组合逻辑部分可以使用线网的连续赋值描述。线网不能够像寄存器那样储存当前数值,它需要驱动源提供信号,这种驱动是连续不断的,因此线网变量的赋值称为连续赋值,这与寄存器变量在过程中的单次赋值不同,而且所用的运算符也有区别。在Verilog里,线网连续赋值的关键字为assign,下面为一个例子:
module and
wire out;
wire in1, in2;
assign out = in1 & in2;
在这个例子中,线网变量out在系统运行过程中总为两个输入线网变量in1和in2
逻辑与
的结果。
线网的连续赋值可以在关键字assgin附加延迟信息,例如上面的代码可以改为:
assign #5 out = in1 & in2; //in1和in2逻辑与的结果在5个
时间周期
后才施加在out上
Verilog HDL
时序控制
Verilog能够描述过程中的时序特性,这也是硬件描述语言与普通
计算机编程语言
的重要差别之一。过程的时序控制可以通过三种方式实现:延迟时序控制、事件时序控制以及
电平
敏感时序控制。
过程中的时序控制可以控制代码的执行时间。在Verilog中,除了过程中的时序控制,还可以定义元件、路径的延迟。这些延迟请参见本条目后面有关逻辑门级延迟的部分。
延迟时序控制
在代码中使用关键字#和延迟的时间,就可以通过延迟来进行时序控制。延迟的时间可以是数字、变量或者表达式。延迟时序控制又分为两种:常规延迟和内嵌延迟。
常规延迟在赋值语句的左边,系统执行到这一行代码时,系统先进行延迟,延迟完成后,再计算表达式,并将结果赋值给左边的变量;而内嵌延迟在赋值语句的右边,系统执行到这一行代码时,系统先立即计算表达式,再进行延迟,最后把表达式的结果赋值给左边的变量。在上述两种延迟方式中,设计人员需要注意表达式的
自变量
在延迟过程中
可能发生
变化。常规延迟是先延迟再计算表达式,这时表达式的自变量可能已经发生了变化;而内嵌延迟在延迟前就已经进行了计算,表达式的自变量在延迟过程中发生的变化,对已经计算的表达式结果没有影响,延迟只是指这个结果需要等待一段时间再赋值给左边的变量。
下面的代码片段分别展示了常规延迟和内嵌延迟:
parameter
latency = 8;
initialbegin x = 1;
y = 2;
#5 x = 3; //使用常规延迟:等待5个系统周期后对x赋值
#latency y = 4; //使用变量进行常规延迟,再等待8个系统周期后对y赋值
z = #10 (x+y); //使用内嵌延迟:先用当前时刻的x、y
数值计算
(x+y),再等待10个系统周期后对z赋值
end //z的最终数值为3
在顺序语句块(begin...end)中,由于语句是从上到下、一行一行地执行,而所有常规延迟时间都是实际执行时间相对于这一句本来应该开始执行的时间(也是上一句执行完成之时)的延迟值。因此,在上面的代码示例中,对变量y的赋值时间相对于上一句结束延迟了8个系统周期,而上一句相对系统零时刻已经延迟了5个系统周期,因此对y的赋值发生在第13个系统周期。不过,如果顺序语句块中存在非阻塞赋值,由于这个结构有着类似并行语句块的特点,因此需要特别考虑。
在并行语句块(fork...join)中,由于所有语句都是并发执行的,而所有常规延迟时间都是实际执行时间相对于这一句本来应该开始执行的时间(也是上一句执行完成之时)的延迟值,因此各个常规延迟所指的时间都是相对于系统零时刻。
事件时序控制
事件时序控制的意思是,如果指定的事件发生,则代码被触发执行。它的关键字为@,后面可以加变量或者事件名称。参见下面的例子:
@(clk) x = 1; //当变量clk发生变化,则将1赋值给x
@(posedge clk) y = 2; //在变量clk的
上升沿
,将2赋值给y
z = @(negedge clk) (x+y); //先立即计算表达式(x+y),然后在变量clk
下降沿
,将表达式的结果赋值给z
上面@后面括号里的是常规事件。Verilog允许设计人员通过关键字event和触发符号->定义自己所需要的命名事件触发:
always @(posedge clock)
begin
if(a > 2) ->bigger_than_two; //如果a大于2,则事件bigger_than_two被触发
endalways
@(bigger_than_two) //当bigger_than_two被触发,执行下面的过程
begin
//过程的代码
end
一种经典的用法结构如下,可以理解为“在整个
仿真过程
中,一旦某变量发生变化,就执行某操作”:
always @(a)
begin
x = x+1;
end
另一种用法称为OR事件时序控制,其代码结构为@(a or b)或@(a, b),即当a或b其中任意一个变量发生变化时,代码或代码块才被触发执行。监视的变量如果有3个,则其代码结构变为@(a or b or c)或@(a, b, c),以此类推。如果需要监视的变量很多,则可以使用@*或@(*),它表示对之后代码块中的所有输入变量敏感。此外,敏感列表中除了变量,还可以是前面所提到过的常规事件、命名事件。
电平敏感时序控制
Verilog中还有一种电平敏感时序控制方式,即使用wait(a),当变量a为真,则执行后面的代码块。
Verilog HDL
顺序块与并行块
begin、end组合代表了这个代码块的各行代码是
顺序执行
的,这种代码块称为顺序代码块;后面的fork、join代表了这个代码块的各行代码是并发执行的,这种代码块称为并行代码块。与模块、过程不同,两种代码块是可以嵌套,即顺序代码块中可以包含并行代码块。下面的例子展示了这两种代码块嵌套使用的效果:
initial
fork
x = 1;
y = 2;
begin
z = 3;
w = 4;
end
join
由于这个initial过程使用了关键字fork、join,其中x、y的赋值同时于系统零时刻发生,而z和w由于位于一个顺序代码块中,因此w的赋值在z的赋值后才进行。
在使用并行代码块的时候,有可能引起代码的竞争,例如两个语句对一个变量同时进行赋值。虽然理论上两个语句同时执行,但是具体的情况是必然有一句先执行,但这与顺序语句块的“先后”有本质区别。实际的先后顺序取决于所用的仿真系统。这并不是Verilog硬件描述语言本身的缺陷,并行语句块是一种人为设定的功能,这可以让设计人员更容易地描述某些过程,当然他们必须认真考虑竞争带来的潜在问题。
Verilog HDL
任务和函数
如果某部分代码需要在不同地方多次使用,可以在模块中定义任务或函数。
任务通过关键字task来声明。任务可以有零个或者多个输入变量,但是没有输出
返回值
。调用任务时,将按照任务内指定的方式处理这些变量。由于它相当于一个子过程,因此任务中赋值的变量只能是寄存器类型的,而且只能使用过程赋值语句。任务可以具有时序结构,例如延迟、非阻塞赋值等。任务中可以调用任务和函数。与模块的声明不同,任务的声明没有类似模块端口列表的输入变量列表。尽管如此,调用任务的时候,还是需要在括号里按照任务声明时的顺序罗列输入变量。在某种程度上,任务和C语言中没有返回值的函数有些类似。
函数通过关键字function来声明。任务不仅有输入变量,还有一个返回值作为
输出变量
,这个返回值的名称与函数的名称相同。函数与任务不同,它是一个只有逻辑功能的部分,不能包含时序结构。函数中只能调用函数。Verilog中的函数与C语言中有返回值的函数有些类似。通常将函数放在
赋值运算符
的右边,它的返回值被赋值给左边的变量。
如果任务或函数同时在多个地方被调用,则需要使用automatic关键字声明,这样系统可以为不同地方的调用分配独立的
内存空间
。
Verilog HDL
逻辑门和晶体管的延迟
真实的硬件电路不可避免地都存在延迟现象。在Verilog中,可以对逻辑门、晶体管这些元件的延迟信息进行描述。可以为元件的延迟指定一个时间,则上升、下降、关断的延迟都使用这个时间;也可以按照先后顺序分别指定上升延迟、下降延迟,而关断延迟取二者较小值;当然也可以为上升、下降、关断各指定一个时间。例如,下面的代码为
与门
实例添加了三个
延迟时间
,分别对应上升、下降、关断:
and #(1, 2, 3) my_and (out, in1, in2);
逻辑门和晶体管的延迟属于“惯性延迟”。它的意思是,逻辑门和晶体管获得外部输入之后,延迟指定的时间后,才会将结果呈现在输出端上。在延迟期间,如果输入改变,但是这个信号的持续时间小于指定延迟的时间,则不会影响逻辑门和晶体管的输出;如果这个信号的持续时间大于指定延迟的时间,则之前的结果将不会呈现在输出端,改变输入信号后的结果将经过延迟后将呈现在输出端
Verilog还允许设计人员为每个延迟时间设置
最大值
、
典型值
、
最小值
,在编译阶段可以通过编译代码选择其中一个。
Verilog HDL
概念
设计人员编写的Verilog代码通常是在较高抽象级别的,例如
寄存器传输级
。这一抽象级别包含了对电路信号在
寄存器
之间传输情况的描述。但是逻辑门级的
网表
,即逻辑门的相互连接形式,才最接近真实的硬件电路。这一形式与寄存器传输级的描述,在功能上是等效的。为了给后续硬件制造人员提供这种低抽象级别的描述,需要将高抽象级别的Verilog代码转换为低抽象级别的逻辑
门级网表
。这一过程称为
逻辑综合
(Logic Synthesis)。
在自动化逻辑综合工具出现之前,尽管人们可以用硬件描述语言进行设计,但是还是需要人工进行逻辑综合。例如,如果电路模块只有少数几个输入端,我们可以使用类似
卡诺图
的方法来对
逻辑函数
进行化简。随着电路规模不断增加,人工逻辑综合的容易出错、耗费大量时间的缺点逐渐凸显。同时,在某种
特殊器件
工艺下最优化的综合结果不一定在另一种工艺下还合适,如果需要采用另外的工艺,设计人员需要花费很长时间重新进行逻辑综合。随着自动化逻辑综合工具的出现,硬件描述语言、所需器件工艺信息(工艺库)可以直接被逻辑综合工具读取,通过其内部的自动综合算法,输出符合
设计约束
(通常包括时序、功耗、面积的约束)的逻辑门级网表。借助自动综合工具,设计人员可以将更多的精力放在高抽象级别的硬件描述语言设计。
Verilog HDL
可综合代码
逻辑综合工具不能接受所有的Verilog代码。设计人员需要确保硬件描述语言代码是周期到周期的
寄存器传输级
描述。诸如while的
循环结构
必须通过
信号边缘
的形式(如@(posedge clock))提供终止条件;initial结构可能也不能被转换。如果不指明数字的
位宽
,那么系统可能默认它为一个较大的值(如32位),这就可能产生规模非常庞大的逻辑门级网表,其中一部分是不必要的,这将造成资源的浪费。与未知逻辑x、高阻态z有关的
运算符
不能被转换,例如===、!==此外,条件结构如果只有if而没有对else的情况进行设计,或者
选择结构
缺少默认情况default,很可能产生预期之外的
锁存器
。由于需要使用与工艺相关的逻辑门,因此用户自定义的原语很可能不能被转换。设计人员需要采取良好的
代码风格
,以获得更优化的逻辑综合结果。为了适应符合可重用
设计思想
的
系统芯片
、
IP核
设计,设计人员还应该遵循更严格的编码规范。
不可综合结构
结构类型
|
注
|
initial
|
只用于仿真测试文件(test bench)
|
events
|
Events对于同步测试文件的各个组件比较有意义
|
real
|
Real数据类型不可综合
|
time
|
Time数据类型不可综合
|
force和release
|
Force和release不可综合
|
assign和deassign
|
reg类型的assign和deassign操作不可综合,但是wire类型的assign操作可以综合
|
fork join
|
使用非阻塞赋值可以获得同样效果
|
primitive
|
只有门级的原语(primitives)可综合
|
table
|
用户自定义原语(UDP)及table不可综合
|
#1
|
延迟只用于仿真,综合器一般直接忽略延迟
|
Verilog HDL
编程语言接口
编程语言
接口(Program Language Interface, PLI)提供了通过
C语言函数
对Verilog数据结构进行存储、读取操作的途径。
Verilog编程语言接口的发展先后经过了三代,其中第一代为任务或函数
子程序
,它可以在C程序和Verilog设计之间传递数据;第二代为存取子程序,它可以在用户自定义C程序和Verilog的内部
数据表示
的接口上被使用;第三代为Verilog过程接口,它进一步扩展了前两代编程语言接口的功能。
通过使用编程语言接口,设计人员可以自定义接口的功能,然后通过类似调用系统任务的方式调用这些自定义功能。这样,设计人员可以很大程度地扩展他们能使用的功能,例如监视、激励、调试功能,或者用它来提取
设计信息
、
显示输出
等。
VHDL——VHSIC(Very High Speed Integrated Circuit) HDL,由美国DOD支持开发的HDL,1987年成为
IEEE
1076-1987 标准,后修订为IEEE 1076-1993 标准。
Verilog来自C 语言,易学易用,编程风格灵活、简洁,使用者众多,特别在
ASIC
领域流行;
VHDL 来自
ADA
,语法严谨,比较难学,在欧洲和国内有较多使用者;
两者描述的设计层次有所不同:
VerilogHDL:行为级、RTL 级、门级、开关级,不支持:电路级(
spice
)、版图级(GDSII/CIF)
Verilog HDL就是在用途最广泛的C语言的基础上发展起来的一种硬件描述语言,它是由GDA(Gateway Design Automation)公司的PhilMoorby在1983年末首创的,最初只设计了一个仿真与验证工具,之后又陆续开发了相关的故障模拟与
时序分析
工具。1985年Moorby推出它的第三个商用
仿真器
Verilog-XL,获得了巨大的成功,从而使得Verilog HDL迅速得到推广应用。1989年CADENCE公司收购了GDA公司,使得VerilogHDL成为了该公司的独家专利。1990年CADENCE公司
公开发表
了Verilog HDL,并成立LVI组织以促进Verilog HDL成为IEEE标准,即IEEE Standard 1364-1995.
Verilog HDL的最大特点就是易学易用,如果有C语言的编程经验,可以在一个较短的时间内很快的学习和掌握,因而可以把Verilog HDL内容安排在与
ASIC设计
等
相关课程
内部进行讲授,由于HDL语言本身是专门面向硬件与
系统设计
的,这样的安排可以使学习者同时获得设计
实际电路
的经验。与之相比,VHDL的学习要困难一些。但Verilog HDL较自由的语法,也容易造成初学者犯一些错误,这一点要注意。