这一篇在干嘛?

综合工具给你几十个优化选项,但大多数人只是瞎试组合碰运气。本章基于实战经验讲清楚这些优化到底做了什么、什么时候有效、什么时候反而帮倒忙:速度与面积的权衡真相、资源共享、流水线/寄存器平衡、状态机编译与重编码、黑盒时序模型、物理综合。

14.1 速度 vs 面积:一个误导性的开关

综合工具都有”优化目标:速度/面积”的开关。直觉上想快就选速度、想小就选面积——但这个开关可能产生相反的结果(让它跑快,设计反而变慢)。要理解为什么,先看这两个选项实际做了什么。

综合阶段的速度/面积优化,决定的是用哪种逻辑拓扑(logic topology)实现你的 RTL。此时综合工具对 FPGA 的物理特性(尤其是布局布线后的互连延时)几乎一无所知——它只能用线负载模型(wire load model,基于统计的互连延时估算)。所以综合优化主要是门级优化:状态机编码、并行 vs 串行 mux、逻辑复制等。

经验法则:更快的电路需要更多并行结构,而并行 = 更大面积。这就是速度/面积的基本权衡。但 FPGA 布局的二阶效应会打破它:

  • 综合阶段选定逻辑拓扑后,要到布局布线完成工具才知道器件有多拥塞;
  • 如果你选了”速度”优化,综合生成一大坨并行逻辑,而器件已经快满了,布局工具只能把逻辑塞到随便哪里能放下的地方——次优布线带来长延时,结果比紧凑设计还慢;
  • 设计师出于成本考虑常常选尽可能小的 FPGA,所以这种情况非常常见。

关键启发

当资源利用率逼近 100% 时,综合层的速度优化未必产生更快的设计——面积优化反而可能更快。

约束与实际速度的四个区域

下图是某 RISC 微处理器在 Virtex-II 上的实测数据(关键路径被物理约束在紧凑区域内),横轴是约束频率、纵轴是实际达到的频率:

约束速度与实际速度的关系 图 14.1 实际速度与约束速度的关系曲线

曲线分为四个区域:

  1. 欠约束(Underconstrained):底部平坦区(约束 < 95MHz)。逻辑的紧凑实现就能跑 95MHz 左右,不需要什么时序优化。
  2. 优化区(Optimization region):95~135MHz 的线性区。在这个范围内,提高约束可以换来综合器相应地生成更快(也更大)的逻辑结构。
  3. 峰值(Peak):逻辑结构优化 + 器件空间能支持的最高约束。
  4. 过约束(Overconstrained):顶部平坦区,约束超过可达频率,再怎么加也没用。

实践含义——不要过度约束:目标频率设在最终速度的 +15~20% 以内。设得太高,初次实现时综合器发现离时序目标太远可能提前放弃,用激进的(大面积)结构实现,反而导致更低的最高速度;约束设得合理,逻辑会用最小面积达成目标,给时序收敛留出灵活性。

14.2 资源共享

架构级的资源共享(用选通逻辑复用运算单元)在前面讲速度架构时讨论过。综合优化层的资源共享更简单:在两组寄存器之间的逻辑里,找出互斥的相似算术运算,合并成一个。看例子:

module addshare (
    output oDat,
    input iDat1, iDat2, iDat3,
    input iSel);
 
assign oDat = iSel ? iDat1 + iDat2: iDat1 + iDat3;
endmodule

直接映射:两个加法器各算各的,再选择:

两个加法器的直接实现 图 14.2 两个加法器的直接实现

有经验的设计师一眼看出:两个加法都含 iDat1,只需一个加法器,把 iDat2/iDat3 在输入端 mux 即可。打开综合工具的资源共享选项,它会自动识别这两个加法互斥并合并:

合并后的加法器资源 图 14.3 合并后的单个加法器:输入端加 mux

代价在哪里?看这个三加法版本:

module addshare (
    output oDat,
    input iDat1, iDat2, iDat3,
    input [1:0] iSel);
 
assign oDat = (iSel == 0) ? iDat1 + iDat2:
    (iSel == 1) ? iDat1 + iDat3:
    iDat2 + iDat3;
 
endmodule

三个加法器的直接映射 图 14.4 三个加法器的直接映射:最坏延时 = 一级加法器 + 一级 mux

共享后的加法器 图 14.5 共享后:一个加法器,但关键路径变成三级逻辑

直接映射的最坏路径是”加法器 + mux”两级;共享后所有加法合并成一个、输入端要叠两层 mux——关键路径变成三级逻辑。是否真的影响时序,取决于具体逻辑和 FPGA 剩余资源。

聪明的综合工具只在非关键路径上做资源共享;如果你的工具没这个能力,就得自己检查关键路径有没有被加延时。

启发:开资源共享后,检查它有没有拖慢关键路径。

14.3 流水线、重定时与寄存器平衡

这组优化不增加也不删除寄存器,而是移动触发器位置,让任意两级寄存器之间的逻辑延时均衡,从而压缩最坏延时。概念如图:

均衡组合逻辑 图 14.6 寄存器平衡:移动触发器使两级间的逻辑负载均衡

三个术语大同小异,各厂商叫法略不同:

  • 流水线(pipelining):最早的方法,针对规则结构(流水线化的存储器、乘法器),要求工具能识别出规则的流水线。
  • 重定时(retiming)/寄存器平衡(register balancing):更一般的情况——触发器穿越逻辑移动,保持对外功能不变。

例子一:可参数化流水线乘法器

module multiple #(parameter width = 8, parameter depth = 3) (
    output [2*width-1: 0] oProd,
    input [width-1: 0] iIn1, iIn2,
    input iClk);
    reg [2*width-1: 0] ProdReg [depth-1: 0];
    integer i;
assign oProd = ProdReg [depth-1];
always @(posedge iClk) begin
    ProdReg[0] <= iIn1 * iIn2;
    for (i=1;i <depth;i=i+1)
        ProdReg[i] <= ProdReg [i-1];
end
endmodule

输入相乘后接 depth 级流水寄存器。不做自动流水时,只有1 个寄存器被拉进乘法器当输出寄存器,其余流水寄存器全堆在输出端:

乘法器后面接流水线 图 14.7 乘法器 + 输出流水线:寄存器堆积在输出,逻辑严重不均衡

启用流水线优化后,寄存器被推进乘法器内部(符号里的”3”表示内部三层流水):

流水线移入乘法器 图 14.8 寄存器移入乘法器内部:逻辑被流水线均衡

例子二:一般情况的重定时

module regbalance(
    output reg oProd,
    input [7:0] iIn1,
    input iReset,
    input iClk);
    reg [7:0] inreg1;
always @(posedge iClk)
    if(iReset) begin
    inreg1 <= 0;
    oProd <= 0;
    end
    else begin
    inreg1 <= iIn1;
    oProd <= (inreg1[0] | inreg1[1]) & (inreg1[2] | inreg1[3]) & (inreg1[4] | inreg1[5]) & (inreg1[6] | inreg1[7]);
    end
endmodule

逐寄存器精确映射(accurate register-for-register)时,所有逻辑都挤在两级寄存器之间:

不均衡的逻辑 图 14.9 不均衡的逻辑:全部组合逻辑堆在两级寄存器之间

启用寄存器平衡后,寄存器被移进关键逻辑中间:

均衡后的逻辑 图 14.10 均衡后的逻辑:寄存器移入逻辑中部,最坏延时减半

整体时序改善——延迟和吞吐率都没有损失(寄存器数量可能增减,运行时间会延长)。所以:聪明的工具只对关键路径做寄存器平衡。

启发:不要对非关键路径应用寄存器平衡。

14.3.1 复位对寄存器平衡的影响

要合并/移动两个触发器来均衡逻辑,两个触发器的复位行为必须一致

  • 一个同步复位、另一个异步复位(本身就是坏设计)→ 不能合并;
  • 一个是置位(set)、一个是复位(reset)→ 不能合并。

混合复位类型阻止寄存器平衡 图 14.11 驱动逻辑门的寄存器初始化为 1-0-1-0 交替模式,复位类型不兼容,无法平衡

聪明的工具有时能绕过:反相复位类型 + 同时反相触发器输入和输出(workaround)。但如果 workaround 引入的延时抵消了平衡收益,工具会退回直接映射。结论:相邻触发器复位类型不同,可能让寄存器平衡失效。 这也是”每个模块统一复位类型”的又一理由。

14.3.2 再同步寄存器:绝不能被平衡

寄存器平衡会出问题的一个明确场景是信号再同步(resynchronization):对来自 FPGA 外部或另一时钟域的异步信号,用两级触发器打拍(double-flop)防亚稳态:

再同步寄存器未被平衡 图 14.12 再同步寄存器(未被平衡的正确形态)

平衡被应用到再同步寄存器 图 14.13 危险:平衡把后续逻辑推进了两级同步器之间

如果开启寄存器平衡,再同步器后面的逻辑可能被推进两级同步寄存器之间(图 14.13)。这对亚稳态防护是致命的:我们恰恰要求不对可能亚稳态的信号做任何逻辑运算,并给它尽可能多的稳定时间。

启发:用约束保护再同步寄存器,禁止寄存器平衡触碰它们。(多数综合工具支持对个别寄存器加约束。)

14.4 状态机编译(FSM Compilation)

FSM 编译指综合工具自动识别 RTL 中的有限状态机,并按速度/面积约束重新编码。含义很解放:只要你的状态机用标准风格编写,具体怎么编码(用什么值表示状态)并不重要——工具能提取状态跳转和输出依赖,转换成对当前设计更优的形式。

启发:状态机用标准编码风格写,让综合工具能识别并重新优化。

三种编码的适用场景:

  • 二进制/顺序编码(binary/sequential):状态译码依赖状态寄存器的所有位,需要译码逻辑。适合逻辑资源丰富、有多输入门的工艺。
  • 独热编码(one-hot):每个状态一个独占的位,无需状态译码,通常更快;缺点是寄存器多。FPGA 寄存器富余,独热通常是首选——大多数状态机会被工具替换成独热结构,所以干脆一开始就用独热写,省运行时间。
  • 格雷码(gray code):两大应用场景——驱动异步输出低功耗设计

为什么异步输出要用格雷码

独热码的状态跳转会有”一位清零、另一位置位”同时发生。如果状态机输出(或它驱动的逻辑)是异步的,两位状态寄存器的路径延时差(取决于布局和寄生参数)就会造成竞态(race condition)和毛刺。看一个 Moore 机的输出编码:

Moore 机输出示例 图 14.14 Moore 机的输出编码:跳变时一位清零、另一位置位

潜在冒险 图 14.15 潜在冒险:两位变化不同步,输出出现瞬态毛刺

格雷码的构造方法(镜像追加法,mirror-append):

  1. 竖排写下 “0” 和 “1”;
  2. 从底部开始镜像复制一份;
  3. 镜像中复制的上半部分追加 “0”;
  4. 镜像中新生成的下半部分追加 “1”。

格雷码的构造 图 14.16 用镜像追加法构造格雷码

结果:任意一次状态跳变只改变一位——异步逻辑中的竞态条件从根源上消除。

启发:驱动异步输出时使用格雷码。

14.4.1 不可达状态的移除与安全模式(Safe Mode)

状态机编译器通常会移除未使用的状态,甚至能检测并移除不可达状态——这优化速度也省面积。但有一类设计需要保留不可达状态的处理:高可靠性电路(航空、军用、航天)。在极小工艺下,太阳或核事件的辐射粒子能让触发器自发翻转(SEU)。如果状态机没有为所有寄存器状态组合准备恢复路径,一次翻转就可能让电路进入无法恢复的状态。所以综合工具普遍提供 safe mode:为所有状态(包括不可达的)覆盖恢复逻辑。

看一个三状态循环机:

module safesm (
    output [1:0] oCtrl,
    input iClk, iReset);
    reg [1:0] state;
 
    // 把 state 别名到输出
    assign oCtrl = state;
 
    parameter STATE0 = 0,
    STATE1 = 1,
    STATE2 = 2,
    STATE3 = 3;
 
    always @(posedge iClk)
    if (!iReset) state <= STATE0;
    else
    case(state)
    STATE0: state <= STATE1;
    STATE1: state <= STATE2;
    STATE2: state <= STATE0;
    endcase
endmodule

注意:状态寄存器有 2 位,能表示 4 个状态,但 case 只处理 3 个——STATE3(以及非法的 2’b11 组合)没有恢复路径。普通实现就是一个移位寄存器:

简单状态机实现 图 14.17 简单状态机实现:一个移位寄存器

问题:如果位 1 和位 2 同时被辐射误置位,这个错误状态会一直循环传播,输出持续错误,直到下次复位。开启 safe mode 后:

开启 safe mode 的状态机实现 图 14.18 safe mode:附加逻辑检测非法状态并强制复位

附加逻辑检测到非法状态后立即把状态寄存器拉回复位值。

14.5 黑盒(Black Boxes)

黑盒是综合流程中的占位模块:真正的网表或优化过的模块要到实现流程后期才加入(典型如 Xilinx Core Generator 生成的 FIFO、RAM 核)。黑盒的问题是:综合器无法优化它,也很难围绕它优化——时序引擎不知道黑盒接口的时序,只能按最坏情况假设。

启发:能用 RTL 就别造需要黑盒的核;实在要用,必须把核生成工具提供的时序模型带上。

看一个例子:16 位自由计数器周期性地把数据推入 FIFO:

module fifotop(
    output [15:0] oDat,
    output    oEmpty,
    input iClk, iReset,
    input iRead,
    input iPushReq);
reg [15:0] counter;
wire wr_en, full;
 
assign wr_en = iPushReq & !full;
 
always @(posedge iClk)
    if(!iReset) counter <= 0;
    else counter <= counter + 1;
 
myfifo myfifo (
    .clk (iClk),
    .din (counter),
    .rd_en(iRead),
    .rst (iReset),
    .wr_en(wr_en),
    .dout (oDat),
    .empty(oEmpty),
    .full (full));
endmodule

黑盒模块用 syn_black_box 综合指令声明,只留 I/O 占位:

module myfifo(
    output [15: 0] dout,
    output empty,
    output full,
    input clk,
    input [15: 0] din,
    input rd_en,
    input rst,
    input wr_en) /* synthesis syn_black_box */;
endmodule

这条指令告诉综合器:别把这个模块优化掉,在网表里留占位,后端工具再填入真实网表。代价是时序和面积完全未知。看下面的例子:

黑盒周围的关键路径 图 14.19 穿过黑盒的关键路径:无时序信息时 setup/hold 被当作 0

最坏路径被判定为从黑盒输出到黑盒输入。因为没有任何 setup/hold 时序定义(默认按 0 处理),综合器给出的最高频率估算高达 248MHz——显然过于乐观。解决办法是在黑盒声明上补充 I/O 时序模型:

module myfifo(
    output [15: 0] dout,
    output empty,
    output full,
    input clk,
    input [15: 0] din,
    input rd_en,
    input rst,
    input wr_en) /* synthesis syn_black_box
syn_tsu1 = "din[15:0]->clk=4.0"
syn_tsu2 = "rd_en->clk=3.0"
syn_tsu3 = "wr_en->clk=3.0"
syn_tco1 = "clk->dout=4.0"
syn_tco2 = "clk->empty=3.0"
syn_tco3 = "clk->full=3.0"
*/;
endmodule

给控制信号定义 3ns、数据 I/O 定义 4ns 的 setup/hold 时间后,综合器对同一条路径的估算修正为 125MHz——只有原来的一半!这就是不带时序模型的黑盒对你撒的谎。

14.6 物理综合(Physical Synthesis)

物理综合 = 在综合过程中利用物理布局信息做优化。通过预先估算布局(或直接执行试布局),综合器能拿到接近最终实现的负载模型,优化质量大幅提升;估算出的布局信息会前向标注(forward annotate)给后端布局布线工具。

FPGA 的物理综合比 ASIC 更难:ASIC 布线资源灵活,延时可以按两点距离统计估算;FPGA 布线资源是固定的——少量长距离快布线资源(低偏斜长线)+ 大量处理局部拥塞的慢速开关矩阵(switch matrix):

简化的布线矩阵 图 14.20 简化的布线矩阵:快慢两级布线资源

在先进工艺下布线延时已成主导,布线延时估算不准,综合优化就是盲人摸象。物理综合的解法就是在综合阶段生成布局信息。

14.6.1 前向标注 vs 后向标注

  • 后向标注(back-annotation):传统时序收敛路径。综合按统计估算实现 → 布局布线 → 时序分析 → 违例反馈回综合 → 改约束重跑。能收敛,但迭代次数多、自动化程度低,设计师被迫反复跑工具:

后向标注时序违例 图 14.21 传统流程:时序信息从后端反馈回前端

  • 前向标注(forward annotation):把综合做出的布局假设作为约束/物理参数向前传,驱动后端实现。综合估算与最终实现强关联,重跑次数大减:

前向标注布局数据 图 14.22 物理综合流程:布局数据前向传递,迭代大幅减少

即使时序失败仍需反馈(要么调布局、要么改逻辑结构),但综合与布局紧密耦合后,收敛前的迭代次数显著减少

启发:物理综合让综合与布局的关联更紧密。

14.6.2 基于图的物理综合

高性能物理综合的关键是准确估算布线拥塞和延时。Synplicity 的 graph-based physical synthesis 把布线资源和时序信息抽象成布线资源图(routing resource graph),布局决策不按物理距离,而按图上的加权计算

基于图的物理综合 图 14.23 图基物理综合:按拥塞加权的”距离”与物理最短距离不同

这个方法综合考虑任意两点之间的布线资源和现有利用率,让综合与布局关联更紧。但它解决不了高层分区问题和关键路径约束——那是第 15 章布局规划的主题。

常见坑

黑盒不带 I/O 时序模型是最容易踩的优化陷阱:综合器把黑盒的 setup/hold 当作 0,报告的”最高频率 248MHz”看起来一片大好,实际按 4ns 数据建立时间修正后只剩 125MHz。时序收敛决策全部建立在虚假估算上,等后端核网表进来才发现差一倍。任何黑盒(FIFO/RAM/DSP 核)实例化时都要补齐 syn_tsu/syn_tco 类时序参数,或导入核生成工具的时序模型。另一个隐蔽坑:再同步(防亚稳态)两级寄存器忘了加”禁止寄存器平衡”约束,综合器把后续逻辑推进两级同步器之间,亚稳态防护形同虚设。

通关标准:

学完本篇你应该能做到:

  1. 解释为什么”选速度优化”在资源利用率接近 100% 时可能反而更慢,并把目标频率控制在最终速度 +20% 以内;
  2. 说明资源共享如何合并互斥的相似运算,以及它可能给关键路径增加逻辑级数;
  3. 区分流水线/重定时/寄存器平衡(移动触发器、不加不减)及其对延迟、吞吐率的影响;
  4. 说出复位类型不一致会阻止寄存器平衡,并主动用约束保护再同步寄存器;
  5. 为不同场景选 FSM 编码:FPGA 首选独热、异步输出用格雷码(会用镜像追加法构造)、高可靠设计开 safe mode;
  6. 给黑盒补齐 setup/hold 时序模型,理解前向标注相比后向标注减少迭代的原因。