(一)流驱动和调用式
架构设计是每个FPGA工程师都要面临的第一关。经常有这样的项目,需求分析,架构设计匆匆忙忙,号称一两个月开发完毕,实际上维护项目就花了一年半时间。主要包括几个问题,一,性能不满足需求。二,设计频繁变更。三,系统不稳定,调试问题不收敛。
磨刀不误砍柴工,FPGA设计的需求分析是整个设计第一步。如何将系统的功能需求,转换成FPGA的设计需求,是FPGA架构设计的首要问题。首先, 需要明确划分软件和硬件的边界。软件主要处理输入输出、界面显示、系统管理、设备维护。而FPGA则负责大数据流的处理。
如果使用几百元FPGA实现了一个十几元单片机就能完成的功能,就算实现的非常完美,那么这是一个什么样的神设计?任何一个项目都要考虑成本,研发成本、物料成本、维护成本等等。FPGA的使用位置必定是其他器件难以企及的优势。
因此对于一个FPGA架构设计,其首先需要考虑就是性能,如没有性能的需求,其他的处理器ARM就可能替代其功能。其次就是接口,用于处理器扩展其没有的接口,作为高速接口转换。最后,需要考虑就是可维护性,FPGA的调试是非常耗时的,一个大型的FPGA的编译时间在几小时甚至更高(通过嵌入式分析仪抓信号,每天工作8小时,只能分析两到三次)。而软件调试只需make,编译时间以秒来记(这个问题可以通过提升编译服务器性能改善而不能消失,本质上要考虑可测性设计)。如果不考虑维护性和可测性,调试成本和压力就非常之大。
通常,FPGA的大部分架构设计可以采用数据流驱动的方式来实现,例子1,假设一个实现视频解压缩FPGA的设计,输入是无线接口,输出为显示屏。那么输入输出的接口基本就能确定。以数据流为驱动可以粗略划分,输入接口->解压缩模块->视频转换模块->显示接口。如需要视频缓冲,则确定是否需要连接外部存储器。那就需要确认在什么位置进行数据的缓冲。通过要支持显示的画面的质量,就能确认最大码流,同样可以计算视频解压模块和转换模块的计算能力,从而导出所需的内部总线宽度,系统频率,以及子模块个数等等。例子2,某支持通过有线电视网上网电视IP网关,同样也是输入的普通IP网络,输出为有线调试网的调试解调器。将IP报文等长填充后,在固定时隙内送入有线电视网中,同样也是基于数据流驱动的方式。
数据流驱动式架构,可以作为FPGA设计中一个最重要的架构。通常来说应用于IP领域、存储领域、数字处理领域等较大型FPGA设计都是数据流驱动式架构,主要包括输入接口单元,主处理单元,输出接口单元。还可能包括,辅助处理单元、外部存储单元。这些单元之间一般采用流水式处理,即处理完毕后,数据打包发完下一级处理。其中输入输出可能有多个,此时还需要架构内部实现数据的交换。
另一种较为常用的架构方式为调用式架构,即一般FPGA通过标准接口如PCI、PCI-E,CPCI,PCI-X,EMIF等等。各种接口,FPGA内部实现某一加速单元,如视频加速,数据处理,格式转换等操作。这种结构基本基本围绕FPGA接口、加速单元展开,属于数据的反馈类型,即处理完数据又反馈回接口模块。
其他虽然各型各样,如SOPC,如各型接口,但本质上其都是为上述架构服务的,或做配置管理替代外部CPU,或在数据流中间传递中间参数。或在内部实现CPU+协处理器的架构,因此说,无他变化。
孙子兵法云:“兵无常势,水无常形”。但是对于一种设计技术来说,没有一种固定演进的架构和设计,那么项目的整个设计层次总是推到重来,从本质上说,就是一种低水平重复。如果总结规律,提炼共性,才能在提升设计层次,在小作坊中取得大成果。
(二)稳定压倒一切
敏捷开发宣言中,有一条定律是“可以工作的软件胜过面面俱到的文档”。如何定义可可以工作的,这就是需求确定后架构设计的首要问题。而大部分看这句话的同志更喜欢后半句,用于作为不写文档的借口。
FPGA的架构设计最首先可以确定就是外接接口,就像以前说的,稳定可靠的接口是成功的一半。接口的选择需要考虑几个问题。
1, 有无外部成熟IP。一般来说,ALTERA和XILINX都提供大量的接口IP,采用这些IP能够提升研发进度,但不同IP在不同FPGA上需要不同license,这个需要通过代理商来获得(中国国情,软件是不卖钱的)。
2, 自研接口IP,能否满足时间、进度、稳定性、及兼容性的要求。
案例1: 设计一个网络接口在逻辑设计上相对简单,比如MII接口等同于4bit数据线的25MHZ样,而RGMII可以使用双沿125Mhz的采样专用的双沿采样寄存器完成(使用寄存器原语)。但是如何支持与不同PHY连接一个兼容性问题(所谓设计挑PHY的问题,这个问题后面详述)。
案例2: CPU通过接口连接FPGA时,如果CPU此时软复位,则有管脚会上拉,此时如果该管脚连接FPGA接口是控制信号且控制信号高电平有效,则此时FPGA逻辑必然出错。同样FPGA在配置时,管教输出高阻,如此时CPU上电且板级电路管脚上拉,则同样会导致CPU采样出错(误操作的问题)。
不能只是考虑编写verilog代码仿真能对就行,接口设计应该站在系统的角度来看问题,问题不是孤立的,还是互相联系。
设计中,如果需要存储大量数据,就需要在外部设计外部存储器,这是因为FPGA内部RAM的数量是有限的。是采用SRAM、DDR2、DDR3。这就需要综合考虑存储数据大大小,因为SRAM的容量也有限,但是其接口简单,实现简单方便,且读取延时较小。DDR2、DDR3的容量较大,接口复杂,但FPGA内部有成熟IP可用,但是读取的延时较大,从发起读信号到读回数据一般在十几个时钟周期以上。如果对数据时延有要求,需要上一次存储数据作为下一次使用,且数据量不太大(几百K到几兆),则SRAM是较好的选择。而其他方面DDR2/DDR3是较好的选择。为什么不用SDRAM或者DDR。这是因为设计完毕,采购会告诉你,市场上这样老的芯片基本都停产了。
FPGA接口在设计选择的原则就是:能力够用,简单易用。特别值得一提的是高速SERDES接口,最好使用厂商给的参考设计,有硬核则不选择软核,测试稳定后,一定要专门的位置约束,避免后面添加的逻辑拥挤后影响到接口时序,也可避免接口设计人员与最终的逻辑设计人员扯皮(不添加过多逻辑,接口是好用的)。一个分析高速SERDES的示波器,采样频率至少20G甚至更高以上,动辄上百万,出现问题,不一定有硬件条件可调试。
回到开头,如何定义“可用的”设计,稳定我想是前提,而接口的稳定性更是前提的前提。这里稳定包括,满负荷边界测试,量产、环境试验等一系列稳定可靠。而在架构设计中,就选择成熟的接口,能有效的避免后续流程中的问题,从源头保证产品的质量。
(三)时钟和复位
接口确定以后,FPGA内部如何规划?首先需要考虑就是时钟和复位。
时钟:根据时钟的分类,可以分为逻辑时钟,接口时钟,存储器时钟等;
(1)逻辑时钟取决与逻辑的关键路径,最终值是设计和优化的结果,从经验而不是实际出发:低端FPGA(cyclone spantan)工作频率在40-80Mhz之间,而高端器件(stratix virtex)可达100-200Mhz之间,根据各系列的先后性能会有所提升,但不是革命性的。
(2)接口时钟,异步信号的时序一般也是通过FPGA片内同步逻辑产生,一般需要同步化,即接口的同步化采样。某些接口的同步时钟一般是固定而精确的,例如下表所示,如SERDES的时钟尽量由该BANK的专用时钟管脚输入,这样可保证一组SERDES组成的高速接口时钟偏斜一致。
(3)外部存储器时钟:这里时钟主要为LPDDR/DDR2/DDR3等器件的时钟,一般来说FPGA的接口不用工作在相应器件的最高频率。能够满足系统缓存数据的性能即可,但是一般这些IP的接口都规定了相应的最小时钟频率,因为这些接口状态机需不停进行外部器件的刷新(充电),过低的频率可能会引起刷新的问题,造成数据丢失或者不稳定。
(4)另外一些需要输出的低速时钟,例如I2C、MDIO、低速采样等操作,可以通过内部分频得到。不用通过PLL/DCM产生所需时钟。在XILINX的FPGA中,禁止PLL产生的时钟直接输出到管脚上,而ALTERA的器件可以如此操作。解决此类问题的方法可通过ODDR器件通过时钟及其180度相位时钟(反向)接入的时钟管脚分别采样0、1逻辑得到。
因为有了DCM/PLL这些专用产生时钟的器件,似乎产生任意时钟输出都是可能是,但实际例化的结果,时钟的输出只能选取某些范围和某些频率,取决于输入时钟和分频系数,CLK_OUT = CLK_IN *(M/N) 。这些分频系数基本取整数,其产生的频率也是有限的值。
复位:根据复位的分类,FPGA内部复位可以分为硬复位,逻辑复位、软复位等;
硬复位:故名思议,即外部引脚引入的复位,可以在上电时给入,使整个FPGA逻辑配置完成后,能够达到稳定的状态,这种复位重要性在于复杂单板上除了FPGA外,可能还有多个器件(CPU、DSP),其上电顺序不同,在未完成全部上电之前,其工作状态为不稳定状态。这种复位引脚可以通过专用时钟管脚引入,也可通过普通I/O引入,一般由单板MCU或者CPLD给出。
逻辑复位:则是由FPGA内部逻辑产生,例如可以通过计数产生,等待一段时间开始工作,一般等待外部某些信号准备好,另一种FPGA内部逻辑准备好的状态信号,常见的有DCM/PLL的LOCK信号;只有内部各逻辑准备好后,FPGA才能正常工作。另外FPGA内部如设计逻辑的看门狗的话,其产生的复位属于这个层次。
软复位:严格的说,应属于调试接口,指FPGA接收外部指令产生的复位信号,用于复位某些模块,用于定位和排除问题,也属于可测性设计的一部分。例如FPGA通过EMIF接口与CPU连接,内部设定软复位寄存器,CPU通过写此寄存器可以复位FPGA内部单元逻辑,通过写内部寄存器进行软复位,是复杂IP常用的功能接口。调试时,FPGA返回错误或无返回,通过软复位能否恢复,可以迅速定位分割问题,加快调试速度。
复位一般通过与或者或的方式(高电平或、低电平与),产生统一的复位给各模块使用。模块软复位信号,只在本模块内部使用。
问题:同步复位好、还是异步复位好? XILINX虽然推荐同步复位,但也不一概而论,复位的目的是使整个系统处于初始状态,这根据个人写代码经验,这些操作都可以,前提是整个设计为同步设计,时钟域之间相互隔开,复位信号足够长,而不是毛刺。下面推荐一种异步复位的同步化方式,其电路图如下:
时钟和复位基本上每个模块的基本输入,也是FPGA架构上首先要规划的部分,而不要用到才考虑,搞的整个设计到处例化DCM或者输出LOCK进行复位,这些对于工程的可维护性和问题定位都没有益处。《治家格言》说:“宜未雨而绸缪,毋临渴而掘井。这与FPGA时钟和复位的规划是同一个意思。
(四)并行与复用
FPGA其在众多器件中能够被工程师青睐的一个很重要的原因就是其强悍的处理能力。那如何能够做到高速的数据处理,数据的并行处理则是其中一个很重要的方式。
数据的并行处理,从结构上非常简单,但是设计上却是相当复杂,对于现有的FPGA来说,虽然各种FPGA的容量都在增加,但是在有限的逻辑中达到更高的处理能力则是FPGA工程师面临的挑战。常用并行计算结构如下图所示:
上图中:前端处理单元负责将进入数据信息,分配到多个计算单元中,图中为3个计算单元(几个根据所需的性能计算得出)。然后计算单元计算完毕后,交付后端处理单元整合为统一数据流传入下一级。如果单个计算单元的处理能力为N ,则通过并行的方式,根据并行度M,其计算能力为N*M;在此结构中,涉及到几个问题:
一,前端处理单元如何将数据分配到多个计算单元,其中一种算法为round-robin,轮流写入下一级计算单元,这种方式一般使用用计算单元计算数据块的时间等同。更常用的一种方式,可以根据计算单元的标示,即忙闲状态,如果哪个计算单元标示为闲状态,则分配其数据块。
二,计算单元和前后端处理之间如何进行数据交互。一般来说,计算单元处理频率较低,为关键路径所在。前后端处理流量较大,时钟频率较高,因此通过异步FIFO连接,或者双端口RAM都是合适的方式。如果数据可分块计算,且块的大小不定,建议使用FIFO作为隔离手段,同时使用可编程满信号,作为前端处理识别计算模块的忙闲标示。
三,如果数据有先后的标示,即先计算的数据需要先被送出,则后端处理模块需要额外的信号,确定读取各个计算模块的顺序。这是因为:如果数据等长,则计算时间等长,则先计算的数据会先被送出。但是如果数据块不等长,后送入的小的数据块肯能先被计算完毕,后端处理单元如果不识别先后计算的数据块,就会造成数据的乱序。这可以通过前端计算单元通过小的FIFO通知后端计算单元获知首先读取那个计算单元输出的数据,即使其他计算单元输出已准备好,也要等待按照顺序来读取。
数据的并行处理是FPGA常用的提升处理性能的方法,其优点是结构简单,通过计算单元模块的复用达到高性能的处理。缺点,显而易见就是达到M倍的性能就要要耗费M倍逻辑。
与之相反减少逻辑的另一种方式,则是复用,即一个处理能力较强的模块,可以被N的单元复用,通过复用,而不用每个单元例化模块,可以达到减少逻辑的效果,但控制复杂度就会上升。其结构图如下所示:
上图复用的结构图中,分别介绍了流过模式复用和调用模式复用。流过模式下,计算单元处理多路数据块,然后将数据块分配到多路上,这种情况下,通过round-robin可以保证各个通路公平机会获得计算单元。其处理思路与上图描述并行处理类似。
调用模式下,计算单元被多个主设备复用,这种架构可以通过总线及仲裁的方式来使各个主设备能够获取计算单元的处理(有很多成熟的例子可供使用,如AHB等)。如果多个主设备和多个计算单元的情况,则可以不通过总线而通过交换矩阵,来减少总线处理带来的总线瓶颈。
实际应用场合,设计的架构都应简单实用为好,交互矩阵虽然实用灵活,但其逻辑量,边界测试验证的难度都较大,在需要灵活支持多端口互联互通的情况下使用,可谓物尽其用。但如果仅仅用于一般计算单元能力复用的场景,就属于过度设计,其可以通过化简成上述两种简单模式,达到高速的数据处理的效果。
并行和复用,虽然是看其来属性相反的操作,但其本质上就是通过处理能力和逻辑数量的平衡,从而以最优的策略满足项目的需要。设计如此,人生亦然。
(五)数字电路的灵魂-流水线
流水线,最早为人熟知,起源于十九世纪初的福特汽车工厂,富有远见的福特,改变了那种人围着汽车转、负责各个环节的生产模式,转变成了流动的汽车组装线和固定操作的人员。于是,工厂的一头是不断输入的橡胶和钢铁,工厂的另一头则是一辆辆正在下线的汽车。这种改变,不但提升了效率,更是拉开了工业时代大生产的序幕。
如今,这种模式常常应用于数字电路的设计之中,与现在流驱动的FPGA架构不谋而合。举例来说:某设计输入为A种数据流,而输出则是B种数据流,其流水架构如下所示:
每个模块只负责处理其中的一部分,这种处理的好处是,1、简化设计,每个模块只负责其中的一个功能,便于功能和模块划分。2,时序优化,流水的处理便于进行时序的优化,特别是处理复杂的逻辑,可以通过流水设计,改善关键路径,提升处理频率,并能提升处理性能。
各个流水线之间的连接方式也可通过多种方式,如果是处理的是数据块,流水模块之间可以通过FIFO或者RAM进行数据暂存的方式进行直接连接、也可以通过寄存器直接透传。也可通过某些支持brust传输的常用业界标准总线接口进行点对点的互联,例如AHB,WISHBONE,AVALON-ST等接口,这种设计的优点是标准化,便于模块基于标准接口复用。每个模块的接收接口为从接口(SLAVE),而发送接口为主接口(MASTER)。
架构流水的好处一目了然,但另一个问题,对于某些设计就需要谨慎处理,那就是时延。对于进入流水线的信息A,如果接入的流水处理的模块越多,其输出时的时延也越高,因此如对处理时延要要求的设计就需要在架构设计时,谨慎对待添加流水线。架构设计时,可以通过处理各个单元之间的延时估计,从而评估系统的时延,避免最终不能满足时延短的需求,返回来修改架构。
流水架构在另种设计中则无能为力,那就是带反馈的设计,如下图所示:
图中,需要处理模块的输入,需要上一次计算后的结果的值,也就是输出要反馈回设计的输入。例如某帧图像的解压需要解压所后的上一帧的值,才能计算得出。此时,流水的处理就不能使用,若强行添加流水,则输入需等待。
如上图中,如在需反馈的设计中强加流水,则输入信息Ai需要等待Ai-1处理完毕后,再进行输入,则处理模块1,就只能等待(空闲)。因此,问题出现了,流水线等待实际上就是其流水处理的的效果没有达到,白白浪费了逻辑和设计。
流水应用在调用式的设计中,可以通过接口与处理流水并行达到。即写入、处理、读出等操作可以做到流水式架构,从而增加处理的能力。
流水是FPGA架构设计中一种常用的手段,通过合理划分流水层次,简化设计,优化时序。同时流水在模块设计中也是一种常用的手段和技巧。这将在后续重陆续介绍。,流水本身简单易懂,而真正能在设计中活用,就需要对FPGA所处理的业务有着深刻的理解。正如那就话,知晓容易,践行不易,且行且珍惜。