1.3 Altera推荐的Coding Style
当代的可编程逻辑设计日趋复杂,其系统复杂度和设计频率要求与5年前不可同日而语。良好的Coding Style对设计的工作频率,所消耗的芯片面积,甚至整个系统的稳定性都非常重要,而良好规范的Coding Style将更便于设计的移植,如从一个器件族移植到另一个器件族,或者同一器件族不同速度等级之间的移植,还包括Altera FPGA向结构化ASIC的移植等。
随着EDA技术的不断发展,综合、实现工具的优化能力越来越强大,可以自动完成许多复杂设计的面积和时序方面的优化,其优化效果也日趋先进。如果读者盲目依赖综合、实现等EDA工具的优化,而忽略自己设计的代码风格,就大错特错了。因为Coding Style对综合、实现等EDA工具的优化结果的影响可以用这样一句话来概括:好的Coding Style会使综合、实现等优化事半功倍,达到最优化的结果;不良的Coding Style会使综合、实现优化南辕北辙,甚至产生错误的结果。所以读者必须明确:综合、实现等EDA工具的优化能力和正确性最终取决于设计的Coding Style的优劣。
1.3.1 Coding Style的含义
Coding Style,即代码风格。Coding Style分为一般性Coding Style和针对综合工具、实现工具、器件类型的Coding Style。
所谓一般性的Coding Style,是指不依赖于综合、实现工具和器件类型的代码风格。不同的综合实现工具对一些语法细节的阐释略有不同,特别是那些关于优先级,实现的先后顺序等,所以不同的综合工具在个别细节上对Coding Style的解释有一定的差异。本章所述Coding Style,如果没有特别声明,一般都是不依赖于具体EDA工具,一般意义上的Coding Style,适用于不同厂商的综合实现工具和不同厂商的器件族。
作为一般性代码风格的补充,本章另有部分Coding Style特别声明是针对Altera器件的特点和硬件结构的,这种Coding Style是与器件紧密联系的。用Altera推荐的Coding Style能够正确地实例化底层单元模块,合理地使用其固有的硬件结构,以达到最优化的设计效果。
本章的Coding Style主要是基于RTL级(寄存器传输级)而言,并非其他描述层次,所以诸如业界炒得非常热的结构化设计方法(Architectural-based design)的代码风格的原则和方法与本章无关,甚至有很多原则和方法是与本章所述背道而驰的。
1.3.2 结构层次化编码(Hierarchical Coding)
结构层次化编码是模块化设计思想的一种体现。目前大型设计中必须采用结构层次化编码风格以提高代码的可读性,易于模块划分,易于分工协作,易于设计仿真测试激励。最基本的结构化层次由一个顶层模块和若干个子模块构成,每个子模块根据需要还可以包含自己的子模块。结构层次化编码结构如图1-14所示。
图1-14 结构层次化编码示意图
结构层次化编码有以下注意事项。
•结构的层次不易太深,一般为3到5层即可。在综合时,一般综合工具为了获得更好的综合效果,特别是为了使综合结果所占用的面积更小,会默认将RTL代码的层次打平(Flatten)。有时为了在综合后仿真和布局布线后的时序仿真中较方便地找出一些中间信号,比如子模块之间的接口信号等,可以在综合工具中设置保留结构层次,以便于仿真信号的查找和观察。
•顶层模块最好仅仅包含对所有模块的组织和调用,而不应该完成比较复杂的逻辑功能。较为合理的顶层模块由输入/输出管脚声明、模块的调用与实例化、全局时钟资源、全局置位/复位、三态缓冲和一些简单的组合逻辑等构成。
•所有的I/O信号,如输入、输出、双向信号等的描述在顶层模块完成。
•子模块之间也可以有接口,但是最好不要建立子模块间跨层次的接口,如图1-14中模块A1到模块B1之间不宜直接连接,两者需要交换的信号可以通过模块A、模块B的接口传递。这样做的好处是增加了设计的可读性和可维护性。
•子模块的合理划分非常重要,应该综合考虑子模块的功能、结构、时序和复杂度等多方面因素。
结构层次化编码的本质不应该简单地理解为一种具体的设计手段,而应该认识到它其实更是一种系统层次的设计方法。回顾本章1.1.3小节“系统原则”中图1-6所示的“系统规划的简化流程”,层次结构化设计方法对应于从第1步“系统功能定义和逻辑功能划分”到第5步“逻辑模块的划分与模块间接口的定义”之间的所有步骤。
很多初学者都有这样一个疑问:一个设计完全可以在同一个模块内完整描述,为什么还要将其中的时序逻辑和组合逻辑、不同优化目标、不同功能的电路分成多级层次,使用多个模块描述呢?模块划分增加了内部接口描述的工作量,这不是“自讨苦吃”么?
虽然理论上,任何设计都可以在同一个模块中完成,但是如果将不同功能、不同层次、不同类型的电路混淆在同一个模块中,这不是一种好的系统设计方法,对于比较复杂的设计,将会导致整个设计杂乱无章,不利于设计的阅读与维护,也会给综合和实现过程带来许多麻烦。
1.3.3 模块划分的技巧(Design Partitioning)
结构层次化设计方法的第一个要点就是模块划分,模块划分非常重要,关系到能否最大程度上发挥项目成员协同设计的能力,更重要的是它直接决定着设计的综合、实现的耗时与效率。模块划分的基本原则介绍如下。
(1)对每个同步时序设计的子模块的输出使用寄存器(Registering)。
本原则也被称为用寄存器分割同步时序模块的原则。使用寄存器分割同步时序单元的好处有:便于综合工具权衡所分割的子模块中的组合电路部分和同步时序电路部分,从而达到更好的时序优化效果,这种模块划分符合时序约束的习惯,便于利用约束属性进行时序约束。
(2)将相关的逻辑或可以复用的逻辑划分在同一模块内。
该原则有时被称为呼应系统原则。这样做的好处有:一方面将相关的逻辑和可以复用的逻辑划分在同一模块,可在最大程度上复用资源,减少设计所消耗的面积;更利于综合工具优化某个具体功能的时序关键路径,其原因是传统的综合工具只能同时优化某一部分的逻辑,而所能同时优化的逻辑的基本单元就是模块,所以将相关功能划分在同一模块将在时序和面积上获得更好的综合优化效果。
(3)将不同优化目标的逻辑分开。
在介绍速度和面积平衡与互换原则时,谈到合理的设计目标应该综合考虑面积最小和频率最高两个指标。好的设计,在规划阶段设计者就应该初步规划了设计的规模和时序关键路径,并对设计的优化目标有一个整体上的把握。对于时序紧张的部分,应该独立划分为一个模块,其优化目标为“Speed”,这种划分方法便于设计者进行时序约束,也便于综合和实现工具进行优化。例如,时序优化的利器Amplify,以模块为单元进行物理区域约束,从而优化关键路径时序,以达到更高的系统工作频率。另一类情况是设计的矛盾主要集中在芯片的资源消耗上,这时应该将资源消耗过大的部分划分为独立的模块,这类模块的优化目标应该定为“Area”。同理,将它们规划到一起,更有利于区域布局与约束。这种根据优化目标进行优化的方法的最大好处是,对于某个模块综合器仅仅需要考虑一种优化目标和策略,从而比较容易达到较好的优化效果。相反,如果同时考虑两种优化目标,会使综合器陷入互相制约的困境,耗费巨大的综合优化时间也得不到令人满意的综合优化结果。
(4)将松约束的逻辑归到同一模块。
有些逻辑的时序非常宽松,不需要较高的时序约束,可以将这类逻辑归入同一模块,如多周期路径(Multi-cycle Path)等。将这些模块归类,并指定松约束,则可以让综合器尽量节省面积资源。
(5)将存储逻辑独立划分成模块。
RAM、ROM、CAM和FIFO等存储单元应该独立划分模块,这样做的好处是便于利用综合约束属性显化指定这些存储单元的结构和所使用的资源类型,也便于综合器将这些存储单元自动类推为指定器件的硬件原语。另一个好处是在仿真时消耗的内存也会少些,便于提高仿真速度。这是因为大多数仿真器对大面积的RAM都有独特的内存管理方式,以提高仿真效率。
(6)合适的模块规模。
从理论上讲,模块的规模越大,越利于模块资源共享(Resource Sharing)。但是庞大的模块,将要求综合器同时处理更多的逻辑结构,这将对综合器的处理能力和计算机的配置提出较高的要求。另外,庞大的模块划分不利于发挥目前非常流行的增量综合与实现技术的优势。
1.3.4 组合逻辑的注意事项
相对复杂一些的设计都是由两部分组成的,分别为时序逻辑(Sequential Logic)和组合逻辑(Combination Logic)。同步时序设计系统中并不是不包含组合逻辑,而是要更加合理地设计、划分组合逻辑。对于组合逻辑,要注意以下问题。
一、避免组合逻辑反馈环路(Combinational Loops)
组合逻辑反馈环路是PLD设计的大忌,它最容易因振荡、毛刺、时序违规等引起整个系统的不稳定和不可靠。
图1-15所示为一个典型的组合逻辑反馈环路,寄存器的Q端输出直接通过组合逻辑反馈到寄存器的异步复位端,如果Q输出为0时,经组合逻辑运算后为异步复位端有效,则电路进入不断清零的死循环。
图1-15 组合逻辑反馈环路示意图
组合逻辑反馈环路是一种高风险设计方式,主要原因如下。
•组合反馈环的逻辑功能完全依赖于其反馈环路上组合逻辑的门延迟和布线延迟,如果这些传播延迟有任何改变,则该组合反馈环单元的整体逻辑功能将彻底改变,而且改变后的逻辑功能很难确定。
•组合反馈环的时序分析是无穷循环的时序计算,综合、实现等EDA工具迫不得已必须主动割断其时序路径,以完成相关的时序计算,而不同的EDA工具对组合反馈环的处理方法各不相同,所以组合反馈环的最终实现结果有很多不确定因素。
同步时序系统中应该避免使用组合逻辑反馈环路,具体操作方法主要有以下两种。
•牢记任何反馈环路必须包含寄存器。
•检查综合、实现报告的Warning信息,发现Combinational Loops后进行相应修改。
二、替换延迟链(Delay Chains)
延迟链(Delay Chains)是异步时序设计的常用手段,特别是在早期PLD设计和当代ASIC设计中,经常使用Delay Chains实现两个节点间的延迟调整。常见的Delay Chains形式为添加延迟Buffer或利用门延迟,如在ASIC设计领域,常用两个非门调整延迟。
正如本章1.1.4小节所述,当代PLD设计推荐使用同步实现设计方法,一般要避免使用异步的Delay Chains,异步电路带来的隐患参见本章1.1.4小节所述。
在同步时序设计中,取代异步Delay Chains的常用方法是用分频或倍频的时钟或者同步计数器完成所需延迟。当然,根据具体电路实现同步延迟的方法还有很多,需要具体问题具体分析。
三、替换异步脉冲产生单元(Pulse Generator)
在异步时序设计中,常用Delay Chains完成脉冲产生,常用的异步脉冲产生方法如图1-16所示。
图1-16 常用的异步脉冲产生方法示意图
这类异步方法设计的脉冲产生电路的脉冲宽度取决于Delay Chains的门延迟和线延迟,而在FPGA/CPLD中,大多数Timing Driven的综合、布线工具无法保证其布线延迟恒定。另外,PLD器件本身在不同的PVT(工艺、电压、温度)环境下其延时参数也有微小波动,所以脉冲宽度无法可靠地确定。而且STA(静态时序分析)工具也无法准确分析脉冲的特性,为时序仿真和验证带来了很多的不确定性。
异步脉冲序列产生电路(Multi-vibrators)也被称为毛刺生成器(Glitch Generator),利用组合反馈环路振荡而不断产生毛刺。正如前面所述,组合反馈环是同步时序必须避免的,这类基于组合反馈环的Multi-vibrator也会给设计带来稳定性、可靠性等方面的问题,必须避免使用。
同步时序设计脉冲电路的常用方法如图1-17所示。该设计的脉冲宽度不因器件改变或设计移植而改变,恒等于时钟周期,而且避免了异步设计的诸多不确定因素,其时序路径便于计算、STA分析和仿真验证。
图1-17 常用的同步脉冲产生方法示意图
四、避免使用锁存器(Latch)
同步时序设计要尽量避免使用Latch。综合出与设计意图不吻合的Latch结构的主要原因在于:在设计组合逻辑时,使用不完全的条件判断语句,如有IF而没有ELSE,或不完整的case语句等(这仅仅是一种可能,并不一定生成Latch);另外一种情况是设计中有组合逻辑的反馈环路(combinatorial feedback loops)等异步逻辑,一旦产生了Latch,工具将无法正确分析Latch所在的链路的静态时序,同时有可能出现仿真和硬件实测不一致的现象。
典型的生成Latch的Verilog和VHDL语句如下。
Verlog:
reg data_out; always @(cond_1,data_in) begin if (cond_1) data_out <= data_in; end
VHDL:
process(cond1) begin if (cond_1 = ’1’) then data_out <= data_in; end if; end process;
上述描述,由于未指定在条件“cond_1”等于“0”时的动作,一般情况下会生成图118所示的Latch结构。
图1-18 Latch的RTL示意图
防止产生非目的性的Latch的措施如下。
•使用完备的if...else语句。
•检查设计中是否含有组合逻辑反馈环路。
•为每个输入条件设计输出操作,对case语句设置default操作。特别是在状态机设计中,最好有一个default的状态转移,而且每个状态最好也有一个default的操作。
•使用case语句时,特别是在设计状态机时,尽量附加综合约束属性,综合为完全条件case语句(full case)。目前,大多数综合工具支持full case的综合约束属性,具体的语法请参考综合工具的约束属性指南。
仔细检查综合器的综合报告,目前大多数的综合器对所综合出的Latch都会报“warning”,通过综合报告可以较为方便地找出无意中生成的Latch。
1.3.5 时钟设计的注意事项
时钟是同步设计的基础,在同步设计中,所有操作都是基于时钟沿触发的,所以时钟的设计对同步时序电路而言非常重要。组合逻辑设计时钟的方法多种多样,但是这些设计如果直接移植到同步时序电路中会带来各种各样的问题。本小节强调同步时序电路时钟的设计方法,并帮助读者辨析各种其他时钟设计方法的优劣。
一、同步时序电路推荐的时钟设计方法
同步时序电路推荐的时钟使用方式为:时钟经全局时钟输入引脚输入,通过FPGA内部专用PLL(Altera、Lattice多为PLL)或DLL(Xilinx多为DLL)进行分频/倍频(一般可实现小数分频倍频)、移相等调整与运算,然后经FPGA内部全局时钟布线资源(一般为全铜工艺)驱动到达芯片内所有寄存器和其他模块的时钟输入端。这样做的好处有以下3点。
•由全局时钟专用引脚输入,通过PAD的Skew和Jitter等都最小,而且专用全局时钟引脚到PLL和全局时钟驱动资源的路径最短。
•使用PLL或DLL进行分频/倍频、移相等调整,附加Skew和Jitter都最小,而且操作简单,精度高。
•使用全局时钟布线资源驱动,到达芯片内部任何寄存器和其他单元时钟输入端的Skew和u Jitter都最小,而且驱动能力最强。
通过以上3种措施,可以保证同步时序设计的时钟质量最佳,从而保证了设计的稳定性和可靠性。图1-19所示为同步时序电路推荐的全局时钟设计示意图。
图1-19 同步时序电路推荐的全局时钟设计方法
当然,上述情况仅仅是理想情况,实际设计的时钟系统结构要复杂得多,可能会因为专用时钟输入端、PLL/DLL、全局时钟布线资源不够用,或者其他各种具体情况而无法按照理想情况设计时钟,这时有很多变通方法:如某个频率没有从专用时钟引脚输入,则可以使用内部产生的时钟经PLL/DLL分频,然后用全局时钟资源驱动;如使用PLL/DLL不够时,可以使用同步计数器分频;如全局布线资源用尽后,可以使用第二全局布线资源(长线资源)等完成Low Skew的时钟驱动。另外,读者可以参考后面各小节讨论的其他设计方式,选择自己需要的时钟设计方法。
二、Altera器件的时钟和PLL资源
前面已经介绍过,Altera器件推荐使用PLL完成时钟的分频、倍频、移相等操作。Altera器件的PLL使用比较方便,一般都是用EDA辅助工具比如MagaWizard生成IP,然后调用。Altera FPGA不同器件可用的PLL单元不同,使用时需要根据器件类型选择相应的资源。比方说,Arria10时钟资源包含全局时钟GCLK,区域时钟RCLK和外围电路时钟PCLK,而PLL包含分数fPLL和I/O PLL。图1-20和图1-21所示为Arria10器件族GCLK时钟网络和fPLL结构示意图。图1-22所示为Arria10 I/O PLL示意图。Altera PLL的详细讨论参见2.1.2小节“锁相环应用”。
图1-20 Arria10器件族的GCLK时钟网络示意图
图1-21 Arria10器件族的fPLL结构示意图
图1-22 Arria10 I/O PLL示意图
在使用Altera PLL的时候,应该根据自己所选的器件类型,查看该器件可用的PLL资源,然后根据需要设计PLL的参数,通过MagaWizard设置PLL的参数并生成IP Core,在代码中调用即可。整个使用过程的关键在于弄清Altera PLL的常用参数含义。关于MagaWizard的使用方法详见《Intel FPGA/CPLD设计(基础篇)》第4章“Altera的IP工具”的介绍。下面强调一下不同时钟域数据交换时,使用Altera PLL时钟的注意事项。在不同时钟域间交换数据时,除了需要注意本章1.2.4小节“异步时钟域数据同步”所述注意事项外,还要注意如下问题。
(1)同步同型时钟域之间的数据交换。
如果两个PLL时钟是由同一个PLL产生的同类型的时钟(如都是全局时钟,或者都是区域性时钟),则这两个时钟域之间的register-to-register数据交换最为简单,不需要附加任何逻辑。因为从前面“异步时钟域数据同步”中我们已经知道,在这种情况下,使用一级寄存器采样就能完成数据的同步化过程,不会产生错误电平和亚稳态的传播。
(2)同步异型时钟域之间的数据交换。
如果两个时钟信号是由不同的PLL产生,或者是不同类型的时钟(一个是全局时钟,一个是区域性时钟),且它们由无延时或相位差的同样的时钟源来驱动,则在进行数据交换时,数据路径上必须插入至少一级LE(LogicElement,逻辑单元),以保证图1-23所示的两个寄存器能够通过本地互连线资源连接。
图1-23 同步异型时钟域之间的数据交换,需要插入LE
(3)异步时钟域之间的数据交换。
如果是完全异步的时钟域间的数据交换,请按照本章1.2.4小节“异步时钟域数据同步”所述方法,使用DPRAM或FIFO完成,可以简单地绕过异步时钟域数据同步的握手和错误电平或亚稳态等问题。图1-24所示为使用DCFIFO完成异步时钟域之间的数据交换的范例。
图1-24 使用DCFIFO完成异步时钟域之间的数据交换
对于一些低频、低扇出、低精度要求的时钟,采用同步计数器或状态机完成分频运算也是可以接受的,但是必须要注意使用同步计数器或在时钟输出端插入寄存器,以完成毛刺的过滤。
三、时钟布线资源
前面讲过,对于有高扇出、高精度、低Jitter、低Skew的时钟信号应尽量使用全局时钟资源驱动,如果全局时钟资源不够,还可以使用其他快速布线资源。Altera FPGA中包含丰富的快速布线资源,如Arria10系列器件,除了有专用全局时钟网络(Global Clock)外,还有区域时钟网络(Regional Clock)、外围时钟网络(PeripheryClock)等时钟布线与驱动资源和内部逻辑产生的时钟。
如果需要使用内部逻辑产生时钟,必须要在组合逻辑产生的时钟后插入寄存器,如图1-25所示。如果直接使用组合逻辑产生的信号作为时钟信号或异步置位/复位信号,会使设计不稳定。这是因为组合逻辑难免产生毛刺,这些毛刺到达一般数据路径,在经过寄存器采用后一般影响不大,但是作为时钟信号或异步置位/复位信号时,如果毛刺的宽度足以驱动寄存器的时钟端或异步置位/复位端,则必将产生错误的逻辑操作;即使毛刺的宽度不足以驱动时钟端或异步置位/复位端,也会带来寄存器的不稳定,甚至激发寄存器产生亚稳态。所以对于时钟路径,必须插入寄存器以过滤毛刺。
图1-25 内部时钟设计必须插入寄存器
另一方面,组合逻辑产生的时钟还会带来另外一个问题,组合逻辑电路的Jitter和Skew都比较大,如果时钟产生逻辑的延迟比数据路径的延迟更大,会带来负的Skew,负的Skew对同步逻辑设计而言是灾难性的,所以使用组合逻辑产生内部时钟仅仅适用于时钟频率较低、时钟精度要求不高的情况。另外,这类时钟应该使用全局布线资源或者第二全局布线资源之类的快速布线资源布线,而且需要对组合逻辑电路附加一定的约束条件(如单路径的最大延迟等),以确保时钟质量。
四、Ripple Counter
Ripple Counter,即行波计数器,其结构为:一组寄存器级联,每个寄存器的输出端接到下一级寄存器的时钟管脚,这种计数器常常用于异步分频电路。早期的PLD设计经常使用Ripple Counter以节约芯片资源。由于Ripple Counter是一种典型的异步时序逻辑,正如本章1.1.4小节“同步设计原则”所述,异步时序逻辑会带来各种各样的时序问题,在同步时序电路设计中必须严格避免使用Ripple Counter。对于这点有疑问的读者请参考本章1.4.1小节的论述。
五、时钟选择
在通信系统中,为了适应不同的数据速率要求,经常要进行时钟切换。有时为了节约功耗,我们也会把高速时钟切换到低速时钟,或者进行时钟休眠操作。时钟切换的最佳途径是使用FPGA/CPLD内部的专用Clock MUX和ALTCLKCTRL,这些MUX的反应速度快,锁定时间短,切换瞬间带来的冲击和抖动小。Cyclone III和Stratix IV系列的FPGA中都有这类资源。
如果所需器件没有专用的Clock MUX,应该尽量满足以下几点。
•时钟切换控制逻辑在配置后将不再改变。
•时钟切换后,对所有相关电路复位,以保证所有寄存器、状态机和RAM等电路的状态不会锁死或进入死循环。
•所设计系统对时钟切换过程发生的短暂错误不敏感。
六、门控时钟
门控时钟即Gated Clock,如图1-26所示,是IC设计的一种常用减少功耗的手段。通过Gating Logic的控制,可以控制门后端的所有寄存器不再翻转,从而非常有效地节约功耗。
使用ALTCLKCTRL来实现门控时钟,可以有效避免毛刺并使时钟的Skew(偏斜)、Jitter(延时)最小。
图1-26 门控时钟
虽然Altera提出,如果非要使用门控时钟减少功耗的时候,可以使用图1-27所示的电路完成类似门控时钟的功能,但是笔者仍强烈建议读者不要使用该图所示的门控时钟改进电路。虽然这个改进电路已经在较大程度上解决了门控电路产生的毛刺,但是这个电路工作的前提是时钟源Clock的占空比(Duty Cycle)是非常理想的50%,如果时钟的占空比不能保证50%,则会产生许多有规律的毛刺信号。另外这个电路还有一个前提,那就是Clock与Enable信号的布线Skew为0,否则也会造成宽度为Skew的毛刺。
图1-27 门控时钟改进电路
如果功耗真的成为了设计的首要问题,建议采用其他方法减少功耗。如最近发展起来的低核电压FPGA(Core电压为0.9V)、FPGA休眠功能、动态部分重构技术和Clock MUX等技术,选择这些新技术器件能有效地节约芯片功耗。
七、时钟同步使能端
大多数如寄存器等同步单元都支持时钟的同步使能(Synchronous Clock Enable)。需要注意的是虽然使能无效时这些单元无输出,但是这种方法并不能如Gated Clock一样减少功耗。使用Synchronous Clock Enable却能非常方便地完成一些逻辑功能,通过使用Synchronous Clock Enable端完成某些逻辑功能,有时可以节约芯片面积并提高设计频率。
图1-28上半部分所示的同步使能功能,在目前大多数的器件上可以直接将使能信号连接到芯片的同步使能端实现,如图1-28下半部分所示。
图1-28 同步使能端
1.3.6 全局异步复位资源
目前大多数FPGA都具有专用的全局异步复位/置位资源,这类资源通常使用专用的低Skew、低Delay布线资源,直接到达寄存器、Block RAM等底层单元,以保证高性能的复位/置位效果。Altera器件中一般使用全局时钟网络驱动全局同步或异步复位/置位信号。全局同步或异步复位/置位信号的应用比较复杂,不是所有的情况下都推荐使用,但是在一般情况下使用全局同步或异步复位/置位信号可以简化设计、节约芯片面积。
1.3.7 判断比较语句case和if...else的优先级
一般来说,case语句是“平行”(Balance,Parallel)的结构,所有的case的条件和执行都没有“优先级”,而“if...else”在大多数情况下是有优先级(Prior)的。建立优先级结构(优先级树)会消耗大量的组合逻辑,如果能够使用case语句的地方,推荐使用case替换if...else结构。做两点补充:首先,if...else也可以写出不带优先级的“平行”结构的条件判断语句。事实上,if...else可以描述所有的条件判断逻辑,完全可以取代case语句。其次,随着现在综合工具的优化能力越来越强,大多数情况下可以将不必要的优先级树优化掉。
1.3.8 使用Pipelining技术优化时序
Pipelining,即流水线时序优化方法,其本质是调整一个较长的组合逻辑路径中的寄存器位置,用寄存器合理分割该组合逻辑路径,从而降低对路径的Clock-To-Output和Setup等时间参数的要求,达到提高设计频率的目的。必须要注意的是,使用Pipelining优化技术只能合理地调整寄存器位置,而不应该凭空增加寄存器级数,所以Pipelining有时也被称为Register Balance。
目前一些先进的综合工具能根据用户参数配置,自动运用Pipelining技术,通过用寄存器平衡设计中的较长组合路径(Register Balance),在一定程度上提高设计的工作频率。这种时序优化手段对乘法器、ROM等单元效果显著。
1.3.9 模块复用与Resource Sharing
前面已经讨论了如何在系统层次上复用硬件模块,并通过【例1-4】举例说明。“硬件原则”是站在宏观的角度分析,而本节的模块复用和Resource Sharing主要站在微观的角度观察节约面积的问题。为了便于理解,首先我们看两个例子。
【例1-5】Verilog Resource Sharing的例子,一个补码平方器。
这是一个补码平方器的例子,输入是8bit补码,求其平方和。由于输入是补码,因此当最高位是1时,表示原值是负数,需要按位取反,加1后再平方;当最高位是0时,表示原值是正数,直接求平方。
下面是两种描述方式,请读者判断一下优劣,并体会Resource Sharing的含义。
第一种实现方式。
module resource_share (data_in,square); input [7:0] data_in; //输入是补码 output [15:0] square; wire [7:0] data_bar; assign data_bar = ~data_in + 1; assign square=(data_in[7])? (data_bar*data_bar) : (data_in*data_in); endmodule
第二种实现方式。
module resource_share (data_in,square); input [7:0] data_in; //输入是补码 output [15:0] square; wire [7:0] data_tmp; assign data_tmp = (data_in[7])? (~data_in + 1) : data_in; assign square = data_tmp * data_tmp; endmodule
仔细观察一下可以发现:第一种实现方式需要两个16bit乘法器同时平方,然后根据输入补码的符号选择输出结果,其关键在于使用了两个乘法器,选择器在乘法器之后;第二种实现方法,首先根据输入补码的符号换算为正数,然后做平方,其关键在于选择器在乘法器之前,仅仅使用了一个乘法器,节约了资源。第二种实现方式与第一种实现方式相比节约的资源有两部分:第一部分,节约了一个16bit乘法器;第二部分,后者的选择器是1bit判断8bit输出,而前者的1bit判断16bit输出。
两种代码的硬件结构示意图如图1-29和图1-30所示。
图1-29 未Resource Sharing,两个乘法器的实现方案
图1-30 Resource Sharing,1个乘法器的实现方案
细心的读者也许已经注意到,本例所示的结构示意图是Synplify Pro综合后提取的RTL视图,这里需要强调以下几点。
(1)本例综合选用的工具是Synplify Pro 9.6.2,目标器件为Altera Cyclone III EP3C80C6 F780,并关闭了“Resource Sharing”等所有优化参数。第一种实现方法所占逻辑资源为236ATOMs,第二种实现方法所占逻辑资源为112ATOMs。两者所占资源相差一倍以上!
(2)上例资源共享的单元是乘法器,通过Resource Sharing,节省了一个乘法器和一些选择器占用的资源。其实如果拓展一下思维,将乘法器换成加法器、除法器等,甚至推广到任何一个普通的模块或后续结构含有选择器,都可以使用本例的设计思想,通过Resource Sharing成倍地节省前级模块所消耗的资源。
(3)不同的综合工具、同一综合工具的不同版本、不同的优化参数、不同厂商的目标器件、同一厂商的不同器件族等因素都可能造成不同的综合结果。
(4)目前很多综合工具都有“Resource Sharing”之类的优化参数,选择该参数,综合工具会自动考察设计中是否有可以资源共享的单元,在保证逻辑功能不变的情况下,进行Resource Sharing以获得面积更小的综合结果。例如,上例中,打开Synplify Pro的“Resource Sharing”综合优化参数,Synplify Pro会自动运用资源共享的优化算法,其综合结果将与第二种代码描述的综合结果完全一致。
(5)最后需要强调的是,不能因为综合工具的优化能力增强,而片面依靠综合工具,放松对自己Coding Style的要求。第一,综合工具的优化力度毕竟有限,很多情况下不能智能地发现需要Resource Sharing的逻辑;第二,前面已经说过,“不同的综合工具、同一综合工具的不同版本、不同的优化参数、不同厂商的目标器件、同一厂商的不同器件族等因素”都会直接影响综合工具的优化能力和效果,所以依靠综合工具的优化能力不十分可靠;第三,在ASIC设计中,综合工具非常忠于用户意图,这时Coding Style更加重要。所以逻辑工程师必须注意自己Coding Style方面的修养并不断提高。
1.3.10 逻辑复制
逻辑复制是一种通过增加面积而改善时序条件的优化手段。逻辑复制最常使用的场合是调整信号的扇出。如果某个信号需要驱动后级的很多单元,换句话说,也就是其扇出非常大,为了增加这个信号的驱动能力,必须插入很多级Buffer,这样就在一定程度上增加了这个信号路径的延时。这时我们可以复制生成这个信号的逻辑,使多路同频同相的信号驱动后续电路,平均到每路的扇出变低,不需要加Buffer也能满足驱动能力的要求,这样就节约了该信号的路径时延,如图1-31所示。
图1-31 用逻辑复制改善扇出
需要说明的是,现在很多综合工具都可以自动设置最大扇出值(Max Fanout),如果某个信号的扇出值大于最大扇出,则该信号自动被综合工具复制。最大扇出值和器件的工艺密切相关,其合理值应该根据器件手册的声明和工程经验设置。这里举例用逻辑复制手段调整扇出,达到优化路径时延仅仅是为了讲述逻辑复制的概念,其实逻辑复制还有其他很多形式,例如,香农扩展(Shannon Expansion)等时序优化技术,后面将会有详细的介绍。
有的读者会有疑问,逻辑复制和资源共享是两个矛盾的概念,既然使用了资源共享优化技术,为什么还要做逻辑复制呢?
其实这个问题的本质还是面积与速度的平衡。逻辑复制与前面的Resource Sharing是两个对立统一的概念,Resource Sharing的目的是为了节省面积资源,而逻辑复制的目的是为了提高工作频率。当使用逻辑复制手段提高工作频率的时候,必然会增加面积资源,这是与资源共享相对立的方面;正如前面介绍的面积与速度的对立统一一样,逻辑复制和资源共享都是要到达设计目标的两种手段,一个侧重于速度目标,一个侧重于面积目标,两者存在一种转换与平衡的关系,所以两者又是统一的。
首先看下面的一个例子。
【例1-6】一个加法器的资源共享例子。
这个例子和前面乘法器的例子非常相似,只是将平方器换成了加法器。实现这个加法器也有两种代码写法,对应两种不同的硬件结构,如图1-32所示。
图1-32 资源共享示例
第一种写法,对应左边的RTL结构示意图。
assign data_out= (sel)? (a+b) : (c+d) ;
第二种写法,对应右边的RTL结构示意图。
wire temp1,temp2; assign temp1 = (sel)? (a) : (c) ; assign temp1 = (sel)? (a) : (c) ; assign data_out = temp1+temp2;
第一种写法比第二种省了一个加法器,但是严格来讲,第一种写法比第二种写法耗时略少一些,这在本例还不算十分明显,当运算模块不是加法器而是一些较复杂的逻辑时会比较明显。当设计的时序满足要求或设计的面积紧张时,我们一般会采用资源共享的优化方法,将第一种设计转换为第二种设计,但是在某些特殊情况下,时序非常紧张,我们会反其道而行之,将第二种设计转换为第一种设计,从而便于调整组合逻辑信号的到达时间,提高这个加法选择器的工作频率。
1.3.11 香农扩展运算
前面已经讲到,香农扩展(Shannon Expansion)也是一种逻辑复制、增加面积、提高频率的时序优化手段。
其定义如下,布尔逻辑可以做如下扩展。
从上面的定义可以看到,香农扩展即布尔逻辑扩展,是卡诺逻辑化简反向运算,香农扩展相当于逻辑复制,可以提高频率;而卡诺逻辑化简相当于资源共享,可以节约面积。
香农扩展通过增加MUX,从而缩短了某个优先级高,但是组合路径长的信号的路径时延,从而提高了该关键路径的工作频率。通过下面的例子,读者会对香农扩展有一个更全面的理解。
【例1-7】使用香农扩展优化组合逻辑时序。
设所需运算的逻辑表达式为:
F = ((({8{late}}| in0) + in1)= = in2)& en;
其中信号in0、in1和in2都是8bit的数据,信号late和信号en是控制信号,信号late是本逻辑运算的关键路径信号,延时最大。
使用香农扩展:
F = late.F(late=1) + ~late.F(late=0)
= late.[((({8{1'b1}}| in0) + in1)= = in2)& en] +
~late.[((({8{1'b0}}| in0) + in1)= = in2)& en]
= late.[(8'b1+in1)= = in2)& en] + ~late[((in0 + in1)= = in2)& en]
这相当于一个以late为选择信号,以[(8'b 1+in1)= = in2)& en]和[((in0 + in1)= = in2)&en]为两个输入信号的2选1的MUX。因此,late信号的优先级被提高,其信号路径的延时降低,但是其代价是设计的面积增加了,并且需要两个比较运算符。
未使用香农扩展的Verilog代码描述如下。
module un_shannon (in0,in1,in2,late,en,out); input [7:0] in0,in1,in2; input late,en; output out; assign out = ((({8{late}}| in0) + in1) == in2) & en; endmodule
使用Synplify Pro 9.6.2综合,选择目标器件为Altera Cyclone III EP3C80C6 F780,所得到的RTL视图如图1-33所示。
从图中可以清晰地看到,未使用香农扩展时,从输入PAD late到输出PAD out之间共有4个逻辑单元,5段路径。其综合结果使用了1个8bit或门,1个8bit输入加法器,1个8bit比较器,1个2输入与门。不选择任何优化参数,用Synplify Pro在上述目标器件上综合,并用Quartus II实现,其结果为共使用了22个LE,最差的tpd时间约为13ns。
图1-33 未使用香农扩展前的逻辑表达式对应的RTL视图
使用香农扩展的Verilog代码如下。
module shannon_fast (in0,in1,in2,late,en,out); input [7:0] in0,in1,in2; input late,en; output out; wire late_eq_0,late_eq_1; assign late_eq_0 = ((in0+in1) == in2) & en; //assign late_eq_0 = ((({8{1'b0}}| in0) + in1) == in2) & en; assign late_eq_1 = ((8'b1+in1) == in2) & en; //assign late_eq_1 = ((({8{1'b1}}| in0) + in1) == in2) & en; assign out = (late) ? late_eq_1 : late_eq_0; endmodule
为了方便读者理解,在此一并提供对应的VHDL代码。
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_signed.all; entity shannon_fast is port( in0,in1,in2 : in std_logic_vector (7 downto 0); late,en : in std_logic; out1 : out std_logic); end shannon_fast; architecture arch_shannon_fast of shannon_fast is signal late_eq_0,late_eq_1,late_0,late_1 : std_logic; begin late_0 <= '1' when ((("00000000" or in0) + in1) = in2) else '0'; late_eq_0 <= late_0 and en; late_1 <= '1' when ((("11111111" or in0) + in1) = in2) else '0'; late_eq_1 <= late_1 and en; out1 <= late_eq_1 when (late = '1') else late_eq_0; end arch_shannon_fast;
同样,使用Synplify Pro 9.6.2综合,选择相同的器件目标为Cyclone III EP3C80F780C6,所得到的RTL视图如图1-34所示。
图1-34 香农扩展后的逻辑表达式对应的RTL视图
在图中可以清晰地看到,使用香农扩展后,从输入PAD late到输出PAD out之间共有1个逻辑单元,2段路径。其综合结果使用了2个8bit输入加法器,2个8bit比较器,2个输入与门和1个2输入选择器。采用默认参数,用Synplify Pro在上述目标器件上综合,并用Quartus II实现,其结果为共使用了27个LE,最差的tpd时间约为12.7ns。
虽然使用不同的器件族,综合结果的工作频率不一致,但是从RTL视图可以清晰地看到,采用香农扩展后,对于late信号这一关键路径,消除了3个逻辑层次,从而在一定程度上提高了设计的工作频率。作为提高工作频率的代价,多用了1个加法器和选择器,消耗了更多的面积。由于本例十分简单,因此多消耗的LUT和缩短的路径时延都不十分显著,如果在复杂设计中运用香农扩展,就会取得更加显著的效果。
正如前面反复强调的面积和速度的平衡关系所述,是否使用香农扩展时序优化手段,关键要看被优化对象的优化目标是面积还是路径。
1.3.12 信号敏感表
几乎所有的Coding Style指导手册都有关于信号敏感表的论述。时序逻辑的信号敏感表比较好写,在信号敏感表写明时钟信号的正负触发沿即可,关于信号敏感表的主要问题集中在组合逻辑的信号敏感表的写法。在此,我们仅仅强调一下组合逻辑的信号敏感表的相关要点。
•正确的信号敏感表设计方法是将操作进程(Verilog的always block或VHDL的process block)中使用到的所有输入信号和条件判断信号都列在信号敏感表中。
•希望通过信号敏感表的增减完成某项逻辑功能是大错特错的。
•不完整的信号敏感表会造成前仿真结果与综合、实现后仿真结果不一致。
•一般综合工具对于不完整的信号敏感表的默认做法是,将处理进程中用到的所有输入和判断条件信号都默认添加到综合结果的信号敏感表中,并对原设计代码敏感表中遗漏的的信号报警告(warning)信息。
有些初学者发现,在信号敏感表中增减一些信号会得到不同的仿真结果,于是企图依靠修改信号敏感表而方便地完成某些逻辑的设计,这种做法是大错特错的。其实一般综合工具的默认操作都是将操作进程(Verilog的always block或VHDL的process block)中使用到的所有输入信号和条件判断信号都当做触发信号,综合到信号敏感表中,所以增减信号敏感表,其实得到的综合结果完全一致。而增减信号敏感表,得到的仿真结果不同是由仿真器的工作机制决定的,大多数仿真器是数据流和时钟周期驱动的,如果信号敏感表中没有某个信号,则无法触发和该信号相关的仿真进程,从而得到的仿真结果不同。
1.3.13 状态机设计的一般原则
状态机是逻辑设计中最重要的设计内容之一,通过状态转移图设计手段可以将复杂的控制时序图形化表示,分解为状态之间的转换关系,将问题简化。使用HDL语言高效、完备、安全地描述状态机,在一定程度上是一件体现代码功底的设计项目。下面是一些适用于Verilog和VHDL等HDL语言编写状态机的一般性原则。
(1)选择状态机的编码方式。
Binary、gray-code编码使用最少的触发器,较多的组合逻辑,而one-hot编码反之。由于CPLD更多地提供组合逻辑资源,而FPGA更多地提供触发器资源,因此CPLD多使用gray-code,而FPGA多使用one-hot编码另一方面,对于小型设计使用gray-code和binary编码更有效,而大型状态机使用one-hot更高效
在代码中添加综合器的综合约束属性或者在图形界面下设置综合约束属性,可以比较方便地改变状态的编码。需要注意的是,Synplicity、Synopsys、Exemplar和QIS(Quartus Integrated Synthesis)等综合工具关于FSM的综合约束属性的语法格式各不相同。
FSM分为两大类,米勒型(Mealy)和摩尔型(Moore)。组成要素有输入(包括复位)、状态(包括当前状态的操作)、状态转移条件和状态的输出条件。
(2)两段式状态机的设计方法。
设计FSM的方法和技巧多种多样,但是总结起来有两大类:第一种,将状态转移和状态的操作、判断等写到一个模块(process、block)中;另一种是将状态转移单独写成一个模块,将状态的操作和判断等写到另一个模块中(在Verilog代码中,相当于使用两个“always”block)。
其中较好的方式是后者,因为FSM和其他设计一样,最好使用同步时序方式设计,其好处不再累述,而状态机实现后,状态转移是用寄存器实现的,是同步时序部分。状态的转移条件的判断是通过组合逻辑判断实现的,之所以第2种比第一种编码方式合理,就在于第二种编码将同步时序和组合逻辑分别放到不同的程序块(process block)中实现。这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。
将FSM所有的逻辑用一个状态机描述有如下缺点:时序约束、更改、调试等问题,而且不能很好地表示米勒FSM的输出,容易写出Latch,容易出错。
(3)初始化状态和默认状态。
一个完备的状态机(健壮性强)应该具备初始化状态和默认状态。当芯片加电或复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。需要注明的一点是,大多数FPGA有GSR(Global Set/Reset)信号,当FPGA加电后,GSR信号拉高,对所有的寄存器、RAM等单元复位/置位,这时配置于FPGA的逻辑并未生效,所以不能保证正确地进入初始化状态。在使用GSR企图进入FPGA的初始化状态,常常会产生种种不必要的麻烦。一般的方法是采用异步复位信号,当然也可以使用同步复位,但是要注意同步复位逻辑的设计。解决这个问题的另一种方法是将默认的初始状态的编码设为全零,这样GSR复位后,状态机自动进入初始状态。
另一方面,状态机也应该有一个默认(default)状态,当转移条件不满足,或者状态发生了突变时,要能保证逻辑不会陷入“死循环”。这是对状态机健壮性的一个重要要求,也就是常说的要具备“自恢复”功能。对应于编码就是对case和if…else语句要特别注意,尽量使用完备的条件判断语句。在VHDL中,当使用case语句时,要使用“When Others”建立默认状态;使用if...then...else语句时,要在else指定默认状态;在Verilog中,使用case语句时要用default建立默认状态,使用if...else语句也要力求完备。
另外提一个技巧:大多数综合器都支持Verilog编码状态机的完备状态属性──full case。这个属性用于指定将状态机综合成完备的状态,如Synplicity的综合工具Synplify/Synplify Pro、Amplify和Synopys的综合工具FPGA Compiler II等。
Synplicity综合工具还有一个关于状态机的综合属性,叫“// synthesis parallel_case”,其功能是检查所有的状态是“并行的”(parallel),也就是说在同一时间只有一个状态能够成立。
不是所有的FSM都必须设计成为full case和parallel case,有时这样做会影响状态机的功能,并会比用二进制编码或gray码耗费更多的资源。
(4)指定默认输出值。
对状态机的所有输出变量指定一个默认的输出值,这样做的好处是能够防止无意生成的Latch。另外所有的输出最好用寄存器打一拍,以获得更好的时序环境和状态的稳定。
(5)状态机输出逻辑复用。
如果在状态机中有多个状态都会执行某项操作,则在状态机外部定义这个操作的具体内容,然后在状态机中仅仅调用这个操作的最终输出值即可,这个做法就相当于前面所述的“Resource Sharing”技巧。例如,使用Verilog语言描述状态机,可以将输出逻辑独立出来,单独写一个部分。如果一定要混合写入描述状态转移的组合逻辑中,最好将输出逻辑写成一个“task”任务,而在每个状态转移的case语句调用这个“task”任务。
1.3.14 Altera Megafunction资源的使用
通过对《Intel FPGA/CPLD设计(基础篇)》第4章“Altera的IP工具”的学习,我们已经认识到Altera的Megafunction是重要的设计输入资源。由于Megafunction是基于Altera底层硬件结构最合理的成熟应用模块的表现,因此在代码中尽量使用Megafunction这类IP资源,不但能将设计者从烦琐的代码编写中解脱出来,更重要的是在大多数情况下Megafunction的综合和实现结果比用户编写的代码更优。
Megafunction包括Altera的参数化模块库(LPM,Library of Parameterized Modules)、器件专有的Megafunction模块、用Altera MegaCore IP生成工具调用的IP Core及Altera Megafunction计划协作者(AMPP,Altera Megafunction Partners Program)提供的第三方IP Core。
特别是对于一些与Altera器件底层结构相关的特性,必须通过Megafunction实现,例如,一些存储器模块(DPRAM、SPRAM、FIFO、CAM等)、DSP模块、LVDS驱动器、PLL、高速串行收发器(SERDES)和DDR输入/输出(DDIO)等。另外一些如乘法器、计数器、加法器、滤波器等电路虽然也可以直接用代码描述,然后用通用逻辑资源实现,但是这种描述方法不但费时费力,在速度和面积上与Megafunction的实现结果仍然有较大差距。
Megafunction的使用方法主要有两种,一是使用MegaCore/MegaWizard调用参数化的IP和底层模块,另一种方法是直接在代码中实例化LPM。关于Altera IP资源的详细分类和使用方法请参考《Intel FPGA/CPLD设计(基础篇)》第4章的论述。
1.3.15 三态信号的设计
Altera推荐在设计顶层中所有的双向总线(既做输出又做输出的总线)定义为三态信号。必须注意的是,禁止在除顶层以外的其他子层次定义双向端口,除非是顶层中直接驱动双向端口的,禁止赋值高阻态Z。下面分别为Verilog和VHDL代码定义三态信号的范例。
Verilog定义三态信号范例。
module tristate(myinput, myenable, mybidir); input myinput, myenable; inout mybidir; assign mybidir = (myenable ? myinput : 1'bZ); endmodule
VHDL定义三态信号范例。
library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_arith.all; entity tristate is port ( mybidir: inoutstd_logic; myinput: instd_logic; myenable: instd_logic ); end tristate; architecture rtl of tristate is begin mybidir <= 'Z' when (myenable = '0') else myinput; end rtl;
1.3.16 加法树的设计
这里介绍加法树的设计方法主要是想告诉读者:根据所选的器件结构,适当地调整设计以充分利用底层宏单元,可以取得最佳的综合与实现效果。Altera不同器件的底层硬件资源有一定的差异,像45nm,28nm和20nm器件族,与之前的其他器件族相比,甚至连基本逻辑单元都完全不同。45nm,28nm和20nm器件族的底层逻辑单元是ALM,它可以灵活配置为宽度为3到8输入的LUT,与传统4输入LUT相比灵活得多,非常适合实现一些宽输入或多种组合输入的组合逻辑结构。ALM的最大特点是兼有了“窄”逻辑结构的高利用率和“宽”逻辑结构的高性能。
《Intel FPGA/CPLD设计(基础篇)》第2章2.1.1小节较为详细地介绍了ALM的硬件结构与优势,从该节的举例中,读者可以联想到,用组合宽度输入的ALM结构可以更高效地实现一个加法树结构。下面简单地讨论一下Altera提供的在不同器件结构上使用2加数加法器或3加数加法器的范例。
目前大多数FPGA都是基于LE结构的,一个LE由一个4输入查找表(4-LUT)加上一个触发器组成,如Altera的Stratix、APEX、Cyclone和FLEX 10K等器件族都是标准的基于LE的结构。在这类器件上实现如A+B+C 3个加数的加法器,最高频率的方法是先实现其中两个数的加法,如A+B,将和用寄存器打一拍,然后将寄存器的和与第3个被加数(C)相加。这种方法实现1bit加法器时,A+B相加将使用1个加法器和1个寄存器,即1个LE,需1级逻辑完成。这种思路通常被称为2输入加法树结构。将加法树逐级拓展,可以实现更长的加法树结构。然而如果实现A+B+C+D+E 5个加数的加法器,使用4-LUT则需4个上述的1bit加法器,共需要3级寄存器缓存。实现这种2输入加法树结构的代码如下。
Verilog HDL Binary Tree Example
module binary_adder_tree(A, B, C, D, E, CLK, OUT); parameter WIDTH = 16; input [WIDTH-1:0] A, B, C, D, E; input CLK; output [WIDTH-1:0] OUT; wire [WIDTH-1:0] sum1, sum2, sum3, sum4; reg [WIDTH-1:0] sumreg1, sumreg2, sumreg3, sumreg4; // Registers always @ (posedge CLK) begin sumreg1 <= sum1; sumreg2 <= sum2; sumreg3 <= sum3; sumreg4 <= sum4; end // 2-bit additions assign sum1 = A + B; assign sum2 = C + D; assign sum3 = sumreg1 + sumreg2; assign sum4 = sumreg3 + E; assign OUT = sumreg4; endmodule
使用2输入加法器结构实现上述16bit宽、5个加数的加法树,如选择目标器件为Stratix IV EP4SGX230KF40C3,使用Synplify Pro 9.6.2的综合结果为:默认约束条件下,共需64个LE,综合估算时钟频率为478.4MHz。使用Quartus II 9.0实现结果为:默认约束条件下,共需64LE,即64个FF和64个4-LUT,实现估算的时钟频率为422.1MHz。图1-35所示为在Synplify Pro 9.6.2中综合得到的RTL视图。
图1-35 Altera Stratix IV器件同步复位结构视图
由于Stratix IV的ALM可以配置成6输入LUT,使用6-LUT可以同时完成3bit加法,如果用2个3输入的加法器取代上述的4个2输入加法器,实现上述16bit的5个加数求和的加法树,则仅需2级寄存器缓冲。如果在Stratix II中实现这个加法树,仍然使用上述代码描述的2输入加法树结构,其实现效率必然降低,造成一定程度的ALM资源浪费。在Stratix IV中,应该将每级加数变为3个,使用3输入加法树结构,则能有效地节约ALM资源。改进后的代码如下。
Verilog HDL Ternary Tree Example
module ternary_adder_tree(A, B, C, D, E, CLK, OUT); parameter WIDTH = 16; input [WIDTH-1:0] A, B, C, D, E; input CLK; output [WIDTH-1:0] OUT; wire [WIDTH-1:0] sum1, sum2; reg [WIDTH-1:0] sumreg1, sumreg2; // Registers always @ (posedge CLK) begin sumreg1 <= sum1; sumreg2 <= sum2; end // 3-bit additions assign sum1 = A + B + C; assign sum2 = sumreg1 + D + E; assign OUT = sumreg2; endmodule
使用3输入加法器结构实现上述16bit宽、5个加数的加法树,如选择目标器件为Stratix IV EP4SGX230KF40C3,使用Synplify Pro 9.6.2的综合结果为:默认约束条件下,共需32个half-ALM,综合估算时钟频率为274.3MHz。使用Quartus II 9.0实现结果为:默认约束条件下,共需32个寄存器,23个ALM,实现估算的时钟频率为230.8MHz。图1-36所示为在Synplify Pro 9.6.2中综合得到的RTL视图。
图1-36 Altera Stratix器件同步复位结构视图
利用ALM的6-LUT形态,在Stratix IV中使用3输入加法树结构,可以较大幅度地节省寄存器资源和ALM资源。本例所有源代码、Synplify Pro综合工程及Qautus II实现工程在本书附带光盘中都有范例,请读者使用相应的工具打开工程,对比加法树结构及综合实现结果。