前言

一言以蔽之,软件编程与FPGA编程之间最本质的区别在于FPGA编程是并行的,而软件编程是串行的,为了更深刻理解这句话,我们看FPGA之道中对这一区别是如何解释的。

名称对比分析

在软件设计中,所采用的语言一般称之为软件设计语言,而在FPGA设计中,所采用的语言一般统称为HDL。HDL,英文全称Hardware Description Language,即硬件描述语言,其中包括两对关键字对比——“软件”对比“硬件”和“设计”对比“描述”。

“软件”对比“硬件”

什么是软件?什么是硬件?这是我们首先应该搞清楚的一件事。
在现实生活中,硬件是具体的、形象的,也是看得见、摸得着的,例如一张桌子、一把椅子。而软件则相反,它没有具体的形态与形象的外观,你看不见它,也摸不着它,但是你却能感受到它,因为软件总是依托于硬件而存在的,你可以通过硬件的行为去感受它。如果以人本身打个比方的话,人的硬件就是指人的身体,包括人的骨骼、大脑、五脏六腑、皮肤等等,而人的软件就是指人的智商、情商、身体协调性、速度、力量等等。
具体到我们的计算机及电子相关领域,软件一般指的就是程序代码,而硬件指的就是具体的电路板、芯片、电子元器件等等。很明显,程序代码是看不见摸不着的(有人说我天天编程,怎么可能看不见代码?其实编程时看到的仅仅是显示器,显示器也是一种硬件载体罢了),你不可能将它捧在手心(因为能捧在手心的只有“优乐美”和你的U盘),但是一旦有了电脑等相关硬件的支撑,程序代码可以发挥出自己的本领,例如视频播放、电子游戏、智能机器人等。
经过上述关于软件、硬件概念的讨论,我们知道软件设计语言针对的对象自然是软件,而从HDL的名称,我们知道HDL语言针对的对象是硬件。那么这个区别就是造成软件编程思路与FPGA编程思路不同的根源所在。

“设计”对比“描述”

“描述”,描写叙述的意思。一般什么情况下我们用这个词?试想你刚刚看到一个美女(或帅哥),这会正在给朋友激动的形容这个美女(或帅哥)头发有多长、眼睛有多大等等,这就叫描述。
“设计”,有目标有计划进行的创作活动叫设计。一般什么情况下我们用设计这个词?试想,你给凤姐整容了,让她变成了世界第一美女,你这就叫设计。
因此,虽然并不能完全割裂开来,但“描述”偏重的是对已知事物的转述,而“设计”偏重的是对未知事物的创造。除此以外,“描述”是可以不十分精确的,但是“设计”是来不得半点含糊的。
例如,我们可以这样形象的来理解软件“设计”与硬件“描述”行为上的不同:
如果我们要用软件设计语言在一个不支持乘法的硬件系统上实现一个乘法功能,那么在编写软件代码的时候我们的思路大概是这样的:第一步应该判断乘数的最高位是否为‘1’,如果是怎么怎么样,如果不是怎么怎么样;第二步应该判断乘数的次高位……;最后一步输出结果到那里等等;每一步都要很具体。
如果我们要用HDL在一个没有硬件乘法器的FPGA芯片上实现一个乘法功能,那么在编写HDL代码的时候我们的思路可以是这样的:这个功能对应的硬件结构相当于一个黑盒,那么这个黑盒有什么特征呢?它有两个输入和一个输出,并且输出等于输入的两个数按照无符号数形式相乘的结果,描述结束!当然,这是一个很极端的描述,它描述的非常含糊,因此至于编译器会用FPGA中什么样的硬件结构来实现乘法功能我们不确定,甚至编译器能不能根据我们的描述做出这样一个乘法功能来我们也不敢保证。因此,软件编程可以天马行空,只要你写的代码符合语法标准,那么你写的出来,PC机就执行的起来,充其量死机而已;而FPGA编程要脚踏实地,不能毫无忌惮的去写代码,如果无法用底层的基本单元完成你所描述的功能,那么一切都白搭。所以对于用HDL编写FPGA设计来说,描述的越具体、越详细、越充分,最终电路的成功性、可行性和可控性就越好,但与此同时,“描述”的工作量和内容也就会越接近“设计”。
最后,结合“软件”与“硬件”的对比,让我们来最终阐明一下“设计”与“描述”的本质区别。通常来说,代码即是程序,程序即是软件,编写代码即是软件创作,那么如果我们写的代码最终转化为软件产品,也就是从软件到软件,这就叫“设计”,因此承载这种功能的语言就叫软件设计语言;如果我们写的代码最终转化为硬件产品,也就是从软件到硬件,那这就叫“描述”,因此承载这种功能的语言就叫做硬件描述语言。

抽象层级对比

软件的执行必须依附于硬件,在编程时,如果需要对软件运行的硬件环境有详细的了解,那么这种代码对应的软件产品一般叫驱动程序;如果不需要对软件运行的硬件环境有太多了解,那么这种代码对应的软件产品一般叫应用程序。不管是驱动程序还是应用程序,它们都是直接或间接的利用它们所基于的硬件资源做事情,而无法改变硬件资源本身的行为和结构,因此我们称之为软件编程。这也是为什么再厉害的计算机病毒程序也无法直接破坏电脑硬件(间接的情况是存在的,例如通过程序控制光驱的不断读写,导致光驱过劳死;通过程序修改cpu或者显卡的一些倍频参数,导致硬件超负荷工作致死;等等),这是因为软件的运行层级比硬件高,“皮之不存毛将焉附”,因此,软件的抽象级别肯定是凌驾于硬件之上的。
而基于FPGA芯片的HDL编程,最终直接改变的是FPGA芯片内部的硬件结构,因此HDL代码的编写是位于硬件层级的,因此抽象级别较软件编程低很多。
因此,编程语言抽象级别的不同,也是造成软件编程思路与FPGA编程思路不同的原因。
最后,多余说明一下,位于抽象层级较高的软件编程与抽象级别较低的FPGA编程之间,还有一种编程的抽象层级,叫做嵌入式编程。嵌入式编程一般运行于单片机、ARM、DSP等微处理器相关硬件产品,它主要还是具有软件编程的特征,但是程序中通过设置也可以对微处理器内部一些灵活的硬件结构等进行模式配置,从而一定程度上改变了硬件的结构。

编译原理对比

同样是代码,为什么一个是软件产品,而一个是硬件产品呢?我们都知道,写出来的代码是不能直接运行的,无论是软件设计语言还是硬件描述语言,写出的代码都需要经过编译,才可以被硬件使用(对于解释型的软件设计语言,相当于实时编译)。因此,要了解软件编程与硬件编程的区别,还需要从代码的编译过程说起。
先看软件设计语言,以C语言程序设计为例。C语言是一种高级程序设计语言,用它编写的程序方便人的理解,但是CPU却不懂。因此,编译的第一步,是按照一定规则,将C语言的代码转换为一种叫汇编语言的中间代码。汇编语言的代码人阅读起来比较困难,但是比较接近CPU的理解方式,不过CPU还是不懂。接下来,编译的第二步,把汇编语言每条语句划分为指令和操作数,然后根据相应的翻译对照表格翻译成为机器语言,俗称“01”代码。机器语言的代码,人几乎无法阅读,但是CPU却可以完全理解。因此,整个编译过程结束后,将以前的C语法代码转换为CPU可以直接执行的机器代码,从代码到代码,本质没有任何改变,所以软件设计语言最终对应的是软件产品。
再看硬件描述语言——HDL。用HDL语言编写的程序方便人的理解,但是FPGA却一点都不懂。因此,编译第一步,将HDL语言转化成为以FPGA内部基本资源模块为基础的门级网表。目前这个阶段我们可以把门级网表理解为一个用基本门电路搭建的数字电路图,这种门级网表,稍微有点规模,人就很难理解,但是却比较符合FPGA的理解方式,不过FPGA还是不懂。接下来,编译第二步,将门级网表的各个资源映射到FPGA芯片内部的具体位置,并却定好连线开关等一些资源的配置状态,进而再生成用于配置FPGA的流文件。这种流文件人无法阅读,但是FPGA却能很好理解,并通过载入该流文件,从而对自己的硬件电路结构进行配置。从代码到FPGA硬件结构配置,所以硬件描述语言最终对应的是硬件产品。

执行方式对比

软件程序的执行方式

尽管现在多核、多线程等概念已经充斥着CPU市场,但是这都无法改变软件程序串行执行的本质。多线程可以简单理解为CPU分时的执行多个任务,而多核可以简单理解为将多个单核CPU集成到一个芯片中。例如以下是C语言的一段代码:
int a; //step 1
a = 100; //step 2
a += 1; //step 3
a *= 2; //step 4
printf(“%d”, a); //step 5
上述代码,必须严格按照从上到下的顺序一步一步执行,最后打印的正确结果应该是202。无论是以后出现再新的名词,再时髦的概念,软件代码的执行永远都必须是顺序的。对于上述例子,在任何时候,都不可能有任意两个步骤颠倒,更不可能有两个步骤并发执行,否则结果肯定会出错(尽管CPU最终执行的是机器语言,但是,我们以上述C语言的源代码来阐述软件的执行过程更加明白易懂些,并且原理上是一致的)。
下面,就详细讲解一下软件的执行方式。通常情况下,上述C代码编译好的可执行文件会存储在电脑的硬盘上,当我们双击该可执行文件后,CPU会将该可执行文件调入计算机内存中,并且为该程序划分一片独占的程序存储区和数据存储区。可执行文件载入内存后,程序指针(简称PC指针)通常会初始化指向程序存储区中的第一行代码,CPU会从根据PC指针的初始值从程序存储段中读取一行代码,然后完成译码、执行等步骤。如果该代码的功能不涉及到程序跳转及中断,那么PC指针默认自增1,然后CPU会读取新的一条指令,继续重复译码、执行等步骤。如果某条指令改变了PC的值,那么CPU下一指令周期便会去读取新PC指针值所对应的指令来进行译码、执行等操作。若PC指针移动到程序段的末尾后,整个程序执行结束,结果可能已经显示在屏幕上或者存储到硬盘上,稍后,可执行文件占用的整个内存空间也会被释放掉。
因此,关于软件程序的执行方式,可以总结为:串行、离散、有限。串行,表示通常每次只能执行一条语句;离散,表示指令顺序执行的时间间隔为若干个指令周期,而指令周期跟CPU主频有关;有限,表示程序的生命周期一般有限,除了至关重要的系统进程外,一般都小于开机时间。

FPGA程序的执行方式

虽然HDL也有自己严格的语法解释,并且也支持串行的语句,但是这主要是为了便于人和仿真器理解所设定的。真正的FPGA程序的执行方式都是并行的,这种并行可不是类似CPU多核概念那么简单。如果说单核CPU同一时刻只能执行一条机器代码的话,那么X核CPU充其量同一时刻也就执行X条机器指令而已,并且这X条机器指令还必须分属于不同的线程。而FPGA的并行执行,举个不太恰当的例子,对于具有1000行功能代码的HDL程序,同一时刻会有1000行代码都在运行。为什么会这样呢,让我们通过一段HDL代码的例子来了解一下:
– VHDL example
a1 <= b1 and b2;
a2 <= c1 or c2;
a3 <= a1 xor a2;

// Verilog example
assign a1 = b1 & b2;
assign a2 <= c1 | c2;
assign a3 <= a1 ^ a2;
这两段代码分别用VHDL和Verilog语法描述了下图这样的一个数字电路结构,因此在FPGA中,最终实现的硬件结构也类似如此。
FPGA之道(6)软件编程思路与FPGA编程思路的变革_软件产品
如果,在该电路的4个输入b1、b2、c1、c2波形如下:
FPGA之道(6)软件编程思路与FPGA编程思路的变革_软件产品_02
那么如果可能的话,将三台示波器的探针分别置于a1、a2、a3处,得到的波形图应该如下:
FPGA之道(6)软件编程思路与FPGA编程思路的变革_软件产品_03
通过上例,关于FPGA程序的执行方式,可以总结为:并行、连续、无限。并行,表示通常HDL代码都是并发执行的;连续,表示HDL代码的执行是无时间间隔的,即无时无刻都在执行;无限,表示HDL代码的生命周期是和系统运行时间一致的。因此,在编写HDL代码的时候,可不是像编写软件代码那样,仅仅考虑和前后句之间的衔接那么简单。
其实,考虑到硬件电路的工作事实,FPGA程序执行方式的特点是显而易见的。

资源占用与释放

针对软件程序与FPGA程序的执行方式对比,还有一点需要说明,那么就是资源的占用与释放。
对于软件程序来说,可以动态的申请与释放资源。对于某一个具体的可执行文件,通过合理的控制,例如及时的释放掉没有用的内存,可以到达整个执行期间对系统存储资源占用率一直比较低的水平。
而对于FPGA程序来说,由于它对应的是FPGA中具体的硬件资源,这就就好比电路板上的电子元器件和芯片一样,你用或不用,它就在那里,不多不少!因此,如果FPGA程序中用到了一个乘法器,又用到了一个加法器,即使不是同时需要用到,那么FPGA程序也必须一直占用着两个资源,只不过不需要用的时候就不去读取相应资源的输出而已。
最后,一个CPU可以同时运行多个软件程序,因此软件程序分享硬件资源;而一片FPGA芯片只能运行一个FPGA程序,因此FPGA程序独占FPGA芯片。