3、FPGA配置与比特流基础

各位好,欢迎来到第三章。这一章我们聊聊FPGA的配置和比特流。说实话,这是逆向工程里最核心、也最绕不开的一关。你想想看,FPGA本身是一张白纸,上电后它怎么知道自己该干什么?全靠配置过程把“灵魂”灌进去。这个灵魂,就是比特流。

我个人习惯把FPGA的配置过程比作“给芯片喂饭”。喂的方式不同,速度、安全性、适用场景都不一样。今天我们就从配置模式开始,一步步拆解。

3.1 FPGA配置模式详解

Lattice的FPGA支持多种配置模式。我挑三个最常用的讲:SPI、JTAG、Slave Serial。这三种模式,我在项目里都踩过坑,咱们一个一个说。

3.1.1 SPI配置模式

SPI模式,说白了就是用外部Flash存比特流,FPGA上电后自己去读。这是最常用的方式,没有之一。

为什么常用? 因为便宜、灵活。你买一颗SPI Flash,几毛钱到几块钱,容量从几Mb到几百Mb随便选。FPGA上电后自动加载,用户无感。

SPI配置的典型连接是这样的:

FPGA (Master)          SPI Flash (Slave)
-----------------      -----------------
CCLK (时钟输出)  ---->  CLK
SI (数据输出)    ---->  DI
SO (数据输入)    <----  DO
CS (片选)        ---->  CS#

嗯,这里要注意:Lattice的FPGA在SPI模式下是Master,时钟由FPGA自己产生。我曾经遇到过一个问题,Flash的时钟频率上限是33MHz,但FPGA默认配置时钟跑到了50MHz,结果死活加载不上。后来查了半天,才发现是时钟太快了,Flash跟不上。

避坑指南: 我曾经在SPI配置时忽略了Flash的时序要求,导致批量生产时部分芯片配置失败。后来我养成了习惯:选型时一定确认Flash的CLK上限,并在FPGA设计里把配置时钟分频到安全范围。

3.1.2 JTAG配置模式

JTAG模式,这是调试阶段的标配。你想想看,开发板上电后,用一根下载线就能把比特流烧进去,靠的就是JTAG。

JTAG有4根线:TCK(时钟)、TMS(状态机控制)、TDI(数据输入)、TDO(数据输出)。Lattice的FPGA还多了一根PROGRAMN(配置触发)和INITN(初始化状态指示)。

JTAG配置的好处是:

  • 实时性:可以随时下载、调试,不用擦写Flash
  • 可读性:通过JTAG可以回读FPGA内部状态,这对逆向分析太重要了
  • 安全性:JTAG端口通常可以被永久关闭(后面会讲Security Bit)

我个人习惯在开发阶段用JTAG,量产时切到SPI。但要注意:JTAG端口如果不加保护,别人拿个下载器就能把你的比特流读出来。嗯,这个后面细说。

3.1.3 Slave Serial配置模式

Slave Serial模式,FPGA是Slave,外部控制器(比如MCU、CPU)提供时钟和数据。这种模式在复杂系统里很常见,比如你的FPGA被一个ARM处理器控制,ARM从SD卡或网络加载比特流,然后通过Slave Serial喂给FPGA。

Slave Serial的时序很简单:

外部控制器提供:
- CCLK:配置时钟
- DATA:串行数据(每时钟1bit)
- PROGRAMN:拉低触发重配置
- INITN:FPGA初始化完成指示
- DONE:配置完成指示

我曾经在一个项目里用STM32通过Slave Serial配置Lattice FPGA,踩了一个坑:STM32的GPIO翻转速度不够快,导致配置时钟只有几MHz,整个配置过程花了快1秒。后来改用SPI+DMA才解决。

小技巧: Slave Serial模式下,配置时钟可以跑得比SPI模式高。Lattice的FPGA通常支持到66MHz甚至更高。但要注意信号完整性,线太长的话容易出错。

3.2 比特流的物理结构

比特流是什么?说白了就是一堆二进制数据,FPGA上电后加载到内部配置存储器里,决定每个LUT、每个MUX、每个布线开关的状态。

Lattice的比特流结构,我拆解给你看:

内容 长度(典型值)
Preamble 同步头,用于FPGA识别比特流起始 32 bits
Header 包含器件ID、配置选项、CRC校验等 64-128 bits
Configuration Data 真正的配置数据,按帧(Frame)组织 取决于器件容量
Postamble 结束标志,触发DONE信号 32 bits
CRC 循环冗余校验,验证数据完整性 16-32 bits

这里有个关键点:配置数据是按帧组织的。每一帧对应FPGA内部的一列逻辑资源。你想想看,一个Lattice ECP5有几百列,每列有几十个逻辑单元,每个单元又有几十个配置位。整个比特流就是这些配置位的线性排列。

我刚开始逆向比特流时,最头疼的就是找到帧的边界。后来发现,Lattice的比特流里有一个固定的同步字(0xFFFFFFFF或者0xAAAAAAAA),找到它就能定位帧起始。

核心要点: 比特流逆向的第一步,就是解析帧结构。你只要知道每帧有多少bit、每bit对应哪个配置点,就能把比特流还原成FPGA内部的电路网表。

3.3 配置时钟与初始化序列

FPGA上电后,不是立刻就能工作的。它有一个严格的初始化序列。我画了一张图,帮你理解这个过程:

FPGA上电初始化序列 上电 VCC稳定 初始化 INITN拉高 配置加载 CCLK有效 启动 DONE拉高 运行 用户模式 ~1ms ~10ms ~100ms ~1ms 持续 配置时钟CCLK在配置阶段有效,启动阶段后停止 DONE信号拉高表示配置成功,FPGA进入用户模式 (时间仅供参考,取决于器件和配置模式)

这个序列里,有几个关键信号你要记住:

  • PROGRAMN:拉低触发重配置。我经常用它来强制FPGA重新加载
  • INITN:初始化完成指示。如果它一直拉低,说明配置出错了
  • DONE:配置完成指示。它拉高后,FPGA才开始执行用户逻辑
  • CCLK:配置时钟。在SPI模式下由FPGA产生,Slave模式下由外部提供

配置时钟的频率很关键。太快了Flash跟不上,太慢了配置时间太长。我一般建议:SPI模式用25-33MHz,Slave Serial可以跑到50-66MHz。但具体要看器件手册,别超了上限。

3.4 安全位(Security Bit)机制

终于讲到安全位了。这是逆向工程里最让人头疼的东西,也是保护FPGA知识产权的最重要手段。

安全位是什么?说白了就是一个熔丝位(或者非易失存储位),烧进去之后,JTAG端口就不能再回读比特流了。有些Lattice器件还支持永久锁定,烧了就改不回来。

安全位的典型设置:

// 伪代码示意
if (Security_Bit == 1) {
    JTAG_Read_Disable = TRUE;
    // 不能通过JTAG回读配置数据
    // 不能通过JTAG擦除配置
    // 只能通过重新上电加载新比特流
}

我遇到过最狠的一次:客户拿来一块板子,FPGA里烧了核心算法,但安全位锁死了。我们想逆向分析,结果JTAG读出来全是0。后来用了物理攻击手段才搞定,但那又是另一个故事了。

重要提醒: 安全位不是万能的。它只能阻止通过JTAG回读,但阻止不了:
  • 侧信道攻击(通过功耗、电磁辐射分析)
  • 物理探针(直接读取配置存储器)
  • SPI Flash直接读取(如果比特流未加密)
所以,真正安全的方案是:安全位 + 比特流加密。

Lattice的比特流加密支持AES-128或AES-256。加密密钥存储在FPGA内部的非易失存储器里,外部无法读取。配置时,FPGA从SPI Flash读取加密的比特流,内部解密后再加载。

嗯,这里有个细节:加密后的比特流,在SPI Flash里是密文。你就算把Flash拆下来用编程器读,也是一堆乱码。没有密钥,谁也解不开。

个人经验: 我建议所有量产产品都开启安全位,并且使用比特流加密。虽然会增加一点点成本(需要存储密钥),但比起核心算法被盗,这点成本不值一提。我曾经见过一个客户,因为没开安全位,竞争对手买了几块板子就把比特流读出来了,损失惨重。

最后总结一下:配置模式决定了你怎么喂饭,比特流结构决定了饭的内容,初始化序列决定了吃饭的节奏,安全位决定了这顿饭会不会被别人偷吃。这四个知识点,是FPGA逆向工程的基石。你把这些搞透了,后面分析具体器件时就会轻松很多。


公众号:蓝海资料掘金营,微信deep3321