



作为一门高级程序设计语言,Verilog HDL包含的内容也很丰富。本书不深入探讨其内容,仅为了方便后续介绍,对Verilog HDL基础语法进行概述。
在Verilog HDL中,注释分为两种类型:单行注释和多行注释。
● 单行注释:从“//”符号开始直到该行末尾的所有内容均为注释内容。
● 多行注释:被“/*”和“*/”符号所包围的内容均为注释内容。
变量(包括常量)和数据类型是程序设计语言的两个基本要素。尽管wire和reg是Verilog HDL设计者使用最频繁的变量声明方式,但Verilog HDL实际上还规定了多种其他类型的变量和常量,例如整数常量、实数常量、字符串、时间变量(time)以及参数(parameter)等。
在Verilog-1995标准中,描述变量reg时使用了register一词,这导致许多初学者对reg变量产生了误解,认为reg对应于硬件中的寄存器;他们还形成了“wire用于编写组合逻辑,而reg用于编写时序逻辑”的理解,这其实比较片面。
首先,wire变量确实仅限于组合逻辑建模,无法用于时序逻辑建模。然而,reg变量不仅适用于时序逻辑建模,还可以用于组合逻辑建模。一些设计者倾向于将wire类型称为线网类型,实际上,当使用reg变量进行组合逻辑建模时,reg变量本质上也充当了线网的角色。在Verilog HDL的后续标准中,术语variable取代了register,这在一定程度上减少了初学者对reg变量可能产生的误解。
其次,wire和reg是Verilog HDL中仅有的两种变量类型。在声明wire或reg变量时,它们可以是一位的,也可以是多位的。一位的wire或reg变量亦被称为标量,而多位的wire或reg变量则被称作矢量。此外,与其他程序设计语言类似,Verilog HDL也可以声明数组(Array)类型的变量。
在使用方法上,对wire和reg这两种类型的变量赋值存在显著差异:对于wire类型的变量,赋值必须通过assign语句(即连续赋值语句)来实现;而对于reg类型的变量,赋值则应在过程赋值语句中完成,例如在always语句块内进行。
将Verilog HDL标准中的Procedure译作“进程”并不准确。Procedure一词,实际上是指代码段,即由一个或多个语句构成的代码段。
Verilog HDL中的进程有以下4种形式。
● always语句。
● initial语句。
● function(函数)。
● task(任务)。
在这些形式中,使用频率最高的当数always语句。在构建测试平台时,initial语句也经常被采用。一些设计者会在可综合的设计代码中加入initial语句,以初始化特定变量在复位后的电平。然而,这种做法并非最佳解决方案,因为没有逻辑硬件电路与之对应。实际上,使用initial语句反映了软件设计人员的思维习惯,而Verilog HDL本质上是用来描述硬件的。对于硬件电路,我们期望它在上电或复位时能够达到一个确定的电平状态,无论是高电平还是低电平。实现这一点的最佳方式是通过不同的电路单元结构来完成。例如,若希望复位后电路处于高电平状态,应选择使用同步置位寄存器或异步置位寄存器。
在软件工程领域,进程和赋值等术语通常与程序设计语言紧密相关。Verilog HDL的特殊性就在于,虽然它也属于程序设计语言的范畴,但是其主要功能是对硬件进行详细描述。那么,在实体硬件电路中,赋值这一概念是如何实现和体现的呢?
在代码1-1中,为了使管脚led_out输出低电平,使用了assign关键字进行赋值操作:
assign led_out = 1'b0 ;
因此,在Verilog HDL中,对变量进行赋值实际上等同于在硬件层面设置信号的驱动源。
在Verilog HDL中运用预编译指令,可以有效应对特定的设计场景。例如,某个功能模块被编写并验证无误后,设计者可能不希望再对模块内部的代码进行修改,然而该模块可能需要适应两种不同的应用场景,一种用于实现功能A,另一种用于实现功能B。当然,设计者必须确保功能A和功能B都是正确的。
要实现这样的设计需求场景,除了使用预编译指令,也可以为功能模块设置一个控制信号。比如可以为一个模块设置一个控制信号bist_en,当该信号为低电平时,模块对输入管脚的其他信号进行处理;而当该信号为高电平时,模块内部切换到内置的测试激励来驱动对应的输入管脚,这样就可以通过输出管脚的响应来判断模块的输出是否符合预期。
下面用代码1-4提供的部分编码来详细说明这两种方式的差别。
代码1-4中,右侧的代码使用控制信号bist_en的方式,这相当于用bist_en作为输入的选择控制信号:当bist_en为高电平时,用cnt作为输入,这时可以设计cnt为规则变化的数据(每次累加1),以便分析输出数据;当bist_en为低电平时,使用pwm_gen_result作为输入。使用这种方式设计的硬件结构是一个两输入的数据选择器,由于两个数据源都会被用到,因此产生cnt、pwm_gen_result的逻辑资源都不可少。
代码1-4中,左侧的代码使用预编译指令的方式。当设计工程中定义了BIST_MODE时,只有`ifdef到`else之间的逻辑功能生效,即使用cnt作为输入。当没有定义BIST_MODE时,使用pwm_gen_result作为输入。从描述上看,好像和右侧代码一样,也是一个数据二选一的功能。但是,它实际综合的结果会根据是否定义了BIST_MODE而定:定义了BIST_MODE时,最后结果并不包含产生pwm_gen_result相关的功能;而没有定义BIST_MODE时,产生cnt的相关逻辑也会被优化掉。所以,可以简单地认为使用预编译指令的方式进行设计时,使用的逻辑资源比设置控制信号的方式要少。