3.【Verilog】Verilog 门延迟
第一步:分析与整理Verilog 门延迟
1. 门延迟基础
1.1 为什么需要门延迟?
- 实际门电路有传输延迟,Verilog 允许用户在门级单元例化时指定延迟,使仿真更接近真实硬件行为。
1.2 三种基本延迟类型
| 延迟类型 | 定义 | 示例 |
|---|---|---|
| 上升延迟 (rise delay) | 输出从0, x, z变为1的时间 | 开启晶体管 |
| 下降延迟 (fall delay) | 输出从1, x, z变为0的时间 | 关断晶体管 |
| 关断延迟 (turn-off delay) | 输出从0,1,x变为z的时间 | 三态门进入高阻 |
- to_x 延迟:输出变为
x所需时间,取上述三种延迟的最小值。 - 多输入门(与门、或门)最多只定义 2 个延迟(输出不会变
z)。 - 三态门、MOS 开关可以定义 3 个延迟。
- 上下拉(
pullup/pulldown)无延迟。 tran双向开关无延迟;tranif1/0可定义 1 或 2 个延迟(开通/关断延迟)。
1.3 延迟指定格式
gate_type [delay] [instance_name] (signal_list);1.3.1 延迟个数与含义
| 延迟个数 | 上升 | 下降 | 关断 | to_x |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 (d) | d | d | d | d |
| 2 (d1, d2) | d1 | d2 | min(d1,d2) | min(d1,d2) |
| 3 (d1, d2, d3) | d1 | d2 | d3 | min(d1,d2,d3) |
示例:
and #(1) (OUT1, IN1, IN2); // 所有延迟=1 or #(2.1, 2) (OUT2, IN1, IN2); // 上升2.1,下降2,关断=min(2.1,2)=2 bufif0 #(2, 1, 1.3) (OUT3, IN1, CTRL); // 上升2,下降1,关断1.31.3.2 最小/典型/最大延迟(min:typ:max)
- 模拟工艺波动,仿真时可选择使用最小值、典型值或最大值。
- 语法:
#(min:typ:max)或#(min1:typ1:max1, min2:typ2:max2, ...)
示例:
and #(1:2:3) (OUT1, IN1, IN2); // 所有类型: min=1, typ=2, max=3 or #(1:2:3, 3:4:5) (OUT2, IN1, IN2); // 上升: 1:2:3; 下降: 3:4:5; 关断: min(1,3):min(2,4):min(3,5) = 1:2:3 bufif0 #(1:2:3, 3:4:5, 2:3:4) (OUT3, IN1, CTRL); // 上升:1:2:3; 下降:3:4:5; 关断:2:3:42. D 触发器的门级建模(带门延迟)
2.1 基础结构演进(理解原理)
SR 触发器(由两个与非门交叉耦合)
- 真值表:S=0,R=1 → Q=1;S=1,R=0 → Q=0;S=1,R=1 → 保持;S=0,R=0 → 禁止(Q=Q’=1)
带使能的 SR 锁存器(增加两个与非门)
- EN=0 时保持;EN=1 时与 SR 触发器相同。
D 锁存器(在 SR 锁存器的 D 输入加反相器,使 S 和 R 互补)
- 当 EN=1,Q = D;当 EN=0,保持。电平触发,在 EN=1 期间输出随 D 变化。
D 触发器(上升沿或下降沿触发)– 主从结构
- 两级 D 锁存器级联,时钟反相连接。
- 主锁存器在 CP=1 时采样(透明),从锁存器在 CP=0 时锁存输出。
- 最终实现仅在时钟下降沿(本例是下降沿)采样 D 并保持一个周期。
2.2 门级 D 触发器 Verilog 模型(带延迟参数)
完整模块D_TRI如下(保留原文代码):
module D_TRI( input D, CP, output Q, QR); parameter RISE_TIME = 0.11 ; parameter FALL_TIME = 0.07 ; //part1, not gate wire CPN, DN ; not #(RISE_TIME, FALL_TIME) (CPN, CP); not #(RISE_TIME, FALL_TIME) (DN, D); //part2, master trigger wire G3O, G4O ; nand #(RISE_TIME, FALL_TIME) (G3O, D, CP); nand #(RISE_TIME, FALL_TIME) (G4O, DN, CP); wire #(RISE_TIME, FALL_TIME) G1O, G2O ; // 这里wire的延迟?原文如此,但实际应该是无延迟,可能是笔误 nand #(RISE_TIME, FALL_TIME) (G1O, G3O, G2O); nand #(RISE_TIME, FALL_TIME) (G2O, G4O, G1O); //part3, slave trigger wire G7O, G8O ; nand #(RISE_TIME, FALL_TIME) (G7O, G1O, CPN); nand #(RISE_TIME, FALL_TIME) (G8O, G2O, CPN); wire G5O, G6O ; nand #(RISE_TIME, FALL_TIME) (G5O, G7O, G6O); nand #(RISE_TIME, FALL_TIME) (G6O, G8O, G5O); assign Q = G5O ; assign QR = G6O ; endmodule注意:原文中
wire #(RISE_TIME, FALL_TIME) G1O, G2O ;这种写法并不常见(wire 通常不带延迟),可能是教程笔误,实际门延迟已在nand实例中给出。我们在教学时应强调:延迟应附在门实例上。
2.3 Testbench 与仿真结果
`timescale 1ns/1ps module test ; reg D, CP = 0 ; wire Q, QR ; always #5 CP = ~CP ; // 周期10ns,占空比50% initial begin D = 0 ; #12 D = 1 ; #10 D = 0 ; #14 D = 1 ; #3 D = 0 ; #18 D = 0 ; end D_TRI u_d_trigger( .D(D), .CP(CP), .Q(Q), .QR(QR) ); initial begin forever #100 if ($time >= 1000) $finish ; end endmodule仿真结果分析:
- Q(即
G5O)在 CP 下降沿采样 D 的值,并保持到下一个下降沿。 - QR 是 Q 的反相(因为
G6O为另一个交叉耦合输出)。 - 输出相对时钟下降沿有延迟(约 360ps),由门级延迟累积而成。
延迟追踪(原文对cap3时刻的分析):
- CP → CPN:上升延迟 110ps
- CPN → G8O:下降延迟 70ps
- G8O → G6O:上升延迟 110ps
- G6O → Q:下降延迟 70ps
- 总和 360ps,与设置的
RISE_TIME=0.11ns, FALL_TIME=0.07ns匹配。
第二步:费曼教学法 – 通俗讲解门延迟与 D 触发器
费曼技巧核心:用最简单的比喻,把复杂概念讲得连外行也能听懂。
作为验证工程师,会告诉你:这些东西在实际工作中哪里用得上?怎么用?踩过哪些坑?
一、门延迟:给数字电路加上“时间标尺”
1.1 为什么需要门延迟?
想象你设计了一个电路,仿真时所有信号瞬间变化(0延迟)。但真实芯片里,信号从一个门传到另一个门需要时间。如果没有延迟,你会发现:
- 时序检查(setup/hold)无法进行
- 竞争冒险看不到
- 综合后的网表仿真与 RTL 行为不一致
加入延迟后,仿真能更真实地反映硬件行为,提前发现时序问题。
1.2 三种延迟的生活化比喻
| 延迟类型 | 比喻 |
|---|---|
| 上升延迟 | 你推一个秋千从地面(0)到最高点(1)需要的时间 |
| 下降延迟 | 秋千从最高点回到地面的时间 |
| 关断延迟 | 水龙头从关紧到完全没水的“断流时间” |
为什么有上升和下降之分?
因为晶体管导通(输出1)和截止(输出0)的电阻不同,时间也不同。比如 NMOS 拉低快,PMOS 拉高快。
1.3 如何指定延迟?记住“个数含义”
- 1个数:通通一样(懒人模式)
- 2个数:第一个上升,第二个下降,关断取两者最小
- 3个数:上升、下降、关断分别指定
你可能会犯的错误:
- 给
and门写了 3 个延迟 → 语法错误,因为and输出不会变z。 - 给
tranif1写了 3 个延迟 → 也是错误,它最多 2 个(开通/关断)。
工作中如何应用?
- 在门级网表仿真(GLS,Gate Level Simulation)时,库文件(
.lib或.v)会为每个标准单元提供延迟值,你不需要手动写。 - 但在教学或简单模型中,自己加延迟可以观察时序逻辑的行为。
1.4 最小/典型/最大延迟:应对芯片制造波动
同一个芯片,因工艺偏差,有的批次跑得快(最小延迟),有的慢(最大延迟)。你可以这样仿真:
// 选择快模型 `define FAST // 或者仿真器选项 +mindelays / +typdelays / +maxdelays这样做的好处:验证电路在**最差(max)情况下是否满足时序,在最快(min)**情况下是否出现保持时间违例。
二、D 触发器的门级建模:从最简电路一步步搭出来
2.1 为什么要学这个?工作中又不用门级搭触发器
你说得对,现在没有人手画 6 个与非门做一个 DFF。但是:
- 理解原理:知道为什么 DFF 是边沿触发,为什么有 setup/hold 要求。
- 调试底层:当你仿真一个 PLL 或 IO 单元时,可能会看到门级网表。
- 写延时模型:有时需要自己写一个带延迟的简单触发器用于测试。
学习路线:
SR 触发器 → SR 锁存器 → D 锁存器 → D 触发器(主从)
这是一个循序渐进的智力体操,理解后你会对“时序”有通透的认识。
2.2 核心:主从结构为什么能实现边沿触发?
- 主锁存器:时钟高电平透明(像个打开的窗户)
- 从锁存器:此时因为时钟反相,它是锁住的(窗户关着)
- 当时钟下降沿到来瞬间:
- 主锁存器立即锁住(窗户关闭),固定了当时的 D 值
- 从锁存器变为透明(窗户打开),把主锁存器的值传出去
结果:只有下降沿那一刻 D 的值被采样,其他时间输出不变。
关注点:
- 延迟参数
RISE_TIME和FALL_TIME作用于每个与非门。 - 输出延迟累积:从 CP 变化到 Q 变化的路径上有多个门,总延迟 = 各门延迟之和。
2.3 仿真波形的现实意义
图中 CP 下降沿后,Q 并没有立刻变化,而是过了 360ps 才变。这在实际时序分析中叫Clock-to-Q 延迟(Tckq)。
验证工程师需要知道:这个延迟是否影响下游电路的建立时间?
工作中如何学习?
- 跑一下这个示例:用 VCS/Icarus Verilog 仿真,打开波形。
- 修改延迟参数:比如把
RISE_TIME改大,看 Q 的变化如何滞后。 - 尝试自己搭一个上升沿触发的 DFF:只需把 CP 和 CPN 交换。
三、验证工程师的实战心法
3.1 门延迟在什么场景下真正重要?
| 场景 | 说明 |
|---|---|
| 门级网表仿真(GLS) | 逻辑综合后的网表包含标准单元延迟,必须用 SDF(Standard Delay Format)反标进行时序仿真。 |
| IO 电路建模 | 比如 PAD 模型,需要指定输入到输出的延迟、三态使能延迟。 |
| 异步电路分析 | 判断毛刺是否会产生错误触发。 |
| 培训与教学 | 理解时序基础。 |
3.2 常见误区和调试技巧
误区1:以为延迟指定多了就能自动处理所有情况。
实际上,门延迟只是传输延迟(惯性延迟),不包含线延迟。精确时序要用 SDF。误区2:在 RTL 代码中使用
#delay。
这是仿真延迟,不可综合;而门级延迟(如nand #(2,3) (…))是建模延迟,两者不要混淆。调试技巧:
当你发现门级仿真波形与 RTL 不一致,首先检查timescale设置(是否1ns/1ps?),然后看延迟值是否过大导致信号在某个时间窗口内为x。
3.3 如何将这部分知识融入工作?
学会读标准单元库的 Verilog 模型:
里面会有bufif0 #(DELAY_RISE, DELAY_FALL) …这样的描述。掌握 SDF 反标:
$sdf_annotate("design.sdf", top_module);这是验证工程师的必备技能。
写简单的带延迟的激励模型:
比如模拟一个外部芯片的输出延迟:assign #(5,3) ext_data = (ctrl) ? int_data : 1'bz;
总结:一句话记住门延迟与 D 触发器
门延迟给每个门加上“时间标签”,让仿真接近真实;D 触发器的主从结构,是边沿触发的本质。工作中直接面对的是带 SDF 的网表,但理解这些原理能让你快速定位时序问题。
建议学习路径:
- 手打 D 触发器代码,跑仿真,看波形。
- 改变
RISE_TIME和FALL_TIME,观察总延迟变化。 - 用
$display打印每次 Q 变化的时间,验证延迟累计。 - 思考:如果给
nand只写两个延迟,关断延迟会取哪个值? - 尝试将 D 触发器改为上升沿触发,并测试。
当你遇到一个门级仿真失败,你能说出:“可能是时钟路径的上升延迟过大,导致数据在下降沿之前没准备好” —— 这节就没白学。
