ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多周期路径约束精讲:从STA检查沿到跨时钟域实战

多周期路径约束精讲:从STA检查沿到跨时钟域实战 做后端或者写约束的工程师一定都在时序报告里见过类似这种令人头疼的场景一条路径明明逻辑延迟不大可偏偏就是修不完report_timing翻来覆去看到的都是同一条路径的setup违例数据路径加buffer、换驱动、调尺寸折腾半天发现时序余量纹丝不动。最后偶然翻代码发现这条路径的功能本来就允许数据晚两个周期到达约束里却一直按单周期路径检查等于让芯片白白去追一个根本不存在的“最坏情况”。这就是多周期路径Multicycle Path在真实项目里的样子。它不像时钟频率、组合逻辑延迟那样直观但它决定了一件非常重要的事STA工具默认以“一个时钟周期内完成数据传输”来检查路径而你的设计里恰恰有大量路径根本不需要在一个周期内完成。正确地把这些路径声明出来不仅能让收敛时间大大缩短还能帮工具省掉大量没必要的优化让面积、功耗、时序同时受益。这篇文章我不打算只贴命令手册而是从“工具到底在检查什么”讲起把set_multicycle_path这个命令的参数、原理、配合关系和常见坑全部捋一遍适合刚接触SDC约束的工程师也适合那种已经会写约束但总在slow到fast、fast到slow场景里踩坑的老手。1. 什么是多周期路径为什么默认检查不适用1.1 STA默认假设与“单周期”的约束先回到STA的基本模型。一条从发起触发器launch flop到捕获触发器capture flop的数据路径工具默认按照单周期检查。什么意思呢就是说如果launch flop在某个时钟沿T0发出数据那么capture flop应该在紧接着的下一个时钟沿T1把数据采进去。T0和T1之间只有刚好一个时钟周期所有组合逻辑延迟、走线延迟、时钟偏斜都必须在这个周期里全部完成。这个“单周期”假设说起来简洁实际却很严苛。它要求工具对每条时序路径都做最坏情况的setup检查数据必须在T1沿到来之前的建立时间窗口内到达capture端。如果设计里有一条路径确实需要2个周期才能稳定工具却按1个周期去约束那结果只有两种要么这条路径修到死也满足不了要么你为了强行满足这个不存在的目标在数据路径上堆一堆buffer、抬驱动能力最后面积和功耗白白浪费。我见过不少初学者对“单周期”这几个字没有体感总觉得STA的检查就是拿“时钟周期”和“延迟”比较一下。但实际上工具把所有逻辑路径默认都当成单周期路径来约束。这是它的默认行为也是很多时序违例的根源不是你的设计有问题而是你的约束没有告诉工具真实的时间关系。1.2 什么样子的路径需要声明为多周期那么工程里哪些路径天然就属于多周期呢我举几个最高频的场景。第一种是数据通路中间有握手逻辑或者流水使能。比如A模块把数据算出来之后B模块并不会立刻采样而是要等使能信号拉高后的第2个、第3个周期才去取数。这种情况下数据路径的“有效时间窗口”就不止一个周期它允许数据在多个周期内到达并保持稳定。第二种是分频时钟相关的路径。比如一个100MHz的时钟经过4分频得到25MHz的时钟两个时钟域之间如果存在同源的跨时钟路径那么源时钟域的数据到目标时钟域去采样它可用的时间窗口可能取决于分频比例不再是简简单单的一个快时钟周期。第三种是RAM、寄存器堆、外部接口这类“固有延迟”比较大的模块。比如读RAM地址发出后经过译码、cell访问、数据输出好几级逻辑叠在一起如果RAM模型本身默认的是一个周期读出来但实际你在地址路径上又串了选择逻辑就可能出现需要两个周期才能稳定到输出端的情况。只要这种“数据不需要在下个时钟沿就绪”的关系是设计意图的一部分你就应该用多周期路径约束把这个意图告诉工具。声明之后工具会调整setup检查的捕获沿位置不再要求在下一个时钟沿前必须到达而是允许在更晚的沿到来前到达。1.3 多周期路径改变的是检查沿不是延迟值这里有一个特别容易混淆的概念必须先搞清楚set_multicycle_path不会改变门延迟、走线延迟也不去修改任何物理实现上的东西。它唯一做的事情是告诉STA工具“这条路径的检查沿不是默认的那个请往后移N个周期再检查。”工具听到这个指令之后会重新计算时序余量原来它要求数据在T1沿之前稳定setup现在它允许数据在T_N沿之前稳定就行。这相当于在时间坐标上把“检查截止线”往后推了路径上原本看起来“不够用”的组合逻辑延迟自然就有富余了。但注意这里还有个非常容易被忽视的连锁反应当你把setup检查往后推N个周期时默认的hold检查并没有跟着往后推。hold检查仍然卡在原来的位置launch沿附近。这会导致什么后面我专门用一节来讲清楚这个“N和N-1”的关系。可以说没搞懂这个关系的人十个里面有八个会在多周期路径上栽跟头。2. set_multicycle_path命令的完整语法与参数拆解2.1 命令语法与常用参数set_multicycle_path这条命令长这样set_multicycle_path [-setup|-hold] [-start|-end] [-rise|-fall] [-from] [-to] [-through] path_multiplier其中path_multiplier是一个整数表示“把检查沿往后移几个周期”。其他选项里-from、-to、-through用来指定路径范围和set_false_path、set_max_delay这些约束的用法一致核心区别在于后面的path_multiplier以及-setup、-hold、-start、-end这几个修饰选项的组合。我来做一张常用参数速查表方便你写约束的时候对照。参数作用默认值备注-setup指定设置的是建立时间检查沿默认命令默认行为就是setup-hold指定设置的是保持时间检查沿非默认需要显式指定-start基于发起时钟的沿来计算setup默认-endhold默认-start跨频域时非常关键-end基于捕获时钟的沿来计算setup默认-endhold默认-start多数单时钟域场景用默认就够-rise/-fall指定上升沿或下降沿两者都匹配一般不用特殊时钟沿场景才用-from/-to/-through指定路径起点、终点、经过点无配合get_pins/get_cells/get_clocks使用看到这个表你应该能发现一个关键点setup和hold的默认计算基准不一样。setup默认走-end捕获时钟沿hold默认走-start发起时钟沿。这个差异稍微绕但理解透了后面跨时钟域约束的时候就会顺很多。2.2 -setup与-hold的配合为什么是N和N-1前面我说过当你把setup检查往后移N个周期时hold检查默认不会跟着往后移。这时候会出现什么情况呢举个例子。某条路径你允许数据在第2个时钟周期末到达于是写了set_multicycle_path 2 -setup -from [get_pins instA/reg1/Q] -to [get_pins instB/reg2/D]工具收到这个约束后setup检查沿从原来的T1移到了T2数据只要在T2之前到达就行。但hold检查呢它还在原来的位置也就是T0沿附近检查“launch沿之后数据有没有保持住”。数据是T0发出的在T0之后它当然应该保持稳定所以从功能上看原本的hold检查好像没问题。但问题出在更深的层次。STA里的hold检查实际比对的是“当前这个launch沿发出的数据”和“上一个launch沿发出的数据”之间的关系。当setup检查沿往后移了N个周期工具会默认“数据是在N个周期前就绪并保持的”。它不会自动把“数据实际发生变化的时间点”往后挪。这时候如果你不给hold打补丁工具可能用一套完全不符合功能的时间关系去检查hold结果就是在某些工艺角下报出莫名其妙的hold违例。解决方法是把hold检查沿也往后退。标准做法是setup设Nhold设N-1。写成命令就是set_multicycle_path 2 -setup -from [get_pins instA/reg1/Q] -to [get_pins instB/reg2/D] set_multicycle_path 1 -hold -from [get_pins instA/reg1/Q] -to [get_pins instB/reg2/D]这里的1不是指“hold检查在第1个周期”而是指“hold检查沿相对默认位置往后移1个周期”。这正好就是N-11。如果你理解成“hold也设1”这种机械记忆碰到N3、N4的场景就容易搞错。顺带说一句我见过有的工程师写hold约束的时候喜欢写成set_multicycle_path 0 -hold意图是不调整hold检查。这么做在某些工具版本里会得到和默认一样的效果但语义不清晰团队协作时别人看不懂不推荐这么写。2.3 -start与-end两条容易搞混的选项-start和-end的差异在单时钟域里几乎感觉不到因为发起时钟和捕获时钟是同一个沿的对应关系很清晰。一旦跨时钟域这两个选项就直接决定了你的约束对不对。先记住一个总原则-start是相对发起时钟launch clock来数周期-end是相对捕获时钟capture clock来数周期。setup默认是-endhold默认是-start。为什么setup默认-end因为setup检查的本质是看捕获触发器在它的时钟沿到来之前数据有没有满足建立时间。这个“沿”是捕获端的时钟沿所以用捕获时钟来数周期最自然。hold检查则是在launch沿之后检查数据保持所以默认以发起时钟为基准。但在实际项目中setup什么时候该用-start呢当发起时钟比捕获时钟快的时候。打个比方发起时钟100MHz捕获时钟25MHz数据是在100MHz的沿上发出的那么它每10ns就有一次更新的机会可捕获端25MHz要40ns才采一次。这种情况下你用-setup 1 -end是把捕获时钟的检查沿往后推40ns检查得太松了而用-setup 1 -start是把发起时钟的沿往后推10ns更符合“数据实际每10ns刷新一次”的真实情况。相反如果发起时钟比捕获时钟慢比如发起25MHz、捕获100MHz数据40ns才更新一次捕获端10ns就采一次这时候用-end按捕获时钟来数周期反而更符合采样逻辑。这就是业内常说的“慢到快用-end快到慢用-start”的经验来源。3. 同频时钟域下的完整案例实操3.1 案例背景流水握手导致数据延迟两拍为了把前面的理论落到实际我拿一个具体的工程场景来讲。假设有一个数据通路模块A经过两级组合逻辑后把数据送到模块B模块B并不是每拍都收而是收到valid信号后才在下一个沿采样。从波形上看A的Q端在T0沿发出数据B真正把这个数据采进去要等到T2沿。也就是说这条路径的可用时间是两个时钟周期。如果没有多周期约束STA默认把setup检查放在T1沿。而T0到T1只有一个周期组合逻辑如果稍微长一点T1时刻数据根本到不了B的D端时序报告必然大片setup违例。更麻烦的是这些违例你越修越烦躁因为数据路径上即使你强行把它压到一个周期内功能上也不是必须的。正确的做法是把这条路径声明为2周期路径。给个具体的约束示例create_clock -period 10 [get_ports clk] set_multicycle_path 2 -setup -from [get_pins instA/reg1/Q] -to [get_pins instB/reg2/D] set_multicycle_path 1 -hold -from [get_pins instA/reg1/Q] -to [get_pins instB/reg2/D]这两行约束合在一起表达的意思就是数据从instA/reg1/Q发出后在第2个时钟沿之前到达instB/reg2/D即可hold检查则相对默认位置后移1个周期保证数据在launch沿之后不会因为过分宽松的setup调整而产生hold风险。3.2 约束写法与检查沿换算为了确保你真的理解这两行命令背后的检查沿换算我画个时间轴来推演一下。假设时钟周期是10ns。T00ns是launch沿数据在0ns从instA/reg1/Q发出。默认情况下setup检查沿在T110ns。设了set_multicycle_path 2后setup检查沿被推到T220ns。这时候只要数据在20ns减去建立时间之前到达B的D端就没问题。再看hold。默认的hold检查沿在0ns附近检查的是0ns时刻数据变化后有没有保持住。设了set_multicycle_path 1 -hold后hold检查沿从0ns推到了10ns。实际效果是工具会检查在0ns之后到10ns这个区间内数据不能被过早地“下一次变化”破坏因为捕获端是在20ns采样数据必须从某个时刻开始稳定到20ns而这个“稳定开始时刻”不能比10ns更晚。这里有一个很直观的理解方式你把setup往后放了2拍等于说捕获端用的是20ns这个沿。那么在20ns之前数据其实已经稳定了很长一段时间。如果hold检查仍然卡在0ns那等于要求数据在0ns之后立刻稳定住不能有一点点抖动。这显然太苛刻了因为数据是0ns才刚发出组合逻辑不可能0ns就稳定。所以hold必须往后推到上一次数据更新的位置也就是10ns附近这才符合“数据每10ns更新一次”的实际情况。3.3 用report_timing验证约束效果写完约束之后千万别直接就跑PR。先用report_timing看看检查沿到底有没有按预期移动这一步能帮你省下大量排错时间。report_timing -from [get_pins instA/reg1/Q] -to [get_pins instB/reg2/D] -delay_type max -nworst 1正常情况下时序报告的Path Group和Startpoint/Endpoint下面会出现和multicycle相关的信息。你重点看两处第一处是Path Type它会标明这是maxsetup路径还是minhold路径。第二处是Path的“capture clock edge”如果setup检查沿从默认的10ns变成了20ns说明你的-setup 2生效了。hold报告里如果捕获沿变成了10ns相关说明-hold 1也正确。我见过不少人写完约束不看报告直接跑完布局布线再看结果。等到PR跑完发现一堆局部违例回头再查约束才发现是set_multicycle_path的路径范围写错了比如-from和-to把无关的路径也圈了进来。检查沿的确认花不了两分钟却能避免后续几个小时的无效迭代。4. 跨时钟域场景慢到快与快到慢的差异4.1 同源分频时钟的特殊性跨时钟域约束是set_multicycle_path最容易翻车的场景尤其是同源分频时钟之间。什么叫同源分频呢就是一个PLL或者一个时钟源分频出两个不同频率的时钟比如100MHz和50MHz它们之间存在明确的相位关系上升沿在某个点是精确对齐的。这种场景下两个时钟域之间传递数据能不能用set_multicycle_path能用。但要注意一个前提数据在两个时钟域之间必须有确定的时序关系不能是异步的。只要两个时钟是同源的相位关系确定工具就能通过多条约束来精确描述路径的时序要求。反之如果两片时钟来自完全不同的PLL、没有确定的相位关系那是真正的异步跨时钟域通常应该用set_clock_groups或者set_false_path来处理而不是硬写多周期路径。这里我多说一句。很多人一看到“两个时钟域”就习惯性写set_false_path这种一刀切的做法在功能正确的情况下可能没问题但代价是时序报告里完全看不到这些路径。如果数据确实需要保证某些时序边界那false path会掩盖真实风险。分频时钟之间我更倾向于用多周期路径把真实的时间关系显式表达出来。4.2 慢到快场景的-end用法慢到快也就是发起时钟慢、捕获时钟快。比如发起时钟25MHz捕获时钟100MHz且是同源分频。这种情况下数据40ns才更新一次而捕获端10ns就采一次。如果你不写多周期约束工具默认找的是launch沿之后的第一个capture沿。由于分频沿是对齐的这个捕获沿可能和launch沿几乎重合数据完全没有时间到达setup必然违例。正确的处理方式是把捕获时钟的检查沿往后推用-endset_multicycle_path 1 -setup -from [get_clocks clk_div4] -to [get_clocks clk] set_multicycle_path 0 -hold -from [get_clocks clk_div4] -to [get_clocks clk]这里-setup 1的意思是把捕获沿往后退1个快时钟周期也就是从0ns推到10ns。数据在40ns才更新在10ns到达没问题。hold设0的含义是保持默认的hold检查位置不变。慢到快的场景里数据在慢时钟沿才更新默认hold检查点卡在慢时钟沿附近反而是合理的所以hold通常不用额外拖。我在实际项目中见过有人在这种场景下机械地套“N-1”写完-setup 1后跟着写-hold 0结果没问题但也见过有人写-hold -1直接把hold检查沿弄到负周期去了时序分析一片混乱。你得想明白这里数据更新的节拍是慢时钟hold检查点维持在和launch沿对齐的位置是对的不需要强行挪。4.3 快到慢场景的-start用法快到慢发起时钟快、捕获时钟慢。还是拿100MHz和25MHz举例。数据每10ns就更新一次但捕获端40ns才采一次。如果你用-end把捕获沿往后推推到40ns之后的某个位置这期间数据其实已经更新了好几轮工具会误以为数据保持了40ns对hold检查会提出不切实际的要求。这就是为什么这种场景推荐用-start以发起时钟为基准来数周期。set_multicycle_path 1 -setup -start -from [get_clocks clk] -to [get_clocks clk_div4] set_multicycle_path 0 -hold -start -from [get_clocks clk] -to [get_clocks clk_div4]这里的-setup 1 -start表达的是数据从发起时钟的第0个沿发出后到发起时钟的第1个沿也就是下一个10ns处完成更新捕获端在它之后的第一个捕获沿40ns处采样。整个过程里数据实际只被要求和一次launch沿对应而不是被要求从一个错误的捕获沿往前倒推。关于快到慢的hold调整实际项目中要结合分频相位仔细确认。如果launch沿和capture沿完全对齐数据在0ns发出、在下一个快时钟沿10ns处更新那么默认的hold检查点0ns检查的是旧数据余量充足基本不用动。但如果存在较大的时钟延迟差异hold检查可能需要额外调整。我的建议是先把setup调对然后打开hold报告看一看根据实际余量决定要不要对hold做偏移不要盲抄公式。5. 多周期路径的常见误用与排查技巧5.1 六类典型的错误约束多周期路径相关的坑我在不同的项目里反复见到过踩来踩去无非这几类。列个表方便你写约束的时候对照自查。错误类型表现正确做法只写setup不写holdsetup检查沿推后hold仍卡原位报出莫名hold违例按N和N-1成对设置setup和hold都写Nhold检查沿推得太远hold过度宽松掩盖真实风险hold写N-1慢到快场景误用-startsetup检查沿推得太远时序过度乐观慢到快用-end快到慢场景误用-endsetup检查沿按捕获时钟推hold和实际更新节拍不符快到慢用-start路径范围写得太宽本不该受约束的路径被错误限定时序用get_pins精确指定from/to对异步跨时钟域硬写MCP用多周期约束描述根本不存在的相位关系改用set_clock_groups或set_false_path这里我特别想强调第5条。set_multicycle_path的-from和-to可以写时钟、端口、引脚。写时钟的时候约束会覆盖这个时钟域里所有对应方向的路径影响范围很大。如果只是想约束某一条具体的modul之间通路建议尽量用get_pins把起点终点圈住否则很容易把无关路径一起改了到时候排查起来非常费劲。5.2 排查思路与常用命令多周期路径设置完之后如果发现时序还有问题我建议按这个顺序排查。第一步确认约束有没有真的作用到目标路径上。用report_timing -path_type full_clock_expanded把时钟路径展开来看确认capture edge有没有移到预期的位置。如果报告里capture edge没变说明约束的作用域写错了或者命令压根没被工具读进去。第二步检查是不是hold和setup的基准不一致。如果一个setup约束用了-endhold却用了-start在跨时钟域场景下这俩检查基准可能完全错开导致hold检查点和你想象的不一样。这时候回到报告里把Path Type分别设为max和min对比检查沿的位置基本就能定位。第三步看是不是被其他约束覆盖了。同一个路径上如果既有set_multicycle_path又有set_false_path后期写的约束会覆盖前期的行为。SDC是顺序执行的后写的命令优先级更高。你要是发现多周期约束没生效查一查是不是后面某个set_false_path把它冲掉了。还有一个很实用的命令report_constraints -all它能列出当前设计里所有已经生效的时序约束。当你怀疑某个约束没被工具接受或者被其他约束覆盖这个命令非常管用。5.3 多周期路径vs伪路径vs时钟组最后聊一个经常被混淆的话题一条路径什么时候用多周期什么时候用伪路径什么时候用时钟组隔离。我的判断标准其实很朴素看这条路径的时序关系是不是“确定且可以量化的”。如果数据在A时钟的某个沿发出在B时钟的某个沿采样两者之间存在确定的相位和时间关系那就用set_multicycle_path把具体周期数写清楚。如果数据虽然是同步的但功能上完全不需要关心时序比如测试逻辑、配置寄存器的某些静态信号那就用set_false_path告诉工具“这条路径不用检查”。如果是两组独立的异步时钟它们之间没有确定的相位关系要么用set_clock_groups把它们设成asynchronous要么对具体路径设false path。一个真实项目里三条命令往往都会用到。多周期路径处理的是“有时间要求但不止一个周期”的路径伪路径处理的是“没有时间要求”的路径时钟组隔离处理的是“不同时钟域之间本来就不该做严格时序分析”的整体关系。把它们混为一谈是很多约束病根的开始。顺便提一个技巧在设置多周期路径之前先问设计者一个问题——“数据从发出来到被采中间最多能等几个周期”问清楚了这个数字再去写命令基本不会错。很多时候我看到工程师对着时序报告反复试-hold 1还是-hold 2其实问题根本不在于数字而在于他压根没想明白数据更新的真实节拍。6. 多周期路径的收敛经验与最终建议6.1 设计阶段就考虑约束不要等违例了再补我在多个项目里观察到一个规律多周期路径相关的违例往往不是PR阶段才出现的而是RTL设计阶段就没有把时间窗口的关系定义清楚。一个模块输出数据给下一个模块接收方到底在哪一拍采样中间有没有使能信号控制这个信息应该在设计文档里就写明然后约束工程师照着写。如果你等到布局布线完了看到大片setup违例才开始翻RTL、问设计者“这里能不能晚两拍”整个流程的成本就高了。因为约束一变PR工具对路径的优化目标也随之改变之前为了强行收敛而插入的buffer、调整的单元大小可能全部变成多余的需要重新做优化。省事的做法是在综合阶段就把多周期路径约束写进去让工具从一开始就按正确的时间关系做优化。6.2 团队协作中如何规范多周期约束多周期路径约束散落在sdc文件里如果没有规范时间一长就变成一团乱麻。我建议团队内部至少约定几件事。第一条所有set_multicycle_path必须写注释说明为什么这条路径需要多周期数据更新节拍是几个周期。不要小看注释三个月后你自己回头看都可能想不起来当时为什么设2而不设3。第二条同一组setup和hold的约束必须写在一起中间不要插入别的命令。SDC是顺序执行的如果中间插了一个set_false_path把整条路径屏蔽了后面再写hold也没意义。把两行约束紧挨着放既方便阅读也避免被无关命令干扰。第三条约束的from/to对象尽量用有意义的命名。get_clocks当然简洁但如果你能写到模块级引脚后续通过报告回溯问题的时候定位会快很多。命名规范本身不直接影响时序但它直接影响排查效率。6.3 我在实际项目里踩过的一个坑最后分享一段个人经历。有一次处理一个跨模块数据通路功能上数据确实是允许两个周期到达的我一开始也按这个写了set_multicycle_path 2 -setup。但项目后期做低功耗检查的时候发现这条路径上的hold违例特别严重。排查半天发现原因是RTL里虽然数据晚两拍才被采但数据源头的寄存器是在每个时钟沿都更新的数据到达接收端的时间窗口其实非常紧张。这个案例给我的教训是多周期路径的周期数不能只看“接收端隔了几拍采样”还要看“发送端的数据是不是每一拍都在更新”。如果发送端每拍更新接收端隔两拍采样那你需要考虑的不仅是setup检查沿往后移还要确保数据在接收端的采样窗口内保持稳定hold分析必须非常小心。这也是为什么我一直强调写约束之前先和数据通路的owner把数据更新的真实节拍对齐了再动手。多周期路径这个约束本身不难难的是把它放到整个设计的时间关系里去理解。它没有一套万能公式可以直接套每个场景的N和hold偏移都要回到“数据实际什么时候更新、什么时候采样”这两个问题上来。把这两个问题问清楚再复杂的跨时钟域约束也有章可循。
返回列表