Verilog
模块的基本构成要素有三大部分: 端口信息、输入/输出说明、逻辑功能描述。这里将其看成一种计算机语言就行了,没有那么网上说的什么花里胡哨的。计算机语言就是用来实现功能的,我们日常接触最多的就是c++
、python
这种,做的一般是计算仿真,而这个verilog
做的是与硬件相关的,也就是直接控制信号的与或非这种。
而学习一门计算机语言最快的方式就是直接上手干,话不多说看个例子:
module block1(a, b, c, d, e);
input a, b, c;
output d, e; .
assign d=a|(b&~c);
assign e=(b&~c );
endmodule
可以看到verilog
模块由两部分组成:端口信息和内部功能。上述代码所述功能是输入三个信号a
,b
,c
(这里没有指定位宽,说明这里都是一位位宽信号); 输出d
,e
也是没有指定位宽,表示一根线输出。中间assign
赋值语句就是内部功能,表示输入与输出之间的关系。d
信号的输出等于a
或上b
与c
的非,e
同理。
verilog
模块的结构在module
和enmodule
关键词之间,主要有四个主要部分组成。上述是verilog的一个基本的程序模块,如果我们想要设计一个verilog模块的话,其大体组成部分如下图所示:
module
模块名(端口1,端口2,端口3,…);。 任何一个模块都是以module
开头的。在端口定义的圆括弧中,是设计电路模块与外界联系的全部输入输出端口信号或引脚,它是设计实体对外的一个通信界面,是外界可以看到的部分(不包含电源和接地端),多个端口名之间用“,”分隔," ; "结尾。
I/O
说明、信号类型声明和功能描述。模块的I/O
说明用来声明模块端口定义中各端口数据流动方向包括输入(input
) 、输出(output
) 和双向(inout
) 。I/O
说明格式如下:input ina , inb,cin;
output sum, cont;
这里写成一行的话位宽都是一样的。所以有时候需要指定位宽的时候我们对每个输入、输出单独写。
信号类型声明用来说明设计电路的功能描述中,所用的信号的数据类型以及函数声明。信号的数据类型主要有连线(wire
) 、寄存器(reg
)、整型(integer
) 、实型(real
) 和时间(time
)等类型。在通常设计的时候用的最多的就是连线型(wire
)和整型(reg
),有时候也会用到整型。
功能描述是Verilog HDL
程序设计中最主要的部分,用来描述设计模块的内部结构和模块端口间的逻辑关系,在电路上相当于器件的内部电路结构。
功能描述可以用assign
语句、元件例化(instantiate
) 、always
块语句、initial
块语句等方法来实现,通常把确定这些设计模块描述的方法称为建模。
(1) 用assign
语句建模的方法很简单,只需要在“assign”后面再加一个表达式即可。assign
语句一般适合对组合逻辑进行赋值,称为连续赋值方式。
module adder1 (sum, cout, ina, inb, cin) ; //模块端口定义
input ina, inb, cin;
output sum,cout; //I/O声 明
assign {cout, sum} = ina+inb+cin; //功能描述语句
endmodule //endmodule后不加分号
默认的数据类型为wire
(连线)型,{ }
为拼接运算符,是将cout
、sum
这样两个1
位操作数拼接为一个2
位操作数。
(2) 元件例化方式建模是利用Verilog HDL
提供的元件库实现的。.例如,用与门例化元件定义一个3
输入端与门可以写为and myand3(y,a,b,c)
; and
为关键字,名称为myand3
。
(3) always
块语句可以产生各种逻辑,常用于时序逻辑的功能描述。一个程序设计模块中,可以包含一个或多个always
语句。程序运行中,在某种条件满足时,就重复执行一遍always
结构中的语句。
module cnt8(out,cout,data,load,cin,clk,clr);
input [7:0] data; // 输入数据八个比特
input load, cin, clk, clr; // 一个比特
output| 7:0] out;
output cout;
reg [7:0] out; //寄存器型参量,具有寄存功能
always @(posedge clk) //时钟 上升沿,每次上升沿,执行always语句
begin
if (clr) out < =8'b0;
else if (load) out <= data;
else out <= out+8'b1;
end
assign cout = &out & cin; //&out”-与缩减运算式
endmodule
输出out
八位、cout
输出进位一位、data
八位,load
置数信号一位,cin
输入进位,clk
时钟,一位,clr
复位。这里输出信号为out
,之后又将其定义为寄存器信号。
always@
括号中的是条件,posedge clk
表示的时钟上升沿。也就是时钟上升沿一进来进开始执行内部语句。如果clr
复位信号为高电平,输出out
就被赋值为8
比特0
(这里的<=
就是赋值语句)。当out
全为1
且进位cin
也为1
的时候cout
才为1
。
由于这里out
信号在always
块中赋值,所以必须定义为寄存器型变量。也就是always
块中的变量必须定义为寄存器类型。assign
赋值的变量必须是wire
型。
(4) initial
块语句与always
语句类似,不过在程序中它只执行1
次就结束了。
Verilog HDL
的常数包括数字、未知X和高阻z三种。数字可以用二进制、十进制、八进制和十六进制等4种不同数制来表示,完整的数字格式为<位宽>'<进制符号><数字>
其中,位宽表示数字对应的二进制数的位数宽度; 进制符号包括b
或B
(表示二进制数),d
或D
(表示十进制数),h
或H
(表示十六进制数),o
或O
(表示八进制数)。
字符串是用双引号括起来的可打印字符序列,它必须包含在同一行中。
标识符是用户编程时为常量、变量、模块、寄存器、端口、连线、示例和begin-end
块等元素定义的名称。标识符可以是字母、数字和下划线等符号组成的任意序列。
关键字是Verilog HDL
预先定义的单词,它们在程序中有不同的使用目的。所有关键字都用小写。
操作符也称为运算符,是Verilog HDL
预定义的函数名字,这些函数对被操作的对象(即操作数)进行规定的运算,得到一个结果。
操作符通常由1~3个字符组成,例如,“+”表示加操作,“= =” (两个=字符)表示逻辑等操作,“===”(3个=字符)表示全等操作。
有些操作符的操作数只有1个,称为单目操作;有些操作符的操作数有2个,称为双目操作;有些操作符的操作数有3
个,称为三目操作。
parameter常量名1 =表达式,常量名2 =表达式,...,
常量名n=表达式;
parameter
是常量定义关键字,常量名是用户定义的标识符,表达式是为常量赋的值。
Verilog HDL
中,变量分为网络型(nets type
)和寄存器型(register type
)两种。 nets
型变量是输出值始终根据输入变化而更新的变量,它一般用来定义硬件电路中的各种物理连线。常用的是wire
类型。
register
型变量是一种数值容器,不仅可以容纳当前值,也可以保持历史值,这一属性与触发器或寄存器的记忆功能有很好的对应关系。
register
型变 量与wire
型变量的根本区别: register
型变量需要被明确地赋值,并且在被重新赋值前–直保持原值。.
register型变量是在always
、initial
等 过程语句中定义,并通过过程语句赋值。
integer
、real
和time
等3种 寄存器型变量都是纯数学的抽象描述,不对应任何具体的硬件电路,但它们可以描述与模拟有关的许算。例如,可以利用time型变量控制经过特定的时间后关闭显示等。
reg
型变量是数字系统中存储设备的抽象,常用于具体的硬件描述,因此是最常用的寄存器型变量。reg
型变量定义的关键字是reg
,定义格式如下:
reg [位宽]变量1, 变量2,...,变量n;
用reg
定义的变量有-一个范围选项( 即位宽),默认的位宽是1
。位宽为1
位的变量称为标量,位宽超过1
位的变量称为向量。
向量定义时需要位宽选项:
reg[7: 0] data; //定义1个8位寄存器型.变量, 最高有效位是7,最低有效位是0
reg[0: 7] data; //I定义1个8位寄存器型变量,最高有效位是0,最低有效位是7
reg
型数组变量即为memory
(存储器)型变量。mymemory[1023:0];
上述语句定义了一个1024
个字存储器变量mymemory
,每个字的字长为8
位。在表达式中可以用下面的语句来使用存储器:
mymemory[7]=75; // 存储器mymemory的第7个字被赋值75。
基本逻辑门关键字是Verilog HDL
预定义的逻辑门,包括and
、or
、not
、xor
、nand
、nor
等。
过程赋值语句出现在initial和always块语句中,赋值符号是“=”,格式为
赋值变量=表达式;
在过程赋值语句中,赋值号“=”左边的赋值变量必须是reg (寄存器)型变量,其值在该语句结束即可得到。如果一个块语句中包含若干条过程赋值语句,那么这些过程赋值语句是按照语句编写的顺序由上至下一条一条地执行,前面的语句没有完成,后面的语句就不能执行,就象被阻塞了一样。 因此,过程赋值语句也称为阻塞赋值语句。
非阻塞赋值语句也是出现在initial
和always
块语句中,赋值符号是“<=
”,格式为
赋值变量<=表达式;
在非阻塞赋值语句中,赋值号“<=”左边的赋值变量也必须是reg型变量
,其值不象在过程赋值语句那样,语句结束时即刻得到,而在该块语句结束才可得到。
循环语句包含for
语句、repeat
语句、while
语句和forever
语句4
种。
与C
语言相比,verilog
语句是并行的,比如两个always
块都是时钟沿出发的,那么它们就是并行的。
特殊符号“#”常用来表示延迟。使用'define
编译引导能提供简单的文本替代功能。
`define <宏名> <宏文本>
举例如下:
`define on 1'b1 // 0比特1定义on
`define off 1'b0 // 0比特0定义off
`define and delay #3 // 定义延时
使用'include
编译引导在编译的时候能把其指定的整个文件包括进来一起处理。如’include “global.v"
等。
可以将模块的实例通过端口连接起来构成一个大的系统或元件。每个实例都有自己的名字。实例名是每个对象唯一的标记,通过这个标记可以查看每个实例的内部。实例中端口的次序与模块的定义的次序相同。模块实例化与调用程序不同。每个实例都是模块的一个完全拷贝,相互独立、并行。
在调用模块时,可以用顺序连接和按名连接把模块定义的端口与外部信号连接起来。
顺序连接:需要连接的信号需要与模块声明的端口列表一致;
按名连接:端口和外部信号按名字连接在一起。当设计大规模系统时,端口太多,记住端口顺序不大可能,可以采用按名连接方法。
不需要连接的端口直接忽略掉即可: