Verilog HDL 学习笔记(十)| 第10章 时序和延迟

10.1 延迟模型的类型

在真实电路中,信号从输入到输出需要时间,这个时间来自两个方面:

  • 门延迟(Gate Delay) :信号通过逻辑门(与门、或门等)需要的时间

  • 线延迟(Wire Delay) :信号在导线中传播需要的时间

Verilog 提供了三种延迟模型来描述这些延迟。

一、分布延迟(Distributed Delay)

定义 :将延迟值分散在电路中每一个独立的元件上。

两种建模方式:

复制代码
// 方式1:在门级原语上指定延迟
module and4(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire an1, an2;

    and #1 (an1, a, b);      // 第1个与门延迟 1 个单位
    and #2 (an2, c, d);      // 第2个与门延迟 2 个单位
    and #1.5 (out, an1, an2); // 第3个与门延迟 1.5 个单位
endmodule

// 方式2:在 assign 语句上指定延迟
module and4(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire an1, an2;

    assign #1 an1 = a & b;   // 延迟 1 个单位
    assign #2 an2 = c & d;   // 延迟 2 个单位
    assign #1.5 out = an1 & an2; // 延迟 1.5 个单位
endmodule

特点分析:

特点 说明
精度高 每个元件的延迟都可以单独指定,与实际电路更接近
建模繁琐 大规模电路需要为每个门指定延迟,工作量巨大
依赖内部结构 需要了解模块内部的逻辑门细节
适用场景 小规模电路、底层单元库建模

二、集总延迟(Lumped Delay)

定义 :将整个模块所有路径的延迟汇总到最后一个输出门上。

复制代码
// 集总延迟:将所有延迟集中到最后一个与门
module and4(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire an1, an2;

    and (an1, a, b);          // 无延迟
    and (an2, c, d);          // 无延迟
    and #3.5 (out, an1, an2); // 所有延迟集中在这里(取最大路径延迟)
endmodule

计算集总延迟的方法:

  • 计算所有输入到输出的路径延迟

  • 最大值作为集总延迟值

以上面分布延迟的电路为例:

  • 路径 a → out:1 + 1.5 = 2.5

  • 路径 b → out:1 + 1.5 = 2.5

  • 路径 c → out:2 + 1.5 = 3.5

  • 路径 d → out:2 + 1.5 = 3.5

  • 集总延迟 = 3.5(取最大值)

特点分析:

特点 说明
建模简单 只需在最后一个输出门指定一个延迟值
精度较低 无法区分不同输入到输出的不同延迟
不依赖内部结构 只需知道整体延迟,无需了解内部细节
适用场景 小规模电路、快速评估

三、路径延迟(Path Delay / 引脚到引脚延迟)

定义 :为模块的每一个输入引脚到每一个输出引脚的路径分别指定延迟时间。

复制代码
module and4(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire an1, an2;

    // 路径延迟:为每条输入→输出路径指定延迟
    specify
        (a => out) = 2.5;
        (b => out) = 2.5;
        (c => out) = 3.5;
        (d => out) = 3.5;
    endspecify

    and (an1, a, b);
    and (an2, c, d);
    and (out, an1, an2);
endmodule

特点分析:

特点 说明
精度最高 每个输入到输出的延迟都可以独立指定
易于获取 可以从数据手册中直接查到
模块独立 即使模块内部逻辑改变,路径延迟说明可保持不变
适用场景 大规模电路、标准单元库、IP核(最常用)

四、三种延迟模型对比

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                         三种延迟模型对比                                   │
│                                                                             │
│  分布延迟                   集总延迟                   路径延迟              │
│  ┌─┐  ┌─┐  ┌─┐            ┌─┐  ┌─┐  ┌─┐            ┌─┐  ┌─┐  ┌─┐         │
│  │1│  │2│  │1.5│           │ │  │ │  │3.5│           │2.5│  │2.5│  │3.5│     │
│  └─┘  └─┘  └─┘            └─┘  └─┘  └─┘            └─┘  └─┘  └─┘         │
│  每个门有自己的延迟         所有延迟集中到最后       每个输入→输出有独立延迟  │
│                                                                             │
│  精度:高                   精度:低                  精度:最高             │
│  工作量:大                 工作量:小                工作量:中等           │
│  适用:小规模               适用:快速评估            适用:大规模/标准库    │
└─────────────────────────────────────────────────────────────────────────────┘

0.2 路径延迟建模

路径延迟是最常用的延迟模型,通过 specify来实现。

一、specify

specify 块是模块中一个独立的部分 ,以 specify 关键字开始,以 endspecify 结束。

重要规则:

  • specify不能 出现在 initialalways 等其他块中

  • specify 块中不能包含其他语句块

specify 块的三大功能:

功能 说明
指定路径延迟 为所有输入→输出路径指定引脚到引脚的延迟
定义 specparam 常量 在块内定义延迟参数常量
设置时序检查 使用 $setup$hold$width 等系统任务

二、并行连接(Parallel Connection)

语法( <源域> => <目标域> ) = <延迟值>;

特点 :源域的每一位与目标域中相应位 连接,位宽必须相同

复制代码
module M(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire e, f;

    specify
        // 并行连接:每个输入独立连接到输出
        (a => out) = 9;
        (b => out) = 9;
        (c => out) = 11;
        (d => out) = 11;
    endspecify

    and a1(e, a, b);
    and a2(f, c, d);
    and a3(out, e, f);
endmodule

向量并行连接:

复制代码
// a[3:0] 和 out[3:0] 都是4位向量
specify
    (a => out) = 9;  // 等价于下面4条语句
    // (a[0] => out[0]) = 9;
    // (a[1] => out[1]) = 9;
    // (a[2] => out[2]) = 9;
    // (a[3] => out[3]) = 9;
endspecify

位宽必须匹配(a[4:0] => out[3:0]) 是非法的,因为位宽不同(5位 vs 4位)。

三、全连接(Full Connection)

语法( <源域> *> <目标域> ) = <延迟值>;

特点 :源域的每一位 与目标域的每一位 都建立连接,位宽可以不同

复制代码
module M(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire e, f;

    specify
        // 全连接:a、b的每一位都与out连接,延迟9
        (a, b *> out) = 9;
        // 全连接:c、d的每一位都与out连接,延迟11
        (c, d *> out) = 11;
    endspecify

    and a1(e, a, b);
    and a2(f, c, d);
    and a3(out, e, f);
endmodule

全连接的展开

复制代码
// (a, b *> out) = 9; 等价于:
// (a => out) = 9;
// (b => out) = 9;

// 如果 a[31:0] 是32位,out[15:0] 是16位
// (a *> out) = 9; 等价于 32 × 16 = 512 条并行连接!

四、并行连接 vs 全连接

对比维度 并行连接 => 全连接 *>
连接方式 一对一(位对位) 一对多(每位到每位)
位宽要求 必须相同 可以不同
连接数量 N 条(N = 位宽) M × N 条(M和N为位宽)
适用场景 数据总线按位对应 控制信号到多位输出

五、specparam 声明

specify 块内部,可以使用 specparam 定义延迟参数常量

复制代码
module D_FF(q, d, clk, reset);
    output q;
    input d, clk, reset;

    specify
        // 在 specify 块内定义参数
        specparam d_to_q = 9;
        specparam clk_to_q = 11;

        (d => q) = d_to_q;
        (clk => q) = clk_to_q;
    endspecify
endmodule

specparam 的特点:

特点 说明
作用域 只能在定义它的 specify 块内部使用
parameter 的区别 parameter 是全局参数,specparamspecify 块专用
用途 集中管理延迟值,方便修改
建议 使用 specparam 而不是硬编码数值

六、边沿敏感路径(Edge-Sensitive Paths)

只在源信号的特定边沿才有效的路径延迟。

复制代码
specify
    // 在 clock 上升沿,从 clock 到 out 的延迟
    // 上升延迟10,下降延迟8
    (posedge clock => (out +: in)) = (10:8);
endspecify

语法说明:

  • out +: in:表示从 inout 的数据路径(不反相)

  • out -: in:表示从 inout 的数据路径(反相)

七、条件路径延迟(Conditional Path Delay / SDPD)

路径延迟的值可能依赖于输入信号的状态

复制代码
module M(out, a, b, c, d);
    output out;
    input a, b, c, d;
    wire e, f;

    specify
        // 条件路径延迟:根据信号a的值选择不同的延迟
        if (a) (a => out) = 9;
        if (~a) (a => out) = 10;

        // 条件表达式包含两个信号
        if (b & c) (b => out) = 9;
        if (~(b & c)) (b => out) = 13;

        // 使用拼接操作符
        if ({c, d} == 2'b01) (c, d *> out) = 11;
        if ({c, d} != 2'b01) (c, d *> out) = 13;
    endspecify

    and a1(e, a, b);
    and a2(f, c, d);
    and a3(out, e, f);
endmodule

特点:

  • 条件表达式可以包含逻辑操作符、位操作符、缩减操作符、拼接操作符等

  • if 语句中不能使用 else 结构

  • 又称状态依赖路径延迟(SDPD)

八、上升/下降/关断延迟

引脚到引脚的延迟可以详细指定上升、下降和关断延迟值。

复制代码
specify
    // 1个延迟值:所有类型都用这个值
    specparam t_delay = 11;
    (clk => q) = t_delay;

    // 2个延迟值:上升延迟、下降延迟
    // 上升:0→1, 0→z, z→1
    // 下降:1→0, 1→z, z→0
    specparam t_rise = 9, t_fall = 13;
    (clk => q) = (t_rise, t_fall);

    // 3个延迟值:上升延迟、下降延迟、关断延迟
    // 关断:0→z, 1→z
    specparam t_rise = 9, t_fall = 13, t_turnoff = 11;
    (clk => q) = (t_rise, t_fall, t_turnoff);

    // 6个延迟值:精确指定所有转换
    // 顺序:0→1, 1→0, 0→z, z→1, 1→z, z→0
    specparam t_01 = 9, t_10 = 13, t_0z = 11;
    specparam t_z1 = 9, t_1z = 11, t_z0 = 13;
    (clk => q) = (t_01, t_10, t_0z, t_z1, t_1z, t_z0);
endspecify

延迟值数量规则:

延迟值数量 含义 是否合法
0个 无延迟(默认0)
1个 所有类型相同
2个 上升、下降(关断取较小者)
3个 上升、下降、关断
4个 - ❌ 非法
5个 - ❌ 非法
6个 0→1, 1→0, 0→z, z→1, 1→z, z→0
7-11个 - ❌ 非法
12个 完整指定所有转换(含x)

九、最小/典型/最大延迟值(Min/Typ/Max)

与第5章门延迟一样,路径延迟也可以指定最小值、典型值、最大值

复制代码
specify
    // 上升延迟:min=8, typ=9, max=10
    // 下降延迟:min=12, typ=13, max=14
    // 关断延迟:min=10, typ=11, max=12
    specparam t_rise = 8:9:10;
    specparam t_fall = 12:13:14;
    specparam t_turnoff = 10:11:12;
    (clk => q) = (t_rise, t_fall, t_turnoff);
endspecify

仿真器命令行控制:

命令选项 使用的延迟值
+maxdelays 使用最大值
+typdelays 使用典型值(默认)
+mindelays 使用最小值

十、X状态转换的保守处理

如果没有显式指定 X 转换的延迟,Verilog 采用保守方法

  • X → 已知状态 :消耗可能的最大时间

  • 已知状态 → X :消耗可能的最小时间

    // 6个延迟的路径延迟说明
    specparam t_01 = 9, t_10 = 13, t_0z = 11;
    specparam t_z1 = 9, t_1z = 11, t_z0 = 13;
    (clk => q) = (t_01, t_10, t_0z, t_z1, t_1z, t_z0);

    // X 转换的计算:
    // 0 → x : min(t_01, t_0z) = min(9, 11) = 9
    // 1 → x : min(t_10, t_1z) = min(13, 11) = 11
    // z → x : min(t_z0, t_z1) = min(13, 9) = 9
    // x → 0 : min(t_10, t_z0) = min(13, 13) = 13
    // x → 1 : min(t_01, t_z1) = min(9, 9) = 9
    // x → z : min(t_1z, t_0z) = min(11, 11) = 11

10.3 时序检查(Timing Checks)

指定路径延迟的目的是让仿真更接近实际电路。但光有延迟还不够------还需要检查设计是否满足时序约束(如建立时间、保持时间等)。

重要规则: 所有时序检查系统任务必须放在 specify 块内部

一、$setup ------ 建立时间检查

建立时间(Setup Time) :数据必须在有效时钟边沿之前到达的最小时间。

复制代码
specify
    // 检查 data 相对于 clock 上升沿的建立时间
    // 如果 T(posedge_clk - Tdata) < 3,报告违规
    $setup(data, posedge clock, 3);
endspecify

时序图:

复制代码
        ────建立时间────
        ←──────────────→
        ┌──────────────┐
data    │   有效数据    │
────────┴──────────────┴────────
                    ↑
                clock上升沿

二、$hold ------ 保持时间检查

保持时间(Hold Time) :数据必须在有效时钟边沿之后保持不变的最小时间。

复制代码
specify
    // 检查 data 相对于 clock 上升沿的保持时间
    // 如果 T(data - Tposedge_clk) < 5,报告违规
    $hold(posedge clock, data, 5);
endspecify

注意: $setup$hold 的参数顺序是不同的

系统任务 参数顺序
$setup $setup(data_event, ref_event, limit)
$hold $hold(ref_event, data_event, limit)

三、$setuphold ------ 同时检查建立和保持

Verilog 提供了同时检查建立时间和保持时间的系统任务。

复制代码
specify
    // 同时检查建立时间(2ns)和保持时间(3ns)
    $setuphold(posedge clk, data, 2, 3);
endspecify

四、$width ------ 脉冲宽度检查

检查脉冲宽度是否满足最小宽度要求

复制代码
specify
    // 检查 clock 的高电平脉冲宽度
    // 如果高电平持续时间 < 6,报告违规
    $width(posedge clock, 6);
endspecify

时序图:

复制代码
        ←──── 宽度 ────→
        ┌──────────────┐
clock   │              │
────────┘              └────────

$width 会自动检测 posedge 指定的边沿到下一个反向边沿之间的时间。

五、完整示例:带时序检查的 D 触发器

复制代码
module D_FF(q, d, clk, reset);
    output reg q;
    input d, clk, reset;

    // 时序逻辑
    always @(posedge clk or posedge reset) begin
        if (reset)
            q <= 1'b0;
        else
            q <= d;
    end

    // 时序检查和路径延迟
    specify
        // 路径延迟
        specparam t_d_to_q = 2;
        specparam t_clk_to_q = 3;
        (d => q) = t_d_to_q;
        (clk => q) = t_clk_to_q;

        // 时序检查
        specparam t_setup = 1.5;
        specparam t_hold = 2.0;
        specparam t_width = 3.0;

        $setup(d, posedge clk, t_setup);
        $hold(posedge clk, d, t_hold);
        $width(posedge clk, t_width);
    endspecify
endmodule

10.4 延迟反标注(Delay Back-Annotation)

一、什么是延迟反标注?

延迟反标注(Delay Back-Annotation) 是将物理版图提取的真实延迟信息标注回门级网表的过程。

简单说:前面的延迟都是"估算"的,而反标注是把芯片制造前的真实延迟填进去,让仿真结果更接近实际芯片。

二、为什么需要延迟反标注?

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    为什么需要延迟反标注?                                   │
│                                                                             │
│  1. 初步估算的延迟不精确                                                    │
│     └── 综合工具给出的延迟是粗略估计的,没有考虑布线延迟                    │
│                                                                             │
│  2. 布局布线后的延迟才真实                                                  │
│     └── 版图确定后,可以根据电阻R和电容C计算出真实的线延迟                  │
│                                                                             │
│  3. 验证时序是否真正满足                                                    │
│     └── 用真实延迟重新仿真,确认芯片流片后能正常工作                        │
└─────────────────────────────────────────────────────────────────────────────┘

三、延迟反标注的完整流程

复制代码
第1步:RTL设计 + 功能仿真
    ↓
第2步:逻辑综合 → 门级网表(含粗略延迟估计)
    ↓
第3步:初步时序仿真 + STA(静态时序分析)
    ↓
第4步:布局布线 → 版图
    ↓
第5步:从版图提取R和C参数 → 计算真实延迟
    ↓
第6步:生成SDF文件(标准延迟格式文件)
    ↓
第7步:$sdf_annotate 将延迟反标注到门级网表
    ↓
第8步:精确时序仿真 + STA
    ↓
第9步:验证通过 → 流片生产

四、SDF 文件(Standard Delay Format)

SDF 是用于延迟反标注的标准格式文件

SDF 文件包含的内容:

  • IOPATH:输入到输出的路径延迟

  • INTERCONNECT:互连线延迟

  • TIMING CHECK:时序约束参数(建立时间、保持时间等)

SDF 文件格式示例(简化):

复制代码
(DELAYFILE
    (DESIGN "top")
    (DATE "Sep 7 2026")
    (TIMESCALE 1ns)
    (CELL
        (CELLTYPE "and_gate")
        (INSTANCE u_and)
        (DELAY
            (ABSOLUTE
                (IOPATH A Z (1.5:1.8) (1.3:1.7))
                (IOPATH B Z (1.5:1.8) (1.3:1.7))
            )
        )
    )
)

五、$sdf_annotate 系统任务

$sdf_annotate 是 Verilog 提供的用于延迟反标注的系统任务。

语法:

复制代码
$sdf_annotate("sdf_file.sdf", top_module);

示例:

复制代码
module testbench;
    // 实例化设计模块
    top u_top(...);

    initial begin
        // 将 SDF 文件中的延迟反标注到设计
        $sdf_annotate("top.sdf", u_top);
        #1000 $finish;
    end
endmodule

常用选项:

  • +sdf_verbose:输出详细的反标注信息,用于验证反标注是否正确
相关推荐
幼儿园蛋小黄1 小时前
嵌入式学习总结:从入门到实战的完整路径
学习
-To be number.wan1 小时前
编译原理第三章:编译器是怎么“认字“的?——词法分析全解析
学习·编辑器
zyf1044162 小时前
暑期实践日志 Day47:复盘第七章内容,规整对应成果
学习·计算机网络·剪辑·暑期实践·课题任务
传奇开心果编程2 小时前
【Rust入门知识点学与练】第10课:Option 和 Result(错误处理入门)
开发语言·学习·rust
喵的前端路2 小时前
《EDVAC》学习记录(第二部分)
学习
lee_curry2 小时前
AI Agent 工程师完整学习路线(面向生产级项目与面试)
人工智能·学习·ai·面试·agent
小毛驴8503 小时前
前端开发学习路线可以分为几个层次
学习
血小板要健康3 小时前
链表 阶段算法总结
java·数据结构·笔记·算法·leetcode·链表
star learning white3 小时前
STM32入门学习3
stm32·单片机·学习