本节将讲述Verilog HDL 语言的基础知识, 也会一并介绍基于Icarus Verilog 和GTKWave 的仿真环境。本书使用的Verilog HDL 是基于Verilog HDL 2001 标准的语言规范。这一节主要说明Verilog HDL 的基础语法,读者们可以跳跃阅读,在读写代码需要的时候再翻回来查阅。 1.4.1 什么是Verilog HDL Verilog HDL 是一种HDL 语言(Hardware Description Language,硬件描述性语言)。使用Verilog HDL 语言可以进行抽象度较高的RTL(Register Transfer Level,寄存器传输级)电路设计。RTL 是根据寄存器间的信号流动和电路逻辑来记述电路动作的一种设计模型。 很早以前,电路设计是将一个个逻辑与、逻辑或等门电路绘制在电路图纸上。但随着半导体技术的发展,这种方式很难高效地实现大规模硬件的设计。如今的电路设计通常采用RTL 模型。 图1-33 是一个使用Verilog HDL 进行硬件设计的流程示例。首先,在硬件功能确定之后,使用Verilog HDL 语言进行目标电路和测试程序的编写。同时根据硬件的设计目标设定面积、时钟周期等约束参数。然后在仿真器上使用测试程序对设计好的电路进行功能验证。最后,验证成功的Verilog HDL 在约束参数条件下进行逻辑综合并生成电路网表。 逻辑综合是将RTL 级别记述的抽象电路转换到门电路级别的电路网表的过程。逻辑综合时, 针对ASIC(Application Specific Integrated Circuit)、FPGA(Field Programmable Gate Array)等不同电路实现技术,需要使用这些技术厂商提供的相应的目标元件库。 图1-33 展示的是一条自上而下的单向设计流程,当发生电路验证失败、逻辑综合结果无法满足约束条件(无法收敛)等情况时,需要更正设计或参数并返回到设计的上流重新开始。电路网表生成以后还有布局布线等过程,在此不作阐述。 图1-33 使用Verilog HDL进行硬件设计的流程例 1.4.2 电路描述 本节讲述如何使用Verilog HDL 进行电路的描述。 模块 Verilog HDL 中使用模块来设计一个功能单位的逻辑。模块也是Verilog HDL 语言中最基本的构成单位。模块声明的语法如图1-34 所示。 图1-34 模块声明的语法 下面,我们一起来看一下如何使用模块来描述一个32 位的加法器。将要实现的加法器有in_0 和in_1 两个32 位的输入信号,它们相加的结果从32 位的out 信号输出。图1-35 是该加法器的框图,图1-36 展示了它的程序代码。 图1-35 加法器的框图 图1-36 加法器的程序 模块声明的语法是在module 关键字后记述该模块名。图1-36 中的示例使用adder 作为模块名。在紧随模块名的圆括号中对该模块的输入输出信号进行定义。输入信号的声明使用input 关键字,输出信号的声明使用output 关键字,双向信号的声明使用inout 关键字进行描述。信号声明的关键字后分别要对数据类型、信号线的位宽和信号名进行描述。变量位宽的定义是在方括号中记述最高位和最低位的位置,中间用冒号隔开,如[31:0]。比特数据的最高位被称为MSB(Most Significant Bit),最低位称为LSB(Least Significant Bit)。图1-36 中声明的in_0 和in_1 信号是32 位wire 型输入信号,out 是32 位wire 型输出信号。输入输出信号的声明之后使用右圆括号加分号结束,如 );。接下来对模块内的电路逻辑进行描述。在图1-36 的示例中,使用assign 语句,将in_0 和in_1 相加的结果输出到out。电路逻辑记述完毕,最后使用endmodule 关键字结束模块的定义。 Verilog HDL 是自由格式语言,可以在任意地方加入换行、空格以及Tab 等空白符号。另外,因为Verilog HDL 语言区分大小写,所以大小写的英文字符分别表示不同的含义。有效的标识符包括英文字母(a~z, A~Z)、数字(0~9)、下划线(_)和美元符号($)。标识符可以用来命名变量和模块。用户自定义的标识符必须以英文字母或下划线开头。Verilog HDL 语言中,在 /* 和 */ 之间,或从 // 开始到一行末尾的文字被视为注释。begin 和end 之间的部分称为块。 模块的实例化 设计好的模块可以被其他模块调用。模块实例化的方法如图1-37 所示。该示例调用了图1-36 中实现的加法器。使用分层的设计方式可以将复杂的电路分割成多个功能单元简化设计,也有助于增强代码的可维护性和移植性。 图1-37 模块的实例化 逻辑值与常数表达 Verilog HDL 中可以使用的逻辑值如表1-4 所示。逻辑值可以表达为0 和1。当由于复位等操作后未经初始化或因设计问题无法确定是0 还是1 时,使用不定值x 来表达。此外,电气概念上的绝缘状态(没有任何连接)被称为高阻状态,用z 来表示。 表1-4 Verilog HDL 的逻辑值 常数的格式如图1-38 所示。首先在位宽中指定常数的宽度,然后是单引号加表示该常数为几进制的底数符号。二进制底数符号为b、八进制为o、十六进制为h。最后在数值中指定该常数的数值。图1-38 中的示例说明了十进制数60 的各种表达方式。 图1-38 常数的格式与示例 变量的声明与数据类型 变量声明的格式如图1-39 所示。数据类型和变量名是必要项目,其他项可以省略。符号和位宽如果省略则根据数据类型设置为默认值。元素数省略默认声明元素数为1 的变量。 图1-39 变量声明的格式 数据类型有寄存器型和网络型两种。寄存器型是可以保存上次写入数据的数据类型, 根据程序不同可以生成锁存器、触发器等存储元件,也可能生成组合电路。寄存器型变量如表1-5 所示。本章主要使用reg 和integer 两种类型。 表1-5 寄存器型变量 寄存器型变量可以在接下来将要介绍的always 和initial 语句中实现过程赋值(Procedural Assignment)。这种方式称为过程赋值。过程赋值分为阻塞式和非阻塞式赋值两种。 阻塞式赋值是一种按照代码顺序进行赋值的方式。在先赋值的代码赋值完成之前阻塞后续代码的赋值,因此得名阻塞式赋值。阻塞式赋值使用= 运算符。 非阻塞式赋值中所有代码不会互相阻塞,同时进行赋值。非阻塞式赋值使用<= 运算符。 在一个过程块中,阻塞式赋值和非阻塞式赋值只能使用其中一种。阻塞式赋值的格式如图1-40 所示。非阻塞式赋值的格式如图1-41 所示。 图1-40 阻塞式赋值 图1-41 非阻塞式赋值 网络型是用来描述模块和寄存器间连接的数据类型。网络型只描述信号的传输不持有数据。表1-6 对网络型变量进行了说明。本章只使用wire 型。 表1-6 网络型变量 网络型变量可以在assign 语句或声明语句中实现连续赋值(Continuous Assignment)。连续赋值就是进行连续的赋值。图1-42 给出了assign 语句中连续赋值的格式和示例。图1-43 给出了声明语句中连续赋值的格式和示例。 变量的符号用signed 和unsigned 关键字指定。在赋值或比较等处理时,如果需要在有符号数和无符号数间进行转换,需要使用$signed() 和$unsigned() 系统任务(system task)。无符号数转换为有符号数时使用$signed(),有符号数转换为无符号数时使用$unsigned()。变量声明的示例如图1-44 所示。 图1-42 assign语句中连续赋值 图1-43 声明语句中连续赋值 图1-44 变量声明示例 专栏 默认网络类型 使用网络型变量时,如果定义默认网络类型,可以不用声明直接使用。引入这种方式, 是为了可以在大量使用网络型变量的网表程序中减少代码量。但是默认网络类型也有副作用。由于失误而未声明类型的信号会被自动处理为默认网络类型,编译器无法检测错误。 默认网络类型由编译器指示词`default_nettype 指定。图1-45 给出了默认网络类型的格式与示例。默认网络类型为none 时,不启用默认网络类型。RTL 设计时为了规避默认网络类型的副作用,推荐将默认网络类型设置为无效。 图1-45 默认网络类型的指定 运算符 Verilog HDL 中的运算符如表1-7 所示,运算符的优先级如图1-46 所示。运算首先根据运算符优先级的高低顺序执行,优先级相同的运算符按照从左到右的顺序执行。使用圆括号可以改变运算的优先顺序。 表1-7 Verilog HDL 中的运算符 图1-46 运算符的优先顺序 缩减运算符的特点是对信号的所有位进行位运算,最终输出1 位的运算结果。缩减运算符的说明如图1-47 所示。 图1-47 缩减运算符示例 图1-48 使用拼接运算符组合比特序列 图1-49 使用拼接运算符重复比特序列 条件分支语句if与case 条件分支可以使用if 或case 语句实现。if 和case 语句的语法格式与示例分别如图1-50 和图1-51 所示。 if 语句括号中的条件成立时,执行其中的语句序列。当含有else 语句,if 条件不成立时,执行else 中的语句。else 语句中也可以再使用if 进一步限制条件。case 语句是执行与括号中条件相等的表达式内的语句序列。if 和case 语句可以在initial 或always 语句声明的过程块中使用。 图1-50 if语句的格式与示例 图1-51 case语句的格式与示例 循环语句for 与while 使用for 和while 语句可以实现循环操作。for 和while 语句的语法格式与示例分别如图1-52 和图1-53 所示。 for 语句在圆括号中央的表达式条件成立时执行其中的语句序列。第一次进入for 语句时,执行圆括号内左边语句并进入重复过程。第二次循环开始先执行圆括号内中央的表达式,如果为真则先执行循环体内的语句序列,随后执行圆括号内右边的语句。然后再次执行圆括号内中央的表达式,为真的话继续重复上述操作。while 语句是在圆括号中表达式为真时重复执行其中的语句序列。for 和while 语句可以在initial 或always 语句声明的过程块中使用。 图1-52 for语句的格式与示例 图1-53 while语句的格式与示例 always过程块 always 是为了描述过程块而存在的语句。always 的语法格式如图1-54 所示。 图1-54 always语句的格式 当指定always 语句中的事件表达式时,所指定的事件触发时执行其中的语句序列。事件可以是特定信号的变化、信号的上升沿(posedge)、信号的下降沿(negedge)等。always 语句中如果使用常数,则会在每经过该常数时间便执行一次always 中的语句序列。这个功能主要是在仿真时使用。always 过程中可以使用寄存器变量赋值、if、case、for、while 等语句。 使用always语句描述组合电路 使用always 语句描述组合电路时,事件表达式描述方式如图1-55 所示。事件表达式中写入通配符*。这样一来,任何输入信号变化时都会执行过程块中的代码。 示例中定义了一个adder 模块,它有两个32 位wire 型输入in_0 和in_1、一个32 位reg 型输出。always 中将两个输入相加后赋值给了输出。这里使用了阻塞式赋值。 图1-55 使用always语句描述组合电路 专栏 组合电路描述中锁存器的推定与Don’t care 使用always 语句描述组合电路时,如果信号未被赋值,有可能会引入锁存器。以图1-56 所示的代码为例,当in 为2'b11 时会发生什么情况呢?这时由于没有赋值,out 的值不发生变化,也就是说会保持之前的值。而为了保持之前的值就需要存储元件。这时会生成异步的存储元件锁存器。因此虽然设计的是组合电路,但产生了本不应有的存储元件,变成了时序电路。 寄存器推定的发生原因是不完整的case 语句或没有else 的if 语句。为了规避这个问题,一定要将case 语句的条件写全,或者使用default 来确定默认值。并且,使用if 语句时一定要写else 条件,或者在if 语句前为变量值赋予默认值。 也存在这种情况:确定不存在case 和if 语句设定之外的条件,或者设定条件之外随便输出什么都可以。这种情况称为Don’t care(忽略),输出为逻辑综合时优化的数值。Verilog HDL 中Don’t care 的指示方法是在default 中为输出赋予不定值。图1-56 的示例中加入Don’t care 指示的程序如图1-57 所示。 图1-56 寄存器推定示例 图1-57 Don’t care 指示方法 使用always描述时序电路 使用always 语句描述时序电路时,事件表达式描述方式如图1-58 所示。时序电路含有触发器等存储元件,基本上都是按照时钟同步执行。因此事件表达式中要指定时钟的信号边沿和时钟信号名。 时钟信号边沿是指确定在时钟信号上升时触发电路动作,或者在时钟信号下降时触发电路动作。上升时动作记述为posedge,下降时动作记述为negedge,然后记述信号名。事件表达式还可以使用or 列举多个条件。为存储元件设置异步复位(reset)信号时,除了时钟信号还要写上复位信号的边沿和信号名。 图1-58 的示例中定义了一个叫做ff 的模块,它有clk、reset_、d_in 三个一位wire 型输入信号,和一位reg 型输出信号d_out。d_out 在clk 的上升沿同步动作,将d_in 的值储存。并且d_out 在reset_ 的下降沿被异步地复位,初始化为0。 预处理 预处理是在代码编译前对其进行预先处理的程序。Verilog HDL 中的预处理可以实现宏定义和条件编译。预处理使用编译指示符可对编译器进行各种控制。图1-59 介绍了本书用到的编译指示符的格式和示例。 编译指示符以后引号(`)开头。使用`include 语句可以插入引用文件。使用`define 语句可以进行宏的定义。在图1-59 的示例中,定义了名为BYTE_DATA_W、值为8 的宏。Verilog HDL 中为了区分宏与变量名,宏的名称前也加有后引号(`)。代码中使用宏时,要像`BYTE_DATA_W 一样记述。 图1-58 使用always语句记述时序电路 图1-59 编译指示符 使用`ifdef 和`ifndef 可以实现条件编译。`ifdef 是在指定的宏存在的条件下,执行`ifdef 到`endif 的代码。`ifndef 是在指定的宏不存在的条件下,执行`ifndef 到`endif 的代码。两者都可以使用`else 指定不满足条件时执行的动作。 图1-59 的示例中,宏TEST1 存在时执行从`ifdef 到`else 的代码,不存在时执行从`else 到`endif 的代码。宏TEST2 不存在时执行从`ifndef 到`else 的代码,存在时执行从`else 到`endif 的代码。 程序例 下面利用前面介绍的Verilog HDL 语法,演示一个代码示例。本示例制作了32 组位宽为32 的寄存器堆。图1-60 是寄存器堆的框图。寄存器堆中有作为存储的32 个32 位寄存器,以及读写寄存器序列用的接口。 读取操作时将地址信号(addr)指定的寄存器的内容,通过多路选择器选择,输出到输出信号(d_out)。对于写入操作,当写入使能信号(we_)有效时,向地址信号(addr)指定的寄存器写入输入数据(d_in)。 图1-60 寄存器堆框图 寄存器堆模块在regfile.v 文件中实现。regfile.v 引用了regfile.h 头文件。regfile.h 的内容在代码1-1 中列出,regfile.v 的内容在代码1-2 中列出。 代码1-1 头文件示例(regfile.h) 代码1-2 代码示例(regfile.v) [Ⅰ]引用头文件 引用头文件的语句写在模块之外。 [Ⅱ]模块声明 声明regfile 模块并定义输入输出接口。 [Ⅲ]内部信号的声明 定义寄存器序列ff 和for 语句的i(循环的计数器)。 [Ⅳ]读取访问 将addr 指定地址的寄存器序列的值连续赋值给d_out。 [Ⅴ]写入访问 (1) 中进行异步复位操作。reset_ 信号使能时,使用for 循环将全部ff 的值初始化为0。 (2) 中进行写入访问操作。we_ 信号使能时,将输入信号d_in 的值写入addr 地址指定的寄存器序列中。 专栏 正逻辑与负逻辑 控制信号的有效、无效与信号高低电平相对应时,高电平有效、低电平无效的分配方式称为正逻辑。反之,高电平无效、低电平有效的分配称为负逻辑。 不论信号电平的高低,控制信号转为有效状态的动作称为assert(断言),转为无效状态的动作称为negate(无效)。并且,信号有效时称为enable(使能),信号无效时称为disable(非使能)。 1.4.3 电路仿真 使用Verilog HDL 不仅可以设计电路,还可以对所设计的电路进行仿真。通过仿真可以实现逻辑验证,从而测试设计好的电路是否可以正确工作。记述仿真程序的文件称为Testbench。下面,我们来看一下Testbench 的制作方法。 Testbench的构造 Testbench 是对制作的电路进行仿真、测试的模块。Testbench 的构造如图1-61 所示。 Verilog HDL 中的Testbench 本身就被定义为一个模块。通常,Testbench 没有输入输出信号。Testbench 调用被测模块,传递输入信号并观测输出。被测模块输入输出端口上的信号作为Testbench 的内部信号进行定义。通常,输入端口为了将值带入使用寄存器型变量,而输出信号接网络型变量对输出值进行观测。Testbench 中,使用initial 语句生成测试用例,然后观测模块的输出。 图1-61 Testbench的构造 在Testbench 中,`timescale 用来设定仿真执行的时间单位。`timescale 的设定中使用数字和单位(fs、ps、ns、us、ms、s)指定单位时间和时间精度。图1-62 列出了`timescale 的使用格式。 单位时间用来指定仿真的一个单位时间相当于多少秒。时间精度用来表示仿真处理的时间精度,并根据时间精度取数值的近似值。没有必要取过小的时间精度,这会延长仿真时间。单位时间和时间精度的关系必须满足“单位时间≥时间精度”。 图1-62 Testbench的格式 用initial语句生成测试用例 initial 语句是在仿真开始时只会执行一次的语句。initial 语句的格式与示例如图1-63 所示。initial 语句和延迟描述组合,可以用来生成测试用例。 图1-63 initial的格式与示例 延迟语句 # 字符用来记述延迟语句,其格式与示例如图1-64 所示。延迟语句中指定的数值意味着`timescale 中设定单位时间的个数。延迟语句只用在仿真程序中,用来在特定时间延迟后施加信号并生成测试用例。不会对逻辑综合的结果产生影响。 图1-64 延迟语句的格式与示例 专栏 同步电路中信号变化的时序 仿真与时钟同步的电路时需要注意信号变化的时序。 图1-65 所示的电路,时刻10 的时候out 信号值会变成什么呢?如果是在信号变化之前值为L,如果是在信号变化之后则值为H。实际上,结果是哪个值取决于仿真器。由于仿真中将信号变化的延迟作为0 来处理,因此会引起这样的问题。在实际电路中,从时钟信号上升到信号变化之间会产生一定时间的延迟。因此时刻10 时的值应该是L。 仿真时为了避免这种情况,如图1-66 所示,通常使用延迟语句将信号变化的时序向后顺延一个时间单位。由此,时刻10 时out 的值依然是L。这种记述方式不会对逻辑综合的结果产生影响。 图1-65 同步电路信号时序示例 图1-66 信号变化时序的延迟 时钟的生成 如果被测模块要用到时钟,需要在Testbench 中生成时钟信号。图1-67 展示了一种使用always 语句生成时钟的方法。always 语句中指定常数作为时间间隔,每经过这个时间间隔就会执行一次always 中的语句。也就是说,图1-67 中的clk 信号值每过10 个单位时间就会翻转。用这个方式就可以生成时钟信号。需要注意的是,clk 应该在initial 语句中的时刻0 时被初始化。 图1-67 时钟的生成 系统任务 通过使用Verilog HDL 预置的系统任务,可以达到控制仿真、输出字符串等目的。下面列出了一些经常用到的系统任务。 $display("含有格式的字符串", ...) 根据第1 参数中含有格式的字符串, 将第2 参数开始的任意个数的参数格式化,最后加换行符输出。 $write("含有格式的字符串", …) 与$display 功能相同,但输出后不换行。 $time 返回目前的仿真时间。 $finish 结束仿真。 载入存储镜像 仿真时,有时需要向存储器等读入预先准备好的数据。可以使用$readmemh 系统任务从文件中读入数据并设置存储器。$readmemh 格式如下所示。 $readmemh("文件名", 读入对象) 第1 参数所指定文件的数据读入第2 参数指定的存储器中。 $readmemh 使用的存储镜像文件使用十六进制文本文件记录。每一行记录一个地址的数据。图1-68 是读入存储镜像的示例。 图1-68 存储镜像读入示例 波形的输出 仿真时的信号变化可以输出到波形文件中。波形文件有很多种,本书将介绍多数波形软件都支持的VCD 格式波形文件的输出方法。 VCD 文件的输出使用$dumpfile 和$dumpvars 两个系统任务来实现。波形输出的格式和示例如图1-69 所示。在initial 中调用$dumpfile 和$dumpvars,可以实现波形文件的输出。 图1-69 波形文件的格式与示例 Testbench实例 接下来,我们为程序示例中实现的寄存器堆制作Testbench。代码1-3 中列出了Testbench 的代码。 代码1-3 Testbench 示例(regfile_test.v) [Ⅰ]定义Time scale 单位时间为1ns,时间精度为1ps。 [Ⅱ]引用头文件 引用regfile.h。 [Ⅲ]声明模块 声明regfile_test 模块。Testbench 没有输入输出端口。 [Ⅳ]定义内部信号 被测模块的输入端口连接reg 型变量,输出端口连接wire 型变量。为仿真循环定义了名为STEP 的参数,STEP 的值为100ns。 [Ⅴ]生成时钟 这里生成了频率为10MHz 的时钟。10MHz 的时钟每50ns(STEP/2)在H 与L 间重复一次。 [Ⅵ]实例化测试模块 实例化regfile。 [Ⅶ]测试用例 (1)处的时刻0 时对信号线进行初始化。(2)处的语句解除复位信号。(3)处对寄存器堆的读写进行验证。最初一次STEP 时,对寄存器地址i 处写入数值i, 下一个STEP 时将其读出,并用if 语句比较、验证读出的结果是否正确。 这个过程使用for 语句重复32 次。(4)处结束本次仿真。 [Ⅷ]输出波形 在initial 中输出仿真波形。将实例化的regfile,从时刻0 开始的波形输出到regfile.vcd 文件中。 1.4.4 Verilog HDL 的仿真环境 本节对Verilog HDL 语言的仿真环境进行说明。Verilog HDL 仿真器工具有很多,本书仅介绍Icarus Verilog。本书还会介绍可以查看仿真生成的波形文件的工具GTKWave。这两个工具都是自由软件,并且可以在Windows、Linux 等各种平台运行。 下载与安装 Icarus Verilog 与GTKWave 官方网站URL 如下所示: Icarus Verilog http://iverilog.icarus.com/ GTKWave http://gtkwave.sourceforge.net/ 从这些网站可以下载并安装上述两个软件。Icarus Verilog for Windows 页面提供了这两个软件捆绑的安装程序,本书使用这种安装方式。 Icarus Verilog for Windows http://bleyer.org/icarus/ 访问上述URL 就会打开如图1-70 所示的页面。从Download 下方列出的链接下载最新版的安装程序。图1-70 中iverilog-0.9.5_setup.exe [6.84MB] 的链接为最新版。接着运行下载的安装文件,并根据安装向导安装程序。不需要指定特殊的参数,默认安装即可。 图1-70 Icarus Verilog for Windows Icarus Verilog 仿真功能需要从命令行执行。为了确认Icarus Verilog 已经正确安装, 我们打开命令行窗口执行一下。 要打开命令行窗口,先按windows 键,依次点击所有程序→附件→命令提示符。之后会打开图1-71 所示的黑色画面。画面中显示的文字“C:UsersKazutoshi Suito>”称为提示符,是提示用户输入命令的信息。提示符的前半部分提示的是当前目录。Windows 中的目录称为文件夹。用户可以在提示符后输入命令。 然后我们在命令行窗口中试着执行一下iverilog 命令。如果出现“iverilog: no source files...”信息,则没有问题。如果出现“'iverilog' 不是内部或外部命令,也不是可运行的程序或批处理文件。”请按照下面的步骤设定环境变量。 图1-71 命令行窗口 设定命令查找路径 iverilog 无法正确执行的原因是没有正确设定命令搜索路径。命令搜索路径是Windows 查找可执行文件的场所。当输入iverilog 命令时,命令行窗口会在命令搜索路径中搜索名为iverilog 的可执行文件。iverilog 执行文件在Icarus Verilog 安装文件夹下的bin 目录中。但是因为这个目录并未包含在命令搜索路径中,因此命令行窗口找不到执行文件。 命令搜索路径可以在环境变量中设置。环境变量是在程序执行时操作系统向应用传递的通用参数。通过设定环境变量,可以对应用的动作进行设定。环境变量设定的步骤如图1-72 所示。 图1-72 环境变量设定步骤 [Ⅰ]打开“计算机属性”窗口并单击“高级系统设置”。 打开“计算机属性”窗口是在开始菜单中右键单击“计算机”,选择属性。 [Ⅱ]点击“高级系统设置”中的“环境变量”。 [Ⅲ]选择“系统变量”中的“Path”,并单击编辑。 [Ⅳ] 在变量值的末尾追加“; 安装路径bin”,点击“确定”。默认安装的情况下,设定字符串为“;C:iverilogbin”。 设定好环境变量之后,再次打开命令行窗口并执行iverilog 命令。这次应该会出现正确的输出信息iverilog: no source files... 了。 使用Icarus Verilog进行仿真 使用Icarus Verilog 进行仿真,首先需要用iverilog 命令对源代码进行编译。为iverilog 命令的参数指定正确的选项和源代码文件,执行后就会输出编译后的文件。iverilog 命令的选项如表1-8 所示。 表1-8 iverilog 的选项 -D 选项用来定义宏。这个选项与代码中用`define 命令定义的方式等效。通过参数设定的宏在控制仿真动作方面非常有用。-I 选项用来指定引用文件的查找路径。代码中`include 语句引用的文件会在-I 选项指定的目录中查找。-o 选项用来指定输出文件的文件名。省略-o 选项时,默认输出文件名为a.out。-s 选项用来指定最上层模块的名称。-y 选项用来指定库文件的查找路径。 编译之后,使用vvp 命令来执行仿真。vvp 的参数中需要指定iverilog 命令所输出的文件。vvp 命令执行后,就会按照Testbench 中记述的测试序列进行仿真。如果Testbench 中有波形输出,就会输出波形文件。 下面我们尝试使用Icarus Verilog 进行仿真试验。试验用的文件为之前示例中制作的寄存器堆(regfile.v)和Testbench(regfile_test.v)。进入代码与Testbench 所在的目录,并在命令行中执行以下命令。 C:Users…> iverilog -s regfile_test -o regfile_test.out regfile_test.v regfile.v C:Users…> vvp regfile_test.out iverilog 参数中指定了代码文件与Testbench 文件。-s 参数将最上层模块名指定为Testbench 的模块名。-o 参数设定输出文件名为regfile_test.out。 然后执行vvp 进行仿真,参数设置为iverilog 的输出文件。如果仿真正确执行,画面中会出现Testbench 中的输出信息。我们可以看到寄存器堆模块读写测试完成的信息。最后,Testbench 执行后的波形文件输出到了regfile.vcd 中。 C:Users…>vvp regfile.out VCD info: dumpfile regfile.vcd opened for output. 475 ff[ 0] Read/Write Check OK ! 675 ff[ 1] Read/Write Check OK ! ……… 6475 ff[ 30] Read/Write Check OK ! 6675 ff[ 31] Read/Write Check OK ! 使用GTKWave 查看波形 下面,我们使用GTKWave 软件查看Icarus Verilog 输出的波形文件。GTKWave 使用gtkwave 命令进行启动。如果启动时设定波形文件参数,启动后会自动载入波形文件。GTKWave 的界面与使用方法如图1-73 所示。 图1-73 GTKWave 的界面与使用方法 [Ⅰ] 窗口左上方会显示模块的树状列表。单击即可选择想要查看波形的模块。 [Ⅱ] 窗口左侧中间会显示已选模块的信号。单击即可选择想要查看波形的信号。 [Ⅲ] 单击左下方的Append 按钮,波形就会在右侧窗口中出现。 [Ⅳ] 在窗口右侧观察波形,同时可以使用滚动条或工具栏对波形的显示进行调整。 下面,我们来查看一下刚才Icarus Verilog 输出的波形文件regfile.vcd。进入波形文件所在的文件夹并执行以下命令,GTKWave 会启动并载入波形文件。 C:Users…> gtkwave regfile.vcd 1.4.5 小结 在本节中,我们介绍了Verilog HDL 的语法、示例与仿真环境等,这是阅读接下来的章节必须掌握的基础知识。必要时,读者们可以返回查阅。 专栏 Verilog HDL 相关书籍 入門Verilog HDL記述-ハードウェア記述言語の速習&実践(小林優、CQ出版) (中文译名:《入门Verilog HDL 记述•硬件描述语言速成与实践》) 本书是以Verilog HDL 为基础的硬件设计入门书。结合实例进行讲解,通俗易懂地介绍了如何编写代码。篇幅适中、示例简洁,推荐Verilog HDL 初学者阅读。 LSI設計の基本RTL設計スタイルガイド-Verilog HDL編(STARC監修、培修館) (中文译名:《LSI 设计基础RTL 风格指南:Verilog-HDL 篇》) 本书讲解了RTL 设计进阶的设计风格,阐述了设计时需要遵守的约定、代码风格等,旨在帮助正在使用Verilog HDL 进行开发的读者,学习更高深的设计技术。这本书主要以具体的设计问题为中心进行讲解,难度稍高,要成为RTL 设计达人,必备此书。