4.2 延迟模型与毛刺


文档摘要

4.2 延迟模型与毛刺 本节摘要:Verilog 用 给事件排班,但仿真延迟与真实物理延迟是两套刻度;惯性延迟会吞掉窄脉冲,让门级仿真并非物理世界的完整投影。毛刺是组合逻辑的天然分泌物,应对之道不是消灭它,而是设计上不让任何人依赖它。 触发资格与调度次序清楚了,这一节看时间刻度本身:延迟怎么写、仿真器怎么解释、以及组合逻辑为什么天然产毛刺。这一节的内容在 RTL 设计里用得少、在仿真与调试里用得多——它是看懂门级波形的前置课。 延迟的三种挂法 延迟控制有三处挂点,语义各不相同: 语句前延迟是「先等再算」,assign 延迟是「先算再晚点送达」——一个推迟动作,一个推迟结果,方向相反。测试台里大量使用前者制造激励节拍;

4.2 延迟模型与毛刺

本节摘要:Verilog 用 #延迟 给事件排班,但仿真延迟与真实物理延迟是两套刻度;惯性延迟会吞掉窄脉冲,让门级仿真并非物理世界的完整投影。毛刺是组合逻辑的天然分泌物,应对之道不是消灭它,而是设计上不让任何人依赖它。

触发资格与调度次序清楚了,这一节看时间刻度本身:延迟怎么写、仿真器怎么解释、以及组合逻辑为什么天然产毛刺。这一节的内容在 RTL 设计里用得少、在仿真与调试里用得多——它是看懂门级波形的前置课。

延迟的三种挂法

延迟控制有三处挂点,语义各不相同:

// 挂在语句前:块内该语句暂停指定时间再执行(过程块内) initial begin rst_n = 1'b0; #23 rst_n = 1'b1; // 23 个时间单位后释放复位 end #10 x = a & b; // 等待 10 再求值赋值 // 挂在 assign 上:右侧变化后,延迟一段时间再更新左侧 assign #2 y = a & b; // a 或 b 变化,y 在 2 个单位后更新 // 挂在门原语上:门延迟,门级仿真的标配 and #3 g1(t, a, b);

语句前延迟是「先等再算」,assign 延迟是「先算再晚点送达」——一个推迟动作,一个推迟结果,方向相反。测试台里大量使用前者制造激励节拍;RTL 里写成综合是无效的(综合器忽略所有延迟),它的合法舞台是行为建模与门级网表反标。

延迟还可以细分为上升、下降、关断三段:and #(2,3) g1(t,a,b) 表示上升沿路径 2 个单位、下降沿路径 3 个单位。这套精细控制在库单元建模里常见,RTL 层面知道即可。

惯性与传输:两种传播哲学

assign 与门的默认延迟是惯性延迟:脉宽短于延迟量的脉冲会被吞掉。物理直觉是——真实的门有响应时间,输入变化太快、来不及完成一次完整响应时,输出根本追不上。传输延迟则像理想传输线:来什么传什么,多窄都照传。语言里默认前者,要后者需要特殊建模手法,日常遇到的几乎都是惯性延迟。

module pulse_eater(input wire a, output wire y); assign #5 y = a; // 惯性延迟:宽度不足 5 的脉冲到不了 y endmodule

y 送一个宽度为 3 的脉冲,波形上 y 纹丝不动——脉冲在半路被吞了。这在调试时会制造假象:源端明明发了请求,目的端毫无反应,各波形窗口来回比对才发现请求窄于路径延迟。记住这条特性,看到「窄脉冲消失」先查惯性延迟,再看握手逻辑。

毛刺:组合逻辑的天然分泌物

毛刺(glitch)指组合逻辑输出在稳定前的短暂错误翻转。成因是「汇聚路径不等长」:同一个信号经过不同延迟的多条路径汇合,先到的变化与后到的变化短暂共存,输出闪一下。仿真里这些翻转表现为同一时刻内多拍 delta 的来回震荡;真实电路里表现为纳秒级的窄脉冲。

关键认知:毛刺不是 bug,是组合逻辑的物理属性,消灭它既不必要也不可能。真正的纪律是下游设计「不依赖无毛刺假设」——组合输出直接被当作另一个模块的时钟、直接被当作异步复位,才是事故:

// 危险:组合信号当时钟 assign tick = (cnt == 8'd99); always @(posedge tick) pulse_cnt <= pulse_cnt + 1'b1; // tick 由比较器产生,比较器输出翻转瞬间可能毛刺, // 毛刺被 posedge 捕获就是假时钟沿——计数器多发脉冲 // 正确:统一时钟 + 使能 always @(posedge clk) begin tick_d <= tick; if (tick & ~tick_d) // 上升沿检测,与 clk 同步 pulse_cnt <= pulse_cnt + 1'b1; end

两段代码功能描述相同,电路命运相反:前者把组合毛刺喂进时钟端,后者把组合条件寄存一拍、用同步使能表达同样的逻辑。规矩由此而来:组合信号永远只进数据端,不进时钟端、不进异步复位端——这是硬件设计里少数没有例外可讲的铁律之一。

案例展开:寄存器输出为什么是美德。 背景:某模块的「完成」信号由组合译码产生,直连下游的状态机,联调时下游偶发提前一拍动作。操作:在完成信号后插入一级寄存器(输出打一拍),下游以寄存后的信号为准;重跑联调,异常消失。结果:模块对外接口全部改为寄存器输出,成为团队接口规范。解读:寄存器输出把「组合求值 + 传输延迟」的不确定窗口锁进一拍,下游看到的是沿上稳定的值;这不仅消除毛刺传播,还把两级模块间的时序路径切成两段独立路径,综合工具各自优化、互不牵连——工程上叫「时序弧切断」,是模块化设计的核心手法。变式:输出打拍会引入一拍延迟,对延迟敏感的路径(如存储器写使能)要核算协议时序余量再决定打拍位置,不能一刀切。

RTL 仿真为什么常常见不到毛刺

行为级 RTL 仿真用零延迟或统一延迟建模,组合逻辑在同一个 delta 内完成求值,毛刺被「抽象」掉了。门级仿真带真实单元延迟,毛刺才会显形。这解释了仿真策略的分工:RTL 仿真验功能,门级仿真验时序相关的物理现象,两者验证的目标不同、谁也替代不了谁。第九章调试技巧里会给出「什么时候值得花时间跑门级仿真」的判断标准。

对 RTL 设计者的实操结论:既然 RTL 仿真看不到毛刺,就更要靠设计纪律预防它——不把组合信号当时钟与复位、模块出口寄存器化、跨模块握手全部同步化。这些纪律的本质,是让设计正确性不依赖「组合输出干净」这种仿真里无法验证的假设。

延迟建模的一组追问

问:RTL 阶段要不要写延迟

可综合代码里不写——写了也被忽略,徒增误导。有两处例外:测试台里的激励延迟(那是仿真的节拍器)与行为级参考模型的粗略延迟(验证系统级时序假设时用)。区分「被综合的代码」与「只做仿真的代码」,延迟的使用边界就清楚了。

问:为什么仿真里看不到真实延迟

因为 RTL 模型根本不含物理延迟信息。真实延迟由门单元速度与布线长度决定,两者都要等综合布局后才存在。流程上的衔接是:布局布线完成后工具导出延迟文件,反标进门级仿真——这就是门级仿真排在流程后段的原因:它依赖物理实现的产物。

问:零延迟模型会不会掩盖功能 bug

会掩盖时间类 bug(毛刺、竞争、建立保持),但不会掩盖功能类 bug(逻辑错误、协议错误)。这正是 RTL 仿真的定位:先保证功能正确,再由带延迟的验证层保证时间正确。两层验证的目标清单不同,跳过任何一层都是在另一层的盲区里赌博。

本节要点回顾

  • 延迟两种挂法方向相反:语句前延迟先等再算,assign 延迟先算晚送;
  • 默认是惯性延迟:窄于延迟量的脉冲被吞,追「消失的脉冲」先查这里;
  • 毛刺是属性不是 bug:汇聚路径不等长必产生毛刺,消灭不可能,隔离是正道;
  • 组合信号禁入时钟与复位端:假时钟沿与假复位都来自这里,铁律无例外;
  • 寄存器输出是接口美德:锁住不确定窗口、切断时序弧,代价是增加一拍延迟;
  • RTL 仿真抽象掉毛刺:功能验证在 RTL、物理现象验证在门级,分工不可互相替代。

延迟与毛刺的规律清楚了,下一节落地到每个设计的节拍源:时钟与复位怎么建模才可靠。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U