An Introduction To VHDL
Abstract
FPGA(现场可编程逻辑阵列),是一种领先的硬件设计方式,简单地说,用户可以根据需要为开发板设计功能,并且可以随着需求的改变随时改变功能,而对开发板功能的编程,就是由VHDL(高速集成电路硬件描述语言)完成。
由于方便易用的特性,FPGA和VHDL语言在现代嵌入式系统中占据着统治地位,并且在机器学习设计中扮演着越来越重要的作用,FPGA设计师的需求也水涨船高,然而,由于VHDL语言和传统程序设计语言的诸多差异,许多人在接触伊始感到困难。
< XillinxFPGA 设计基础(VHDL版)>是西安电子科技大学出版的一套教材。语言简洁易懂,内容充实,设计合理,注重实验,在实际教学中长期使用。然而,这本书的讲述依然存在着问题,有一些有可能造成困惑的地方,人非圣贤,孰能无过,本文的目的是结合这本经典教材,从一个读者的角度进行梳理,补充和注解,帮助苦于入门的朋友释疑解惑为盼。
顶级抽象:实体和结构体
抽象是计算机科学的基础方法,任何高级语言都将抽象概念作为安身立命之本,例如,C语言中的变量,结构体,Java中的类与实例。VHDL语言作为一种直接定义硬件的高级语言,自然拥有更为庞大的抽象层次1。
实体(entity)就是一个函数,一个明确输入和输出的单元,定义实体时,只需要考虑输入和输出各是什么,不需要考虑输入到输出是如何得到的,这就是计算机科学中著名的黑盒抽象。
构造体(architecture),称之为结构或许更好理解,因为它的功能就是定义黑盒内部的结构,也就是,输入到底是如何变成输出的。
那么,在VHDL中如何定义实体呢?
entity name is
port (A : 数据类型;
B : 数据类型
);
end entity;
可以看到,定义实体就是定义port,也就是输入和输出,在已知输入输出之后,实体还需要一个名字,将其和其他实体区分。
在VHDL中又是如何定义构造体呢?
architecture arc_name of entity_name is
do something to get B from A
end architecture;
构造体具体的编写方式,将在下文学习,目前只需要知道,你需要为构造体赋予一个名字,将其与实体绑定即可。实体和构造体之间可以看作一对多的关系,同一个实体可以拥有不同的构造,因此需要名字将其区分。
次级抽象:组件,实例
在构造体中使用已经编写好的实体时,你需要将初始化为实例,具体有两种方式,一是在库中找到你想使用的实体,直接将其初始化,二是先将实体声明为组件(component),再从组件初始化。
A: entity work.entity_name(arc_name) port map(...);
component entity_name
port(...)
end component
A: entity entity_name(arc_name) port map(...)
其中,port map的作用就是指定实体输入的来源,输出的去向,既可以使用位置对应,也可以使用命名对应。
我们可以看到,第一种方式要比第二种方便得多,因为component这一层抽象本身不是必要的,我们将会在后面考虑这个问题。
次级抽象:信号
我们讨论了在构造体中使用实体,是通过实例化,那么,想象这样一种情况:
这是一个AND_OR器件,AND_OR的四个输入分别成为两个2位AND器的输入,而AND_OR的输出就是二位OR的输出,但是AND的输出呢?在定义U0的时候,是不知道U2的存在的,因此我们没办法直接说:U0的输出是U2的输入。
我们可以发现,原件互相独立的初始化方式,为结构描述带来了困难,因此我们需要一些中间变量,这就是信号(signal)。
signal SIG1:std_logic;
U0:entity MY_AND2 map port(INP(0),INP(1),SIG1)
Plug into PCB:配置和组件
我们在第二部分留下了疑问:为什么需要PCB这样一个看似多此一举的抽象?
为了解决这个疑问,请想象一个PCB电路板:
一个电子器件由电路板上许多小部件组成,也就是我们定义的功能单元:实体,然而你会发现,实体在被插到PCB上之前,首先上了一个套,这个套把内部结构完全屏蔽起来,外界只知道针脚的信息。
而这个套就是组件(component),第二部分中,我们说:先将实体声明为组件,其实这是不确切的,因为实体和组件之间并不是绑定的关系,我们之所以必须为二者取相同的名字和输入输出,只是因为这是默认的匹配方式,当你这样定义之后,如果没有显式写配置语句,编译器就会直接找到库下和组件同名的实体2,将其插入套中。
正如更换CPU,在VHDL中,你可以随时更换组件对应的实体,只要它们的针脚(输入输出)对应。
configuration MUX2_specified_CFG of MUX2 is
for STRUCTURE --对于mux2的structure构造体
for G2 : AOI --对于构造体中G2这一组件
use entity work.AOI(v1); --使用库中AOI(v1)实体
end for;
end for;
end MUX2_specified_CFG;
迄今为止,我们学习了实体,它是完整的功能单元,学习了构造体,它是实体功能的描述,学习了信号,通过它,我们将功能单元自小而大,自底向上的组织起来,学习了配置,它帮助我们有条理地组织和动态更换实体,那么,目前为止已经可以完成很多设计实验了。
编程方式:结构和表现
有时你会希望以结构的方式描述功能3,但有的时候,你很清楚实体应该做什么,将他分解为小的组件却是困难的,例如,仅仅知道真值表,却不知道真值表对应的逻辑表达式。
在这种情况下,表现结构(behavioral architecture)4是适用的,它允许你用if,case,loop等控制语句描述硬件行为,换句话说,如果你使用控制语句而不是硬件结构来描述功能,这就是表现式的,它说明了硬件在什么情况下应该怎么做,却不关心具体怎么实现4。
例如,一个二位数据选择器:
如果你幸运地化简出它的表达式:
Z=!(!(a&&sel)||(b&&!sel))
你可能会这样编写:
selb <= not sel;
fb <= not((a and sel) or (b and selb));
f <= not fb;
晦涩难懂,不是吗?
相反地,采用表现结构的VHDL如下所示:
process (sel, a, b)
begin
if sel = '1' then
f <= a;
else
f <= b;
end if;
end process;
这很简单,它的功能是什么,就怎么编写代码。
一言以蔽之,我们可以用过程(process)替代掉繁琐的实体分解,直接描述功能。同一个实体可能拥有多个过程,只要功能上相互独立,就应该以不同的过程描述。
Last Issue:并行和顺序
一言以蔽之,结构体中的所有元素(<=赋值,process)都是并行的,因为它们都定义了实体一部分独立的功能,而过程中的语句是顺序执行的,因为它们是一个整体。
A <= '0'
B <= A
Process(A)
begin
C <= A
D <= C
end process;
Process(A)
begin
E <= A
F <= C
end process;
Conclusion
在这篇文章中,我们叙述了VHDL最重要的几个概念,这是理解VHDL的重点和难点。
当然,还有很多基础功能是本文没有涵盖的,例如:操作符,generic,变量,为了实际使用VHDL,你必须学习这些内容。
Reference
- < XillinxFPGA 设计基础(VHDL版)> -西安电子科技大学出版社
- VHDL Designer Guide -duolos.com
- Many Great Questions in Stackoverflow