FPGA之道(29)VHDL的串行语句

举报
李锐博恩 发表于 2021/07/15 02:19:46 2021/07/15
【摘要】 文章目录 前言VHDL的串行语句VHDL直接信号赋值语句VHDL变量赋值语句VHDL条件语句优先级条件语句无优先级条件语句优先级条件语句与无优先级条件语句的对比case-when的一些变形 VHDL空语句VHDL循环语句VHDL等待语句VHDL过程调用语句 前言 所谓的串行语句,不过是形式上的串行,映射为硬件电路时仍然为并行的。 VHDL的串行语句...

前言

所谓的串行语句,不过是形式上的串行,映射为硬件电路时仍然为并行的。

VHDL的串行语句

串行语句的执行方式是顺序执行的,一般高级编程语言中的语句执行方式都是顺序执行的,例如C语言,由此可见,顺序执行的语句更容易帮助我们来抽象和表达我们的设计思想,尤其是这样能够使时序逻辑的描述变得容易。所以,虽然FPGA的设计思路都是并行的,architecture中也仅支持并行语句的调用,但是为了方便设计者表达自己的思想,尤其是表达时序逻辑的思路,VHDL中的一些并行语句中的子语句体允许是顺序执行的,例如process。
除此以外,由于我们的代码是需要运行在PC机上的编译器来“读懂”并“翻译”成为数字电路的,而PC机上的运行的程序都是顺序执行的,所以,VHDL也必须支持串行的思路,否则编译器也无法工作。还有,仿真VHDL代码的工作也是在PC机上来执行的,所以VHDL本身也必须支持顺序的执行思路来达到仿真的目的。但是请注意,在VHDL中,串行语句的顺序执行只是一种帮助开发者、编译器、仿真器等理解代码、描述功能的一个辅助“思路”,而代码真正对应的硬件结构肯定是并行的!
到底VHDL里面有哪些串行语句可以供我们使用呢?以process为例描述如下:
process(<sensitive_list>)
begin
<VHDL直接信号赋值语句>;
<VHDL变量赋值语句>;
<VHDL条件语句>;
<VHDL空语句>;
<VHDL循环语句>;
<VHDL等待语句>;
<VHDL过程调用语句>;
end process;

VHDL直接信号赋值语句

直接信号赋值语句,也知道它是可以直接写在architecture中的并行语句,不过直接信号赋值语句具有双重身份,那就是它还可以当做串行语句来使用,例如:
process(a, b)
begin
c <= a and b; – 直接信号赋值语句
end process;

VHDL变量赋值语句

变量的特性就是赋值立即生效,由此也决定了它的执行结果必然跟语句的先后顺序有关,因此,变量赋值语句肯定是串行语句的一种。而变量的声明语句也是不能出现在architecture下的,而只能出现process、function、procedure等语句体的内部,所以变量的赋值语句也必然只能出现在这些语句体的内部。例如:

process(a, b)
	variable c : std_logic;
begin
	c := a and b; -- 变量赋值语句
	d <= c;
end process;

  
 

VHDL条件语句

条件语句是一种典型的串行语句。VHDL中共有两种条件语句——if-else和case-when,它们的根本区别是if-else中的各个条件分支是具有优先级的,且分支优先级按照书写顺序从高至低,而case-when语句是没有优先级的。关于它们的具体语法介绍如下:

优先级条件语句

优先级条件语句即if-else语句,它的完全语法如下:

if <expression1> then
		<statements>
elsif <expression2> then
		<statements>
<other elsif branchs>
else
		<statements>
end if;

  
 

其中的 elsif和else分支都不是必须的,可以根据具体情况来确定。举例如下:
– 求A、B、C三者中的最大值

if (A >= B and A >= C) then
	max <= A;
elsif (B >= C) then
	max <= B;
else
	max <= C;
end if;

  
 

注意,上例中,if、then(elsif、then)之间的条件判断表达式可以省略最外层的括号。
为什么说if-else语句是具有优先级的条件语句呢?需要从两个方面来说:

  • 第一,从语句的功能描述来分析。如果要描述上述求最大值的例子,我们可以这样翻译代码:首先,判断数A是不是大于等于B和C,如果成立,则最大值是A,结束判断;否则说明A不是最大值,那么这时候只需判断数B是不是大于等于C,如果成立,则最大值是B,判断结束;否则,由于之前已经得出A、B两数都不是最大值,那么最大值只能是C了。由此可见,每一个分支的判断都是建立在写在它之前的所有分支的基础上的。
  • 第二,从硬件实现上来说。上述求最大值的例子,对应到门级电路上,肯定是从A到max之间的路径最短,即所经过的逻辑门最少,而从B到max之间的路径次之,从C到max之间的路径最长。关于门级实现可以参考如下示意图:
    在这里插入图片描述
    由此可见,基于优先级条件语句的特点,如果我们知道A、B、C三个数中最大值的概率是B大于C大于A,那么我们应该把对B的判断放在第一个分支,然后C放在第二个分支,而A放在最后一个分支。这样,今后的仿真效率会更高,且对于具体的FPGA实现,也能保证最短路径得到最充分的利用,这样芯片即使工作在比较恶劣的环境下,也能保证故障率达到最低。

无优先级条件语句

无优先级条件语句即是case-when语句,它的完全语法如下:

case (<expression>) is
	when <constant_value1> =>
		<statements>;
	when <constant_value2> =>
		<statements>;	
	<other branchs>	
	when others =>
		<statements>;	
end case;

  
 

其中,的值必须互相不同,这和选择式信号赋值语句的要求是一样的。举例如下:
– 四选一多路选择器

case (sel) is -- sel is type of std_logic_vector(1 downto 0) when "00" => data <= channel0; when "01" => data <= channel1; when "10" => data <= channel2; when "11" => data <= channel3; when others => data <= channel0;
end case;

  
 

注意,上例中,case后的判断表达式也可以省略括号。
上述例子中的分支已经覆盖完全,但是还是有一个when others分支,这虽然有些画蛇添足,但确是一个编程的好习惯,请大家注意!
为什么说case-when语句是无优先级的条件语句呢?也需要从两方面来说:

  • 第一,从语句的功能描述来分析。如果要描述上述多路选择器的例子,我们可以这样翻译代码:如果sel等于”00”,那么选择第一路输出;如果sel等于”01”,那么选择第二路输出;如果sel等于”10”,那么选择第三路输出;如果sel等于”11”那么选择第四路输出。可见这四个分支的判断之间没有任何相互关系,也互不为前提。
  • 第二,从硬件实现上来说。上述多路复用器的例子,对应到门级电路上,无论是channel0~3中的任何一个,到data的路径都是等长的。关于门级实现可以参考如下示意图:
    在这里插入图片描述
    由此可见,在使用无优先级条件语句时,分支的顺序是无关的,不会影响电路的最终实现。

优先级条件语句与无优先级条件语句的对比

为了进一步说明if-else与case-when的不同,我们将之前用if-else编写的求最大值的例子用case-when重写如下:

x <=1’ when A >= B else0;
y <=1’ when A >= C else0; 
z <=1’ when B >= C else0;

  
 
judge <= x & y & z;
case (judge) is when "000" => max <= C; when "001" => max <= B; when "010" => max <= A; -- unreachable when "011" => max <= B; when "100" => max <= C; when "101" => max <= A; -- unreachable when "110" => max <= A; when "111" => max <= A; when others => <statement>;
end case;

  
 

关于其门级实现可参考如下电路图:
在这里插入图片描述
可见,此时,A、B、C到max的路径都是完全相等的。当然,由于上图并不是最简形式,所以此处我们没必要深究它与【优先级条件语句】小节中的例子到底孰优孰劣,但是请注意,由于目前的编译器都会对我们的代码有一定优化或修改作用,因此有时候if和case也可能会被综合成为一样的电路结构。

从上例可以看出,case结构中有很多分支输出结果完全一样,还有一些分支本身就不可达,例如,不可能A大于等于B,B大于等于C,然后A却小于C的。那么其实我们有时候为了书写方便,可以对case语句做一些变形。

case-when的一些变形

首先,利用特殊的“或”符号——“|”来简化代码,例如, 上述最大值无优先级条件语句可以变形为:

case (judge) is when "110" | "111" => max <= A; when "001" | "011"=> max <= B; when "000" | "100" =>
max <= C; when others =>
max <= A;
end case;

  
 

其次,对于整数类型的表达式,还可以使用to关键字,例如:

case (number) is -- number is type integer when 0 to 100=> data <= A; when 101 to 200 => data <= B; when others =>
data <= C;
end case;

  
 

第三,对于非整数类型的表达式,例如常用的std_logic_vector,可以通过类型转换函数先转成整数类型,然后再利用to关键字简化代码。
第四,切忌不可以利用std_logic的不关心态“-”来简化状态,例如,如果想当然的将上述最大值无优先级条件语句变形为:
– this is not right!

case (judge) is when "11-" => max <= A; when "0-1" => max <= B; when "-00" =>
max <= C; when others =>
max <= A;
end case;

  
 

虽然编译器不会报错,但是它的行为肯定是不对的,代码会始终停留在others分支。其原因是编译器对VHDL的不关心态“-”的理解跟我们预想的不一样。例如:
if (a = ‘-’) then
我们可能以为任何情况都能匹配,其实不然,目前为止的编译器还是把它理解为
if (FALSE) then
即,该条分支永远不可达。所以建议大家在平时的代码设计中尽量不要使用不关心态,如果非要使用,请参考下一点。
第五,不关心态在case中的应用。从第四点我们可以知道不关心态不能用于状态的简化,但是它的一个正确用法也是和case-when相关的,请看下面这个地址译码的例子:

case (address) is when "00" => d <= "0001"; when "01" => d <= "0010"; when "10" => d <= "0100"; when "11" => d <= "1000"; when others => d <= "0000";
end case;

  
 

上例中,为了避免引入锁存,必须添加others分支,可是如果将others分支改为
d <= “----”;
那么会带来一定程度的逻辑电路简化。究其原因,是因为在本例的情况下,others分支本身就是我们不关心的分支,所以在此分支里面d到底等于多少都不影响程序的正常功能。可是如果强行给d赋一个确定的值"0000",那么编译器就会为不能匹配的address分支强行把d变为"0000"。可是也许此时让d等于"0010"更能简化我们的逻辑电路。所以,一个偷懒的方法就是给d赋值"----",告诉编译器我们不关心这个值,然后让编译器自己去优化这个分支的数字电路。所以,我们之前在讲逻辑数据类型时说过,不关心态“-”指的是我们不关心,而不是编译器不关心。

VHDL空语句

空语句本身不做任何的处理,仅仅是将程序的执行引导到下一个串行语句而已,它的语法如下:
null;
老实说,空语句其实没有什么作用,最多就是个占位符号而已。有些人习惯在条件语句的无处理动作分支中加入null语句,以达到列举完全所有分支的目的。不过这仅仅是你编写代码时列举出来了而已,只有null语句的分支对于编译器来说跟没有是一样的,所以如果用于组合逻辑还是会产生锁存器。

VHDL循环语句

VHDL中有两种循环语句,即for-loop语句和while-loop语句,语法分别为
for in <lower_limit> to <upper_limit> loop
;
end loop;

while loop
;
end loop;
关于循环语句,使用的时候有以下几点建议:
1、循环语句主要是起到简化代码书写和增加代码可读性的作用,此时,循环语句中控制循环次数的变量不会跟元件中的任何输入、输出端口或者信号量或者变量进行运算或比较,所以无论其它信号怎么变化都不会影响循环实现的功能,例如:
for i in 0 to 15 loop
a(i) <= b(15 - i);
end loop;

i <= 0;
while (i <= 15) loop
a(i) <= b(15 - i);
i <= i + 1;
end loop;
这样就省得我们写上16行代码,或者用一条长长的映射或连接赋值语句了,并且日后程序需求有变动时,修改起来也很容易。
2、循环语句的循环次数必须是有限的,因为循环语句是串行的,它必须依附于进程、过程或者函数语句,而进程本身就是一个不断循环的语句体,那么如果一个循环内部套了一个无限循环,那么显然,从代码上面我们就无法解释我们描述的到底是一个什么东西,并且从硬件上也得不到有意义的解。
3、循环语句通常不应该作为功能语句使用,因为所有的循环功能都可以通过进程的循环特性来描述和表示。而循环语句作为功能描述时的抽象层级过高,很难确定编译器到底会怎么理解。例如,可以接受的例子如下:
for i in 0 to 15 loop – 其实可以用一个针对t的case-when结构来实现
if(i < conv_integer(t))then – t is an input signal
c(i) <= b(15 - i);
else
c(i) <= b(i);
end if;
end loop;
而有问题的例子如下:
process(sel,b,c)
begin
while sel = ‘1’ loop
if(c = b)then
a <= b;
else
a <= c;
end if;
end loop;
end process;
首先,这段代码无法仿真,因为,一旦sel为’1’,那么循环将会一直运行,所以进程无法执行到末尾,从而也就无法响应sel、b、c的变化,从而即使外部的信号值已经改变,可是内部的代码仍然无法得到这个变化,因此代码将会无休止的在一个时刻点上被仿真器不停的调用同一个分支,从而产生死循环。
其次,这段代码的初衷可能是要完成这样的工作:
process(sel,b,c)
begin
if sel = ‘1’ then
if(c = b)then
a <= b;
else
a <= c;
end if;
end if;
end process;
只不过忘记了process的循环特性,才写成的while。
最后,建议大家主要还是用循环语句来做一些简化代码方面的工作,至于功能上的事情,完全可以交给process的循环机制来完成。

VHDL等待语句

VHDL中有四种等待语句,分别介绍如下:

wait;

  
 

表示永远挂起,用于仿真;

wait on <sensitive signal list>; 

  
 

–信号发生变化将结束挂起,置于进程最下方代替进程开始的敏感量表,例如:

process
begin
	a <= b and c;
	wait on b, c; -- b, c任一信号发生变化时,进程重新开始
end process;

  
 
wait until <condition>; 

  
 

条件表达式中的信号发生变化且满足条件时结束挂起,它可以用来描述触发、锁存等时序相关逻辑,例如:

process
begin
	wait until clk = '1';
	a <= b;
	wait until clk = '0';
end process;

  
 

它等效于

Process(clk)
Begin
	If(clk'event and clk = '1')then
a <= b;
		end if;
end process;

  
 

不过通常情况下,还是建议大家使用第二种方式来描述时序逻辑,而对wait until语句仅作了解即可。

wait for <time expressions>;

  
 

超时等待语句,它主要用于仿真,等待够时间表达式中的时间后结束挂起。

VHDL过程调用语句

过程——procedure,也是VHDL子程序的一种过程的调用是一种串行的调用,它不可以直接出现在architecture的语句部分当中,一般需要依附于process等内部支持顺序执行的语句,例如:

	-- procedure min() is predefined process(a,b)
	begin
min(a, b, c); -- a,b is input and c is the output
end process;

  
 

文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。

原文链接:reborn.blog.csdn.net/article/details/104289618

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。