Verilog HDL基础语法讲解之模块代码基本结构
本章主要讲解Verilog基础语法的内容,文章以一个最简单的例子"二选一多路器"来引入一个最简单的Verilog设计文件的基本结构。
以下为本章中例子中的代码:
01 /*======================================= 02 * file neme : mux2.v 03 * author : 小梅哥 04 * Verison : V1.0 05 * date : 2015年07月01日 06 * description: 07 * 当sel为0时,将输入端口in_a上的数据通过out端口输出, 08 * 否则将输入端口in_b上的数据通过out端口输出 09 =======================================*/ 10 11 module mux2(in_a,in_b,sel,out); 12 13 input [1:0] in_a; //输入端口in_a 14 input [1:0] in_b; //输入端口in_b 15 16 input [0:0] sel; //通路选择线 17 18 output [1:0] out; //输出端口out 19 20 /*-------------------------------------------- 21 当sel为0时,选择将in_a端口的数据通过out端口输出, 22 当sel为1时,选择将in_b端口的数据通过out端口输出. 23 --------------------------------------------*/ 24 assign out = (sel == 1'b0)? in_a : in_b; 25 26 endmodule |
(注:最左侧一列行号是作者为了讲解方便加上的,不属于代码内容,请知悉)
以上代码描述的电路如下图所示:
图:二选一多路器电路模型
第1到9行:
该部分为文件头,记录了本设计文件中的一些基本信息如设计者、版本号、修改历史以及代码实现的功能描述。在编译时,该部分属于注释内容,将不被综合成任何内容。
第11行:module mux2(in_a,in_b,sel,out);
该行第一个字符串"module"为Verilog中的一个保留字(关键词),该保留字的出现表明了一个模块内容的开始。相对应的,第26行的"endmodule"也是一个保留字,该保留字的出现表明了一个模块内容的结束。在Verilog语法中,module和encmodule总是成对出现,module和encmodule之间的内容则是用户代码。
module之后是一个空格(或制表符"tab"),空格或制表符数量不一定限定为1个,也可以是多个,对结果没有任何影响。紧跟空格之后的mux2则是模块名称,该名称由用户自己定义,但一般要求与设计内容有一定关系,通过该名称能够体现出模块的功能或作用,例如这里使用mux2表明这是一个二选一多路器,让人一目了然。换个例子,如果要做PWM波生成模块,则可以直接以PWM作为模块名(module PWM(……);)这样简洁直观,一看就能理解模块的功能。
mux2之后使用圆括号括起来的部分则是端口列表,该列表中列出了该模块所有需要外部输入或者需要输出到外部的信号,信号间以英文中的逗号","隔开。端口名的命名也尽量能够代表该端口信号的实际功能或意义,例如"in_a",很清晰的就能告诉读者这是数据输入通道的a端口。
第13行到18行:
13 input [1:0] in_a; //输入端口in_a
14 input [1:0] in_b; //输入端口in_b
15
16 input [0:0] sel; //通路选择线
17
18 output [1:0] out; //输出端口out
这里蓝色保留字"input"和"output"是端口类型,input表示该端口是本模块的输入型端口,output表示该端口是本模块的输出型端口。另外,在实际项目中,还有一种很常见的端口类型是双向端口,Verilog中用关键字"inout"来表示。在本例中,由于没有使用到双向端口,因此不做介绍,该部分内容将在后面以一个单独的章节来进行讲解。
第13行紧跟着"input"之后用方括号括起来的部分[1:0]表示端口的宽度,这里表示输入端口in_a的位宽为2。需要注意的是,定义位宽时虽然写成[1:0]和[0:1]表示的位宽是一致的,但是Verilog中习惯使用[MSB:LSB]的形式,请大家在自己写代码时也统一遵照这一要求。当位宽为1时,位宽定义部分可以省略,例如第16行的[0:0]是可以省略的,即写成"input sel;",与"input [0:0] sel;"效果是一样的。
第20行到23行:
该部分为注释内容,注释主要是为了方便阅读和理解代码,在综合电路时会被忽略。Verilog语法中,注释的格式与C语言中一致,也支持单行注释和块注释。单行注释以"//"开始,有效作用域只在以此符号开始的本行紧随其后的内容,换行后就不起作用了。例如第13行的"//输入端口in_a"就是注释内容,换行后第14行就不再是被注释掉的内容了。第20行到第23行为块注释,块注释以"/* */"组织,两个"*"之间的内容即为注释内容,此种注释方式支持换行,注释内容以"/*"开始,换行后注释属性依然有效,直到出现"*/",则注释部分结束。不过需要注意的是,块注释不支持嵌套,当强行使用这种方式时会导致编译错误。例如以下这种格式就是错误的:
/*
一级块注释内容
/*
二级块注释内容
*/
……
*/
第24行:24 assign out = (sel == 1'b0)? in_a : in_b;
这一行为赋值语句,这里是Verilog中最基础的一种赋值方式——连续赋值语句。这段代码的意义就是"判断括号中的条件是否成立,若成立,则将in_a的值赋给out,否则,则将in_b的值赋给out",即通过这种方式实现了根据条件选择通道的功能。其中,"?"之后":"之前的信号为括号中条件满足时需要赋给out的的源信号,":"之后的信号则是括号中条件不满足时需要赋给out的源信号。此种赋值方式还支持多重选择,例如下面的代码:
assign out = (sel1 == 1'b0)? in_a : (sel2 == 1)? in_b : in_c; |
这里有三个数据输入端口"in_a , in_b , in_c",两个选择输入端口"sel1 , sel2"。该句话的意思就是,当第一个括号中的条件满足时,则将in_a的数据赋值给out,否则再判断第二个括号中的条件是否满足,满足则将in_b的值赋给out,不满足则将in_c的值赋给out。括号中的内容可以直接简写为(!sel1)或者(!sel2),这一点与C语言一样。
通过这一章,我们学习了采用Verilog HDL设计简单的模块的基本代码结构,包括声明模块的"module endmodule"、端口的定义、端口类型以及位宽的声明、注释内容的格式以及连续赋值语句的写法,而且通过此例子完成了一个简单的二选一多路器的设计。希望对于Verilog HDL语法零基础的读者在看罢此文后能够关闭此文档,在Quartus II或Modelsim、ISE等工具中实际动手敲一遍。可结合前面介绍Altera 公司FPGA器件开发流程一章中的步骤,实际建立工程并输入代码,然后分析和综合,通过综合工具,查看自己编写的代码是否有错误,如有错误,则根据软件提示加以改正。
下一章,将以此模块的测试文件(testbench)为例,介绍Testbench的基本语法。
小梅哥FPGA设计思想与验证方法教程系列文档
2015年07月02号于Snow Dream电子工作室