verilog

程序结构

Verilog 的基本设计单元是模块(module)。一个模块是由两部分组成的,一部分描述接口,一部分描述逻辑功能,用来定义怎么由输入到输出的。

Verilog 程序由三部分构成:I/O 端口声明、信号声明、功能描述。

端口声明

模块的端口声明了模块的输入输出接口。其格式如下:

module 模块名(口1, 口2, 口3, ...);

模块的端口表示的是模块的输入和输出口名,也就是说,它与别的模块联系端口的标识。在模块被引用时,在被引用的模块中,有些信号要输入到被引用的模块中,有的信号需要从被引用的模块中取出来。在引用模块时其端口可以用两种方法连接: - 在引用时,严格按照模块定义的端口顺序来连接,不用标明原模块定义的规定的端口名,例如:模块名(连接端口1信号名,连接端口2信号名,连接端口3信号名,…); - 在引用时用“.”符号,标明原模块时定义时规定的端口名,例如:模块名(.端口1名(连接信号1名),.端口2名(连接信号2名), …);。这样表示的好出在于可以用端口名与被引用模块的端口相对应,而不必严格按端口顺序对应,提高了程序的可读性和可移植性。

模块内容

模块的内容包括 I/O 说明、内部信号声明和功能定义。

I/O 说明的格式如下:

// 输入口
input [信号位宽-1:0] 端口名1;
input [信号位宽-1:0] 端口名2;
// ...
input [信号位宽-1:0] 端口名i;//共有i个输入口

// 输出口
output [信号位宽-1:0] 端口名1;
output [信号位宽-1:0] 端口名2;
// ...
output [信号位宽-1:0] 端口名j;//共有j个输出口

// 输入/输出口
inout [信号位宽-1:0] 端口名1;
inout [信号位宽-1:0] 端口名2;
// ...
inout [信号位宽-1:0] 端口名k;//共有k个双向总线端口

I/O 说明也可以写在端口声明语句中。其格式如下:

module module_name(input port1, input port2, ..., output port1, output port2, ...);

在模块内部用到的与端口有关的 wire 和 reg 类型变量的声明。例如:

reg [width-1: 0] R变量1, R变量2;
wire [width-1: 0] W变量1, W变量2;
// ...

模块中最重要的部分是逻辑功能定义部分。有 3 种方法可在模块中产生逻辑。

  • 用“assign”声明语句,如 assgin a = b&c;
    • 这种方法的句法很简单,只需写一个“assgin”,后面再加一个方程式即可。示例中的方程式描述了一个有两个输入的与门

  • 用实例原件,如 and #2 ul(q,a,b);
    • 采用实例元件的方法像在电路图输入方式下调入库元件一样,键入元件的名字和相连的引脚即可。

    • 这表示在设计中用到一个跟与门(and)一样的名为 ul 的与门,其输入端为 a、b,输出端为 q。输出延迟为 2 个单位时间。

    • 要求每个实例元件的名字必须是唯一的,以避免与其它调用与门(and)的实例混淆。

  • 用“always”块,如 always@(posedge clk or posedge clr) begin if(clr) q<=0; else if(en) q<= d; end

采用“assgin”语句是描述组合逻辑最常用的方法之一。而“always”块既可用于描述组合逻辑,也可描述时序逻辑。用“always”块的例子生成了一个带有异步清除端的 D 触发器。“always”块可用很多描述手段来表达逻辑,例如上例就用了 if…else 语句来表达逻辑关系。如按一定的风格来编写“always”块,可以通过综合工具把源代码自动综合成用门级结构表示的组合或时序逻辑电路。

数据类型及其常量和变量

verilog 中总共有 19 种数据类型。数据类型是用来表示数字电路硬件中的数据存储和传送元素的。这里介绍 4 种最基本的数据类型,它们是:reg 型、wire 型、integer 型和 parameter 型。

verilog 语言种也有常量和变量之分,它们分别属于 19 种数据类型。下面就最常用的几种进行介绍。

常量

在程序运行中,其值不能被改变的量城为常量。下面首先对在 verilog 语言中使用的数字及其表示方式进行介绍。

数字

整数

在 verilog 中,整型常量即整常数有以下 4 种进制表示形式: - 二进制整数(b或B) - 十进制整数(d或D) - 十六进制整数(h或H) - 八进制整数(o或O)

数字表达方式有以下三种:

  • <位宽><进制><数字>,这是一种全面的描述方式。

  • 在 <进制><数字> 这种描述方式中,数字的位宽采用默认位宽。

  • 在 <数字> 这种描述方式中,采用默认进制(十进制)。

在表达式中,位宽指明了数字的精确位数。例如:一个 4 位二进制数的数字的位宽为 4,一个 4 位十六进制数字的位宽为 16(因为每单个十六进制数就要用 4 位二进制数来表示)。

例如:

8'b10101100 // 位宽位 8 的数的二进制表示,'b 表示二进制
8'ha2       // 位宽为 8 的数的十六进制表示,'h 表示十六进制

x和z值

在数字电路中,x 代表不定值,z 代表高阻值。一个 x 可以用来定义十六进制数的 4 位二进制数的状态,八进制数的 3 位,二进制数的 1 位。z 的表示方式同 x 类似。z 还有一种表达方式是可以写作“?”。在使用 case 表达式时建议使用这种写法,以提高程序的可读性。

例如:

4'b10x0  // 位宽为 4 的二进制数从低位数起第 2 位为不定值
4'b101z  // 位宽为 4 的二进制数从低位数起第 1 位位高阻值
12'dz    // 位宽为 12 的十进制数,其值为高阻值(第1种表达方式)
12'd?    // 位宽为 12 的十进制数,其值为高阻值(第2种表达方式)
8'h4x    // 位宽为 8 的十六进制数,其低 4 位值为不定值

负数

一个数字可以被定义为负数,只需在位宽表达式前加一个减号,减号必须写在数字定义表达式的最前面。减号不可以放在位宽和进制之间,也不可以放在进制和具体的数之间。

例如:

-8'd5   // 这个表达式代表 5 的补数(用八位二进制数表示)
8'd-5   // 非法格式

下划线

下划线可以用来分隔开数的表达以提高程序的可读性。它不可以用在位宽和进制处,只能用在具体的数字之间。

例如:

16'b1010_1011_1111_1010  // 合法格式
8'b_0011_1010            // 非法格式

当常量不说明位数时,默认值是 32 位,每个字母用 8 位的 ASCII 值表示。例如:

10=32'd10=32'b1010
1=32'd1=32'b1
-1=-32'd1=32'hFFFFFFFF
'BX=32'BX=32'BXXXXXXX...X
"AB"=16'B01000001_01000010  // 字符串 AB,为十六进制数 16'h4142

参数(parameter)型

在 verilog 中用 parameter 来定义常量,即用 parameter 来定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量,采用标识符代表一个常量可提高程序的可读性和可维护性。parameter 型数据是一种常数型的数据,其说明格式如下:

parameter 参数名1=表达式, 参数名2=表达式, ..., 参数名n=表达式;

parameter 是参数型数据的确认符。确认符后跟着一个用逗号分隔开的赋值语句表。在每一个赋值语句的右边必须是一个常数表达式。也就是说,该表达式只能包含数字或先前已经定义过的参数。例如:

parameter msb = 7;     // 定义参数 msb 为常量 7
parameter e = 25, f = 29;  // 定义两个常数参数
parameter f = 5.7;     // 声明 r 为一个实型参数
parameter byte_size = 8, byte_msb=byte_size-1;  // 用常数表达式赋值
parameter average_delay = (r+f)/2;  // 用常数表达式赋值

参数型常量经常用于定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数。

变量

变量是一种在程序运行过程中可以改变的量,在 verilog 中变量的数据类型有多种,这里介绍几种常用的。

网络数据类型表示结构实体之间的物理连接。网络类型的变量不能储存值,而且它必须受到驱动器(例如门或连续赋值语句,assign)的驱动。如果没有驱动器连接到网络类型的变量上,则该变量就是高阻值,即其值为 z。

wire 型

wire 型数据常用来表示用以 assign 关键字指定的组合逻辑信号。verilog 程序模块中输入、输出信号类型默认时自动定义为 wire 型。wire 型信号可以用做任何方程式的输入,也可以用做“assign”语句或实例元件的输出。

wire 型信号的格式同 reg 型信号的格式很类似。其格式如下:

wire [n-1:0] 数据名1, 数据名2, ..., 数据名i; // 共有 i 条总线,每条总线内有 n 条线路
wire [n:1] 数据名1, 数据名2, ..., 数据名i;
// wire 是 wire 型数据的确认符; [n-1:0] 和 [n:1] 代表该数据的位宽,即该数据有几位;最后跟着的是数据的名字。如果一次定义多个数据,数据名之间之间用逗号隔开。声明语句的最后要用分号表示语句结束。例如:
wire a;   // 定义了一个 1 位的 wire 型数据
wire [7:0] b; // 定义了一个 8 位的 wire 型数据
wire [4:1] c,d; // 定义了二个 4 位的 wire 型数据

reg 型

寄存器是数据储存单元的抽象。寄存器数据类型的关键字是 reg。通过赋值语句可以改变寄存器储存的值,其作用与改变触发器储存的值相当。verilog 语言提供了功能强大的结构语句,使设计者能有效地控制是否执行这些赋值语句。这些控制结构用来描述硬件触发条件,例如时钟的上升沿和多路器的选通信号。reg 类型数据的默认初始值位不定值 x。

reg 型数据常用来表示“always”模块内的指定,常代表触发器。通常,在设计中要由“always”模块通过使用行为描述语句来表达逻辑关系。在“always”模块内被赋值的每一个信号都必须定义成 reg 型。

reg 型数据的格式如下:

reg [n-1:0] 数据名1, 数据名2, ..., 数据名i;
reg [n:1] 数据名1, 数据名2, ...,数据名i;
// reg 是 reg 型数据的确认标识符;[n-1:0] 和 [n:1] 代表该数据的位宽,即该数据有几位;最后跟着的是数据的名字。如果一次定义多个数据,数据名之间之间用逗号隔开。声明语句的最后要用分号表示语句结束。例如:
reg rega;   // 定义了一个 1 位的名位 rega 的 reg 型数据
reg [3:0] regb;  // 定义了一个 4 位的名位 regb 的 reg 型数据
reg [4:1] regc, regd;  // 定义了二个 4 位的名为 regc 和 regd 的 reg 型数据

对于 reg 型数据,其赋值语句的作用就如同改变一组触发器的存储单元的值。在 verilog 中有许多构造(construct)用来控制何时或是否执行这些赋值语句。这些控制构造可用来描述硬件触发器的各种具体情况,如触发条件时用时间的上升沿,或用来描述判断逻辑的细节,如各种多路选择器。

reg 型数据的默认初始值时不定值。reg 型数据可以赋正值,也可以赋负值。但当一个 reg 型数据时一个表达式中的操作数时,它的值被当作时无符号值,即正值。例如,当一个 4 位的寄存器用做表达式中的操作数时,如果开始寄存器被赋以值 -1,则在表达式中进行运算时,其值被认为是 +15。

memory 型

verilog 通过对 reg 型变量建立数组来对存储器建模,可以描述 RAM 型存储器、ROM 存储器和 reg 文件。数组中的每一个单元通过一个数组索引进行寻址。在 verilog 语言中没有多维数组存在。menory 型数据是通过扩展 reg 型数据的地址范围来生成的。其格式如下:

reg [n-1:0] 存储器名[m-1:0];
reg [n-1:0] 存储器名[m:1];
// reg[n-1:0] 定义了存储器中每一个存储单元的大小,即该存储单元是一个 n 位的寄存器;存储器名后面的 [m-1:0] 或 [m:1] 则定义了该存储器中有多少个这样的寄存器;最后用分号结束定义语句。

这里通过一个例子来说明:

reg [7:0] mema [255:0];
// 定义了一个名为 mema 的存储器,该存储器有 256 个 8 位的存储器。该存储器的地址范围是 0 到 255。

运算符及表达式

verilog 语言的运算符范围很广,其运算符按其功能可分为以下几类:

  • 算术运算符(+,-,x,/,%)

  • 赋值运算符(=,<=)

  • 关系运算符(>,<,>=,<=)

  • 逻辑运算符(&&,||,!)

  • 条件运算符(?:)

  • 位运算符(~,|,^,&,^~)

  • 移位运算符(<<,>>)

  • 拼接运算符({ })

  • 其它

算术运算符

在 verilog 语言中,算术运算符又称为二进制运算符,共有下面几种:

    • (加法运算符,或正值运算符)

    • (减法运算符,或负值运算符)

  • x (乘法运算符)

  • / (除法运算符)

  • % (模运算符,或称为求余运算符,要求%两侧均为整型数据。)

在进行整数除法运算时,结果值要略去小数部分,只取整数部分;而进行取模运算时,结果值的符号位采用模运算式里第一个操作数的符号位。

位运算符

verilog 作为一种硬件描述语言,是针对硬件电路而言的。在硬件电路中信号有 4 种状态值,即 1,0,x,z。在电路中信号进行与、或、非时,反映在 verilog 中则是相应的操作数的位运算。verilog 提供了以下 5 种位运算符:

  • ~ 取反

  • & 按位与

  • 按位或
  • ^ 按位异或

  • ^~ 按位同或(异或非)

逻辑运算符

在 verilog 语言中存在 3 种逻辑运算符:

  • && 逻辑与

  • || 逻辑或

  • ! 逻辑非

逻辑运算符中“&&”和“||”的优先级别低于关系运算符,“!”高于算术运算符。

为了提高程序的可读性,明确表达各运算符元间的优先关系,建议使用括号。

关系运算符

关系运算符共有以下 4 种:

  • a<b,读作a小于b

  • a>b,读作a大于b

  • a<=b,读作a小于或等于b

  • a>=b,读作a大于或等于b

在进行关系运算时,如果声明的关系是假的(false),则返回值是0;如果声明的关系是真的(true),则返回值是1;如果某个操作数的值不定,则关系是模糊的,返回值是不定值。

所有的关系运算符有着相同的优先级别。关系运算符的优先级别低于算术运算符的优先级别。

等式运算符

在 verilog 语言中存在 4 种等式运算符:

  • == (等于)

  • != (不等于)

  • === (等于)

  • !== (不等于)

“==”和“!=”又称为逻辑等式运算符,其结果由两个操作数的值决定。由于操作数种某些位可能是不定值 x 和高阻值 z,结果可能为不定值 x。而“===”和“!==” 运算符则不同,它在对操作数进行比较时对某些位的不定值 x 和高阻值 z 也进行比较,两个操作数必须完全一致,其结果才是1,否则为0。“===”和“!==” 运算符常用于 case 表达式的判别,所以又称为“case 等式运算符”。这 4 个等式运算符的优先级别是相同的。

移位运算符

在 verilog 种有两种移位运算符:“<<”(左移位运算符)和“>>”(右移位运算符)。其使用方法如下:

a >> n
a << n
// a 代表要进行移位的操作数,n 代表要移几位。这两种移位运算都用0来填补移出的空位。

位拼接运算符

在 verilog 语言中有一个特殊的运算符:位拼接运算符。用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作。其使用方法如下:

{信号1的某几位, 信号2的某几位, ..., ..., 信号n的某几位}
// 即把某些信号的某些位详细地列出来,中间用逗号分开,最后用大括号括起来表示一个整体信号。
// 例如
{a, b[3:0], w, 3'b101}

在位拼接表达式中不允许存在没有指明位数的信号。这是因为在计算拼接信号位宽的大小时必须知道其中每个信号的位宽。

赋值语句和块语句

赋值语句

在 verilog 中,信号有两种赋值方式:

  • 非阻塞赋值方式
    • 在语句块中,上面语句所赋的变量值不能立即就为下面的语句所用

    • 块结束后才能完成这次赋值操作,而所赋值的变量值是上一次赋值得到的

    • 在编写可综合的时序逻辑模块时,这是最常用的赋值方法

  • 阻塞赋值方式
    • 赋值语句执行完后,块才结束

    • 值在赋值语句执行完后立刻就改变的

    • 在时序逻辑中使用时,可能会产生意想不到的结果

块语句

块语句用来将两条或多条语句组合在一起,使其在格式上看更像一条语句。通常用 begin_end 来标识顺序执行的语句,用它来标识的块称为顺序块。块内的语句时按顺序执行的,即只有上面一条语句执行完后下面的语句才能执行

顺序块的格式如下:

begin
  语句 1;
  语句 2;
  // ...
  语句 n;
end

// 或者

begin: 块名
  块内声明语句
  语句 1;
  语句 2;
  // ...
  语句 n;
end
// 块名即该块的名字,一个标识名。块内声明语句可以是参数声明语句、reg 型变量声明语句、integer型变量声明语句和reg型变量声明语句。

条件语句

if_else 语句

if 语句是用来判断所给定的条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一。 verilog 语言提供了 3 种形式的 if 语句。

// if(表达式) 语句
// 例如
if(a > b)
  out1 = int1;

// if(表达式)
//    语句1
// else
//    语句2
// 例如
if(a > b)
  out1 = int1;
else
  out1 = int2;

// if(表达式)
// else if(表达式2) 语句2;
// else if(表达式3) 语句3;
// ...
// else if(表达式m) 语句m;
// else            语句n;

条件语句必须在过程块语句中使用。所谓的过程块是指由 initial 和 always 语句引导的执行语句集合。除这两种块语句引导的 bengin end 块中可以编写条件语句外,模块汇总的其它地方都不能编写。

case 语句

case 语句是一种多分支选择语句,if 语句只有两个分支可供选择,而实际问题中常常需要用到多分支选择。verilog 语言提供的 case 语句直接处理多分支选择。它的一般形式如下:

case(表达式) <case分支项> endcase
// case 分支项的一般格式如下
// 分支表达式: 语句;
// 默认项(default项): 语句;
  • case 括弧内的表达式称为控制表达式,case 分支项中的表达式称为分支表达式。控制表达式通常表示为控制信号的某些位,分支表达式则用这些控制信号的具体状态值来表示,因此分支表达式又可以称为常量表达式。

  • 当控制表达式的值与分支表达式的值相等时,就执行分支表达式后面的语句。如果所有的分支表达式的值都没有与控制表达式的值相匹配,就执行default后面的语句。

  • default 项可有可无,一个case 语句里只准有一个default项。

  • 每一个 case 分项的分支表达式的值必须互不相同,否则就会出现问题,即对表达式的同一个值,将出现多种执行方案,产生矛盾。

  • 执行完 case 分项后的语句,则跳出该 case语句结构,终止 case 语句的执行。

  • 在用 case 语句表达式进行比较的过程中,只有当信号的对应位的值能明确进行比较时,比较才能成功。因此,要注意详细说明 case 分项的分支表达式的值。

  • case 语句的所有表达式的位宽必须相等,只有这样,控制表达式和分支表达式才能进行对应位的比较。

case 语句与 if_else_if 的区别主要有两点:

  • 与 case 语句中的控制表达式和多分支表达式这种比较结构相比,if_else_if 结构中的条件表达式更为直观一些。

  • 对于那些分支式中存在不定值x和高阻值z的位时,case语句提供了处理这种情况的手段。

条件语句的语法

条件语句用于根据某个条件来确定是否执行其后的语句,关键字 if 和 else 用于表示条件语句。verilog 语言共有3种类型的条件语句,条件语句的用法如下所示。

// 第一类条件语句:没有 else 语句
// 其后的语句执行或不执行
if( <expression> ) true_statement;

// 第二类条件语句:有一条 else 语句
// 根据表达式的值,决定执行 true_statement 或者 false_statement
if( <expression> ) true_statement; else false_statement;

// 第三类条件语句:嵌套的 if_else_if 语句
// 可供选择的语句有许多条,只有一条被执行
if( <expression1> ) true_statement1;
else if( <expression2> ) true_statement2;
else if( <expression3> ) true_statement3;
else default_statement;

条件语句的执行过程为:计算条件表达式 <expression>,如果结果为真(1或非零值),则执行 true_statement 语句;如果条件为假(0或者不确定值x),则执行 false_statement 语句。造条件表达式中可以包含任何操作符。true_statement 和 false_statement 可以时一条语句,也可以时一组语句。如果时一组语句,则通常使用 begin、end 关键字将它们组成一个块语句。

多路分支语句

前面讲述的条件语句 if_else_if 的形式从多个选项中确定一个结果。如果选项的数目很多,那么使用起来很不方便。而使用 case 语句来描述这种情况时非常简便的。

case 语句使用关键字 case、endcase、和 default 来表示。

case (expression)
  alternative1 : statement1;
  alternative2 : statement2;
  alternative3 : statement3;
  // ...
  default: default_statement;
endcase

case 语句中的每一条分支语句都可以时一条语句或一组语句。多条语句需要使用关键字 begin_end 组合为一个块语句。在执行时,首先计算条件表达式的值,然后按顺序将它和各个候选项进行比较;如果等于第一个候选项,则执行对应的语句 statement1;如果和全部候选项都不相等,则执行 default_statement 语句。

循环语句

在 verilog 中存在着 4 种类型的循环语句,用来控制执行语句的执行次数。

  • forever:连续的执行语句

  • repeat 语句:连续执行一条语句n次

  • while语句:执行一条语句直到某个条件不满足。如果一开始条件即不满足(为假),则语句一次也不能被执行

  • for语句:通过以下3个步骤来决定语句的循环执行
    • 先给控制循环次数的变量赋初值

    • 判定控制循环的表达式的值,如为假,则跳出循环语句,如为真,则执行指定的语句后,转到第三步

    • 执行一条赋值语句来修正控制循环变量次数的变量的值,然后返回第二步

forever 语句

forever 语句的格式如下:

forever 语句;
// 或者
forever begin 多条语句 end

forever 循环语句常用于产生周期性的波形,用来作为仿真测试信号。它与always语句不同之处在于不能独立写在程序中,而必须写在 initial 块中。

repeat 语句

repeat 语句的格式如下:

repeat(表达式) 语句;
// 或者
repeat(表达式) begin 多条语句 end

在 repeat 语句中,其表达式通常为常量表达式。

while 语句

while 语句的格式如下:

while(表达式) 语句;
// 或者
while(表达式) begin 多条语句 end

for 语句

for 语句的一般形式为:

for(表达式1; 表达式2; 表达式3) 语句;

它的执行过程如下:

  • 先求解表达式1

  • 求解表达式2,若其值为真(非0),则执行 for 语句中指定的内嵌语句,然后执行下面的第三步。若为假(0),则结束循环,转到第五步。

  • 若表达式为真,在执行指定的语句后,求解表达式3

  • 转回上面的第二步骤继续执行

  • 执行 for 语句下面的语句

for 语句最简单的应用形式时很容易理解的,其形式如下:

for(循环变量赋初值; 循环结束条件; 循环变量增值)
  执行语句;

for 循环语句实际上相当于采用 while 循环语句建立以下的循环结构:

begin
  循环变量赋初值;
  while(循环结束条件)
    begin
      执行语句;
      循环变量增值;
    end
end

这样对于需要8条语句才能完成的一个循环控制,for循环语句只需要两条即可。

生成块

生成语句可以动态地生成 verilog 代码。这一声明语句方便了参数化模块的生成。当对矢量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者在根据参数的定义来确定程序中是否应该包括某段 verilg 代码的时候,使用生成语句能够大大简化程序的编写过程。

生成语句能够控制变量的声明、任务或函数的调用,还能对实例引用进行全面的控制。编写代码时必须在模块中说明生成的实例范围,关键字 generate-endgenerate 用来指定该范围。

在 verilog 中有3种创建生成语句的方法,它们是:

  • 循环语句

  • 条件语句

  • case 生成

循环生成语句

循环生成语句允许使用者对下面的模块或模块项进行多次实例引用:

  • 变量声明

  • 模块

  • 用户定义原语、门级原语

  • 连续赋值语句

  • initial 和 always 块

这里用一个例子来说明如何使用生成语句对其中两个 N 位的总线用门级原语进行按位异或。在这里其目的在于说明循环生成语句的使用方法。

// 本模块生成两条 N 位总线变量进行按位异或
module bitwise_xor(out, i0, i1);

// 参数声明语句,参数可以重新定义
parameter N = 32;

// 端口声明语句
output [N-1:0] out;
input [N-1:0] i0,i1;

// 声明一个临时循环变量,该变量只用于生成块的循环计算
genvar j;

// 用一个单循环生成按位异或的异或门(xor)
generate
  for(j=0; j<N; j=j+1)
    begin: xor_loop //xor_loop 是赋予循环生成语句的名字,目的在于通过它对循环生成语句之中的变量进行层次化引用。
      xor g1(out[j], i0[j], i1[j]);
    end // 在生成块内部结束循环
endgenerate //结束生成块

// 另外一种编写形式

reg [N-1:0] out;
generate
  for(j=0; j<N; j=j+1)
    begin: bit
      always @ (i0[j] or i1[j]) out[j] = i0[j]^i0[j];
    end
endgenerate

endmodule

条件生成语句

条件生成语句类似于 if_else_if 的生成构造,该结构可以在设计模块中根据经过仔细推敲并确定表达式,有条件地调用(实例引用)以下这些 verilog 结构:

  • 模块

  • 用户定义原语、门级原语

  • 连续赋值语句

  • initial 或 always 块

这里用一个例子来说明如何使用条件生成语句实现参数化乘法器。如果参数 a0_width 或 a1_width 小于8(生成实例的条件),则调用(实例引用)超前进位乘法器;否则调用(实例引用)树形乘法器。

// 本模块实现一个参数化乘法器
module multiplier(product, a0, a1);

// 参数声明
parameter a0_width = 8;
parameter a1_width = 8;

// 本地参数声明
// 本地参数不能用参数重新定义修改,也不能在实例引用时通过传递参数语句的方法修改
localparam product_width = a0_width + a1_width;

// 端口声明语句
output [product_width-1:0] product;
input [a0_width-1:0] a0;
input [a1_width-1:0] a1;

// 有条件地调用(实例引用)不同类型的乘法器
// 根据参数 a0_width 和 a1_width 的值,在调用时引用相对应的乘法器实例
generate
  if(a0_width<8 || a1_width<8)
    cal_multiplier #(a0_width, a1_width) m0(product, a0, a1);
  else
    tree_multiplier #(a0_width, a1_width) m0(product, a0, a1);
endgenerate

endmodule

case 生成语句

case 生成语句可以在设计模块中,根据仔细推敲确定多选一 case 构造,有条件地调用(实例引用)下面这些 verilog 结构:

  • 模块

  • 用户定义原语、门级原语

  • 连续赋值语句

  • initial 或 always 块

这里用一个例子来说明如何使用 case 生成语句实现 N 位加法器

// 本模块生成 N 位的加法器
module adder(co, sum, a0, a1, ci);
// 参数声明
parameter N = 4;

// 端口声明
output [N-1:0] sum;
output co;
intput [N-1:0] a0,a1;
input ci;

// 根据总线的位宽,调用(实例引用)相应的加法器
// 参数 N 在调用(实例引用)时可以重新定义,调用(实例引用)不同位宽的加法器是根据不同的 N 来决定的
generate
case(N)
  // 当N=1或2时分别选用位宽为1位或2位的加法器
  1: adder_1bit adder1(co, sum, a0, a1, ci);
  2: adder_2bit adder2(co, sum, a0, a1, ci);
  default: adder_cla #(N) adder3(co, sum, a0, a1, ci);
endcase
endgenerate

endmodule