FPGA 比特流(Bitstream)深度解析
什么是比特流?
简单理解:比特流是 FPGA 的配置数据,就像给一块空白的可编程电路板装配零件的指令清单。
形象比喻:
Verilog 代码 → 综合/布局布线 → 比特流
(高级描述) (逻辑门级) (具体施工指令)
比特流的本质
1. FPGA 内部结构
FPGA 由数百万个可配置单元组成:
┌─────────────────────────────────┐
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │LUT│──│FF │──│LUT│──│FF │ │
│ 查找表(LUT) 触发器(FF) │
│ └───┘ └───┘ └───┘ └───┘ │
│ │ │
│ 可编程互连矩阵(Switch) │
│ ┌───────────────────────────┐ │
│ │ │ │
│ └───────────────────────────┘ │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │LUT│──│MUX│──│LUT│──│RAM│ │
│ └───┘ └───┘ └───┘ └───┘ │
└─────────────────────────────────┘
2. 比特流就是配置这些单元的开关指令
每个比特控制:
比特 [0] = 1 → LUT0 配置为:输入 A&B 输出 1
比特 [1] = 0 → 开关 S1 断开
比特 [2] = 1 → 触发器 F3 连接到 LUT5 的输出
比特 [3] = 0 → MUX 选择通道 0
...
实际例子:
// 你的代码
assign out = a & b;
// 综合后变成 LUT2 #(.INIT(4'b1000))
// 这 4 位就会编码到比特流中
lut_inst (.I0(a), .I1(b), .O(out));
为什么要烧录?
FPGA vs 单片机对比
| 特性 | FPGA | 单片机 (MCU) |
|---|---|---|
| 硬件 | 空白的可重构逻辑单元 | 固定的 CPU+ 外设 |
| 程序 | 比特流=重新配置硬件 | bin 文件=CPU 执行的指令 |
| 本质 | 改变电路结构 | 改变程序流程 |
形象类比:
单片机(执行 bin 文件):
你雇了一个厨师 (CPU),给他一份菜谱 (bin 文件)
厨师按菜谱步骤做菜:
1. 切菜 (执行指令 1)
2. 炒菜 (执行指令 2)
3. 装盘 (执行指令 3)
FPGA(加载 bitstream):
你有一个魔法厨房 (FPGA),可以变形
给它施魔法 (bitstream) 后:
- 灶台变成 3 个 (并行处理)
- 刀具自动切菜 (硬件加速)
- 整个厨房变成专门做这道菜的流水线
为什么不烧录 bin 文件?
本质区别:
BIN 文件(单片机)
; bin 文件内容:CPU 指令序列
0x00: MOV R0, #0x01 ; 把 1 存到寄存器
0x04: ADD R0, R0, #1 ; 寄存器 +1
0x08: STR R0, [0x100] ; 存到内存
0x0C: B 0x00 ; 跳转到开头
CPU 按顺序执行这些指令
BIT 文件(FPGA)
; bit 文件内容:硬件配置数据
地址 0x000000: 10110101 → 配置 CLB[0][0] 的 LUT
地址 0x000008: 01001110 → 配置开关矩阵连接
地址 0x000010: 11000011 → 配置 IOB 引脚方向
地址 0x000018: 00101010 → 配置时钟资源
...
不是执行,而是物理配置电路
深入理解:你的代码如何变成比特流
完整流程:
┌─────────────────┐
│ led_blink.v │ 你的 Verilog 代码 (高级描述)
└────────┬────────┘
│ Synthesis(综合)
↓
┌─────────────────┐
│ 网表 (Netlist) │ 逻辑门级描述 (LUT, FF, MUX)
│ │ "需要哪些逻辑单元"
└────────┬────────┘
│ Place(布局)
↓
┌─────────────────┐
│ 物理位置 │ 每个单元放在芯片哪个位置
│ │ LUT@(5,10) -> CLB[5][10]
└────────┬────────┘
│ Route(布线)
↓
┌─────────────────┐
│ 互连配置 │ 单元之间如何连接
│ │ S[100]=ON -> 开关 100 打开
└────────┬────────┘
│ Bitgen(生成比特流)
↓
┌─────────────────┐
│ .bit 文件 │ 最终的二进制配置
│ │ 01101010... -> 每个比特对应一个配置点
└─────────────────┘
实际例子:
你的代码:
module example(
input a,
input b,
output c
);
assign c = a & b;
endmodule
综合后的网表(简化):
LUT2 #(.INIT(4'b1000)) // AND 门的真值表
lut_0 (
.I0(a), // 输入 0 接引脚 a
.I1(b), // 输入 1 接引脚 b
.O(c) // 输出接引脚 c
);
布局布线后:
LUT2 位置:CLB_X5Y10
引脚 a → SLICE_X5Y10.A6LUT.I0 (通过开关 S[1024])
引脚 b → SLICE_X5Y10.A6LUT.I1 (通过开关 S[1025])
输出 → IOB_X15Y30 (通过开关 S[2048])
比特流内容(16 进制,简化):
地址 0x012400: 0x08 → CLB_X5Y10 配置为 LUT 模式
地址 0x012408: 0x80 → LUT 初始值 = 4'b1000 (AND 功能)
地址 0x015600: 0x01 → 开关 S[1024] 闭合
地址 0x015608: 0x01 → 开关 S[1025] 闭合
地址 0x018800: 0x01 → 开关 S[2048] 闭合
...
FPGA 存储比特流的方式
1. SRAM 型 FPGA(如 Xilinx 7 系列)
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 外部 │ USB │ FPGA │JTAG│ 配置 Flash│
│ 电脑 │────→│ (SRAM) │←───│ (SPI) │
└──────────┘ └──────────┘ └──────────┘
↑ 断电丢失 ↑ 断电保存
上电流程:
- FPGA 从 Flash 读取 bit 流
- 加载到内部 SRAM 配置单元
- 配置完成,开始工作
- 断电后 SRAM 清空,需重新加载
2. Flash 型 FPGA(如 Microchip PolarFire)
┌──────────┐
│ FPGA │ 内部直接有 Flash
│ (Flash) │ 上电自动加载,断电不丢失
└──────────┘
BIT vs BIN vs MCS 文件对比
| 文件类型 | 用途 | 目标设备 |
|---|---|---|
| .bit | 通过 JTAG 直接下载到 FPGA | FPGA 的 SRAM |
| .bin | 原始二进制,用于特殊工具 | - |
| .mcs/.hex | 烧录到外部配置 Flash | SPI Flash 芯片 |
使用场景:
开发调试:
Vivado → Generate Bitstream → 得到 design.bit
→ Open Hardware Manager → Program Device
→ 直接下载到 FPGA(JTAG 方式)
特点:快速,但断电丢失
产品部署:
Vivado → Generate Memory Configuration File → 得到 design.mcs (包含 bit 流)
→ 烧录到板载 SPI Flash → FPGA 上电自动从 Flash 加载
特点:断电保存
查看比特流内容
比特流文件结构:
┌────────────────────────────────────┐
│ Header (文件头) │
│ - 同步字:0xAA995566 │
│ - 器件 ID: XC7A35T │
├────────────────────────────────────┤
│ Configuration Commands (配置命令) │
│ - Write CLB 配置 │
│ - Write 互连配置 │
│ - Write IOB 配置 │
├────────────────────────────────────┤
│ Configuration Data (配置数据) │
│ - 101010110101... (几百万比特) │
└────────────────────────────────────┘
实际查看(hex 编辑器):
00000000: ff ff ff ff ff ff ff ff aa 99 55 66 20 00 00 00
00000010: 30 03 e0 01 00 00 00 00 30 00 80 01 00 00 00 12 ...
常见疑问解答
Q1: 为什么比特流这么大?
XC7A35T: 约 1.7MB
XC7A100T: 约 4.0MB
原因:
- 每个 CLB 需要几百比特配置
- 芯片有几万个 CLB
- 互连矩阵也需要大量配置
Q2: 可以反向工程比特流吗?
bit 文件 → Verilog 代码?
答案:理论可行但极难
- Xilinx/Intel 有加密保护
- 即使破解,得到的是门级网表,不是原始代码
Q3: 每次都要重新综合吗?
改一行代码 → 必须重新生成 bit 流
原因:任何逻辑改变都会影响:
- 布局位置
- 布线路径
- 时序
所以需要完整走一遍流程
实际操作示例
Vivado 生成比特流流程:
1. Synthesis (综合) → 约 30 秒
2. Implementation (实现) → 约 2 分钟
- Opt Design
- Place Design
- Route Design
3. Generate Bitstream → 约 30 秒
→ 得到 design.bit (1.7MB)
下载到 FPGA:
# Vivado TCL 命令
open_hw_manager
connect_hw_server
open_hw_target
set_property PROGRAM.FILE {design.bit} [current_hw_device]
program_hw_devices
总结
| 概念 | 解释 |
|---|---|
| 比特流是什么 | FPGA 硬件配置的二进制数据 |
| 为什么烧录 | FPGA 是空白硬件,需要配置才能工作 |
| 为什么不是 bin | bin 是 CPU 指令,FPGA 不是执行指令而是重构硬件 |
| 本质区别 | MCU=软件改变行为,FPGA=硬件改变结构 |
记住:FPGA 不是运行程序,而是变成你设计的电路!
