1. VHDL的总体结构
一个相对完整的VHDL程序,通常包含库(library)、程序包(package)、实体(entity)、结构体(architecture)和配置(configuration) 5部分,其总体结构如图2-3所示。
库用于存放已编译的实体、结构体、包集合和配置;程序包用于存放各设计模块能共享的数据类型、常数和子程序等;实体用于描述设计实体的外部接口信号(即I/O信号);结构体用于描述设计实体的内部电路;配置用于从库中选取所需元件,并将其安装到设计单元的实体中。除了库以外,VHDL的其余4部分是可以分别编译的源设计单元。
并不是所有的VHDL程序都必须具备这5个部分,在项目设计中,库和程序包根据需要进行调用,实体和结构体是需要进行设计的主要部分,而配置部分仅在一个实体对应有多个结构体时才需要编写。由于模块化概念日益加深,一个模块只完成一个特定的功能,所以一般不需要编写配置。在此,以二选一电路的VHDL程序为例,讲述VHDL的程序结构,其程序清单如下:
图2-3 VHDL程序总体结构图
此程序中,A和B都是被选对象,SEL为选择控制端,Y为选择结果输出端。该程序的含义是,当SEL=0时,将A中的内容输出到Y;否则将B中的内容输出到Y。下面简单分析这个VHDL程序源代码的结构。
第1行为注释行,程序中的注释使用双横线“--”来表示。在VHDL程序的任何一行中,双横线“--”后的文字都不参加编译和综合。第2行为调用IEEE库。第3行是使用IEEE库中的std_logic_1164程序包。第4行至第8行为实体部分,其实体名为select_2to1,其中第5行到第7行为端口说明语句,定义了输入端口A、B和SEL,以及输出端口Y。第9行至第13行为结构体部分,其结构体名为gate,其中第11行和第12行为结构体功能描述语句,描述了Y与A、B和SEL的关系。
对编写的VHDL程序进行保存时,其文件名可以由设计者任意给定,但具体取名最好与文件实体名相同;文件的扩展名必须是“.vhd”,如select_2to1.vhd。但考虑到某些EDA软件的限制和VHDL程序的特点,即在元件(如语句中的被调用文件)调用中,其元件名与文件名是等同的,因此建议程序的存盘的文件名应该与该程序的实体名一致。
2.实体
实体(entity)主要用于描述设计实体(即设计项目)与外部电路的接口,是设计实体的表层设计单元。它规定了设计单元的I/O端口信号或引脚,给出了设计模块与外界的通信接口,但并不描述电路的具体构造和实现的功能。
1)实体格式 任何一个基本设计单元的实体,其语句格式如下:
一个基本设计单元的实体说明以“entity实体名is”开始,以“end\[实体名\];”结束。在VHDL中,通常是不区分大写或小写的;实体名是由设计者自行定义的,可在其他设计实体对该设计实体进行调用时使用;中间方括号内的语句描述,设计者可根据实际情况的需求而决定是否输入。
2)类属参数说明 Generic语句称为类属参数说明语句,该语句必须放在指定的端口说明前,用于向模块传递参数。该语句格式为:
VHDL定义了很多数据类型,类属变量可以采用的数据类型受编译环境的限制,依编译器不同而不同。设定值是初始化值,即传递给模型的参数。
【例2-1】有类属说明的分频器的实体描述。
在例2-1中,generic类属参数说明语句中对分频系数n做了定义,其设定值为整数12。设定n后,只要在该程序中出现n,就表示该数为整数12。
3)端口说明 端口说明是对基本设计实体与外部接口的描述,也可以是对外部引脚信号的名称、数据类型和I/O类型的描述,其语句格式如下:
其中,端口名是设计者为实体的每个对外通道所取的名字,它可以是由字母、数字、下划线组成,但只能以字母开头,下划线不能放在开头和结尾。
IEEE 1076标准包中定义了4种常用的端口模式,各端口模式功能及符号见表2-1。
表2-1 端口模式说明
IN相当于只可输入的引脚,规定为单向只读模式,即信号只能从外部流向该设计实体内部,而不能反向,可以将变量或信号信息通过该端口读入。
OUT相当于只可输出的引脚,规定为单向输出模式,即信号只能从该端口流出设计实体,而不能反向,可以将信号通过该端口输出。
BUFFER相当于带输出缓冲器并可以回读的引脚(与TRI引脚不同)。缓冲模式的驱动源既可以是其他实体的缓冲端口,也可以是被设计实体内部的信号源。缓冲模式从本质上仍然是一个输出模式,只是在内部结构中具有将在端口上输出至外部的信号反馈的功能,即允许内部回读输出的信号。
INOUT相当于双向引脚(即BIDIR引脚),既允许信号从外部流向该设计实体内部,也允许信号从该端口流出设计实体,即通过这个端口既可以读入数据,也可以对此端口赋值,它满足了设计实体数据流中双向数据的要求。它可以引入内部反馈,是一种比较完备的模式,可以替代其他3种模式,但是为了识别信号的用途和任务,一般不作这种替代。
BUFFER与INOUT的区别在于,BUFFER只能接受一个驱动源,不允许多重驱动。此外,INOUT双向模式的反馈信号是从外部读入的,而缓冲模式反馈的信号是由内部产生并向外输出的信号。通常设计者有两种方法实现内部反馈,即利用BUFFER模式建立一个缓冲端口,或者在结构体中定义一个内部节点信号,再将它利用输出端口输出。
3.结构体
结构体(architecture)又称为构造体,它是描述设计实体的内部结构和外部设计实体端口之间的逻辑关系,即具体定义了设计单元的功能。结构体对其基本设计单元的I/O关系可以用3种方式进行描述,即行为描述(基本设计单元的数学模型描述)、结构描述(逻辑元件连接描述)和以上两种方法混合使用。不同的描述方式仅体现在描述语句上,而结构体的基本结构是完全相同的。
1)结构体格式 结构体必须放在实体的后面,一个实体可以有多个结构体,结构体的具体格式如下:
结构体语句必须以“architecture结构体名of实体名is”开始,以语句“end\[architecture\]\[结构体名\];”结束。实体名必须是所在设计实体的名字,而结构体名由设计者自己命名,允许与实体名相同。但是,当一个实体中同时拥有多个结构体时,每个结构体都应有自己的名字,且不能相同。
2)结构体说明语句 结构体说明语句位于“architecture”和“begin”之间,用于对结构体的内部所使用的信号、常数、数据类型和函数进行定义。
【例2-2】结构体说明语句。
在例2-2中,结构体名为one,实体名为complex_ lamp;自定义的数据类型为state,它包含st0~st4这5种状态;定义信号current_state,其数据类型为state;定义信号cnt,其数据类型为整数,取值范围为0~n;定义信号clk_new,其数据类型为标准逻辑;定义信号LED_tmp,其数据类型为标准逻辑矢量;定义信号cnm,其数据类型为整数,取值范围为0~15。
3)结构体功能描述语句 结构体功能描述语句位于“begin”和“end architecture\[结构体名\]”之间,用于描述设计实体的功能、具体行为。按照语句执行的方式不同,它包含顺序语句和并行语句。
顺序语句是以顺序执行方式工作的语句,它总是在进程语句(process)的内部。从仿真的角度来看,该语句是按书写顺序执行的。顺序语句主要包括if语句、case语句、loop语句等。
并行语句又称为并发语句,是以并行方式工作的语句,该语句的执行与书写顺序无关。并行语句在结构体中的执行都是同时进行的,这种并行性是由硬件本身的并行性决定的,一旦电路接通电源,各部分就会按照事先设计好的方案同时工作。并行语句主要包含并行信号赋值语句、块(block)语句、进程语句、子程序调用语句、元件例化语句、生成语句等。
4.库
库(Library)是经编译后的数据的集合,它存放包定义、实体定义、结构体定义和配置定义等。在设计单元内的语句可以使用库中的结果,所以有库就可以使设计者共享编译后的设计结果。库的说明总是放在设计单元的最前面,表示该库资源对以下单元开放。库语句格式如下:
VHDL中的库大致可归纳为5种,即IEEE库、STD库、WORK库、VITAL库和用户定义库。
1) IEEE库 IEEE库是VHDL设计中最常用的资源库,它包含IEEE标准的程序包和其他一些支持工业标准的程序包。IEEE库中的标准程序包主要包括std_logic_1164(标准逻辑类型和相应函数)、std_logic_arith(数学函数)、std_logic_signed(符号数学函数)、std_logic _unsigned(无符号数学函数)。
IEEE库中的程序包虽然符合IEEE标准,但并不表示符合VHDL语言标准,因此使用时必须在设计实体的开头采用库和程序包声明语句预先声明。如果要使用上面提到的程序包,必须在设计实体开头声明如下:
2) STD库 STD库为设计库,它包含了STANDARD和TEXTIO这两个程序包。STD库符合VHDL语言标准,是VHDL语言编译和综合工具的一部分,只要在VHDL应用环境中,就可随时调用这两个程序包中的所有内容,即VHDL在编译过程中会自动调用这个库,所以使用时不需要用语句另外说明。
由于STD库符合VHDL语言标准,它定义了最基本数据类型(bit、bit_vector、boolean、integer等),因此在应用中不需要在使用前用“library std;”这样的语句进行声明。但是,在STD库中还包含“TEXTIO”程序包,该程序包包含对文本文件进行读/写的过程和函数,按行对文件进行处理,一行为一个字符串,并以回车符、换行符作为结束符;另外还定义了一些支持ASCII I/O操作的若干类型的程序。
程序包在使用TEXTIO程序中的数据时,应先说明库和包集合名,然后才可以使用该程序包中的数据,如:
3) WORK库 WORK库是用户(程序设计者)的VHDL设计的当前工作库,用于存放用户设计和定义的所有设计单元,并且接受用户的分析和修改。用户在项目中设计好的、未验证的、正在仿真的中间模块都放在WORK库中。采用VHDL进行数字电路或系统设计时,必须为设计项目设定一个目录以保存此项目中所有的设计文件和生成文件,VHDL综合器会将此目录默认为WORK库,使它对当前设计实体的所有设计单元可见,因此不允许将设计项目直接存放在根目录下。注意,工作库并不是这个目录的目录名,而是一个逻辑名。WORK库的使用方法如下:
4) VITAL库 VITAL库中包含了预定义的时序程序包VITAL_TIMING和基本元件程序包VITAL_PRIMITIVES,可以提高VHDL门级时序仿真的精度,因此只在VHDL仿真器中使用。VITAL程序包已经成为IEEE标准,在当前的VHDL仿真器的库中,VITAL库中的程序包都已经并到IEEE库中。
5)用户定义库 用户(程序设计者)定义库简称用户库,是由用户自己创建并定义的库。设计者可以把自己经常使用的非标准(一般是自己开发的)包集合和实体等汇集在一起定义成一个库,作为对VHDL标准库的补充。用户定义库在使用前必须使用library子名加以说明。
5.程序包
程序包是已定义的常数、数据类型、元件调用及子程序的一个集合,是库结构中的一个层次,是一个可以编译的设计单元。一个库中可以包含多个不同的程序包。程序包实现了资源的重复利用和共享,对于大规模的复杂数字系统设计来说,避免了重复开发和资源浪费,从而缩短开发周期,节省开发成本。
程序包主要有4种基本组成,即常数说明(主要用于预定义系统的宽度,如数据总线通道的宽度),数据类型说明(主要用于说明在整个设计中通用的数据类型,如地址总线数据类型定义等),元件定义(主要用于规定在VHDL设计中参与元件例化的文件与外界的接口界面),子程序说明(用于说明在设计中任意一处可调用的子程序)。在任意一个程序包中,至少包含4种结构中的一种。
程序包分为包头和包体两部分。包头(又称为程序包说明)是对包中使用的数据类型、元件、函数和子程序进行定义,其形式与实体定义类似。包体规定了程序包的实际功能,存放函数和过程的程序体,而且还允许建立内部的子程序、内部变量和数据类型。包头、包体均以关键字PACKAGE开头,程序包格式如下。
常用的预定义的程序包有STD_LOGIC_1164、STD_LOGIC_ARITH、STD_LOGIC_ UNSIGNED、STD_LOGIC_SIGNED、STANDARD、TEXTIO NUMERIC_STD和NUMERIC_BIT。下面详细介绍其中6个最常用的程序包。
1) STD_LOGIC_1164程序包 它是IEEE库中最常用的程序包,是IEEE的标准程序包。该程序包中包含了一些数据类型、子类型和函数的定义,这些定义将VHDL扩展为一个能描述多值逻辑(即除具有0和1外,还有其他的逻辑量,如高阻态“Z”、不定态“X”等)的硬件描述语言,很好地满足了实际数字系统的设计需求。该程序包中包含的数据类型有STD_ULONGIC、STD_ULONG_VECTOR、STD_LOGIC和STD_LOGIC_VECTOR。其中用得最多和最广的是定义了满足工业标准的两个数据类型STD_LOGIC和STD_LOGIC_VECTOR,它们非常适合于CPLD/FPGA器件中的多值逻辑设计结构。
2) STD_LOGIC_ARITH程序包 它预先编译在IEEE库中,是Synopsys公司的程序包。此程序包是在STD_LOGIC_1164程序包的基础上扩展了3个数据类型,即UNSIGNED、SIGNED和SMALL_INT,并为其定义了相关的算术运算符和转换函数。
3) STD_LOGIC_UNSIGNED和STD_LOGIC_SIGNED程序包 这两个程序包都是Synopsys公司的程序包,均预先编译在IEEE库中。这些程序包重载了可用于INTEGER型,以及STD_LOGIC和STD_ LOGIC_VECTOR型混合运算的运算符,并定义了一个由STD_ LOGIC_VECTOR型到INTEGER型的转换函数。这两个程序包的区别是,STD_LOGIC_ SIGNED中定义的运算符考虑到了符号,是有符号数的运算,而STD_LOGIC_UNSIGNED则与其正好相反。
虽然程序包STD_LOGIC_ARITH、STD_LOGIC_UNSIGNED和STD_LOGIC_SIGNED未成为IEEE标准,但已经成为事实上的工业标准,绝大多数的VHDL综合器和VHDL仿真器都支持它们。
4) STANDARD和TEXTIO程序包 这两个程序包是STD库中的预编译程序包。STANDARD程序包中定义了许多基本的数据类型、子类型和函数,它是VHDL标准程序包,在实际应用中已被隐性地打开了,故不必再用USE语句另作声明。TEXTIO程序包定义了支持文本文件操作的许多类型和子程序,在使用本程序包前,需加语句USE STD.TEXTIO.ALL。
TEXTIO程序包主要供仿真器使用。可以用文本编辑器建立一个数据文件,文件中包含仿真时需要的数据,仿真时用TEXTIO程序包中的子程序存取这些数据。在综合器中,此程序包被忽略。
6.配置
配置用于描述层与层之间的连接关系,以及实体与结构体之间的连接关系。设计者可以利用配置语句来选择不同的结构体,使其与要设计的实体相对应,也可以使元件与实体—结构体相对应。配置语句被广泛应用于仿真,它提供了一个可变的快速交互设计的方式,但在综合时,该语句不被支持。
配置语句的基本书写格式如下:
配置名由设计者自行命名,配置说明语句部分根据不同的情况而有所区别。默认情况下,配置语句的格式如下:
这种配置用于选择不包含块语句(block)和元件(component)的结构体。在配置语句中,只包含为了进行配置的实体所选配的结构体名,通过选择不同的结构体来组成设计实体,以体现不同的实现方案。
【例2-3】默认配置格式的逻辑门VHDL程序。
在例2-3中,描述了两种不同的功能,其中结构体one描述了“与门”的功能;结构体two描述了“或门”的功能。在结构体配置中使用了“for one”,表示为实体选择结构体one。如果要为实体选择结构体two,其配置语句应为:
在例2-3中,如果没有配置语句部分,则综合器将采用默认配置,即为实体配置的是最后一个编译的结构体two。
VHDL的语言要素主要包括文字规则、数据对象、数据类型和运算符等。
1.文字规则
VHDL的文字主要包括数值和标志符,其中数值型文字主要包括数字型、字符串型和位串型。在书写这些文字时,必须要遵守相应的规则。
1)数字型文字 主要包含整数文字、实数文字和以数制基数表示的文字。
整数文字是以十进制表示的数,如2,86,0,321E3(= 321000),8_123_456_789(= 8123456789)等。其中,“321E3”是以科学计数法表示的数;下划线并无实际意义,只是为了提高文字的可读性,相当于一个空格符,不影响文字本身的数值。
实数文字与整数文字一样,也是以十进制表示的数,只不过实数文字必须带有小数点,如3.12,0.0,3.5E-2(=0.035),123_234.456_987(= 123234.456987)等。
以数制基数表示的文字由5部分组成:第1部分,用十进制数标明数制进位的基数,如八进制为8,十六进制为16;第2部分,数制隔离符号“#”;第3部分,表达的文字;第4部分,指数隔离符号“#”;第5部分,指数部分,如果这一部分为0,则可以省略。如:
2)字符串型文字(文字串和数位串) 字符是用单引号括起来的ASCII字符,既可以是字符,也可以是符号或字母,如'a'、'b'、'@'等。而字符串是用双引号括起来的一维字符数组,它包含文字字符串和数位字符串。
文字字符串是用双引号引起来的一串文字,如“ERROR”、“GOOD”、“thank you”、“C”等。
数位字符串又称位矢量,是预定义的数据类型bit的一维数组。它们所代表的是二进制、八进制、十六进制的数组,其位矢量的长度即为等值的二进制数的位数。数位字符串的表示首先要计算基数,然后将该基数表示的值放在双引号中,基数符号以“B”、“O”和“X”表示,并且放在字符串的前面,它们的含义如下所述。
B:二进制基数符号,表示二进制数位0或1,在字符串中每一位表示一个位(bit)。
O:八进制基数符号,在字符串中每个数代表一个八进制数,即代表一个3位(bit)的二进制数。
X:十六进制基数符号,在字符串中每个数代表一个十六进制数,即代表一个4位(bit)的二进制数。
数位字符串的格式为:基数符号“数值”。例如:
3)标志符 标志符是设计者用于定义常数、变量、信号、端口、子程序或参数的名字。在VHDL中,标志符的书写规范规定了VHDL语言中符号书写的一般规则。在VHDL的基本标志符书写时,应遵循以下规则。
以英文字母开头,不能以下划线结尾('_');
不能连续使用下划线('__'),且前面必须为英文字母及数字;
有效字符为26个英文字母('a'~'z'或'A'~'Z')、数字('0'~'9')及下划线('_');
保留字(关键字)不能作为标志符(VHDL中的保留字见表2-2)。
合法的标志符: ex_and,ok_2_ f,return_ok等。不合法的标志符: entity、_ex3、3ex、sign#2、ex-3、ex3_、ex__3等。
表2-2 VHDL中的保留字
4)下标名及下标段名 下标名用于指示数组型变量或信号的某一元素,其语句格式如下:
数组类型信号名或变量名(表达式);
其中,数组类型信号名或变量名由设计者来定义,表达式所代表的值必须是数组元素下标范围内的值,可以是计算的,也可以是不可计算的。不可计算的值有时可能不被综合,或者综合时消耗的资源较大。
下标段名则用于指示数组型变量或信号的某一段元素。其语句格式如下:
其中,表达式1与表达式2的数值必须在数组元素下标号范围内,并且必须是可计算的。“to”表示数组下标序列由低到高,如0 to 3;“downto”表示下标序列由高到低,如7 downto 4。
2.数据对象
VHDL语言中的数据对象(Data Objects)主要包括常量(constant)、变量(variable)和信号(signal)这3种。这3种数据对象的含义和使用场合见表2-3。
表2-3 VHDL数据对象含义和使用场合
1)信号 信号包括I/O引脚信号及IC内部缓冲信号,有硬件电路与其相对应,所以信号之间的传递有实际的附加延时。在实体中定义为外接I/O引脚信号,在architecture语句与begin语句之间定义为全局信号。信号与变量的形式几乎相同,只有一项重要的差异,即信号可以用于存储或传递逻辑值,因此可被集成为存储器件或数据总线,而变量则不能。信号可以在不同进程之间传递,变量则不能。信号的定义格式如下:
其中,信号名是设计者自行命名的合法标志符,允许同时有多个信号名,在信号名之间用逗号(“,”)隔开;初始值的设计不是必须的,并且仅在行为仿真中有效。例如:
信号的赋值语句表达式如下:
2)变量 变量并不对应任何I/O引脚,只是用于作为指针或存储程序中计算用的暂时值,所以变量之间的传递是瞬时的,并没有实际的附加延时。变量只能出现在process、function、procedure等语句中,其值仅局部有效。要先指定给信号,才能将其最终值传出进程。
变量定义的一般格式如下:
其中,变量名是由设计者自行命名的合法标志符,允许同时有多个变量名,在变量名间用逗号(“,”)隔开;初始值可以是一个与变量具有相同数据类型的常数值,也可以是一个与变量具有相同数据类型的全局静态表达式,初始值不必非要在声明时给出,且只在仿真中有效,综合时将被综合器所忽略。如:
变量数值的改变是通过变量赋值来实现的,其赋值语句格式如下:
3)常量 常量的定义和设置是为了使设计实体中的常数更容易阅读和修改,它代表了数字电路中的电源、地及恒定逻辑值等常数。
在程序中,常量在使用前必须加以定义说明,一旦说明赋值后,程序中的常量值将不再改变,因而具有全局意义。常量定义的一般格式为:
其中,常量名是由设计者自行命名的合法标志符;数据类型必须与表达式的数据类型一致,可以是标量类型或复合类型,但不能是文件类型(file)或存取类型(access)。如:
常量可以在程序包、实体、结构体、块、子程序或说明区域进行定义,其使用范围取决于它在何处被定义。定义在程序包中的常量具有最大全局化特征,可以用在调用此程序包的所有设计实体中;定义在设计实体中的常量,其有效范围为这个实体定义的所有的结构体;定义在设计实体的某一结构体中的常量,则只能用于此结构体;定义在结构体的某一单元的常量,如一个进程中,则这个常量只能用在该进程中。
4)计数指针 计数指针本身是一种整数类型,但不对应任何信号,只是在程序中用做计数器,所以不需要说明其数据类型(正整数),如下面例子中的指针I。
如果用于运算,则应加以说明,例如:
5)数据对象定义的例外 在VHDL中,大部分的数据类型必须事先说明才能被使用,而且可以用“:=”运算符赋初值。但是有些数据例外,例如:
IC器件的I/O引脚已隐含定义为信号,不用再说明;
IC器件的参数值(Generics)已隐含定义为常数,不用再说明;
格式化函数(Function)参数必须是常数或信号,而且已在函数说明中隐含定义;
循环(Loop)或生成(Generate)语句开始时,其指针(Index)的数据类型已隐含定义了,而在语句结束后,则自动消失。
3.数据类型
VHDL是一种强类型语言,要求设计实体中的每个常数、信号、变量、函数及设定的各种参量都必须具有确定的数据类型,并且只有数据类型相同的量才能互相传递和作用。VHDL中的数据类型主要包含预定义数据类型、用户自定义数据类型、复合数据类型及枚举数据类型。
1)预定义数据类型(Pre-Defined Data Types) VHDL的预定义数据类型都是在VHDL标准程序包standard中定义的,在实际使用中已自动包含在VHDL的源文件中,不必通过use语句加以显示调用。
(1)位(bit):在数字系统中,信号值通常用一个位来表示,位允许的数值为“0”或“1”。位类型的示例如下:
(2)位矢量(bit_vector):位矢量由一串用双引号括起来的位组成,可以按递增或递减方式排列,用于表示总线的状态。位矢量的示例如下:
示例中,首先使用递增(to)方式定义了均为4位数据总线的a和b,然后再以增减(downto)方式定义了4位数据总线c,并且给c赋初值。赋初值后,c(3)='1',c(2)= '0',c(1)='1',c(0)='0'。
在VHDL中,单独的一位用单引号('')标明,而位矢量的常数则用双引号("")标明。如果程序中赋值语句为c<= O"7",则表示c="0111",而“O”表示八进制。若程序中赋值语句为c<=X"54",则表示c="01010100",而“X”表示十六进制。
(3)布尔量(boolean):一个布尔量只有false(假)和ture(真)两种状态,布尔量不能进行算术运算,只能进行关系运算。例如,它可以在if语句中被测试,测试结果产生一个布尔量false或ture。
(4)字符(character):字符允许的数据内容为128个标准ASCII字符,字符量通常用单引号引起来,如'A'。
(5)字符串(string):字符串由一串字符组成,如:
该语句定义了字符串s_word=Chang-Sha,其中s_word(1)='C',s_word(9)='a'。
字符与字符串类型的数据是不可综合的,只有仿真器可以处理字符与字符串。一般情况下,VHDL对大小写不敏感,但是对字符量中的大小写字母则认为是不一样的。
(6)标准逻辑(std_logic):标准逻辑位类型是对标准位数据类型(bit)的扩展,共定义了9种取值,即U(未初始化的)、X(强未知的)、0(强0)、1(强1)、Z(高阻态)、W(弱未知的)、L(弱0)、H(弱1)、-(忽略)。这意味着,对于定义为数据类型是标准逻辑位std_logic的数据对象,其可能的取值已非传统的bit那样只有0和1两种取值,而是如上述定义的那样有9种可能的取值。目前在设计中一般只使用IEEE的std_logic标准逻辑的位数据类型,bit类型则很少使用。
标准逻辑位类型是在IEEE的std_logic_1164程序包中定义的。使用标准逻辑位类型时,必须在程序的开头声明这个程序包。该程序包还定义了std_ logic型逻辑运算符(AND、NAND、OR、NOR、XOR和NOT等),以及用于bit与std_logic之间的相互转换函数。因此,在程序中使用std_logic时,必须在程序的开头加入以下语句:
由于标准逻辑位数据类型具有多值性,在编程时应当特别注意。因为在条件语句中,如果未考虑到std_logic的所有可能的取值情况,综合器可能会插入不希望的锁存器。在仿真和综合中,std_logic值是非常重要的,它可以使设计者精确模拟一些未知和高阻态的线路情况。对于综合器,高阻态和“-”(忽略态)可用于三态的描述。但就综合而言,std_logic型数据能够在数字器件中实现的只有其中的4种值,即“-”、“0”、“1”和“Z”。当然,这并不表明其余的5种值不存在。使用标准逻辑位类型的示例如下:
(7)标准逻辑矢量(std_logic_vector):标准逻辑矢量是基于标准逻辑位类型的数据类型,是一个由标准逻辑位类型数据元素组成的数组。标准逻辑位矢量在使用时要注明位宽,即数组长度和方向。同样,在程序中使用std_logic_vector时,必须在程序的开头声明std_ logic_1164这个程序包。
std_logic_vector数据类型的数据对象赋值的原则是,同位宽、同数据类型的矢量间才能进行赋值。使用标准逻辑位矢量类型的示例如下:
(8)整数(integer):整数类型的数包括正整数、负整数和零。在VHDL中,整数的取值范围是-(2 31 -1)~+ 2 31 。通常所有预定义的算术运算(如加、减、乘、除)都适用于整数类型。在实际应用中,VHDL仿真器通常将integer类型作为有符号数来处理,而VHDL综合器则将integer作为无符号数处理。在使用整数时,VHDL综合器要求用range子句来限定所定义的整数范围,然后根据所限定的范围来决定表示此信号或变量的二进制数的位数,因为VHDL综合器无法综合未限定的整数类型的信号或变量。使用整数数据类型的示例如下:
整数数据类型还包括了两个子类型,即自然数(natural)和正整数(positive)。其中,自然数是非负的整数,包括零和正整数;正整数即整数中非零和非负的部分。这两者的使用如下:
(9)实数(real):实数类型又称为浮点类型(float),其预定义的实数的取值范围为-1.0E38~+1.0E38。由于实数的硬件实现非常复杂,因此VHDL综合器不支持实数类型。实数类型仅在VHDL仿真器中使用,作为有符号数来处理。与整数一样,也可以采用range子句来限定实数范围。使用实数数据类型的示例如下:
(10)时间(time): VHDL中唯一的预定义物理类型是时间。完整的时间类型包括整数和物理量单位两部分,整数和单位之间至少留一个空格,如“55ms”、“20ns”。预定义的时间类型的量纲有fs(10 -15 s,飞秒)、ps(10 -12 s,皮秒)、ns(10 -9 s,纳秒)、us(10 -6 s,微秒)、ms(10 -3 s,毫秒)、sec(s,秒)、min(min,分)、hr(h,时)。使用时间类型的示例如下:
除了时间外,距离、电压、电流等也被定义为物理类型。综合时,所有的物理类型数据都将被VHDL综合器忽略。物理类型必须预定义后才能被使用,使用规范与时间类型一样。物理类型定义格式如下:
2)其他预定义标准数据类型 VHDL综合工具自带的扩展程序包中,定义了一些有用的类型。如Synopsys公司在IEEE库中加入的程序包STD_LOGIC_ARITH中定义了如下的数据类型:无符号型(unsigned)、有符号型(signed)和小整型(small_int)。
如果将信号或变量定义为这些数据类型,就可以使用STD_LOGIC_ARITH程序包中定义的运算符。在使用前,必须加入如下语句:
unsigned类型和signed类型是用于设计可综合的数学运算程序的重要类型,其中unsigned用于无符号数的运算; signed用于有符号数的运算。在实际应用中,大多数运算都需要用到它们。
(1)无符号数据类型(unsigned): unsigned数据类型代表一个无符号的数值,在综合器中,这个数值被解释为一个二进制数,这个二进制数的最左位是其最高位。例如,十进制的9可以表示为unsigned("1001")。如果要定义一个变量或信号的数据类型为unsigned,则其位矢长度越长,所能代表的数值就越大。如一个4位变量的最大值为15,一个8位变量的最大值则为255,0是其最小值。unsigned不能用于定义负数。使用无符号数据类型的示例如下:
其中,信号s1有8位数值,最高位为s1(7),而非s1(0);变量s2有4位数值,最高位是s2(0),而非s2(3)。
(2)有符号数据类型(signed): signed数据类型表示一个有符号的数值,综合器将其解释为补码,此数的最高位是符号位。例如,signed(“0101”)代表+ 5,即5; signed(“1011”)代表-5。使用有符号数据类型的示例如下:
其中,信号s3的最高位s3(0)为符号位;变量s4的最高位s4(4)为符号位。
除了上述两种常用的数据类型外,STANDARD程序包中还定义了小整型(small_int),其定义为small_int: 0 to 1。
3)用户自定义的数据类型(User-Definable Data Type) VHDL允许用户自行定义新的数据类型,用户自定义数据类型是用类型定义语句type和子类型定义语句subtype实现的。
(1)类型定义语句(type)。type语句格式如下:
其中,数据类型名是由设计者自行定义的合法标志符;数据类型定义部分用于描述所定义的数据类型的表达方式和表达内容;关键词of后的基本数据类型是指数据类型定义中所定义的元素的基本数据类型,一般都是取已有的预定义数据类型,如bit、std_logic或integer等。使用type定义数据类型的示例如下:
第1句定义的数据类型cnt是一个具有16个元素的数组型数据类型,数组中的每个元素的数据类型都是std_logic型;第2句所定义的数据类型week是由一组文字表示的,而其中每个文字都代表一个具体的值,如可令sun="1010";
在VHDL中,任一数据对象(signal、varialbe、constant)都必须归属某一数据类型,只有同数据类型的数据对象才能进行相互作用。利用type语句可以完成各种形式的自定义数据类型以供不同类型的数据对象之间的相互作用和计算。
(2)子类型定义语句(subtype):子类型subtype是由type定义的原数据类型的一个子集,它满足原数据类型的所有约束条件。原数据类型称为基本数据类型,子类型的定义只在基本数据类型上作一些约束,并没有定义新的数据类型。子类型定义中的基本数据类型必须是在前面已通过type定义过的类型,包括已在VHDL预定义程序包中用type定义过的类型。子类型定义语句格式如下:
使用subtype定义子类型的示例如下:
在该定义语句中,integer是标准程序包中已定义过的数据类型,子类型digits只是将integer约束到只含100个值的数据类型。注意,不能用subtype来定义一种新的数据类型,如“subtype cnt is array(0 to 15) of std_logic;”这样的类型定义语句是错误的。
由于子类型与其基本数据类型属同一数据类型,因此属于子类型的和属于基本数据类型的数据对象之间的赋值和被赋值可以直接进行,不必进行数据类型的转换。
利用子类型定义数据对象的好处是,除了使程序提高可读性和易处理外,其实质性的好处在于有利于提高综合的优化效率,这是因为综合器可以根据子类型所设定的约束范围,有效地推知参与综合的寄存器的最合适的数目。
4)复合数据类型(Composite Data Type) 复合数据类型是由其他数据类型的元素组合而成的,有两种复合数据类型,即数组(array)和记录(record)。
(1)数组array:数组是相同类型元素的集合,数组之间可以互相从属(部分集合关系),数组可以是一维或多维的。其元素属于同一种数据类型,每个元素都可以用数组指针标志出来。VHDL允许定义两种不同类型的数组,即限定性数组和非限定性数组。它们的区别是,限定性数组下标的取值范围在数组定义时就被确定了,而非限定性数组下标的取值范围需留待以后根据具体数据对象再确定。
限定性数组的声明格式如下:
其中,数组名是设计者自行定义的数组名称;数组范围可以使用关键字to或downto,以增量或减量的方式来给定,以整数来表示;数据类型即指数组各元素的数据类型。
非定性数组的声明格式如下:
其中,数组下标名是以整数类型设定的一个数组下标名称,符号“<>”是下标范围待定符号,用到该数组类型时再输入具体的数据范围。注意,符号“<>”间不能有空格。
数组类型定义的示例如下:
前3句为限定性数组,最后两句为非限定性数组。其中,第1句中定义的数组类型名称是din,它有8个元素,其下标排序是7,6,…,1,0,各元素的排序是din(7),din(6),……,din(1),din(0)。第2句中定义m为两元素的枚举数据类型,然后在第3句中将dout定义为一个数组类型,其中每个元素的数据类型是bit。
(2)记录(record):记录可以将不同类型元素合成为一种新的数据类型,记录中的元素的数据类型可以不是VHDL预先设定的,而是在程序中临时定义的。记录数据类型的定义格式为:
其中,记录类型名是由设计者自行定义的数据类型名称。声明语句中的基本类型为VHDL定义的类型或设计者已经定义好的其他类型。记录类型的使用示例如下:
在上例中,声明了一个包含3个元素的记录类型record_data1。这3个元素的类型分别为标准逻辑位型、有约束范围的整数类型和标准逻辑矢量类型。定义这个复合数据类型后,可以使用它来定义信号、变量等。
对于记录类型的数据对象赋值的方式,可以是整体赋值,也可以对其中的单个元素进行赋值。在使用整体赋值方式时,可以采用位置关联或名字关联方式。如果使用位置关联时,则默认的元素赋值的顺序与记录类型声明时的顺序相同。如果使用了others选项,则至少应有一个元素被赋值,如果有两个或多个的元素由others选项来赋值,则这些元素必须具有相同的类型。此外,如果有两个或多个元素具有相同的子类型,就可以以记录类型的方式放在一起定义,如:
上述语句等价于:
5)枚举式数据类型 枚举式数据类型是混合不同数据类型的元素,组合出特殊的数据类型,也可将相同类型元素以枚举方式组成一种新的数据类型。不同枚举可以包含相同的元素,但调用时必须标明该枚举的名称,如A’(green)、B’(green)。枚举类型的定义语句格式如下:
在数字系统设计中,枚举类型应用较广泛。例如,状态机的每一状态在实际电路中虽是以一组触发器的当前二进制数位的组合来表示的,但设计者在状态机的设计中,为了更便于阅读和编译,往往将表征每一状态的二进制数组用文字符号来表示。枚举数据类型的定义示例如下:
第1句所定义的枚举类型state是包含st0~st7这8个枚举元素,在第2句中定义信号s _state和next_state,其类型为state,即这两个信号的取值范围为st0~st7这8个枚举元素。
在综合过程中,枚举类型文字元素的编码通常是自动实现的,编码顺序是默认的,一般将第1个枚举量(最左边的量)编码为0,以后的依次加1。综合器在编码过程中自动将第1枚举元素转变成位矢量,位矢的长度将取所需表达的所有枚举元素的最小值。如上例中用于表达8个状态的位矢长度应该为3,编码默认值为如下方式:
于是它们的数值顺序便成为st0<st1<st2<st3<st4<st5<st6<st7。一般而言,编码方法因综合器不同而不同。
6)数据类型转换 在VHDL中,为了实现正确的代入操作,必须将要代入的数据进行类型转换。转换时,可以使用类型标志符进行数据类型的转换,还可以使用转换函数进行数据类型的转换。
使用类型标志符进行类型转换时,其一般格式如下:
这种类型转换方式一般只用于数据类型相互间的关联性比较大的数据类型之间。如标志real就能将整数类型的数据对象转换成实数类型;反之,标志integer就能将实数类型的数据转换成整数类型。如:
在上述最后一条语句中,将B由整数类型转换为实数类型后与实数C相乘,乘积结果由实数类型转换为整数类型,然后赋值给A。
数据类型转换函数的作用就是将一种属于某种数据类型的数据对象转换成属于另一种数据类型的数据对象。在IEEE库的std_logic_1164、std_logic_arith和std_logic_unsigned程序包中,都有预定义好的类型转换函数,见表2-4。要使用程序包中预定义的类型转换函数,必须先在程序开头声明相应的程序包。
表2-4 转换函数表
转换函数的使用示例如下:
4.运算符
运算符又称为操作符,它规定了运算的方式。在VHDL中,通常有4类运算符,即逻辑运算符(Logical Operator)、关系运算符(Relational Operator)、算术运算符(Arithmetic Operator)和符号运算符(Sign Operator)。前三类运算符是完成逻辑和算术运算的最基本的运算符单元。此外还有重载运算符(Overloading Operator),它是对基本运算符作了重新定义的函数型运算符。表2-5所列为各运算符所要求的运算数类型,表2-6所列为各运算符之间运算的优先级。
表2-5 VHDL运算符列表
续表
表2-6 VHDL运算符运算优先级
1)算术运算符 算术运算符是用于处理整数或位矢量类型的数据的,它又分为求和操作符、符号操作符、求积操作符、混合操作符和移位操作符。
求和操作符包括加法操作符(+)、减法操作符(-)和并置操作符(&)。VHDL规定求和操作符中,“+”(加)、“-”(减)操作数的数据类型为整数,操作规则与常规的加/减法相同。当“+”(加)、“-”(减)操作数的位宽大于4位时,VHDL综合器将调用库元件进行综合。一般加减操作符的数据对象为信号或变量时,经综合后所消耗的硬件资源比较多;而其中的一个操作数或两个操作数为常量时,经综合后所消耗的硬件资源比较少。
并置操作符是一个比较特殊的求和操作符,它的两个操作数的数据类型必须都是一维数组,其作用是将普通操作数或数组连接起来构成新的数组。在应用中,要注意并置操作后数组的矢量长度应与赋值对象数组的长度保持一致。并置操作符的使用示例如下:
符号操作符包括“+”(正)、“-”(负)两种操作符。符号操作符的操作数只有一个,操作数的数据类型为整数类型。“+”对操作数不作任何改变;“-”操作符作用于操作数后的结果是对该操作数取负。
求积操作符包括“*”(乘)、“/”(除)、MOD(取模)、REM(取余)这4种操作符。“*”(乘)和“/”(除)的两个操作数的数据类型为整数或实数类型,在一定条件下也可以对物理类型的数据对象进行操作; MOD(取模)和REM(取余)的两个操作符的数据类型为整数类型。注意,乘、除运算通常消耗很多的硬件资源,为节省资源,通常很少直接使用乘、除运算操作,而改用移位操作以达到乘、除的目的。取模和取余操作的本质与除法操作一致,可综合的取模和取余操作要求操作数必须是以2为底的数。
混合操作符包括“**”(乘方)、ABS(取绝对值)两种操作符,这两种操作符的操作数的数据类型一般为整数类型。乘方操作符的左边可以是整数或实数,但右边必须为整数,而且只有左边为实数时,其右边才可以为负数。通常乘方操作符作用的操作数的底数为2时,综合器才可以综合。
移位操作符包括SLL(逻辑左移)、SRL(逻辑右移)、SLA(算术左移)、SRA(算术右移)、ROL(循环左移)和ROR(循环右移)这6种操作符。这6种移位操作符都是VHDL 93标准新增加的操作符,其操作数的数据类型都是一维数据,并且数组中的元素必须都是bit或boolean的数据类型,移位的位数必须是整数。逻辑移位操作符SLL和SRL在进行移位操作时,空缺位补0;算术移位操作符SLA和SRA在进行移位操作时,空缺位用当前位补充;循环移位操作符ROL和ROR在进行移位操作时,空缺位用移出位填充。移位操作符的使用示例如下:
2)关系运算符 关系运算符用于判断两个具有相同数据类型操作数之间的相等、不相等及大小关系,其结果为布尔量。在关系运算符中,小于等于符“<=”和代入符“<=”是相同的,在读VHDL语言的语句时,应该按照上下文的关系来判断此符号到底是关系运算符还是代入符。
3)逻辑运算符 逻辑运算符的功能就是对操作数进行逻辑运算。在VHDL中有7种逻辑操作符,即AND(逻辑与)、OR(逻辑或)、NAND(逻辑与非)、NOR(逻辑或非)、XOR(异或)、XNOR(同或)、NOT(逻辑非)。
逻辑运算的操作数必须具有相同的数据类型,VHDL标准逻辑运算符允许的操作数类型有位类型(bit)、布尔类型(boolean)及位矢量类型(bit_vector)。在IEEE库的std_logic_ 1164程序包中对逻辑运算符进行了重新定义,也可以用于标准逻辑位类型(std_logic)和标准逻辑位矢量类型(std_logic_vector)数据的逻辑运算。但是,应用于这两种数据类型必须事先声明std_logic_1164程序包。逻辑运算表达式的结果数据类型与操作数数据类型相同。
4)重载操作符 为了方便各种不同数据之间的运算,VHDL允许用户对原有的基本操作符重新定义,赋予新的含义和功能,从而构成一种新的操作符,这就是重载操作符。重载后的操作符允许对新的数据类型实行操作,或者允许不同数据类型的数据之间使用该操作符进行运算。定义这种操作符的函数称为重载函数。
Synopsys的程序包std_logic_arith、std_logic_unsigned和std_logic_signed中已经为许多类型的运算重载了算术运算符和关系运算符,因此只要引用这些程序包,在signed、unsigned、std_logic和integer之间即可进行混合运算,integer、std_logic和std_logic_vector之间也可以进行混合运算。