尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Verilog时序检查系统任务详解:setup/hold/recrem

Verilog时序检查系统任务详解:setup/hold/recrem 做FPGA或ASIC验证的朋友大概率都见过这么一幕后仿真跑着跑着仿真器突然刷出一屏以$setup、$hold、$recrem开头的时间检查错误然后紧跟着一大串看不懂的路径信息。我第一次看到的时候也懵过以为仿真器出了问题后来才明白这是库单元里的时序检查系统任务被触发了本质上是设计里存在真实可以被激励覆盖到的时序违例。Verilog仿真中的时序检查系统任务timing check system task就是用来给仿真过程加“时间规矩”的。它们能检查建立时间setup、保持时间hold、时钟偏斜skew、脉冲宽度width、恢复时间recovery和移除时间removal配合STA静态时序分析能非常高效地暴露设计中的时序风险。这篇文章我就把这些任务的原理、参数、实操方法以及和STA的配合关系一次讲清楚适合正在做后仿真、被时序violation整得头疼的验证工程师也适合刚接触门级仿真的同学提前避坑。1. 时序检查系统任务在验证流程中的定位1.1 为什么要有这么多时序检查任务功能仿真默认是零延迟模型信号变化在同一个仿真时间步里完成根本不会出现“数据还没稳定就被采样”的情况。但真实芯片不是这样触发器有建立时间、保持时间时钟树有偏斜异步复位有恢复/移除时间要求。纯粹做RTL功能仿真这些物理限制完全体现不出来所以需要一套专门检查时间关系的机制。时序检查系统任务就是干这件事的。它们在仿真过程中持续监控指定事件对之间的时间间隔一旦不满足约束就按配置报告warning或error甚至触发notifier事件来让输出变成X态模拟真实电路里的亚稳态行为。简单说这就是在仿真世界里给电路立规矩谁坏了规矩立刻报警。1.2 系统任务与STA静态时序分析的分工很多人会问既然有PrimeTime这类STA工具做全路径时序分析仿真里还有必要放这些检查吗答案是需要而且两者的分工完全不同。STA静态时序分析是对整个设计的所有路径做穷举计算它不关心你给了什么激励只要约束写好了它就把每条路径的最大延迟、最小延迟都算一遍看setup/hold是否满足。它的强项是覆盖全、速度快、不依赖激励。但STA有一个前提所有路径都要有合理的约束。异步信号、跨时钟域、门控时钟这类场景约束稍微写得不好STA就“看不见”风险了。动态仿真里的时序检查任务则是随激励走的每一个事件沿都实时比对检查。它的优势在于能覆盖STA约束不到的动态场景尤其是异步交互。比如异步复位释放相对时钟沿太近STA里如果没做特殊约束根本不会报但后仿真时$recrem任务会立刻报出来。所以我的习惯是STA解决“全路径最差情况是否满足”仿真时序检查解决“实际激励下会不会出问题”两者互相兜底谁都不能缺。1.3 Specify块、SDF反标与系统任务的绑定关系标准Verilog里时序检查任务一般写在一个叫specify块的区域里跟模块的路径延迟声明放一起。库厂商在写仿真模型时会按工艺库里的时序参数把$setup、$hold这些调用直接嵌入specify块。到了门级仿真阶段综合或布局布线工具会生成SDFStandard Delay Format文件里面携带了实际的单元延迟和时序检查约束。仿真开始后通过$sdf_annotate任务把SDF反标到门级网表上specify块里的时序参数就会被SDF里的值覆盖。这样每次跑完布局布线只要更新SDF仿真模型里的时序检查参数就自动跟着工艺结果走不用手改。理解了这个机制你就明白了后仿真里那些violation不是仿真器随便报的而是实实在在对应着后端给的时序约束。2. 六大核心时序检查任务拆解与参数讲解2.1 $setup建立时间检查数据必须先到$setup用于检查数据事件相对于时钟事件是否满足建立时间要求。标准调用格式是$setup(data_event, reference_event, limit, [notifier]);其中data_event通常是数据信号的变化沿reference_event是时钟有效沿limit就是建立时间值。检查引擎在每次事件到来时比较两个事件的时间差如果数据沿距时钟沿的间隔小于limit就报违例。举个例子库模型里典型的写法specify $setup(posedge D, posedge CLK, 1.0); endspecify意思是D端的数据必须在CLK上升沿之前至少1ns到达并稳定。假设仿真中D在t9ns时变化CLK在t10ns时上升差值只有1ns恰好压线算满足如果D在t9.5ns才变化差值0.5ns就会报violation。实际使用要注意的是数据事件可以写成posedge D或negedge D取决于信号有效沿。有些低电平有效的控制信号就应该检查negedge沿。2.2 $hold保持时间检查数据不能跑太快$hold检查的是参考事件时钟沿之后数据事件必须保持稳定的最小时间。格式为$hold(reference_event, data_event, limit, [notifier]);注意参数位置跟$setup不一样参考事件在前数据事件在后。原因是hold检查关心的是时钟沿采样完成后数据不能立刻变化。库模型里常见写法specify $hold(posedge CLK, posedge D, 0.5); endspecify表示CLK上升沿之后D至少要保持0.5ns不变。如果D在CLK上升沿后0.2ns就跳变就报hold违例。这里有个容易混淆的点$hold的limit在很多库里是负值。比如$hold(posedge CLK, posedge D, -0.2)表示允许D在CLK沿之前0.2ns内就已经变化。这是合理的设计需求因为某些触发器的数据路径本身存在延迟数据在时钟沿附近小幅抖动是允许的。分析负值时心里只要记住一点仿真器比较的是两个事件的实际时间差limit为负就是在放宽限制方向移动门槛。2.3 $setuphold把建立与保持打包检查实际工程中更常用的是合并版本$setuphold一次把setup和hold都查掉$setuphold(reference_event, data_event, setup_limit, hold_limit, [notifier]);例如specify $setuphold(posedge CLK, posedge D, 1.2, 0.4, notifier); endspecify相比分开写$setup和$hold$setuphold的优点是当setup窗口和hold窗口存在重叠时报告行为更可控不会出现两个任务同时告警、信息互相干扰的情况。现在的标准单元库仿真模型里绝大多数时序检查都是用$setuphold实现的。再补充一点从SDF文件反标时序检查值时SDF里是拆成SETUP和HOLD两个条目分别给出的而Verilog模型里用一个$setuphold承接。工具在反标时会把SDF的两条数据映射到合并任务的setup_limit和hold_limit上这个对应关系在排查“为什么反标后检查值不对”的时候很关键。2.4 $recovery与$removal异步复位的恢复与移除检查$recovery和$removal专门用于异步信号的检查最常见的目标就是异步复位、异步置位。恢复时间recovery time的意义是异步信号比如复位释放沿到下一个有效时钟沿之间必须留出足够时间让触发器退出复位后能稳定工作。如果复位释放离时钟沿太近触发器可能采集到不确定状态。移除时间removal time的意义是时钟有效沿到来之后异步信号必须继续保持有效一段时间防止触发器的复位状态刚被采样就立刻撤销产生亚稳态。标准接口$recovery(reference_event, data_event, limit, [notifier]); $removal(reference_event, data_event, limit, [notifier]);库模型里常见的组合写法specify $recovery(posedge CLK, posedge RST_N, 1.0); $removal(posedge CLK, posedge RST_N, 0.8); endspecify这里posedge RST_N是低有效复位信号的释放沿从0变1把它当作data_event。时钟沿是reference_event。$recovery检查释放沿相对时钟沿之前的时间间隔$removal检查相对时钟沿之后的时间间隔。同样也有合并版本$recrem$recrem(posedge CLK, posedge RST_N, 1.0, 0.8, notifier);这个任务在后仿真里出场率极高尤其是验证异步复位释放策略的时候。2.5 $skew两个信号边沿之间的偏差检查$skew用来检查两个事件之间的时间偏差格式为$skew(reference_event, data_event, limit, [notifier]);它检查的是reference_event和data_event之间的时间差是否小于limit。如果小于limit表示两个信号边沿靠得太近可能触发问题。$skew的典型应用场景是检查差分时钟、门控时钟和原始时钟之间的偏斜。比如一个时钟门控单元要确保门控后的时钟沿与原始时钟沿之间的偏差不能太大specify $skew(posedge CLK, posedge GCLK, 0.3); endspecify意思是CLK上升沿和GCLK上升沿之间的间隔必须大于0.3ns太近就报错。这跟$setup和$hold的检查逻辑不一样$setup/$hold是与一个共同的时钟沿比较$skew是直接衡量两个事件之间的间隔更像一个“最小间距守卫”。2.6 $width脉冲宽度检查$width专门检查脉冲宽度调用格式$width(reference_event, limit, threshold, [notifier]);它通常以脉冲的起始沿作为reference_event比如specify $width(posedge CLK, 3.0, 0, notifier); endspecify表示CLK从上升沿开始高电平持续宽度必须至少3ns。如果CLK在2ns后就掉下去了就报错。threshold参数用于设置检测阈值一般取0表示不做额外的电平滤波对于某些模拟行为模型可以设置非零阈值来抑制小幅毛刺。$width用的地方相对少但在检查时钟最小脉宽、复位脉冲最小宽度时很实用。芯片后端对最小脉宽是有严格要求的时序报告里会有min pulse width检查项仿真里用$width可以提前发现激励侧的问题。2.7 notifier参数让时序违例真正影响仿真行为前面几个任务的最后一个参数都是可选的notifier这个参数很多人忽略但它恰恰是时序检查的精髓。notifier必须是一个寄存器变量。当时序检查失败时仿真器会让这个寄存器变量的值发生翻转。如果在specify块后面跟着时序建模逻辑把notifier连接到输出端的X态赋值条件上仿真结果就会出现真实的X态传播模拟亚稳态导致的输出不确定。没有接notifier的检查违反时序时只打印一条告警功能上电路照样按理想情况跑接了notifier之后输出会变成X下游逻辑跟着受影响这种“感染式”的传播更能暴露真实故障。标准单元库的仿真模型里notifier通常被很好地利用起来了。3. 实操过程与关键实现细节3.1 在testbench的specify块中手动添加时序检查很多时候我们并不想等到门级仿真才看时序在RTL阶段就想给关键信号加上时序约束做冒烟验证。这时候可以在testbench里自己写一个时序检查模块专门做这件事。下面是一个完整的示例覆盖了$setuphold、$recrem和$width三种检查timescale 1ns/1ps module timing_checker( input clk, input d, input rst_n ); reg notifier; specify $setuphold(posedge clk, d, 2.0, 0.5, notifier); $recrem(posedge clk, posedge rst_n, 1.5, 0.8, notifier); $width(posedge clk, 4.0, 0, notifier); endspecify always (notifier) begin if (notifier ! 1bx) begin $display(%0t [TIMING] notifier toggled, timing violation detected, $time); end end endmodule然后在主testbench里把需要监控的信号接到这个checker上timescale 1ns/1ps module tb_top; reg clk 0; reg d 0; reg rst_n 1; wire q; always #5 clk ~clk; // 被测设计 dff_async u_dut( .clk(clk), .d(d), .rst_n(rst_n), .q(q) ); timing_checker u_checker( .clk(clk), .d(d), .rst_n(rst_n) ); initial begin rst_n 0; #5 rst_n 1; #3 d 1b1; // clk上升沿在t10d在t8变化setup间隔2ns压线 #10 d 1b0; // clk上升沿在t15? 不对实际是t10之后下一个沿t20 #20 $finish; end endmodule这里d在t8变化下一个clk上升沿是t10建立时间刚好2ns满足$setuphold里2.0的约束。如果你想看violation把d的驱动时间改成t9建立间隔只有1ns仿真器就会报错。有一个地方要提醒specify块里的事件表达式所使用的信号必须模块端口可见这是语法要求。所以我上面用了一个独立模块来做检查器而不是直接写在tb_top里否则某些仿真器会报端口相关限制。3.2 门级后仿与SDF反标流程真实项目里时序检查任务大多是库模型自带并在门级仿真时被自动激活的。跑门级后仿的标准流程大概是这样的。第一步准备好门级网表和SDF文件。综合或布局布线后工具会输出top_netlist.v和top.sdf同时你需要一套和工艺匹配的仿真库模型通常是sim_models.v。第二步编译库模型和网表。以常见的VCS/ModelSim流程为例vlog -work work lib/sim_models.v vlog -work work netlist/top_netlist.v vlog -work work tb/tb_top.v第三步在testbench里调用$sdf_annotate反标SDFinitial begin $sdf_annotate(../output/top.sdf, tb_top.u_dut, , sdf_annotate.log); end$sdf_annotate的第一个参数是SDF文件路径第二个参数是被反标的设计实例路径。反标完成后库模型specify块里的时序参数会被SDF中的值覆盖时序检查任务开始按真实时序约束工作。第四步跑仿真并收集violation。这个阶段报出的所有timing violation每一行都值得认真对待因为它们背后大概率对应真实的时序风险。我在实际项目中见过不少团队跳过SDF反标只跑功能后仿。那样做的话门级网表的单元延迟出来了但时序检查参数还是库默认值根本反映不出后端最终时序收敛状态。所以反标这一步不能省。3.3 异步复位释放场景的recovery/removal检查实操下面用一个非常典型的异步复位释放场景演示recovery/removal检查的实际表现。假设被测模块是一个带异步复位的D触发器时钟周期10nsSDF反标后库模型要求recovery时间为1.5nsremoval时间为0.8ns。testbench里让复位信号在时钟上升沿附近释放timescale 1ns/1ps module tb_async_reset; reg clk 0; reg rst_n 0; reg d 1; wire q; always #5 clk ~clk; dff_async u_dut( .clk(clk), .d(d), .rst_n(rst_n), .q(q) ); initial begin // 复位保持3个时钟周期 repeat(6) (posedge clk); // 在时钟下降沿后1ns释放复位也就是离下一个上升沿4ns (negedge clk); #1 rst_n 1; repeat(2) (posedge clk); $display(%0t [TB] reset release test done, $time); $finish; end endmodule复位释放发生在negedge clk之后1ns到下一个posedge clk的间隔是4ns远大于1.5ns recovery所以这轮不会有问题。如果把释放时刻改成negedge clk之后4.5ns(negedge clk); #4.5 rst_n 1;下一个posedge clk在5ns之后中间间隔只有0.5ns小于1.5ns的recovery要求。此时仿真器会给出类似下面的报告** Error: $recrem: (tb_async_reset.u_dut) Order: posedge rst_n, posedge clk (rst_n changed at time 24500 ps, clk at 25000 ps) Violation: recovery time 1500 ps not met这个报错直接告诉我们复位释放离时钟沿太近了。看到这种错误第一反应不是去改仿真激励把报告压掉而是应该反思复位释放策略本身是不是需要加同步器是不是复位树延迟没有评估准确这才是时序检查的价值所在。3.4 实例一个不满足时序的复位释放如何被排查定位有一次我在做异步FIFO模块的后仿STAR里时序全部收敛但后仿一直报$recrem违例。刚开始我以为是SDF反标出问题排查了半天发现反标本身没问题真正原因是复位释放路径上的约束在STA里被误设成了false path导致STA根本没检查异步复位释放的时序。这个案例特别典型。STA工具全路径检查的前提是约束完备异步路径一旦被错误地set_false_path它就认为“这里不用查”但实际上异步复位的释放相对时钟沿是需要真正的时序保证的哪怕它不在STA默认的同步约束框架里。排查步骤我归纳下来是这样的第一步从后仿log里定位violation的具体路径和信号名确认是哪个实例报的。第二步打开该实例对应的库模型查specify块里对应的时序检查参数值和SDF反标结果比较。如果SDF里的值明显不合理优先检查反标路径是否写错或SDF文件本身是否包含该单元。第三步回到STA工具里用同样的路径做一轮report_timing。如果STA报告全部meet就要检查约束里是不是有false path、case analysis这类例外覆盖了路径。第四步如果确认是约束问题修正约束后重新做STA并把新SDF拿回来后仿再跑一遍。这套流程走下来基本能解决大部分“STA过了但后仿报时序错”的诡异问题。4. 常见问题与排查技巧实录4.1 后仿violation问题速查表工作中我整理过一张时序检查violation排查表放在这里给各位参考。现象可能原因排查方向大量$setup/$hold同时报错SDF未正确反标或库模型和网表不匹配检查sdf_annotate日志单独反标一条路径做验证只有$setup报错$hold正常数据路径延迟过大或时钟树偏斜导致建立时间裕量不足STA中检查max_delay路径分析时钟偏斜复位释放附近报$recrem复位释放沿离时钟沿太近或异步路径被设成false path调整复位释放策略检查STA例外约束$width频繁报错激励侧时钟/复位脉宽不满足最小要求查看时钟产生逻辑确认分频/门控电路无误报错位置在testbench内部信号检查器事件沿选错posedge/negedge反了核对信号极性确认数据有效沿后仿有violation但功能正常未接notifierviolation没有传播X态检查库模型notifier连接尽量保留X态传播这张表不只是排查用在写仿真环境的时候也可以反过来当设计checklist你要提前想清楚哪些检查必须开哪些信号需要被监控别等violation刷屏了再手忙脚乱。4.2 仿真报错信息的解读与定位方法刚接触时序检查的人经常被仿真器里一大段报错信息搞晕。其实把关键字段拆开看结构很清楚。比如VCS里常见的这种输出** Error: $setuphold: (tb_top.u_dut.u_reg1) Order: posedge clk, data (data changed at time 13750 ps, clk at 15000 ps) Violation: setup time 1500 ps not met第一行说明了任务类型和实例路径$setuphold告诉你这个错误来自哪个检查tb_top.u_dut.u_reg1告诉你发生在哪个实例。第二行Order说明了参数排列。这里posedge clk对应reference_eventdata对应data_event顺序信息决定了哪个是时钟、哪个是数据。第三行给出了实际时间data在13750ps变化clk在15000ps上升间隔1250ps小于要求的setup时间1500ps所以报错。看到这种信息先算差值再跟库里的时序约束值对比很快就能判断违例的严重程度。还有一个实操技巧很多仿真器会把所有timing violation统一汇总到仿真日志开头或结尾但中间也会穿插打印。建议在仿真命令里加上参数把violation单独输出到一个文件例如VCS里加上-assert report相关选项这样不会在海量日志里捞错误。4.3 与STA报告不一致时的思考路径同事最常问的问题是“为什么PT报告setup都meet了后仿还在报$setup violation”这跟前面异步复位案例类似通常可以从三个层面找原因。第一层激励本身不合理。仿真里给的数据变化沿比真实场景严苛得多比如testbench里让数据在一个时钟周期内连续翻转导致动态仿真触发setup违例但实际场景根本不会这样驱动。STA做的是静态最差路径分析关注的是路径延迟不是激励波形。这种情况属于激励问题修正激励时机即可。第二层约束和STA不一致。STA里做了set_multicycle_path、set_false_path、或时钟分组这些例外不会自动同步到仿真环境。仿真里的时序检查任务只认specify块和SDF里的值不知道你设了多少条多周期路径。这是最常见的不一致来源。第三层异步路径与仿真检查的覆盖范围不同。STA默认只查同步路径异步信号交互属于仿真检查的领地。如果PT没对异步路径建模那后仿报$recrem反而说明仿真环境比STA更敏感这时候应该把问题带回前端设计去讨论而不是忽略告警。我个人在实际操作中的一个体会是不要急着让后仿“闭嘴”把所有violation都手动waive掉。每一条时序violation都是信息尤其是那些和STA结论不一致的项往往是设计约束、复位策略或跨时钟域方案真正需要优化的地方。把这些violation当回事项目越往后越省心。最后再分享一个小技巧后仿真log里凡是出现timing check相关报错我都会顺手在STA工具里对同一实例重新查一遍路径两边对照着看很多隐藏问题一眼就能揪出来。
返回列表