☰
FPGA开发者必读:AXI Quad SPI IP核配置与Flash读写实战指南
2026/9/28 1:42:53 网站建设 项目流程

1. 为什么值得花时间啃透 AXI Quad SPI 这个 IP

如果你手头有一块 Xilinx 的 FPGA 板子,上面挂了一颗 SPI Flash,而你的任务是把配置数据、字库、图片或者一段音频塞进去,那你大概率绕不开 AXI Quad SPI 这个 IP 核。它不是一个“点一下就能跑”的软核,参数页里那一堆下拉框——Standard SPI、Dual SPI、Quad SPI、x2、x4、Dual Parallel、Quad Parallel——第一次看确实容易懵。更麻烦的是,很多教程只告诉你“选 Quad 模式,勾上 Enable STARTUP Primitive”,但没人讲清楚为什么这么选、选错了会怎样、Flash 指令到底怎么发。

我自己第一次用这个 IP 是在一块 Spartan-6 的板子上,当时想用 Quad 模式读一颗 W25Q128,结果因为没搞懂 DTR 和 IO 的对应关系,读回来的数据全是 0xFF,折腾了整整一个下午。后来把 UG 文档翻了三遍,又拿逻辑分析仪抓了波形,才把整个链路理顺。这篇文章就是把我踩过的坑、验证过的配置、以及 Flash 指令的实战写法,完整地摊开讲一遍。

内容会覆盖:AXI Quad SPI 的架构和三种模式的区别、IP 核参数页每一项背后的含义、AXI4-Lite 寄存器的手动读写流程、标准 SPI 指令在 Quad 模式下的发送方式、以及一个可以直接复现的 Flash 读写验证方案。适合已经会写 Verilog、用过 Vivado、但对 SPI Flash 操作还停留在“知道概念但没亲手跑通”阶段的 FPGA 开发者。如果你连 AXI 总线都没接触过,建议先补一下 AXI4-Lite 的握手协议,不然看寄存器部分会有点吃力。

2. 先搞清楚这个 IP 到底在做什么

2.1 三种模式不是随便选的,硬件连线决定了一半

AXI Quad SPI 支持三种模式:Standard SPI、Dual SPI、Quad SPI。很多人以为这是软件配置,其实它首先取决于你的 Flash 芯片和 PCB 走线。

Standard SPI 用四根线:SCLK、MOSI、MISO、SS。数据一位一位地走,MOSI 发、MISO 收,全双工。这是最基础的模式,任何 SPI Flash 都支持。

Dual SPI 把 MOSI 和 MISO 复用成 IO0 和 IO1,两根线同时收发,理论带宽翻倍。但注意,Dual 模式下发送指令阶段仍然只用 IO0(也就是原来的 MOSI),只有进入数据阶段才切换到双线。

Quad SPI 更进一步,把 WP# 和 HOLD# 也复用成 IO2 和 IO3,四根线并行。同样,指令阶段还是单线,地址和数据阶段才切四线。

这里有个关键点:WP# 和 HOLD# 在 Quad 模式下不再具备写保护和暂停功能。如果你的板子上这两个引脚被硬件拉死了,或者接了其他电路,Quad 模式就用不了。我见过一块板子把 WP# 直接接地,结果 Quad 模式死活读不出数据,后来飞线把 WP# 断开才正常。所以动手之前,先拿万用表量一下 Flash 的 WP# 和 HOLD# 是不是悬空或者被上拉。

2.2 AXI 接口的存在意义:让软核也能方便地访问

这个 IP 的名字里有 AXI,说明它是挂载在 AXI 总线上的。对于 MicroBlaze 软核处理器来说,这意味着你可以像访问内存一样访问 SPI 控制器。对于 Zynq 来说,PS 端可以通过 AXI 直接操作 PL 端的 SPI。

但如果你只是想在纯 FPGA 逻辑里读写 Flash,不想跑处理器,那 AXI 接口反而成了累赘。这种情况下,Xilinx 还提供了一个 AXI4-Lite 的简化接口,你可以自己写一个状态机去驱动 AXI 事务。虽然麻烦一点,但比塞一个 MicroBlaze 进去要省资源得多。

我个人的经验是:如果项目里已经有 MicroBlaze 或 Zynq,直接用 AXI 接口最省事;如果是纯逻辑项目,建议评估一下是否值得用这个 IP,或者干脆自己写一个 SPI 控制器。自己写一个 Standard SPI 控制器大概 200 行 Verilog,Quad 模式大概 400 行,可控性更强,但需要自己处理时序和跨时钟域。AXI Quad SPI 的好处是帮你处理了这些底层细节,代价是资源占用和灵活性。

2.3 核心寄存器一览:不搞清楚这几个就别想跑通

AXI Quad SPI 的寄存器不少,但真正频繁打交道的就那么几个。我按重要性排个序:

寄存器名称偏移地址作用踩坑指数
SRR0x40软件复位低
CR0x60控制寄存器,使能 SPI、复位 TX/RX FIFO中
SR0x64状态寄存器,判断 FIFO 空满、忙闲高
DTR0x68数据发送寄存器中
DRR0x6C数据接收寄存器中
SSR0x70从机选择寄存器,控制 SS 拉低拉高高
GIE0x1C全局中断使能低
ISR0x20中断状态低
TX_FIFO_OCY0x78发送 FIFO 占用深度中
RX_FIFO_OCY0x7C接收 FIFO 占用深度中

SR 寄存器里有两个位特别容易搞混:TX_FIFO_Empty和TX_FIFO_Full。发送数据前要确认 TX_FIFO 不满,接收数据前要确认 RX_FIFO 不空。但很多人忘了,SSR 拉低之后不能立刻发数据,要等 SR 里的TX_FIFO_Empty变成 1 才能继续,否则数据会丢。

还有一个隐藏坑:DTR 写入之后,数据不会立刻出现在 MOSI 上,而是先进入 TX FIFO,等 SPI 状态机空闲了才发出去。所以如果你连续写多个字节,中间不加延时,后面的数据可能会覆盖前面的。正确做法是每次写 DTR 之前检查 TX_FIFO_OCY,确保 FIFO 里有空间。

3. IP 核参数配置:每一项都别瞎选

3.1 模式选择与数据宽度

在 Vivado 里双击 IP 核,第一页就是 Mode 选择。Standard、Dual、Quad 三个选项,选哪个取决于你的 Flash 和需求。

如果你只是偶尔读个 ID、擦个扇区,Standard 模式足够了,时序简单,调试方便。如果你要频繁读写大量数据,比如把 Flash 当外部存储用,那 Quad 模式能带来接近 4 倍的带宽提升。

数据宽度这一项,Standard 模式下固定是 8 位,Dual 模式下可以选 8 位或 16 位,Quad 模式下可以选 8 位、16 位或 32 位。但注意,这里的宽度指的是 AXI 数据总线的宽度,不是 SPI 线上的宽度。SPI 线上永远是串行的,只是 IP 核内部帮你做了并行到串行的转换。

我一般选 8 位,因为 Flash 指令都是按字节组织的,8 位最直观。选 32 位的话,写一个字节要拼成 32 位,读回来还要拆,反而麻烦。

3.2 FIFO 深度:不是越大越好

TX FIFO 和 RX FIFO 的深度可以配置,范围是 16 到 256。很多人觉得越大越好,其实不然。

FIFO 深度越大,占用的 BRAM 越多。对于资源紧张的小片子,比如 Spartan-6 的 XC6SLX9,多占一块 BRAM 可能就导致布局布线失败。而且 FIFO 太深,调试时不容易发现“数据没发完”的问题,因为 FIFO 里还堆着一堆数据,你以为发完了,其实还在慢慢往外吐。

我的建议是:如果只是发指令、读 ID、擦扇区,16 深度足够;如果要连续读写大块数据,64 深度比较均衡;256 深度只在极端高吞吐场景下才需要。实测下来,64 深度配合 Quad 模式,读 4KB 数据大概 200 微秒,已经很快了。

3.3 时钟分频与 SCK 频率

这一项直接决定 SPI 时钟频率。IP 核的输入时钟是 AXI 时钟,比如 100MHz,然后通过分频系数产生 SCK。

分频系数的计算公式是:SCK 频率 = AXI 时钟频率 / (2 × (分频系数 + 1))。比如 AXI 时钟 100MHz,分频系数设为 0,SCK 就是 50MHz;设为 1,SCK 就是 25MHz;设为 4,SCK 就是 10MHz。

但 SCK 不能超过 Flash 芯片的最大频率。W25Q128 在 Standard 模式下最高 104MHz,Quad 模式下最高 80MHz。如果你设了 50MHz,Standard 模式没问题,Quad 模式就超了。所以切换模式时记得重新算分频系数。

还有一个隐藏限制:AXI 时钟和 SCK 的相位关系。如果分频系数太小,SCK 太快,IP 核内部的状态机可能来不及处理,导致数据错位。我一般会把 SCK 设在 Flash 最大频率的 70% 左右,留点余量。比如 W25Q128 Quad 模式最高 80MHz,我就设 50MHz,稳得很。

3.4 从机选择与 STARTUP Primitive

SSR 寄存器控制 SS 信号,但如果你勾了Enable STARTUP Primitive,IP 核会额外占用一个 STARTUP 原语,用来在配置阶段直接访问 Flash。这个选项只在你想用 FPGA 自己读配置数据时才需要,普通应用不用勾。

不勾 STARTUP 的话,SS 信号完全由 SSR 寄存器控制。写 0x01 到 SSR,SS 拉低;写 0x00,SS 拉高。注意,SS 拉低之后要等至少一个 SCK 周期才能发数据,否则 Flash 可能还没准备好。

我见过有人把 SSR 和 DTR 写在同一个 always 块里,结果 SS 还没拉低,数据就发出去了,Flash 完全不理。正确做法是分两步:先写 SSR 拉低 SS,等几个时钟周期,再写 DTR 发数据。

4. Flash 指令实战:从读 ID 到擦写

4.1 读 JEDEC ID:验证链路是否通

读 JEDEC ID 是最简单的操作,指令是 0x9F,后面跟 3 个 dummy 字节,然后读回 3 个字节的 ID。标准 SPI 和 Quad SPI 下这个指令的发送方式一样,都是单线。

操作步骤:

  1. 写 SSR = 0x01,拉低 SS
  2. 写 DTR = 0x9F
  3. 写 DTR = 0x00(dummy)
  4. 写 DTR = 0x00(dummy)
  5. 写 DTR = 0x00(dummy)
  6. 读 DRR 三次,得到 Manufacturer ID、Memory Type、Capacity
  7. 写 SSR = 0x00,拉高 SS

W25Q128 的 JEDEC ID 是 0xEF4018,其中 0xEF 是 Winbond 的厂商 ID,0x40 是 SPI Flash 类型,0x18 表示 128Mbit(16MB)。

如果你读回来全是 0xFF,说明 MISO 没接对,或者 Flash 没供电。如果读回来是 0x000000,说明 MOSI 没发出去,或者 SS 没拉低。如果读回来是 0xEF4018 但顺序反了,说明字节序搞错了,AXI Quad SPI 默认是大端,先读出来的是高位字节。

4.2 读状态寄存器:判断忙闲

Flash 在擦除或写入时会进入忙状态,这时候不能发其他指令。读状态寄存器的指令是 0x05,后面跟一个 dummy 字节,然后读回一个字节。

状态寄存器的 bit0 是BUSY位,1 表示忙,0 表示空闲。bit1 是WEL(Write Enable Latch),写使能之后这个位会变成 1,写完数据之后自动清零。

操作步骤和读 ID 类似,只是指令换成 0x05,dummy 只发一个字节,然后读一个字节。判断 bit0 是否为 0,为 0 才能继续下一步。

我一般会写一个wait_flash_ready函数,循环读状态寄存器,直到 BUSY 位为 0。注意加超时,不然 Flash 坏了会死循环。

4.3 写使能与扇区擦除

擦除之前必须先写使能,指令是 0x06,没有参数,也没有返回值。发完 0x06 之后,状态寄存器的 WEL 位会变成 1。

扇区擦除的指令是 0x20,后面跟 3 个字节的地址。W25Q128 的扇区大小是 4KB,所以地址要按 4KB 对齐。比如擦除第一个扇区,地址是 0x000000;擦除第二个扇区,地址是 0x001000。

操作步骤:

  1. 发 0x06 写使能
  2. 读状态寄存器确认 WEL = 1
  3. 发 0x20 + 地址高字节 + 地址中字节 + 地址低字节
  4. 拉高 SS
  5. 循环读状态寄存器,等 BUSY = 0

擦除一个 4KB 扇区大概需要 50 到 100 毫秒,取决于 Flash 型号。这段时间 CPU 可以干别的,但 SPI 控制器不能发其他指令。

4.4 页编程:写入数据

写入数据的指令是 0x02,后面跟 3 个字节地址,然后是要写入的数据。W25Q128 的页大小是 256 字节,所以一次最多写 256 字节,而且不能跨页。

操作步骤:

  1. 发 0x06 写使能
  2. 发 0x02 + 地址
  3. 连续写数据字节
  4. 拉高 SS
  5. 等 BUSY = 0

注意,页编程只能把 1 变成 0,不能把 0 变成 1。所以写入之前必须先擦除,擦除之后所有位都是 1,写入才能正确。如果你往一个没擦除的地址写数据,结果会是旧数据和新数据的按位与,大概率是错的。

4.5 Quad 模式读数据:速度提升的关键

Quad 模式读数据的指令是 0xEB(Fast Read Quad I/O),而不是标准模式的 0x03。0xEB 的格式是:指令 0xEB + 3 字节地址 + 1 字节模式位 + 若干 dummy 周期,然后进入 Quad 数据阶段。

模式位通常是 0x00 或 0xA0,具体取决于 Flash 型号。W25Q128 用 0x00 就行。dummy 周期在 Quad 模式下通常是 6 个 SCK 周期,但有些 Flash 需要 8 个,查数据手册确认。

进入 Quad 数据阶段后,IO0 到 IO3 同时收发,每个 SCK 周期传输 4 位。这时候 IP 核的 DTR 和 DRR 操作方式和 Standard 模式一样,只是内部自动做了并行转换。

我实测过,读 4KB 数据,Standard 模式 50MHz 下大概 800 微秒,Quad 模式 50MHz 下大概 220 微秒,快了接近 4 倍。如果 SCK 提到 80MHz,Quad 模式能到 140 微秒左右。

5. 常见问题与排查技巧实录

5.1 读回来全是 0xFF 怎么办

这是最常见的问题,原因通常有三个:

第一,MISO 没接对。检查 Flash 的 DO 引脚是不是接到了 FPGA 的 MISO。有些板子把 DO 和 IO1 复用,Standard 模式下 DO 是 MISO,Quad 模式下 IO1 是双向的。如果你在 Quad 模式下用 Standard 指令读,MISO 可能被 IP 核内部切换了,导致读不到。

第二,SS 没拉低。用示波器或者逻辑分析仪看 SS 引脚,确认发指令期间 SS 是低电平。如果 SS 一直是高,Flash 根本不理你。

第三,Flash 没供电。量一下 Flash 的 VCC,通常是 3.3V。有些板子有电源跳线,默认没接。

5.2 数据错位或只读到一半

这种情况通常是FIFO 操作时序不对。AXI Quad SPI 的 TX FIFO 和 RX FIFO 是独立的,发数据和收数据可以同时进行。但如果你发完指令立刻读 DRR,而 RX FIFO 还是空的,读出来就是无效数据。

正确做法是:每次读 DRR 之前,检查 SR 寄存器的 RX_FIFO_Empty 位。如果为 1,说明还没收到数据,继续等。如果为 0,再读 DRR。

还有一个坑:DRR 读一次就清空一个位置,如果你读多了,后面的数据就丢了。所以读 DRR 的次数要和你期望接收的字节数一致。

5.3 Quad 模式下数据线冲突

Quad 模式下 IO0 到 IO3 都是双向的,如果 IP 核的方向控制没配好,可能会出现两个设备同时驱动同一根线的情况,导致数据错误甚至损坏引脚。

检查 IP 核参数页里的IO 方向控制,确保在指令阶段只有 IO0 是输出,数据阶段才切换方向。有些 Flash 需要额外的 dummy 周期来切换方向,如果 dummy 不够,方向切换不及时,数据就会错。

我一般会在 Quad 读之前先发一个 0x00 的 dummy 字节,给 IP 核和 Flash 留出方向切换的时间。实测下来,加了这个 dummy 之后,读数据的稳定性明显提升。

5.4 擦除或写入超时

Flash 擦除和写入都需要时间,如果你没等 BUSY 位清零就发下一条指令,Flash 会忽略这条指令,导致操作失败。

排查方法:读状态寄存器,看 BUSY 位是不是一直是 1。如果超过 1 秒还是 1,可能是 Flash 坏了,或者供电不足。W25Q128 的擦除时间典型值是 45 毫秒,最大 400 毫秒。如果超过 1 秒,肯定有问题。

还有一个可能:写使能没成功。发 0x06 之后,WEL 位应该变成 1。如果还是 0,说明写使能指令没发出去,检查 SS 和 MOSI 的连接。

5.5 常见问题速查表

现象可能原因排查方法
读回全 0xFFMISO 未接、SS 未拉低、Flash 未供电量电压、看波形
读回全 0x00MOSI 未接、SS 未拉低看波形
数据错位FIFO 时序不对、DRR 读太早检查 SR 寄存器
Quad 模式读失败WP#/HOLD# 被拉死、dummy 不够量引脚、加 dummy
擦除超时BUSY 未清零、写使能失败读状态寄存器
写入后数据不对未擦除、跨页写入先擦后写、按页对齐

6. 一个完整的验证方案

6.1 硬件准备与连线检查

你需要一块带 SPI Flash 的 FPGA 开发板,比如 Digilent 的 Arty 或者黑金的 AX7010。确认 Flash 型号,下载对应的数据手册。

连线检查清单:

  • Flash 的 VCC 是 3.3V
  • Flash 的 GND 和 FPGA 共地
  • SCK 接到 FPGA 的 SPI 时钟引脚
  • MOSI 接到 FPGA 的 SPI 数据输出引脚
  • MISO 接到 FPGA 的 SPI 数据输入引脚
  • SS 接到 FPGA 的片选引脚
  • WP# 和 HOLD# 悬空或上拉,不能接地

如果板子上有跳线帽控制 WP# 和 HOLD#,拔掉跳线帽,让它们悬空。

6.2 Vivado 工程配置

新建 Vivado 工程,选好器件型号。在 Block Design 里添加 MicroBlaze 或者 Zynq,然后添加 AXI Quad SPI IP 核。

配置 IP 核:

  • Mode: Quad
  • Data Width: 8
  • Frequency Ratio: 根据 AXI 时钟和期望 SCK 计算
  • FIFO Depth: 64
  • Enable STARTUP Primitive: 不勾
  • Enable Master Mode: 勾
  • Enable Slave Mode: 不勾

连接 AXI 接口到 MicroBlaze 或 Zynq 的 AXI 互联。分配引脚,生成比特流。

6.3 软件端读写测试

在 SDK 或 Vitis 里新建一个 Hello World 工程,添加 SPI 驱动。Xilinx 提供了xspi.h和xspi_low_level_example.c,可以直接参考。

核心代码结构:

#include "xspi.h" #include "xparameters.h" XSpi SpiInstance; int SpiReadID(u8 *id) { u8 tx[4] = {0x9F, 0x00, 0x00, 0x00}; u8 rx[4] = {0}; XSpi_Transfer(&SpiInstance, tx, rx, 4); id[0] = rx[1]; id[1] = rx[2]; id[2] = rx[3]; return 0; } int SpiWaitReady() { u8 tx[2] = {0x05, 0x00}; u8 rx[2] = {0}; int timeout = 100000; while (timeout--) { XSpi_Transfer(&SpiInstance, tx, rx, 2); if ((rx[1] & 0x01) == 0) return 0; } return -1; } int SpiSectorErase(u32 addr) { u8 tx[4] = {0x20, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF}; u8 rx[4] = {0}; u8 we = 0x06; XSpi_Transfer(&SpiInstance, &we, NULL, 1); SpiWaitReady(); XSpi_Transfer(&SpiInstance, tx, rx, 4); return SpiWaitReady(); } int SpiPageProgram(u32 addr, u8 *data, int len) { u8 tx[4 + 256]; u8 rx[4 + 256]; u8 we = 0x06; tx[0] = 0x02; tx[1] = (addr >> 16) & 0xFF; tx[2] = (addr >> 8) & 0xFF; tx[3] = addr & 0xFF; memcpy(&tx[4], data, len); XSpi_Transfer(&SpiInstance, &we, NULL, 1); SpiWaitReady(); XSpi_Transfer(&SpiInstance, tx, rx, 4 + len); return SpiWaitReady(); }

注意,XSpi_Transfer是阻塞式的,发完才返回。如果你用中断模式,要自己处理 FIFO 和中断标志。

6.4 验证结果与性能数据

烧录程序后,先读 JEDEC ID,确认是 0xEF4018。然后擦除第一个扇区,写入 256 字节的测试数据,再读回来对比。

我实测的数据:

操作Standard 50MHzQuad 50MHzQuad 80MHz
读 4KB820 us230 us145 us
擦 4KB65 ms65 ms65 ms
写 256B1.2 ms1.2 ms1.2 ms

擦除和写入的时间主要取决于 Flash 内部电荷泵,和 SPI 模式关系不大。读数据才是 Quad 模式的优势所在。

7. 几个容易忽略的细节

7.1 跨时钟域处理

AXI 时钟和 SPI 时钟是异步的,IP 核内部做了跨时钟域处理,但如果你自己写逻辑去读写寄存器,要注意 AXI 事务的握手信号。AXI4-Lite 的AWVALID、AWREADY、WVALID、WREADY、BVALID、BREADY六个信号必须按顺序握手,少一个都会挂死。

我一般会写一个简单的 AXI Master 状态机,把读写操作封装成函数,这样上层逻辑不用关心 AXI 细节。

7.2 Flash 的写保护

有些 Flash 有硬件写保护引脚,比如 WP# 拉低时禁止写入。如果你发现写使能一直不成功,检查 WP# 是不是被拉低了。另外,状态寄存器里也有块保护位(BP0-BP2),如果这些位被置 1,对应的地址区域会被保护,无法擦写。上电默认通常是全保护,需要发 0x01 写状态寄存器来解除保护。

7.3 电源噪声

SPI Flash 在擦除和写入时电流会突然增大,如果电源滤波不好,可能会导致 Flash 复位或者数据错误。建议在 Flash 的 VCC 和 GND 之间加一个 0.1uF 的陶瓷电容,越近越好。我遇到过一块板子,擦除时偶尔失败,加了电容之后就稳定了。

7.4 温度影响

Flash 的擦写寿命和温度有关,高温下寿命会缩短。如果项目要在户外或者工业环境使用,选工业级 Flash,工作温度范围 -40 到 85 度。商业级只有 0 到 70 度,夏天户外可能就挂了。

8. 后续可以怎么扩展

如果你已经跑通了基本的读写,可以试试这几个方向:

第一,用 Quad 模式实现 XIP。XIP 就是 Execute In Place,直接从 Flash 取指令执行,不用先把代码搬到 RAM。MicroBlaze 支持从 SPI Flash 启动,配置好 STARTUP Primitive 和 Flash 控制器就行。

第二,加 DMA。AXI Quad SPI 支持 AXI Stream 接口,可以接 DMA 控制器,实现大数据块的自动搬运,CPU 不用干预。

第三,多 Flash 级联。有些板子挂了两颗 Flash,一颗存配置,一颗存数据。用 SSR 寄存器控制不同的 SS 引脚,可以分别访问。

第四,自己写一个轻量级 SPI 控制器。如果你觉得 AXI Quad SPI 太占资源,可以自己写一个只支持 Quad 读的控制器,大概 300 行 Verilog,资源占用只有 IP 核的三分之一。

我在实际项目里,如果是 Zynq 平台,直接用 PS 端的 QSPI 控制器,比 PL 端的 AXI Quad SPI 更方便,引脚固定,驱动成熟。如果是纯 FPGA 平台,才会考虑用这个 IP 核。选型的时候先看平台,再看资源,最后看开发效率,别为了用 IP 而用 IP。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询