Design Compiler指南——后综合过程

       本文我们着重讨论使用Design Compiler综合大型设计时要注意的一些问题,比如怎样调整综合方法,出现约束违反后怎样修正,怎样给不同的子模块作时序和负载预算,以及给整个设计在具体综合之前先作一个预估(Design Exploration)等等。

一、编译一个大型设计

        对于一个大型设计而言,由于模块规模的扩大,编译时间也相应的变长,要长达几个小时甚至超过一天,这样的时间对于讲究”Time to market”的设计者是比较重要的。因此就更加注重编译的技巧,本节我们主要讨论下面三个方面的技巧——

  • 编译层次化设计的技巧
  • 第二次(Second-pass)编译技巧
  • characterize

1、层次化编译

        对一个大型设计来讲,有两种层次化编译技巧——自上而下(Top-down)以及自下而上(Bottom-up)。自上而下的方法是指将整个设计一次性读入,施加顶层约束后直接进行编译;自下而上的方法则先一个个编译比较底层的子模块,给它们加入时序和负载预算,然后在顶层将各个子模块整合起来。

1)自上而下(Top-down)

Design Compiler指南——后综合过程

        上图是自上而下编译方法的具体步骤,可以看出假如顶层设计是RISC_CORE这个模块,则先直接将它读入,然后处理多次例化的模块,施加顶层约束后就直接编译。它的代码基本上如下所示 

Design Compiler指南——后综合过程

        自上而下的编译方法有一个明显的优点,即它使得设计者无需考虑各个子模块之间的依赖关系,也就不需要制定子模块之间的时序和负载预算,这一切都由Design Compiler自动考虑。另外,使用这种方法也使得设计者编写脚本变得简单,维护起来也比较方便。 

        在介绍自上而下的编译方法的时候,我们还要顺便提及DC编译的一种模式——Simple Compile Mode(简单编译模式)。这种模式在设计没有严格的约束的情况下能取得较快的编译速度,另外多例化模块的处理也自动进行。下面是RISC_CORE的Simple Compile Mode脚本:

Design Compiler指南——后综合过程

        可见,使用这种模式省去了uniquify这句,同时编译之前要先设置一个变量set_simple_comile_mod。 

2)自下而上(Bottom-Up)

        自下而上的编译方法其步骤如下图所示        

Design Compiler指南——后综合过程

        和前一种方法不同,自下而上的编译方法需要先单独编译各个子模块,在编译子模块的同时要考虑到与其它模块之间的关系,看是否满足约束,然后再读入顶层文件,施加顶层约束,顶层编译完成之后还必须看顶层约束是否满足。下面是单个模块编译的脚本:

Design Compiler指南——后综合过程

        下面是顶层模块编译脚本:

Design Compiler指南——后综合过程

         从上面的过程不难看出Bottom-Up方法的一些特点:

        优点是利用了”分而治之”的策略,这对于大型的不可能一次编译的设计是十分有用的;另外它也摆脱了Top-down方法的对工作站硬件条件的限制,使得大型设计也能在一般的机器上编译完成。

        缺点是实现步骤比较多,尤其对各个模块之间的时序和负载预算要求很高,如果不注意会很容易造成违反。

        综合上述两种方法,我们可以做一个小结:对于规模不算太大的设计,我们推荐使用Top-down的编译方法,这样可以在不长的时间内得到满意的结果。对于其他需要Bottom-Up的设计,我们必须确认时序负载预算能很好的反映实际的工作情况。

2、第二次(Second-pass)编译

         第二阶段(Second-Pass)编译是指当第一阶段(First-Pass)编译出现违反之后,分析违反原因从而重新编译的过程,对应的还有第零(Zero-Pass)阶段编译。

        关于第二阶段编译,前面的编译策略中有比较详细的介绍,上一节介绍的Top-down的第二阶段编译的步骤主要有

  • 检查模块划分
  • 检查约束脚本
  • 用更高的map_effort编译——compile –inc –map_effort high

        这一节中,我们主要讨论用Bottom-Up方法编译后出现违反的情况

1)重新编译顶层模块

        这种方法是在Bottom-Up出现顶层模块时序违反的情况下采用的,具体的命令如下:

dc_shell > compile  -top

        这个命令仅仅修正顶层子模块之间的路径,因此速度会比compile –inc更快。

2)修正设计预算(Design Budget)

        设计预算对于Bottom-Up的方法来说是至关重要的,在预算的时候,我们都尽量能收紧(Tighten)每一个子模块时序、负载和驱动的预算,例如我们在最初介绍设计预算的时候,举的例子是给本模块留整条路径的40%,因此模块之间能够空出20%的裕量。在编译子模块的时候能尽量做到满足预算的要求。这样最后整合顶层设计的时候就不会出现大的问题。

        如果出现问题了,一个方法就是调整预算脚本。看看施加的约束是否与综合后的电路相吻合。下一节,我们将介绍调整设计预算的一个很有用的命令——characterize。

3、characterize

        Characterize这个命令用于映射到门级的子模块,作用是计算出该子模块周围的环境(延时、负载和驱动),并将得到的实际值作为该子模块的新的约束。如下图一个例子

Design Compiler指南——后综合过程

        由于整个设计已经映射到了门级,因此这个例子可以计算出子模块U2周围的输入输出延时、输入驱动和输出负载的实际值。然后将这些实际值施加在U2模块中,作为U2的新的约束。这种方法有点类似于给U2的周围照了一张照片。U2施加了新的约束之后,就可以在这个基础上做一次高级别的编译。

        通过write_script命令,我们可以看到characterize之后到底照下了哪些信息

Design Compiler指南——后综合过程

        characterize给我们提供了一种比较好的第二次编译的方法,加入一个子模块所占的延时很重,就可以在保持其他模块不动的情况下将这个子模块重新编译一次,当然重新编译可以从HDL代码开始,下面是一个例子:

Design Compiler指南——后综合过程

        这个例子和前一个例子的不同在于,它没用compile –inc high,而是直接将它从内存中删除,读入它的源文件重新编译,这样可以取得较上一种方法更好的结果。

        characterize无疑向我们提供一种较好的子模块二次编译方法,但是同时它也有一定的局限性,在使用的时候务必要注意——

  1. 它要求所有的模块必须映射到门级,这是使用characterize的一个前提。
  2. characterize只能一次对一个子模块使用,即给U2作characterize的时候U1和U3模块必须保持不变,否则U2得到的环境就不是确定的值。
  3. characterize将外界环境直接作为它的约束,这使得它和其他的子模块之间不存在任何裕量(margin),这些裕量全部被该子模块吸收。

二、设计预估

        设计预估(Design Exploration)是指在整个设计的RTL代码尚处在验证的阶段就对设计进行预先综合的过程,在这一节里,我们将讨论相比瀑布式的设计流程,设计预估的优点以及设计预估的流程,这些内容可能更多的不是介绍Design Compiler的使用,而是设计方法学问题。

1、为什么要设计预估

        Design Compiler指南——后综合过程

        上图左边是一个传统的设计流程,这个流程先作HDL代码的编写,在代码仿真通过之后作设计综合,然后插入扫描单元,通过以后作后端的物理设计,直到最后交给Foundry流片。这种流程之所以称为瀑布式的是因为后面的步骤都必须等前一个步骤完成之后才能进行。

        稍加分析,不难发现这种流程有很大的危险性,从上图的右半部分可以看到,从综合到后端设计,每一个步骤都不可能不产生错误,如综合的时候碰到RTL代码引起的时序违反,加入扫描单元后发现测试覆盖率很低,后端布线由于过于拥挤而布不通,等等。这些错误如果可以通过工具的技巧修复还好,如果不能修复,就必须要返回到前面的步骤直至RTL代码的修改,这样做的代价是随着从前到后迅速增加的 。

        因此,要提高设计的效率,就必须在很早的情况下就发现问题并解决它,越是拖到后面,则越不划算。最理想的情况就是在编写RTL代码的时候就万无一失,并且在这个时候就能估算出最后的芯片面积和运行速度,以及内部长线的延时。这种思想就要进行设计预估的初衷。

        下面是引入设计预估之后的设计流程图

Design Compiler指南——后综合过程

        在这个流程中,前端逻辑设计与后端物理设计同时进行,在编写code的同时,对芯片的初步布局和布线先作一个规划,然后检查工艺库中提供的连线负载模型(WLM),根据实际需要创建新的负载模型。而code在仿真的同时也可以作一个设计规划,在设计规划的时候调用新的WLM,以便得出更加真实的结果。

        在设计预估做完,code仿真完成之后,就可以正式编译设计的主要模块,并将它们整合到顶层模块中去。同时对这些模块进行布局重新创建更加真实的WLM,直到最后的分析直至流片。

        可以看到这个流程是一个前端与后端同时进行的过程,也是一个不断融合的过程。前端的设计者总是想尽早知道模块在实际的芯片中的位置,以便得到准确的WLM。这样才能尽量把错误提前找出来,避免留到后面的高成本。

2、设计预估的目标

        在上面的这个流程中,设计预估是与code的功能验证平行进行的,而不是等到功能验证完成后再作。每当code的改动足以影响系统性能的时候,我们都需要进行一次设计预估。

        一般说来,设计预估有下面几个目标——

  • 验证代码的可综合性:这是设计预估的一个基本功能,不能综合的代码对后面的一切步骤都只能是空谈。
  • 控制代码的时序违反在一定的范围(10-15%):预估后的网表的时序违反如果超过这个范围就可以即使更改代码,保证在正式综合的时候能通过调整综合参数达到时序要求,而不再该代码。
  • 验证施加的约束的真实性和充分性:设计约束不是设计者凭空想象出来的,他需要通过多次的设计预估,并对得到的网表进行分析,从而对施加的约束不断修正,让他能足够反映真实情况,并且不漏掉容易忽视的约束条件。
  • 鉴别时序的特殊情况:大型设计中难免会有一些DC不能综合或者无需综合的路径,设计预估需要把这些路径找到。
  • 鉴别模块划分问题:模块划分可以在编写code的时候进行,也可以在DC的约束脚本中用group/ungroup完成,设计预估需要得到一个较好的设计划分。
  • 鉴别测试问题:测试问题我们再DFT课程中详细介绍。
  • 保证WLM的合理:WLM是DC处理连线延时的依据之一,合理的WLM可以保证时序准确性。

3、设计预估的流程

Design Compiler指南——后综合过程

        上面的设计预估流程可以看出设计预估包括——读入HDL代码,施加约束,加入扫描链,分析综合结果,根据初始化布局和创建的新WLM分析时序等等。每当其中一个步骤出现大的错误,都需要修改代码。 

上一篇:使用 IntelliJ IDEA 进行编译的时候提示 Java 的支持 Level 不够


下一篇:idea上springboot热部署