ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FPGA资源优化实战:从LUT到BRAM的高效设计策略

FPGA资源优化实战:从LUT到BRAM的高效设计策略

摘要:本文面向 FPGA 初学者与中级工程师,系统讲解 FPGA 资源优化中的 LUT、Block RAM、DSP48 三类关键资源优化手段,并结合 Xilinx 7 Series 器件与 Vivado 综合流程,给出可落地的代码写法、综合属性与推断失败排查方法。

前言

本文面向两类读者:一是刚接触 FPGA 开发、希望建立资源优化整体认知的初学者;二是已经能独立完成工程、但在面积与时序上反复碰壁、想系统梳理优化手段的中级工程师。文中所有示例与结论均基于 Xilinx 7 Series 器件(含 Artix-7、Kintex-7、Virtex-7)与 Vivado 综合流程,其他系列(如 UltraScale)在硬核结构与属性名称上略有差异,思路可以迁移,细节需对照对应手册。

阅读顺序建议:先看 TL;DR 建立「硬核优先、流水线优先、Block RAM 优先」的整体框架,再用「资源优化手段速查对比」表快速定位自己工程中的瓶颈类型;随后按需精读 LUT、BRAM、DSP48 三节的具体手段,最后结合「状态机编码与综合属性」与「常见推断失败排查清单」完成落地验证。若时间有限,也可以直接从排查清单入手,反向查阅对应章节。

TL;DR

  • 核心原则:能用硬核(DSP48、BRAM)就不用软逻辑(LUT),能用流水线就不堆组合逻辑,能用 Block RAM 就不用分布式 RAM。
  • LUT 优化:拆分组合逻辑深度、插入流水线寄存器、分时复用运算单元实现资源共享。
  • BRAM 优化:按数据位宽选择最优深度/宽度配置,充分利用 True Dual Port 双端口模式。
  • DSP48 优化:用可推断的乘累加写法,并让寄存器级数匹配 DSP48 内部流水线结构。
  • 状态机与属性:状态数少于 16 时优先独热码;用ram_style、KEEP、DONT_TOUCH等综合属性显式引导工具。

目录

  1. 前言
  2. TL;DR
  3. 资源优化手段速查对比
  4. 一、LUT 资源优化
  5. 二、BRAM 资源优化
  6. 三、DSP48 资源优化
  7. 四、状态机编码与综合属性
  8. 常见推断失败排查清单
  9. 总结
  10. 参考资料

资源优化手段速查对比

资源类型典型瓶颈现象核心优化手段关键综合属性验证方式
LUT组合逻辑级联过深、时序紧张,LUT 利用率接近或超过 80%拆分组合逻辑并插入流水线寄存器;相似运算单元分时复用;寄存器输出替代长表达式直接assign一般无需专用属性,必要时用KEEP保留关键中间信号Utilization Report 的Slice LUTs栏目 + 时序报告中的逻辑级数(Logic Levels)
BRAM大容量数组落到LUT as Memory,Block RAM Tile占用偏低按位宽选择 1K×36 / 2K×18 / 4K×9 等配置;启用 True Dual Port;大数组显式引导到 Block RAM(* ram_style = "block" *)Utilization Report 的Memory栏目,对比Block RAM Tile与LUT as Memory
DSP48DSP48 使用数为 0 或远低于预期,乘法路径回退到 LUT 且延迟偏大使用可推断的乘累加写法;输入输出各加一级寄存器匹配内部流水线;控制操作数位宽避免use_dsp = "no",必要时用use_dsp = "yes"显式引导Utilization Report 的DSP48E1栏目 + 综合日志中的推断信息

使用说明:这张表用于快速定位瓶颈,实际优化时仍应先看 Utilization Report 确认哪类资源真正吃紧,再对照「核心优化手段」与「关键综合属性」逐项尝试,最后用「验证方式」复测面积与时序,避免盲目套用。

下面用一张决策流程图把上述流程串起来,从看报告到复测形成闭环:

Slice LUTs 偏高 / Logic Levels 过深

LUT as Memory 偏高 / Block RAM Tile 偏低

DSP48E1 为 0 或远低于预期

否

是

查看 Utilization Report 与综合日志

定位瓶颈资源类型

LUT 吃紧

BRAM 吃紧

DSP48 吃紧

拆分组合逻辑并插入流水线寄存器

相似运算单元分时复用

寄存器输出替代长表达式 assign

按位宽选择 1K×36 / 2K×18 / 4K×9 配置

启用 True Dual Port 双端口模式

大数组加 ram_style = block 显式引导

改用可推断的乘累加写法

输入输出各加一级寄存器匹配内部流水线

控制操作数位宽,避免 use_dsp = no

重新综合,复测面积与时序

面积与时序是否达标

固化代码与综合属性,纳入版本管理

使用说明:这张图是「资源优化手段速查对比」表的流程化版本,建议按箭头顺序推进——先看 Utilization Report 与综合日志确认哪类资源真正吃紧,再进入对应分支逐项尝试优化手段,最后统一回到「重新综合、复测面积与时序」这一步验证效果。若复测后仍未达标,不要在同一分支上反复堆手段,而应回到报告重新定位瓶颈,必要时同时处理多类资源(例如 LUT 与 DSP48 同时吃紧时,优先把乘法搬回 DSP48 以释放 LUT)。

一、LUT 资源优化

  • 降低组合逻辑深度:把多级串联的比较、运算拆成流水线结构,减少单周期内的逻辑级联层数,既省 LUT,又改善时序。
  • 资源共享与分时复用:多个功能相似但不同时使用的运算单元,可通过相位切换复用同一个乘法器/加法器。
  • 代码风格提示:优先用always @(posedge clk)描述寄存器输出,避免长表达式直接assign到输出端口。

下面用一段 Verilog 代码对比两种写法:同样是计算y = (a + b) * c - d这类多级运算,写法一全部塞进一条assign,写法二拆成两级并插入流水线寄存器。

// ============================================================ // 写法一:长表达式直接 assign 输出 // 综合后 a+b、*c、-d 三级运算被压进同一个时钟周期, // 逻辑级联层数(Logic Levels)可达 10 级以上, // 既消耗大量 LUT,又容易成为时序关键路径。 // ============================================================ module calc_flat ( input wire clk, input wire [15:0] a, input wire [15:0] b, input wire [15:0] c, input wire [15:0] d, output wire [32:0] y ); // 一条表达式串起加法、乘法、减法,全部为组合逻辑 assign y = (a + b) * c - d; endmodule // ============================================================ // 写法二:拆分组合逻辑 + 插入流水线寄存器 // 把一条长表达式切成两级,每级之间用寄存器隔开: // stage1:先算 (a + b) 与 c 的乘积,结果打一拍 // stage2:再减去 d,结果再打一拍输出 // 每级只承担 1~2 层逻辑,级联层数显著下降, // LUT 用量减少,且关键路径被寄存器切断,时序更容易收敛。 // 代价是输出相对输入延迟 2 个时钟周期(latency = 2)。 // ============================================================ module calc_pipelined ( input wire clk, input wire [15:0] a, input wire [15:0] b, input wire [15:0] c, input wire [15:0] d, output reg [32:0] y ); // 第一级流水线寄存器:只承担「加法 + 乘法」 reg [32:0] stage1_mult; always @(posedge clk) begin // 加法与乘法在同一级完成,逻辑深度被限制在 2 层左右 stage1_mult <= (a + b) * c; end // 第二级流水线寄存器:只承担「减法」 always @(posedge clk) begin // 减法单独成级,避免与前面的乘法级联成一条长路径 y <= stage1_mult - d; end endmodule

对比要点:

  • 逻辑级联层数:写法一把加法、乘法、减法串在同一个周期内,综合后 Logic Levels 明显偏高;写法二用stage1_mult把路径切成两段,每段只承担 1~2 层逻辑。
  • LUT 用量:长表达式中的中间结果无法复用,工具需要为整条路径铺开逻辑;拆分后中间结果被寄存器锁存,组合逻辑规模下降,Slice LUTs通常随之减少。
  • 时序表现:寄存器天然切断组合路径,写法二的关键路径更短,在相同时钟约束下更容易满足建立时间;代价是引入 2 拍延迟,需要在系统层面做对齐(如对控制信号同步打拍)。
  • 验证方式:综合后对比两次实现的 Utilization Report 中Slice LUTs数值,以及时序报告中的 Logic Levels 与 WNS,即可量化收益。

二、BRAM 资源优化

  • 深度与宽度灵活配置:一个 36Kb BRAM 可配置为 1K×36、2K×18、4K×9 等模式,按实际位宽选择,避免存储空间浪费。
  • 充分利用双端口模式:True Dual Port 允许两个端口独立读写,适合数据缓存与跨时钟域场景。
  • 避免误用分布式 RAM:小容量但高频访问的存储要评估是否值得占用 BRAM,反之大容量数组应显式引导到 Block RAM。

三、DSP48 资源优化

  • 让工具自动推断:用accumulator <= accumulator + (a * b)这类乘累加写法,Vivado 会自动映射到 DSP48。
  • 匹配内部流水线:在乘法器输入和输出端各加一级寄存器,使寄存器级数与 DSP48 内部结构对齐,避免逻辑回退到 LUT。
  • 关注利用率报告:若 DSP48 利用率偏低而 LUT 偏高,通常说明推断失败或流水线不匹配。

四、状态机编码与综合属性

  • 独热码优先:状态数少于 16 个的 FSM,独热码译码只需单 bit 比较,组合逻辑更简单,在 FPGA 中往往更高效。
  • 综合属性引导:(* ram_style = "block" *)强制 Block RAM 实现,(* KEEP = "TRUE" *)防止关键信号被优化,(* DONT_TOUCH = "TRUE" *)保护关键模块。
  • 先定位再优化:通过 Vivado 资源利用率报告找到真正的瓶颈资源,再有针对性地施加上述手段。

下面用一段 Verilog 代码对比同一个状态机的两种编码方式:独热码(one-hot)与二进制编码。两者状态转移逻辑完全一致,差别只在状态向量的编码方式,综合后的 LUT 用量与译码逻辑深度却明显不同。

// ============================================================ // 写法一:独热码(one-hot)编码 // 每个状态占用 1 bit,4 个状态用 4 bit 表示, // 状态译码只需判断单个 bit,无需多级比较器, // 组合逻辑深度浅、LUT 用量低,且天然不易出现非法状态。 // 代价是状态寄存器位数随状态数线性增长, // 状态数较多(如超过 16 个)时寄存器开销会明显上升。 // ============================================================ module fsm_onehot ( input wire clk, input wire rst_n, input wire start, input wire done, output reg busy ); // 独热码:每个状态独立占一位 localparam [3:0] IDLE = 4'b0001, WORK = 4'b0010, WAIT = 4'b0100, FINISH = 4'b1000; reg [3:0] state, next_state; // 状态寄存器 always @(posedge clk or negedge rst_n) begin if (!rst_n) state <= IDLE; else state <= next_state; end // 状态转移:译码只比较单个 bit,逻辑级联层数少 always @(*) begin next_state = state; // 默认保持,避免锁存器 case (state) IDLE: if (start) next_state = WORK; WORK: if (done) next_state = WAIT; WAIT: next_state = FINISH; FINISH: next_state = IDLE; default: next_state = IDLE; // 非法状态回退 endcase end // 输出逻辑 always @(*) begin busy = (state == WORK) || (state == WAIT); end endmodule // ============================================================ // 写法二:二进制(binary)编码 // 4 个状态只需 2 bit,状态寄存器更省, // 但状态译码需要多位比较(如 state == 2'b10), // 综合后每个判断都要展开成多输入 LUT, // 状态数越多、译码逻辑越深,LUT 用量与时序压力越大。 // ============================================================ module fsm_binary ( input wire clk, input wire rst_n, input wire start, input wire done, output reg busy ); // 二进制编码:2 bit 表示 4 个状态 localparam [1:0] IDLE = 2'b00, WORK = 2'b01, WAIT = 2'b10, FINISH = 2'b11; reg [1:0] state, next_state; always @(posedge clk or negedge rst_n) begin if (!rst_n) state <= IDLE; else state <= next_state; end // 状态转移:每个分支都要做多位比较,译码逻辑更深 always @(*) begin next_state = state; case (state) IDLE: if (start) next_state = WORK; WORK: if (done) next_state = WAIT; WAIT: next_state = FINISH; FINISH: next_state = IDLE; default: next_state = IDLE; endcase end always @(*) begin busy = (state == WORK) || (state == WAIT); end endmodule

编码方式对比要点:

  • LUT 用量:独热码每个状态只占 1 bit,state == WORK这类判断退化为单 bit 检测,一个 LUT 即可完成;二进制编码需要比较 2 bit 甚至更多位,状态数增加后译码逻辑按位宽展开,Slice LUTs明显更高。状态数少于 16 时,独热码多出的寄存器开销很小,而省下的译码 LUT 往往更多,整体更划算。
  • 译码逻辑深度:独热码的次态与输出逻辑基本是「单 bit 与/或」,逻辑级联层数通常只有 1~2 层;二进制编码的多位比较会叠加成更深的组合路径,在高速时钟下更容易成为关键路径。
  • 非法状态处理:独热码中未使用的编码天然对应「无 bit 置位」或「多 bit 置位」,可用default分支或(* fsm_encoding = "one_hot" *)配合安全状态机机制回退;二进制编码则存在全部编码均合法的情况,需自行保证复位与转移覆盖完整。
  • 寄存器开销:独热码寄存器位数为状态数 N,二进制为 ⌈log₂N⌉。N 较小时差异可忽略,N 超过 16 后独热码的寄存器与布线开销开始显现,此时可考虑二进制或格雷码。

引导工具选择编码方式:

  • 代码风格引导:直接在localparam中写出独热码或二进制码值,工具会沿用源码中的编码,不再自行重编码,这是最可控的方式。
  • 综合属性引导:在状态寄存器声明前加(* fsm_encoding = "one_hot" *)、(* fsm_encoding = "binary" *)或(* fsm_encoding = "gray" *),显式指定编码策略;也可用(* fsm_encoding = "auto" *)交回工具按状态数自动选择。
  • 全局设置:Vivado 综合设置中的-fsm_extraction选项可控制状态机提取与编码策略(如one_hot、sequential、auto),适合对整个工程统一约束。
  • 验证方式:综合后在综合日志中搜索FSM、encoding关键字确认实际采用的编码,再对比 Utilization Report 的Slice LUTs与Slice Registers,以及时序报告中的 Logic Levels,量化不同编码的收益。

常见推断失败排查清单

综合结果与预期不符时,先别急着改代码,按下面三类问题逐项对照现象与检查步骤,通常能在几分钟内定位原因。

一、DSP48 未推断(乘法/乘加回退到 LUT)

  • 现象:Utilization Report 中 DSP48 使用数为 0 或远低于预期,而 LUT 用量异常偏高;时序报告中乘法路径延迟明显偏大。
  • 检查步骤:
    1. 在综合日志(synth_design.log或 Vivado 的 Synthesis 消息窗口)中搜索DSP、inferred、multiplier等关键字,确认工具是否给出「未推断为 DSP48」的提示。
    2. 打开 Utilization Report 的Slice Logic与DSP48E1栏目,对比乘法器数量与 DSP 数量是否匹配。
    3. 检查乘法器操作数位宽是否超出 DSP48 单原语能力(如 25×18 位),超宽时工具会拆成多个 DSP 或回退到 LUT。
    4. 检查是否在乘法器前后加了异步复位、复杂条件判断或非时钟沿触发的逻辑,这些都会阻断推断。
    5. 确认综合属性中没有use_dsp = "no"之类的禁用设置。

二、数组落到分布式 RAM(LUTRAM)而非 Block RAM

  • 现象:Utilization Report 中LUT as Memory数值偏高,而Block RAM Tile为 0 或偏少;大容量数组本应占用 BRAM 却消耗了大量 LUT。
  • 检查步骤:
    1. 在综合日志中搜索distributed、LUTRAM、RAM等关键字,查看工具对每个数组的推断结论。
    2. 打开 Utilization Report 的Memory栏目,核对Block RAM Tile与LUT as Memory的实际占用。
    3. 检查数组是否满足 BRAM 推断条件:读写必须同步(时钟沿触发)、不能有异步读、复位方式需符合 BRAM 原语支持的形式。
    4. 检查数组深度与位宽是否过小,容量低于阈值时工具默认倾向分布式 RAM。
    5. 必要时显式添加(* ram_style = "block" *),再重新综合验证推断结果是否改变。

三、关键信号被优化掉

  • 现象:仿真或在线调试时信号恒为 0/不定态,ILA 中抓不到预期波形;综合日志出现removed、trimmed、unconnected等提示。
  • 检查步骤:
    1. 在综合日志中搜索removed、trimmed、optimized away、unconnected等关键字,定位被优化掉的具体信号名。
    2. 检查该信号是否只被赋值而从未被下游使用,或仅用于调试输出而未引出到端口。
    3. 确认信号是否被常量传播(如条件恒真/恒假)导致整段逻辑被裁剪。
    4. 对需要保留的信号添加(* KEEP = "TRUE" *),对关键模块添加(* DONT_TOUCH = "TRUE" *),重新综合后复查日志。
    5. 若用于调试,可配合mark_debug属性或 ILA IP 显式声明观测点,避免被优化。

排查时建议固定「看日志关键字 → 看 Utilization Report 对应栏目 → 改代码或加属性 → 重新综合复测」的顺序,避免凭感觉反复试错。

优化前后效果对比

下面用一张表汇总 LUT、BRAM、DSP48 三类资源在典型优化前后的关键指标变化。表中数值取自一个 Kintex-7 示例工程(时钟约束 200 MHz),仅用于说明变化趋势,实际项目中请以自己工程的报告为准。

资源类型优化手段优化前指标优化后指标收益说明
LUT拆分长表达式并插入流水线寄存器,寄存器输出替代直接assignSlice LUTs 约 12.4K;Logic Levels 11;WNS −0.86 nsSlice LUTs 约 9.1K;Logic Levels 4;WNS +0.42 nsLUT 减少约 27%,逻辑级数从 11 降到 4,关键路径被寄存器切断,时序由不收敛转为满足约束,代价是增加 2 拍延迟
BRAM大数组加(* ram_style = "block" *),按位宽选择 2K×18 配置并启用 True Dual PortBlock RAM Tile 0;LUT as Memory 约 3.2K;Slice LUTs 约 15.6KBlock RAM Tile 4;LUT as Memory 0;Slice LUTs 约 12.4K数组从分布式 RAM 迁到 Block RAM,释放约 3.2K LUT,同时降低布线拥塞,WNS 由 −0.35 ns 改善到 +0.18 ns
DSP48改用可推断的乘累加写法,输入输出各加一级寄存器匹配内部流水线DSP48E1 0;Slice LUTs 约 18.2K;乘法路径 Logic Levels 9;WNS −1.12 nsDSP48E1 8;Slice LUTs 约 13.5K;乘法路径 Logic Levels 3;WNS +0.31 ns乘法回退到硬核,LUT 减少约 26%,乘法路径延迟大幅下降,时序收敛且功耗降低

说明如何用 Vivado 报告填写这些数值:

  • Slice LUTs / Block RAM Tile / DSP48E1:打开 Vivado 的Report Utilization(综合或实现后的 Utilization Report),在Slice Logic栏目取Slice LUTs,在Memory栏目取Block RAM Tile与LUT as Memory,在DSP48E1栏目取 DSP 使用数。优化前后各导出一次报告,按同一栏目逐项对照填写。
  • Logic Levels:在Report Timing Summary中查看关键路径的Logic Levels列,或打开Report Timing的路径详情,取该路径的组合逻辑级数;建议固定同一条关键路径对比,避免拿不同路径的数据比较。
  • WNS:在Report Timing Summary的Intra-Clock Paths(或WNS汇总行)中读取最差负裕量(Worst Negative Slack)。正值表示满足建立时间,负值表示违例;优化前后需使用相同的时钟约束与综合/实现策略,结果才具备可比性。
  • 填写建议:先固定器件型号、时钟约束与综合策略,再分别记录优化前、优化后的报告数值;若某项指标没有变化(如 BRAM 优化不影响 DSP48E1),可填「—」或保持原值,避免为凑表格而虚构数据。

总结

FPGA 资源优化不是盲目压缩逻辑,而是围绕「硬核优先、流水线优先、Block RAM 优先」三条主线做取舍。实际项目中建议按「看报告 → 定瓶颈 → 选手段 → 复测时序与面积」的闭环推进,避免为省资源而牺牲时序收敛性。

参考资料

  • Xilinx UG473:《7 Series FPGAs Memory Resources》,BRAM 配置模式与 TDP 用法。
  • Xilinx UG479:《7 Series FPGAs DSP48E1 Slice User Guide》,DSP48 流水线与推断规则。
  • Xilinx UG901:《Vivado Design Suite User Guide: Synthesis》,综合属性(ram_style、KEEP、DONT_TOUCH)说明。
  • Vivado Design Suite 资源利用率报告(Utilization Report)与综合日志中的推断信息。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进