tag 标签: 代码风格

相关博文
  • 热度 18
    2013-12-31 10:00
    1477 次阅读|
    0 个评论
    引子中提到的那个设计,已经基本完成了。优化后,电路工作的时钟频率提升了一倍左右,还是有明显效果的。   在通向时序收敛的路途中,Xilinx的一些白皮书、《高级FPGA设计-结构、实现和优化》、特权与rickysu等前辈的博文,都对我有很大的帮助。不过,在这一过程中,我还是强烈地感觉到相关的资料太少、太简略。   现在回头想想,能够理解这种现象的原因了。毕竟是进行FPGA设计,完成设计后,大量的工作还是要交给EDA工具。这之中,能够人为干涉的内容有限,干涉的效果也未必好。 改进时序的过程中,主要的工作还是在静态时序分析的基础上优化代码。   因此,我们要做的其实还是:在设计时写出那些能够被EDA工具高效综合、便于EDA工具优化的具有良好代码风格的HDL程序。 在FPGA开发的过程中,我深深地体会到HDL是非常容易被误用的。尤其是在初学的时候,因为对电路的理解不够深,常常写出一些综合效率很低的代码。或许,针对HDL的也需要一本《Effective C++》吧!   在这个部分中,主要介绍一些有利于优化电路时序的代码风格。 这些内容主要来源于我自己对于专著与Xilinx的白皮书的理解,如果有理解错误的地方,还请指正。   1. 复位   能不用复位就不用复位;不能的话,采用同步复位。   从学习FPGA开始,大量的示例代码中,模块都带有一个异步复位端口。这本身没有什么问题:有些电路,比如计数器、状态机,寄存器没有初值的结果是灾难性的;而FPGA中的寄存器单元又具有异步复位的引脚,何乐而不为?   然而,在大型设计中,这却真的会带来一些问题。   首先,如果所有模块都带有复位端口,复位信号就会具有很大的扇出,导致布线拥塞,影响电路的速度。而且大扇出信号到达不同模块的时间,会有很大的差距。   XST本身会对这种扇出极大的信号(时钟、复位等)进行处理:将大扇出的信号接到BUFG上,以缓解大扇出信号的各种问题。不过,ISE映射(MAP)时对连接BUFG的管脚的类型有要求(必须为时钟管脚),否则将报错。如果确实无法采用时钟管脚作为复位管脚,可在UCF文件中添加语句放宽这一限制(具体的语句忘了,如果真出现了这一错误,在ISE的错误报告中会告知解决方案)。   复位的另一个问题,是阻止了综合器可能进行的一些优化,从而同时影响电路的速度与面积。这其中一个典型的例子,就是当移位寄存器具有复位信号时,便无法采用SRL单元实现。其它的这一类问题主要是因为异步复位造成的,将在之后介绍异步复位的内容中介绍。   其实,在FPGA设计中,有许多模块确实不需要复位。 在设计时,认真考虑一下这一点,去掉那些无用的复位信号。   下面来谈谈异步复位的问题。   异步复位的一个常见问题,是亚稳态问题。这一问题在很多资料中均有阐述。简单来说,就是异步复位信号的释放可能正好发生在寄存器的保持/建立时间内。这样,复位是否被释放是一种随机的状态:有些寄存器处于复位状态,而有些寄存器的异步复位信号则已经释放,导致不可重复的随机错误。这种错误如下图所示。     另外,异步复位将阻止综合器对电路进行优化。前文提到过,当有些电路具有复位信号后,综合器就无法对其进行一些优化。而另一种更常见的情况是,综合器能够针对同步复位的电路进行一些优化,而对异步复位的电路,则无法进行这些优化。在Xilinx的白皮书WP231《HDL Coding Practices to Accelerate Design Performance》中,举了一些这类的例子。比如: 同步复位电路能够被综合为性能更好的基于硬核(块RAM、DSP等)的电路,异步复位电路则不能。 综合器能够将同步复位信号与其它信号放在一起优化,而异步复位信号只是复位信号。 综上所述,同步复位要优于异步复位。采用同步复位时出现亚稳态的可能性很小,而且电路可得到更好的优化。   一些资料中提到,与异步复位相比,同步复位需要更多的寄存器(如下图所示)。不过,实际上不用担心这点: 较新的Xilinx器件中的寄存器都具有专门的同步复位端口,不需要额外的寄存器实现同步复位。     2. 条件判断语句   在HDL代码中,尽量不要使用多层嵌套的条件判断语句。   这么做的原因是多层嵌套的代码将被综合为具有优先级的电路。其中,最典型的例子是状态机。   以一个4状态的独热码状态机为例。   以下是代码片段:      reg sta;        always@(posedge clk)      if (sta ) ...;      end else if (sta ) ...;      end else if (sta ) ...;      end else if (sta ) ...;   如果使用上述代码实现状态机,综合出的电路是具有优先级的电路。而如果采用下面的代码实现,并如 PART2 中所述,将【HDL Options】中的【Case Implementation Style】设为【Full-Parallel】,则综合出的电路为并行结构,关键路径明显减小。   以下是代码片段:      reg sta;        always@(posedge clk)      case(1'b1)      sta : ...;      sta : ...;      sta : ...;      sta : ...;      endcase   两者的差别可以参考下面的示意图(A为优先级结构,B为并行结构)。 注意,只是示意图,实际的电路与之有较大差别。     可以看到,在第一段代码中,由于sta 、sta 、sta 、sta 的优先级依次递减,综合而成的优先级电路中有很长的组合逻辑链路,严重影响电路的速度;而第二段代码综合出的是并行电路,其关键路径要短不少。我对8状态的状态机进行了测试,并行电路的关键路径仅有优先级电路的一半。   为什么综合器没有把功能相同的电路优化为相同的结构? 答案是两者的逻辑其实并不相同。 考虑sta为4'b0101的情况,优先级电路的输出与sta为4'b0100时的输出一致;而并行电路的输出则未知(这里指电路是并行结构;如果代码是并行的,但未开启之前所述的【Full-Parallel】选项,也将被综合为优先级电路)。   所以说,综合器并没有问题。如果可能出现4'b0101,确实需要优先级电路,否则电路将出现异常。但是,在独热码状态机中,不会出现4'b0101的情况,使用优先级电路,是一种浪费。   在实际设计时,有时候无法避免多层嵌套的逻辑。 并不是什么时候都有替代的并行电路,该嵌套的时候还是得嵌套,毕竟实现功能是首要的目的。 如果关键路径确实无法满足需求,就需要考虑进行一些电路结构上的变换了。这部分内容,将在 PART4 中阐述。   3. 模块的输入输出寄存器   通常需要用寄存器对模块的输入与输出进行缓存。   对于连接至其它异步电路的输入输出,缓存是必须的。 否则,可能导致错误。此时,用寄存器缓存输入,能够减小亚稳态发生的概率;用寄存器缓存输出,能够消除组合逻辑竞争与冒险带来的毛刺。   如果输入输出连接的是同步电路呢?这时候,不缓存输入输出不会引发错误,但缓存仍有一定的必要性。   一方面,是基于寄存器复制的需求来考虑的。如 PART2 中所述,综合器无法进行跨模块的寄存器复制。未经缓存的输入输出,即使扇出很大,也不会被复制。   另一方面,不缓存输入输出,容易在模块连接处形成关键路径。与缓存输入输出的模块相比,不缓存的模块的输入输出门延时要大不少。虽然也会去注意控制输入输出门延时的大小,但是毕竟外部的情况是未知的,不确定的因素太多。与其如此,倒不如为输入输出添加缓存,将输入输出门延时降到最低。   当然,也不是所有模块都必须缓存输入输出。 比如,只完成子模块连接的顶层模块,就不该缓存输入输出(其实都在子模块内部完成缓存了)。   另外,电路内部的一些同步子模块,如果能够确保扇出不大,并且模块之间不形成关键路径的话,也可以考虑不缓存输入输出。不管怎么说,缓存需要占用资源,还带来一个时钟周期的延迟,并非免费的午餐。   总结 能不用复位就不用复位;不能的话,采用同步复位。 尽量不要使用多层嵌套的条件判断语句。 一般情况下,需要用寄存器对模块的输入与输出进行缓存。
  • 热度 43
    2013-12-24 10:46
    2604 次阅读|
    17 个评论
        观文识人,代码无疑是了解一个程序员的最佳途径。     且不论算法思路,单就代码的界面风格,便可知一二。     1. 追求精美的对齐。 尽可能精确到每一个"断点",是他的追求。               2.没有tab字符,而是采用4个或8个空格键代替。     编辑时敲入的是tab键,但其编辑器会设置"文件保存时tab用空格字符替代" 为何呢,因为这样的代码,无论在那种编辑器上打开,都一定是对齐的。 拷给别人,才不担心形象受损。   下接: 一线研发之声 之 完美主义者的程序员特征 二  
相关资源