模块的结构
verilog的基本设计单元是“模块”(block)。模块是由两部分组成的:① 描述接口的部分 ② 描述逻辑功能的部分。
Verilog结构位于在module 和 endmodule 声明语句之间,每一个Verilog程序包括四个部分:端口定义,I/O声明、内部信号声明和功能定义。
模块端口声明
声明格式: module 模块名(端口1,端口2……)
模块的端口表示的是模块的输入输出口名,是其与其他模块联系端口的标识。
在模块被引用时,在引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来。在引用模块时其端口可以用两种方法连接:
1. 引用时严格按照模块定义的端口顺序进行连接,不用标明原模块定义时规定的端口名。例如:模块名(连接端口1信号名,连接端口2信号名……)
2. 在引用时使用“ . ”符号,标明原模块是对应的时规定的端口名。例如:模块名(.端口1名(连接信号1名),端口2名(连接信号2名)……)
这样的好处在于可以直接定义端口名和被引用的模块端口对应关系,而不用严格按照端口顺序定义,提高程序可读性与移植性。
模块内容
模块内容包括 I/O 说明、内部信号定义和功能定义。
1. I/O 说明的格式
input [信号位宽-1:0] 端口名
output [信号位宽-1:0] 端口名
inout [信号位宽-1] 端口名
I/O声明也可以写在端口说明语句里面:
module module_name(input port1,output port2 ……)
2.内部信号说明
在模块内用到的与端口有关的 wire、reg 类型变量的声明。
reg [width-1:0] 变量1,变量2……
wire [width-1:0] 变量1,变量2……
3. 功能定义
用来定义模块中最重要的,逻辑功能定义的部分。有三种办法定义逻辑:
(1)用“assign” 声明语句,格式:assign 逻辑表达式,例如
assign c = a|b
(2)用实例元件,格式:原件名 (延时时间(可选)) 实例名(输出端口、输入端口),例如
and #2 subject1(c,a,b)
实例名称要唯一,不能重名
(3)用 always 块 。always 块可以描述组合逻辑和时序逻辑,可以编写出很复杂的逻辑系统,后续再详细讲。
一般来说,assign语句在描述组合逻辑中最常用。
要点记录
- Verilog模块中所有的过程块(如always,initial块),连续赋值语句,实例引用都是并行的,只有过程块内部是顺序运行的。它们表示的是一种通过变量名相互连接的关系,在同一模块里过程块,连续赋值语句,实例引用三者出现的先后顺序没有关系。
- 只有连续赋值语句 assign 和实例引用语句可以独立于过程块而存在于模块的功能定义部分。
数据类型、常量变量
数据类型是用来表示数字电路硬件中数据储存和传送元素的。
常量是程序过程中值不能改变的量。
1. 数字
整数(整型常量)表达方式有三种:
1) <位宽>’<进制><数字> 如 8’d10101010 注意进制前面有个撇 ’
2)<进制><数字> 数字的采用使用默认位宽,具体由机器系统决定
3)<数字> 默认采用10进制
进制的标示: 二进制(b、B)十进制(d、D) 八进制(o、O) 十六进制(h、H)
另外 x 和 z 也是常用的数字常量, 在数字电路中,x表示不定值,z(也可写作?)表示高阻值。
负数的表示:在整数定义最前面加 “-” 号
下划线: 只能用在整数定义的数字部分中间,用来分隔数字提高可读性。
常量不说明位数的时候,默认值是32位,每个字母用8 位ASCII值表示
2.参数型(parameter)
parameter 用来定义一个标识符来代表一个常量,成为符号常量,类似于C语言里面的define,在工程中常用于定义延时时间和变量宽度
格式: parameter 参数名1=表达式,参数名2=表达式
3.变量
网络数据类型表示结构实体(如:门)之间的物理连接,网络类型的变量不能储存值,而且它必须受到驱动器(例如门或连续赋值语句,assign)的驱动。如果没有驱动器连接到网络类型的变量上,则该变量就是高阻态的,值为z。
常用的网络数据类型是wire 和 tri型,两种变量都用于连接器件单元,具有相同的语法格式和功能。wire型变量通常用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据。
1. wire型
wire型数据常用于以assign 关键字指定的组合逻辑信号。Verilog 程序模块中的输入、输出信号类型默认时候自动定义为wire型。
wire型信号可以用作任何方程式的输入,也可以用作assign语句或实例单元的输出。
定义格式:
wire [n-1:0] 数据名1,数据名2,数据名3……
2. reg 型
reg型数据用来表示 “always”模块内的指定信号,常常表示触发器。“always”模块内被赋值的每一个变量都必须定义为reg型。
reg型数据定义的格式:
reg[n-1:0] 数据名1,数据名2……
reg类型的数据默认初始值为不定值x,可正可负。但是当一个reg型数据是一个表达式中的操作数,它的值被当做无符号值,即正值。例如一个4为寄存器开始被附为-1,用在表达式计算的操作数中会被当做+15.
注意: reg型只表示被定义的信号将用在“always”模块内,但不是说reg型的信号一定是寄存器或触发器的输出,只是常常是这样。
3.memory型
在Verilog语言中没有多维数组的存在,memory型数据是通过扩展reg型数据的地址范围来生成的,定义格式如下:
reg[n-1:0] 存储器名[m-1:0]
在这里,reg[n-1:0] 定义存储器中每一个存储单元的大小,即这个存储单元是一个n位寄存器,存储器名[m-1:0]定义该存储器中有m个这样的寄存器。
尽管memory型数据和reg型数据定义方式相似,但是注意他们的不同:如一个由n个1位寄存器构成的存储器组是不同于一个n位寄存器的,可以向n位寄存器直接赋值,但是无法向一个完整的存储器直接赋值,需要写明存储器中的具体地址赋值才行。(类似于c语言中给数组赋值要表明数组中的哪一个)
运算符和表达式
按照运算符的功能,可以分成以下几类:
算术运算符 | 和其他语言一样, 有: + ,-, *, / ,% |
赋值运算符 | =,<= |
关系运算符 | > ,<,>=,<= |
逻辑运算符 | &&(与),||(或) ,!(非) |
条件运算符 | ?: |
位运算符 | ~ ,|,^(按位异或xor), &, ^~ (按位同或)。 在硬件电路中信号有四种状态:1、0、x、z |
移位运算符 | << ,>> 如:a>>n , a为操作数,n为要移位的位数。 |
拼接运算符 | {} 用这个运算符可以把两个或者多个信号的某几位拼接起来继续操作,使用方式:{信号a某几位,信号b某几位,信号c某几位……},也可以嵌套、重复,例如{c,3{a,b}}表示{c,a,b,a,b,a,b} |
运算符优先级:
这里对赋值语句和块语句多介绍一下:
在Verilog HDL 语言中,信号有两种赋值方式:
非阻塞赋值方式(Non_blocking),如 b<=a
- 在语句块中,上面语句所附的变量值不能马上为下面的语句使用
- 在整个块结束之后才能完成这条语句的赋值操作
- 在编写可综合的时序逻辑模块的时候,这是最常用的方法。
阻塞赋值方式(blocking),如 b=a
- 赋值语句结束之后,块才结束
- b的值在赋值语句执行完成之后立刻就可以改变的
在时序电路电路中,可能会出现意想不到的结果,慎用
对于块语句,一般有两种:begin_end 标识顺序块, fork_join标识并行块。
顺序块内的语句就和其他高级语言差不多,按照顺序一句一句执行,执行完最后一句话退出块。
并行块内的语句是同时执行的,每条语句的延时时间是相对于程序流程控制进入到块内的仿真时间的,而延时时间是用来给赋值语句提供执行时序的,时序排在最后的语句执行完,或者执行一个 disable 语句,程序就会跳出块。
注意,在Verilog语句里面所有的变量都是静态的,所有的变量都只有唯一的存储地址,进入和跳出块不影响变量内的值。
补充小结
- 在verilog里面所有的过程块(initial块、always块等等)、连续赋值语句、实例引用都是并行的,他们之间通过变量名相互连接。
- 只有连续赋值语句(assign引出的语句)和实例引用语句(用已定义的模块名引出的语句),可以独立于过程块而存在于模块的功能定义部分。
- always模块内的被赋值的每一个变量都必须定义为reg。