现在很多年轻人喜欢用MP3播放器放音乐,这种播放器体积很小,里面只包含一个解码芯片;在它出现之前,我们通常是在PC上,用winamp等软件播放MP3。这种差别正好反映了两种计算模式的不同:前者的专用解码芯片是硬件模式(或者称ASIC模式),后者即软件模式。硬件模式专为特定的应用进行设计与优化,具有较之软件模式高得多的执行速度和更低的功耗,但是设计的周期长,功能不可能轻易改变,即缺少灵活性;软件模式在通用处理器上编程执行,执行速度比较慢,功耗大,但是软件开发过程相对硬件设计要简单得多,而且改变源程序就可以改变功能,因此更加灵活。
通过以上分析容易看出,硬件模式和软件模式有各自的优势,因此它们在各自不同的领域都发挥得很好。然而,正如爱因斯坦晚年研究统一场一样,人类总是希望找到单一的方法就解决目前的所有问题。于是自然而然地——研究者也在寻求一个途径以综合软硬件各自的优势——既能获得很高的计算性能,又能在很短的时间内设计实现并且易于改变功能。
可编程逻辑芯片(广义的概念,包括各种CPLD、FPGA和类似的器件),以及不断发展的IP核技术,在一定程度上适应了上述的需求。下面以FPGA为例,从性能和设计两方面分析。为了利于区分,如无说明,本文中硬件模式均特指ASIC芯片,软件模式指通用处理器,FPGA作为可编程逻辑芯片的特例来叙述。
ASIC包括前端设计和后端设计两个部分,因为现在硬件设计者经常利用FPGA验证ASIC设计的正确性,所以前端设计几乎可以和FPGA设计等价;后端设计特指芯片的物理布局布线。这样一来,因为少掉了物理优化,FPGA不可能达到像ASIC那样高的运行频率和那样低的功耗,除此之外ASIC的优势都被FPGA继承下来,也就是说FPGA的性能介于软硬件模式之间。
FPGA设计一般采用“自顶向下”的方法,即在系统设计的最初,把系统划分为若干模块;划分原则是使得每个模块有较独立的功能,模块之间的耦合尽可能小(通常表现为相互通信尽量简单)。划分之后,再分别实现每个模块,最后把模块像搭积木似的组装起来。某些模块可能已经有现成的,即所谓IP核心。可见系统设计的难点是模块的实现,那么IP核的思想有利于模块的重复使用,提高了设计的效率。再配合成熟的EDA工具作为设计流程的工具链,可以说FPGA的设计已经相当容易。即便如此,这个“组装”的过程,相对使用高级语言的软件编程,仍然是难于设计和缺乏灵活性的。如果把IP核心拿来和编程作对比,我们观察到IP的思想和软件的静态链接库很相似。按照这个思路分析,如果我们希望进一步简化系统的设计,最好把IP核心封装成软件可以调用的库的形式。库又分为静态库和动态库,动态库允许在程序执行时按需加载和卸载,这相当于FPGA在运行的时候,IP核心可以动态的载入和卸出,当然前提是并不破坏原有程序和数据,而目前的EDA工具不可能实现类似的功能。为了进一步提高硬件计算的灵活性,最好使用动态库。把IP核心封装成动态库,是可重构计算平台最为核心的思想。本文不严格区分IP核心和其封装库的概念。我们把IP核封装成函数的形式,利于调用;与此同时,从外部看来,封装成的函数与软件链接库函数并没有差别,因此程序员可以同等地对待它们。一个函数的基本组成部分有函数名、函数体、参数和返回值。函数名唯一性地指代了不同的IP核心,当然这其中可能要使用不同的命名空间,以区分不同的包。函数体是真正实现功能的部分,在这里应该就是指IP核心本身,当然还要包括一些封装所要用到的代码。函数参数是IP核心的输入值,可以接受形参,也可以接受实参。
我认为IP核心本身及其封装并不需要包括实参的互斥机制,这在大多数时间可以保证结果的正确性,而且节约了硬件资源和执行时间。但是前头提到过FPGA上同时运行多个处理器,因此共享变量并发地读写有可能使得执行结果出错,那么程序员在必要的时候应该显式声明临界区域。函数的返回值可以用来传递IP核的输出,但是这样做很不妥当。因为通用处理器的程序是串行执行的,主机调用一个IP核,实际上是调用封装它的函数,那么只有等到这个函数返回以后,主机程序才能继续运行下去。如此一来意味着执行IP核心的时候,主机的CPU就停滞了(虽然分时系统会把CPU资源分配给其他线程,但是很显然这种停滞的开销依然不能接受)。所以调用封装函数以后,我们让调度器同时做两件事,一个是启动IP核心,开始执行硬件任务,另一个就让函数立即返回值,这个是本平台的关键技术。主机收到返回值以后,可以调用其他IP或者执行软件任务,因为该函数不等硬件任务执行就返回,所以函数执行时间远远小于硬件执行时间,也就实现了多个硬件任务之间及软硬件任务之间的时间并行。函数返回的取值有可能包括 enum{ SUCCESS, FAILURE, WAITING} 当中的值,SUCCESS表示调度成功(只是调度,执行有可能失败);FAILURE表示所请求的IP核心永远也不可能被调度,这是个彻底失败的返回值,但是我们不能让整个应用崩溃,此时主机收到FAILURE应当利用软件计算来代替硬件任务;WAITING表示未来某个时间可以调度,当然如果调度器设计得好,那么能够返回具体要等待的时间,反之就不能给出精确的时间,但是至少也应该有一个量级,总而言之,主机可以据此判断是否等待FPGA资源或是直接用软件代替。
当然我们还要考虑到,IP核的输出肯定是要返回的,这个可以从函数的输入实参返回。前面提到主机不等IP核执行结束就继续运行其他程序了,那么IP核执行结束的消息如何通知主机呢?其实这里又是给程序员留了一个灵活的接口,因为有些应用不需要返回值,例如MP3,处理完的数据就直接交给D/A转换器输出,而不需要主机的干涉。但是有些应用又需要通知主机,对于这种情况,调用IP的时候需要传递一个回调函数的指针,此时IP任务结束后调度器会给主机发中断。函数有一个可重入的概念,即这个函数在没有返回的时候就可以再次被调用,而不破坏原先的结果。最典型的例子是递归调用。这个问题对于软件编程并不难解决,只要让每次调用对应不同的内存堆即可。但是硬件任务允许并行执行,而其内部是一个硬件核心,因此不可能递归调用主机程序。也就是说多次调用同一个函数,每次都应该在FPGA上为其分配资源,让该函数并行执行。
FPGA内部各模块间通信应该采用片上总线技术,业界有完备且经过实践验证的协议。使用片上总线的优点有:因为协议规定了数据通路和控制信号,能够容易解决硬件任务互通信,容易解决总线争用。总线上各设备是对等的,任何一个硬件任务都可以成为总线的主控设备,也就是解决了硬件任务发消息的问题。发送数据可以是单播,也可以是多播。成熟的协议有ABUS和xx,前者适合ARM处理器(CPU核心),后者适合PowerPC。
另一个重要问题是FPGA如何和主机通信,一个主机能够带几个FPGA。当然理想的情况是越多越好,目前一些商业化产品,八个或者十六个FPGA的硬件计算平台都有(不是可重构平台)。从可重构技术的角度来看,因为其实运行的硬件任务能够被替换出来,所以并非同时加载全部IP核,需要的FPGA数量应该较小些。但是作为可扩充的系统,至少要有带4~8颗FPGA的能力。这个数量目前还不能确定,因为要作一些实验,考虑主机CPU的负载能力,主机也要运行很多软件任务,太多的FPGA会使主机过载。
因为CPU的IO有限,所以多个FPGA不可能同时连接到CPU上。那么它们之间应该有一个总线协议。最容易想到的是PCI或者更新的PCI-e,使用总线通信的好处和前面类似,就不再赘述,但是我们还要考虑到,采用成熟的片外总线,非常有利于产品的商品化,还有利于用户和原有的系统集成。PCI-e出现的晚,先进一些,传输率高,占用IO少(数据线少),实现也应该复杂一些,具体没有调研。
这样一来,主机CPU和每个FPGA都应该一个PCI控制器。PC的控制器放在主板的芯片组里,但是FPGA的控制器放在哪里,也是值得思考的问题。按照常规的思路,因为FPGA的资源非常多,把控制器放在FPGA内部是没有问题的,但是这样做并不妥。原因有:一个固定的、占用很多资源的控制器,必然使得剩余的FPGA资源分布更加不平衡;控制器是系统必备的,放在FPGA内部有潜在的危险,在用户配置FPGA的时候把控制器破坏,使整个系统崩溃。相反的,如果在FPGA外部加一个专用的控制器(相当于PC的北桥),除了避免以上问题,还能获得额外的利益:专用控制器相当于MAC层,也就是说改变专用控制器的功能,就可以适应更多的接口,除了PCI以外,还可以让FPGA卡通过USB或者FireWire等和主机连接。
文章评论(0条评论)
登录后参与讨论