FPGA基础回顾:从结构到流程,一次讲透

说实话,每次带新人做项目,我都要先问一句:「你搞清楚FPGA里面到底长什么样了吗?」很多人上来就写代码,结果综合出来资源不够用,或者时序跑不过去,回头才发现——哦,原来LUT和FF是干这个用的。

这一章,咱们就把FPGA的底裤扒开看看。不搞那些虚的,全是实战中天天要打交道的玩意儿。

FPGA内部结构:你手里的积木盒

FPGA说白了就是一大盒可编程的积木。你想想看,它不像CPU那样指令一条条跑,也不像ASIC那样焊死了功能。它的牛逼之处在于——你可以把电路「画」进去,然后让它真的变成硬件。

那这盒积木里到底装了啥?我按重要程度排个序:

1. LUT(查找表)—— 最基础的逻辑单元

LUT的全称是Look-Up Table,翻译过来就是「查表」。它本质上是一个小型的RAM,输入几个信号,输出一个结果。比如一个4输入的LUT,可以实现任意4输入的组合逻辑。

我在项目中遇到过一个坑:有人用LUT实现了16位加法器,结果资源爆了。为什么?因为加法器用LUT做太浪费了,应该用DSP或者进位链。嗯,这里要注意——LUT适合做随机逻辑,不适合做算术运算。

核心要点: 一个LUT ≈ 一个小型真值表。输入越多,需要的LUT越多。

2. FF(触发器)—— 记住状态的家伙

FF就是Flip-Flop,触发器。它的作用很简单:在时钟沿到来的时候,把输入的值「锁住」并输出。没有FF,你的电路就没法做流水线,没法做状态机。

我个人的习惯是:写代码时先想清楚哪些信号需要寄存器打一拍。比如跨时钟域的信号,必须用两级FF同步,否则亚稳态会教你做人。

小技巧: 在Verilog中,always @(posedge clk) 里赋值的变量,综合出来就是FF。组合逻辑用 assign 或 always @(*)。

3. BRAM(块RAM)—— 存数据的仓库

BRAM是FPGA内部的专用存储资源。它不像LUT搭出来的分布式RAM那么零碎,而是一整块一整块的。常见的BRAM大小是18Kb或36Kb,可以配置成单口、双口、真双口等模式。

我曾经在做一个图像处理项目时,需要缓存一行1920像素的数据。如果用FF搭,资源直接爆炸。换成BRAM,轻轻松松搞定。所以记住:大块数据存储,优先用BRAM。

存储方式 适用场景 资源消耗
FF(寄存器) 少量数据、频繁读写
分布式RAM(LUT) 小容量、深度浅 中等
BRAM 大容量、块状存储

4. DSP(数字信号处理单元)—— 算数的利器

DSP slice是FPGA里专门做乘加运算的硬核。一个DSP可以完成一次乘法+一次加法,而且一个时钟周期就能出结果。如果你用LUT搭乘法器,不仅慢,还费资源。

我建议:只要涉及乘法、乘加、乘累加,直接例化DSP。别自己手写乘法器,除非你想体验时序收敛的痛苦。

避坑指南: 不同FPGA系列的DSP结构略有不同。比如Xilinx 7系列的DSP48E1支持25×18位乘法,而UltraScale系列支持27×18位。用之前一定要查手册。

Verilog基础语法:够用就行

Verilog这东西,说难不难,说简单也不简单。我见过有人写了三年Verilog,还是分不清阻塞赋值和非阻塞赋值的区别。咱们不搞学院派那套,直接说实战中怎么用。

模块化设计

每个模块就是一个功能块。比如一个计数器模块,一个FIFO模块,一个状态机模块。模块之间通过端口连接。

module counter #(
    parameter WIDTH = 8
)(
    input  clk,
    input  rst_n,
    input  en,
    output reg [WIDTH-1:0] cnt
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt <= 0;
        else if (en)
            cnt <= cnt + 1;
    end
endmodule

你看,这个模块有参数、有输入输出、有时序逻辑。写模块时我习惯把参数放在最前面,这样别人调用时一目了然。

阻塞 vs 非阻塞

一句话总结:组合逻辑用阻塞赋值(=),时序逻辑用非阻塞赋值(<=)。别问为什么,这是硬件描述语言的铁律。我曾经在面试时问过这个问题,十个人里有六个答不上来。

记住: always @(posedge clk) 里永远用 <=,always @(*) 里永远用 =。

状态机写法

状态机是FPGA设计的灵魂。我推荐三段式写法:第一段描述状态转移,第二段描述次态逻辑,第三段描述输出。这样代码清晰,综合出来也漂亮。

// 三段式状态机示例
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
    case (state)
        IDLE: if (start) next_state = WORK;
              else next_state = IDLE;
        WORK: if (done) next_state = DONE;
              else next_state = WORK;
        DONE: next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

// 第三段:输出逻辑
always @(*) begin
    case (state)
        IDLE: out = 0;
        WORK: out = 1;
        DONE: out = 0;
    endcase
end

开发流程:从代码到比特流

写完了代码,怎么让它跑在FPGA上?这中间有三步:综合、实现、下载。每一步都有坑,我一个个说。

1. 综合(Synthesis)

综合就是把Verilog代码翻译成网表——也就是LUT、FF、BRAM这些基本单元的连接关系。说白了,就是把你的「想法」变成「电路蓝图」。

我遇到过一个情况:代码写得很漂亮,但综合出来资源爆了。后来发现是某个循环被综合成了巨大的查找表。所以综合完后一定要看报告,看看资源用了多少,有没有警告。

建议: 综合时打开retiming选项,可以让工具自动优化寄存器位置,有时候能救回时序。

2. 实现(Implementation)

实现又分三步:翻译(Translate)、映射(Map)、布局布线(Place & Route)。

  • 翻译: 把网表和约束文件合并成一个整体
  • 映射: 把逻辑单元映射到具体的LUT、FF上
  • 布局布线: 决定每个单元放在芯片的哪个位置,以及怎么连线

这一步最耗时间,也最容易出问题。时序不满足?多半是布局布线没做好。我个人的经验是:先跑一次看看资源分布,如果某个区域特别密集,手动加个区域约束把它分散开。

3. 下载(Download)

最后一步,把生成的比特流文件(.bit)烧录到FPGA里。下载方式有两种:JTAG和SPI Flash。JTAG用于调试,掉电就丢;SPI Flash用于固化,上电自动加载。

避坑: 下载前一定要检查FPGA的供电电压和配置模式。我曾经因为跳线帽没插对,折腾了整整一个下午。

本章知识体系

下面这张图,是我自己画的FPGA基础回顾的知识结构。你看一眼,心里就有数了。

FPGA基础回顾知识体系 FPGA内部结构 LUT - 查找表(组合逻辑) FF - 触发器(时序逻辑) BRAM - 块RAM(数据存储) DSP - 数字信号处理单元 Verilog基础语法 模块化设计(module/endmodule) 阻塞赋值 vs 非阻塞赋值 三段式状态机 参数化设计(parameter) 开发流程 综合(Synthesis) 实现(Implementation) 布局布线(P&R) 下载(Download/Bitstream) 核心思想:理解硬件结构 → 写出高效代码 → 掌握开发流程 三者缺一不可,否则项目必踩坑 💡 个人经验总结: • 写代码前先想清楚用哪种资源(LUT/FF/BRAM/DSP) • 综合报告一定要看,别等实现跑完才发现资源不够

好了,这一章的内容就到这儿。FPGA基础回顾这块,说白了就是三件事:搞清楚芯片里有什么,学会怎么描述电路,走通从代码到硬件的流程。这三件事搞定了,后面的项目实战你就能游刃有余。

一句话总结: LUT做逻辑,FF做寄存,BRAM存数据,DSP做运算。Verilog写清楚,综合实现别偷懒。记住这些,FPGA入门就算稳了。

专注资料整理