综合是前端模块设计中的重要步骤之一,综合的过程是将行为描述的电路、RTL级的电路转换到门级的过程;Design Compiler是Synopsys公司用于做电路综合的核心工具,它可以方便地将HDL语言描述的电路转换到基于工艺库的门级网表。本章将初步介绍综合的原理以及使用Design Compiler做电路综合的全过程。
§1. 综合综述 1.1什么是综合?
综合是使用软件的方法来设计硬件,然后将门级电路实现与优化的工作留给综合工具的一种设计方法。它是根据一个系统逻辑功能与性能的要求,在一个包含众多结构、功能、性能均已知的逻辑元件的单元库的支持下,寻找出一个逻辑网络结构的最佳实现方案。即实现在满足设计电路的功能、速度及面积等限制条件下,将行为级描述转化为指定的技术库中单元电路的连接。
图1 综合示意图
如图4-1所示,综合主要包括三个阶段:转换(translation)、映射(mapping) 与优化(optimization)。综合工具首先将HDL的描述转换成一个与工艺独立(technology-independent)的RTL级网表(网表中RTL模块通过连线互联),然后根据具体指定的工艺库,将RTL级网表映射到工艺库上,成为一个门级网表,最后再根据设计者施加的诸如延时、面积方面的约
束条件,对门级网表进行优化。
①
1.2 综合的不同层次
图2 抽象层次
设计的不同的抽象层次如图2所示,随着抽象层次的升高,设计者对于最终硬件(门和触发器)的控制能力越来越小。设计者可以在上述的三个层次用HDL语言描述他的设计,根据HDL语言描述的层次的高低,综合也相应的可以分为逻辑级综合,RTL级综合以及行为级综合。
1.2.1 逻辑级综合
在逻辑级综合中,设计被描述成布尔等式的形式,触发器、锁存器这样的基本单元采用元件例化(instantiate)的方式表达出来。下面是一个加法器的逻辑级描述,输出寄存——
①
有关逻辑综合优化的不同阶段,请参考«VLSI handbook» 的第33章
它综合以后的电路网表如下图3所示,对比一下不难看出,逻辑级描述实际上已经暗示了综合以后的网表。
图3 综合后的网表
1.2.2 RTL级综合
与逻辑级综合不同,在RTL级综合中,电路的数学运算和行为功能分别通过HDL语言特定的运算符和行为结构描述出来。对于时序电路,我们可以明确的描述它在每个时钟边沿的行为。下面同样是一个加法器的描述,它综合以后的网表如图3所示——
注意到,图3中的三个触发器不是例化而是通过HDL的特定结构推断出来的。这种推断是根据一些推断法则(Inference rule)进行的,例如在这个例子中,当一个信号(变量)在时钟的边沿进行赋值(always语句),那么这个信号(变量)可以推断为一个触发器。
1.2.3 行为级综合
行为级综合比RTL级综合层次更高,同时它描述电路也越抽象,在RTL级中,电路在每个时钟边沿的行为必须确切的描述出来,而行为级描述却不是这样,这里没有明确规定电
路的时钟周期,推断法则也不是用来推断寄存器。电路的行为可以描述成一个时序程序(sequential program),综合工具的任务就是根据指定的设计约束,找出哪些运算可以在哪个时钟周期内完成,需要在多个周期内用到的变量值需要通过寄存器寄存起来。
请看一个简单的行为综合的例子——
上面这个例子没有任何时钟的信息,现在假设一次加法操作(加法器)需要5ns的延时并且假设系统的时钟是6ns,那么可以看出执行完上述操作需要3个周期的时间。另外,所有的三个加法语句可以通过重用一个加法器来实现,而且只需要一个叫做Tree的寄存器保存中间变量的值(不同时钟周期的变量值)。这种假设下的电路结构图如图4所示,控制器的时序关系如图5所示。
图4 6ns周期下的电路结构
图5 控制时序图
如果改变约束条件,假设时钟周期是11ns,那么完成全部操作仅需2个周期,同时需要2个加法器,图6和图7分别是此时的电路结构图和控制器时序图。①
①
有关行为综合和RTL综合的具体描述请参考«VLSI handbook» 的第75章
图6 11ns周期下的电路结构
图7 控制时序
1.2.4 Design Compiler所处的位置
图8向我们展示了一个设计从最初的最抽象的概念阶段到最终的芯片阶段的转化过程,在这个过程当中,Design Compiler主要完成将设计的RTL级描述转化到门级网表的过程,比RTL更高的行为级的综合,将由Synopsys的另外一个工具——Behavior Compiler来完成。在以下的章节中,我们主要围绕怎样将一个RTL级描述的设计转化为门级网表来进行讨论。
图8 Design Compiler所处位置
1.3 使用Design Compiler做综合的流程示意图
图9 DC综合示意图
与一般的综合过程相同,使用DC做综合也包含转换、优化和映射三个阶段。转换阶段综合工具将HDL语言描述的电路或未映射的电路用工艺独立的RTL级的逻辑来实现,对于Synopsys的综合工具DC来说,就是使用gtech.db库中的RTL级单元来组成一个中间的网表。优化与映射是综合工具对已有的中间网表进行分析,去掉其中的冗余单元,并对不满足限制条件(如constraints.tcl)的路径进行优化,然后将优化之后的电路映射到由制造商提供的工艺库上(如core_slow.db)。
①1.4 超深亚微米给综合工具带来的挑战
当半导体工艺的最小特征尺寸小于1um时,称之为亚微米设计技术,当最小特征尺寸小于0.5um时称为深亚微米设计技术(DSM:Deep Sub Micrometer),而当进一步小于0.25um时,则称为超深亚微米设计技术(VDSM:Very Deep Sub Micrometer)。当进入超深亚微米设计后,原有的综合工具受到了很大的挑战,其中一个主要表现是:连线的延时迅速上升。
①
gtech.db 的介绍可参看第四节
图10 VDSM对电路延时的影响
当特征尺寸大于0.5um时,电路的延时主要集中在门级单元的延时上,如果门级单元延时占系统延时的70%以上,则前端综合后的电路延时与后端进行布局布线以后反标(back-annotate)回来的电路延时相差不大。从图10可以看出,当特征尺寸进一步缩小时,单位连线上的延时以及互连线的总长度迅速上升,这两方面的因素都使得连线延时在系统总延时上所占的比重越来越大。通常在0.35um设计时,连线延时已经达到了总延时的50%以上,于是版图发标的延时与综合出来得到延时相差会比较大,单靠一次综合已经不能准确估计电路的延时情况,此时需要经过前端后端工具不断叠代来达到比较真实的结果(见图11)。在0.18um的时候,连线延时已经达到了70%,这时就算增加叠代的次数也不一定能得到满意的结果,因而必须引入新的综合手段,保证优化叠代过程的收敛,这就是物理综合方法(Physical Synthesis Flow)。
图11 VDSM对综合的影响
Physical Compiler是Synopsys推出的新一代综合工具,它逐渐打破了前后端设计分离的设计方法而将它们结合在一起。它与Design Compiler的最大区别是综合出的门级网表除了反映电路的连接关系之外还有它在版图中具体位置的信息,有了单元的位置信息,综合工具就可以比较准确的估算出互连线的延时,从而减少叠代次数,提高设计效率。
§2. Verilog语言结构到门级的映射
Verilog编码效率的高低是综合后电路性能高低的决定性因素,两种不同风格的编码,即使它们所表达的逻辑功能一下,也会产生出大不一样的综合结果。就算综合工具运用的再好,也不能完全依赖它把一段编码很差的代码综合出一个像样的电路来。本节将通过大量的实例介绍综合时Verilog的各种语言结构(always、if、case、loop等等)到门级的映射。这些语言结构是在编写Verilog代码的时候经常用到的一些基本结构,希望通过分析他们与门级网表之间的对应关系,大家能够对什么样的语句能生成什么样的具体电路有个初步的认识。
编写用于综合的HDL代码的三个原则——
z 编写代码的时候注意代码综合后大概的硬件结构,不写不可综合的语句① z 编写代码的时候注意多用同步逻辑,并将异步和同步逻辑分开处理 z 编写代码的时候注意代码的抽象层次,多用RTL级的描述
2.1 always语句的综合
always语句用来描述电路的过程行为(procedural behavior), 表示当事件列表中的状态发生变化时,执行语句体中的语句。下面是一个包含过程赋值的always语句的例子。
电路综合后的网表如下图所示——
①
图12 always语句的综合结果
DC不能综合的Verilog结构请参阅《HDL Compiler for Verilog Reference Manual》中的《Unsupported Verilog Language Constructs》一节
使用always语句描述组合电路要注意的是:在该语句中读入的所有变量都需要出现在事件列表中(对Verilog语言而言是指”@”符号之后的信号),否则可能会得不到用户期望的结果。
2.2 If语句的综合
if 语句用于描述受条件控制的电路,下面是一个例子——
图13 if语句的条件选择操作
如果在使用if语句时,没有指出条件判断的所有可能情况,则会在电路中引入锁存器(Latch),如下例所示——
图14 if语句条件未全引入Latch
上面的电路中,当条件Marks的值为小于5时,电路的输出Grade的值为FAIL,如果Marks的值为5到10之间,那么Grade的值为PASS,由于没有指定Marks的值大于10的时候Grade的值,于是产生的电路中引入了锁存器保存原来Grade的值。
由于锁存器和触发器两种时序单元共存的电路会增大测试的难度,因此,在综合的时候尽量只选用一种时序单元,为了不在电路中引入锁存器,可以在使用该语句时设置缺省的状态,即在判断条件之前先对输出赋值,或者使用if…else if…else的语句结构。
上面的例子经过改动后可以得到下面的例子——
图15 if语句条件补全消除Latch
2.3 case语句的综合
2.3.1 条件互斥的case语句
对于条件互斥(mutually exclusive)的case语句来说,它不存在优先级的概念。 先看一个case语句的简单实例——
图16 一个2-bit的ALU
从条件互斥的case的行为来看,它有点像if…else if…else这种结构的if语句,也就是说case表达式(Op)先同case的第一个case项(ADD),如果不匹配的话再同第二个(SUB)比较,依次类推。与上例等价的if语句如下所示——
2.3.2 Casex语句
在casex语句中,case项中的x或者z的值表示不关心(don’t-care)。从综合的角度看,这些值不能作为case表达式的一部分。
下面是一个用casex语句描述优先级编码器的例子——
001,011,101,111010,110,100000
图17 用casex描述的优先级编码电路
上述的casex语句所表达的电路与下面的if语句一样——
可见casex所描述的电路是具有优先级的。如果我们将上例中的casex用case语句代替(同时将case项中的x用0代替),则综合出来的电路将不具有优先级。
2.3.3 隐含Latch的case语句
与if语句类似,如果没有指出case项的所有可能情况,则会在电路中引入锁存器(Latch),下面是一个例子——
图18 case语句条件不全引入Latch
上例中的Zip信号没有在所有可能情况下赋值,故在Toggle=0或3时,NextToggle保持原有的值,从而产生Latch。避免产生latch的一个方法是给被赋值信号(NextToggle)赋初值。一种方法是加在case的最后加入一种default的情况(如下)
或者在case语句之前就对被赋值信号加上一个初值(如下)
2.3.4 Full_case
从2.3.3的例子我们知道,如果条件不全,case综合之后会产生latch。如果设计者知道除了列出的一些case项之外不会再有其他的条件出现,也就是说Toggle的值除了2’b01和2’b10之外不会有其他的值,而且又不想让电路产生latch,那么他就需要把这种情况告诉综合工具,这里就可以通过一条综合指令(synthesis directive)——synopsys full_case来传达。综合指令是HDL语言中的一类特别的代码,它负责向综合工具传递额外的信息;由于综合指令是以注释的形式存在于HDL代码中的,它对Verilog语言本身没有其他影响。
加入综合指令之后,电路如下所示——
synopsys full_case
图19 加入full_case后的综合结果
可见在加入full_case综合指令后,综合的结果不存在Latch。但是要注意的是: 1. 加入综合指令会使代码的结果依赖于所用的综合工具,从而降低代码的可移植性。 2. 加入综合指令后产生的电路网表会和当初的Verilog建模有出入,导致验证的复杂①
2.3.5 Parallel_case
从2.3.3节可以看出,casex语句是具有优先级的,与if…else if …else语句相当。那么假如我们知道case项是互斥的该怎么办呢?(在互斥的情况下,case将平行的检查case项中所有可能的情况,而不是先检查第一个然后第二个……)。这时,我们需要将互斥的信息传达给综合工具,这就是parallel_case的综合指令。当加上这条指令后,Design Compiler能够 ①
有关具体实例请参考 《Verilog HDL Synthesis—A Practical Primer》的第5章
理解case项是互斥的,这样就不会产生带优先级的电路,而是平行的译码结构。
加入综合指令后的电路如下所示——
图20 加入parallel_case后的综合结果
与上面的casex语句对应的if语句如下——
由于parallel_case同full_case一样都是综合指令语句,在应用parallel_case的时候也要考虑到可移植性和提高验证复杂度的问题。
2.3.6 case项不是常数的case语句
前面讨论的case项都是常数,实际上我们也会遇到case项不是常数的情况,如下例—
图21 case项不是常数的优先级译码
这里加入full_case指令是相当必要的,否则综合后会引入latch(Pbus全不为1的情况没有考虑)。另外,也可以通过赋初值的办法避免latch的产生。
值得注意的是,case项不是常数与case项是常数不同,它综合后的电路是带优先级的。
2.4 loop语句的综合
在Verilog语法中,一共有四种loop语句——while-loop, for-loop, forever-loop以及repeat-loop。其中for-loop使用的最多,也是一种典型的可以被综合的loop语句。For-loop语句综合的基本原则就是将里面的所有语句进行展开,下面举一个例子——
图22 loop语句的综合
将loop语句展开之后,可以得到下面的if语句——
使用loop语句值得注意的地方是:不要在循环体内加入一些延时或面积较大的单元(例如加法器)。由于loop语句综合时需要展开循环体,所以相当于把循环体内的单元复制出来,循环多少次则复制多少次,这样势必会造成综合后的网表面积和延时很大,影响性能。
2.5 触发器的综合
触发器是组成时序电路的一个基本元件,也是Design Compiler作静态时序分析的要素之一,当一个信号(变量)在通过always语句在时钟边沿(上升沿或下降沿)赋值时,触发器就可以推断出来。
先看下面的一个例子——
图23 2-bit加法器的综合
上面的always语句说明,每当ClockA出现一次上升延跳变,信号Counter就加1。由于Counter是受时钟上升沿控制的,那么它综合以后会出现上升沿的触发器。
描述时序电路要注意一点:如果一个信号既要在always内部赋值也要在always语句之外赋值,那在always内部赋值时需要用非阻塞赋值语句,如上例中的Counter<=Counter + 1。这样可以准确的反映时序电路的行为。
在读入Verilog文件的时候,Design Compiler能够分析出代码中的时序元件(触发器和锁存器),并将结果报告出来。
图24 DC报告时序元件
如图21所示的Q_reg,Design Compiler推断出是一个一位宽的异步复位(AS)的触发器。
2.6 算术电路的综合
DC在综合遇到运算符的时候,会在DesignWare中选取合适的逻辑电路来实现该运算符。DesignWare是集成在DC综合环境中的可重用电路的集合,主要包括‘+’‘-’‘×’等算术运算符和‘<’‘>’,‘<=’‘>=’等逻辑运算符。针对同一种运算符,DesignWare可能提供不同的算法,具体选择那一种是由给定的限制条件决定的。
DesignWare分为DesignWare Basic与DesignWare Foundation,DesignWare Basic提供基本的电路,DesignWare Foundation提供性能较高的电路结构。
图25 DW Foundation 与DW Basic
从上图的加法器可以看出,DW Foundation除了具有Carry Look-Ahead 和Ripple Carry两种结构的加法器外,还有另外四种结构,并且速度更快。
如果要Foundation的DesignWare,除了需要有DesignWare Foundation的Licesen之外还需要在综合的时候设置synthetic_library。
在verilog语言中,一个reg类型的数据是被解释成无符号数,integer类型的数据是被解释成二进制补码的有符号数,而且最右边是有符号数的最低位。
进行算术运算时,它也有各个运算符的优先级,运算按照由左至右进行,如下面的式子——
它综合出来会是下面的样子——
图26 不好的运算电路结构
显然这种结构的延时是很大的,我们可以通过交换运算次序和加入括号形成优化的结构
图27 优化后的电路结构
本节详细介绍了Verilog的各种语句和运算符的综合电路,本节的综合没有施加任何的限制条件,在下一节中,将会详细给出Design Compiler工具的详细介绍和具体的命令。
§3. 使用Design Compiler进行综合
在接下来的几章里,我们将具体介绍使用Synopsys公司的Design Compiler作综合的过程,整个过程大致可以分为下面几个部分——
z 预综合过程(Pre-synthesis Processes) z 施加设计约束(Contrainting the Design) z 设计综合(Synthesizing the Design) z 后综合过程(Post-synthesis Process)
下面我们这个步骤分别对各个过程作一个详细的介绍。
3.1 预综合过程
预综合过程是指在综合过程之前的一些为综合作准备的步骤,包括Design Compiler的启动、设置各种库文件、创建启动脚本文件、读入设计文件、DC中的设计对象、各种模块的划分以及Verilog的编码等等。其中Verilog编码在第2章中已经讨论过。
3.1.1 Design Compiler的启动
对于2000.11版的Design Compiler,用户可以通过四种方式启动Design Compiler,他们是——dc_shell命令行方式、dc_shell-t命令行方式、design_analyzer图形方式和design_vision图形方式。其中后面两种图形方式是分别建立在前面两种命令行方式的基础上的。
1. dc_shell命令行方式
该方式以文本界面运行Design Compiler。在shell提示符下直接输入”dc_shell”就可以进入这种方式。也可以在启动dc_shell的时候直接调用dcsh的脚本来执行(dc_shell –f script)。目前这种方式用的已经不是很普遍。 2. dc_shell-t命令行方式
该方式是以TCL(Tool Command Language后面章节将有介绍)为基础的,在该脚本语言上扩展了实现Design Compiler的命令。用户可以在shell提示符下输入”dc_shell-t”来运行该方式。该方式的运行环境也是文本界面。也可以在启动dc_shell-t的时候直接调用tcl的脚本来执行(dc_shell-t –f script)。TCL命令行方式是现在推荐使用的命令行方式,她相对shell方式功能更强大,并且在Synopsys的其他工具中也得到普遍使用。① 3. design_analyzer图形界面方式
Design Analyzer使用图形界面,如菜单、对话框等来实现Design Compiler的功能,并提供图形方式的显示电路。用户可以在shell提示符下打“design_analyzer”来运行该方式。Design_analyzer图形方式是今后要经常用到的图形界面方式。由于它所对应的是dc_shell的命令行方式,所以我们不能在design_analyzer里运行tcl命令。另外需要注意的是:Design analyzer的工作模式不是用于编辑电路图的,它只能用于显示HDL语言描述电路的电路图。
4. design_vision图形界面方式
Design_vision是与tcl对应的图形方式,用户可以在shell提示符下打”design_vision”来运行该方式。由于它是在Windows NT下开发的,在工作站环境下不太普及,今后的课程中将不会用到。
不论dcsh模式还是tcl模式都提供了类似于unix的shell脚本的功能,包括变量赋值、控制流命令、条件判断等等,但是dcsh模式的语法规则不同于tcl的语法规则,因此,使用dcsh书写的脚本不能直接用于TCL工作模式;使用TCL书写的脚本也不能直接用于dcsh ①
图28 DC的四种界面
此后的DC命令如无说明都使用TCL方式
工作模式。
Design Analyzer在启动时自动在启动目录下面创建两个日志文件:command.log和view_command.log,用于记录用户在使用Design Compiler时所执行的命令以及设置的参数,在运行过程中同时还产生filenames.log的文件,用于记录design compiler访问过的目录,包括库、源文件等,filenames.log文件在退出design compiler时会被自动删除。启动dc_shell时则只产生command.log的日志文件。
3.1.2 库文件的设置
在Design Compiler的运行过程中需要用到几种库文件,他们是工艺库、链接库、符号库以及综合库,下面对他们一一说明。
1. 工艺库(target_library)
工艺库是综合后电路网表要最终映射到的库,读入的HDL代码首先由synopsys自带的GTECH库转换成Design Compiler内部交换的格式,然后经过映射到工艺库和优化生成门级网表。工艺库他是由Foundary提供的,一般是.db的格式。这种格式是DC认识的一种内部文件格式,不能由文本方式打开。.db格式可以由文本格式的.lib转化过来,他们包含的信息是一致的。下面是一个.lib的工艺库例子——
图29 工艺库文件(.lib)
从图中可以看出,工艺库中包含了各个门级单元的行为、引脚、面积以及时序信息(有的工艺库还有功耗方面的参数),DC在综合时就是根据target_library中给出的单元电路的延迟信息来计算路径的延迟。并根据各个单元延时、面积和驱动能力的不同选择合适的单元来优化电路。
在tcl模式下,我们可以根据下面的命令指定工艺库——
2. 链接库(link_library)
link_library设置模块或者单元电路的引用,对于所有DC可能用到的库,我们都需要在link_library中指定,其中也包括要用到的IP。
值得注意的一点是:在link_library的设置中必须包含’*’, 表示DC在引用实例化模块或者单元电路时首先搜索已经调进DC memory的模块和单元电路,如果在link library中不包含’*’,DC就不会使用DC memory中已有的模块,因此,会出现无法匹配的模块或单元电路的警告信息(unresolved design reference)。
另外,设置link_library的时候要注意设置search_path,请看下面这个例子——
图30 link_library的使用(1)
图中设置了link_library,但是DC在link的时候却报错,找不到TOP中引用的DECODE模块,这说明link_library默认是在运行DC的目录下寻找相关引用。要使上例的DECODE能被找到,需要设置search_path,如下图所示——① ①
lappend 是tcl的一个命令,表示在原有的search_path上附加上bob这个目录
图31 link_library的使用(2)
3. 符号库 (symbol_library)
symbol_library是定义了单元电路显示的Schematic的库。用户如果想启动design_analyzer或design_vision来查看、分析电路时需要设置symbol_library。符号库的后缀是.sdb,加入没有设置,DC会用默认的符号库取代。
设置符号库的命令是:set symbol_library 4. 综合库(synthetic_library)①
在初始化DC的时候,不需要设置标准的DesignWare库standard.sldb用于实现Verilog描述的运算符,对于扩展的DesignWare,需要在synthetic_library中设置,同时需要在link_library中设置相应的库以使得在链接的时候DC可以搜索到相应运算符的实现。
①
综合库在前面的算术单元的综合一节中已经介绍过
3.1.3 设置启动文件.synopsys_dc.setup
图32 .synopsys_dc.setup
启动文件顾名思义,就是DC在启动的时候首先读入的文件,DC在启动的时候,会自动在三个目录下搜索该文件(如上图所示),对DC的工作环境进行初始化:
1. $SYNOPSYS/admin/setup目录下,DC安装的标准初始化文件。
2. 当前用户的$HOME目录下,一般用于设置一些用户本人使用的变量以及一些个性
化设置。 3. DC启动所在的目录下,一般用于与所在设计相关的设置。
其中后面的setup 文件可以覆盖前面文件中的设置。该文件主要包括库的设置、工作路径的设置以及一些常用命令别名的设置等等。①
由于dcshell的启动脚本和tcl的脚本语法不一致,所以如果只有一种方式的启动脚本,那么运行另一种方式的时候会报错。因此,DC的启动脚本有一种兼容两种方式的格式。下面是这种脚本的举例——
①
在Unix中,该文件是隐藏文件,需要用ls –a显示
它区别与其他启动脚本的特征是第一行有一个”#”,说明它是dcshell的一个子集,同时兼容两种方式。文件的第一段设置工艺库和链接库,第二段设置符号库和搜索路径,第三段设置DC命令的别名,这一点与Shell相似。
3.1.4 读入设计文件
图33 读入设计文件
Design Compiler支持多种硬件描述的格式,.db、.v、.vhd、等等,对于dcsh工作模式来说,读取不同的文件格式只需要带上不同的参数,对于TCL的工作模式来说,读取不同的文件格式需要使用不同的命令。两种工作模式的读取命令的基本格式如下:
read –format verilog[db、vhdl etc.] file
//dcsh的工作模式
read_db file.db //TCL工作模式读取DB格式 read_verilog file.v //TCL工作模式读取verilog格式
read_vhdl file.vhd //TCL工作模式读取VHDL格式 Design Compiler可以读取设计流程中任何一种数据格式,如行为级的描述、RTL级的描述、门级网表等等,不过由于不同的数据格式使得Design Compiler综合的起点不同,即使实现相同的功能,也可能会产生不同的结果。
读取源程序的另外一种方式是配合使用analyze命令和elaborate命令:analyze是分析HDL的源程序并将分析产生的中间文件存于work(用户也可以自己指定)的目录下;elaborate则在产生的中间文件中生成verilog的模块或者VHDL的实体,缺省情况下,elaborate读取的是work目录中的文件。
当读取完所要综合的模块之后,需要使用link命令将读到Design Compiler存储区中的模块或实体连接起来,如果在使用link命令之后,出现unresolved design reference的警告信息,需要重新读取该模块,或者在.synopsys_dc.setup文件中添加link_library,告诉DC到库中去找这些模块,同时还要注意search_path中的路径是否指向该模块或单元电路所在的目录。
3.1.5 设计对象
图34 DC中的设计对象
上图是一个Verilog描述的设计实例,里面包含了我们所要讨论的几种设计对象。这些对象也是今后DC命令的操作对象。Verilog描述的各个模块可以称之为设计(Design),里面包含时钟(Clock),他的输入输出称为端口(Port),模块中的互连线是线网(Net),内部引用的
元件和实例的区别元件称为引用(Reference),引用的实例称为单元(Cell),引用单元的内部端口是管脚(Pin)。 其中值得注意的是DC识别Clock不是通过HDL的书面表达,而是要通过设计者施加
一定的约束来区分的,具体内容后续章节会讨论。
如果各个设计对象互相重名怎么办?
图35 对象的重名
在上图中,一个设计的端口,连线以及内部一个管脚都有一个相同的名字,假如要对名叫”CLK”的线网设置一个为5的负载,那应该怎样表示呢?这里,我们需要借助DCTCL的一个特殊的数据类型集合(collection)。
其中的方括号里面表示在所有的线网中搜索名叫CLK的线网,将它的负载值设置为5。get命令返回对象的集合,如果这个对象没有找到,则返回为空集合。①
集合是DCTCL的特殊数据类型,它不同于TCL语言中的一种标准数据类型列表(list)。列表类似与C语言中的字符串。我们可以通过下面的方式定义一个列表——
集合相当于C语言的指针,这个指针指向一个数据结构。例如上面指向的是一个名为CLK的线网数据类型,这个数据类型中除了包含负载这个属性之外,还有电阻值、扇出(fanout)等属性,而这些是在列表中不存在的。
除了前面的get_nets外,还有下面的一些命令可以搜索设计对象,这里列出了TCL和dcshell两种语法,便于对比——
①
可以在dc_shell-t环境下输入”help get*”列出所有以get打头的DC命令
图36 搜索对象
3.1.6 设计划分
把一个复杂的设计分割成几个相对简单的部分,称为设计划分(Design Partition)。这种方法,也可以称为“分而治之”(Divide and conquer)的方法,在平常的电路设计中这是一种普遍使用的方法,一般我们在编写HDL代码之前都需要对所要描述的系统作一个系统划分,根据功能或者其他的原则将一个系统层次化的分成若干个子模块,这些子模块下面再进一步细分。这是一种设计划分,模块(module)就是一个划分的单位。在运用DC作综合的过程中,默认的情况下各个模块的层次关系是保留着的,保留着的层次关系会对DC综合造成一定的影响,比如在优化的过程中,各个子模块的管脚必须保留,这势必影响到子模块边界的优化效果。下面我们将详细介绍在作设计划分的过程中要注意的几点原则,并根据每个原则举一个实例说明。
原则一. 不要让一个组合电路穿越过多的模块
图37 设计划分实例
上图中的组合逻辑电路存在于寄存器A与寄存器C之间,它同时穿过了模块A、模块B以及模块C。前面提到了,如果直接将这样的划分交给DC综合,那么综合后的电路将仍旧保持上面的层次关系,即端口定义不会改变。这样的话,DC在作组合电路的优化的时候就会分别针对A、B、C三块电路进行,这样势必会影响到DC的优化能力,不必要的增加了这条路径的延时和面积。因此,可以考虑将三块分散的组合逻辑划分到一个模块中,如下图所示——
图38 较好的划分
这张图说明了组合电路划分到一个模块之后的电路情况,这样DC就可以充分的施展它的的优化技巧,综合出比较满意的电路来。为什么说它只是一个较好的划分呢?因为它只是考虑到组合电路的最优划分而没有想到时序电路部分。先看下面一张图——
图39 最好的划分
在这张图里,组合逻辑被划到了C模块中,它不仅能保证组合的最佳优化还能保证时序的最佳优化,因为里面的寄存器在优化的过程中可以吸收前面的组合逻辑,从而形成其他形式的时序元件,如由原先的D触发器变成JK触发器、T触发器、带时钟使能端的触发器等等。这样工艺库中的大量的时序单元都可以得到充分的利用了。
原则二. 寄存模块的输出 图40 寄存模块输出
通过前面的讨论,可以知道:在编写代码或者综合的过程中,我们可以把模块尽量写成如图37那样的逻辑结构:将所有的输出寄存起来。其实这样不但是最佳的优化结构,也可以简化时序约束(使得所有模块的输入延时相等)。①
就算遵循了输出寄存的原则,我们还是可能范下面的错误——
①
施加时序约束将在后面的章节详细讨论
图38 粘滞逻辑(Glue Logic)
图中可以看到,一个与非门连接了A、B、C三个模块,同样的不难看出来,它也会影响到C的组合逻辑的优化。一般这种情况只会在至下而上的综合策略中才出现①。可以通过把与非门吸收到C中的组合逻辑的方法消除粘滞逻辑(如下图),从而使得电路的顶层模块仅仅是将子模块拼接在一起,而没有独立的电路结构,这样的一个另一个好处是可以使得在至下而上的设计策略中不需要编译顶层模块。
图41 消除粘滞逻辑
原则三. 根据综合时间长短控制模块大小
综合时间主要受到硬件条件(主频、内存)的制约,对于早期的工作站而言,硬件水平不高,跑一个大型的设计可能会一次花上几天时间才会有结果,这样对调试和缩短工期是不利的,所以需要根据工作站的能力选择合适的模块大小。请看下面一个例子——
①
综合策略后面章节有详细讨论
图42 针对模块大小不好的划分
这个例子的模块大小从500门到37万门不等,假设工作站的硬件条件限制最多只能跑30万门的设计,那么上面的这种划分就有一些弊病。首先,TEENY模块太小,不适合优化出最好的结果,可以考虑将它合并到其他模块中。其次,另一个组合模块逻辑太大,这势必使得综合的时间变得不能承受。因此,改进的划分可以如下图所示——
图43 针对模块大小较好的划分
可以看到,虽然各个模块也是大小不一,但却可以取得较前面的划分更好的结果。值得注意的是——Design Compiler软件本身没有模块大小的限制,它完全根据工作站的环境决定,在具体作设计的时候,我们可以在硬件条件允许的条件下编译较大的模块,假如硬件条件的确有限,只能选择小的模块来综合了/
模块大小该如何划分丠
原则四. 将同步逻辑部分与其他部分分离
DC可综合的全同步逻辑电路
图44 顶层设计的划分
上图是一个芯片的顶层设计,可以看到它被分层了三个层次——最外边是芯片的Pad,Pad是综合工具中没有的,也不是工具能生成的,它由Foundry提供,并由设计者根据芯片外围的环境手工选择;中间一层被分成四个部分,其中最里面那个称为Core,也就是DC可以综合的全同步逻辑电路,另外的三个部分DC不能综合,需要其他的办法来解决:ASYNCH是异步时序部分,不属于DC的范畴;CLOCK GEN是时钟产生模块(可能用到PLL),尽管有一部分同步电路,但也不符合综合的条件;JTAG是边界扫描的产生电路,这一部分可以由Synopsys的另外一个工具BSD Compiler自动生成。
同步用DC综合丆异步和时钟怎么办啊丠上面我们介绍了四个划分的原则,当然这些原则并不是我们在编写HDL代码的时候就必须遵守的,它只是说明什么样的设计划分对于DC来说是最理想的,能得到最优化的结果。事实上除了通过HDL中的模块体现划分,我们还可以运用DC的两个命令(Group及Ungroup)来调整设计划分。
先看一个例子,调整”原则一”里提到的那个划分不好的模块——
图45 用group创建新层次
第一步是使用group命令,创建一个新的模块NEW_DES(设计名),单元名为U23,包含了U2和U3,这个命令很直观,很容易看懂。
第二步则是使用ungroup命令,将U23中的U2和U3的边界去掉,使之称为一个整体,如下图所示——
图46 用ungroup去除层次关系
3.2 施加设计约束
Design Compiler是一个约束驱动(constrain-driven)的综合工具,它的结果是与设计者施加的约束条件密切相关的。在这一章里,我们主要讨论怎样给电路施加约束条件,这些约束主要包括——时序和面积约束、电路的环境属性、时序和负载在不同模块之间的分配以及时序分析,在本章的最后一节还将讨论DC Tcl语言的一些基本语句。
3.2.1 时序和面积
图43 RTL模块综合示意图
上图是RTL模块的综合示意图,可以看出在RTL代码仿真通过以后,就开始将它进行综合,综合时需要对他加入约束和设计属性的信息,DC根据这些约束将RTL模块综合成门级网表,然后分析综合出的网表是否满足约束条件,如果不满足就要修改约束条件,甚至重写RTL代码。值得注意的是,上面提到的仅仅是RTL模块的综合过程,而不是整个芯片的综合,整个芯片是由很多这样的模块组成的,它的综合过程与上图描述的过程有一定的区别,具体我们将在后面的章节中进行讨论。
3.2.1.1 定义面积约束
因为芯片面积直接关系到芯片的成本,面积越大,成本越高,因此,集成电路的设计总是希望面积尽量小,以减小芯片成本。定义面积约束是通过set_max_area命令来完成的,比如——
上面的例子给PRGRM_CNT_TOP的设计施加了一个最大面积100单位的约束。100的具体单位是由Foundry规定的,定义这个单位有三种可能的标准:一种是将一个二输入与非门的大小作为单位1;第二种是以晶体管的数目规定单位;第三种则是根据实际的面积(平方微米等等)。至于设计者具体用的是哪种单位,可以通过下面的一个小技巧得到——即先综合一个二输入与非门,用report_area看他的面积是多少,如果是1,则是按照第一种标准定义的;如果是4,则是第二种标准;如果是其他的值,则为第三种标准。
3.2.1.2 同步设计的时序特点和目标
同步时序电路是DC综合的前提,因此这里有必要先讨论一下同步时序电路的特点及目标。这里所讨论的同步时序电路的特点是——电路中的信号从一个受时钟控制的寄存器触发,到达另一个受时钟控制的寄存器。而我们要达到的目标是——约束电路中所有的时序路径,这些时序路径可以分为三类:输入到寄存器的路径 、寄存器到寄存器之间的路径以及寄存器到输出的路径。他们分别对应与下图所示的标号为N、X和S的电路。
图44 同步设计的时序特点
假设在上面的电路中,我们要控制触发器FF2到FF3之间的时序,即X电路的延时,那要通过什么方式让DC知道呢?显然一个直观的办法就是定义系统的时钟Clk,如果我们定义好了Clk的周期,那么DC会自动的尽量保证从FF2触发的信号能在一个周期内到达
FF3寄存器。假如周期是10ns,FF3触发器的建立时间(setup time)是1ns,那么留给X电路的延时最大只能有10-1=9ns。
3.2.1.3 定义时钟
图45 定义时钟
在电路综合的过程中,所有时序电路以及组合电路的优化都是以时钟为基准来计算路径延迟的,因此,一般都要在综合的时候指定时钟,作为估计路径延迟的基准。定义时钟的时候我们必须定义它的时钟源(Clock source),时钟源可以是端口也可以是管脚;另外还必须定义时钟的周期。另外有一些可选项,比如占空比(Duty Cycle)、时钟偏差(Clock Skew)和时钟名字(Clock Name)。定义时钟采用一个语句create_clock完成——
第一句定义了一个周期为10ns的时钟①,它的时钟源是一个称为Clk的端口。 第二句对所有定义的时钟网络设置为don’t_touch,即综合的时候不对Clk信号优化。如果不加这句,DC会根据Clk的负载自动对他产生Buffer,而在实际的电路设计中,时钟树(Clock Tree)的综合有自己特别的方法,它需要考虑到实际布线后的物理信息,所以DC不需要在这里对它进行处理,就算处理了也不会符合要求。 ①
单位由Foundry的工艺库中指定,一般为ns
图46 定义时钟后的电路
可以看到,定义了系统时钟后,图44中的X电路已经被约束起来了,但是电路的输入输出两块还没有施加约束,这可以通过DC的另外两个命令来完成——
3.2.1.4 约束输入路径
图47 定义输入路径延时
从上图可以看出,我们所要定义的输入延时是指被综合模块外的寄存器触发的信号在到达被综合模块之前经过的延时,在上图中就是外围触发器的clk-q的延时加上M电路的延时。当确定这段延时之后,被综合模块内部的电路延时的范围也可以确定下来了。加入时钟周期是20ns,输入延时是4ns,内部触发器的建立时间为1.0ns,那么就可以推断出要使电路正常工作,N电路的延时最大不能超过20-4-1.0=15.0ns。
设置输入延时是通过DC的set_input_delay命令完成的——
如上面的语句指出了被综合模块的端口A的最大输入延时为4ns。-max选项是指明目前设置的是输入的最大延迟,为了满足时序单元建立时间(setup time)的要求。另外还有一个选项是-min,它是针对保持时间的约束使用的,后面章节有详细介绍。-clk是指出这个端口受哪个时钟周期的约束。
定义了输入延时之后,相对应的还要设置电路的输出延时。
3.2.1.5 约束输出路径
图48 定义输出路径延时
上图中,信号在被综合模块的触发器U3里触发,被外围的一个触发器接收。对外围电路而言,它有一个T电路延时和外围触发器的建立时间。当确定了他们的延时之后,被综合模块内部的输出路径延时范围也就确定下来了。假如,时钟周期20ns,输出延时5.4ns,U3触发器的clk-q延时为1.0ns,那么输入路径S的最大延时就是20-5.4-1.0=13.6ns。
设置输入延时是通过DC的set_output_delay命令完成的——
上面的语句指出了被综合模块的输出端口B的最大输出延时为5.4ns。-max选项是指明目前设置的是输入的最大延迟;-clk是指出这个端口受哪个时钟周期的约束。
至此,模块的面积、时钟、输入输出延时都施加了相应的约束。在施加了这些约束之后,可以使用下面的几个命令检查约束是否施加成功——
z report_port –verbose
报告在当前设计中所有的输入输出端口属性和施加的约束值
z report_clock
报告当前设计中定义的时钟及其属性情况
z reset_design 删除当前设计中所有的属性值和约束(通常用在约束脚本的第一句)
z list_libs
列出内存中所有可用的库
3.2.2 环境属性
图49 加入时序约束后的电路图
在3.2.1节中,我们主要讨论了怎样电路中加入时序约束,如设置clock周期、设置输入输出延时等,但是仅仅靠这些约束还是不够的。因为还要考虑到被综合模块周围环境的变化,举个例子说,如果当外界的温度变化,或者电路的供电电压发生变化时,延时会相应的改变,所以这些方面也是必须考虑到的。类似的上一节仅仅约束了输入输出的延时,而没有考虑到他们的电平转化时间(transition time),这些是有输入输出的外围电路的驱动能力和负载大小决定的。另外,电路内部的互连线的延时也没有估计在内。这一节我们主要讨论怎样给电路施加这些环境属性。
设置环境属性的命令如下图所示——
图50 设置环境属性
3.2.2.1 设置输出负载
为了更加准确的估计模块输出的时序,除了知道它的输出延时之外还要知道输出所接电路的负载情况,如果输出负载过大会加大电路的transition time,影响时序特性。另外,由于DC默认输出负载为0,即相当于不接负载的情况,这样综合出来的电路时序显然过于乐观,不能反映实际工作情况。
设置输出负载是通过DC的set_load命令完成的——
该命令有两种用法,一种是直接给端口赋一个具体的值,另外则结合另一个命令load_of指出它的负载相当于工艺库中的哪个单元的负载值。
图51 设置输出负载(1)
例如上图,给OUT1端口设了一个负载为5的值。这里的单位也是由Foundry提供,具体的单位,可以通过report_lib命令查看,一般而言是pf。
图52 设置输出负载(2)
图52中设置负载采用了第二种方法,从图中可以看出,第一条语句说明OUT1端口接的负载值是my_lib中and2a0单元的A管脚的负载值。第二条语句则多用了TCL语言的表达式的语法,它说的是,OUT1相当于接了三个inv1a0单元的A管脚的负载值。一般后面的这种方法用的多些。
3.2.2.2 设置输入驱动
与设置输出负载类似,为了更加准确的估计模块输入的时序,我们同样需要知道输入端口所接单元的驱动能力。在默认的情况下,DC认为驱动输入的单元的驱动能力为无穷大,也就是说,transition time为0。
设置输入驱动是通过DC的set_driving_cell命令完成的。set_driving_cell是指定使用库中的某一个单元来驱动输入端口。该命令是在输入端口之前假想一个驱动单元,然后按照该单元的输出电阻来计算transition time,从而计算输入端口到门单元电路的延迟——
图53 设置输入驱动单元
如上图所示,它设置了模块输入端口IN1的驱动单元是工艺库中的and2a0。
3.2.2.3 设置工作条件
工作条件包括三方面的内容——温度、电压以及工艺。在Foundry提供的工艺库里,它的各个单元的延时是在一个“标准”(nominal)条件下得到的,比如说温度25.0度、工艺参数1.0和工作电压1.8V。一旦工作条件发生了改变,电路的时序特性也必将收到影响,以上三方面的因素对电路时序的影响如下所示——
图54 工作条件对时序的影响
从图中可以看出,单元的延时会随着温度的上升而增加;随着电压的上升而减小;随着工艺尺寸的增大而增大。以上的这些工作条件的变化,Foundry在建库的时候已经考虑到了,因此它在工艺库中提供了几种工作条件的模型(operating condition model)以供设计者选择。这些工作条件一般分为三种:最好情况(best case)、典型情况(typical case)以及最差情况(worst case)。我们为了以后能使电路正常的工作在上面的三种情况下,在综合的时候就必需要将他们考虑进来。一般综合只要考虑到最差和最好两种情况,最差情况用于作基于建立时间(setup time)的时序分析,最好情况用于作基于保持时间(hold time)的时序分析。
在默认情况下,Design Compiler不会自动指定工作条件,我们可以先通过report_lib命令来列出在当前的工艺库里提供了哪几种工作条件——
图55 列出可选的工作条件
然后指定需要用到的工作条件,在做建立时间分析的时候需要用到最差情况的条件——
如果我们既要分析建立时间,又要分析保持时间那么就要同时指定最差和最好情况——
其中core_slow.db和core_fast.db分别是最差和最好条件下的工艺库文件,第一句话先用set_min_library设定作保持时间检查的库,第二句话则分别对应了两种时间检查需要用到的工作条件。
3.2.2.4 设置连线负载模型
在DC综合的过程中,连线延时是通过设置连线负载模型(wire load model)确定的。连线负载模型基于连线的扇出,估计它的电阻电容等寄生参数,它是也是由Foundry提供的。Foundry根据其他用这个工艺流片的芯片的连线延时进行统计,从而得到这个值。
下面是一个负载模型的例子——
图56 连线负载模型(WLM)
这个例子可以通过命令report_lib得到,它是ssc_core_slow这个工作条件下的一个名为
160KGATES的负载模型。其中时间单位为1ns,电容负载单位为1pf,电阻单位为1kΩ。从图中可以看出单位长度的电阻以及电容值,DC在估算连线延时时,会先算出连线的扇出,然后根据扇出查表,得出长度,再在长度的基础上计算出它的电阻和电容的大小。若扇出值超出表中的值(假设为7),那么DC就要根据扇出和长度的斜率(Slop)推算出此时的连线长度来。
事实上,在每一种工作条件下都会有很多种负载模型,各种负载模型对应不同大小的模块的连线,如上图的模型近似认为是160K门大小的模块适用的。可以认为,模块越小,它的单位长度的电阻及电容值也越小,负载模型对应的参数也越小。
设置输入驱动是通过DC的set_wire_load_model命令完成的。
如上面的语句,则设置了addtwo这个模块的连线负载模型为160KGATES。
另外我们也可以让DC自动根据综合出来的模块的大小选择负载模型,这个选项在默认下是打开的。如下图所示,当综合出的电路的面积小于43478.00时,使用5KGATES的模型,属于43478.00和86956.00之间时,使用10KGATES的模型。
图57 自动设置连线负载模型(WLM)
以上讨论的情况是一个模块内部连线的负载模型的估计。如果连线连接的是不同的模块,那么它的负载模型又将怎么估计呢?这就要用到连线负载模式(set_wire_load_mode)这个命令了。
图58 连线负载模式
连线负载模式一共有3种,围绕(enclosed)、顶层(top)以及分段(segmented)。如上图所示,一根连线连接了B2和B2两个模块,这两个模块都位于TOP下的SUB这个子模块中,围绕模式是指连接B1和B2的连线的负载模型用围绕它们的模块的负载模型代替,即用SUB
的负载模型;顶层模式是指用顶层模块的负载模型代替;分段模式顾名思义,分别根据穿过的三段的模型相加得到。如果要设置成围绕模式,可以使用如下命令——
在定义完环境属性之后,我们可以使用下面的几个命令检查约束是否施加成功—— z check_timing
检查设计是否有路径没有加入约束
z check_design
检查设计中是否有悬空管脚或者输出短接的情况
z write_script
将施加的约束和属性写出到一个文件中,可以检查这个文件看看是否正确
3.2.3 时序和负载预算
前面的两节里我们讨论了怎样给一个被综合的电路模块施加时序约束以及设置环境属性,大家学习完之后可能有这样一个疑问:模块的输入输出延时、负载和驱动单元的具体的值是怎样确定下来的呢?这一节里,我们就着重来探讨这个问题,也就是说怎样在综合模块之前先给它的时序和负载作一个预算,一般而言,这个工作是由项目的体系设计者(Achitecture Designer)完成,当他先确定好各个模块外围的时序和负载预算之后,再由具体的模块设计者(Module Designer)完成模块的综合。
图59 MY_BLOCK的时序预算
3.2.3.1 时序预算
先看时序预算,假设电路中有三个模块X_BLOCK、MY_BLOCK和Y_BLOCK,时钟周期是10ns。如果要先综合MY_BLOCK模块,我们可以看到它的输入部分的S电路必须和X_BLOCK中的N电路共享一个周期的延时,同样输出部分的S电路也必须和Y_BLOCK的N电路共享一个周期的延时。我们可以作这样一个估计,认为处于两个模块交界部分的S和N电路只能分别占用40%的周期延时(如下图所示),也就是说限定所有的S和N电路
图60 保守的时序预算
的延时为最大为4ns,这样可以看出S+N的延时总共只有8ns,小于10ns,因此这是一种保
守的预算方法,预留的2ns的延时可以在电路不满足时序的时候再加上。经过预算之后的MY_BLOCK模块的约束如下——
同样的方法,我们也可以得出另外两个模块的时序约束——
3.2.3.2 负载预算
图61 负载预算
负载预算也是在实际综合编译之前体系设计者根据模块将来可能的工作情况认为估计出来的,一般它的估计有几个原则:
z 保守起见,假设驱动模块的单元的驱动能力较弱 z 限制每一个输入端内部的负载电容
z 估计每一个输出端口最多可以驱动几个相同的模块
请看下面这个负载运算的例子——
图62 保守的负载预算实例
在这个例子中,我们假设输入的驱动单元是一个inv1a0的反相器,然后限制了最大的输入负载,即每个输入端口的负载最大不得超过10个二输入与非门的负载大小,同时也规定了一个模块最多能同时驱动三个同样大小的其他模块。
3.2.4 时序分析
Design Compiler是一个约束驱动(Constraint-driven)的综合工具,约束中最重要的就是时序约束,前面我们已经讨论了怎样在设计中施加时序约束,但是综合出来的电路能否满足这
些约束条件却是另一个重要的问题。在这一章里,我们主要将着重介绍Design Compiler内嵌的一个时序分析引擎Design Time,并分析它是怎样根据工艺库中的单元延时信息和连线负载模型分析电路的静态时序的,并介绍调用Design Time的命令report_timing。
图63 Design Time在DC中的位置
图63中告诉了我们Design Time是DC的一个内嵌的静态时序分析引擎,DC就是依靠它来计算电路的延时情况。DesignTime和PrimeTime都是静态时序分析的工具,但是两者并不完全相同,PrimeTime是在DesignTime的基础上发展起来的独立的专业的时序工具,而且效率和应用范围更高。①
图64 静态时序分析(STA)
静态时序分析(STA)是进行电路时序分析的一种方法,它的主要特点是分析不需要通过动态仿真,并且对电路的覆盖率更高。动态仿真(比如VCS)需要给电路施加一个激励,并检查输出信号,与理想信号比较,这种办法速度较慢,而且不一定能覆盖到所有的逻辑。
静态时序分析的分为三个步骤—— 1. 将电路分解成不同的时序路径(timing paths) 2. 计算每段路径的延时
3. 检查所有路径的延时,看是否能满足时序要求
下面我们就按照这三个步骤详细介绍DC是怎样分析电路的时序的。
①
有关PrimeTime,后续课程将有详细介绍
3.2.4.1 分解时序路径
DesignTime对时序路径的分解是根据时序路径的起点和终点的位置来决定的。每一条时序路径都有一条起点和终点,起点是输入端口或者触发器的时钟输入端;终点是输出端口或者触发器的数据输入端,如图64就有三条时序路径。另外根据终点所在的触发器的时钟不同还可以对这些时序路径进行分组(Path Group),如下图电路中存在4条时序路径,3个路径组,CLK1和CLK2组分别表示他们的终点是受CLK1和CLK2控制的,DEFAULT组则说明他们的终点不受任何一个时钟控制。
图65 时序路径和路径组
再例如下面的一个电路,一共有12条时序路径和3条路径组。
图66 时序路径和路径组举例
3.2.4.2 计算单个路径延时
时序路径的延时包含单元延时和连线延时,为了便于观察,我们将上图的时序路径进一步划分,得到如下图所示——
图67 时序弧
z 可以看出,每条时序路径被分割成了一段段的时序弧(timing arc),每条时序弧代表
的不是一个单元延时就是一段连线的延时。下一步的工作就是计算出它们的值。
单元延时的计算
单元延时的计算是根据单元延时模型进行的,这里介绍两种单元延时模型,线性延时模型(Linear Delay Model)和非线性延时模型(Nonlinear Delay Model),他们的计算方法如下图所示——
线性模型由三部分组成:
图68 延时模型(delay model)
DSlope表示单元输入信号的延时、Dintrinsic表示单元的固有延
时、RCell(CNet,CPin)表示输出的管脚电容和连线电容对单元的附加延时。
非线性模型是DC计算单元延时的主要模型。它分为两部分:单元的输入延时(transition time)和输出负载的函数。与线性模型不同的是,它是通过查找表的方式得到的,请看下面
一个例子——
图69 非线性延时模型的计算
对于一个单元来说,它有两张查找表,一张用于计算单元延时,另一张用于计算输出延时,并作为下一级单元的输入。在这个例子中,通过查表可以得到,单元延时为6ps,输出的transition time为25个单位,这个单位又作为下一级的输入。
不难看出,非线性的模型的计算类似波浪的传播,从上一个单元的延时得出下一个单元的延时,并以此类推,对于一个较大规模的电路来说,计算量是比较大的,但也能得到比较准确的结果。
连线延时计算及其拓扑
在设置环境属性这一节中我们讲到了连线负载模型,通过连线负载模型我们可以得到一条连线上的电阻和电容的值,但是仅仅有RC值并不能得到连线的延时,还需要知道这些RC的分布,RC分布有三种情况——
图70 连线拓扑(RC-树)
z best_case_tree是一种理想情况,它假设连线的电阻为零,平常很少使用 z balanced_tree认为连线的RC均匀的分布在各条负载支路上
z worst_case_tree假设RC值全部集中在负载共有的连线上,因此它的延时是最大的
连线的不同拓扑结构是通过工作条件的不同体现出来的,工作条件不但影响连线的延时,还通过温度、电压和工艺的变化影响单元延时的计算。
图71 工作条件对连线延时的影响
3.2.4.3 计算整条路径的延时
DesignTime计算完所有的路径延时之后的下一步工作就是根据这些延时,找出电路中延时最大或者最小的路径来。对于设计者而言,他们或许不关心每个单元的延时而更加关注到底电路是不是满足了设定的时序约束的要求。例如下图,有两个触发器FF1和FF2,它们之间是一个很多单元组成的组合逻辑云,当作建立时间检查的时候,设计者就需要知道这个逻辑云的最大延时是否满足建立时间,即最大延时加上FF2的建立时间是否小于一个周期的时钟周期。
图72 建立时间检查
那么怎样计算整条路径的最大延时呢?是不是把这条路径上的所有单元和连线的最大延时简单相加得到的呢?答案是否定的,因为这里涉及到一个时钟边沿敏感性(Edge Sensitivity)的问题。
图73 时钟边沿敏感性
如上图,一条路径上有两个不同的反相器,他们的固有上升时间和下降时间都不同,要计算他们的最大最小延时,需要弄清楚他们的工作过程——当第一个反相器的输入在时钟上
升沿时,它的延时是1.2,同时第二个反相器处于时钟的下降沿,延时为0.3;反之,当第一个反相器输入为时钟下降沿时,它们的延时分别为0.5和1.5。因此,最大路径延时不是简单的1.2+1.5,而是分别检查0.5+1.5,在输入为时钟下降沿得到。实际上,DesignTime在计算每条路径的时候,都会考虑边沿敏感性,即分别根据上升下降沿计算两次。
3.2.4.4 用report_timing检查时序
上面几节讲的是DesignTime作静态时序分析的基本原理和步骤,这节介绍介入DesignTime的命令——report_timing。
report_timing命令的具体参数如下,具体信息请查看DC的man页。
默认的时候report_timing报告每一条路径组中的最长路径。报告一共分为4个部分——
图74 report_timing(1)
第一部分显示了路径的基本信息——工作状态是slow_125_1.62,工艺库名称为ssc_core_slow,连线负载模式是enclosed。接下来指出这条最长路径的起点是data1(输入端口),终点是u4(上升沿触发的触发器),属于clk路径组,做的检查是建立时间检查(max)。这一部分的最后还报告了电路的连线负载模型。
图75 report_timing(2)
第二部分列出了这条最长路径所经过的各个单元的延时情况,分成三列:第一列说明的是各个节点名称,第二列说明各个节点的延时,第三列说明路径的总延时,后面所接的f或者r则暗示了这个延时是单元的哪个时钟边沿。例如图中的路径经过了一个反相器,一个二输入与非门,一个二输入MUX,最后到达D触发器。其中反相器的延时为0.12ns,路径总延时为1.61ns。
图76 report_timing(3)
第三部分说明了这条路径所要求的延时,它是设计者通过时序约束施加的。例如时钟周期为5ns,触发器的建立时间为0.19(从工艺库中得到),要满足建立时间的要求,组合路径延时必须在4.81ns之内。
图77 report_timing(4)
第四部分为时序报告的结论,它把允许的最大时间减去实际的到达时间,得到一个差值,这个差值称为时序裕量(Timing margin),如果为正数,则说明实际电路满足时序要求,为负数则说明有时序违反。上图的裕量为3.20,说明最长路径满足建立时间的要求,且有3.20ns的裕量。暗含的意思是所有这个clk组的路径都满足建立时间的要求,并且裕量大于3.20ns。
图78 时序裕量
report_timing除了报告电路综合后的时序之外,还可以帮助我们诊断综合电路中存在的时序问题,下面是一个例子——
图80 诊断综合结果
这个例子仅仅列出了报告的第二部分,报告的右边有四头鲸,它们分别指出了电路中存在的四个问题:
第一个问题出现在input external delay一栏,这一栏对应的就是时序约束中施加的
input_delay,它的值为22.4ns,假设时钟周期为30ns,那么可以看出这个input_delay就已经占据了整个周期的70%多,因此留给下面单元的裕量就很少了。需要考虑是不是需要设置这么大的input_delay。
第二个问题出现在路径中的6个串连的buffer上,它们对应的单元分别是INVF、NBF、BF、BF、NBF以及NBF。就逻辑功能来看,6个显得多余,而且产生了不必要的延时。 第三个问题是由一个延时为10.72的或门造成的。其他的单元延时一般不超过2ns,一个10.72的单元是不是因为负载过重引起的呢?值得仔细审查。
最后一个问题反映在这条路径穿过的层次上。从设计分层那一节可以知道,要使延时最小,应该把组合路径全部放在一个模块内。而这条组合逻辑同时穿过了u_proc、u_proc/u_dcl、u_proc/u_ctl以及u_init四个层次,可以通过group/ungroup命令把层次重新组织一下。
3.2.5 DC Tcl初步
TCL的全称是Tool Command Language,它是由UCA berkeley开发的一种开放的工业标准语言。和dc_shell相比,Tcl功能更加强大,使用范围也更加广泛,除了Design Compiler外,synopsys的其他工具如Formality、PrimeTime、Physical Compiler等等都支持Tcl。另外一些EDA厂商的工具也大多使用Tcl界面。①
DC除了DC-Tcl之外,还支持较老的命令行方式dc_shell,我们可以在Unix Shell下通过使用dc-transcript命令将dc_shell的脚本转化为DC-Tcl——
3.2.5.1 执行DC-Tcl脚本
Tcl脚本类似于windows环境下的.bat批处理文件,就是将要执行的Tcl命令写在一个脚本中,让后调用dc_shell-t执行之,这样可以提高综合的效率。
执行Tcl脚本有两种方式:一种是在dc_shell-t中调用——
另一种是直接在Unix Shell中调用——
上面的>my.log表示将DC输出定向到my.log文件中,|tee my.log表示除了定向到my.log文件外还要在屏幕中输出。
①
更多Tcl内容,请访问http://www.scriptics.com
3.2.5.2 在DC-Tcl中获得帮助
在Tcl环境中最基本的获得帮助的命令就是help——
直接键入help后将列出DC-Tcl中的所有命令。另外我们也可以使用通配符(“*”)找到需要查找的命令,例如要找到含有clock的命令——
如果要进步找到某一个命令的参数,就可以使用help –v 选项,下面这个例子就列出了所有的set_input_delay的开关——
注意:假设要看这个命令的更详细的帮助,就只能借助man命令。
3.2.5.3 DC-Tcl中的注释
图81 Tcl中注释
DC-Tcl的注释有两种:行注释和行内注释。行注释和Cshell类似,只要在开头加井号即可(“#”);行内注释除了在注释前加井号外还要在井号前加分号(“;”)。
3.2.5.4 DC-Tcl 中的变量
1. Tcl变量可以通过set来创建和赋值,如
设置变量my_var的值为10。
2. 引用变量的值,需要在变量名之前加入”$”符号,如
将my_var的值赋给了新的变量my_New_Var。
3. 打印变量的值,使用echo语句,如
4. 删除变量,使用unset语句,如
5. 变量的弱引用,使用双引号,如
上面的data[$a]中变量a的值为5
6. 变量的强引用,使用大括号,如
上面的data[$a]中的a将不再作为一个变量使用。
7. 变量中的表达式,使用expr语句,如
注意expr两边的空格。
3.2.5.5 DC-Tcl中的列表
列表是一般Tcl语言的组成部分,它是一串元素的集合,中间以空格分开。列表相当于C语言中的字符串。如下面的语句定义了一个L1列表,包含3个元素,echo$L1语句返回所有的元素值,llength用于返回这个列表的长度。
另外lappend语句用于给列表附加新的元素——
3.2.5.6 DC-Tcl中的集合
集合(Collection)是DC-Tcl特有的数据类型,它主要用于描述有多个属性的元素的集合,它是为了描述硬件而设计的。例如电路中的端口(ports),具有输入/输出、驱动单元、最大电容等属性,电路中的设计(designs),具有面积、工作条件、最大面积等属性。要描述这些具有属性的元素的集合,列表是不能胜任的。因此引入了集合的概念,它相当于C语言的指针。每一个集合都有一个集合柄(collection handle),相当于指针指向的地址。
我们可以通过”get_”语句和”all_”语句创建一个集合,DC-Tcl用到的创建集合的命令有如下一些(部分)——
为了便于和list对比,下面举了一个例子——
可以看到,这里先创建了一个mylist的列表和foo的集合,llength返回的foo的长度为1,echo $foo返回的是集合柄(_sel5),这些是与列表不一样的。我们可以用sizeof_collection类似llength,用query_objects类似echo。
在DC-Tcl中查找所有包含有collection的命令,可以得到——
其中remove_from_collection前面章节已经接触过,它表示从集合中删去元素;相反的,在集合中增加元素的命令是add_to_collection,下面是一个例子——
这个例子灵活的使用了变量定义了时钟周期和时序预算,这样的好处是参数化,便于以后修改。另外还定义了IO_DELAY变量和all_except_clk集合。请大家仔细观察上面的约束脚本的写法,并争取能在自己的脚本中引入上述方法。
另外一个比较重要的命令是filter_collection,它相当于一个过滤器,找出符合要求的元素形成一个新的集合。例如——
从所有的cell的集合中过滤出引用名为AN2的一个,返回值为一个新的集合
从所有的cell集合中过滤出映射过的cell,返回值为一个新的集合。 除了filter_collection之外,-filter选项也能完成相同的任务——
创建一个设置了don’t_touch属性的cell的集合
创建名为fastclks的clock的集合,它的周期属性小于10(ns)
3.2.6 高级时钟约束
图82时序约束回顾
在3.2.1时序和面积一节钟,我们讨论了一些简单的时序约束,如定义时钟,设置模块的输入输出延时等等(如上图),但是这些时序约束都是简单的约束,离较大规模芯片的实际工作条件还有一定的差距,比如时钟信号只有一个,并且周期严格遵守给定的值。在这一节里,我们着重讨论时钟约束的下面几个问题——
z 非理想的单时钟网络 z 同步多时钟网络 z 异步多时钟网络 z 多周期路径
3.2.6.1 非理想的单时钟系统
图83 实际的时钟网络(时钟树)
前面提到过,在定义时钟之后,都要给该时钟设置一个dont_touch,这是告诉DC不要给时钟网络作综合,因为综合时钟网络需要考虑单元的实际物理位置,这是前端的逻辑综合目前不能完成的工作。如上图所示,实际的时钟网络也称为时钟树,它在时钟的各条路径上产生大小不一的buffer,目的是为了保证时钟到达每个触发器的延时尽量相等。
虽然DC无法最终综合时钟树,但是我们可以加入一些约束让此时的时钟更加接近实际的工作情况。例如,实际的时钟达到各个触发器的时间不是一样的,它们之间有一个偏差,称为时钟偏差(Clock Skew),为了反映这个偏差,我们在综合的时候可以用一个命令来模拟
它,即set_clock_uncertainty,下面是一个例子——
图84 模拟时钟偏差
假设时钟周期是10ns,FF2的建立时间为0.2ns,预先估计时钟偏差为0.5ns,从FF1触发的数据必须在一个周期之内到达FF2,当引入时钟偏差以后,所谓的一个周期就不再是10ns,而可能最短为10-0.5=9.5ns,再减去0.2的建立时间,实际留给X路径的延时最大只能有9.3ns。
除了时钟偏差(Clock Skew)之外,还有两个命令值得注意,这就是set_clock_latency,和set_propagated_clock,如下图所示——
图85 时钟延时
一般而言,时钟都是由一个专门的模块(Clock_gen)来生成的,这里称为时钟源(Clock Source),时钟产生之后,必定要经过一段网络延时才能到达被综合的模块,这段延时称为时钟源延时(Source Latency),到达模块的端口后,要到达内部的触发器,也要经过一定的延时,这个延时称为网络延时(Network Latency)。这分别通过set_clock_latency –source 和set_clock_latency来描述。另外,set_propagated_clock主要用在布局之后(post-layout)的综合上,意思是说,此时的网络延时已经可以由时钟树上的buffer确切的推断出来。 布局之前和布局之后描述时钟的命令对比如下图——
图85 布局前后时钟描述对比
在布局前正因为没有buffer,才需要用网络延时和时钟偏移来模拟布局后的情况。
3.2.6.2 同步多时钟网络
图86 同步多时钟网络
上图是一个同步多时钟网络,中间的模块是我们要综合的模块,内部只有一个CLKC,但是输入和输出都是由不同周期的时钟控制的,也就是说,它们属于不同的路径组(Path Group),但是这些时钟CLKA-CLKE都是从一个时钟分频得到的,因此称为同步多时钟。 观察被综合模块的输入端口,它同时受两个时钟的约束,由于它们周期不同,所以CLKA触发的信号到达FF2的时间也不是固定的。
对于这样的时钟网络,我们需要用到虚拟时钟的概念(Virtual Clock)。对上图要综合的模块而言,除了CLKC之外的其他时钟都可以称为虚拟时钟,它们有如下要求——
z 在顶层模块之内的其他模块内定义的时钟
z 在当前的被综合模块(current_design)内不包含虚拟时钟驱动的触发器 z 作为当前模块的输入输出延时参考
定义虚拟时钟和定义时钟的命令差不多,只是不要指定虚拟时钟的端口或者管脚,另外必须指定时钟的名字——
如上述语句指定了一个周期为20ns的虚拟时钟vTEMP_CLK。
定义了虚拟时钟之后,我们就可以描述图86的电路时序了,先设定输入延时(set_input_delay),从图中看出,CLKA和CLKC的周期分别为30ns和20ns,假设IN1端口的输入延时为5.5ns,可以设定如下——
图87 同步多时钟的输入延时
值得注意的就是先定义虚拟时钟CLKA,然后在set_input_delay的驱动时钟开关中选择CLKA。在设定完输入延时之后,Design Compler就会在CLKA和CLKC的所有情况中找到其中最短的周期,作为对这段路径的约束——
图88 寻找输入最小周期
寻找最小周期的过程如下:先计算CLKA和CLKC的最小公约数(30和20的公约数)为60ns,即两个CLKA的周期,然后分别以这两个上升沿为触发沿,计算此时的最短捕捉(被CLKC接收)的时间,最后对比这两个时间,取其中最小的一个。如下图计算出的最短捕捉时间为10ns,因此留下给路径N的延时为——
按照上面同样的方法,我们也可以得出图86的输出延时(set_output_delay),如下图所示——
图89 同步多时钟的输出延时
值得注意的是:这里的输出同时驱动两个虚拟时钟电路CLKD和CLKE,因此描述第二个延时的时候需要加入-add_delay的开关,否则将覆盖前一条路径的约束。
这里的各时钟波形关系如下所示——
图90 寻找输出最小周期
可见,CLKC到CLKD的最小捕捉时间为6.7ns,到CLKE的最小捕捉周期为10ns。因此输出最小周期为6.7ns和10ns,对应的输出电路的延时必须满足下面的条件
从前面的推导过程可以看出,DC是在各个相关时钟周期的最小公约数的基础上计算最小输入/输出时间的,因此我们在定义时钟的时候尽量选用整数,不要加上小数点(比如20ns和30.1ns),避免不必要的麻烦。
3.2.6.3 异步多时钟网络
异步多时钟网络和同步多时钟网络的结构类似,只是它的各个时钟CLKA-CLKE不是从同一个时钟源中分频产生的,而可能是不同的两个晶振,如下图所示——
图91 异步多时钟网络
由于是不同的晶振产生的时钟,它们之间的就不存在最小公约数的关系,但是在默认情况下,DC并不知道,它会认为它们是同步的时钟网络而尽量去找两个时钟之间的最小捕捉时间,不但浪费了时间而且会产生出不符合要求的电路。在这种情况下,我们需要告诉DC不要管两个时钟之间路径的时序,这里需要用到一个命令——set_false_path。
False Path(伪路径)是指电路中的一些不需要考虑时序约束的路径,它一般出现在异步逻辑之中。上面的例子设置伪路径的语句如下——
这样,所有的从CLKA到CLKB和CLKB到CLKA的路径都被认为是伪路径。
3.2.6.4 多周期路径
在前面的讨论中,我们默认所有组合路径的延时都是一个周期(如图91中的N和X路径),然而实际电路中也可能存在超过一个周期的路径,如下图所示——
图92 多周期路径
在FF1和FF2之前,存在一个BIG_LOGIC,假设我们允许它的延时在两个周期之内,那么因该怎样把这个信息告诉Design Compiler呢?这里就需要用到DC的一个设置多周期路径的命令——set_multicycle_path①
第一个语句说明建立时间是在FF1触发后的第二个周期后检查,第二个语句说明保持时间在FF1触发后的第一个周期检查。可得此时的波形图如下——
图93 多周期路径波形
①
set_multicycle_path在PrimeTime的课程中将有详细介绍
3.3 设计综合过程
在前面一章介绍完施加约束之后,接下来要做的工作就是将设计进行综合编译(compile),这一章我们将主要讨论综合编译的过程。这一章主要分为这样几个部分——
z 优化的三个阶段及其特点 z 编译的策略 z 编译层次化的设计
3.3.1 优化的三个阶段
这一节我们介绍Design Compiler进行优化的三个阶段:结构级、逻辑级以及门级,在不同的阶段,DC运用的方法和优化余地是不一样的,通过这一节的学习,你将对这几个阶段的特点和优化方法有一个比较清楚的了解,其中有一些方法我们在前面的章节中已经提及过,这里一起归纳一下,希望能加深大家的认识。
图94 DC优化的三个阶段
上图是这三个阶段的关系图,可以看到,结构级属于最高的抽象层次,当读入Verilog代码或者没有经过映射的db文件后,DC的优化从这个阶段层次开始,可以说,结构级是优化的最高层次,所以对DC来说,这个层次的综合可以称为高层次综合(High-Level Synthesis)。结构层的下一个优化阶段是逻辑级阶段,也是读入映射过的db文件的DC的初始优化阶段。再往下一个阶段是门级阶段,也是优化的最后阶段,这里所要作的工作主要就是GTECH到工艺库的映射。
3.3.1.1 结构级优化
因为结构级是优化过程中层次最高的一级,因此它也是DC可以采用的优化方法最多的
一级,它的主要优化方法如下图所示——
图95 结构级优化
1. DesignWare选择
DW选择是结构级优化的一个很主要的特点,在这个阶段DC能够根据设计者施加的时序或者面积的约束在DW的不同实现方式中找到它认为最佳的实现方案。比如加法器的实现方式一共有如下几种——
图96 DW 选择
其中DW Foundation需要有专门的license,而且使用之前还要设置综合库(synthetic library)。
2. 共享子表达式(Sub-Expressions)
这里的子表达式主要是指数学表达式,比如下面这个例子,如果按照原来的语句综合,会包含6各加法器,但是如果表达式之间的公共项提取出来,便可以大大的减小面积,如下图①——
①
这里的RTL级描述使用的是VHDL语言
图97 共享子表达式
如果要直接综合出共享后的电路,可以在编写RTL代码的时候强制指定共享项——
3. 资源共享(Resource Sharing)
资源共享的原理与共享子表达式类似,只不过这里指的所谓资源是一些HDL的运算符和表达式,比如加(+)、减(-)、乘(*)、除(/)以及大于(>)、大于等于(>=)、小于(<)、小于等于(<=)。这里举的例子,在前面的章节里也见过,比如给定一个语句——
它可能有下面两种电路实现——
图98 资源共享
DC会根据具体的约束条件综合出最符合要求的结构来。
4. 运算符排序(Operator Reordering)
对于下面这个表达式(输出Z是施加了一定时序约束),DC最初是按照从左至右的顺序计算的,也就是说它最初的排序如下——
图99 预算符排序(1)
如果几个输入信号到达的时间相同,DC会通过运算符排序优化成下图的平衡的结构,减小延时——
图100 预算符排序(2)
如果A信号较迟到达,则综合出的电路结构会如下——
图101 预算符排序(2)
3.3.1.2 逻辑级优化
在经过结构级优化之后,电路被转化成了工艺无关的GTECH库的形式,这级也称为逻辑级,对于逻辑级优化来说,只有一个方法,那就是——结构化 (structure)或者扁平化(flatten)。
1. 结构化(structure)
图102 逻辑级优化
结构化是DC在逻辑级的默认的优化方法,它是指:使用电路的一些中间项构成一个多级的电路结构。如下图的电路一共有三级逻辑,下一级的输入是上一级的输出,使用这种优化方法一般情况下能综合出兼顾时序和面积的电路来。
图103 结构化电路
结构化电路的典型是奇偶校验电路。 2. 扁平化(flatten)
扁平化是将所有的组合逻辑打平成乘积项和(SOP)的两级结构①,类似与可编程阵列逻辑(PAL)。使用这种结构的特点由于没有利用中间项,综合后电路面积将会变得很大,但是却不一定能取得较好的时序。
扁平化结构的电路和设置扁平化的DC命令如下所示——
图104 扁平化电路
综合结构化和扁平化的特点,可以归纳如下——
①
由于工艺库单元的类型和驱动能力的限制,最终电路不一定是两级结构
图105 结构化Vs扁平化
Bad candidates for SOP flattening Arithmetic components
XOR structures (parity trees) Blocks with many muxes
Good candidates for SOP flattening
State machines
Random logic
Blocks that use don’t-cares
由于DC默认是用结构化的方式综合逻辑级电路,而且这种方式可以得到兼顾时序和面积的结果,因此我们可以先用这种方式优化。在优化后的电路中找出关键路径,看看关键路径上有没有符合使用SOP电路的模块,再将这些方便使用SOP的模块set_flatten,以便取得最佳的效果。
3.3.1.3 门级优化
门级优化是优化的最后阶段,它所要完成的任务就是将GTECH的电路映射到最终的工艺库中,并且保证映射后的电路不违反设计规则(Design Rule)。
1. 工艺映射
图106 门级优化
工艺映射包括组合逻辑映射和时序逻辑映射。组合逻辑映射是指DC使用工艺库中
的各种门替换GTECH单元,并选择能实现相同逻辑的符合时序及面积要求的单元——
图107 组合逻辑映射
时序逻辑映射的方法和组合逻辑相类似,也是出于速度和面积的考虑,尽量使用复杂的时序单元吸收一部分组合逻辑。
2. 设计规则检查(DRC)
对于工艺库的单元而言,Foundry都指定了每个单元的工作条件的限制,比如最大电容(max_capacitance)等等,这些限制也可以称为设计规则(Design Rule),在设计规则限定的范围内,Foundry提供的参数才有实际的意义。比如一个单元允许的最大电容为5pf,而实际工作电路中出现的电容值为10pf,那么这时,便违反了设计规则,单元的参数也就不能确保是准确的了。
因此,DC在作门级优化的时候,在映射的过程中也会检查电路的设计规则,一般的做法是在单元中插入buffer增加驱动能力,或者将小驱动的单元替换为大单元。设计规则检查分为两个过程——DRC I 和DRC II。(如图106所示)
DRC I是指Design Compiler在不影响电路的时序和面积的前提下修正违反规则的一些单元,如果在这个前提下不能完全修正,则要进行下一步的检查,即DRC II,这一步的修正必然是以牺牲一部分时序和面积为代价的。
图108 时序逻辑映射
3.3.2 编译策略
编译过程是指设计经过三个阶段的优化,最终形成门级网表的过程,在这一节里,我们主要就编译的策略,它包含如下几方面的内容——
z 中断编译的方法
z 从报告中检查时序,调整策略 z 修正保持时间违反(Hold time violations)
3.3.2.1 中断编译的方法
在DC-Tcl的界面下,当我们键入compile命令时,DC就开始了编译,也就是优化的过程。优化是在设计规则的条件下,运用不同的算法,综合最终出满足时序和面积的电路。优化首先是时序驱动(timing-driven)的一个过程,其次再是面积。如果找到了一个满足时序和面积等约束的电路,编译将会停止;如果通过种种编译仍不能满足时序,编译也会停止下来;另外,我们也可以人为的中断编译。
人为中断编译的方法是键入Ctrl-C,经过一段时间的等待后(有可能时间会很长),优化过程暂停,并弹出如下菜单——
这里有四个选项,设计者可以根据情况作出选择。
DC在编译的过程中,会自动打印出一个报表,报告编译的总时间,设计的面积,关键路径的时序违反和总共时序违反情况,我们可以根据需要更改打印的列项目——
图109 编译报表
3.3.2.2 分析报告,调整策略
一般情况下,我们先作一个默认的编译,这样一般可以取得既快又准确的结果,然后在编译完成后使用一些报告时序的命令,并分析它们的输出结果,使用的命令主要有——
1. report_constraint –all_violators 报告电路中所有没有满足的约束条件,包括设计规则、建立时间、保持时间以及面积。通常这应该是最先执行的命令。 2. report_timing –delay max
报告基于建立时间检查的关键路径,每一个路径组的关键路径都被报告出来。 3. report_timing –delay min
报告基于保持时间检查的关键路径,每一个路径组的关键路径都被报告出来。
从这些报告中,我们可以看到电路中是否有违反的约束,如果有,那么它是什么类型,还有电路中的最大负裕量(worst negative slack)是多少,等等。下面我们就几个常见的约束违反情况,谈谈纠正它的综合策略——
较大的时序违反
请看下面这个例子——
图110 较大时序违反
从report_constraint –all这个命令的报告可以看出,需要到达的时间是1.20ns,而实际到达为2.84ns,违反了1.64ns。之所以判断它是一个较大时序违反的情况,并不是因为1.64这个绝对值很大,而是相比较需要时间而言,1.64是一个较大的值。一般而言,如果电路中的最大负裕量(简称WNS)所占时钟周期的15%以上的话,可以认为电路存在较大的时序违反。
确认存在较大时序违反之后,下一步就是找出原因,消除违反情况。可供参考的步骤有下面几种——
1. 检查约束条件,看是否有疏漏或错误 2. 检查模块划分,看组合逻辑是否穿过多个模块 3. 重新编译优化后的网表 4. 修改RTL代码
下面详细讨论后面的三种情况—— z 重新编译(Re-Compile)
当重新读入映射后的网表进行重新编译时,DC会自动将门级的网表重新返回到GTECH的结构,相当于逻辑级。然后分别进行逻辑级和门级的优化,但是同时也可以进行DesignWare的替换。 如果设计者仅仅将映射后的网表拿来再做一次compile,编译后的结果并不会不一定会比原来的好,无非把以前做过的优化再跑一遍。因此,重新编译之前会改变一些参数,如——改变设计约束、改变set_structure和set_flatten参数以及改变编译的map_effort。
z 改变map_effort重新编译
对设计进行编译的时候,有三种编译级别可以选择,它们分别时低级、中级和高级。
不同的级别编译要求的编译时间和编译结果都各不相同,compile –map_effort low编译时间最短,但是结果不一定好,它一般用于设计预估(Design Exploration)①,不用在重新编译环节。
compile –map_effort medium是DC默认的编译级别,大多能在一定的时间内得到较为满意的结果。这也是我们推荐的初始编译级别。
compile –map_effort high 编译的过程中会使用前面的级别中没有的算法,因此它所要求的时间是最多的,结果也是相对最好的。这种级别一般用在重新编译的阶段。
z 修改RTL代码
修改源代码所能取得的效果是最直接的,同时也是代价最高的。修改代码后,DC会从最上层的结构级开始优化,前面也讨论过,越上层次的优化方法越多。所以通常这样得到的结果也越满意。但是,修改代码也不一定放之四海皆准的方法,因此并不是所有的设计我们都能获得源代码,同时也不是可以随便修改的。
图111 重新编译
①
设计预估在后面章节将有详细介绍
较小的时序违反
请看下面这个例子——
图112 较小时序违反
从上图看出,相比较1.20的允许路径延时,0.10的负最大裕量(WNS)是比较小的(小于15%),而且已经认定了约束和模块划分都是正确的,那么应该怎样修复这个错误呢?
这里主要讲一下Incremental Mapping—— z
这个开关告诉DC,在重新编译的时候不需要把网表返回到GTECH结构,因此也不需要作逻辑级优化,速度也较一般的编译更省时间。这里DC所要作的是进行门级单元的替换,即在不违反设计规则的情况下用延时小的单元替换延时较大的单元。另外,如果读入的是db格式的网表,在这个阶段也可以进行DesignWare的替换。
图113 重新编译(-inc)
z
这里多加了一个-map high的开关,-map high开关前面已经提到过,它是让DC使用更多的优化算法优化电路,与上面的优化不同,这里需要把层次提高到结构级,如右图所示。
需要注意的一点是:这里所指的优化仅仅优化电路中的关键路径(critical path),也就是说,如果电路中的一个路径组中有多条路径违反,优化后也不可能全部满足时序。
如果要同时优化多条路径,需要使用另外一个命令——set_critical_range
图114 重新编译(-inc –map high)
这个命令设置的critical_range是以WNS的值为基准的,优化的是和这个值的绝对值差设置值的那些路径。因此,如果设置值为0,那么就仅仅优化一条关键路径。
例如,假设电路中的WNS为-3.4ns,如果设置了
那么,当前设计中的所有负裕量的绝对值大于1.4的路径都将被优化掉。
图115 set_critical_range
设计规则违反
有些时候的时序违反是由于设计规则违反引起的,比如说一个单元的扇出(fanout)过大,导致它的transition time的时间迅速增加。对于这种情况,我们可以通过
两个命令审查连线的连接和负载情况。
要修正设计规则的错误,可以使用一个编译的开关
如下面这个例子,为了满足最大电容的规则,在A端口的内部加上了一个buffer,用于缓冲N路径对A的负载。
图116 修正设计规则违反
3.3.2.3 修正保持时间违反
一个时序电路要想正常工作,除了必须满足建立时间要求之外,也需要满足保持时间要求。
保持时间的概念和设置
图117 保持时间(1)
从前面的课程可以知道,为了满足建立时间,X路径的延时加上FF3的建立时间必须小于CLK一个周期的时间。这样做可以保证从FF2触发的数据能在一个周期后被FF3捕捉到。 同样,触发器还有一个保持时间,它是指在时钟边沿过后的一段时间触发器输入必须稳定,否则就会出现数据异常。为了满足这个条件,就必须要使得触发器在一个时钟边沿触发数据后必须等待一段保持时间才能接收新的数据。上图中假设FF3的保持时间为0.5ns,可见,在图示的上升沿,FF2触发新的数据,FF3捕捉了FF2在前一个周期触发的数据,如果X路径延时足够小,那么有可能在0.5ns之内FF2触发的新数据也到达了FF3的输入端,这样就引起了保持时间违反。 从上面的分析不难看出,保持时间容易出现在组合路径延时较小的路径中。下面我们分析时钟偏移(Clock Skew)对保持时间的影响。
图118 保持时间(2)
假设CLK到达FF3比FF2晚0.5ns,那么从上图可以看出,FF2的新数据到达FF3的可能性会比没有Skew增加。它现在的保持时间要求也从05ns提高到1.0ns。
除了时钟偏移之外,工作条件也会对保持时间产生一定的影响。工作条件的变化直接影
响到的是各个单元(时序和组合)的延时。前面讨论过,最差情况(Worst case)下组合电路的延时最大,所以检查建立时间时用的都是最差情况。相反的,在最佳情况(Best case)情况下,组合电路的延时会变小,产生保持时间违反的可能性也增加了。
虽然保持时间检查和建立时间检查是同样重要的,但是我们在实际综合的过程中却不是把它们同时考虑,而是更多的把保持时间的检查放到布局后。这是因为——
z 时钟偏移必须要到布局完成后才能得到准确值
z 修正保持时间的通常做法是插入buffer,而这可能会增加建立时间违反的可能性,
并且增大了组合电路的面积 z 保持时间检查用的一般都是电路工作的最佳条件,而在这个条件下,连线延时往往
是被忽略的,连线延时也是必须在布局后才准确 如果确定要同时作建立和保持时间检查,那么在施加电路约束的时候要加入相应的开关,比如——
以及设置各自的工艺库——
下面详细谈一下设置保持时间的输入/输出延时—— z set_input_delay -min
图119 保持时间的输入延时
上面描述的是基于保持时间的输入延时,需要设置外围输入电路最快到达被综合模块输入端口的时间,假设CLK周期10ns,FF2的保持时间为1ns,输入最小延时0.3ns,那么可以写成——
可以推断出,此时N路径必须满足的最小延时为1ns-0.3ns=0.7ns。 z set_output_delay -min
图120 保持时间的输出延时
图中所示,FF4是输出电路外围的一个触发器,它的保持时间是0.5ns,T路径的最小延时是0.3ns,那么得到FF3所在电路的输出最小延时(set_output_delay –min)就要比较小心,它的计算公式不是0.5-0.3=0.2ns,而是0.3-0.5=-0.2ns。这一点需要引起大家的注意,此时留给S路径的最小延时为0.2ns。
设置最小输出延时的命令如下——
修正保持时间违反
默认情况下,DC不修正保持时间的违反。如果确定要作修正,需要先设置一个变量再作检查——
加上only_design_rule的开关后,编译过程中仅仅更换单元大小,并增加buffer,以便修正设计规则违反和保持时间违反
下面是一个设置保持时间约束和修正保持时间违反的脚本——
3.3.3层次化设计的编译 3.3.3.1 层次化设计的编译过程
一个层次化设计的编译过程包含两个阶段—— 1. 将所有的子模块映射到门级
图121 层次化设计的编译(1)
从上图可以看出,D_design含有三个不同的模块,在编译的这个阶段,U1、U2、U3分别由RTL级映射到门级,并且各个模块之间的层次关系保持不变。在映射的过程中,设计约束都暂时没有考虑。
2. 优化
在这个阶段,Design Compiler根据各个子模块的时序和面积约束对它们分别进行优化,并且在优化的过程中要考虑到不同子模块周围的环境,修正产生的错误。
图122 层次化设计的编译(2)
3.3.3.2 多次例化模块的编译
在一个层次化的设计中,我们可能会遇到下面这种情况——
图123 多次例化的单元
上图中,被综合的模块中D_design中含有三个子模块U1、U2和U3,其中U1和U3都是由模块Ades例化而来,这里的Ades称为多次例化的模块。对于这样一个设计,在compile之前使用check_design作检查的时候会报一个warning,即设计中存在多次例化的模块(multiple instantiations),如果在这种情况下,我们不考虑多次例化的模块(Ades),那么在继续的compile时候程序会终止退出。因此,必须对它进行处理,这一节里我们介绍两种方法——uniquify和compile + dont_touch。
z 方法一:uniquify
使用uniquify,DC会对每个例化的模块作一份拷贝,然后对它们分别取一个名字,即把不同的例化模块当作不同的两个模块处理——
图124 uniquify
注意看上图,U1和U3两个模块的设计名分别由原来的Ades变成了Ades_0和Ades_1,因此在编译时,DC会将它们当作两个不同的模块,这样就可以根据它们不同的周围环境作优化。
使用uniquify的具体实现方法如下——
这段脚本与以前的脚本只有一处不同,即在compile之前加上uniquify这一行。
z 方法二:compile + dont_touch
这种方法先将多次例化的模块作单独的约束和编译,然后在整合到上一级模块的过程中将它的属性设置为dont_touch,再编译。
图125 compile+dont_touch
上图中,U1和U3两个模块的设计名都没有变化,只是在编译D_design之前先将Ades编译一次。这样U1和U3实际上是一模一样的模块。
compile+dont_touch的实现方法如下——
这里的约束文件有两个,一个是Ades的Aconstraints.tcl,另一个是D_design的Dconstraints.tcl,并且在source后一个约束文件之前要对编译过的Ades设置成dont_touch。
在设置了dont_touch属性之后,编译D_design的时候就会忽略Ades,这样有好处也有坏处,好处是可以保护模块不被修改,但是这样同时也限制了DC对U1和U3的进一步的优化。
z uniquify Vs compile + dont_touch
通过对上述两种方法的介绍,我们不难看出它们各自的优缺点——
compile+dont_touch由于只需要对多次例化的模块编译一次,因此可以减少整个设计的编译时间,也可以减少内存的使用量。在多次例化的模块很复杂并且工作站的硬件条件有限的情况下,使用这种方法的优越性的比较明显的。还有,如果这个Ades是一个第三方提供的硬核(hard-core),那么我们也只能使用这种方法。
使用这种方法的缺陷也是显而易见的:由于顶层模块在编译的时候Ades设置了dont_touch,这就妨碍了DC针对Ades的各个实例周围环境的不同的进一步优化,从而使得结果不能真实的反映各个实例周围的环境变化。
uniquify由于把各个多例化模块作为独立的模块来看,因此DC可以分别针对它们作出更好的优化,从而得到的结果也是比较理想的。缺点就是编译的时间稍微较长,但是对于一些不大的模块来说,这些是可以忽略的。
正因为uniquify可以综合出更好的结果,所以如果一般推荐使用uniquify解决多例化模块的综合问题。
3.3.4 DC-Tcl控制流及函数
在前面的DC-Tcl初步中,我们介绍了DC-Tcl的基本数据类型——变量、列表和集合,在这一节里将进一步介绍DC-Tcl的控制流语句——条件转移语句和循环语句,以及DC-Tcl
函数。
3.3.4.1 DC-Tcl的条件转移语句
DC-Tcl的条件转移语句有两种——if语句和switch语句,前者相当于verilog的if语句,后者相当与case语句,下面是它们的例子——
z if语句
上面这个例子说明,如果存在My_Design.db这个文件,则将该文件读入DC的内存中,否则打印“Could not read My_Design.db”。
使用if语句的时候要注意else必须和前一个条件的”}”在同一行。 z switch 语句
这段语句先将My_Design.db的文件类型返回到FTYPE变量中,再根据不同的文件类型分别进行不同的操作。
3.3.4.2 DC-Tcl的循环语句
循环语句介绍三种——foreach语句、while语句以及foreach_in_collection语句 z foreach语句
这个例子先创建一个列表Mylist,然后将Mylist中的元素依次赋值给list_element变量,并将其打印出来。这里echo $list_element要执行两次。 z while语句
这个while语句很像C语言中的while语句。它所要完成的任务是将CLK0至CLK9这10个时钟分别赋值为10、20、40…。 z foreach_in_collection语句
这条语句顾名思义,是用在集合的循环中的——
这段语句先创建一个CellColl的集合,包含设计中的所有单元,然后针对每一个单元,分别将它们的名字赋值给CellName变量并打印出来。
3.3.4.3 DC-Tcl的子函数
DC-Tcl支持内部的子函数调用,子函数的引入使得设计者可以定义他们自己的DC命令,并且可以修改这些命令的参数以及初始值。下面是一个很简单的实例——
这里创建了一个myproc.tcl的文件,里面定义了一个名为CALC_PERIOD的子函数,子函数的功能是根据输入的时钟的频率值返回它的周期。①
调用这个子函数的方法如下——
①
定义子函数的时候要注意”{“必须和proc语句在同一行,并且后面要留有空格
一般调用之前先要source函数所在的tcl文件,然后输入函数名和参数,便会自动返回所要求的值。
z 全局变量(Global Variable)
在谈到子函数的时候,不能不提到子函数内部的变量。一般来说,一个子函数内部定义的变量,它的有效范围只在子函数的内部。任何在子函数外部定义的变量称为全局变量(Global Variable),全局变量对于子函数来说是不可见的,要想在子函数内部使用全局变量,就必须在子函数中首先声明,如下面的例子——
在这里,子函数SP需要用到全局变量search_path,因此在使用之前必须先用global语句声明。只有这样,修改后的search_path才能在子函数外部有效。
z 查看子函数
要查看DC内存中存在哪些子函数,可以在dc_shell-t中使用info proc命令。 要查看DC内存中特定子函数的内容,可以在dc_shell-t中使用info body命令——
这个例子显示出了子函数CALC_PERIO的具体内容。 z 检查子函数错误
在编写完一个子函数之后,我们需要先对它进行调试,检查看中间是否有错误,查错比较常用的命令是check_error命令。
如果子函数编写正确,check_error –v的输出将为0——
如果有错误,check_error –v的输出为错误代码,可以用error_info显示错误具体信息——
z 子函数实例
下面是一个比较实用的子函数实例,这里定义了一个TimeBudget的子函数,输入参数为时钟频率以及时序预算的百分比,然后子函数根据输入的值自动设置设计的时钟、输入输出延时。子函数内容如下(proc.tcl)——
下面是调用这个子函数的主程序代码——
3.4 后综合过程
在这一章里,我们着重讨论使用Design Compiler综合大型设计时要注意的一些问题,比如怎样调整综合方法,出现约束违反后怎样修正,怎样给不同的子模块作时序和负载预算,以及给整个设计在具体综合之前先作一个预估(Design Exploration)等等。
3.4.1 编译一个大型设计
对于一个大型设计而言,由于模块规模的扩大,编译时间也相应的变长,要长达几个小时甚至超过一天,这样的时间对于讲究”Time to market”的设计者是比较重要的。因此就更加注重编译的技巧,本节我们主要讨论下面三个方面的技巧——
z 编译层次化设计的技巧 z 第二次(Second-pass)编译技巧 z characterize
3.4.1.1层次化编译
对一个大型设计来讲,有两种层次化编译技巧——自上而下(Top-down)以及自下而上(Bottom-up)。自上而下的方法是指将整个设计一次性读入,施加顶层约束后直接进行编译;自下而上的方法则先一个个编译比较底层的子模块,给它们加入时序和负载预算,然后在顶层将各个子模块整合起来。
z 自上而下(Top-down)
图126 Top-down编译步骤
上图是自上而下编译方法的具体步骤,可以看出假如顶层设计是RISC_CORE这个模块,则先直接将它读入,然后处理多次例化的模块,施加顶层约束后就直接编译。它的代码基本上如下所示——
自上而下的编译方法有一个明显的优点,即它使得设计者无需考虑各个子模块之间的依赖关系,也就不需要制定子模块之间的时序和负载预算,这一切都由Design Compiler自动考虑。另外,使用这种方法也使得设计者编写脚本变得简单,维护起来也比较方便。
在介绍自上而下的编译方法的时候,我们还要顺便提及DC编译的一种模式——Simple Compile Mode(简单编译模式)
这种模式在设计没有严格的约束的情况下能取得较快的编译速度,另外多例化模块的处理也自动进行。下面是RISC_CORE的Simple Compile Mode脚本——
可见,使用这种模式省去了uniquify这句,同时编译之前要先设置一个变量set_simple_comile_mod。
z 自下而上(Bottom-Up)
自下而上的编译方法其步骤如下图所示——
图127 Bottom-Up编译步骤
和前一种方法不同,自下而上的编译方法需要先单独编译各个子模块,在编译子模块的同时要考虑到与其它模块之间的关系,看是否满足约束,然后再读入顶层文件,施加顶层约束,顶层编译完成之后还必须看顶层约束是否满足。下面是单个模块编译的脚本——
下面是顶层模块编译脚本——
从上面的过程不难看出Bottom-Up方法的一些特点——
优点是利用了”分而治之”的策略,这对于大型的不可能一次编译的设计是十分有用的;另外它也摆脱了Top-down方法的对工作站硬件条件的限制,使得大型设计也能在一般的机器上编译完成。
缺点是实现步骤比较多,尤其对各个模块之间的时序和负载预算要求很高,如果不注意会很容易造成违反。
综合上述两种方法,我们可以做一个小结:对于规模不算太大的设计,我们推荐使用Top-down的编译方法,这样可以在不长的时间内得到满意的结果。
对于其他需要Bottom-Up的设计,我们必须确认时序负载预算能很好的反映实际的工作情况。①
3.4.1.2第二阶段编译
第二阶段(Second-Pass)编译是指当第一阶段(First-Pass)编译出现违反之后,分析违反原因从而重新编译的过程,对应的还有第零(Zero-Pass)阶段编译。②
关于第二阶段编译,前面的编译策略一章中有比较详细的介绍,前一章介绍的Top-down的第二阶段编译的步骤主要有——
z 检查模块划分 z 检查约束脚本
z 用更高的map_effort编译——compile –inc –map_effort high 这一节中,我们主要讨论用Bottom-Up方法编译后出现违反的情况—— z 重新编译顶层模块
这种方法是在Bottom-Up出现顶层模块时序违反的情况下采用的,具体的命令如下
图128 编译顶层模块
这个命令仅仅修正顶层子模块之间的路径,因此速度会比compile –inc更快。
z 修正设计预算(Design Budget)
设计预算对于Bottom-Up的方法来说是至关重要的,在预算的时候,我们都尽量能收紧(Tighten)每一个子模块时序、负载和驱动的预算,例如我们在最初介绍设计预算的时候,举的例子是给本模块留整条路径的40%,因此模块之间能够空出20%的裕量。在编译子模块的时候能尽量做到满足预算的要求。这样最后整合顶层设计的时候就不会出现大的问题。
如果出现问题了,一个方法就是调整预算脚本。看看施加的约束是否与综合后的电路相吻合。下一节,我们将介绍调整设计预算的一个很有用的命令——characterize。
①②
有关时序和负载预算的更详细的介绍,请参考本节附录 编译的三个阶段的详细信息见本节附录
3.4.1.3 characterize
Characterize这个命令用于映射到门级的子模块,作用是计算出该子模块周围的环境(延时、负载和驱动),并将得到的实际值作为该子模块的新的约束。如下图一个例子——
z 使用characterize
图129 characterize
由于整个设计已经映射到了门级,因此这个例子可以计算出子模块U2周围的输入输出延时、输入驱动和输出负载的实际值。然后将这些实际值施加在U2模块中,作为U2的新的约束。这种方法有点类似于给U2的周围照了一张照片。U2施加了新的约束之后,就可以在这个基础上做一次高级别的编译。
通过write_script命令,我们可以看到characterize之后到底照下了哪些信息——
characterize给我们提供了一种比较好的第二次编译的方法,加入一个子模块所占的延时很重,就可以在保持其他模块不动的情况下将这个子模块重新编译一次,当然重新编译可以从HDL代码开始,下面是一个例子——
图129 用characterize作第二次编译
这个例子和前一个例子的不同在于,它没用compile –inc high,而是直接将它从内存中删除,读入它的源文件重新编译,这样可以取得较上一种方法更好的结果。
z characterize的局限性
characterize无疑向我们提供一种较好的子模块二次编译方法,但是同时它也有一定的局限性,在使用的时候务必要注意——
首先,它要求所有的模块必须映射到门级,这是使用characterize的一个前提。 其次,characterize只能一次对一个子模块使用,即给U2作characterize的时候U1和U3模块必须保持不变,否则U2得到的环境就不是确定的值。
再次,characterize将外界环境直接作为它的约束,这使得它和其他的子模块之间不存在任何裕量(margin),这些裕量全部被该子模块吸收。
3.4.1.4 附录:Design Budgeter
Design Budgeter是一个专门给层次化的设计作设计预算的工具,不过在dc_shell-t中不能使用该工具,有兴趣的同学可以参照这一节的简单介绍自己做一些小练习。
使用Design Budgeter有两种方式:一种是在PrimeTime中输入allocate_budgets——
另一种是直接调用Design Budget Shell——
Design Budgeter可以根据模块周围的环境和它在组合逻辑中的比重自动调整它们的预算大小,与characterize不同的是,它可以同时对多个模块作Budget。这种预算对下面这几种设计非常有用——
z 层次化的设计 z 大型的设计
z 子模块没有寄存输出的设计 它的大致的设计流程如下图所示——
图130 Design Budgeter设计流程
图中大家可以看到编译的三个阶段——第一阶段由设计者提供初步的设计预算信息(initial constraints),读入RTL代码进行编译,得到的网表交由budgeter,提出新的预算信息(constraints);新的预算信息再和RTL代码综合得到新的网表,这个网表再交给budgeter,生成一个最终的预算信息(refined constraints),这个约束文件就可以提供给DC作第二阶段的编译了。
各个阶段的执行命令如下——
这里Pass Zero中的第一句读入的TOP.db是在施加initial constraints以后编译的结果。 从上面的步骤不难看出,Design Budgeter的流程是一个不断叠代的过程,它根据最初的人为编写的预算信息,一步步编译最终得到与实际情况比较接近的信息。
Design Bugdeter除了可以对门级网表进行操作之外也可以对时序模型(Timing Model)①
进行操作。下面是一个例子——
图131 对Timing Model作Budget
对它作Budget的命令如下——
①
Timing Model在Prime Time课程中有详细介绍
3.4.2 设计预估
设计预估(Design Exploration)是指在整个设计的RTL代码尚处在验证的阶段就对设计进行预先综合的过程,在这一节里,我们将讨论相比瀑布式的设计流程,设计预估的优点以及设计预估的流程,这些内容可能更多的不是介绍Design Compiler的使用,而是设计方法学问题。
3.4.2.1 为什么要设计预估
图132 传统的设计流程
上图左边是一个传统的设计流程,这个流程先作HDL代码的编写,在代码仿真通过之后作设计综合,然后插入扫描单元,通过以后作后端的物理设计,直到最后交给Foundry流片。这种流程之所以称为瀑布式的是因为后面的步骤都必须等前一个步骤完成之后才能进行。
稍加分析,不难发现这种流程有很大的危险性,从上图的右半部分可以看到,从综合到后端设计,每一个步骤都不可能不产生错误,如综合的时候碰到RTL代码引起的时序违反,加入扫描单元后发现测试覆盖率很低,后端布线由于过于拥挤而布不通,等等。这些错误如果可以通过工具的技巧修复还好,如果不能修复,就必须要返回到前面的步骤直至RTL代码的修改,这样做的代价是随着从前到后迅速增加的,如下图所示——
图133 不同步骤的自由度Vs修改代价
上图是各个不同的步骤的自由度与相对应的修改代价的对比关系,可以看出,越是前面的设计步骤(比如体系结构规划、算法的确定)自由度越大,但是相应所需要付出的代价(时间、人力等)则越小,越到后端则修改的自由度越小,而代价却急遽上升。
因此,要提高设计的效率,就必须在很早的情况下就发现问题并解决它,越是拖到后面,则越不划算。最理想的情况就是在编写RTL代码的时候就万无一失,并且在这个时候就能估算出最后的芯片面积和运行速度,以及内部长线的延时。这种思想就要进行设计预估的初衷。
下面是引入设计预估之后的设计流程图——
图134 含有设计预估的设计流程
在这个流程中,前端逻辑设计与后端物理设计同时进行,在编写code的同时,对芯片的初步布局和布线先作一个规划,然后检查工艺库中提供的连线负载模型(WLM),根据实际需要创建新的负载模型。而code在仿真的同时也可以作一个设计规划,在设计规划的时候调用新的WLM,以便得出更加真实的结果。
在设计预估做完,code仿真完成之后,就可以正式编译设计的主要模块,并将它们整合到顶层模块中去。同时对这些模块进行布局重新创建更加真实的WLM,直到最后的分析直至流片。
可以看到这个流程是一个前端与后端同时进行的过程,也是一个不断融合的过程。前端的设计者总是想尽早知道模块在实际的芯片中的位置,以便得到准确的WLM。这样才能尽量把错误提前找出来,避免留到后面的高成本。
3.4.2.2 设计预估的目标
在上面的这个流程中,设计预估是与code的功能验证平行进行的,而不是等到功能验证完成后再作。每当code的改动足以影响系统性能的时候,我们都需要进行一次设计预估。
一般说来,设计预估有下面几个目标—— z 验证代码的可综合性
这是设计预估的一个基本功能,不能综合的代码对后面的一切步骤都只能是空谈。 z 控制代码的时序违反在一定的范围(10-15%)
预估后的网表的时序违反如果超过这个范围就可以即使更改代码,保证在正式综合的时候能通过调整综合参数达到时序要求,而不再该代码。 z 验证施加的约束的真实性和充分性
设计约束不是设计者凭空想象出来的,他需要通过多次的设计预估,并对得到的网表进行分析,从而对施加的约束不断修正,让他能足够反映真实情况,并且不漏掉容易忽视的约束条件。 z 鉴别时序的特殊情况
大型设计中难免会有一些DC不能综合或者无需综合的路径,设计预估需要把这些路径找到。
z 鉴别模块划分问题
模块划分可以在编写code的时候进行,也可以在DC的约束脚本中用group/ungroup完成,设计预估需要得到一个较好的设计划分。 z 鉴别测试问题
测试问题我们再DFT课程中详细介绍。 z 保证WLM的合理
WLM是DC处理连线延时的依据之一,合理的WLM可以保证时序的准确性。
3.4.2.3 设计预估的流程
图135 设计预估的流程
上面的设计预估流程可以看出设计预估包括——读入HDL代码,施加约束,加入扫描链,分析综合结果,根据初始化布局和创建的新WLM分析时序等等。每当其中一个步骤出现大的错误,都需要该代码。
下面我们主要针对施加设计约束和编译的一些技巧作展开——
设计约束
这是一个比较典型的约束脚本,里面包含了定义时钟、输入延时和驱动、输出延时和负
载、运行环境几个方面。里面的命令我们在前面的章节都介绍过,下面着重讨论几个关键的约束——
z 负载约束
图136 负载约束
上面的负载约束设置了比较保守的输入输出的负载值,它的单位负载值是inv1a1的A管脚的负载。允许输出端口带3个这样的负载并且扇出数目是3;允许输入端口带1个这样的负载并且输出数目是1。 z 针对端口的WLM
图137 长线的WLM
这段脚本对顶层模块内部的连线设置了100k_WLM的连线负载模型,对所有连接到端口的连线则设置了GLOBAL_NET_WLM的模型,这样能比较真实的反映连线情况。 z 设计规则约束
在所有的设计约束中,设计规则约束的优先级是最高的,也就是说综合的过程中,DC首先必须满足设计规则。因此,我们可以对整个设计先制定一个设计规则,这样就可以避免单元的规则到达允许的极限值而使延时显著的增加。
设计规则包含——最大电容(max_capacitance)、最大电平转化时间(max_transition)以及最大扇出(max_fanout)。 1. set_max_capacitance
图138 set_max_capacitance
设置最大电容的命令是set_max_capacitance。最大电容这个规则一些工艺库库中会有定义,如果没有则需要设计者自己找出电容最大的那个单元(本例中用的是inv1a27)。一般为了保险起见,都会在原来的设计规则的基础上加上一个裕量形成新规则。这里就在原来的基础上减少了一半,并将这个值加在IN1这个端口上。这样,如果IN1端口有一个1.2pf的负载,那么留给IN1端口内部的电容值就只剩下0.6pf了。 2. set_max_transition
图139 set_max_transition
设置最大电平转化时间的命令是set_max_transition。这个值,也是从inv1a27单元中用get_attribute得到的。同样留下的裕量是一半,使得DC在编译的时候不会接近实际的规则值。这样,如果在IN1端口外部有一个负载,DC会根据这个规则调整内部单元的大小,使得IN1满足max_transition不超过0.200的要求。 3. set_max_fanout
图140 set_max_fanout
上面的例子给输入端口IN1设置了最大扇出为6。值得注意的是,这里的6并不是扇出的数目为6个负载,而是扇出值,即负载的大小。例如,如果IN1接的的负载是inv1a1的单元,该单元的扇出负载是0.25,那么IN1可以带最多24个inv1a1;如果负载单元是负载值3.00的inv127,那么只能带2个这样的单元。
set_max_fanout命令是按照单元的fanout_load属性来计算负载的,也就是说所有的
负载值相加不能超过max_fanout。fanout_load这个属性,有的工艺库单元并不存在,这样的话,DC会自动去找该工艺库有没有默认的fanout_load,若有会使用该值代替,如果还是没有则会将fanout_load设成0,即输入端口可以带任意个负载,这显然是不符合实际的,因此,这时就有必要手动设置一个default_fanout_load。
下面是设置fanout_load的一些技巧——
a) 如果想让所有的输入端口只带一个负载单元,可以简单设置max_fanout为1,也可以根据单元中最小的fanout_load来设置——
b) 查看工艺库中是否存在默认fanout_load——
c) 如果没有,手动设置fanout_load——
设计编译
上面是一个典型的设计编译的脚本,和前面章节讨论的编译没有什么太大差别,这里source的两个约束文件是设计约束脚本以及设计规则脚本。事实上,对于设计预估而言,为
了尽量减少编译时间,引入可用的DW_foundation库可以将上面的代码写成如下所示——
上面是一个比较典型的用于设计预估的编译脚本,remove_attribute MY_BLOCK “max_area”以及compile –area_effort none –scan两句都说明了编译过程不考虑面积因素,因而可以节约编译时间,另外一个提高编译速度的措施是使用了simple_compile_mode,这个命令和紧接的dw_simple_mode都是告诉DC,在编译的时候采用较少的优化算法,并对多例化模块只作一次处理。
下面再介绍两个提高DC速度的方法,当然他们并非仅仅用于设计预估中—— z
这个命令告诉DC,在HDL编译的时候采用”Presto”的代码编译器。”Presto”代码编译器是在2000.10之后的版本中推出的,它相对前期的HDL compiler编译器而言,编译速度提高了6倍,但是内存消耗量减少35%,而且支持更多的Verilog 语言结构。对于大规模的设计,这种方法的收效是很明显的。默认这个开关是打开的。
z
这个命令用在读入门级verilog网表中,它相对于前期的网表读入,能平均提高3倍的速度且少用3倍的内存。默认这个开关是打开的。
3.4.2.4 设计预估实例
前面介绍了设计预估中的一些步骤和特点,这一节我们将讨论顶层设计中的一个子模块的设计预估实例——
我们要做预估的模块是SUBDESIGN_A,这个模块是位于顶层模块(MAJOR_BLOCK_1)中的一个子模块(位置关系如下图所示)。它的规模在40K左右,不算太大,顶层设计140K,准备采用Top-down的编译方法。现在它的RTL源代码已经编写完成了,其他几个子模块的代码还在编写中,由于该模块在整个电路中是一个关键的模块,它的性能对整体设计有较大的影响,因此很有必要在对顶层模块综合之前先单独对它做一个预估。
图141 预估模块在顶层模块中的位置
在结构上,SUBDESIGN_A并不是全部寄存输出的,并且和它相连的其他模块也可能不是寄存输出的。那么我们应该怎样对它进行约束呢?
这时,我们知道的仅仅有时钟周期和电路工作环境,其他的诸如时序预算、输入驱动、输出负载、WLM等等都需要自己估计。估计的驱动和负载模型如下——
这里假设驱动单元为NAND2,WLM为140Kgates,每个输入端接有4个NAND2,输出接6个NAND2。
下面设置输入输出延时,如果SUBDESIGN_A连接的子模块都是寄存输出的话(如下图),可以设置延时为——
图142 子模块输出寄存
或者
如果子模块并不是输出寄存(如下图所示),那么可以假设每个子模块平分50%的延时——
图143 子模块非输出寄存
本例中,子模块SUBDESIGN_A的输出不是寄存,所以使用上面的延时。可以看到,各自占用50%的延时之后,剩下的裕量就不存在了,这样会造成一定的危险性,比如如果该模块存在下图所示的电路——
图143 子模块非输出寄存
这个模块中,除了输出不寄存之外还有一个特点,即中间存在纯的组合逻辑电路(COMBO),试想如果使用50%的预算方案,那么对于COMBO电路而言,输入输出都占了50%,那么留给自己的就只有0%的延时,这也许并不是设计者的本意,但是DC并没有意识到这一点,它会尽量去优化COMBO路径,尽管结果都是时序违反。
碰到这种情况,我们该怎么处理呢?这里介绍一种用于编译的命令group_path,用户自定义路径组。
默认情况下,上述电路有两个路径组——CLK和DEFAULT,其中COMBO和输出电路属于DEFAULT路径组。在优化的时候,DC会根据不同的路径组优化电路,并且报告每个路径组的最大时序负裕量(WNS)。如果新增了用户路径组,DC会把这些新的路径组也作为一个优化的对象,从而便于用户干预优化的过程。
自定义用户组的命令如下——
这三个用户组在图中表示为Input Paths、Output Paths以及Combinational Paths。这几个用户组和默认的CLK组一起构成DC优化的对象。
上面这段代码除了设置3个用户组之外还对clk组设置了0.3的critical_range,这说明和clk组内的WNS差值在0.3内的所有时序违反路径都将被优化,而其余的2个路径组则只优化最差的路径。
path_group和critical_range都是用户介入DC优化的两个手段,具体使用哪个要依情况而定。path_group每增加一个,report_timing的时候就会增加一个WNS路径。critiacl_range主要用于优化一个路径组中的一些违反路径。
3.5 总结
综合与Design Compiler的使用在这里基本上告一段落了,这一节我们将前面的内容做一个简单的总结,大家在使用Design Comiler的同时一定会遇到不少问题,这一节也将介绍一些获取帮助的手段。
3.5.1 课程回顾
在本课程的第二章里,我们花了比较大的篇幅讲述各个Verilog的语法结构对应的综合网表,事实上,我们反复强调RTL代码对Design Compiler的综合效率影响是最大的,我们不能指望工具帮助我们把一段垃圾代码代码综合出像样的电路来,即所谓的Garbage In->Garbage Out。如果综合出来之后的关键路径仅仅是简单的一个门,但是还是违反时序,那这时我们可以断定不是编码的问题了。编码的好坏就相当于一个起点的好坏——
图144 编码对于综合结果的影响
不好的编码只能使我们的工作适得其反。
更进一步说,决定编码的因素则是决定系统的算法(algorithm)和体系结构(architecture)。例如乘法器是采用流水线在多个周期内完成,还是不用流水线在一个周期内完成;采用的是并行还是串行的结构等等。这些已经脱离了综合的范畴,这里不再展开。
在介绍Design Compiler工具时,我们首先介绍了设置库文件及其启动脚本,然后讲述怎样给设计施加约束,接下来是编译和优化的方法和策略,最后介绍了设计预估。基本上是按照一个从前到后,由简入繁的思路讲述的。
关于施加约束,值得一提的是:没有一个放置四海而皆准的黄金脚本可以使用。任何脚本都是根据具体的设计的环境编写的,要得到一个非常满意的约束,需要经过反复的估计。 关于编译,编译的方法很多,compile的开关也很多,但是要注意的是,时序违反并不是由于compile的开关设置不当引起的,原因来自于编码或者约束文件的编写不当。compile的开关只是提供了修正一些小的时序违反的方法。有关编译策略的流程图如下所示——
图145 编译策略流程图
3.5.2 更多其他课程
Design Compiler不是一个完全独立的工具,事实上它是和其他的工具结合起来使用的,前面的章节我们曾经提到过用于静态时序分析的工具(PrimeTime),以及设计预估中提到的
DFT(Design For Test),这两个课程将要在后续课程中深入学习。另外还有一个课程,是本课程的延续,称为高级芯片综合(Advanced Chip Synthesis),主要讲述怎样通过Floorplaner创建新的WLM,这个课程所要讲述的内容如下所示——
图145 编译策略流程图
3.5.3 更多帮助
关于帮助,这里介绍三个——SOLD、SolvNet以及ESUNG。 z Synopsys Online Documentation(SOLD)
图146 SOLD
上图是Synopsys最新的一期SOLD,SOLD里包含了Synopsys所有的工具的在线文档,有关Design Compiler的其他的命令的解释以及使用说明,这里都有非常详细的介绍。
z SolvNet
SolvNet是一个Synopsys官方的用户在线论坛,任何Synopsys的合法用户都可以通
一般说来,通过搜索SolvNet上的文档,过www.synopsys.com注册称为SolvNet的成员。
一些比较常见的问题在这里都能找到答案。
除了在线寻找之外,SOLD的光盘中也会自带一些以前保留下来的SolvNet中的资料。对于设计者来说,可以先从光盘中查找,如果没有相关信息,可以登陆到网站上查找。
下面是SOLD中的SolvNet主页面——
图147 SolvNet
z SNUG
SNUG是一个非正式的Synopsys用户论坛,这里也云集了一大批Synopsys用户,它除了提供一些使用技巧和热点疑难解答之外,还会有一些将Synopsys工具与其他EDA厂商工具的对比的文章。任何注册的synopsys用户都能在SNUG上下载文章,下面是SNUG的网站(www.snug-universal.org)——
148 ESNUG
图
因篇幅问题不能全部显示,请点此查看更多更全内容