FPGA之道(27)VHDL的操作符号
VHDL的操作符号
VHDL赋值运算符
VHDL语言中共有3种赋值符号——“<=”、“:=”和“=>”。
<=
“<=”符号为signal专用,对signal的赋值能且只能使用“<=”赋值符号。 举例如下:
signal a : std_logic;
a <= ‘1’;
“<=”符号是一种有延迟的赋值,即赋值操作不是立即生效的。编译器会根据你代码所描述的功能以及在程序中所处的位置,来决定产生这种延迟的硬件逻辑结构是组合逻辑还是时序逻辑。
:=
单独使用时,“:=”符号为variable专用,对variable的赋值能且只能使用“:=”赋值符号。举例如下:
variable b : std_logic;
b := ‘0’;
此时,“:=”符号是一种无延迟的赋值,即赋值操作立即生效,这点与软件编程的思路很像。
除此以外,“:=”符号还有一种用途,就是初始化赋值。这个时候“:=”是依附于声明语句而使用的,应用的对象可以是signal、variable以及constant中的任何一种。
=>
“=>”符号为映射赋值符号,从某种意义上来说,它不能算是一种赋值运算符,而应该是一种连线符号,因为它只是为赋值符号的左右两边建立起一种连接关系而已。 因此与“<=”和“:=”不同,“=>”本身并不明确表示赋值的方向关系,即到底是把左边的数据写到右边去还是把右边的数据写到左边去。而对于“<=”和“:=”来说,赋值的方向显然非常明显,为从右至左。Architecture中的例化语句就是映射赋值符号应用的一个典型例子,无论端口方向是in还是out,都一律使用“=>”来进行映射赋值,至于赋值的方向,编译器会根据上下文自己去推断。
“=>”符号应用的地方很多,刚刚提到的元件例化语句(instance)的赋值语法如下:
<instance_name> : <component_name>
port map ( <port_name> => <signal_name>, <other ports>...
);
- 1
- 2
- 3
- 4
- 5
而信号量的映射赋值则稍有不同,例如:
signal a, b : std_logic_vector(3 downto 0);
a <= (0 => ‘1’, 2 => '1', others => '0'); -- a = ”0101”
b <= (others => ‘1’); -- b = “1111”;
- 1
- 2
- 3
注意这里面有一个新的语法others,这在对逻辑向量赋值时非常好用,能够帮我们方便的完成一些高位宽向量的赋值。并且,类似像b这样的信号赋值,即是以后代码修改、扩展或减少了b的bit位数,赋值语句也可以不需要做任何修改,非常便于程序的维护和变更。
位置赋值
VHDL语言中仅有的三种赋值符号都已经介绍完了,可是赋值操作的形式并没有就此终结。除了以上三种赋值形式外,VHDL语言还有一种基于位置赋值的方式,这种赋值方式其实也属于映射赋值的范畴,但它不需要任何操作符,编译器仅仅根据参数的位置就可以进行映射赋值。
在VHDL语言中,元件例化、过程及函数调用都可以这样赋值,这一点跟C语言的函数调用非常类似。不过对于凡是支持“=>”符号来进行映射赋值地方,建议大家还是不要使用位置赋值,因为映射赋值是位置无关的,这样会在很大程度上减少程序出错的可能性,并且方便日后的修改与维护。
VHDL按位运算符
按位运算符是一类最基本的运算符,可以认为它们直接对应数字逻辑中的与、或、非门等逻辑门,在VHDL中它们的描述如下:
NOT
NOT是取反运算符。它是一个单目运算符,即作用于一个操作数,对它进行按位取反。相当于数字逻辑电路中的“非门”,例如:
signal a : std_logic := '1';
signal b : std_logic_vector (3 downto 0) := "1000";
a <= not a; -- a = '0'
b <= not b; -- b = "0111"
- 1
- 2
- 3
- 4
AND
AND是与运算符。它是一个双目运算符,即必须有两个操作数,对它们进行按位与运算,相当于数字逻辑电路中的“与门”。例如:
signal a0 : std_logic := '1';
signal b0 : std_logic := '1';
signal c0 : std_logic;
signal a1 : std_logic_vector (3 downto 0) := "1101";
signal b1 : std_logic_vector (3 downto 0) := "1011";
signal c1 : std_logic_vector (3 downto 0);
c0 <= a0 and b0; -- c0 = '1'
c1 <= a1 and b1; -- c1 = "1001"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
OR
OR是或运算符。它是一个双目运算符,即必须有两个操作数,对它们进行按位或运算,相当于数字逻辑电路中的“或门”。例如:
signal a0 : std_logic := '1';
signal b0 : std_logic := '0';
signal c0 : std_logic;
signal a1 : std_logic_vector (3 downto 0) := "1101";
signal b1 : std_logic_vector (3 downto 0) := "1011";
signal c1 : std_logic_vector (3 downto 0);
c0 <= a0 or b0; -- c0 = '1'
c1 <= a1 or b1; -- c1 = "1111"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
XOR
XOR是异或运算符,它是一个双目运算符,即必须有两个操作数,对它们进行按位异或运算,相当于数字逻辑电路中的“异或门”。例如:
signal a0 : std_logic := '1';
signal b0 : std_logic := '0';
signal c0 : std_logic;
signal a1 : std_logic_vector (3 downto 0) := "1100";
signal b1 : std_logic_vector (3 downto 0) := "1010";
signal c1 : std_logic_vector (3 downto 0);
c0 <= a0 xor b0; -- c0 = '1'
c1 <= a1 xor b1; -- c1 = "0110"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
XNOR
XNOR是同或运算符,它是一个双目运算符,即必须有两个操作数,对它们进行按位同或运算,相当于数字逻辑电路中的“同或门”。例如:
signal a0 : std_logic := '1';
signal b0 : std_logic := '0';
signal c0 : std_logic;
signal a1 : std_logic_vector (3 downto 0) := "1100";
signal b1 : std_logic_vector (3 downto 0) := "1010";
signal c1 : std_logic_vector (3 downto 0);
c0 <= a0 xnor b0; -- c0 = '0'
c1 <= a1 xnor b1; -- c1 = "1001"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
VHDL算术运算符
算数运算符,即我们常说的加、减、乘、除等,这类运算符的抽象层级较高,从数字逻辑电路的实现上来看,它们都是基于与、或、非等基础逻辑门来组合实现的。算数运算符主要有两种用途:一种是作用于能对应到FPGA硬件的信号上,完成具体的硬件功能;另一种是作用于VHDL程序的某些参数或者语句结构上,仅仅起到帮助用户更方便的编写代码和帮助编译器更好的理解代码的作用。
它们在VHDL中的描述如下:
+
“+”是加法运算符,它是一个双目运算符,即必须有两个操作数,对它们进行加法运算。例如:
signal a0, b0, c0 : integer;
signal a1 : std_logic_vector (3 downto 0) := "0001";
signal b1 : std_logic_vector (3 downto 0) := "1010";
signal c1 : std_logic_vector (3 downto 0);
a0 <= 97;
b0 <= 103;
c0 <= a0 + b0; -- c0 = 200
c1 <= a1 + b1; -- c1 = "1011",按照二进制数值来进行加法
a1 <= "1000";
c1 <= a1 + b1; -- c1 = "0010",进位被丢掉了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
“-”是减法运算符,它是一个双目运算符,即必须有两个操作数(两个操作数的地位不一样,一个是被减数,一个是减数,不能颠倒),对它们进行减法运算。例如:
signal a0, b0, c0 : integer;
signal a1 : std_logic_vector (3 downto 0) := "1000";
signal b1 : std_logic_vector (3 downto 0) := "0111";
signal c1 : std_logic_vector (3 downto 0);
a0 <= 97;
b0 <= 103;
c0 <= b0 + a0; -- c0 = 200,对比上例可以发现颠倒操作数的位置结果不变
c0 <= a0 - b0; -- c0 = -6
c0 <= b0 - a0; -- c0 = 6,显然减数、被减数颠倒后结果不一样
c1 <= a1 - b1; -- c1 = "0001",按照二进制数值来进行减法
c1 <= b1 - a1; -- c1 = "1111",按照有符号数来理解,"1111"其实就是-1;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
*
“*”是乘法运算符,它是一个双目运算符,即必须有两个操作数,对它们进行乘法运算。例如:
signal a0, b0, c0 : integer;
signal a1 : std_logic_vector (3 downto 0) := "0011";
signal b1 : std_logic_vector (3 downto 0) := "0010";
signal c1 : std_logic_vector (3 downto 0);
a0 <= 25;
b0 <= 40;
c0 <= a0 * b0; -- c0 = 1000
c1 <= a1 * b1; -- c1 = "0110",按照二进制数值来进行乘法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
/
“/”是除法运算符,它是一个双目运算符,即必须有两个操作数(两个操作数的地位不一样,一个是被除数,一个是除数,不能颠倒),对它们进行除法运算。例如:
signal a0, b0, c0 : integer;
signal a1 : std_logic_vector (3 downto 0) := "0110";
signal b1 : std_logic_vector (3 downto 0) := "0010";
signal c1 : std_logic_vector (3 downto 0);
a0 <= 660;
b0 <= 30;
c0 <= a0 / b0; -- c0 = 22
c1 <= a1 / b1; -- c1 = "0011",按照二进制数值来进行除法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
MOD与REM
MOD是取模运算,而REM是求余运算,它们都是完成求余数的运算,都是双目运算符,且操作数不能任意颠倒,语法如下:
x = a mod b;
y = a rem b;
- 1
- 2
如果a,b都是正数的情况下,它们的运算结果一摸一样。实际上,只要a和b同号时,它们的运算结果就是一样的。不一样的情况产生于a与b异号时。即,mod的结果是要与b同号的,而rem的结果是要与a同号的(注意,0的符号可正可负)。那么求余数的通用公式如下:
Remainder = a - b * N;
对于mod来说,N取使remainder绝对值最小,且和b同号的那个结果;
对于rem来说,N取使remainder绝对值最小,且和a同号的那个结果;
以下给出几个例子供大家参考:
7 mod 5 = 2; 7 rem 5 = 2;
(-7) mod 5 = 3; (-7) rem 5 = -2;
7 mod (-5) = -3; 7 rem (-5) = 2;
(-7) mod (-5) = -2; (-7) rem (-5) = -2;
注意,不要轻易在能对应FPGA硬件的信号量上使用这些运算符,会非常浪费资源。如果一定要使用,可以使用一些现成的IP核,或者自动动手编写相关算法,这样实现起来会更加节省资源、更加可控一些。
**
“”是乘方运算,完成求一个数的n次幂运算,它是双目运算符,且操作数不能任意颠倒,语法如下:
a <= 28; – a = 256
注意,不要轻易在能对应FPGA硬件的信号量上使用乘方运算符,如果一定要用,可以使用一些现成的IP核,或者自动动手编写相关算法。
VHDL关系运算符
关系运算符主要用来做一些条件判断使用,通用的语法为:
<expression_left> <relation_operator> <expression_right>
VHDL中的关系运算符包括如下几种:
= -- 两边表达式相等
/= -- 两边表达式不相等
< -- 左边表达式小于右边表达式
<= -- 左边表达式小于等于右边表达式
> -- 左边表达式大于右边表达式
>= -- 左边表达式大于等于右边表达式
- 1
- 2
- 3
- 4
- 5
- 6
关系运算符一般不单独使用,都需要配合具体的语句来实现完整的意思。关系表达式会返回true或者false的结果。即,如果左右表达式满足相应关系运算符表示的关系时,返回true,否则返回false。
需要注意一点,VHDL语言中是不支持简化书写的,即:
if (a = ‘1’) then
不可以写成
if (a) then -- error! will report : a is not a boolean expression!
- 1
- 2
- 3
VHDL逻辑运算符
逻辑运算符是连接多个关系表达式用的,从而实现更加复杂的判断,一般也不单独使用,都需要配合具体语句来实现完整的意思。它的语法如下:
<logical_operator> < relation_expression_right>
或者
<relation_expression_left> <logical_operator> < relation_expression_right>
VHDL中的逻辑运算符包括如下几种:
NOT -- 右边表达式的逻辑结果取逻辑反,这是一个单目的操作符;
AND -- 左右两边表达式的逻辑结果取逻辑与,即同为true才返回true,否则返回false;
OR -- 左右两边表达式的逻辑结果取逻辑或,即同为false才返回false,否则返回true;
- 1
- 2
- 3
结合关系运算符和逻辑运算符,给出以下几个例子:
if (a > b) then
max <= a;
else
max <= b;
end if;
if(a > 90 and a < 110)then
stable <= ‘1’;
else
stable <= ‘0’;
end if;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
我们可以看出,VHDL逻辑运算符和按位运算符的前三个长得真是一模一样,只不过它们所处理的数据类型不一样。但是,如果我们把true看成’1’,把false看成’0’,其实也可以近似把它们看成是通用的,而且如果从硬件实现角度上来说,它们是没有区别的,例如:
if (a = '1' and b = '1')then
c <= '1';
else
c <= '0';
end if;
等价于
c <= a and b;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
VHDL连接运算符
连接运算符不完成任何运算,它只是可以将若干个标准逻辑类型或者逻辑向量首尾相接,描述成为一个新的逻辑向量。它的语法如下:
<signal 1> & <signal 2> &
例如,
signal a, b : std_logic;
signal c : std_logic_vector(1 downto 0);
signal d : std_logic_vector(3 downto 0);
d(3) <= a;
d(2 downto 1) <= c;
d(0) <= b;
或者
d <= a & c & b;
该运算符尤其是在描述串行移位寄存器的时候最为方便,例如:
d <= d(2 downto 0) & a;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
VHDL移位运算符
VHDL中共有3类移位运算符,分别介绍如下:
循环移位运算符,包括rol、ror,分别对应循环左移和循环右移;
算数移位运算符,包括sla、sra,分别对应算数左移和算数右移;
逻辑移位运算符,包括sll、srl,分别对应逻辑左移和逻辑右移。
- 1
- 2
- 3
它们的通用语法如下:
<signal_name> <shift_operator> <shift_amount_in_integer>;
循环移位的意思是在移位的时候,高(低)位的移出补到低(高)位上;
算数移位的意思是在移位的时候空出来的位保留原值;
逻辑移位的意思即是在移位的时候空出来的位用’0’填充。
举例如下:
signal a : std_logic_vector(3 downto 0) := "0111";
a <= a rol 2; -- a = "1101"
a <= a ror 1; -- a = "1011"
a <= a sla 1; -- a = "1111"
a <= a sra 2; -- a = "0001"
a <= a sll 2; -- a = "1100"
a <= a srl 1; -- a = "0011"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
以上的6个移位操作符,需要调用IEEE.numeric_std库才能使用它们。我建议大家通常情况下不要使用这6个移位操作符,因为使用上一小节中介绍的连接运算符可以非常方便的实现移位操作,同时还不易出错,并且更加形象、更加接近底层硬件描述,而且通过连接运算符,我们还能定义更多样化的移位方式,例如用’1’填充空位的逻辑移位。 下面把上例的所有操作用连接运算符依次对比描述如下:
a <= a(1 downto 0) & a(3 downto 2);
a <= a(0) & a(3 downto 1);
a <= a(2 downto 0) & a(0);
a <= a(3) & a(3) & a(3 downto 2);
a <= a(1 downto 0) & "00";
a <= '0' & a(2 downto 0);
- 1
- 2
- 3
- 4
- 5
- 6
顺便说一下,移位操作是乘法和除法的实现基础。
文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。
原文链接:reborn.blog.csdn.net/article/details/104280464
- 点赞
- 收藏
- 关注作者
评论(0)