Veloris.
返回索引
深入原理 2026-02-19

从 Verilog 到硬件:你的代码如何映射到 FPGA 底层资源

8 分钟
2.5k words

从 Verilog 到硬件:你的代码如何映射到 FPGA 底层资源

💡 前三篇文章,我们从芯片架构层次到 CLB 内部结构,再到 SRL 和触发器,已经把 FPGA 的底层资源摸了个遍。但有一个关键问题还没回答:

你写的 assign y = a & b 到底变成了什么?always @(posedge clk) 又映射到了哪里?

这篇文章会用 6 个典型的 Verilog 代码片段,带你在 Vivado 中亲眼看到代码从 RTL 到网表、再到物理资源的完整映射过程。看完之后,你写每一行代码时,脑海中都会浮现出它在芯片里的”样子”。


目录


1. 代码到硬件的三步旅程

你的 Verilog 代码从文本变成芯片上的物理电路,要经过三个阶段:

阶段Vivado 操作输入输出你能看到什么
RTL 分析RTL AnalysisVerilog 源码RTL 原理图逻辑门、MUX、加法器等抽象符号
综合Run SynthesisRTL 原理图网表(Netlist)LUT、FF、CARRY4 等 FPGA 原语
布局布线Run Implementation网表物理映射具体的 BEL 位置(如 SLICE_X0Y0/A6LUT

RTL 分析展示的是”你想做什么”,综合展示的是”工具决定用什么资源”,布局布线展示的是”资源放在芯片的哪个位置”。

接下来我们用 6 个例子,逐一走完这三步。


2. 组合逻辑 → LUT

代码

assign out = a & b & c & d & e & f;

RTL 分析结果

RTL 视图会显示 5 个与门级联——这是逻辑层面的理解。

综合结果

综合后变成一个 LUT6。查看网表可以看到 INIT 值为 64'h8000000000000000——64 位中只有最高位为 1,对应 6 个输入全为 1 时输出 1。

布局布线结果

这个 LUT6 被放置到某个 Slice 中的一个具体 LUT BEL 上,比如 SLICE_X0Y12/A6LUT

关键认知

任何 6 输入以内的组合逻辑,无论多复杂,都只占用 1 个 LUT6,延迟完全相同。

这就是 LUT 的核心优势——它不区分逻辑函数的类型,只关心输入数量。


3. 多路选择器 → LUT + 专用 MUX

4:1 MUX

always @(*) begin
    case ({sel1, sel0})
        2'b00: out = d0;
        2'b01: out = d1;
        2'b10: out = d2;
        2'b11: out = d3;
    endcase
end

综合结果:1 个 LUT6(4 数据 + 2 选择 = 6 输入,刚好)。

8:1 MUX

always @(*) begin
    case ({sel2, sel1, sel0})
        3'b000: out = d0;
        // ... 省略中间
        3'b111: out = d7;
    endcase
end

综合结果:2 个 LUT6 + 1 个 F7MUX。

8 个数据 + 3 个选择 = 11 个输入,远超单个 LUT6 的能力。综合工具把它拆成两个 4:1 MUX(各用 1 个 LUT6),再用 F7MUX 做最终选择。

16:1 MUX

综合结果:4 个 LUT6 + 2 个 F7MUX + 1 个 F8MUX,全部在一个 Slice 内完成。

关键认知

MUX 的规模决定了资源消耗:4:1 → 1 LUT,8:1 → 2 LUT + F7MUX,16:1 → 4 LUT + F7/F8MUX。超过 16:1 就需要跨 Slice,时序会变差。


4. 算术运算 → Carry Chain

代码

assign sum = a[7:0] + b[7:0];

综合结果

综合后你会看到 CARRY4 原语出现在网表中。8 位加法器需要 2 个 CARRY4(每个处理 4 位),分布在同一 CLB 的 2 个 Slice 中。

布局布线结果

两个 CARRY4 在物理上垂直相邻——低 4 位的 CARRY4 在下方 Slice,高 4 位在上方 Slice,进位信号通过专用通路直接传递。

关键认知

a + ba - b 时,综合工具会自动使用 Carry Chain。你不需要手动例化,但需要知道:位宽越大,Carry Chain 越长,延迟越大。

💬 你可能会问:乘法运算也用 Carry Chain 吗?

小位宽的乘法可能用 LUT + Carry Chain 实现,但综合工具通常会优先使用 DSP Slice(如 DSP48E1)来实现乘法,因为 DSP 的硬核乘法器性能远优于 LUT 搭建的乘法器。你可以在综合报告中查看乘法被映射到了哪种资源。


5. 时序逻辑 → FF

同步复位的 D 触发器

always @(posedge clk) begin
    if (rst)
        q <= 8'h0;
    else
        q <= d;
end

综合结果

8 个 FDRE(同步复位 D 触发器)。

布局布线结果

这 8 个 FF 会被分配到 Slice 中的 FF BEL 上。有趣的是,Vivado 可能不会把 8 个 FF 全放在一个 Slice 里——布局工具会综合考虑布线质量和资源平衡。

异步复位的变体

always @(posedge clk) 改为 always @(posedge clk or posedge rst),综合结果会变成 FDCE(异步复位)。

关键认知

always @(posedge clk) 块中的赋值 → FF。复位方式决定 FF 类型:同步复位 → FDRE/FDSE,异步复位 → FDCE/FDPE。


6. 移位寄存器 → SRL(或 FF 链)

正确写法(推断为 SRL)

reg [31:0] shift_reg;

always @(posedge clk) begin
    if (ce)
        shift_reg <= {shift_reg[30:0], din};
end

assign dout = shift_reg[9];

综合结果:1 个 LUT(SRL32),资源极其节省。

错误写法(退化为 FF 链)

reg [31:0] shift_reg;

always @(posedge clk) begin
    if (rst)                    // ← 加了复位
        shift_reg <= 32'h0;
    else if (ce)
        shift_reg <= {shift_reg[30:0], din};
end

综合结果:32 个 FF + 若干 LUT,资源消耗暴增。

关键认知

移位寄存器能否被推断为 SRL,取决于代码风格。核心规则:不加复位信号。SRL 原语没有复位端口,加了复位就只能用 FF 实现。


7. 存储器 → 分布式 RAM 或 Block RAM

小容量存储

reg [7:0] mem [0:31];  // 32×8 的存储器

always @(posedge clk) begin
    if (we) mem[addr] <= din;
end
assign dout = mem[addr];  // 异步读

综合结果:分布式 RAM(使用 SliceM 中的 LUT)。异步读是分布式 RAM 的典型特征。

大容量存储

reg [7:0] mem [0:255];  // 256×8 的存储器

always @(posedge clk) begin
    if (we) mem[addr] <= din;
    dout <= mem[addr];        // 同步读
end

综合结果:Block RAM。同步读写是 Block RAM 推断的关键条件。

关键认知

异步读 → 分布式 RAM(LUT)。同步读写 → Block RAM。 实践中,分布式 RAM 适合 256 bit 以下的小容量存储(如 32×8),Block RAM 适合 1 Kbit 以上的大容量存储。中间地带的容量可用 (* ram_style = "block" *)(* ram_style = "distributed" *) 属性强制指定。

💡 工程师手记:我曾经声明了一个 reg [7:0] big_array [0:65535] 的大数组,结果综合时 LUT 用了 90% 以上,时序全崩了。原因是代码中用了异步读,综合工具把它推断成了分布式 RAM,用 LUT 来”硬堆”。改成同步读写后,工具正确推断为 Block RAM,LUT 利用率直接降到了个位数。


8. 综合报告:你的设计体检单

每次综合后,Vivado 会生成一份详细的综合报告。以下是你应该重点关注的几个部分:

💡 工程师手记:我第一次打开 Vivado 综合报告时,看到密密麻麻的表格和数字,完全不知道该看哪里。后来我总结出了一个“三步法”:先看资源利用率(是否超标),再看时序摘要(WNS 是否为正),最后看推断报告(资源是否按预期映射)。这三步能帮你在 30 秒内判断设计的健康状态。

8.1 资源利用率(Utilization)

+----------------------------+------+-------+
|          Resource          | Used | Avail |
+----------------------------+------+-------+
| LUT as Logic               | 1200 | 20800 |
| LUT as Memory (SRL/DRAM)   |   48 |  9600 |
| Register (FF)              | 2400 | 41600 |
| CARRY4                     |   64 |  8150 |
| Block RAM                  |    4 |    50 |
| DSP48E1                    |    2 |    90 |
+----------------------------+------+-------+

关注点:

  • LUT 利用率超过 70% 要警惕,超过 80% 布线会困难
  • LUT as Memory 显示了 SRL 和分布式 RAM 的使用量
  • FF 数量如果远超预期,可能有不必要的寄存器复制

8.2 时序摘要(Timing Summary)

关注 WNS(Worst Negative Slack)

  • WNS > 0:时序满足
  • WNS < 0:时序违例,需要优化

8.3 控制集报告(Control Sets)

如果控制集数量过多,报告会给出警告。检查是否有不必要的独立复位或使能信号。

8.4 推断报告(Inference)

综合工具会报告它推断出了哪些特殊资源:

  • Inferred SRL → 移位寄存器被正确推断
  • Inferred BRAM → 存储器被推断为 Block RAM
  • Inferred DSP → 乘法被推断为 DSP Slice

如果你期望的推断没有出现,检查代码风格是否符合推断条件。


9. 总结

Verilog 代码模式映射到的 FPGA 资源关键条件
assign y = f(a,b,c...)LUT6 输入以内 = 1 个 LUT
case/if-else 选择逻辑LUT + 专用 MUX>4:1 MUX 会用到 F7/F8MUX
a + ba - bCarry Chain自动使用,位宽影响延迟
always @(posedge clk)FF复位方式决定 FF 类型
移位寄存器(无复位)SRL不能带复位信号
存储器(同步读写)Block RAM大容量 + 同步读
存储器(异步读)分布式 RAM小容量 + 异步读

写代码时心中有硬件,看报告时手中有代码——这就是 FPGA 工程师和纯软件程序员的核心区别。

理解了代码到硬件的映射关系,你就能:

  • 预判设计的资源消耗
  • 写出对综合工具友好的代码
  • 快速定位时序问题的根因
  • 在 Vivado 的 Device View 中”读懂”你的设计

常见问题

Q1:综合工具的推断总是最优的吗?

不一定。综合工具在大多数情况下做得很好,但在某些边界条件下可能不是最优。比如小容量存储器,工具可能在分布式 RAM 和 Block RAM 之间做出非最优选择。你可以用综合属性(如 (* ram_style = "block" *))来引导工具。

Q2:同一段代码在不同的综合策略下会映射到不同的资源吗?

会。Vivado 提供了多种综合策略(如 Flow_AreaOptimized、Flow_PerfOptimized 等),不同策略下工具的优化方向不同,可能导致资源映射差异。但核心的推断规则(如 SRL 不能带复位)是不变的。

Q3:如何强制某段逻辑使用特定的资源?

三种方式:① 使用综合属性(如 (* use_dsp = "yes" *));② 直接例化原语(如 CARRY4SRLC32E);③ 使用 Vivado IP 核(如 Block Memory Generator)。推荐优先使用属性,其次 IP 核,最后才是原语例化。

Q4:RTL 分析和综合的结果为什么差别这么大?

RTL 分析只做语法解析和逻辑推断,展示的是“你描述了什么逻辑”。综合则要把这些逻辑映射到目标 FPGA 的具体资源上,还会做大量优化(如常量传播、逻辑合并、资源共享等)。所以综合结果通常比 RTL 分析结果更精简。

Q5:时序报告中的“关键路径”怎么看?

在 Vivado 的时序报告中,点击 WNS 对应的路径,可以看到完整的关键路径信息:起点 FF、经过的 LUT 级数、布线延迟、终点 FF。重点关注两个数字:Logic Delay(逻辑延迟,取决于 LUT 级数)和 Route Delay(布线延迟,取决于物理距离)。如果 Logic Delay 占比大,考虑插入流水线;如果 Route Delay 占比大,考虑用 Pblock 约束布局。


参考资料


系列导航:本文是「FPGA 内部资源深度解析」系列第 4 篇(完结)。

如果这个系列对你有帮助,欢迎点赞、收藏,也欢迎在评论区分享你在代码到硬件映射方面的经验和踩坑故事。

End of file.