2.4 组合逻辑乘法器设计
由于硬件乘法器的高速性能,它在数字信号处理和现代通信技术中有广泛的应用。基于HDL的硬件乘法器的构建和实现有多种途径,如串行乘法器、并行乘法器、查找表型乘法器、流水线乘法器,以及由时序电路或纯组合逻辑方式基于移位相加原理实现的乘法器,还有更实用、更高效、更灵活的基于FPGA硬件IP核的DSP乘法器等。
相比于基于时序逻辑构建的乘法器,纯组合逻辑乘法器的速度更高。除介绍这些乘法器的设计方法之外,本节还希望通过它们,再学习一些新的语法知识和Verilog编程经验。为此,以下首先介绍在乘法器示例中将会出现的一些新的语句及其用法。
以下将结合示例中出现的,包括利用parameter的参数定义,整数寄存器类型integer的定义和循环语句等新的语言现象和语句的使用方法,给予详细说明,最后给出两则乘法器设计示例。
2.4.1 参数定义关键词parameter和localparam
参数是一个特殊常量,parameter就是定义参数的关键词。在Verilog中,用关键词parameter来定义常量,即用parameter来定义一个标识符,用以代表某个常量,如延时、变量的位宽等,从而成为一个符号化常量。而当改变parameter的定义时,就能很容易地改变整个设计。parameter的一般定义格式如下:
parameter 标识符名1=表达式或数值1,标识符名2=表达式或数值2,...;
例如,参数定义语句如下:
parameter A=15 , B=4’b1011, C=8’hAC ; parameter d=8’b1001_0011 , e=8’sb10101101 ;
第一条语句中,A,B,C分别被定义为等于整数15、四位二进制数1011和两位十六进制数AC的常数;第二条语句中,d被定义为普通8位二进制数,e被定义为8位二进制有符号数,最高位符号是1。
在模块中使用参数定义的常数只能被赋值一次。以下的例2-14至例2-17都定义了常数S=4,当改变S数值的定义后,则能十分容易地改变乘法器的位数。
与parameter具有类似功能的另一常用的定义参数的关键词是localparam。这是一个局部参数定义关键词。它的用法与关键词parameter相同,只是无法通过外部程序的数据传递来改变localparam定义的常量。相关的示例见后面给出的例2-18。
2.4.2 整数型寄存器类型定义
其实,integer类型与前面已介绍过的reg类型都属同类的寄存器类型,或称变量类型。定义为integer类型的变量多数被用于表达循环变量,用于指示循环的次数(见例2-14的4位乘法器程序)。
integer的一般定义表式如下:
integer 标识符1,标识符2,...,标识符n [msb:lsb] ;
integer与reg类型的定义不同,reg类型必须明确定义其位数,如“reg [7:0] A;”和“reg B”;等,其中A定义为8位二进制数位宽的变量,B定义为1位二进制数变量。然而integer类型的定义则不必特指位数,因为它们都默认为32位宽的二进制数寄存器类型。以下赋值方式都是允许的:
module EXAPL (R,G); parameter S=4; //定义参数S output[2*S:1] R,G ; //定义两个8位输出变量 integer A, B[3:0]; //定义了5个integer类型:A、B[0]、B[1]、B[3]等,都是32位 reg[2*S:1] R,G; always @( A, B ) begin B[2]=367; // 整数完整赋值,因为B[2]有32位 R=B[2]; // 32位integer整数类型B[2]赋给8位reg类型R,B[2]高位被截 A=-20; // 整数完整赋值,因为A有32位, 将-20赋予A,对应无符号数65516 G=A; // 32位integer整数类型A赋给8位reg类型G,A高位被截 B[0]= 3'B101; // 允许二进制数直接赋给integer类型B[0]。 end endmodule
2.4.3 for语句用法
例2-14和例2-15都使用了循环语句for语句。以下介绍循环语句。
Verilog有4种循环语句:for语句、repeat语句、while语句和forever语句。最后一种是不可综合的,这里暂时不讨论。首先讨论for语句。
for语句的一般格式可表述如下:
for(循环初始值设置表达式; 循环控制条件表达式;循环控制变量增值表达式) begin 循环体语句结构 end
根据for语句的设置,此循环语句的执行过程可以分成3个步骤:
(1)本次循环开始前根据“循环初始值设置表达式”计算获得循环次数初始值。
(2)在本次循环开始前根据“循环控制条件表达式”计算所得的数据判断是否满足继续循环的条件,如果“循环控制条件表达式”为真,则继续执行“循环体语句结构”中的语句,否则即刻跳出循环。
(3)在本次循环结束时,根据“循环控制变量增值表达式”计算出循环控制变量的数值,然后跳到以上步骤(2)。
需要说明两点:①步骤(3)中的所谓增值是指循环次数增加后的记录值,而非此表达式的值必须是增加的;②此语句的第3项表达式“循环控制变量增值表达式”的值如果不随循环而改变,或导致的循环次数过大,对于逻辑综合来说都将导致设计失败。
2.4.4 移位操作符应用法
例2-14和例2-15还用到了移位操作符,以下介绍移位符的用法:
“>>”是右移移位操作符,“<<”是左移移位操作符,它们的一般格式如下:
V >> n 或 V <<n
以上表达式表示将操作数或变量V中的数据右移或左移n位。这里指的是二进制数移位。移出腾空的位用0填补。例如若:V=8'b11001001,则:V >> 1的值是8'b01100100,V << 3的值是8'b01001000。
Verilog 2001版本还增加对有符号数左右移的操作符,即“>>>”作为右移移位操作符,和“<<<”作为左移移位操作符。它们的一般用法格式如下:
V >>> n 或 V <<< n
以上表达式表示将操作数或变量V中的数据(有符号数)右移或左移n位,且对于右移操作时,一律将符号位,即最高位填补移出的位,而左移操作同普通左移“<<”。
试比较以下左右两段语句的操作结果,注意有符号变量的定义情况:
2.4.5 两则乘法器设计示例
例2-14和2-15是两则利用for语句实现的4位乘法器设计。对于循环控制变量增值表达式,前者采用增值的方式,后者采用减值的方式。
此二例的仿真结果相同,时序仿真图如图2-12所示,其中的数值类型是无符号十进制数,而且逻辑综合后的RTL电路也相同。
【例2-14】
module MULT4B(R,A,B); parameter S=4; output[2*S:1] R ; input[S:1] A,B ; reg[2*S:1] R ; integer i; always @(A or B) begin R=0 ; for(i=1; i<=S; i=i+1) if(B[i]) R=R+(A<<(i-1)); end endmodule
【例2-15】
module MULT4B (R,A,B); parameter S=4; output[2*S:1] R ; input[S:1] A,B ; reg[2*S:1] R,AT; reg[S:1] BT,CT; always @(A,B ) begin R=0; AT={{S{1'B0}},A}; BT=B; CT=S; for(CT=S; CT>0; CT=CT-1) begin if(BT[1]) R=R+AT; AT=AT<<1; BT=BT>>1; end end endmodule
图2-12 四位乘法器时序仿真图
2.4.6 repeat语句用法
例2-6是一则利用repeat语句实现的4位乘法器。
repeat语句的一般格式可表述如下:
repeat(循环次数表达式) begin 循环体语句结构 end
与for语句不同,repeat语句的循环次数是在进入此语句执行以前就已决定了的,无须循环次数控制增量表达式及其计算。语句中的“循环次数表达式”可以是数值确定的整数、变量或定义了常数的参数标识符等。
例2-16中的repeat语句的循环次数一开始就利用常数定义语句,确定了循环次数S=4。此例的仿真结果也是图2-12,综合结果也与例2-14相同。
2.4.7 while语句用法
例2-17给出了利用while语句实现4位乘法器的设计程序。其中的表述CT= CT-1,即为“循环控制变量增值表达式”。显然,for语句和while语句最为相似。
【例2-16】
module MULT4B(R,A,B); parameter S=4; output [2*S:1] R; input [S:1] A,B ; reg [2*S:1] TA,R; reg [S:1] TB; always @(A or B) begin R=0 ; TA=A ; TB=B ; repeat(S) begin if(TB[1]) begin R=R+TA; end TA=TA<<1; TB=TB>>1; end end endmodule
【例2-17】
module MULT4B(A,B,R); parameter S=4; input[S:1] A,B; output[2*S:1] R; reg[2*S:1] R,AT; reg[S:1] BT,CT; always@(A or B) begin R=0; AT={{S{1'b0}},A}; BT=B; CT=S; while(CT>0) begin if(BT[1]) R=R+AT; else R=R; begin CT= CT-1; AT=AT<<1; BT=BT>>1; end end end endmodule
while语句的一般格式可表述如下:
while(循环控制条件表达式) begin 循环体语句结构 end
此语句执行时,首先根据“循环控制条件表达式”的计算所得,判断是否满足继续循环的条件,如果为真,执行一遍“循环体语句结构”中的所有语句;若为伪,即不满足循环表达式的条件,则结束循环。对于此种循环语句,必须在“循环体语句结构”中包含类似for语句的“循环控制变量增值表达式”,以便其计算结果在条件式中做比较。
读者还能通过以上4则乘法器设计实例很好地研究阻塞式赋值和非阻塞式赋值的使用特点。以例2-16为例,如果对其中的变量TA、TB都换做非阻塞式赋值,则整个设计结果将完全不同。因为,如前所述,只有在过程结束后才会把过程中所有非阻塞语句右端的计算结果赋给左端的信号,即寄存器。所以,如果采用非阻塞赋值,则会造成循环语句只执行一次,整个乘法器电路将无法构建,输出将恒为0。所以在这里必须使用阻塞式赋值语句。
2.4.8 Verilog循环语句的特点
在Verilog循环语句的使用中,需要特别注意不要把它们混同于普通软件描述语言中的循环语句。作为硬件描述语言的循环语句,每多一次循环就要多加一个相应功能的硬件模块。因此,循环语句的使用要时刻关注逻辑资源的耗用量和利用率,以及可用资源的大小等因素,尽量要求性能与硬件成本成正比。
在软件语言中,只要时间允许,无论循环多少次都不会额外增加任何资源和成本。此外,与软件语言编程不同,基于硬件语言的程序优劣的标准不是程序的规范、整洁、短小精干或各类运算符号和函数的熟练应用等,而是高性能、高速度和高资源利用率,这些指标的实现与程序的表达形式几乎没有关系。
例如,例2-14至例2-17的形式虽美,但却没有太多实用价值。因为随着乘法器位数的增加,构成其电路模块的加法器和多路选择器的数量和位数也将大幅增加,从而导致其资源的大幅耗用,工作速度则不断降低。
2.4.9 parameter的参数传递功能
这里将给出一个示例,说明parameter的参数传递功能。事实上使用parameter的目的除了能用标识符定义一些常数外,另一重要用途是通过例化语句来传递参数,以便仅通过上层设计中的相关参数的改变来轻易地改变底层电路的结构功能与逻辑规模。
这里从例2-16出发,简要说明parameter在参数传递上的用法。首先将例2-16看成是一个底层乘法器元件,然后通过上层设计文件对此元件的例化,以及乘法器位数参数的传递,即可随意改变此乘法器的数据规模。
为了达到这个目的,首先应该改写例2-16中parameter表述的方式。即只需将此例中最上面的两条语句“module MULT4B(R,A,B)”和“parameter S=4”改写成以下形式:
module MULT4B #(parameter S=4)(R,A,B); 或 module MULT4B #(parameter S)(R,A,B);
顶层设计如例2-18所示。此例只是对例2-16做了简单的例化,但在例化过程中将参数S=8传递进了底层设计例2-16的MULT4B模块,从而使例2-18综合后的结果成为一个8位乘8位,输出结果是16位的乘法器。
【例2-18】
module MULTB (RP,AP,BP); output[15:0] RP ; input[7:0] AP,BP ; MULT4B #(.S(8)) U1(.R(RP), .A(AP), .B(BP) ); endmodule
注意例2-18中的例化语句表述方法。其中第3行中的MULT4B是模块元件名,U1是例化元件名,模块是修改后的例2-16。#号旁的括号是参数传递表,其中的参数名和数量应该与底层文件中的表述相同,例如若原底层文件的模块语句和参数表述是:
module SUB_E #(parameter S1=4,parameter S2=5,parameter S3=2)(A,B,C);
则在例化语句中应做如下类似表述:
SUB_E #(.S1(8), .S2(9), .S3(7)) U1(.C(CP), .A(AP), .B(BP) );
SUB_E是模块元件名,即底层模块名;同样,诸如参数定义parameter S2=5可省略为parameter S2。这是因为当一个底层模块被调用后,其内部原来被parameter定义的参数5已经失效,而对应的参数将来自上层例化语句给定的数据9。
显然,对于此类语法表述中,localparam无法替代parameter。