2.10 硬件描述语言
Verilog和VHDL(VHSIC或者高速集成电路)是两种用于描述数字电路的工业标准HDL。HDL用于在形式上描述数字电路,测试台用于生成电路测试(例如容器)。
如果电路被一组互连的模块进行描述,那么我们说这个HDL描述是结构的。这些模块可以很小,如与、或、与非等逻辑门,也可以大一些,例如译码器、多路选择器、加法器等。一些通常使用的大模块,如加法器,可以预定且在合成过程中使用。
如果HDL代码描述了模块中输入的关系且在描述输出时使用了例如“if-else”或者“case”的高级语言,那么我们称这个HDL描述为行为。
2.10.1 结构模型
本节中,我们将简要介绍Verilog HDL。其他例子将在本书其他地方提到。然而,这里的描述是不完整的,可能需要一些额外的引用。例2-11展示了如图2-39中有两个异或门和三个与非门的全加器的结构模型。Verilog模型从关键字“module”开始,并且包含模块名(例如full_adder),然后是输入和输出接口的列表(例如a、b、cin、s和cout)。模型描述以关键字“endmodule”结束。接口列表可以以任何排序出现,但是必须声明为“input”或者“output”。那些不声明为输入或者输出的信号名就被认为是本地的信号,当结构化设计时会当成线使用。例如,例子中的三个信号out1、out2和out3都是本地的。
例2-11? 如图2-39中的全加器的Verilog结构模块:
标准逻辑门也称为原始逻辑门,它们可以被Verilog编译器所识别,不用重新描述。例子中的x1、x2、n1、n2和n3是可选的,并且是实例化的异或门和三个与非门给定的名称。每一个实例化原始逻辑门最左边的参数为输出,其余的都是输入。例如,信号out1、s、out2、out3和cout都是输出,所以它们在参数的最左边。原始逻辑门可以被一个或者多个输入参数所实例化,这依赖于它的类型。例如,一个三输入的原始与非门可能包含一个输出(最左边)和三个输入参数。
模块可以用任何顺序进行实例化,类似于我们在屏幕上使用图形设计工具进行实例化的过程(例如图2-39)。模块的互连情况由其端口列表确定。例如,第一个实例化原始异或门以out1作为其输出接口,第二个原始异或门用out1作为其输入接口。这意味着有一条线连接两个out1接口。类似地,out2和out3信号分别用一条线连接。这些信号都被声明为“线”。
如例2-12中的例子,测试台模块也用HDL进行描述,其功能是测试电路模块,例如例2-11中的全加器模块。测试台模块没有输入和输出接口。我们可能需要`include指令(取决于设计工具)来导入另一个模块中非原始但已经用HDL创建的模块——例如,测试台。通常每一个被导入的模块可以被实例化一次或多次,在调试前根据需要创建目标电路模块。在例2-12中,“full_adder”HDL模块被导入且在调试中被实例化了一次。
初始块用于列出“full_adder”模块的测试容器。所有初始块中的语句都必须按照顺序处理。初始块或者永久块(稍后讨论)中所有在赋值运算符(例如=)左边的变量都要声明为reg类型。当描述组合电路时,reg类型没有具体的意义。然而reg类型在设计时序电路时将会非常重要。
例2-12? 测试例2-11中全加器结构模块的测试台:
当使用没有调试功能的设计工具时,$display语句可以用来输出一个或者多个输入信号的值。另一方面,$monitor语句在仿真运行期间只输入一次来跟踪一个或者多个信号(输入或者输出)。每一次$monitor语句中列出信号的值发生改变,语句就会被执行来输出信号值。$display语句和$monitor语句句法是一样的,与C编程语言中的“printf”语句相似。输出格式“%d”、“%h”、“%o”和“%b”可以分别用于表示十进制、十六进制、八进制和二进制的数值。附加输出格式,例如“%s”和“%f”用于表示字符串和浮点数值。
仿真的时间步是使用符号“#”后跟着代表仿真时间长度的整数表示。如果假定一个模块没有延迟,功能仿真将假定每个模块都没有传输延迟,并且在一个仿真时间(#1)内可以产生输出。
如果使用Synopsys设计工具,调试例2-12中FA模块的仿真输出如下所示。我们可以看到当a = 1,b = 1,cin = 1时,$monitor语句在仿真时间= 2时输出s = 1、cout = 1。
“=”符号被称为块赋值。所有的包含在原始块或者永久块中的块赋值语句按一定的顺序进行评估,这个过程类似于编程语言。另一方面,非块赋值(稍后讨论)用符号“<= ”表示,并且与其他在原始块或者永久块中的非块赋值语句同时执行。包含在原始块中的HDL语句只被评估一次,就像真正的电路一接通电源就开始工作;而包含在永久块中的HDL语句只要电路是在仿真阶段中就会被评估。
Verilog还可以支持“for循环”、“case”(switch)、“forever”和其他控制语句。然而,不是所有的Verilog语句都可以进行合成。如例2-13所示的测试台,用for循环完全测试了FA模块。在例子中,信号a、b和cin都被声明为1位reg,而用在for循环中的变量k,用小尾数顺序声明为4位reg。语句“reg [0:3] k;”将k定义为大尾数顺序。多位变量可以作为一个组或者按位进行操作引用。例如,在例子中k[2]表示变量k的第三位,k[0]表示k中的最低有效位(LSB)。
例2-13? 完全测试例2-11中FA结构模块的测试台模块:
在例2-13的测试台中,在每一个仿真步骤,都有新的数值赋给输入信号a、b和cin。输入信号也可以在每一个仿真步骤中显示。在每一个仿真步骤结束后,$monitor语句自动地显示输出信号的数值,展示一个或者多个信号值的变化。仿真运行中的输出将会在下一步展示。当测试容器为a = 0、b = 0和cin = 1,a = 0、b = 1和cin = 0,a = 1、b = 0和cin = 1,a = 1、b = 1和cin = 0时,信号s和cout不改变,$monitor语句不会显示输出信号。
2.10.2 传输延迟仿真
在每一个初始逻辑门在实例化的过程中也可以包含可选的传输延迟。这就提供了更现实的功能仿真。例2-14描述了原始与非门有1ns延迟,原始异或门有3ns延迟的全加器。由此可得:Δs = 6ns,Δcout = 5ns。
例2-14? 有延迟的用原始逻辑门实现的FA的结构化模块:
编译器语句timescale表示仿真中应用的时间范围。例2-14中的
timescale语句定义了有100ps(皮秒)增量的1ns。然而,这个时间范围仅仅是一个估计,仿真结果并没有提供真正的时间数据。
例2-15中的测试台包含了分别在仿真时间0和10进入的两个测试容器。如下仿真输出所示,最初信号cout和s的值都为未知(“x”)。对于测试容器在仿真时间= 0时的a = 0、b = 0和cin = 1,语句$monitor有输出,在仿真时间= 5时,输出cout = 0,在仿真时间= 6时,输出s = 1,与预期相符。对于测试容器在仿真时间= 10时的a = 0、b = 1和cin = 1,在仿真时间= 15输出cout = 1,在仿真时间= 16,输出s = 0,与预期相符。这两个测试容器暴露出了电路最严重的延迟情况。
例2-15? 有延迟的FA全加器模块的测试台:
非原始模块不能在有延迟信息的情况下进行仿真。对于这些模块,延迟信息是由模块内部的延迟数值决定的。然而,我们可以在实例化过程中用参数化的延迟去重写延迟信息。
2.10.3 行为建模
Verilog中最基础的行为描述是赋值语句。它使用符号“~”、“&”、“|”和“^”直接进入一个布尔表达式分别去按位表达非、与、或和异或操作。与非、或非和异或非操作也可以分别用符号组合“&~”、“|~”和“~^”或者“^~”表示。表2-13总结了Verilog HDL中的操作符。例2-16使用赋值语句展示了1位2-1 MUX。
例2-16? 用赋值语句描述1位2-1 MUX的行为模块:
描述电路行为最常使用的语句叫永久块。我们用关键字“always”后面跟着符号@和一个称为块的敏感列表的信号名列表来声明永久块,和初始(initial)块类似,永久块也包含begin-end块。例2-17展示了使用“if-else”语句表示1位2-1 MUX的另一种行为模型。因为r信号依赖于信号s、x和y,这些信号都会包括在永久块的敏感列表中。
例2-17? 使用“if-else”语句表示1位2-1 MUX的行为模型:
与初始块类似,永久块中所有在赋值符号(=)左边的变量必须被声明为reg类型。声明一个同时为output和reg类型的输出变量的正确方法为用语法output reg来声明,如例2-17所示。
语法1’b0用来表示1位二进制数。其他表示数字的例子有5’b11111、8’hFF和9’o777,分别表示5位二进制数(11111)2、8位十六进制数0xFF、9位八进制数(777)8,这里“8”用来表示一个八进制数。
依赖于编译器的版本,也有另外的语法来描述敏感列表。例如,“always@(s or x or y)”、“always@(s, x, y)”、“always@()”或者“always@”都是可接受的表示组合电路敏感列表的语法。此外,在组合永久块中用*表示敏感列表是最常见的语法,可以允许编译器决定敏感变量的列表。敏感列表中遗漏变量可以导致组合电路行为的错误。
例2-18用case语句表达其真值表展示了FA全加器模块的行为。在例子中,大括号({})表示一个级联。“default”情况也是考虑遗漏情况的正常操作。可接受的信号值为0、1、x(未知)和z(高阻抗,Z)。作为一种选择,“casex”忽略x和z信号值并且把它们视为无关项。
例2-18? 用“case”语句表示的FA全加器行为模块:
例2-19展示了有低电平使能信号4位三态缓存行为描述,这里的4’bz表示一个4位高阻抗Z值。
例2-19? 4位三态缓存的行为模块:
2.10.4 综合与仿真
例2-18中FA全加器HDL模型运用了Altera Quartus II设计和综合工具来进行综合[4]。在综合过程中,使用了一种叫EP4CGX15BF14A7 1.2V的Altera Cyclone IV GX系列中的可编程芯片。综合后的电路可以用Altera ModelSim仿真工具来进行仿真。图2-40展示了有两个CLB来实现和值和进位输出的表达式的综合电路。在这个例子中,输入a、b和cin,以及输出s和cout都使用缓存来防止可能的扇出问题。图2-41展示了有8个测试容器波形的综合电路的仿真。
类似的,例2-19中的4位三态缓存模型也可以用Altera设计、综合和仿真工具进行综合和仿真。综合电路如图2-42所示。因为使能信号_e是低电平信号,所以不需要附加的非门来操作低电平使能的三态缓存。有两个测试容器的综合电路的仿真图如图2-43所示。