这一篇在干嘛?
综合工具给你几十个优化选项,但大多数人只是瞎试组合碰运气。本章基于实战经验讲清楚这些优化到底做了什么、什么时候有效、什么时候反而帮倒忙:速度与面积的权衡真相、资源共享、流水线/寄存器平衡、状态机编译与重编码、黑盒时序模型、物理综合。
14.1 速度 vs 面积:一个误导性的开关
综合工具都有”优化目标:速度/面积”的开关。直觉上想快就选速度、想小就选面积——但这个开关可能产生相反的结果(让它跑快,设计反而变慢)。要理解为什么,先看这两个选项实际做了什么。
综合阶段的速度/面积优化,决定的是用哪种逻辑拓扑(logic topology)实现你的 RTL。此时综合工具对 FPGA 的物理特性(尤其是布局布线后的互连延时)几乎一无所知——它只能用线负载模型(wire load model,基于统计的互连延时估算)。所以综合优化主要是门级优化:状态机编码、并行 vs 串行 mux、逻辑复制等。
经验法则:更快的电路需要更多并行结构,而并行 = 更大面积。这就是速度/面积的基本权衡。但 FPGA 布局的二阶效应会打破它:
- 综合阶段选定逻辑拓扑后,要到布局布线完成工具才知道器件有多拥塞;
- 如果你选了”速度”优化,综合生成一大坨并行逻辑,而器件已经快满了,布局工具只能把逻辑塞到随便哪里能放下的地方——次优布线带来长延时,结果比紧凑设计还慢;
- 设计师出于成本考虑常常选尽可能小的 FPGA,所以这种情况非常常见。
关键启发
当资源利用率逼近 100% 时,综合层的速度优化未必产生更快的设计——面积优化反而可能更快。
约束与实际速度的四个区域
下图是某 RISC 微处理器在 Virtex-II 上的实测数据(关键路径被物理约束在紧凑区域内),横轴是约束频率、纵轴是实际达到的频率:
图 14.1 实际速度与约束速度的关系曲线
曲线分为四个区域:
- 欠约束(Underconstrained):底部平坦区(约束 < 95MHz)。逻辑的紧凑实现就能跑 95MHz 左右,不需要什么时序优化。
- 优化区(Optimization region):95~135MHz 的线性区。在这个范围内,提高约束可以换来综合器相应地生成更快(也更大)的逻辑结构。
- 峰值(Peak):逻辑结构优化 + 器件空间能支持的最高约束。
- 过约束(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 机的输出编码:
图 14.14 Moore 机的输出编码:跳变时一位清零、另一位置位
图 14.15 潜在冒险:两位变化不同步,输出出现瞬态毛刺
格雷码的构造方法(镜像追加法,mirror-append):
- 竖排写下 “0” 和 “1”;
- 从底部开始镜像复制一份;
- 镜像中复制的上半部分追加 “0”;
- 镜像中新生成的下半部分追加 “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 后:
图 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 类时序参数,或导入核生成工具的时序模型。另一个隐蔽坑:再同步(防亚稳态)两级寄存器忘了加”禁止寄存器平衡”约束,综合器把后续逻辑推进两级同步器之间,亚稳态防护形同虚设。
通关标准:
学完本篇你应该能做到:
- 解释为什么”选速度优化”在资源利用率接近 100% 时可能反而更慢,并把目标频率控制在最终速度 +20% 以内;
- 说明资源共享如何合并互斥的相似运算,以及它可能给关键路径增加逻辑级数;
- 区分流水线/重定时/寄存器平衡(移动触发器、不加不减)及其对延迟、吞吐率的影响;
- 说出复位类型不一致会阻止寄存器平衡,并主动用约束保护再同步寄存器;
- 为不同场景选 FSM 编码:FPGA 首选独热、异步输出用格雷码(会用镜像追加法构造)、高可靠设计开 safe mode;
- 给黑盒补齐 setup/hold 时序模型,理解前向标注相比后向标注减少迭代的原因。
自测:为什么资源利用率接近 100% 时,面积优化反而可能更快?
速度优化生成大规模并行逻辑,器件快满时布局工具只能把逻辑塞进次优位置,长布线带来的延时超过并行结构省下的门延时。紧凑的逻辑(面积优化)布线更从容,实际关键路径更短。根因是综合阶段用统计线负载模型估算布线,真实布线延时只有布局后才知道。
自测:寄存器平衡会不会增加设计的延迟(latency)或降低吞吐率?
不会。寄存器平衡只是移动触发器的位置来均衡两级寄存器之间的逻辑量,寄存器总数基本不变(可能微增微减),输入到输出的延迟和吞吐率都不受影响。代价是综合运行时间延长。所以聪明的工具只对关键路径做,非关键路径不做。
自测:为什么相邻触发器的复位类型必须一致才能寄存器平衡?
平衡需要把两个触发器合并/移动到逻辑的另一侧,这要求它们在复位时的行为完全可合并。如果一个同步复位一个异步复位,或一个复位一个置位,合并后的行为在复位期间无法同时满足,工具只能放弃平衡(或做”反相 workaround”,但引入的额外延时可能抵消收益)。
自测:用镜像追加法从 2 位格雷码构造 3 位格雷码的步骤是什么?
已有竖排的 00、01、11、10(2 位格雷码)。第一步:从底部镜像复制得 10、11、01、00 接在下方;第二步:上半部分(原有的 4 行)每行末尾追加 0;第三步:下半部分(新镜像的 4 行)每行末尾追加 1。得到 000、010、110、100、101、111、011、001——任意相邻码字只有一位不同。
自测:safe mode 适用于什么场景?一个 4 状态编码但只用 3 个状态的状态机,普通实现和 safe mode 实现的差异是什么?
safe mode 适用于高可靠电路(航空/军用/航天),防辐射粒子翻转触发器后状态机进入不可恢复的状态。普通实现(图 14.17)是个移位寄存器,非法状态会一直循环传播直到复位;safe mode 实现(图 14.18)加了检测逻辑,一旦发现非法状态立即把状态寄存器拉回复位值,快速恢复。
自测:一个不带时序模型的黑盒导致综合报告 248MHz,补上 din 的 4ns setup 后变成 125MHz,请解释估算逻辑。
不带时序模型时,综合器把穿过黑盒路径的黑盒内部延时按 0 处理,关键路径只剩外部逻辑延时,算出虚高的 248MHz。补上 syn_tsu/syn_tco 后,综合器把 4ns(数据)/3ns(控制)的黑盒接口时序计入路径,同一关键路径的总延时增大近一倍,估算修正为 125MHz。结论:黑盒必须携带核生成工具提供的 I/O 时序模型,否则时序估算不可信。