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

2.1 半加器电路的Verilog描述

本节将通过介绍半加器电路模块的Verilog描述和设计,给出相关的语法规则说明,由此引导读者迈入对Verilog逐步了解和编程设计的学习历程。

2.1.1 半加器的数据流建模描述方式

在数字电路中,半加器具备了组合逻辑电路的简单性和典型性的特征。这里首先以此电路模块来考察其对应的Verilog表述及其设计,从而引出相关的Verilog基本结构、语句表述、数据特点和语法规则的说明和讨论,使读者能够借此迅速了解使用Verilog进行组合电路描述的关键语法规则和基本设计方法。

半加器模块(假设此模块的器件名是h_adder)的电路结构如图2-1所示,半加器对应的逻辑真值表如图2-2所示。此电路模块由两个基本逻辑门元件构成:与门和异或门。图中的A和B是加数和被加数的数据输入端口;SO是和值的数据输出端口;CO则是进位数据的输出端口。根据图2-1,很容易获得半加器的逻辑表述:

SO=A⊕B; CO=A·B

图2-3是此半加器电路的仿真时序波形,它反映了此模块的逻辑功能。可以认为半加器模型与图2-3的功能表述具有唯一对应关系,而图2-1的电路结构表述却没有唯一性。即对于既定的电路功能描述,对应的电路结构并不是唯一的,它可以对应不同的电路结构方式,这取决于Verilog综合器的基本元件库的来源、优化方向和约束的选择,以及目标器件,如FPGA的结构特点等。注意,在基于EDA的数字系统设计中更注重最终完成的电路的功能和性能(包括系统速度,资源利用率等)而非电路构建的形式。

图2-1 半加器的电路结构

图2-2 半加器的真值表

图2-3 半加器的仿真时序波形

根据以上的叙述和讨论,可以给出例2-1所示的半加器电路模块的Verilog完整描述之一。此描述展示了可综合的Verilog程序的模块结构。对于此程序,可使用Verilog综合器直接综合出实现此模块功能的逻辑电路。

【例2-1】

module h_adder (A,B,SO,CO) ;
    input A,B;
    output SO,CO;
    assign SO=A ^ B;
    assign CO=A & B;
endmodule

由例2-1可见,此电路的Verilog描述由如下三部分组成:

(1)以Verilog语言的关键词module_endmodule引导的完整的电路模块,或称“模块”的描述,模块对应着硬件电路上的逻辑实体(也称实例instance)。

关键词module与endmodule就像一个括号,任何一个功能模块的描述都必须放在此“括号”中;module旁的标识符h_adder是设计者为其设计的模块所取的名称,也可称为模块名。模块名h_adder旁的括号及其内容称为“端口表”,括号中的内容就是此模块的所有端口信号名。

(2)以input和output等关键词引导的对模块的外部端口的语句,用来描述电路器件的端口情况及各信号的性质,如信号流动的方向和信号的数据类型等。

(3)以关键词assign引导的赋值语句,用于描述模块内部的逻辑功能和电路结构。

以下将对例2-1中出现的相关语句结构和语法含义做出说明。这些内容包含了Verilog对组合电路描述和设计的最基本与核心的语法知识。

1.模块语句及其表达方式

前面谈到一个电路模块是以Verilog的关键词module_endmodule引导的。Verilog完整的、可综合的程序结构能够全面地表达一个电路模块,或一片专用集成电路ASIC的端口结构和电路功能,即无论是一片74LS138还是一个CPU,都必须包含在模块描述语句module_endmodule中。模块语句的一般格式如下:

module 模块名 (模块端口名表) ;
   模块端口和模块功能描述 .
endmodule

此表达格式说明,任一可综合的最基本的模块都必须以关键词module开头;在module右侧(空一格或多格)是模块名。模块名属于标识符,具体取名由设计者自定。由于模块名实际表达的应该是当前设计电路的器件名,所以最好根据相应电路的功能来命名。如半加器,模块名可用h_adder;4位二进制计数器,可用counter4b;8位二进制加法器,则可取名为ADDER8B等。但应注意,不应用数字或中文定义实体名,也不应用与EDA软件工具库中已定义好的关键词或元件名作为模块名,如or2、latch等;且不能用数字起头的模块名,如74LS160。

模块名右侧的括号称为模块端口列表,其中须列出此模块的所有输入、输出或双向端口名;端口名间用逗号分开,右侧括号外加分号。端口名也属标识符。

endmodule是模块结束语句,旁边不加任何标点符号。对模块的端口和功能描述语句都必须放在模块描述语句module_endmodule之间。

2.端口语句、端口信号名和端口模式

端口或端口信号是模块与外部电路连接的通道,这如同一个芯片必须有外部引脚一样,必须具有输入输出或双向口等引脚,以便与外部电路交换信息。

通常,紧跟module语句后面的是端口语句和端口信号名,它们将对module旁的端口列表中的所有端口名做更详细更具体的说明。

端口定义关键词有3种:input、output、inout。端口定义语句的一般格式如下:

 input 端口名1,端口名2,...;
output 端口名1,端口名2,...;
 inout 端口名1,端口名2,...;
 input [msb : lsb] 端口名1,端口名2,...;

端口关键词旁的端口名可以有多个,端口名间用逗号分开,最后加分号。在例2-1的实体描述中,用input和output分别定义端口A、B为信号输入端口,SO、CO为信号输出端口,都属于单逻辑位或标量位。对于“位”,也有直接翻译成“比特”的。

如果要描述一个多信号端口或总线端口,则必须使用以上第4句的端口描述方法。此句是端口信号的逻辑矢量位表达方式,其中的msb和lsb分别是信号矢量的最高位和最低位数。例如,信号C,D可定义为:

output [3:0] C,D;

表示定义了两个4位位宽的矢量或总线端口信号C[3:0],D[3:0]。例如对于C[3:0],等同定义了4个单个位的输出信号,它们分别是C[3]、C[2]、C[1]、C[0]。

Verilog的端口模式有如下3种,用于定义端口上数据的流动方向和方式:

(1)input:输入端口。定义的通道为单向只读模式,即规定数据只能由此端口被读入模块实体中。

(2)output:输出端口。定义的通道为单向输出模式,即规定数据只能通过此端口从模块实体向外流出,或者说可以将模块中的数据向此端口赋值。

(3)inout:双向端口。定义的通道确定为输入输出双向端口,即从端口的内部看,可以对此端口进行赋值,或通过此端口读入外部的数据信息;而从端口的外部看,信号既可由此端口流出,也可向此端口输入信号,如RAM的数据口、单片机的I/O口等。

除了用于Verilog直接仿真(用test bench程序进行仿真)的测试模块中不需要定义端口外,所有module模块中都必须定义端口。

3.逻辑操作符

例2-1中出现了两种逻辑操作符,逻辑与“&”和逻辑异或“∧”。这是Verilog中常用的针对位的基本逻辑操作符号,下一节中将对此给予详细说明。

4.连续赋值语句

例2-1采用的是并行语句加纯布尔代数的表达方式实现的模块功能,属于所谓数据流描述方式,该方式通常使用连续赋值语句来描述输入输出间的逻辑关系。

连续赋值语句的基本格式如下:

assign 目标变量名=驱动表达式;

其中assign是连续赋值命名的关键词。由assign引导的赋值语句的执行方式是,当等号右侧的驱动表达式中的任一信号发生变化时,此表达式即被计算一遍,并将获得的数据立即赋给等号左侧的变量名所标示的目标变量。

在这里,驱动的含义就是强调这一表达式的本质是对于目标变量的激励源,或赋值源,它为左侧的目标变量提供运算操作后的结果。

Verilog语言中描述逻辑功能和电路结构的语句可分为顺序语句和并行语句(也称并发语句)。顺序语句的执行方式类似于普通软件语言的程序执行方式,是按照语句的前后排列方式逐条顺序执行的;而对于并行语句,无论有多少行语句,都是同时执行的,与语句的前后次序无关。以关键词assign引导的赋值语句都属于并行赋值语句,即在模块module_endmodule中所有由assign引导的语句都是并行运行的。显然,例2-1中的两条assign语句是同时执行的,没有先后之分;即此二语句右边的逻辑表式同时被运算,运算结果同时分别赋值于左边的输出信号SO和CO。

如果考虑到用Verilog testebench程序来进行仿真,那么此语句更一般的表述如下:

assign [延时] 目标变量名=驱动表达式;

即多了一个延时表述。方括号只是表示,括号中的内容是可以选择使用的。如果选择加入“延时”内容,则表示在任何时刻,当此语句等号右侧的“驱动表达式”中任一变量发生变化时即计算出此表达式的值,而此值经过指定的延时时间后立即被赋值给左侧的目标变量。当然,这个延时值在综合器中是被忽略的,不参与综合。例如

assign #6 R1=A & B;

表示当等号右侧表达式中的A或B中任一变量发生变化后,即刻算出变化后的值,但需要等待6个时间单位之后才将运算结果赋值给左侧的目标变量R1。这个延时称为惯性延时。而时间单元的大小由关键词“'timescale”在编译时指定。例如,加上以下语句:

`timescale 10ns/100ps

表示,仿真的基本时间单位是10ns,仿真时间的精度是100ps。在这个时间单元下,语句assign#6 R1=A & B在执行后,一旦计算出A & B的值后,还要再等待6个时间单元,也就是60ns后才将此值赋给R1。

5.关键字

关键字(Key word)是指Verilog语言中预定义的有特殊含义的英文词语。Verilog程序设计者是不能用关键字命名自用的对象,或用做标识符的。例如,例2-1出现的input、output、module、assign等都是关键词。请特别注意,与VHDL很不一样,Verilog规定,所有关键词必须小写,如INPUT,MODULE都不是关键词。

由于现在的多数Verilog代码编辑器,包括Quartus Ⅱ的编辑器,都是关键字敏感型的(即会以特定颜色显示),所以在专用编辑器上编辑程序通常不会误用关键字,但要注意,不要误将EDA软件工具库中已定义好的关键词或元件名当做普通标识符来用,如AND2、Or2等。

6.标识符

标识符(Identifier)是设计者在Verilog程序中自定义的,用于标识不同名称的词语,如用做模块名、信号名、端口名等。例如,例2-1中出现过的h_adder、A、SO等。此外,标识符是分大小写的,即所谓对大小写敏感。在这一点上也与VHDL不同,即在Verilog程序中相同名称不同大小写的文字代表不同的标识符。

2.1.2 半加器的门级原语和UDP结构建模描述方式

Verilog语言还提供了比例2-1给出的类似布尔代数逻辑操作符更基础、更底层、更直接、更直观的逻辑表达和设计方法。对于图2-1的半加器逻辑电路的实现,例2-3给出了更直接的设计表达方式。

例2-3中的模块和端口定义与例2-1相同,但在随后调用了两个元件:

一个是异或门,名称是XOR2。XOR2是用户自己设计的一个逻辑元件,其程序如例2-2所示。这个程序一看便知,其内部包含了一个异或逻辑的真值表。其端口定义顺序如例2-2的第一行所示,即XOR2(DOUT,X1,X2)。即端口定义括号(端口表)最左侧的信号名(DOUT)是输出信号,右侧所列的是两个输入信号。

另一个是与门and。这个与门元件也可以像例2-2那样由用户自己设计,但例2-3中所用与门元件是Verilog综合器元件库中自带的,可随时调用。元件and的端口表内最左侧信号默认定义为输出信号,其右侧其他信号则默认为输入信号。

例2-3程序中的逻辑元件XOR2和and旁的U1和U2分别是它们的元件名,也可不写。这两句可直接写成XOR2(SO,A,B)和and(CO,A,B)。

【例2-2】

primitive XOR2(DOUT,X1,X2);
  input X1,X2; output DOUT;
    table // X1 X2 : DOUT
             0  0  :  0;
             0  1  :  1;
             1  0  :  1;
             1  1  :  0;
    endtable
endprimitive

【例2-3】

module H_ADDER (A,B,SO,CO) ;
   input A,B;
  output SO,CO;
  XOR2 U1(SO,A,B); // 调用元件XOR2
   and U2(CO,A,B); // 调用元件and
endmodule

XOR元件文件例2-2和主文件例2-3的存盘文件名分别是XOR2.v和H_ADDER.v,它们都必须存放在同一文件夹中进行编译处理。

一般将直接描述基本逻辑元件的连接及由此实现逻辑功能的Verilog表述方式称为结构化建模方式,或结构描述风格。与此相对应的是行为建模方式。

例2-2和例2-3中涉及一些新的语法知识,以下将简要介绍。

1.库元件及其调用

Verilog中预先定义的基本逻辑单元称为原语(primitive),即现成的门级库元件。它们有多种,其中包括and、nand or、nor、xor、xnor、not等,分别是与门、与非门、或门、或非门、异或门、同或门及非门。这些门的特点是有一到多个输入端口,但只有一个输出口,且默认输出口的排列位置在最左侧。以与门为例,二输入与门是and(out,in1,in2);三输入与门是and(out,in1,in2,in3);四输入与门是and(out,in1,in2,in3,in4),如此等等。在使用中需要注意的是,库元件信号名的排列方式与电路(见图2-1)中需要连接的信号名的排列要完全一致,即输出口对输出口,输入口对输入口。

异或门等只有两个输入端,如果在例2-3中直接调用Verilog库中的异或门,则例2-3的第4行可改为:xor U2(SO,A,B),或xor(SO,A,B)。

请注意,这些内建于库中的门级元件的元件名也都是关键词,所以也必须小写。

2.用户自定义原语

用户自定义原语(User-Defined Primitives,简称为UDP)就是用户自定义基础元件。UDP元件定义格式如例2-2所示。UDP的格式与用module_endmodule声明一个模块很相似。UDP则是由关键词primitive起始,引导出这个元件的结构和功能描述,并以关键词endprimitive做结尾。关键词primitive旁的标识符是元件名,如XOR2;元件名右侧的括号是端口表。端口表中的标识符是端口名。且端口表中的端口名是固定的,在调用UDP元件时,应严格按照源文件中端口定义的顺序来定义外部信号的连接。

Verilog规定UDP元件只能有一个输出端,且输出端名要放在端口表括号的最左侧,而在右侧只能列出所有输入端口。

关键词table_endtable将引导出该UDP逻辑功能的真值表。真值表的排列顺序和规则如例2-2所示。

3.注释符号

例2-2中使用了注释符号“//”,可用于隔离程序,添加程序说明文字。因此例2-2程序第4行的“// X1 X2 : DOUT”和例2-3的“//调用元件XOR2”等,仅仅是为了使对应的数据或文字更容易被看懂,它们本身没有功能含义,也不参加逻辑综合。

Verilog中有两类注释符号。符号“//”后的注释文字只能放在同一行;而另一注释符号/* ...*/ 则像一个括号,只要文字在此“括号”中(如:/*调用元件XOR2 */),就可以换行,因此可以连续放更多行的注释文字。

4.规范的程序书写格式

尽管Verilog程序书写格式要求十分宽松,可以在一行写多条语句,也可分行书写,但良好的规范的Verilog源代码书写习惯是高效的电路设计者所必备的。规范的书写格式能使自己或别人更容易阅读和检查错误。如例2-1的书写格式:顶层的module_endmodule模块描述语句放在最左侧,比它低一层次的描述语句向右靠一个“TAB”键距离,即4个小写字母的间隔。同一语句的关键词要对齐,如primitive_endprimitive、module_endmodule、table_endtable、begin_end等。需要说明的是,为在书中纳入更多的内容而节省篇幅,此后的多数程序都未能严格按照此规范来书写。

5.文件取名和存盘

当编辑好Verilog程序代码后,需要保存文件时,必须赋给一个正确的文件名。对于多数Verilog综合器,文件名可以由设计者任意给定,但文件后缀扩展名必须是“.v”,如h_adder.v等。考虑到某些EDA软件的限制、Verilog程序的特点,以及调用的方便性,建议程序的文件名应该与该程序的模块名一致。对于Quartus Ⅱ,尤其必须满足这一规定!如例2-1的存盘文件名应该是h_adder.v。还要注意文件取名的大小写也是敏感的,即存盘的文件名与此文件程序的模块名的大小写必须一致。如例2-1的存盘文件名必须是h_adder.v,而不能是H_ADDER.v。文件名也不应该用中文或数字来命名。另外,如果是SystemVerilog语言的文件,则后缀是“.sv”。还应注意,进入工程设计的Verilog程序必须存入某文件夹中(要求非中文名),不要存在根目录内或桌面上。