FPGA之道(22)VHDL基本程序框架

举报
李锐博恩 发表于 2021/07/15 02:04:26 2021/07/15
【摘要】 文章目录 前言VHDL基本程序框架VHDL基本程序框架模板LibraryEntityArchitecture声明与定义部分语句部分 VHDL基本程序框架范例VHDL注释语法 前言 VHDL相对于Verilog显得更加的严谨,这也意味着更加的“复杂”,本人是从Verilog开始进入FPGA开发的,由于Verilog的简洁性,也推荐如此。 学习任何一门...

前言

VHDL相对于Verilog显得更加的严谨,这也意味着更加的“复杂”,本人是从Verilog开始进入FPGA开发的,由于Verilog的简洁性,也推荐如此。
学习任何一门语言,先从其总体架构上去了解这个语法,特别是对于同一个领域的几种语言进行学习时,可以在一门语言的基础之上对另一种语法进行理解,试着根据自己的知识架构去理解它。
下面根据《FPGA之道》作者的痕迹去试图理解作者想要传达给我们的内容。

VHDL基本程序框架

在介绍详细的VHDL语法之前,让我们先来了解一下VHDL代码的基本程序框架,这样可以让我们先对VHDL程序设计有一个整体的概念把握,进而在后续的VHDL语法学习中做到有的放矢。阅读本节时请着眼于大体,而不要过分去苛求细节语法,细节的语法介绍将在后续的小节中慢慢展开。

VHDL基本程序框架模板

VHDL的基本程序框架可以概括如下:

library <library_name>;
use <library_name>.<packege_name>.<function_name>;
<other packeges and functions>...
<other librarys>...

entity <entity_name> is
generic ( <generic_name> : <type> := <value>; <other generics>... );
port ( <port_name> : <mode> <type>; <other ports>...
);
end <entity_name>; architecture <arch_name> of <entity_name> is component <component_name> generic ( <generic_name> : <type> := <value>; <other generics>... ); port ( <port_name> : <mode> <type>; <other ports>... ); end component; 
<other components>...

signal <signal_name>: <type> := <value>;
<other signals>...

begin <instance_name> : <component_name> generic map ( <generic_name> => <value>, <other generics>... ) port map ( <port_name> => <signal_name>, <other ports>... ); <other instances>... process (<sensitive_signals_list>) variable <name>: <type> := <value>; <other variables>... begin <statements>; end process; <other processes>... <other statements not in process>... end <arch_name>;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

从VHDL的基本程序框架我们可以看出,它主要分为三个部分:library、entity和architecture,下面我们来分别介绍。

Library

Library即是“库”的意思,在VHDL设计中,为了提高设计的效率和一致性(即使设计能够遵循一些统一的语言标准或者数据格式),需要将一些有用的信息汇集到一起,以供开发者调用,从而就形成了库。这就好比编写C语言时,一开头总要include若干个*.h的头文件,否则编译就会出问题,报一些类似“找不到print函数定义”之类的错误。
在VHDL中,要使用库,首先要声明,语法如下:

library <library_name>;

一般,一个library中都会包含若干个程序包,每个程序包中都会包括若干个子程序。因此,为了效率、代码明确性等方面的考虑,我们只需要对具体使用到的库、程序包或者子程序进行引用即可,语法如下:

use <library_name>.<packege_name>.<function_name>;

当然了,如果有时候需要使用一个库或者一个程序包中的全部,那么这样逐个列出显然会非常冗长拖沓,这个时候,可以使用如下语法:

use <library_name>.<packege_name>.all;

或者

use <library_name>.all;

一般来说,库分为两类:一类是设计库,即在具体设计项目中设定的目录所对应的work 库,这个库一般不用显式声明,因为FPGA开发工具一般会自动帮我们完成这个工作;另一类是资源库,即常规元件和标准模块存放的库。我们这里讨论的库主要指第二种。 一个比较完整的标准资源库的引用描述如下:

library STD;
use STD.textio.all;
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_textio.all;
use IEEE.std_logic_arith.all;
use IEEE.numeric_bit.all;
use IEEE.numeric_std.all;
use IEEE.std_logic_signed.all;
use IEEE.std_logic_unsigned.all;
use IEEE.math_real.all;
use IEEE.math_complex.all;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

一般来说,比较常用的就是ieee标准库中的两、三个程序包,所以,通常VHDL代码的最开头都是这样的:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

  
 
  • 1
  • 2
  • 3
  • 4

当然,有时候由于使用了一些不太常用的数据结构或者函数,只包含这几个packege,编译器是无法正确编译代码的,下面对这些标准库进行一下简单的介绍。
首先,这里有一个隐含的std.standard程序包的声明,这个程序包是所有编译器必须包含的,所以不需要显式的声明和引用。这个standard程序包中,主要定义了一些典型的、基本的数据结构,例如bit、bit_vector、integer、boolean等等。
其次,STD.textio主要定义了用户的输入、输出,所以这个包一般也没有必要引用。
最后,我们来简要介绍一下ieee标准库中的程序包的意义,以供参考:
IEEE.std_logic_1164:定义了一些增强型的数据结构,例如std_logic, std_logic_vector;
IEEE.std_logic_textio:针对1164程序包定义了用户的输入、输出;
IEEE.std_logic_arith:提供了数的计算操作,基于有符号数、无符号数以及逻辑矢量等;
IEEE.numeric_bit:提供了数的计算操作,基于bit数组类型的有符号和无符号数;
IEEE.numeric_std:提供了数的计算操作,基于std_logic数组类型的有符号和无符号数;
IEEE.std_logic_signed:提供了基于std_logic_vector的有符号数计算操作;
IEEE.std_logic_unsigned:提供了基于std_logic_vector的无符号数计算操作;
IEEE.math_real:提供了基于real类型的数的计算操作;
IEEE.math_complex:提供了基于复数类型的数的计算操作;
IEEE.std_logic_misc:为1164程序包定义了补充的类型、子类型、常数、函数等;
另外,不同的FPGA编译器还有一些自己特定的库,这些库也比较常用,例如ise的unisim库,在调用底层原语的时候就必须加上这么两行:
library UNISIM;
use UNISIM.VComponents.all;

Entity

Entity即是“实体”意思,在VHDL设计中,它主要描述对象的外貌,即对象的输入和输出的端口信息,而器件的具体功能则由architecture来描述, 这个我们会在稍后详细讲解。Entity和architecture就好比我们电路板上的芯片一样,entity负责描述芯片的管脚信息,而architecture负责实现芯片具体的功能;这也和c语言中的函数调用类似,在main函数前要先声明被调用函数的名字和参数信息,而具体的函数功能则可以放在main函数后面的函数定义中实现。简而言之,entity和architecture,就是接口和内容这样一个关系。
既然是接口,那就少不了对输入、输出端口的描述,而这些描述,都包含在port语法结构中。VHDL中的端口共有4种:输入端口、输出端口、缓冲端口以及双向端口,定义时分别对应语法如下:

<port_name> : in <type>;
<port_name> : out <type>;
<port_name> : buffer <type>;
<port_name> : inout <type>;

  
 
  • 1
  • 2
  • 3
  • 4

当有多个端口具有同样的方向和类型时,也可以写成这样:

<port_name_list> : <mode> <type>;

  
 
  • 1

对于in和out,我想大家都很容易理解,就不需要多说了。下面主要介绍一下buffer与inout类型的端口。

  • Buffer端口其实也属于输出端口的一种,不过由于VHDL语法比较严谨,所以entity中被定义为out的端口,在architecture中就只能够被赋值。以前,如果architecture内部逻辑还需要用到这个输出结果,直接引用的话会报语法错误,所以只能先声明一个中间变量signal作为输出的中转来解决问题,比较麻烦。现在好了,有了buffer类型端口,architecture里面的语法就可以直接引用了,非常方便。
  • 而Inout端口主要在处理总线数据时使用,因为总线一般是读、写共用的,从物理上就不存在独立的读总线和写总线,因此,要和总线打交道,一般都需要用到双向端口。需要注意的是,inout一般是需要三态门来实现的,通常FPGA内部逻辑中是没有三态门的,而在FPGA的I/O接口资源中才会有,所以一般不建议在内部模块的交互中使用双向端口。其实,从inout的特性来看,它也主要是为了和外部一些芯片交互时使用,例如与外部控制器ARM或者存储芯片SDRAM交互,由于这些芯片的总线是读写共用的,所以必须使用双向端口。而对于内部逻辑,完全可以用多路复用器(MAX)来实现总线逻辑。以上四种端口类型也可以形象的通过下图来表明:
    在这里插入图片描述
    细心的你也许会发现,既然entity中的port语法已经完整描述了对象的接口作用,那在基本程序框架中,为什么还会有一个generic语法结构呢?这个要从代码的可重用性上说起,考虑这样一种情况,如果你之前已经编写好了一个输入位宽8bits的加法器,现在由于一些原因,你又需要一个输入位宽为9bits的加法器,你该怎么办?复制代码,修改名称和所有接口及内部处理的数据位宽吗?当你的设计很简单,代码行数也很少的时候,也许你会乐于这么做,但是当你的设计逻辑非常复杂,单模块代码量也成百上千行后,这可能就不那么好玩了。于是,为了解决大家的苦恼,VHDL在entity里面又放入了generic语法结构,专门用来定义一些参数信息,方便代码的复用,以提高可重用性。因此,对于entity来说,port语法是必须的,而generic语法是可选的。需要说明的一点,FPGA里面的代码可重用性与软件编程有着本质的不同:软件的代码可重用性越高,最终会使可执行文件的体积越小,所带来的好处不仅仅是程序设计时的省事,而是实实在在的空间节省;而FPGA里面的代码可重用性仅仅是便利程序设计者的工作,对最终实现设计所使用的资源不会有任何的缩减。 这是因为,软件可以通过反复调用同一个地址空间的同一个函数来完成若干个不同任务中的部分功能,而FPGA如果在需要一个8bits的加法器的同时又需要一个9bits的加法器,那么它必然会同时实现这两个加法器供系统调用,甚至你需要两个都是8bits的加法器,结果也一样,因为软件的执行是串行的,而FPGA的执行都是并行的。但这并不代表在FPGA中就不能够复用功能模块,要做到这点主要取决于系统的时间需求。

Architecture

Architecture即是“构造体”意思,从前面的介绍中,我们已经知道,在VHDL设计中,它主要负责描述entity的具体实现,所以architecture必须依附于entity而存在,因此, architecture语法的第一步就是表明身份,昭告天下自己到底是“效忠”哪个entity的。具体语法见下述代码第一行。注意,一个entity其实可以对应多个architecture,然后在例化的时候通过configuration语法进行配置,来决定到底使用哪个architecture所描述的行为。这是一种比较高级的应用,大家可以先不做了解。通常情况下,建议大家还是一个entity对应一个architecture,这种结构几乎能应对所有的设计问题。
architecture <arch_name> of <entity_name> is

begin

end <arch_name>;
从结构上,architecture中的代码主要分为两部分,即上述代码中的part A与part B。

  • A部分位于architecture的is和begin之间,它是architecture的说明部分,主要负责一些声明和定义工作;
  • B部分位于architecture的begin和end <arch_name>之间(<arch_name>可省略),它是architecture的语句部分,主要负责具体的功能实现,并且位于其中的语句都是并发执行的,即与语句的书写顺序无关。
    下面我们来分别介绍一下这两部分里面所包含的内容。

声明与定义部分

顾名思义,这一部分包含两个子内容:一个是声明,一个是定义。
声明包括两个内容:

  • 内容一,指元件声明,即component语法,VHDL与其他语言类似,并不是习惯一个“函数”就完成了所有的功能,所以,在绝大多数时候也是需要通过调用若干个独立的小规模的元件,把它们有机的组合到一起,进而实现一个规模比较复杂的设计。那么什么是元件,元件又保存在什么地方呢?还记得我们之前在library里面提到的work库么,我们利用开发工具每开发一个完整的VHDL文件,都会被自动添加到work库中,而在work库中,每一个VHDL文件所描述的接口和功能的总和其实就是一个元件。 因此你可以想象work库就是一个电子元器件收纳柜,我们编写的VHDL文件就是一个个电阻、电容、运算放大器等大大小小的器件,FPGA芯片就好比电路实验中使用的面包板,我们通过在面包板上面安放电阻、电容等基本元器件,配合适当连线,来完成最终的设计目标。通过观察VHDL基本程序框架中component的语法结构,可以发现,它和entity的语法结构几乎相同,这是因为它们之间其实就是一个声明和定义的关系。所需要注意的是component也必须依附于entity而存在,只不过这个entity是另外一个entity,不能是当前architecture对应的entity,因为这个世界上应该没有哪一个电阻会递归调用自己吧!一个component具体对应的是哪个entity,在component语法第一句中的<component_name>就已明确指出。
  • 内容二,指所需要使用的中间信号量的声明,就好像C语言中实现函数时,函数体内也会有一些局部变量,而这些变量是在函数外部看不到的。
    定义,主要指数据类型的定义,即一些用户自定义数据类型的创建,以适应不同的数据类型需求。除此以外,也可以定义一些函数或者过程,即function或者procedure。

语句部分

Architecture的语句部分可以有三种实现形式:元件例化语句、进程语句和独立语句,下面我们来分别了解。

  • 元件例化语句,即VHDL基本程序框架中的instance语句。我们可以看出,instance和component、entity的语法结构也非常的类似,只不过多了map关键字以及有些符号不同罢了,同样用C语言来类比,它们三个就好比函数的具体调用、函数的声明和函数的定义一样。Entity与architecture之和就是一个元件,component通过指定<component_name>为某个<entity_name>而声明这个元件,最后,instance也通过指定的<component_name>来具体使用特定的元件,就是这样一个关系。前面提到了,instance语法中与entity和component还是有些不同的,这是因为具体例化时需要为元件进行配置和连接,这一操作叫映射,也相当于赋值操作。这种不一样体现在两方面:第一方面,instance的generic map语法结构。还记得我们之前提到的8bits、9bits加法器例子么,对了,就是在instance的generic map语法结构中,对加法器entity中的参数进行重新赋值,从而实现不同的位宽宽度;第二方面,instance的port map语法结构中,通过对元件的各个管脚进行映射,从而将元件和上层设计连接起来。
  • 进程语句,即VHDL基本程序框架中的process语句。前面说过,architecture中的语句都是并发执行的,但是在绝大部分情况下,我们所要表达的逻辑都具有一定的传递关系,这个时候,可能串行的语句执行思路更容易表达清楚我们的想法,尤其是时序逻辑,必须用顺序描述来表示;而且,人脑在编程的时候更多的是采用串行的思路,所以即使是并行的东西也可以被我们抽象出串行的描述。于是就有了process语法。我们可以这样理解,architecture中的若干个process都是并行的,但是每个process中的语句都是串行执行的。每个process由其对应的<sensitive_signals_list>中的任何一个信号触发,触发后,process中的语句将会从begin开始,执行到end process,然后等待下一次触发,这就是process的串行执行过程。不过这里需要强调一点,对于process的串行思想,我们只是可以这么理解,而不要真的这么认为,否则就会犯了混淆软、硬件编程思路的问题。
    顺便一提,从我们对process的描述中,应该可以发现其实process也是一个代码聚类,那么代码聚类中也是可以拥有一些自己的局部变量的,这便是variable,后面会进一步介绍。
    最后,我们来看一下process中的<sensitive_signals_list>,在VHDL的语言中,<sensitive_signals_list>共有三种表现形式,从而也对应了三种最基本的process类型。
    类型一,纯组合逻辑process。这个process里面只描述组合逻辑,<sensitive_signals_list>一般应该包含该process所有的输入信号。如何判断process的输入信号呢?一般来说,process中满足以下三个条件的信号即是一个process的输入信号:
    条件一:出现在赋值等号右边的信号;
    条件二:出现在条件判断中的信号;
    条件三:没有出现在赋值等号左边的信号。
    条件三的作用是剔除掉那些在条件一、二中被错误选择出来的中间信号。不过上述三点其实是写给看代码的人参考的,因为在现实中,写代码的人不可能不清楚自己的process到底使用了哪些输入信号。
    类型二:纯同步时序逻辑process。这个process只描述同步时序逻辑,<sensitive_signals_list>应该只包含该同步时序逻辑的时钟信号即可。
    类型三:具有异步复位的时序逻辑process。这个process只描述具有异步复位的时序逻辑,<sensitive_signals_list>应该只包含异步复位信号和相应时序逻辑的时钟信号。
    具体程序设计时,可以根据情况灵活选择process的三个“纯种”的基本类型来解决问题,不推荐使用“杂种”的process。
  • 独立语句,即architecture语句部分的一些未包含在process中的语句,当然了,它们也必须是支持并行执行的语句,因为有些语句是串行的,不能单独放在architecture下面,例如条件语句if…then…end if。以下是独立语句的两个例子:
    a <= b and c;

h <= a when flag = “00” else
b when flag = “11” else
c;
如果你想知道它们与process的描述到底有什么不同,那么下面给出同等功能的process描述供你对比
process (b,c)
begin
a <= b and c;
end process;

process (a,b,c,flag)
begin
if(flag = “00”)then
h <= a;
elsif(flag = “11”)then
h <= b;
else
h <= c;
end if;
end process;
由此可见,architecture中的独立语句其实就相当于只有一条代码的纯组合逻辑process,只不过对于一些简单的组合逻辑描述起来更加方便罢了。

VHDL基本程序框架范例

在本节的最后,让我们结合一个范例,来复习一下本节的内容。范例共包含两个文件,cellAnd.vhd和cellNand.vhd。
cellAnd.vhd的代码如下:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity cellAnd is
generic ( WIDTH : integer := 8
);
port ( a : in  std_logic_vector(WIDTH-1 downto 0); b : in  std_logic_vector(WIDTH-1 downto 0); c : out std_logic_vector(WIDTH-1 downto 0)
);
end cellAnd;

architecture Behavioral of cellAnd is
begin c <= a and b;
end Behavioral;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

cellNand.vhd的代码如下:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity cellNand is
port ( a : in  std_logic_vector(3 downto 0); b : in  std_logic_vector(3 downto 0); nc1 : out std_logic_vector(3 downto 0); nc2 : out std_logic_vector(3 downto 0)
);
end cellNand;

architecture Behavioral of cellNand is

	COMPONENT cellAnd
	GENERIC (
		WIDTH : integer := 8
	);
	PORT(
		a : IN std_logic_vector(WIDTH-1 downto 0);
		b : IN std_logic_vector(WIDTH-1 downto 0); c : OUT std_logic_vector(WIDTH-1 downto 0)
		);
	END COMPONENT; signal c : std_logic_vector(3 downto 0); signal tmp : std_logic_vector(3 downto 0);
	
begin

	m0: cellAnd
	GENERIC MAP ( WIDTH => 4
	) PORT MAP(
		a => a,
		b => b,
		c => c
	); process (a,b)
	begin
		tmp <= a and b;
		nc1 <= not tmp;
	end process; nc2 <= not c;

end Behavioral;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

这个范例其实是实现了对两个4bits向量a和b按位做与非逻辑运算的功能,nc1与nc2其实功能上是一摸一样的,只不过给大家演示一下不同的实现方法罢了。

VHDL注释语法

VHDL语言的注释语法如下:

即两个连续的短横杠组成了VHDL的注释符号“–”,凡是写在这之后的并且与注释符号同在一行的任何字符,都会被自动忽略,即成为注释。例如:
nc2 <= not c; – this is a not operater
或者
– this is a not operater
nc2 <= not c;
比较遗憾的一点是VHDL只有“–”这一种注释语法,并且这种注释语法是单行有效的。那么如果需要连续注释多行该怎么办呢?举例如下:
– this is a
– not operater
nc2 <= not c;
由此可见,只能使用这种笨办法来完成VHDL的多行语句的注释。因此,如果纯手工的来添加或解除大量VHDL的注释,将会是一件枯燥而繁琐的事。不过好在天无绝人之路,现在,大部分支持VHDL的工具软件都支持自动注释添加功能,即通过选中需要添加注释的若干行,然后点击类似“添加注释”的按钮,即可一次性完成多行的注释添加;同样道理,如果同时选中若干行注释,然后点击类似“解除注释”的按钮,就可一次性完成多行注释的解除,十分方便。
不过需要注意的是,自动添加或解除注释,不适用于这样的情况:
nc2 <= not c; – this comment can not automatically added or removed!

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

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

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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