EDA技术及其创新实践(Verilog HDL版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 多路选择器不同形式的Verilog描述

本节通过使用Verilog不同的表述方式来描述同一逻辑模块,即4选1多路选择器,从而引出许多新的,且十分重要的Verilog语法知识和编程要点,使读者加速掌握Verilog对组合电路描述的核心语法规则和基本设计方法。

2.2.1 4选1多路选择器及其顺序语句表述方式

4选1多路选择器的电路模型或元件图如图2-4所示,图中,a、b、c、d是4个输入端口;s1和s0为通道选择控制信号端,y为输出端;当s1和s0取值分别为00、01、10和11时,输出端y将分别输出来自输入口a、b、c、d的数据。图2-5是此模块(设此模块名是MUX41a)电路的时序波形。图中显示,当a、b、c、d四个输入口分别输入不同频率信号时,针对选通控制端s1、s0的不同电平选择,则有对应的信号输出。例如当s1和s0都为低电平时,y口输出了来自a端的最高频率的时钟信号。图中的S是选通信号s1和s0的矢量或总线表达信号。

图2-4 4选1多路选择器

图2-5 4选1多路选择器的时序波形

根据此电路的功能要求,可得到相应的逻辑描述。例2-4给出了此电路模块的Verilog完整描述,即可用Verilog综合器直接综合出实现此模块功能的逻辑电路。

与例2-1相比,除了包含以关键词module_endmodule引导的电路模块定义语句和以input和output引导的对模块的外部端口的语句外,例2-4中还包含以下5方面新的语句结构和表述方式:

(1)以reg等关键词定义的模块内相关信号的特性和数据类型。

(2)以always等关键词引导的对模块逻辑功能描述的顺序语句。

(3)以case_endcase引导的多条件分支赋值语句。

(4)begin_end顺序块语句。

(5)Verilog数据并位及数据表达方式。

【例2-4】

module MUX41a (a,b,c,d,s1,s0,y) ;
    input a,b,c,d,s1,s0 ;  output y ;
    reg y ;         //定义输出端口信号y为寄存器型变量
    always @ (a or b or c or d or s1 or s0)
        begin : MUX41
            case({s1,s0})
                2'b00 : y <= a;
                2'b01 : y <= b;
                2'b10 : y <= c;
                2'b11 : y <= d;
                default : y <= a;
            endcase
        end
endmodule

以下将对此例中出现的这些新的语句结构、语法含义和使用方法做出说明。

1.reg型变量定义

例2-4中的关键词reg主要用于定义特定类型的变量,即寄存器型变量或称寄存器型数据类型的变量。在此例中,输出端口y被定义为寄存器型(register)变量。Verilog中最常用的变量(variable)是寄存器型变量和网线型(net,也称线性变量,即电路连接线的意思)数据类型的变量(常用关键词wire等来定义)。根据Verilog程序的描述情况,这些变量分别对应于不同的电路结构和电路连线。

对于模块中功能描述涉及的所有信号都必须定义相应的变量类型。如果没有在模块中显式地定义信号的类型,Verilog综合器都将其默认定义为wire型。

事实上,根据Verilog语法规则,例2-4中的所有端口信号都已默认为wire类变量的数据类型,而当需要信号y为寄存器类型(或称变量类型)时必须使用关键词reg进行显式定义。这是因为被赋值的信号y在过程语句always@引导的顺序语句中规定必须是reg型变量。

回过头去看例2-1,由于由assign引导的赋值语句中信号的类型规定为wire类型,而端口信号都已默认为wire类型,故无须再以显式对y重复定义为wire类型。

实际上,用reg定义的寄存器型变量并非一定会在Verilog程序中映射出时序电路。例如例2-4中,y被定义为寄存器型变量,但由于程序的特定描述,例2-4的综合结果是一个纯组合电路模块。这里,定义reg类型只是always过程语句的需要和语法规则,至于最终是否综合出组合电路或时序电路,则取决于过程语句中的描述方式。另外请注意,输入端口或双向端口信号是不能定义为寄存器型信号类型的。

寄存器型变量的定义格式如下:

reg 变量名1,变量名2,...;
reg [msb:lsb] 变量名1,变量名2,...;

第一条语句是针对一位变量的,第二条语句则是针对矢量型变量的。reg是定义寄存器型变量的关键词。例如,定义总线a[7:0]为寄存器型变量的表式是:reg [7:0] a ;

事实上,根据Verilog-2001版本,还允许在端口名表中对于需要定义矢量,或者说是总线端口的情况,甚至定义端口数据类型的情况,也可一并直接放在端口名表中。例如,定义输入端口num是一个3位矢量,定义输出端口seg是一个寄存器数据类型的7位总线,其表述为

module seg_7 (input [3:0] num, input en, output reg [6:0] seg );

2.过程语句

Verilog中有两类能引导顺序语句的过程语句,always在可综合语句中最为常用。例2-4中出现的由“always @”引导的过程语句结构是Verilog语言中最常用和最重要的可综合语句结构。之所以要称其为某语句结构(或称语句块),是因为它不是一条简单意义上的单独的语句,它总是和其他相关语句一起构成一个满足语法规则的程序块,而过程语句成为这个程序块的引导语句。此类语句,Verilog有许多,需要注意辨别。

设计模块中的任何顺序语句都必须放在过程语句结构中。过程语句的格式如下:

always @(敏感信号及敏感信号列表或表达式)
包括块语句的各类顺序语句

过程语句首先需用关键词always @ 引导,其右侧的括号及括号中所列的信号或表达式都属于敏感信号。通常要求将过程语句中所有的输入信号都放在敏感信号表中。例如例2-4中就将所有输入信号列入了敏感信号表。表中的敏感信号表述方式有多种:

(1)用文字or连接所有敏感信号。例2-4正是采用了这种表述方式。由于敏感表括号中列出的所有信号对于启动过程都是逻辑或的关系,即每当其中任何一个或多个信号发生变化时,都将启动过程语句,执行一遍此结构中的所有程序语句。

(2)按照Verilog HDL—2001新的规范,可用逗号区分所有敏感信号。例如可以将例2-4的敏感信号表写成(a,b,c,d,s1,s0)。

(3)省略形式。由于目前的Verilog主流综合器都默认过程语句中敏感信号表中列全了所有应该被列入的信号,所以即使设计者少列、漏列部分敏感信号,也不会影响综合结果,最多在编译时给出警告信息。所以有时也可干脆不写出具体的敏感信号,而只写成( * ),或直接写成 always @ *。这都符合Verilog HDL-2001规范。

显然试图通过选择性地列入敏感信号来改变逻辑设计是无效的。

3.块语句begin_end

例2-4中用到了块语句,即由关键词begin_end引导的case语句结构。块语句begin_end本身没有什么功能,仅限于always引导的过程语句结构中使用,通常用它来组合顺序语句,故也称其为顺序块语句(相对应的是并行块语句,此语句不可综合,只能用于仿真)。begin_end语句只相当于一个括号,在此“括号”中的语句都被认定归属于同一操作模块。如例2-4中的begin_end中包含了一个case语句结构。Verilog规定,若某一语句结构中仅包含一条语句,且无须定义局部变量时,则块语句被默认使用,即无须显式定义块(即使以显示定义后也不认为错);若含多条语句,也包括含有局部变量定义的单条语句,则必须用begin_end的显式结构将它们“括”起来,从而成为一个具有顺序执行特征的逻辑结构。

尽管从理论上说,块语句begin_end引导的是顺序语句,但由于其中的赋值语句有两类,其中一类执行方式具有并行特征,这将在本书后面详细讨论,望读者关注。

begin_end块语句的一般格式如下:

begin [: 块名]
   语句1;语句2;...;语句n;
end

以上方括号中的“:块名”可以省略。例2-4中的begin右侧的“:MUX41”即为块名,可用于注释当前块的特征等。综合时不参加编译,因此也可不加。

其实例2-4中的begin_end语句是可以不加的,这是因为case_endcase条件语句也是一个结构,只能算做一条语句,尽管它表面上包含了多条不同形式的语句描述。

4.case条件语句

例2-4中case语句的含义是,当满足case右侧括号中的选通信号s1、s0分别等于00、01、10或11时,输入口a、b、c、d将相应的信号传送至输出口y。case语句属于顺序语句。Verilog有两类条件语句,即if_else语句和case_endcase语句,它们都属于最常用的可综合顺序语句,因此必须放在过程语句always中使用。case语句是一种多分支语句,是一种类似真值表直接表述方式的描述,特点是直观、直接和层次清晰。它在电路描述中具有广泛而又独特的应用。case语句的表述方式有3种,即case、casez和casex表述的case语句。以下先讨论case的一般表述。case语句的一般格式如下:

case (表达式)
    取值1 : begin 语句1;语句2; ...; 语句n;  end
    取值2 : begin 语句n+1;语句n+2; ...语句n+m; end
      ...
      default : begin 语句n+m+1; ...; end
endcase

当执行到case语句时,首先获得或计算出“表达式”中的值,然后根据以下条件句中与之相同的值(如“取值1”),执行对应的顺序语句(如其后列出的语句1;语句2等),最后结束case语句,等待下一次过程的启动。case语句下的条件句中的冒号“:”不是操作符,它的含义相当于“于是,Then”。

case语句使用中应该注意以下3点:

(1)条件句中的选择值或标识符,即(表达式)中的值必须在case以下列出的取值范围内,且数据类型必须匹配。如例2-4的s1、s0只能对应2位二进制数。

(2)与VHDL不同,Verilog的case语句各分支表达式间未必是并列互斥关系。允许出现多个分支取值同时满足case表达式的情况。这种情况下将执行最先满足表达式的分支项,然后随即跳出case语句,不再检测其余分支项目。

(3)除非所有条件句中的选择取值能完整覆盖case语句中表达式的取值,否则最末一个条件句中的选择必须加上default语句。关键词default引导的语句表示本语句完成以上已列的所有条件句中未能列出的其他可能取值的逻辑操作。其含义类似于if语句中的else。但如果以上给出的所有选择条件或数据都涵盖了case语句(表达式)中的数据,则可省略default语句。从逻辑设计的角度看,使用default语句的目的是为了使条件句中的所有选择值能涵盖表达式的所有取值,以免综合器会插入不必要的锁存器。

5.Verilog的4种逻辑状态

Verilog中有4种基本数值,或者说任何变量都可能有4种不同逻辑状态的取值:

1、0、z和x,它们的含义有多个方面:

0:含义有4个,即二进制数0、低电平、逻辑0、事件为伪的判断结果;

1:含义也有4个,即二进制数1、高电平、逻辑1、事件为真的判断结果;

z或Z:高组态,或高阻值。高阻值还可以用问号“?”来表示。但问号“?”还有别的含义和用处,即代表“不关心”的意思,因此可以用问号?替代一些位值,以表示在逻辑关系中对这些位不在乎是什么值,以便简化逻辑表述。

再次提醒,尽管表面上例2-4中,{s1,s0}所有可能的数据都已出现在4条条件语句中了,但仍然推荐加上default语句,以免有的综合器会加上不必要的时序模块。因为在更一般的情况下{s1,s0}需考虑取值z和x。

x或X:不确定,或未知的逻辑状态。此值与z都不分大小写;

作为case语句的对应语句,还有casez和casex语句。因为当变量取值为高阻值z和未知值x时,需要使用对应的casez或casex语句。

对于casez语句,如果分支取值的某些位是高阻值z,则这些位的比较就不再考虑,只关注其他位(1或0)的比较结果;而casex语句则把这种比较方式扩展到对未知值x的处理,即若比较双方(语句中表达式的值和取值间)有一方的的某些位为z或x,那么这些位的比较就不予以考虑。

case语句属于行为描述语句,因为它主要是通过对模块所界定的功能和行为,而非具体的电路结构进行表述的。但在传统数字电路设计中,主要是通过对电路的结构,或其间的逻辑关系的描述或组织来实现模块功能的,这就是所谓的结构描述。如通过对基本逻辑元件按不同的连接方式,将获得不同的逻辑功能。显然,基于现代EDA技术的行为描述在设计效率等方面具有更高的层次,更适合于大规模系统的设计。

6.并位操作运算符

例2-4中的case语句的功能是,当case语句的表达式 {s1,s0}=2'b00 时就执行赋值语句y <= a ;这里大括号 {} 是并位运算符。{}可以将两个或多个信号按二进制位拼接起来,作为一个数据信号来使用。如例2-4的选通信号s1、s0的取值范围是二进制数1和0,而若将它们用并位算符拼接起来就得到一个新的信号变量,即位矢:{s1, s0}。这时这个新的信号的取值范围是两位二进制数:00、01、10、11。

并位操作符也可以嵌套使用,用于简化某些重复表述,例如:

{a1, b1, 4{a2,b2}}={ a1, b1, {a2,b2},{a2,b2},{a2,b2},{a2,b2}}={a1,b1,a2,b2,a2,b2,a2,b2,a2,b2}

7.Verilog的数字表达形式

例2-4中的2'b00等数据是Verilog对二进制数的一种表述方式。

Verilog中表示一个二进制数的一般格式是:

<位宽> '<进制> <数字>

即一撇左侧的十进制数表示此数的二进制位数,一撇右侧的字母指示其右侧数据的进制。B表示二进制,O表示八进制,H表示16进制,D表示十进制,且不分大小写。例如,2'b10表示两位二进制数10;4'B1011表示四位二进制数1011;4'hA表示四位二进制数1010;3'D7表示三位二进制数111,等等。

在Verilog中只有标明了数制的数据才能确定其二进制位数。如式S[3:0]=1的等号右侧的1等于二进制数0001,正式表达为4'B0001;式S[5:0]=7的7等于6'b000111;而5'bz可正式表为5'bzzzzz。又若将某标识符,如R,定义为位矢,如reg[7:0] R;则赋值语句中的R<=4的4的正式表述是8'b00000100。

Verilog-2001规范可定义有符号二进制数,如8'b10111011和8'sb10111011是不一样的,前者是普通无符号数,后者是有符号数,其最高位1是符号。sb是定义有符号二进制数的进制限定关键词。

显然,根据例2-4的描述,当always的敏感信号表中的任意一个输入信号发生改变时将执行下面的case语句,若这时case表达式中的s1=0,s0=0,即{s1,s0}=00,将执行此语句的第一条条件语句:2'b00 : y<=a;即执行赋值语句y<=a,于是将输入端口a的数据赋给输出端y。在这里,符号<=是赋值符号,只能用于顺序语句中。而例2-1中的赋值符号“=”只能用在assign引导的并行语句中。

至此已对例2-4中的所有语法现象做了介绍,此后读者不妨自己动手练习设计程序。

2.2.2 4选1多路选择器及其并行语句表述方式

对于以上的多路选择器可以有多种不同的描述方法,本节及随后几节再给出几则使用不同语句形式描述的同一逻辑模块:例2-5、例2-6、例2-7和例2-9。尽管它们有不同的描述方式和语句特点,但其功能和仿真波形都与图2-5相同。这些示例将再次从不同侧面向读者展示Verilog程序对组合电路的描述方法和相关的语句用法。

【例2-5】

module MUX41a (a,b,c,d,s1,s0,y);
    input a,b,c,d,s1,s0;    output y;
    wire [1:0] SEL;     // 定义2元素位矢量SEL为网线型变量wire
    wire AT, BT, CT, DT;  //定义中间变量,以作连线或信号节点
    assign SEL={s1,s0};  //对s1,s0进行并位操作,即SEL[1]=s1;SEL[0]=s0
    assign AT=(SEL==2'D0); //assign语句中的输出变量必须是网线型变量
    assign BT=(SEL==2'D1);
    assign CT=(SEL==2'D2);
    assign DT=(SEL==2'D3);
    assign y=(a & AT)|(b & BT)|(c & CT)|(d & DT);//4个逻辑信号相或
   endmodule

与例2-1类似,例2-5也采用了常被称为数据流的逻辑描述方式。其实就是直接用布尔逻辑表式来描述模块的功能。以下讨论例2-5中出现的新的语法现象。

1.按位逻辑操作符

与例2-1相同,例2-5也包含了利用逻辑操作符,即逻辑与“&”和逻辑或“|”,来实现逻辑功能的语句。

在Verilog中最常用的针对位的基本逻辑操作或称逻辑算符的功能及用法如表2-1所示。由表中的示例结果可知,逻辑操作是按位进行的。如果两个操作数位矢具有不同长度,综合器将自动根据最长位的操作数的位数把较短的数据按左端补0对齐的规则进行运算操作。在赋值语句中,逻辑操作和以下将谈到的运算操作结果的位宽是由操作表达式左端的赋值目标信号的位宽来决定的。通过表2-1,读者可以更详细地了解逻辑操作符的功能细节。

表2-1 逻辑操作符

2.等式操作符

例2-5的表达式(SEL= =2'D0)中有一等式操作符。等式操作符运算的结果是1位逻辑值,也等于1位二进制数,这在Verilog中是不分的,但在VHDL中则完全不同。

Verilog中有4种等式操作符,如表2-2所示。若等式操作的结果为1,则表明关系为真;结果为0,则表示关系为假。用等于操作符 == 做比较,两个二进制数将被逐位比较;如果两个数的位数不等,则自动将较少位数的数的高位补0对齐再进行比较;这时当每一位都相等时,才输出结果1,否则为0;此外,如果其中有的位是未知值x或高阻值z,则都判定为假,输出0。

表2-2 等式操作符

与==不同,全等比较操作符===则将x或z都当成确定的值进行比较,当表述完全相同时输出1。此外,在做全等比较时,对于两个比较数位数不等的情况,不会像处理操作符“= =”那样做高位补0操作,而会直接判断两数据不等。

3.wire定义网线型变量

例2-5中含有使用了wire的语句。如果assign语句中需要有端口以外的信号或连线性质的变量(由于端口都已默认为网线型变量),特别是考虑到assign语句中的输出信号变量(或者说是赋值目标变量,如例2-5中的AT、BT等)必须是网线型变量,则必须用网线型变量定义语句事先给出显式定义。wire的具体定义格式如下:

wire 变量名1,变量名2,...;
wire [msb:lsb] 变量名1,变量名2,...;

其定义格式与reg相同。第一条语句是针对1位变量的,第二条语句则是针对矢量型或总线型变量的。wire是定义网线型变量的关键词。

例如,定义矢量位a[7:0]为网线型变量的表述是:wire [7:0] a ;

用wire定义的网线型变量可以在任何类型的表达式或赋值语句(包括连续赋值和过程赋值语句中)中用做输入信号,也可在连续赋值语句或实体元件例化中用做输出信号。

由于wire和assign在表达信号及信号赋值性质上的一致性,因此还能用wire来表达assign语句。如可以用一条语句

wire Y=a1 ^ a2 ;

取代以下两条语句

wire a1, a2;
assign Y=a1 ^ a2;

2.2.3 4选1多路选择器及其条件操作语句表述方式

例2-6就是利用关键词wire来替代assign直接引导连续赋值语句的。此示例用连续赋值语句中的条件操作符描述了一个4选1多路选择器。图2-6是QuartusⅡ软件对例2-6综合后生成的RTL电路图。电路包含了三个2选1多路选择器,分别对应3条wire语句。程序与电路显示了很直观的对应关系。

【例2-6】

module MUX41a (A,B,C,D,S1,S0,Y);
   input A,B,C,D,S1,S0;
   output Y;
//若S0=1,则AT=D;若S0=0,则AT=C
   wire AT=S0 ? D : C ;
//若S0=1,则BT=B;若S0=0,则BT=A
   wire BT=S0 ? B : A ;
//若S1=1,则Y=AT;若S1=0,则Y=BT
   wire Y=(S1 ? AT : BT);
endmodule

图2-6 例2-6的RTL图

例2-6中的三个赋值语句是条件赋值语句,此语句使用了条件操作符“? :”。条件操作符用法的一般格式如下:

条件表达式 ? 表达式1 :表达式2

即当条件表达式的计算值为真时(数值等于1),选择并计算表达式1的值,否则(数值等于0)选择并计算表达式2的值。基于条件操作符“? :”的逻辑表达式和赋值语句,在连续赋值语句和过程赋值语句结构中都可以使用,即在并行赋值语句或顺序赋值语句中都可使用。

以例2-6中的语句“ wire AT=S0 ? D : C ;”为例,其功能是,如果S0=1成立,则AT=D;如果S0=0成立,则AT=C。这显然是一个2选1多路选择器的逻辑描述。

2.2.4 4选1多路选择器及其条件语句表述方式

例2-7给出了利用了if_else条件语句描述此模块的程序。例2-7和例2-4有相似性,因为都使用了以过程语句always引导的顺序语句;更重要的是,两程序都采用了Verilog中最适合描述复杂逻辑系统的行为描述语句。

【例2-7】

module MUX41a (A,B,C,D,S1,S0,Y);
    input A,B,C,D,S1,S0;   output Y;
    reg [1:0] SEL ;  reg Y;
    always @(A,B,C,D,SEL)
      begin             //块语句起始
            SEL={S1,S0};    //把S1,S0并位为2元素矢量变量SEL[1:0]
        if (SEL==0) Y=A;   //当SEL==0成立,即(SEL==0)=1时,Y=A;
   else if (SEL==1) Y=B;   //当(SEL==1)为真,则Y=B;
   else if (SEL==2) Y=C;   //当(SEL==2)为真,则Y=C;
   else         Y=D;    //当SEL==3,即SEL==2’b11时,Y=D;
      end              //块语句结束
  endmodule

以下对例2-7的程序中出现的一些新的语言现象做一些解释:

1.if_else条件语句

在例2-7中,当过程always被启动后,即刻顺序执行此结构所包含的语句。首先执行“SEL= {S1,S0};”即将由S1、S0并位所得的两位二进制数赋给变量SEL。然后计算出以下的if语句的条件表达式的值。条件表达式(SEL= =0)的计算方法是这样的:若SEL等于00,则(SEL==0)=1,表示满足条件,即执行赋值语句“Y=A;”。即将输入口A的数据赋给输出变量Y。否则,即SEL不等于00,表达式(SEL= =0) =0,随即执行else后的if语句。以如此顺序执行下去,直到完成所有if条件语句。

例2-7给出的是多条件情况。如果只有一个条件,可以表述为:

if (S) Y=A; else Y=B;

这是一个2选1多路选择器的if语句表述方式,即当控制信号S=1时,条件式为真,执行赋值语句Y=A; 而当S=0,条件式为伪时,执行赋值语句Y=B;。

同样,当需要执行的语句有多条时,应该用begin_end块语句将它们“括”起来。例如:

if (S) Y=A; else
begin Y=B; Z=C; Q=1’b0; end

使用if_else语句时必须注意,无论其条件表式是什么形式,都必须用括号括起来,如:if (S)、if (SEL= =1)、if (Y= =a&b),等等。这点也与VHDL不同。

2.过程赋值语句

从以上的一些示例中,读者不难发现,有两种不同的赋值符号,即“=”和“<=”。在过程语句中,Verilog中有以下两类赋值方式,对应这两种赋值符号:

(1)阻塞式赋值

Verilog中,用普通等号“=”作为阻塞赋值语句的赋值符号,如y=b。阻塞式赋值的特点是,一旦执行完当前的赋值语句,赋值目标变量y即刻获得来自等号右侧表达式的计算值。如果在一个块语句中含有多条阻塞式赋值语句,而当执行到其中某条赋值语句时,则其他语句被禁止执行,这时其他语句如同被阻塞了一样。其实阻塞式赋值语句的特点与C等软件描述语言十分类似,都属顺序执行语句,因为顺序语句都具有类似阻塞式的执行方式,即当执行某一语句时,其他语句不可能被同时执行。

显然,阻塞式赋值符号“=”的功能与VHDL的变量赋值符号“:=”的功能十分类似。

需要注意的是,assign语句和always语句中出现的赋值符号“=”(例2-6、例2-7)从理论上说是不同性质的,因为前者属于连续赋值语句,具有并行赋值特性,后者属于过程赋值类中的顺序赋值语句。但从综合角度看其结果则常常是相同的。因为assign语句不能使用块语句,故只允许引导一条含“=”号的赋值语句;而在assign语句作为并行语句的限制下,即使其中的语句具备顺序执行功能,也无从发挥。

(2)非阻塞式赋值

非阻塞式赋值的特点是,必须在块语句执行结束时才整体完成赋值操作。非阻塞的含义可以理解为,在执行当前语句时,对于块中的其他语句的执行情况一律不加限制,不加阻塞。这也可以理解为,在begin_end块中的所有赋值语句都可以并行运行,即实际是如此的(主要指综合),但理论却不是这样(主要指基于testbench的仿真),所有这些都需要读者在实践中注意体会。

事实上,例2-4与例2-7中的赋值符号是可以互换的,即可将程序中所有的阻塞式赋值符号“=”换成非阻塞式赋值符号“<=”,或反之,且功能不变。但是,在一般情况下,事情并非总是这样,即在许多情况下,不同的赋值符号将导致不同的电路结构和逻辑功能的综合结果。因此,准确无误地理解和使用阻塞式与非阻塞式的赋值方式,对正确编程设计十分重要。对于这两种赋值方式本书后面还将深入讨论。另外需要特别注意,在同一过程中对同一变量的赋值,阻塞式赋值和非阻塞式赋值不允许混合使用。

3.数据类型表示方式

读者或许已注意到,在以上的示例中出现了不少相同含义却不同数值表述方式的情况。例如对于式(SEL= =2),似乎SEL与2数据类型不匹配,因为前者的类型是二进制矢量位SEL[1:0],而后者属于整数类型。

对于类似式(SEL= =2)等不匹配的情况,Verilog综合器会自动使其匹配。例如将其中的整数2变换成与SEL[1:0]同类型的二进制数2'b10。所以{S1, S0}=2'b10,(SEL= =2),(SEL==2'D2)三式的含义相同。但如果所赋的值大于某变量已定义的矢量位可能的值,综合器又该如何处理呢?这时,综合器会首先将赋值符号右侧的数据折算成二进制数,然后根据被赋值变量所定义的位数,向左截取多余的数位。

这与VHDL有较大的不同,Verilog具备通过赋值操作达到自动转换数据类型的功能。例如若信号Y定义为Y[1:0],当编译赋值语句Y<=9时,综合器不会报错,最后Y得到的赋值是2'B01,即截去了高位的10。

这里,Verilog综合器至少包含了两项大的操作,即将整数数据类型转换为二进制矢量位数据类型的操作,以及截位以适应目标变量的操作。反之亦然,即通过矢量位向整数类型变量的赋值即可实现反方向的数据类型转换。

显然,Verilog的语法规则比VHDL要宽松多了,所以初学者容易入门。但正因为如此,Verilog的程序设计更需当心,其排错查错时可能需要花费更多的力气。

2.2.5 4选1多路选择器及其利用UDP元件的结构表述方式

例2-8和例2-9展示了使用用户自定义元件UDP的方式来设计4选1多路选择器的方法。其中的语句含义及编程规则,在2.1.2节中已有详细说明。需要关注的是,问号?在这里的含义和用法(?的含义在2.2.1节中已做了说明),以及例2-9中调用元件MUX41_UDP的端口排列顺序必须与例2-8的端口定义的顺序要一致。

【例2-8】

primitive
MUX41_UDP(Y,D3,D2,D1,D0,S1,S0);
  input D3,D2,D1,D0,S1,S0; output Y;
    table //D3 D2 D1 D0 S1 S0 : Y
            ? ? ?  1  0 0 : 1;
            ? ? ?  0  0 0 : 0;
            ? ? 1  ?  0 1 : 1;
            ? ? 0  ?  0 1 : 0;
            ? 1 ?  ?  1 0 : 1;
            ? 0 ?  ?  1 0 : 0;
            1 ? ?  ?  1 1 : 1;
            0 ? ?  ?  1 1 : 0;
  endtable
endprimitive

【例2-9】

module MUX41UDP (D,S,DOUT) ;
   input [3:0] D;
   input [1:0] S;
   output DOUT;
  MUX41_UDP (DOUT,D[3],D[2],
      D[1],D[0],S[1],S[0]);
endmodule