2.2.5 运算符
VHDL的运算符可分为逻辑运算符(Logical Operators)、关系运算符(Relational Operators)、符号运算符(Sign Operators)、算术运算符(Arithmetic Operatorss)、移位运算符(Shift Operators)及连接运算符(Concatenation Operators),每种运算符均在VHDL的程序包中预先进行了定义。与其他编程语言一样,VHDL的运算符也有重载的特性,即不同类型数据之间的运算操作采用相同的运算符时,程序可自动调用相应的运算符操作函数。VHDL是一种类型检查十分严格的编程语言,所有不同数据类型的操作均需要有相应的重载运算符函数支撑,否则程序不能正确编译。
1.逻辑运算符
逻辑运算符有7种:and(与)、or(或)、nand(与非)、nor(或非)、xor(异或)、xnor(同或)、not(非),每种运算符的意义都与数字电路中的门电路符号相同。图2-1所示为几种逻辑运算符表达式综合后的RTL原理图。
图2-1 逻辑运算符表达式综合后的RT L原理图
逻辑运算符除用于赋值语句外,还可以用于产生布尔型数值,用作条件语句中的判断条件,如
2.符号运算符
符号运算符有3种:负值符号(-)、正值符号(+)及取绝对值(abs)。由于正值符号用于变量或信号前不改变原值,所以正值符号几乎用不到。负值符号对原值取相反的值,绝对值运算即取原值的绝对值。由于负值符号及取绝对值均需用有符号数来表示,因此必须声明有符号数运算符号的程序包,如STD_LOGIC_SIGNED。图2-2所示为STD_LOGIC_VECTOR类型数据的负值运算及取绝对值运算表达式综合后的RTL原理图。
图2-2 负值运算及取绝对值运算表达式综合后的RT L原理图
从图2-2中可以看出,负值运算是由取反及加法器两种基本逻辑元件完成的;而取绝对值运算则是由取反、加法器及多路信号选择器三种基本逻辑元件完成的。
在应用符号运算符时,必须声明具有符号数运算符的程序包(STD_LOGIC_SIGNED),否则程序不能编译通过;用XST综合工具综合时,界面中会显示“abs(-)can not have such operands in this context.”(程序中不能有abs(-)运算符)提示信息。
3.关系运算符
VHDL提供了6种关系运算符:等于(=)、不等于(/=)、大于(>)、小于(<)、大于或等于(>=)、小于或等于(<=)。各种关系运算符的意义十分清楚,值得注意的是,STD_LOGIC_SIGNED和STD_LOGIC_UNSIGNED这两个设计中最常用的程序包中对6种关系运算符均进行了重载,使STD_LOGIC_VECTOR类型的数据可以直接与STD_LOGIC_VECTOR或INTEGER类型的数据进行比较,这给编写代码带来了不少方便,因为一长串二进制数据的十进制值读/写起来很不方便。下面是一些关系运算符的语句例子。
读者可能已经发现,在VHDL语言中,符号“<=”、“>=”可表示信号赋值,也可表示关系运算符,在写程序时是否需要刻意注意同情况的明确意义呢?完全不必要,且VHDL语言也没有提供这项语法。在编译程序时,编译器会根据代码的上下文自动确定符号代表的含义。
4.算术运算符
VHDL提供了7种关系运算符:加法(+)、减法(-)、乘法(*)、除法(/)、指数(**)、求模(mod)、求余(rem),但只有加法、减法、乘法这3种运算符在程序包STD_LOGIC_UNSIGNED和STD_LOGIC_SIGNED中进行了定义,且这3种运算符的操作数及运算结果均可以是STD_LOGIC_VECTOR数据类型。之所以重点关心STDLOGIC_VECTOR类型的数据是否能直接使用各种运算符,是因为STD_LOGIC_VECTOR是VHDL设计中使用最为普遍的数据类型。
加法及减法运算在数字电路中的实现相对简单。在用综合工具综合设计时,RTL电路图中的加、减操作会被直接综合成加法器或减法器。乘法运算在其他软件编程语言中实现起来十分简单,但用门电路、加法器、触发器等基本数字电路元件实现却着实不是一件容易的事。
除法、指数、求模、求余运算均没有在STD_LOGIC_SIGNED和STD_LOGIC_UNSIGNED程序包中定义,其操作数及运算结果也没有STD_LOGIC_VECTOR数据类型,因此无法在VHDL程序中直接对STD_LOGIC_VECTOR类型的数据进行相关运算。实际上,用基本逻辑元件构建这4种运算本身是十分繁杂的工作,如果要用VHDL实现这些运算,一种方法是使用开发环境提供的IP核或使用商业IP核,另一种方法是将算法分解成加、减、乘等操作步骤来逐步实现。
FPGA器件一般都提供除法器IP核。VHDL语言中不仅STD_LOGIC_VECTOR的数据类型不能直接使用这些运算,且设计文件中的变量及信号均不能使用这些运算符。那么VHDL提供的这几种运算符在哪里使用呢?只在常量声明时使用,即这些运算符只能对常量类型的数据进行运算。前面说过,常量本身就是一些固定值,直接指定即可,何必多此一举在常量表达式里加运算符?这还是为了代码书写的方便,以及在一些情况下更易表达常量的构成及常量之间的关系,如下面这些例子。
5.移位运算符
NUMERIC_STD程序包对4个移位运算符进行重载定义:逻辑左移(SLL)、逻辑右移(SRL)、循环左移(ROL)和循环右移(ROR)。在进行SLL及SRL运算时,移位后的空位填“0”。图2-3所示是4种移位运算操作的动作示意图。
图2-3 移位运算操作的动作示意图
虽然NUMERIC_STD程序定义了移位运算符,但该运算符只支持BIT_VECTOR类型的数据,所以在实际设计中极少使用。那么在设计中要用到移位运算时怎么办呢?STD_LOGIC_SIGNED和STD_LOGIC_UNSIGNED程序包已定义好了两个移位函数:左移函数SHL及右移函数SHR。
上面两条函数定义语句在STD_LOGIC_SIGNED及STD_LOGIC__UNSIGNED程序包中定义。SHL与SHR是函数,使用时需采用函数调用方法。对于无符号数信号,SHL、SHR与SLL、SRL的作用相同。对于有符号数信号,SHL与SLL仍然相同;SHR运算时,右边的空位补原数据的最高位,即补符号位,也称符号扩展。
6.连接运算符
连接运算符“&”用于将两个操作数连接起来,连接后的数据长度为两个操作数长度之和。连接运算符有什么作用呢?最直接的应用实例是加法操作。我们知道,两个长为2比特的二进制数据相加,为了保证相加的结果不溢出,结果必须用长为3比特的数据保存,考查下面的例子。
程序综合时没有出现问题,但在用ModelSim做仿真时,却出现了问题。
原因是VHDL语言具有严格的数据类型检查的特点,连接运算符正好可以解决这一问题。对于无符号数,可在操作数的高位再连接1比特的“0”即可;对于有符号数,在操作数的高位扩展1比特的符号位即可。在上面的例子中,将语句“c<=a+b”修改为“c<=('0'&a)+('0'&b)”后,程序可正常仿真。对于加法、减法操作来说,运算数据结果的长度与长度较长的操作数相同,也就是说,前面的语句也可修改为“c<=('0'&a)+b”。