好的,接着上篇文章Verilog小叙(一)
常量
Verilog HDL中有三种常量:
整型、实型、字符串型。
下划线符号“_”可以随意用在整数或实数中,它们就数量本身没有意义。它们能用来提高易读性;唯一的限制是下划线符号不能用作为首字符。
下面主要介绍整型和字符串型。
整型
整型数可以按如下两种方式书写:
- 简单的十进制数格式
- 基数格式
简单的十进制格式
这种形式的整数定义为带有一个可选的“+”(一元)或“-”(一元)操作符的数字序列。
下面是这种简易十进制形式整数的例子。
32 十进制数32
-15 十进制数-15
基数表示法
这种形式的整数格式为:
[size ] ‘base value
size 定义以位计的常量的位长;
base 为o或O(表示八进制),b或B(表示二进制),d或D(表示十进制),h 或H (表示十六进制)之一;
value 是基于base 的值的数字序列。值x和z以及十六进制中的a 到f 不区分大小写。
下面是一些具体实例:1
2
3
4
5
6
7
85 'o37 //5位八进制数(二进制11111)
4 'd2 //4位十进制数(二进制0011)
4 'b1x_01 //4位二进制数
7 'hx //7位x(扩展的x), 即xxxxxxx
4 'hz //4位z(扩展的z) , 即zzzz
4 'd-4 //非法:数值不能为负
3' b 001 //非法:和基数b之间不允许出现空格
(2+3)'b10 //非法:位长不能够为表达式
注意,x(或z)在十六进制值中代表4位x(或z),在八进制中代表3位x(或z ),在二进制中代表1位x(或z)。
基数格式计数形式的数通常为无符号数。这种形式的整型数的长度定义是可选的。如果没有定义一个整数型的长度,数的长度为相应值中定义的位数。下面是两个例子:
‘o 7219 3位八进制数
‘h AF8 4位十六进制数
如果定义的长度比为常量指定的长度长,通常在左边填0补位。但是如果数最左边一位为x 或z,就相应地用x 或z 在左边补位。例如:10'b10
左边添0占位,000000001010'bx0x1
左边添x占位, xxxxxxx0x1
如果长度定义得更小,那么最左边的位相应地被截断。例如:3 ' b1001_0011
与3'b011
相等5'H0FFF
与5'H1F
相等
字符串型
字符串是双引号内的字符序列。字符串不能分成多行书写。
例如:"INTERNAL ERROR"
" REACHED->HERE "
用8位ASCII值表示的字符可看作是无符号整数。因此字符串是8位ASCII 值的序列。为存储字符串“INTERNAL ERROR”
,变量需要8x14位。
reg [1:8*14] Message;
Message = "INTERNAL ERROR"
数据类型
Verilog HDL 主要包括两种数据类型
线网类型(net type)
寄存器类型(reg type)
线网类型
- wire 和 tri 定义
线网类型主要有wire 和tri两种。线网类型用于对结构化器件之间的物理连线的建模.如器件的管脚,内部器件如与门的输出等。以上面的加法器为例,输入信号A,B是由外部器件所驱动,异或门X1的输出S1是与异或门X2输入脚相连的物理连接线,它由异或门X1所驱动。简单地讲,S1由X1驱动。
由于线网类型代表的是物理连接线,因此它不存贮逻辑值。必须由器件所驱动。通常由assign进行赋值。如 assign A = B ^ C;
当一个wire 类型的信号没有被驱动时,缺省值为Z(高阻)。
信号没有定义数据类型时,缺省为 wire 类型。
如上面一位全加器的端口信号 A,B,SUM等,没有定义类型,故缺省为wire 线网类型。
两者区别
tri主要用于定义三态的线网。
寄存器类型
定义
reg 是最常用的寄存器类型,寄存器类型通常用于对存储单元的描述,如D型触发器、ROM等。存储器类型的信号当在某种触发机制下分配了一个值,在分配下一个值之时保留原值。
reg 类型定义语法如下:1
2
3
4
5
6
7
8
9
10
11reg [msb: lsb] reg1, reg2, . . . reg N;
msb 和lsb 定义了范围,并且均为常数值表达式。范围定义是可选的;如果没有定义范围,缺省值为1 位寄存器。例如:
reg [3:0] Sat; // Sat 为4 位寄存器。
reg Cnt; //1 位寄存器。
reg [1:32] Kisp, Pisp, Lisp ;
//寄存器类型的值可取负数,但若该变量用于表达式的运算中,则按无符号类型处理,如:
reg A ;
.....
A = -1;
....
则A的二进制为1111,在运算中,A总按 无符号数15来看待。寄存器类型的存储单元建模举例
用寄存器类型来构建两位的D触发器如下:1
2
3
4
5
6
7
8
9
10
11
12reg [1:0] dout ;
.....
always@(posedge clk)
dout <= din;
....
用寄存器数组类型来建立存储器的模型,如对2个8位的RAM建模如下:
reg [7:0] mem[0:1] ;
对存储单元的赋值必须一个个第赋值,如上2个8位的RAM的赋值必须用两条赋值语句:
.....
mem[0] = ’ h 55;
mem[1] = ’ haa;
....书写规范建议
对数组类型,请按降序方式,如[7:0] ;
Tips:
reg的类型不一定是寄存器,只有带有时钟的always块才能是寄存器,不带时钟的always块是组合逻辑。
运算符和表达式
Verilog HDL中的操作符可以分为下述类型:
算术操作符
关系操作符
相等操作符
逻辑操作符
按位操作符
归约操作符
移位操作符
条件操作符
连接和复制操作符
下表显示了所有操作符的优先级和名称。操作符从最高优先级(顶行)到最低优先级(底行)排列。同一行中的操作符优先级相同。
算术运算符
在常用的算术运算符主要是 :
加法(二元运算符):“+”;
减法 (二元运算符):“-”;
乘法(二元运算符):“*”;
在算术运算符的使用中,注意如下两个问题:
- 算术操作结果的位数长度
算术表达式结果的长度由最长的操作数决定。在赋值语句下,算术操作结果的长度由操作符左端目标长度决定。考虑如下实例:
1 | reg [3:0] arc, bar, crt; |
第一个加的结果长度由bar,crt 和 arc 长度决定,长度为4位。
第二个加法操作的长度同样由frx 的长度决定(frx 、bat 和crt 中的最长长度),长度为6位。
在第一个赋值中,加法操作的溢出部分被丢弃;而在第二个赋值中,任何溢出的位存储在结
果位frx [ 4 ]中。
在较大的表达式中,中间结果的长度如何确定?在Verilog HDL 中定义了如下规则:表达式中的所有中间结果应取最大操作数的长度(赋值时,此规则也包括左端目标)。考虑另一个实例:
wire [4:1] box, drt;
wire [5:1] cfg;
wire [6:1] peg;
wire [8:1] adt;
. . .
assign adt = (box + cfg) + (drt + peg) ;
表达式右端的操作数最长为6 ,但是将左端包含在内时,最大长度为8 。所以所有的加操作使用8 位进行。例如:box 和cfg 相加的结果长度为8 位。
关系运算符有:
?>(大于)
?<(小于)
?>=(不小于)
?<=(不大于)
= = (逻辑相等)
= (逻辑不等)
关系操作符的结果为真(1 )或假(0 )。如果操作数中有一位为X 或Z ,那么结果为X 。
例:
23 > 45
结果为假(0 ),而:
52 < 8’hxFF
结果为x 。
如果操作数长度不同,长度较短的操作数在最重要的位方向(左方)添0 补齐。例如:
‘b1000 > = ‘b01110
等价于:
‘b01000 > = ‘b01110
结果为假(0 )。
在逻辑相等与不等的比较中,只要一个操作数含有x 或z,比较结果为未知 (x),如:
假定:
Data = ‘b11x0;
Addr = ‘b11x0;
那么:
Data = = Addr 比较结果不定,也就是说值为x 。
逻辑运算符
逻辑运算符有:
&& (逻辑与)
|| (逻辑或)
!(逻辑非)
用法为:(表达式1) 逻辑运算符 (表达式2) ….
这些运算符在逻辑值0(假) 或1(真) 上操作。逻辑运算的结果为0 或1 。例如, 假定:
crd = ‘b0; //0 为假
dgs = ‘b1; //1 为真
那么:
crd && dgs 结果为0 (假)
crd || dgs 结果为1 (真)
!dgs 结果为0 (假)
按位逻辑运算符
按位运算符有:
?~(一元非):(相当于非门运算)
?&(二元与):(相当于与门运算)
?|(二元或): (相当于或门运算)
?^(二元异或):(相当于异或门运算)
?~ ^, ^ ~(二元异或非即同或):(相当于同或门运算)
这些操作符在输入操作数的对应位上按位操作,并产生向量结果。
例如,假定,
A = ‘b0110;
B = ‘b0100;
那么:
A | B 结果为0110
A & B 结果为0100
如果操作数长度不相等, 长度较小的操作数在最左侧添0补位。例如,
‘b0110 ^ ‘b10000
与如下式的操作相同:
‘b00110 ^ ‘b10000
结果为’b10110。
条件运算符
条件操作符根据条件表达式的值选择表达式,形式如下:
cond_expr ? expr1 : expr2
如果cond_expr 为真(即值为1 ),选择expr1 ;如果cond_expr 为假(值为0 ),选择expr2 。如果cond_expr 为x 或z ,结果将是按以下逻辑expr1 和expr2 按位操作的值: 0 与0 得0 ,1 与1 得1 ,其余情况为x 。
如下所示:
wire [2:0] student = marks > 18 ? grade_a : grade_c;
计算表达式marks > 18; 如果真,grade_a 赋值为student;如果marks < =18, grade_c 赋值为student 。
连接运算符
连接操作是将小表达式合并形成大表达式的操作。形式如下:
{expr1, expr2, . . .,exprN}
实例如下所示:
wire [7:0] Dbus;
assign Dbus [7:4] = {Dbus [0], Dbus [1], Dbus[2], Dbus[ 3 ]};
//以反转的顺序将低端4 位赋给高端4 位。
assign Dbus = {Dbus [3:0], Dbus [7:4]};
//高4 位与低4 位交换。
由于非定长常数的长度未知, 不允许连接非定长常数。例如,下列式子非法:
{Dbus,5} //不允许连接操作非定长常数。
条件语句
if 语句的语法如下:1
2
3
4
5
6if(condition_1)
procedural_statement_1
{else if(condition_2)
procedural_statement_2}
{else
procedural_statement_3}
如果对condition_1 求值的结果为个非1,那么procedural_statement_1 被执,如果condition_1 的值为0 、x 或z ,那么procedural_statement_1 不执行。如果存在一个else 分支,那么这个分支被执行。
1 | if (sum < 60) begin |
若为if - if 语句,请使用块语句 begin — end :
1 | if(clk == 1) begin |
对if语句,除非在时序逻辑中,if 语句需要有else语句。若没有缺省语句,设计将产生一个锁存器,锁存器在asic设计中有诸多的弊端。
如下一例:
if (t == 1)
q = d;
没有else 语句,当t为1(真)时,d 被赋值给q,当t为0(假)时,因为没有else 语句,电路保持 q 以前的值,这就形成一个锁存器。
case语句
case 语句是一个多路条件分支形式,其语法如下:1
2
3
4
5
6case(case_expr)
case_item_expr{ ,case_item_expr} :procedural_statement
. . .
. . .
[default:procedural_statement]
endcase
case 语句首先对条件表达式case_expr 求值,然后依次对各分支项求值并进行比较,第一个与条件表达式值相匹配的分支中的语句被执行。可以在1 个分支中定义多个分支项;这些值不需要互斥。缺省分支覆盖所有没有被分支表达式覆盖的其他分支。
1 | case (HEX) |
case 的缺省项必须写,防止产生锁存器。
只有组合逻辑才可能产生锁存器,时序逻辑不会产生锁存器。
结构建模
模块定义结构
我们已经了解到,一个设计实际上是由一个个module 组成的。一个模块module 的结构如下:
module module_name (
port_list
) ;
Declarations_and_Statements
endmodule
在结构建模中,描述语句主要是实例化语句,包括对Verilog HDL 内置门如与门(and)异或门(xor)等的例化,如全加器的xor 门的调用;及对其他器件的调用,这里的器件包括FPGA厂家提供的一些宏单元以及设计者已经有的设计。
在实际应用中,实例化语句主用指后者,对内置门建议不采纳,而用数据流或行为级方式对基本门电路的描述。
端口队列port_list 列出了该模块通过哪些端口与外部模块通信,在Verilog 2001语法中还包括输入输出、wire/reg类型、位宽定义。
模块端口
模块的端口可以是输入端口、输出端口或双向端口。缺省的端口类型为线网类型(即wire 类型)。输入端口默认为wire类型,不需要定义,输出或双向端口能够声明为wire/reg 型,使用reg必须显式声明,使用wire也强烈建议显式声明。
Verilog 2001语法中的输入输出包括端口名、输入输出、wire/reg类型、位宽定义,极大的减少了端口声明占用的代码行数。当前已经非常普及。
例子:
1 | module LED ( |
该例子包括输入、输出、姓名名称、位宽、输出的信号类型。
实例化语句
一个模块能够在另外一个模块中被引用,这样就建立了描述的层次。模块实例化语句形式如下:
module_name instance_name(port_associations);
信号端口可以通过位置或名称关联;
但是关联方式不能够混合使用。端口关联形式如下:
PortName //通过位置。
.PortName (port_expr) //通过名称。
建议使用名称进行关联。
1 | // instantance RR_CORE |
port_expr 可以是以下的任何类型:
标识符(reg 或net )如 .C(T3),T3为wire型标识符。
位选择 ,如 .C(D[0]),C端口接到D信号的第0bit 位。
部分选择 ,如 .Bus (Din[5:4])。
上述类型的合并,如 .Addr({ A1,A2[1:0]}。
表达式(只适用于输入端口),如 .A (wire Zire = 0 )。
建议:
在例化的端口映射中请采用名字关联,这样,当被调用的模块管脚改变时不易出错。
悬空端口的处理
在我们的实例化中,可能有些管脚没用到,可在映射中采用空白处理,如:
1 | DFF U_DFF_0 ( |
对输入管脚悬空的,则该管脚输入为高阻 Z,输出管脚被悬空的,该输出管脚废弃不用。
建议:
大规模电路设计的时候,悬空的端口最好使用XX_nc(wire类型)进行显式声明,提高检视代码的效率。
不同端口长度的处理
当端口和局部端口表达式的长度不同时,端口通过无符号数的右对齐或截断方式进行匹配。
1 | module Child (Pba, Ppy) ; |
在对Child 模块的实例中,Bdl[2]连接到Pba[ 0 ],Bdl[1] 连接到Pba[ 1 ],余下的输入端口Pba[5]、Pba[4]、Pba[2]和Pba[3]悬空,因此为高阻态z 。与之相似,Mpr[6]连接到Ppy[0],Mpr[5]连接到Ppy[1],Mpr[4] 连接到Ppy[2]。
结构化建模具体实例
对一个数字系统的设计,我们采用的是自顶向下的设计方式。可把系统划分成几个功能模块,每个功能模块再划分成下一层的子模块。每个模块的设计对应一个module,一个module 设计成一个verilog HDL 程序文件。因此,对一个系统的顶层模块,我们采用结构化的设计,即顶层模块分别调用了各个功能模块。下面以一个实例(一个频率计数器系统)说明如何用HDL进行系统设计。
在该系统中,我们划分成如下三个部分:2输入与门模块,LED显示模块,4位计数器模块。系统的层次描述如下:
顶层模块CNT_BCD,文件名CNT_BCD.v,该模块调用了低层模块 AND2、CNT_4b和HEX2LED 。
顶层模块CNT_BCD对应的设计文件 CNT_BCD.v 内容为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29module CNT_BCD (
// ------------ Port declarations --------- //
input CLK,
input GATE,
input RESET,
output wire [3:0] BCD_A,
output wire [3:0] BCD_B,
output wire [3:0] BCD_C,
output wire [3:0] BCD_D
) ;
// ----------- Signal declarations -------- //
wire NET104;
wire NET124;
wire NET132;
wire NET80;
// -------- Component instantiations -------//
CNT_4b U0(
.CLK(CLK),
.ENABLE(GATE),
.FULL(NET80),
.Q(BCD_A),
.RESET(RESET)
);
AND2 U6(
.A0(NET104),
.A1(NET124),
.Y(NET132)
);
endmodule
Tips:
这里的AND2是为了举例说明,在实际设计中,对门级不要重新设计成一个模块,同时对涉及保留字的(不管大小写)相类似的标识符最好不用。