VHDL语言的实体描述只是定义了元件、模块或系统与外部电路的端口界面,而真正要完成的功能则是由结构体中的基本语句进行描述的。从语句的执行方式上看,这些基本语句主要分为顺序语句和并行语句。
顺序语句(Sequential Statements)只能出现在过程、进程及函数中,其中所有的命令语句都是按顺序执行的,因此语句出现的顺序很重要。而整个流程、程序或函数本身则被视为一个整体,是一个并行的语句。VHDL中的顺序描述语句主要包括赋值语句、流程控制语句(if、case、loop语句等)、等待语句、子程序调用语句、返回语句和控操作语句等。
1.进程语句
顺序语句只能出现在进程(Process)和子程序(包括过程和函数)中。在VHDL中,一个进程是由一系列顺序语句构成的,而进程本身属于并行语句,也就是说,在同一设计实体中,所有的进程是并行执行的。然而任一给定的时刻内,在每个进程内只能执行一条顺序语句。一个进程与其设计实体的其他部分进行数据交换的方式只能通过信号或端口来进行。如果要在进程中完成某些特定的算法和逻辑操作,则可以通过依次调用子程序来实现,但子程序本身并无顺序语句和并行语句之分。
process语句是结构体行为描述中使用最频繁的复合语句,也是最具有VHDL语言特色的语句。进程语句格式如下:
标号为设计者自行定义的合法标志符,在程序中它不是必须的,可以省略。通常,如果程序中只使用一个进程时,进程标号可以省略;如果程序中有多个进程时,应为每个进程加上相应的标号,以提高程序的可读性。
进程语句中的敏感信号表是进程赖以启动的敏感表,对于表中列出的任何信号的改变,都将启动进程,执行进程内相应的顺序语句。如果一个进程中有多个敏感信号时,各敏感信号前用逗号分开。在进程中,敏感信号的添加是非常重要的,添加一个不必要的信号到敏感信号表中,可能会意外启动进程,得到意料不到的结果。如果遗漏敏感信号,则有可能得不到想要的结果。通常,如果进程描述的是组合电路,则所有的输入量都必须作为敏感信号;如果进程描述的是时序电路,则时钟信号和异步控制信号必须作为敏感信号。
进程的说明语句部分用于定义该进程所需的一些局部数据环境,可以包括数据类型、常数、变量属性、子程序等。但是,在进程说明语句部分中不允许定义信号和共享变量,在此说明的变量,只有在此进程内才可以对其进行存取。
进程的顺序语句部分是一段顺序执行的语句,描述了该进程的行为。可分为赋值语句、进程启动语句、子程序调用语句、顺序描述语句和进程跳出语句等。
2.赋值语句
赋值语句的功能是将一个值或表达式的运算结果传递给某一数据对象,如信号或变量,或者由此组成的数组。对于VHDL设计实体内的数据传递及对端口界面外部数据的读/写操作,都必须通过赋值语句的运行来实现。
VHDL中的赋值语句有两种,即信号赋值语句和变量赋值语句。每种赋值语句都由3个基本元素组成,即赋值对象(又称赋值目标)、赋值符号和赋值源。赋值对象是所赋值的受体,它的基本元素只能是信号或变量,但表现形式可以有多种,如文字、标志符、数组等。赋值符号只有两种,信号赋值符号是“<=”,变量赋值符号是“:=”。赋值源是赋值的主体,它可以是一个数值,也可以是一个逻辑或运算表达式。VHDL规定赋值对象与赋值源的数据类型必须一致。信号赋值语句和变量赋值语句的语法格式分别如下:
变量赋值与信号赋值的区别在于,变量具有局部特征,它的有效性只局限于所定义的一个进程中或子程序中,它是一个局部的、暂时性数据对象(在某些情况下)。对于它的赋值是立即发生的(假设进程已启动),即一种时间延迟为零的赋值行为。信号则不同,信号具有全局性特征,它不仅可以作为一个设计实体内部各单元之间数据传送的载体,而且可通过信号与其他的实体进行通信(端口本质上也是一种信号)。赋值过程总是有某种延时的,它反映了在硬件系统中并不是立即发生的,它发生在一个进程结束时。赋值过程的延时反映了硬件系统的重要特性,综合后可以找到与信号对应的硬件结构,如一根传输导线、I/O端口或D触发器等。在信号赋值中,当在同一进程中同一信号赋值目标有多个赋值源时,信号赋值目标获得的是最后一个赋值源的赋值,其前面相同的赋值目标不作任何变化。
3. if语句
if语句是根据所指定的条件来确定执行哪些语句的,其条件自上而下来判断,第1个为ture的条件后相应的顺序语句将被执行,如果所有的条件均为false,那么else后的语句将被执行。其书写格式通常可以分为如下3种类型。
1) if-then语句 此种if语句的语法为:
【例2-4】使用if语句描述一个上升沿触发的基本D触发器。
这个程序用于描述时钟信号边沿触发的基本D触发器时序逻辑电路。进程(process)中的CP为敏感信号,CP变化时,进程就要执行一次。条件表达式cp'event and cp='1'用于判断CP是否发生上升沿跳变,判断时钟下降沿的语句为cp'event and cp='0'。若发生上升沿跳变,则执行给Q、QB赋值的语句,否则Q、QB保持不变。其仿真波形如图2-4所示。
图2-4 基本D触发器的仿真波形
2) if-then-else语句 此种if语句的语法为:
【例2-5】使用if-then-else语句描述二选一电路。
在此程序中,A、B、sel均作为进程的敏感信号。当sel为低电平时,Y输出A的内容; 当sel为高电平时,Y输出B的内容。其仿真波形如图2-5所示。
图2-5 二选一电路的仿真波形
3) if-then-elsif语句 此种if语句的语法为:
【例2-6】使用if-then-elsif语句描述三选一电路。
在此程序中,A、B、C、sel均作为进程的敏感信号。当sel为“01”时,Y输出A的内容;当sel为“10”时,Y输出B的内容。否则Y输出C的内容。其仿真波形如图2-6所示。
图2-6 三选一电路的仿真波形
除上述3种基本结构形式外,if语句还有嵌套使用的形式。如果if语句中又包含一个或多个if语句时,这种情况称为if语句的嵌套。if语句的嵌套基本形式如下:
4. case语句
case语句用于从许多不同的语句之中选择其一来执行,其语句格式如下:
执行case语句时,先计算case和is之间表达式的值,当表达式的值与某一条件选择值相同(或在其范围内)时,程序将执行对应的顺序语句。表达式可以是一个整数类型或枚举类型的值,也可以是由这些数据类型的值构成的数值。其中,符号“=>”不是操作符,它只相当于关键词then。case语句中各when语句之间是互相并列的,并且每个when语句后面的条件选择值必须是互相排斥的,即任意两个条件表达式的值不可能在同一时刻同时为真。使用case语句时,需注意以下4点。
条件句中的选择值必须在表达式的取值范围内。
除非所有条件句中的选择值能完整覆盖case语句中表达式的取值,否则最末一个条件句中的选择必须用“others”来表示。它代表已给的所有条件句中未能列出的其他可能的取值。关键词“others”只能出现一次,且只能作为最后一种条件取值。使用“others”的目的是为了使条件句中的所有选择值能涵盖表达式的所有取值,以免综合器插入不必要的锁存器。这一点对于定义为std_logic和std_logic_vector数据类型的值尤为重要,因为这些数据对象的取值除了1和0外,还可能有其他的取值,如高阻态(Z)、不定态(X)等。
case语句中每一条件句的选择只能出现一次,不能有相同选择值的条件语句出现。
case语句执行中必须且只能选中所列条件语句中的一条。这表明case语句中至少要包含一个条件语句。
【例2-7】使用case语句描述三选一电路。
5. loop语句
loop语句是循环语句,也属于流程控制语句,它可以使所包含的一组顺序语句被循环执行,其执行次数由设定的循环参数来决定。loop语句的表达方式有以下3种。
1)单个loop语句 其语法格式如下:
单个loop语句是最简单的循环方式之一,需要引入其他控制语句(如exit)才能确定循环的方式。如:
2) for_loop语句 其语法格式如下:
在for_loop语句中,循环变量是loop内部的一个临时变量,属于loop语句的局部变量,不必事先定义。这个变量只能作为赋值源,不能被赋值,它由loop语句自动定义。循环次数范围规定loop语句中的顺序语句被重复执行的次数,由关键字to或downto引导。
【例2-8】使用for_ loop语句描述8输入与门电路。
在此程序中,定义a为8位,表示8路输入。在进程中定义tmp用于暂存各位“与”操作的结果。8输入与门电路的仿真波形如图2-7所示。从图中可以看出,只有8路输入全为高电平时,输出才为高电平,否则输出为低电平。
运用for-loop语句时要慎重,若运用不当,将会消耗大量逻辑资源。例如:
图2-7 8输入与门电路的仿真波形
在此段程序中,对sum1实现了9次自动加1运算,对sum2实现了1~9的累加计算。如果sum1和sum2的初始值均为0,那么执行该程序段后,sum1和sum2的值分别为9和45。如果将程序改为如下:
若sum1和sum2的初始值均为0,那么执行该程序段后,sum1和sum2的值分别为1和9。因为对信号进行赋值时,只有最后一次赋值有效。但下面一个程序是可行的。
将循环变量作为数组下标,当进行循环操作时,i不断自减,则赋值对象reg(i)也不断改变,不会出现对同信号进行多次赋值的现象。
3) while_loop语句 其语法格式如下:
while_loop语句中的循环控制条件必须为布尔量,一旦计算判断出循环控制条件的值为ture,则循环执行while_loop语句中的顺序语句,否则就终止循环。
【例2-9】使用while_loop语句描述8输入与门电路。
从例2-9中可以看出,虽然使用while_loop语句也可以完成8输入与门电路的设计,但是while-loop和for-loop语句在程序设计中存在着较大的区别。在while_loop语句中,虽然循环变量i也是整数变量,但必须在变量定义语句中进行说明才能被使用,而for_loop语句中的循环变量i不需要说明。在while_loop语句中i的自增1操作需要相应的语句来完成,而for_loop语句中只需用to即可完成i的自增1操作。
while_loop和for_loop语句都可以用于编写循环语句,并且都可以进行逻辑综合,但是一般不采用while_loop语句进行RTL的描述。
6. next语句和exit语句
next语句和exit语句都是只能用在循环体中的顺序语句,用于描述有条件和无条件地跳出本次循环。
1) next语句 next语句类似于C语言中的continue语句,用于跳出本次循环,而执行下一次新的循环,是一种循环内部的控制语句。next语句有以下3种语句格式:
对于第1种语句格式,当loop内的顺序语句执行到next语句时,立即无条件终止当前的循环,跳回到本次循环的loop语句处,开始下一次循环。
对于第2种语句格式,即在“next”后加“loop标号”的功能与未加loop标号的功能基本相同,只是当有多重loop语句嵌套时,前者可以跳转到指定标号的loop语句处,重新开始执行循环操作。
对于第3种语句格式,“when条件表达式”是执行next语句的条件,如果条件表达式的值为ture,则执行next语句,进入跳转操作,否则继续向下执行;但当只有单层loop循环语句时,关键字next与when之间的“loop标号”可以省略。
【例2-10】使用next指令和for-loop指令对两个8位数据的各位进行比较,如果相同位的值相同,则对应的比较输出结果位为1,否则输出0。
在例2-10中,当程序执行到next语句时,如果条件表达式a(i)= b(i)的结果为ture,则执行next语句———返回到L1处,使i加1,然后执行s1开始的赋值语句;反之,将执行s2开始的赋值语句。其仿真波形如图2-8所示。
图2-8 8位数据各位进行比较的仿真波形
2) exit语句 exit语句与C语言中的break语句类似,即提前结束循环,接着执行循环后面的语句。exit语句有以下3种语句格式:
exit的每种语句格式与对应的next语句的格式和操作功能非常相似,唯一的区别是next语句跳转的方向是loop标号指定的loop语句处,当没有loop标号时,跳转到当前loop语句的循环起始处,而exit语句的跳转方向是loop标号指定的loop循环语句的结束处,即完全跳出指定的循环,并开始执行此循环外的语句。
【例2-11】使用exit指令和for_loop指令对两个8位数据的大小进行比较,如果a大于b,则输出结果为1,否则为0。
在例2-11中进行大小比较时,首先从最高位进行比较,如果a的最高位大于b的最高位,即a(7)为1,b(7)为0,说明a大于b,比较结果输出为1,执行exit指令,不需再对其他位进行比较。如果a的最高位小于b的最高位,即a(7)为0,b(7)为1,说明a小于b,比较结果输出为0,执行exit指令,不需再对其他位进行比较,否则执行null语句进行空操作。然后再依此方法对次高位进行比较……,依此类推。其仿真波形如图2-9所示。
图2-9 8位数据的大小进行比较的仿真波形
7. wait语句
wait语句使程序产生等待,直到条件满足再继续执行。wait语句可以设置成4种不同的条件,即无限等待、等待敏感信号变化、等待条件满足和超时等待,这些条件可以并列使用。
1)无限等待 不设置停止挂起条件的表达式,表示永远挂起。其语句格式如下:
wait;
2)等待敏感信号变化 其语句格式如下:
信号表内可以是一个或多个敏感信号。有多个敏感信号时,各敏感信号间用逗号隔开。当进程执行到wait on语句时,进程将被挂起,然后等待指定的敏感信号发生变化。当处于等待状态时,如果这些敏感信号中的任何一个的状态发生了变化,将结束等待状态,再次启动进程,继续执行wait on语句后面的语句。其应用示例如下:
在上例中,当进程执行waiton语句时,进入挂起状态,如果敏感信号temp1或temp2的状态发生了变化,则立即结束挂起,继续执行进程。注意,VHDL规定,已列出敏感量的进程中不能使用任何形式的wait语句,因此在上例中process的后面不能添加“(temp1,temp2)”。
3)等待条件满足 其语句格式如下:
wait until条件表达式;
在wait until语句中,条件表达式实质上隐含了一个敏感信号表,当表中的任何一个信号发生变化,就立即对表达式进行一次逻辑计算。如果逻辑计算的结果为ture值,则进程将被启动;否则进程将进入等待状态。因此,相对wait on语句而言,wait until语句中多了一种重新启动进程条件,即被此语句挂起的进程需顺序满足以下两个条件,进程才能被启动。
条件表达式提供的隐含敏感信号列表中的信号发生变化;
此敏感信号发生改变后,且满足wait语句所设定的条件。
对于wait until语句而言,以上两个条件不仅缺一不可,而且必须依照以上顺序来完成。其应用举例如下(这两种表达方式是等效的)。
如果设clk为时钟信号输入端,以下4条wait until语句所设定的进程启动条件都是时钟上升沿,所以它们对应的硬件结构是一样的。
上述4条wait until语句中隐含了敏感信号clk,在实际使用时可以作为进程中的敏感信号来使用。例如,以下两种方式描述D触发器的功能是等效的。
4)超时等待 其语句格式如下:
wait for时间表达式;
wait for语句后面的时间表达式是一个物理量,它表示需要等待的时间。从执行到当前的wait语句开始处,在这个时间段内进程处于被挂起状态,而超过这段时间后,进程开始执行wait for后的语句。例如:
上面提到的4种形式的wait语句也能同时使用,从而构成多条件wait语句。当多个条件同时出现时,只要其中一个成立,则终止等待。如:
当进程执行到该语句时,则进入等待状态,一旦wait语句等待了10μs,或temp1或temp2的状态发生变化,或者条件表达式n<8逻辑值为ture,则等待结束。
8.子程序调用语句
VHDL语言允许在结构体或程序包中的任何位置调用子程序,子程序包括过程和函数。站在硬件角度上看,一个子程序的调用类似于一个元件模块的例化,也就是说VHDL综合器为子程序(过程和函数)的每次调用都生成一个电路逻辑块,所不同的是,元件的例化将产生一个新设计层次,而子程序调用只对应于当前层次的一个部分。
1)过程 在VHDL中,过程分为过程首和过程体,分别放置在程序包首和程序包体中,供VHDL程序共享。其中,过程首定义了主程序调用过程时的接口,过程体描述该过程具有逻辑功能的实现。过程的语句格式如下:
过程首不是必须的,过程体可以独立存在和使用,即在进程或结构体中不必定义过程首,可以直接定义过程并使用,而在程序中必须定义过程首。
(1)过程首:过程首由过程名和参数表组成,参数表可以对常数、变量和信号3类数据对象目标做出说明,并用关键字in、out和inout定义这些参数的工作模式,即信息的流向。如果只定义了in模式,而未定义目标参量类型,即默认为常量;如果只定义了inout或out,则默认目标参量类型是变量。过程首的定义示例如下:
在以上的过程首定义举例中定义了3个过程,分别为ex1、ex2和ex3。其中,第1行的过程名为“ex1”,它定义了两个双向的实数变量a和b;第2行的过程名为“ex2”,它定义了一个输入的整数类型常量m和一个输出的整数类型变量n;第3行的过程名为“ex3”,它定义了一个双向的位类型信号temp。
(2)过程体:过程体是由顺序语句组成的,过程的调用即启动了对这部分顺序语句的执行。过程体中的说明部分只是局部的,其中的各种定义只能用于过程体内部。过程体的顺序语句部分可以包含任何顺序执行语句,包括wait语句。但是,如果一个过程是在进程中被调用的,且这个进程已列出了敏感参量表,则不能在此进程中使用wait语句。将标准逻辑位矢量转换为整数的过程体的声明如下:
(3)过程调用:过程调用就是执行一个给定名字和参数的过程。过程调用的语句格式如下:
过程名(参数表);
在不同的环境中,可以使用两种不同的语句方式进行过程调用,即顺序语句方式或并行语句方式。在一般的顺序语句自然执行过程中,一个过程被执行,则属于顺序语句方式;当某个过程处于并行语句环境中时,其过程体中定义的任意in或inout的目标参量发生改变时,将启动过程的调用,这时的调用属于并行语句方式。过程可以重复调用或嵌套式调用,综合器一般不支持含有wait语句的过程。
【例2-12】使用过程调用,设计一个8输入的与或非门逻辑电路。本例是通过两次调用程序ex中的过程ex_4and_or_not(4输入与或非门),实现了一个8输入的与或非门。
2)函数 函数也是描述结构复杂的电路模块的一种手段,它可以无返回值,也可以带回一个返回值,其参数列表都是输入信号。函数包括函数首和函数体两个部分。函数首定义了主程序调用函数时的接口参数,包括输入参数或输入信号;函数体则描述了该函数具体逻辑功能的实现。在进程或结构体中可以不需要定义函数首,但是在程序包中必须定义函数首。函数的语法格式如下:
(1)函数首:函数首由函数名、参数表和返回值的数据类型这3部分组成。函数首的名称即为函数的名称,需放在关键字function后,它可以是普通的标志符,也可以是运算符,此时运算符必须加上双引号,这就是所谓的运算符重载。函数的参数表指定函数的输入参数,可以是常量或信号,它们可以是任何可综合的数据类型。参数名需放在关键字constant 或signal后,如果没有特别说明,则参数被默认为常数。函数的参数表也是可选的,如果有的函数不需要输入参数,则可以省略参数表。变量是不可以作为函数的参数的。
如果要将一个已编制好的函数并入程序包,那么函数首必须放在程序的说明部分,而函数体需要放在程序包的包体内。由此可见,函数首的作用只是作为程序包的有关此函数的一个接口界面。如果只是在一个结构体中定义并调用函数,则仅需函数体即可。函数首的定义示例如下:
在以上的函数首举例中定义了3个函数,它们都放在某一程序包的说明部分。其中,第1行的函数首名称为“ex1”,它定义3个整数参量,返回值的数据类型为整数;第2行的函数名是用引号括起来的运算符“*”,它定义了a和b两个实数类型的参量;第3行的函数首名称为“ex2”,它定义了a和b两个整数类型的信号参量。
(2)函数体:函数体是函数具有实质性内容的部分,包含数据类型、常数、变量等的局部说明,以及用以完成规定算法或转换的顺序语句部分,并以关键词end function及函数名结尾。一旦函数被调用,就将执行这部分语句。在程序包中定义的求最大值函数体max如下:
(3)函数调用:函数不能从所在的结构体中直接读取信号值或向信号赋值,只能通过函数调用与函数端口界面进行通信。函数一旦被调用,就将执行函数体中的这部分语句,并将计算或转换结果用函数名返回。
函数在结构体的描述中,通常是作为表达式或语句的一部分,其调用格式如下:
在不同的环境中,可以使用两种不同的语句方式进行函数调用,即顺序语句方式或并行语句方式。
【例2-13】使用函数调用,将4个输入的8位最大数值输出。
9. return语句
和C语言的return的语句类似,VHDL语言中的return语句用于结束当前子程序的执行,并返回主程序。return语句只能用于子程序体中。其语句格式如下:
当表达式缺省时,只能用于过程,它只是结束过程,并不返回任何值;当有表达式时,只能用于函数,并且必须返回一个值。用于函数的语句中的表达式提供函数返回值。每个函数必须至少包含一个返回语句,并可以拥有多个返回语句,但是在函数调用时,只有其中一个返回语句可以将值传递出来。
return语句在过程中的应用示例如下:
上例在过程中使用了return语句,描述了一个R-S触发器,当s和r同时为1时,return语句将中断过程,不返回任何值。
return语句在函数中的应用示例如下:
上例在函数中使用了两个return语句,但是在函数调用时,只有一个return语句会被执行。
10. null语句
null为空操作语句,该语句将执行一个空操作,或者为对应的信号赋一个空值,其功能是使逻辑运行流程跨入下一步语句。其语句格式为:
null;
这种空操作语句在VHDL的描述中是非常有用的。例如,应用case语句必须覆盖所有条件,但是具体模块中许多条件都是可以忽略的,这时使用空操作语句可以起到很好的效果。null语句的应用举例如下:
11.其他语句及说明
1) report报告语句 VHDL中的report报告语句类似于C语言中的printf语句,其功能是在程序运行过程中向编程人员报告程序的运行状态。report语句格式如下:
其中,report后面的输出字符串必须用双引号括起来; severity后面的出错等级共有4级,即failure(失败)、error(错误)、warning(警告)和note(注意)。note可以在仿真时传递信息; waring用于非正常的情况,此时结果可能是未知的; error用在仿真过程已经无法继续执行的情形下; failure则发生在仿真必须立即停止(出现致命错误时)。
report语句不增加硬件的任何负担,仅在仿真时提高程序的可读性,便于程序的修改维护,为程序的调试带来了方便。report语句在程序的应用如下:
2) assert断言语句 断言语句只能在VHDL仿真器中使用,综合器通常忽略此语句。但在仿真时,assert语句却是很有用的VHDL语句。assert语句和report语句十分相似,也可为设计者报告一个文本字符串,它们之间的区别是assert语句是有条件的,其语句格式如下:
关键字assert后跟一个布尔值的条件表达式,它的条件决定report子句所规定的字符串是否输出。当条件表达式为ture时,assert语句不执行任何动作,如果为false,则输出一个用户规定的字符串到标准输出终端。
assert语句也具有以下4种错误等级,即failure(失败)、error(错误)、warning(警告)和note(注意)。assert语句有两个可选的子句: report子句允许设计者输出指定的字符串,如果不指定report语句,则默认值是assertion violation; severity子句允许设计者指定断言语句的严重级别,如果没指定severity语句,则其默认值是error。assert语句在程序中的应用如下:
assert语句可以作为顺序语句使用,也可以作为并行语句使用。作为并行语句时,assert语句可看做一个被动进程。assert语句的条件以静态表达式定义时,它就等价于一个没有敏感信号的以wait语句结束的进程,它在仿真开始时执行一次,然后无限等待下去。
3)属性描述与定义 在VHDL中有一些预先定义好的属性(称为VHDL中预定义属性),这些属性的使用有的是程序中必要的,有的会增加程序的可读性,有的会使程序的设计更为方便。VHDL中可以具有属性的项目包括:类型、子类型;过程、函数;信号、变量、常量;实体、结构体、配置、程序包;元件;语句标号。
属性是以上各类项目的特性,某一项目的特定属性或特征通常可以用一个值或表达式来表示,通过VHDL的预定义属性描述语句就可以加以访问。
属性的值与对象(信号、变量和常量)的值完全不同,在任意给定的时刻,一个对象只能具有一个值,但却可以具有多个属性。VHDL还允许设计者自己定义属性。
表2-7列出了常用的预定义属性。其中,综合器支持的属性有LEFT、RIGHT、HIGH、LOW、RANGE、REVERSE_RANGE、LENGTH、EVENT和STABLE等。
预定义属性描述语句实际上是一个内部预定义函数,其语句格式是:
属性测试项目即属性对象,可由相应的标志符表示;属性标志符就是列于表2-7中的有关属性名。以下仅就可综合的属性项目使用方法作一说明。
表2-7 预定义的属性函数功能表
续表
(1)信号类属性:信号类属性中,最常用的是event。例如,语句“clk’event”就是对以clk为标志符的信号,在当前的一个极小的时间段内发生事件的情况进行检测。所谓发生事件,就是电平发生变化,从一种电平方式转变到另一种电平方式。如果在此时间段内,clk由0变成1或由1变成0,则认为发生了事件,于是这句测试事件发生与否的表达式将向测试语句(如if语句)返回一个boolean值true,否则为false。event属性的使用示例如下:
本例是对clk信号上升沿的测试,即一旦测试到clk有一个上升沿时,此表达式就返回一个布尔值true。
属性stable的测试功能恰好与event相反,它是若信号在Δ时间段内无事件发生时,则返还true值,否则为false。以下两语句的功能是一样的。
在实际应用中,属性event比stable更常用。对于目前常用的VHDL综合器来说,event属性只能用于if语句和wait语句。
〖注意〗对于普通的bit数据类型的clk,它只是有1和0两种取值,因而语句“clk’event and clk=’1’”的表述作为对信号上升沿到来与否的测试是正确的。但若clk的数据类型已定义为std_logic,则其可能的值有9种。这样一来,就不能从“(clk='1') = true”来推断Δ时刻前clk一定是0。对于这种数据类型的时钟信号边沿检测,可用表达式“rising_edge(clk)”来完成。
rising_edge()是VHDL在ieee库中标准程序包内的预定义函数,这条语句只能用于标准位数据类型的信号,其用法为如“if rising_edge(clk) then”或“wait rising_edge(clk)”。
(2)数据区间类属性:数据区间类属性有’range\[(n)\]和’reverse_range\[(n)\]两种,这类属性函数主要对属性项目取值区间进行测试,返回的内容不是一个具体值,而是一个区间。对于同一属性项目,属性’range和’reverse_range返回的区间次序则相反,前者与原项目次序相同,后者则相反,用于返回有限制的指定数组类型的范围。数据区间类属性的使用示例如下:
本例中的for_loop语句与语句“for i in 0 to 15 loop”的功能是一样的,这说明range1’range返回的区间即为位矢量range1定义的元素的范围。如果用’reverse_range,则返回的区间正好相反。
(3)数值类型属性: VHDL中的数值类属性测试函数主要有’left、’right、’high和’low,它们主要用于对属性目标的一些数值特性进行测试,返回一个数据类型或是一个子类型的最左边、最右边或上/下限的数值。数值类型属性的使用示例如下:
(4)数值数组属性:数值数组属性是对数组的宽度或元素的个数进行测定,返回指定数组的长度。其使用示例如下:
(5)用户自定义属性:用户也可以自定义IEEE规范中未定义的新属性。属性与属性值的自定义格式如下:
VHDL综合器和仿真器通常使用自定义的属性实现一些特殊的功能。由综合器和仿真器支持的一些特殊属性一般都包括在EDA工具厂商的程序包里。例如,Synplify综合器支持的特殊属性都在synplify.attributes程序包中,使用前加入以下语句即可:
又如在DATA I/O公司的VHDL综合器中,可以使用属性pinnum为端口锁定芯片引脚。
自定义属性的VHDL代码可以包括在程序包中,也可以直接包括在用户的VHDL设计描述中。一旦属性已被定义,并且给定了一个属性名,在设计描述中就可以引用它。
并行语句的特点是在系统的某一时刻同时执行,不因编程的语句顺序而影响执行的先后次序。并行语句主要有进程语句、块语句、并行信号赋值语句、并行过程调用语句、元件例化语句和生成语句等。
1.块语句
块(block)语句是VHDL中具有的一种划分机制,该机制允许设计者合理地将一个模块分为数个区域,在每个块都能对其局部信号、数据类型和常量加以描述和定义。block语句的书写格式如下:
块标号为设计者自行定义的合法标志符。说明语句可以是接口说明语句或类属说明语句,其中接口说明语句主要用于信号的映射及参数的定义,类似于实体的定义部分,它可以包含由关键词port、generic、portmap和generic map引导的接口说明等语句,对block的接口设置及外界信号的连接状态加以说明。
【例2-14】对于图2-10所示的逻辑电路模块可以使用3个块语句来进行描述,其VHDL程序如下。
图2-10 例2-14电路模块图
2.并行赋值语句
并行赋值语句主要有3种形式,即简单信号赋值语句、条件信号赋值语句和选择信号赋值语句。
1)简单信号赋值语句 简单信号赋值语句作为并行赋值语句的一种,是并行语句结构中最基本的单元。其基本语句格式如下:
其中,信号赋值目标的数据对象必须是信号,其数据类型必须与赋值符号右边表达式的数据类型一致。
【例2-15】用简单信号赋值语句描述表达式y= ab+(c⊕d)。
在例2-15结构体的3行执行语句属于简单信号赋值语句,它们是同时执行的,其执行方式与书写顺序无关。其仿真波形如图2-11所示(图中很窄的尖脉冲属于毛刺,不影响程序的运行)。
图2-11 例2-15的仿真波形
2)条件信号赋值语句 条件信号赋值语句也是一种并行信号赋值语句,可以根据不同的条件将不同的表达式值赋给目标信号,其书写格式如下:
在执行条件信号赋值语句时,每个赋值条件是按书写的先后关系逐项测定的,一旦发现赋值条件为ture,即将表达式的值赋给赋值目标;如果该语句的所有条件表达式均不成立,则把信号量表达式n+1的值赋给赋值目标。使用条件信号赋值语句时,应该注意以下5点。
只有条件满足时,才能将该条件前面的表达式的值赋给目标信号;
对条件进行判断是有顺序的,位置靠前的条件具有较高的优先级,只有不满足本条件时才会去判断下一个条件;
条件表达式的结果为布尔类型数值;
最后一个表达式后面不含有when子句;
条件信号赋值语句允许条件重叠。
【例2-16】用条件信号赋值语句描述的2输入与非门电路。
例2-16的仿真波形如图2-12所示。从图中可以看出,当a和b均为低电平时,y才输出为高电平。
图2-12 例2-16的仿真波形
由例2-16可以看出,条件信号赋值语句由于赋值条件的顺序性,第1句具有最高赋值优先级,第2句其次,优先级依次降低。条件信号赋值语句与进程中的if语句等价,完成相同的功能,所不同的是,在整个条件赋值语句中至少要有一个else子句,而if语句是没有这个要求的,它可以有单个if语句。
〖注意〗条件信号赋值语句是不能嵌套的,不能把自身赋值给目的信号,所以不能用条件信号赋值语句设计锁存器。另外,用条件信号赋值语句描述的电路与实际逻辑电路的工作情况比较贴近,使用这类语句就像使用汇编语言编程一样,需要编程人员有较丰富的硬件设计经验,从而使一般编程人员难于掌握,不建议使用,只有当用进程语句、if语句、case语句难以描述时,才使用条件信号赋值语句。
3)选择信号赋值语句 选择信号赋值语句是一种条件分支的并行语句,其格式如下:
执行该语句时,首先对选择条件进行判断,当选择条件的值符合某一选择条件时,就将该选择条件前面的信号表达式赋给目标信号。例如,当选择条件的值符合条件1时,就将信号表达式1赋给目标信号;当选择条件的值符合条件n时,就将信号表达式n赋给目标信号。使用选择信号赋值语句时,应注意以下4点。
只有当选择条件的值符合某一选择条件时,才将该选择条件前面的信号表达式赋给目标信号;
每个信号表达式后面都含有when子句;
由于选择信号赋值语句是并发执行的,所以不能在进程中使用;
由于选择信号的测试是同时进行的,语句将对所有的选择条件进行判断,而没有优先级之分。这时如果选择条件重叠,就有可能出现两个或两个以上的信号表达式赋给同一目标信号,这样就会引起信号冲突,因此不允许有选择条件重叠的情况发生。
【例2-17】用选择信号赋值语句描述的2输入与非门电路。
需要说明的是,例2-17的选择信号赋值语句中,tmp的值“00”、“01”、“10”和“11”被明确规定,而用保留字others来表示tmp的所有其他可能值。这是因为信号a和b在实体中定义为std_logic类型,它们包含9种逻辑值,所以信号tmp的取值共有81种可能。因此,为了使选择条件能够涵盖选择条件表达式的所有值,这里用others来代替tmp所有可能值。
〖注意〗选择信号赋值语句的每一子句结尾是逗号,最后一句是分号。
3.元件例化语句
元件例化就是将预先设计好的设计实体定义为一个元件,然后利用特定的语句将此元件与当前的设计实体中的指定端口相连接,从而为当前设计实体引入一个新的低一级的设计层次。
元件例化语句又称为端口映射语句,它由两部分组成,前一部分(component)是将一个现成的设计实体定义为一个元件,通常位于结构体说明语句部分,后一部分(portmap)是将此元件与当前设计实体中连接说明,通常位于结构体功能描述语句部分。其语句格式如下:
其中,元件定义语句相当于对一个现成的设计实体进行封装,使其只留出外面的接口界面。就像一个集成芯片只留多个引脚在外一样,它的类属表可列出端口的数据类型和参数,例化元件端口名表可列出对外通信的各端口名。元件例化的第2部分语句即为元件例化语句,其中的元件例化名是必须存在的,它类似于标在当前系统(电路板)中的一个插座名,而例化元件名则是准备在此插座上插入的、已定义好的元件。portmap是端口映射的意思,其中的例化元件端口名是在元件定义语句中的端口名表中已定义好的例化元件端口的名字,连接实体端口名则是当前系统与准备接入的例化元件对应端口相连的通信端口,相当于插座上各插针的引脚名。以上两部分在元件例化中都是必须的。
元件例化语句中所定义的例化元件的端口名与当前系统的连接实体端口名的接口表达有两种方式:①名字关联方式,将例化元件的端口名与关联端口名通过关联(连接)符号“=>”一一对应地联系起来的方式;②位置关联方式,按例化元件端口的定义顺序将例化元件的对应的连接实体端口名一一列出的方式。在名字关联方式下,例化元件的端口名和关联(连接)符号“=>”两者都是必须存在的,而例化元件端口名与连接实体端口名的对应,在portmap子句中的位置可以是任意的。在位置关联方式下,portmap子句中只要按例化元件的端口定义顺序列出当前系统中的连接实体端口名即可。
【例2-18】使用元件例化语句,编写1位全减器的VHDL程序。
分析: 1位全减器有输入a、b和ci,两个输出co和diff,其中diff为全减器的和,co为借位输出。1位全减器内部电路的构成如图2-13所示。图中,h_sub为半减器,or_sub 为2输入的或门。
图2-13 1位全减器内部电路的构成
从图2-17可以看出,全减器由两个相同的半减器和一个2输入或门电路构成,因此使用元件例化语句编写全减器程序时,由3个程序构成,即h_sub、or_sub和f_sub。其中,h_sub用于描述半减器; or_sub用于描述2输入或门电路; f_sub用于描述全减器内部电路的功能,该程序首先在结构体的说明语句部分分别使用component语句声明调用h_ sub和or_sub,然后在结构体的功能描述部分使用portmap语句描述h_sub、or_sub与设计实体的连接关系。全减器的VHDL程序编写如下:
4.类属映射语句
类属映射语句可用于设计从外部端口改变元件内部参数或结构规模的元件,或称类属元件。这些元件在例化中使用特别方便,在改变电路结构或元件升级方面显得尤为便捷。其语句格式如下:
类属映射语句与端口映射语句portmap语句具有相似的功能和使用方法,它描述相应元件类属参数之间的衔接和传送方式,它的类属参数衔接方式同样有名字关联方式和位置关联方式两种。
【例2-19】使用类属映射和端口映射语句编写的n输入端口的或门和与门的VHDL程序。综合后的RTL电路如图2-14所示。
图2-14 n输入端口的或门和与门综合后的RTL电路
在例2-19中,端口映射语句描述了在结构体对外部元件的调用和连接过程中元件间端口的衔接方式,类属映射语句具有相似的功能,它描述的是相应元件类属参数间的衔接和传送方式。
由此可见,利用generic map语句可以灵活地改变参数来满足实际系统设计中的需求,这也正是类属映射语句的关键用途。
5.生成语句
生成语句(generate)用于产生多个相同的电路结构。在设计中,只要根据某些条件设定好某一元件或设计单元后,就可以利用生成语句赋值一组完全相同的并行元件或设计单元电路结构。
生成语句有两种不同的语句格式,分别为for-generate和if-generate结构。
这两种语句格式都由以下4部分组成。
【生成方式】有for语句结构或if语句结构,用于规定并行语句的复制方式。
【说明部分】包括对元件数据类型、子程序数据对象等作一些局部说明。
【并行语句】生成语句结构中的并行语句用于复制基本单元,主要包括元件、进程语句、块语句、并行过程调用语句、并行信号赋值语句、生成语句等。这表示生成语句允许存在嵌套结构,所以可用于生成元件的多维阵列结构。
【标号】生成语句中的标号并不是必须的,但如果是嵌套的生成语句结构,那么就必不可少。
对于for语句结构,主要是用来描述设计中的一些有规律的单元结构的,其生成参数及其取值范围的含义和运行方式与loop语句的十分相似。但需注意,从软件运行的角度来看,for语句格式中生成参数(循环变量)的递增方式具有顺序的性质,但最后生成的设计结构却是完全并行的,这就是为什么必须用并行语句来作为生成设计单元的缘故。
生成参数(循环变量)是自动产生的,它是一个局部变量,根据取值范围自动递增或递减。取值范围的语句格式与loop语句的是相同的,分为由关键词to和downto引导的两种形式,且必须是整数。
【例2-20】使用for-generate语句编写的6位全加器。综合后的RTL电路如图2-15所示。
图2-15 6位全加器综合后的RTL电路
分析: 1位全加器由两个半加器构成,如图2-16所示。图2-16中的ex_hadd表示半加器,它由与门和异或门构成,如图2-17所示。使用for-generate语句编写的6位全加器时,可以采用层次设计方式,即在同一项目中分别编写半加器、全加器及6位全加器的VHDL程序代码。
图2-16 1位全加器内部电路的构成
图2-17 半加器的内部图
【例2-21】使用for-generate和if-generate语句描述的8位串/并转换电路。
细心的读者可能会对for-loop语句和for-generate语句的异同提出疑问。它们二者的语法格式和循环变量变化的方式是相同的,但是二者在性质上有本质的区别:前者属于顺序语句,其内部是顺序描述的;而后者是并行语句,其内部是并行描述的。同样的,if语句和if -generate语句之间也有所区别,if-generate语句没有类似于if语句的else或elsif分支语句,且if语句属于顺序语句,if-generate语句属于并行语句。