模块的基本结构
module 模块名(端口列表); // 模块声明
// 端口定义
input [数据类型] [位宽] 输入端口列表;
output [数据类型] [位宽] 输出端口列表;
inout [数据类型] [位宽] 双向端口列表;
// 数据类型定义
wire [位宽] 线网名,线网名,…;
reg [位宽] 变量名,变量名,…;
//函数与任务声明
function [位宽] 函数名; ...; endfuction
task 任务名; ...; endtask
// 功能描述
assign 线网名=函数表达式; // 数据流描述方式
always/initial过程语句; // 行为描述方式
例化模块名 实例名(端口关联列表); // 结构描述方式
endmodule
1. 模块声明
模块声明以关键词module开始,以endmodule结束。模块声明由模块名和端口列表两部分组成。 4选一数据选择器的模块声明参考如下:
module MUX4to1(d0,d1,d2,d3,a,y); …… endmodule
其中指定模块名为MUX4to1,对外共有四路数据d0、d1、d2和d3,两位地址a和输出y共6组端口。
模块的所有代码书写于关键词module和endmodule之间,包括端口定义、数据类型定义、函数和任务声明以及功能描述部分。
2. 端口定义
端口类型定义用于指定模块对外端口的数据流动方向以及数据类型。 具体的语法格式为:
input [wire] [msb:lsb] 输入端口名x1,输入端口名x2,…;
output [wire/reg] [msb:lsb] 输出端口名y1,输出端口名y2,…;
inout [wire/reg] [msb:lsb] 双向口名z1,双向口名z2,…;
module MUX4to1(d0,d1,d2,d3,a,y); …… endmodule
input d0,d1,d2,d3; // 4路数据
input [1:0] a; // 2位地址
output wire/reg y; // 输出
module MUX4to1(
input d0,d1,d2,d3,
input [1:0] a,
output wire/reg y
);
3. 数据类型定义
数据类型(Data Type)用于:
(1)指定模块端口的数据类型;
(2)定义模块内部的物理连线或者具有存储作用的数据单元。
数据类型定义的语法格式为: wire [msb:lsb] 线网名1,线网名2,…; reg [msb:lsb] 变量名1,变量名2,…;
// 定义4个内部线网
wire atmp,btmp,ctmp,dtmp;
// 定义计数变量
reg [3:0] q;
module MUX4to1 (
input d0,d1,d2,d3,
input [1:0] a,
output wire/reg y
);
需要说明的是:
(1)模块的输入口为wire类型;
(2)模块的端口没有显式说明为reg类型时,默认为wire类型;
(3)输出口/双向口可以被定义为reg类型,而且reg变量的位宽必须与端口类型定义中的位宽严格一致;
4. 功能描述
功能描述用于描述模块的逻辑功能或者说明模块的结构,有数据流描述、行为描述和结构描述三种方法。
数据流描述应用连续赋值语句(在关键词assign后加表达式的方法或者应用操作符)描述线网的逻辑功能。
module MUX4to1 (
input d0,d1,d2,d3,
input [1:0] a,
output wire y );
wire atmp,btmp,ctmp,dtmp;
根据4选一数据选择器的逻辑函数表达式
Y=D0A1'A0'+D1A1'A0+D2A1A0'+D3A1A0
可以直接写出4选一数据选择器的数据流描述为
assign atmp = d0 && !a[1] && !a[0];
assign btmp = d1 && !a[1] && a[0];
assign ctmp = d2 && a[1] && !a[0];
assign dtmp = d3 && a[1] && a[0];
assign y = atmp || btmp || ctmp || dtmp;
式中“&&”表示逻辑与,”||”表示逻辑或,“!”表示逻辑非。
行为描述使用(initial/always)过程语句对模块的功能进行描述,其中always语句是Verilog中最具有特色的过程语句,既可以用于描述组合逻辑电路,也可以用于描述时序逻辑电路。
always语句是反复执行的,过程内部用高级语句来描述模块的逻辑功能。
用always语句描述4选一数据选择器:
always @(d0 or d1 or d2 or d3 or a)
case (a) //根据地址a进行分支
2'b00: y=d0;
2'b01: y=d1;
2'b10: y=d2;
2'b11: y=d3;
default: y=d0;
endcase
用always语句描述4选一数据选择器时,还需要将输出y显式地定义为寄存器变量,即需要在数据类型定义部分添加下述语句:
module MUX4to1 (d0,d1,d2,d3,a,y);
input d0,d1,d2,d3;
input [1:0] a;
output y;
reg y;
结构描述是调用Verilog中内置的门级原语(primitive,门级或开关级元件)、用户定义的功能模块或者宏功能模块来描述模块内部器件之间的连接关系,用于对模块的结构进行说明。
结构描述的语法格式为: 调用模块名 [实例名](端口关联列表);
wire atmp,btmp,ctmp,dtmp;
and U1_and (atmp,d0,!a[1],!a[0]); //调用基元and,实现atmp=D0A1'A0'
and U2_and (btmp,d1,!a[1], a[0]); //调用基元and,实现btmp=D1A1'A0
and U3_and (ctmp,d2, a[1],!a[0]); //调用基元and,实现ctmp=D2A1A0'
and U4_and (dtmp,d3, a[1], a[0]); //调用基元and,实现dtmp=D3A1A0'
or U_or (y,atmp,btmp,ctmp,dtmp); //调用基元or,实现y=atmp+btmp+ctmp+dtmp
Verilog 语法元素
Verilog HDL代码由大量的基本语法元素构成,包括空白符和注释、数值和字符串、标识符和关键字词等。
Verilog从C语言发展而来,保留了C语言的语法特点。例如,空白符(空格键或者TAB)、和注释(//...或者/*...*/)方法与C语言完全相同,标识符同样区分大小写等。但是,Verilog本质上是用来描述硬件电路的,所以也有许多与C语言不同之处,例如线网/变量的概念和取值以及操作符等。
Verilog为线网/变量定义了4种基本取值:0、1、x和z,基本含义如右表所示。其中x表示未知,通常用在测试平台文件中,表示没有经过初始化的输入端口的值。
常量表示方法
常量按照其数值类型的不同可划分为整数常量、实数常量和字符串三种类型。
实数常量既可以用带小数点的十进制数表示,也可以用科学计数法表示。带小数点的实数在小数点两侧都必须至少有一位数字。
字符串定义为双引号内的字符序列,用 ASCII 码序列表示,其中每个字表示为一个 8 位 ASCII 码。
reg [12*8:1] str1;
reg [14*8:1] str2;
...
str1=”Hello world!”;
str2= ”Internal error”
参数定义语句
Verilog使用参数定义语句定义常量,用标识符表示具体的常量值,用于指定数据的位宽、延迟量和状态编码等参数,以提高代码的可阅读性、可维护性以及模块的复用性。
参数定义语句的语法格式如下: parameter 参数名1=常数或常数表达式1,参数名2=常数或常数表达式2,…; localparam 参数名1=常数或常数表达式1,参数名2=常数或常数表达式2,…;
parameter MSB=7, LSB=0; // 定义参数MSB和LSB,值分别为7和0
localparam DELAY=10; // 定义参数DELAY,值为10
...
reg [MSB:LSB] cnt_q; // 引用参数MSB和LSB定义cnt_q的位宽
and #DELAY (y,a,b); // 引用参数DELAY定义赋值的延迟时间
参数定义语句parameter/localparam通常写在模块内部,只对当前模块起作用。
标识符与关键词
Verilog中的标识符应符合以下三条基本规定:
(1)由大小写字母、数字、$和_(下划线)组成;
(2)以字母或下划线开头,中间可以使用下划线,但不能连续使用下划线,也不能以下划线结束;
(3)长度小于1024。
合法的标识符:MUX4to1,d0,d1,d2,d3,a,y; Clk_100MHz,WR_n,_CE,P1_2。
非法的标识符:64bits,ROM__dat,ROM-dat
Verilog HDL中预先保留了许多用于定义语言结构的特殊标识符,称为关键词(keywords),具有特定的含义,如module、endmodule、input、output、inout、wire、reg、integer、real、initial、always、begin、end、if、else、case、casex、casez、endcase、for、repeat、while和forever等。