硬件描述语言(Hardware Description Language,HDL)是一种用形式化方法来描述数字电路和系统的语言,类似于一般的计算机高级语言的语言形式和结构形式。数字电路系统的设计者利用HDL可以从上层到下层(从抽象到具体)逐层描述自己的设计思想,用一系列分层次的模块来表示极其复杂的数字电路系统。然后利用EDA工具逐层进行仿真验证,再利用自动综合工具把HDL描述的系统转换到门级电路网表。接下来使用FPGA自动布局布线工具把网表文件转换为具体电路布线结构的实现。硬件描述语言非常适合复杂的数字电路系统设计。
硬件描述语言已经有近30年的发展历史,并成功地应用于数字电路系统的设计、建模、仿真、验证和综合等各个阶段,使设计过程达到高度自动化。到20世纪80年代,已经出现了上百种硬件描述语言,它们曾极大地促进和推动了设计自动化的发展。然而,这些语言一般各自面向特定的设计领域和层次,而且众多的语言令使用者无所适从。因此,急需一种面向设计的多领域、多层次和普遍认同的标准硬件描述语言。最终,VHDL和Verilog HDL语言适应了这种趋势的要求,先后成为IEEE标准。
VHDL的全称为“超高速集成电路硬件描述语言”(Very-high-speed integrated circuit Hardware Description Language),1987年被IEEE确认为标准硬件描述语言。与Verilog HDL相比,VHDL语言具有更强的行为描述能力、丰富的仿真语句和库函数,语法严格,书写规则较烦琐,入门较难。
Verilog HDL是目前应用最广泛的一种硬件描述语言,用于数字电路系统设计。该语言允许设计者进行各种级别的逻辑设计,进行数字逻辑系统的仿真验证、时序分析和逻辑综合。Verilog HDL于1995年被IEEE确认为标准硬件描述语言,即Verilog HDL1364—1995;2001年IEEE发布了Verilog HDL1364—2001标准,加入了Verilog HDL-A标准,使Verilog有了模拟设计描述的能力。2005年System Verilog IEEE 1800—2005标准的发布更使得Verilog语言在综合、仿真验证和模块的重用等性能方面都有大幅度的提高。Verilog HDL具有C语言的描述风格,是一种比较容易掌握的语言,语言自由,但是初学者容易出错。
Verilog HDL和VHDL作为描述硬件电路设计的语言,其共同的特点是:能抽象表示电路的行为和结构,支持逻辑设计中层次与范围的描述,可借用高级语言的精巧结构来简化电路行为的描述,具有电路仿真与验证机制以保证设计的正确性,支持电路描述由高层到低层的综合转换,硬件描述与实现工艺无关,便于文档管理,易于理解和移植等。VHDL对大小写不敏感,Verilog HDL对大小写敏感。VHDL的注释为--,Verilog的注释与C语言相同,为//或/**/。
一个完整的VHDL程序包括实体(entity)、结构体(architecture)、配置(configuration)、包集合(package)和库(library)5个部分。在VHDL程序中,实体和结构体这两个基本结构是必需的,它们可以构成最简单的VHDL程序。实体用于描述电路器件的外部特性;结构体用于描述电路器件的内部逻辑功能或电路结构;包集合存放各设计模块都能共享的数据类型、常数和子程序等;配置用于从库中选取所需单元来组成系统设计的不同版本;库存放已经编译的实体、结构体、包集合和配置。
1.实体
实体是VHDL程序设计的基本单元。实体声明是对设计实体与外部电路进行接口的描述,以及定义所有输入端口和输出端口的基本性质,是实体对外的一个通信界面。根据IEEE STD 1076_1987的语法,实体声明以关键词entity开始,由end entity或end结束,关键词不分大写和小写。实体声明语句结构如下:
(1)实体名
实体名由用户自行定义,最好根据相应电路的功能来确定。例如4位二进制计数器,实体名可定义为counter4b;8位二进制加法器,实体名可定义为adder8b等。需要注意的是,一般不用数字或中文定义实体名,也不能将EDA工具库中已经定义好的元件名作为实体名,如or2、latch等。
(2)类属参量
类属参量是实体声明中的可选项,放在端口说明之前。它是一种端口界面常数,常用来规定端口的大小、实体中子元件的数目及实体的定时特性等。类属参量的值可由实体的外部提供,用户可以从外面通过重新设定类属参量来改变一个实体或一个元件的内部电路结构和规模。
(3)端口说明
端口为实体和其外部环境提供动态通信的通道,利用port语句可以描述设计电路的端口和端口模式。其一般书写格式为:
1)端口名
端口名是用户为实体的每一个对外通道所取的名字,通常为英文字母加数字的形式。名字的定义有一定的惯例,如clk表示时钟,D开头的端口名表示数据,A开头的端口名表示地址等。
2)端口模式
可综合的端口模式有4种,分别为IN、OUT、INOUT和BUFFER,用于定义端口上数据的流动方向和方式,具体描述如下:
IN定义的通道为单向输入模式,规定数据只能通过此端口被读入实体中。
OUT定义的通道为单向输出模式,规定数据只能通过此端口从实体向外流出,或者可以说将实体中的数据向此端口赋值。
INOUT定义的通道为输入输出双向端口,即从端口的内部看,可以对此端口进行赋值,也可以通过该端口读入外部数据信息,如RAM的数据端口、单片机的I/O口。
BUFFER定义的通道为缓冲模式,功能与INOUT类似,主要区别在于当需要输入数据时,只允许内部回读输出的信号,即允许反馈。缓冲模式用于在实体内部建立一个可读的输出端口。例如,计数器的设计,可以将计数器输出的计数信号回读,作为下一次计数的初值。与INOUT模式相比,BUFFER回读(输入)的信号不是由外部输入的。
2.结构体
结构体描述了实体的结构、行为、元件及内部连接关系,即定义了设计实体的功能,规定了实体的数据流程,制定了实体内部元件的连接关系。结构体是对实体功能的具体描述,一定要跟在实体的后面。
结构体一般分为两部分:第一部分是对数据类型、常数、信号、子程序和元件等因素进行说明的部分;第二部分是描述实体的逻辑行为、以各种不同的描述风格表达的功能描述语句,包括各种顺序语句和并行语句。结构体声明语句结构如下:
(1)结构体名
结构体名由用户自行定义,of后面的实体名指明了该结构体所对应的是哪个实体,有些设计实体有多个结构体,但结构体名不可以相同,通常用dataflow(数据流)、behavior(行为)、structural(结构)命名。上述三个名称体现了三种不同结构体的描述方式,便于阅读VHDL程序时直接了解设计者采用的描述方式。
(2)结构体信号定义语句
结构体信号定义语句必须放在关键词architecture和begin之间,用于对结构体内部将要使用的信号、常数、数据类型、元件、函数和过程加以说明。结构体定义的信号为该结构体的内部信号,只能用于这个结构体中。
结构体中的信号定义和端口说明一样,应有信号名称和数据类型定义。由于结构体中的信号是内部连接用的信号,因此不需要方向说明。
(3)结构体功能描述语句
结构体功能描述语句位于begin和end之间,具体描述了结构体的行为及其连接关系。结构体的功能描述语句可以含有5种不同类型的并行语句。每一语句结构内部可以使用并行语句,也可以使用顺序语句。
3.库
库用来存储已经完成的程序包等VHDL设计和数据,包含各类包定义、实体、结构体等。在VHDL中,库的说明总是放在设计单元的最前面。这样,设计单元内的语句就可以使用库中的数据,便于用户共享已经编译过的设计结果。
(1)库的语法
库的语法采用use语句,通常有以下两种格式:
● use 库名. 程序包名. 项目名;
● use 库名. 程序包名. all;
第一种语句格式的作用是向本设计实体开放指定库中的特定程序包内的选定项目;第二种语句格式的作用是向本设计实体开放指定库中的特定程序包内的所有内容。
(2)常见的库
1)IEEE库
IEEE库中包含以下4个包集合。
● STD__LOGIC__1164:标准逻辑类型和相应函数。
● STD__LOGIC__ARITH:数学函数。
● STD__LOGIC__SIGNED:符号数学函数。
● STD__LOGIC__UNSIGNED:无符号数学函数。
2)STD库
STD库是符合VHDL标准的库,使用时不需要显式声明。
3)ASIC矢量库
各个公司提供的ASIC逻辑门库。
4)WORK库
WORK库为现行作业库,存放用户的VHDL程序,是用户自己的库。
此外,VHDL语法比较规范,对任何一种数据对象(信号、变量、常数)必须严格限定其取值范围,即明确界定对其传输或存储的数据类型。在VHDL中,有多种预先定义好的数据类型,如整数数据类型INTEGER、布尔数据类型BOOLEAN、标准逻辑位数据类型STD_LOGIC和位数据类型BIT等。数据类型的定义包含在VHDL标准程序包STANDARD中,而程序包STANDARD包含于VHDL标准库STD中。
VHDL要求赋值符“<=”两边的信号数据类型必须一致。VHDL共有七种基本逻辑操作符,分别为AND(与)、OR(或)、NAND(与非)、NOR(或非)、XOR(异或)、XNOR(同或)和NOT(取反)。信号在这些操作符的作用下,可构成组合电路。逻辑操作符所要求的操作对象的数据类型有三种,即BIT、BOOLEAN和STD_LOGIC。
Verilog HDL是一种用于数字电路系统设计的语言,既是一种行为描述的语言,也是一种结构描述的语言。Verilog HDL可以在系统级(system)、算法级(algorithm)、寄存器传输级(RTL)、逻辑级(logic)、门级(gate)和电路开关级(switch)等多种抽象设计层次上描述数字电路。
● 系统级:用语言提供的高级结构能够实现待设计模块的外部性能的模型。
● 算法级:采用类似于C语言一样的if、case和loop等语句,实现算法行为的模型。
● 寄存器传输级:采用布尔逻辑方程,描述数据在寄存器之间的流动和如何处理、控制这些数据流动的模型。
● 门级:描述逻辑门及逻辑门之间连接的模型。
● 电路开关级:描述器件中三极管和存储节点及它们之间连接的模型。
运用Verilog HDL设计一个系统时,一般采用自顶向下的层次化、结构化设计方法。自顶向下的设计是从系统级开始,把系统划分为基本单元,然后把每个基本单元划分为下一层次的基本单元,一直进行划分,直到可以直接用EDA元件库中的基本元件来实现为止。该设计方法的优点是,在设计周期开始之前进行系统分析,先从系统级设计入手,在顶层划分功能模块将系统设计分解成几个子设计模块,对每个子设计模块进行设计、调试和仿真。由于设计的仿真和调试主要是在顶层完成的,所以能够早期发现结构设计上的错误,避免设计工作上的浪费,同时减少了逻辑仿真的工作量。自顶向下的设计方法使几十万门甚至几百万门规模的复杂数字电路的设计成为可能,同时避免了不必要的重复设计,提高了设计效率。
一个复杂数字电路系统的完整Verilog HDL模型是由若干个Verilog HDL模块构成的,每一个模块又可以由若干个子模块构成。因此模块(module)是Verilog的基本单元。
1.模块
一个模块是由两部分组成的:一部分描述接口,另一部分描述逻辑功能,即定义输入是如何影响输出的。模块的结构如下:
所有的Verilog程序都以module声明语句开始,模块名用于命名该模块,一般与实现的功能对应。每个Verilog程序包括4个主要部分:端口定义、I/O说明、内部信号声明和功能定义。下面举例进行说明,一个简单的模块结构组成如图2.24所示,其中图2.24(a)为逻辑电路图,图2.24(b)为与之对应的程序模块。该实例中,模块名为block,模块中的第二、第三行定义了接口的信号流向,输入端口为a和b,输出端口为c和d。模块的第四、第五行说明了模块的逻辑功能,分别实现了与门和或门的输出。
图2.24 模块结构的组成
(1)模块的端口定义
模块的端口声明了模块的输入端口和输出端口,其格式为:
(2)I/O说明
I/O说明指的是模块输入/输出说明,I/O说明的格式为:
I/O说明也可以写在端口声明语句里,其格式为:
(3)内部信号说明
内部信号说明指的是模块内部用到的和与端口有关的wire和reg类型变量的声明。
例如:reg [7:0] out; //定义了out的数据类型为寄存器类型
(4)功能定义
模块中最重要的部分是逻辑功能定义部分。有三种方法可以在模块中描述要实现的逻辑功能。
1)使用“assign”声明语句
这种方法的句法很简单,只需写一个“assign”,后面再添加一个方程式即可。
例如:assign a = b & c;描述了一个有两个输入的与门。
2)使用实例元件
Verilog-HDL提供了一些基本的逻辑门模块,如与门(and)、或门(or)等。采用实例元件的方法像在电路原理图输入方式下调入库元件一样,直接输入元件的名字和相连的引脚即可。使用实例元件的语句,就不必重新编写这些基本逻辑门的程序,简化了程序。使用实例元件的格式为:
例如:and u1(c, a, b);表示模块中使用了一个和与门(and)一样的名为u1的与门,其输入端为a,b,输出端为c。
3)使用“always”块语句
例如:
这段代码描述的逻辑功能为:当时钟信号的上升沿到来时,对输入信号a和b进行逻辑与操作,并将结果送给输出信号q。
采用“assign”语句是描述组合逻辑最常用的方法之一。“always”块语句既可用于描述组合逻辑,也可描述时序逻辑。always @(<敏感信号列表>)语句的括号内表示的是敏感信号或表达式,即当敏感信号或表达式发生变化时,执行always块内的语句。
(5)模块要点总结
1)在Verilog模块中所有过程块(如always块)、连续赋值语句、实例引用都是并行的。例如前述的三个例子分别使用了“assign”语句、实例元件和“always”块,如果把这三个例子写到一个Verilog模块文件中,它们的顺序不会影响实现的功能,因为这三项是同时执行的,也就是并发的。
2)在“always”模块内,逻辑是按照指定的顺序执行的。“always”块中的语句称为“顺序语句”,因为它们是顺序执行的,所以“always”块也称为“过程块”。注意:两个或更多的“always”模块都是同时执行的,而模块内部的语句是顺序执行的。
3)只有连续赋值语句“assign”和实例引用语句可以独立于过程块而存在于模块的功能定义部分。
4)Verilog注释之前用//,和C语言一样,也可以将注释放在/*...*/之间。Verilog对大小写敏感,所有关键词都是小写。定义变量时要注意区分大小写。例如,B和b被认为是两个不同的变量。
2.模块的例化
一个模块可以由几个模块组成,一个模块也可以调用其他模块,形成层次结构。对低层次模块的调用称为模块的例化。
例如,利用模块例化语句描述图2.25所示的反相器级联逻辑电路图。
图2.25 反相器级联逻辑电路图
Verilog程序如下:
模块例化语句的格式为:
其中,端口列表中信号的顺序可以采用位置匹配方式,如inv1 G1(b, d),实例化时采用位置关联,G1的b对应模块inv1的输入端口a,G1的d对应inv1的输出端口e。如果对应顺序发生错误,结果也会错误。也可以采用信号名匹配方式,如inv1 G2(.a(d), .e(c)),当端口列表中的信号比较多时,一般都采用信号名匹配方式。
3.数据类型
数据类型是用来表示数字电路硬件中的数据储存和传送元素的,Verilog HDL总共有19种数据类型。常用的4个基本数据类型是:reg型、wire型、integer型和parameter型。
(1)常量
1)整数
在Verilog HDL中,整型常量即整常数有二进制整数(b或B)、八进制整数(o或O)、十进制整数(d或D)、十六进制整数(h或H)。完整的数字表达方式有以下三种:
● <位宽> <进制> <数字>;这是一种全面的描述方式。
● <进制> <数字>;这种描述方式中,数字的位宽采用默认位宽(由具体机器系统决定,至少为32位)。
● <数字>;这种描述方式中,采用默认进制(十进制)。
例如:
2)x和z值
在数字电路中,x代表不定值,z代表高阻值。每个字符代表的位宽取决于所用的进制。例如:8`b1010xxxx和8`hax所表示的含义是等价的。z还有一种表达方式可以写作“?”。在使用case表达式时建议使用这种写法,以提高程序的可读性。例如:
3)参数(parameter)型
在Verilog HDL中,用parameter定义一个标识符代表一个常量,称为符号常量,即标识符形式的常量。采用标识符代表一个常量可提高程序的可读性和可维护性。Parameter型数据的说明格式如下:
例如:
参数型常数经常用于定义延迟时间和变量宽度。在模块或实例引用时,可通过参数传递改变在被引用模块或实例中已定义的参数。
(2)变量
变量是一种在程序运行过程中其值可以改变的量,在Verilog HDL中有多种数据类型的变量,以下介绍常用的几种类型。
1)wire型
连线wire通常表示一种电气连接,采用连线类型表示逻辑门和模块之间的连线。wire型数据常用来表示用以assign关键字指定的组合逻辑信号。Verilog程序模块中输入、输出信号类型缺省时,自动定义为wire型,其格式如下。
① 表示一位wire型的变量:
② 表示多位wire型的变量:
其中,[n-1:0]和[n:1]代表了数据的位宽,即该数据有几位。
例如:
wire型不能出现在过程语句(initial或always语句)中。当采用层次化设计数字系统时,常用wire型声明模块之间的连线信号。
2)reg型
寄存器是数据储存单元的抽象,寄存器数据类型的关键字是reg。reg型变量反映具有状态保持功能的变量,在新的赋值语句执行以前,reg型变量一直保持原值。
reg型数据常用来表示“always”模块内的指定信号,常代表触发器。在“always”模块内被赋值的每一个信号都必须定义成reg型。reg型数据的格式如下。
① 表示一位reg型的变量:
② 表示多位reg型的变量:
其中,[n-1:0]和[n:1]代表了数据的位宽,即该数据有几位。
4.运算符及表达式
Verilog HDL的运算符范围很广,其运算符按其功能可分为以下几类。
(1)算术运算符
+:加法运算符,或正值运算符;
-:减法运算符,或负值运算符;
*:乘法运算符;
/:除法运算符;
%:模运算符。
(2)关系运算符
= =:等于;
!=:不等于;
<:小于;
< =:小于等于;
>:大于;
> =:大于等于。
进行关系运算操作时,如果声明的关系是假,则返回值是0;如果声明的关系是真,则返回值是1;如果某个操作数的值不确定,则结果是一个不确定值“x”。
(3)逻辑运算符
执行逻辑运算操作时,运算结果是1位的逻辑值。
&&:逻辑与;
| |:逻辑或;
!:逻辑非。
(4)位运算符
位运算符可将操作数按位进行逻辑运算。
~:取反;
&:与;
|:或;
^:异或;
^~:同或。
位运算符中除了“~”是单目运算符以外,其他的均为双目运算符,即要求运算符两侧各有一个操作数。位运算符中的双目运算符要求对两个操作数的相应位进行运算操作。
逻辑与“&”和“&&”运算的结果是不同的,例如:
逻辑或“|”和“||”运算的结果也是不同的,例如:
(5)移位运算符
>>:右移;
<<:左移。
例如:如果定义reg [3:0] a,则a<<1表示对操作数左移1位,自动补0。
移位之前:
移位之后: