这一篇在干嘛?

静态时序分析(STA,Static Timing Analysis)是工具替你”穷举”设计中所有时序路径的检查手段,不用写测试激励就能确认设计是否满足时序。本章先复习标准分析,再讲 STA 的”疑难杂症”:锁存器、异步电路、组合逻辑反馈环——这些结构在 FPGA 里不推荐用,但万一遇到,你得会分析。

18.1 标准分析

STA 的定义:对照一组约束,对设计中所有时序路径做穷举分析,判断设计是否”时序合规”(timing compliant)。

FPGA 设计者遇到的基本路径有三类:

典型同步时序路径 图18-1 三类基本路径:输入到触发器、触发器到触发器、触发器到输出

每类路径都有各自的时序要求:输入延迟(input delay)、输出延迟(output delay)、建立时间(setup time,时钟沿到来前数据必须提前稳定的时间)和保持时间(hold time,时钟沿到来后数据必须继续保持稳定的时间)。

两组分析:

  • Setup 分析 = 长路径分析:数据太慢会违例;
  • Hold 分析 = 短路径分析:数据太快(离时钟沿太近)会违例。

设计的最高频率由最长路径(关键路径,critical path)决定。路径分析不只算逻辑延迟,还包括:

时序分析的组成要素 图18-2 路径延迟由 clock-to-Q、逻辑、布线、setup 和时钟偏斜组成

最小周期公式(忽略时钟抖动 jitter):

各符号含义:

符号含义
允许的最小时钟周期
时钟到达至数据出现在 Q 端的延迟
触发器之间逻辑的传播延迟
触发器之间的布线延迟
建立时间:数据须在下一时钟上升沿前提前到达的时间
时钟从发送触发器到捕获触发器的传播延迟差

如果要求的周期大于 ,就有正裕量(positive slack)

正裕量 图18-3 数据在捕获时钟沿减去 setup 之前到达,即为正裕量

数据在”捕获时钟沿 − setup”之前到达 → 正 slack,通过;之后到达 → 负 slack,违例。

Setup 分析同样适用于 I/O:对输入路径,外部芯片的 clock-to-Q 和传播延迟合并成一个”外部延迟”参与分析;输出路径同理。这些外部延迟工具不知道,必须由设计者在约束里定义

关于 hold 违例:数据在时钟沿后”到得太快”导致,理论上加缓冲就能修。FPGA 里 hold 违例很少见——因为布线矩阵自带最小延迟;一旦出现,通常说明时钟偏斜(clock skew)有问题。

放宽约束:多周期与伪路径

除了准确定义时钟,还要把可以放宽的路径明确告诉工具,最常见的两类:

多周期路径 图18-4 多周期路径:数据允许 n 个周期到达

多周期路径(multicycle path):数据不需要在紧跟着的那个时钟沿被使用,允许 n 个周期传播。注意:setup/hold 要求依然生效——不管数据在哪个时钟沿被采样,只要离采样沿太近,仍可能进入亚稳态(metastable,触发器采样到变化中的数据而输出不确定的状态)。典型场景:DSP 应用中采样率固定、每个采样点有多个时钟周期可用。

伪路径(false path):时序工具从电路结构上”看得到”一条路径,但按设计逻辑,信号根本不可能沿这条路径传播:

伪路径 图18-5 逻辑上不可能导通的路径,STA 直接忽略

两者关键区别(考试重点):

多周期路径即使约束给到任意大的 n,仍可能时序违例;伪路径永远不会有任何时序违例。

因为多周期路径仍会被检查 setup/hold,伪路径则被彻底排除在分析和优化之外。

STA vs 动态时序分析(仿真)

STA 相对带时序信息的仿真(动态分析)的优势:

  • 穷举 vs 抽查:动态分析的覆盖率取决于测试激励编写者的水平,STA 则覆盖标准分析范围内的所有路径;
  • 抖动(jitter)等特性很难建模——建模会让 testbench 极复杂、仿真时间爆炸,STA 无此烦恼;
  • 各种条件组合时,STA 的运行时间按比例增长,动态仿真则是指数级爆炸;
  • STA 不需要仿真周期,没有事件调度器的开销;
  • STA 覆盖的违例类型更广:正/负 setup/hold、最小/最大转换时间、时钟偏斜、毛刺检测、总线冲突等;
  • STA 工具能自动识别关键路径、违例和异步时钟。

18.2 锁存器(Latches)

锁存器是电平敏感的存储单元(控制信号有效期间数据直通,无效时保持),与边沿触发的触发器(flip-flop)不同。在高速芯片(如微处理器)设计中锁存器有正当用途,但只应被专家级设计者在有充分理由时使用。现实中最常见的情况是:锁存器由糟糕的编码风格无意中制造出来,然后出现在 STA 报告里造成困惑。

锁存器是怎么被无意造出来的

最典型的”锁存器制造机”:

// 注意:会推断出锁存器(LATCH INFERENCE)
module latch (
    output reg oDat,
    input iClk, iDat);
always @*
    if (iClk) oDat <= iDat;
endmodule

代码点评:always @* 是纯组合逻辑块,但 if 没有 else 分支——当 iClk 为低时工具只能”保持 oDat 原值”,保持 = 存储 = 锁存器。

更隐蔽的”无意锁存器”长这样:

// 糟糕的编码风格(POOR CODING STYLE)
module latchinduced(
    output reg oData,
    input iClk, iCtrl,
    input iData);
    reg rData;
    reg wData;
 
    always @*
    if (iCtrl) wData <= rData;   // 组合块缺 else → 锁存器
 
    always @(posedge iClk) begin
    rData <= iData;
    oData <= wData;
    end
endmodule

代码点评:两个标准 D 触发器之间被插入了一个锁存器——输入输出都打了拍,中间却是电平敏感的,这种结构没人会故意写,但缺 else 就够了。

无意引入的锁存器 图18-6 两个 D 触发器之间被推断出一个高有效锁存器

STA 怎么分析锁存器

分析”高有效”锁存器(控制为高时数据通过、为低时保持)时:

  • 控制为高:数据直通,不关心;
  • 控制为低:输出冻结,不关心;
  • 真正要检查的是数据被”锁住”的那一刻——控制信号从高变低的边沿

STA 工具因此把锁存器的控制信号当作时钟来对待,把锁存器当作下降沿触发的触发器来分析

含锁存器的时序合规 图18-7 时序检查点:控制信号关闭锁存器的那个边沿

双相锁存(Dual-Phase Latching)

一种正经使用锁存器的流水线拓扑:流水线中一级用时钟的一种极性锁存,相邻级用相反极性锁存:

双相锁存 图18-8 相邻流水级交替使用相反极性的锁存器

四锁存交替极性的实现:

// 注意:会推断出锁存器(LATCH INFERENCE)
module dualphase(
    output oData,
    input iCtrl, iNCtrl,
    input iData);
    reg [3:0] wData;
 
    assign oData = wData[3];
 
always @* begin
    if(iCtrl)  wData[0] <= iData;     // 高有效锁存
    if(iNCtrl) wData[1] <= wData[0];  // 低有效锁存
    if(iCtrl)  wData[2] <= wData[1];
    if(iNCtrl) wData[3] <= wData[2];
end
 
endmodule

代码点评:单一时钟产生 iCtrl 与其反相 iNCtrl 两个控制,四个锁存器极性交替,数据像接力一样两级一级传递:

交替极性 图18-9 时钟两个相位交替控制相邻锁存器

在深度流水的设计(如微处理器)里,双相锁存有两个好处:面积更小(锁存器比触发器小)、功耗更低(时钟的两个边沿都在传数据)。前提是流水线划分非常清晰——没有清晰流水线的电路根本用不了这套方法。

STA 报告的呈现方式:整个设计只有一个时钟域(即锁存器控制信号),所有路径都报告在上升沿(低有效锁存)与下降沿(高有效锁存)之间。

18.3 异步电路

这里的”异步电路”指所有不属于”时钟元件到时钟元件”这种标准分析范畴的电路。这些结构在 FPGA 里通常不推荐使用,列出它们不是鼓励你用,而是万一遇到要知道怎么分析。

18.3.1 组合逻辑反馈(Combinatorial Feedback)

定义:一条从某个网线(net)出发、穿过组合逻辑(AND/OR/MUX 等)、不经过任何同步元件又回到同一网线的路径。

组合反馈通常意味着编码错误——综合工具应报警告,而且会让仿真器出问题(尤其组合延迟为零时)。根据具体电路,组合反馈要么表现为振荡(如反相器+延迟搭的自激振荡器),要么表现为存储元件(交叉耦合 NAND 可以构成基本锁存器和触发器)。

问题在于:这类路径的终点不是时序元件,STA 没法按常规方式测量。解决办法是人为在约束中定义时序端点。看这个自激振荡器的例子:

// 糟糕的编码风格(BAD CODING STYLE)
module freeosc(
    output oDat,
    input iOscEn, iOutEn);
    wire wdat;
 
assign wdat = iOscEn ? !wdat: 0;   // 组合反馈:wdat 依赖自身取反
assign oDat = iOutEn ? wdat : 0;
endmodule

代码点评:wdat = !wdat 形成了组合环路,综合成这样的结构——

意外的组合反馈 图18-10 wdat 通过反相器反馈回自身,构成振荡环

STA 默认只会分析从输入到输出的路径,甚至没有数据去测量环路自身的延迟。解决方法:给反馈网线 wdat 加一个时序端点。Xilinx UCF(用户约束文件)写法:

NET "wdat" TPSYNC = "looptime";
TIMESPEC "ts_looptime" = FROM "looptime" TO "looptime" 1 ns;

约束点评:先把 wdat 定义为时序组 looptime 的端点,再约束”从 looptime 到 looptime”的环延迟为 1 ns——这样 STA 就有测量依据了,会报告出约 850 ps 的环路延迟。

要点小结

  • 多周期路径给多大 n 仍可能违例;伪路径永远不会有违例——这是两者最本质的区别。
  • 锁存器多半是编码疏忽(组合块里 if 缺 else)造出来的。
  • STA 分析锁存器时,关注点是控制信号把锁存器”关上”的那个边沿,锁存器等效为下降沿触发器。
  • 深度流水设计中,双相锁存能换来更小、更快的实现。
  • 组合反馈通常指示编码错误;STA 分析时需人为定义时序端点。

常见坑

组合逻辑块(always @*)里的 if 忘写 elsecase 忘写 default,都会推断出锁存器——而且它藏在两个正常触发器中间,时序报告看着眼熟却对不上号,极其折磨人。写组合逻辑时养成习惯:每条 if 必有 else,每个 case 必有 default,让所有分支都给信号赋值,锁存器就无处藏身。

通关标准:

学完本篇你应该能做到:① 用 公式拆解一条路径的延迟构成,说清 setup/hold、slack、clock skew 各是什么;② 区分多周期路径和伪路径,知道什么时候该用哪种约束;③ 识别代码中的无意锁存器,并理解 STA 如何把锁存器当作下降沿触发器来分析。