资讯动态

FPGA开发者必读:AXI Quad SPI IP核配置与Flash读写实战指南

发布时间:2026/9/28 1:42:57 来源:尧图企业网站定制
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 行 VerilogQuad 模式大概 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分频系数设为 0SCK 就是 50MHz设为 1SCK 就是 25MHz设为 4SCK 就是 10MHz。但 SCK 不能超过 Flash 芯片的最大频率。W25Q128 在 Standard 模式下最高 104MHzQuad 模式下最高 80MHz。如果你设了 50MHzStandard 模式没问题Quad 模式就超了。所以切换模式时记得重新算分频系数。还有一个隐藏限制AXI 时钟和 SCK 的相位关系。如果分频系数太小SCK 太快IP 核内部的状态机可能来不及处理导致数据错位。我一般会把 SCK 设在 Flash 最大频率的 70% 左右留点余量。比如 W25Q128 Quad 模式最高 80MHz我就设 50MHz稳得很。3.4 从机选择与 STARTUP PrimitiveSSR 寄存器控制 SS 信号但如果你勾了Enable STARTUP PrimitiveIP 核会额外占用一个 STARTUP 原语用来在配置阶段直接访问 Flash。这个选项只在你想用 FPGA 自己读配置数据时才需要普通应用不用勾。不勾 STARTUP 的话SS 信号完全由 SSR 寄存器控制。写 0x01 到 SSRSS 拉低写 0x00SS 拉高。注意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 下这个指令的发送方式一样都是单线。操作步骤写 SSR 0x01拉低 SS写 DTR 0x9F写 DTR 0x00dummy写 DTR 0x00dummy写 DTR 0x00dummy读 DRR 三次得到 Manufacturer ID、Memory Type、Capacity写 SSR 0x00拉高 SSW25Q128 的 JEDEC ID 是 0xEF4018其中 0xEF 是 Winbond 的厂商 ID0x40 是 SPI Flash 类型0x18 表示 128Mbit16MB。如果你读回来全是 0xFF说明 MISO 没接对或者 Flash 没供电。如果读回来是 0x000000说明 MOSI 没发出去或者 SS 没拉低。如果读回来是 0xEF4018 但顺序反了说明字节序搞错了AXI Quad SPI 默认是大端先读出来的是高位字节。4.2 读状态寄存器判断忙闲Flash 在擦除或写入时会进入忙状态这时候不能发其他指令。读状态寄存器的指令是 0x05后面跟一个 dummy 字节然后读回一个字节。状态寄存器的 bit0 是BUSY位1 表示忙0 表示空闲。bit1 是WELWrite Enable Latch写使能之后这个位会变成 1写完数据之后自动清零。操作步骤和读 ID 类似只是指令换成 0x05dummy 只发一个字节然后读一个字节。判断 bit0 是否为 0为 0 才能继续下一步。我一般会写一个wait_flash_ready函数循环读状态寄存器直到 BUSY 位为 0。注意加超时不然 Flash 坏了会死循环。4.3 写使能与扇区擦除擦除之前必须先写使能指令是 0x06没有参数也没有返回值。发完 0x06 之后状态寄存器的 WEL 位会变成 1。扇区擦除的指令是 0x20后面跟 3 个字节的地址。W25Q128 的扇区大小是 4KB所以地址要按 4KB 对齐。比如擦除第一个扇区地址是 0x000000擦除第二个扇区地址是 0x001000。操作步骤发 0x06 写使能读状态寄存器确认 WEL 1发 0x20 地址高字节 地址中字节 地址低字节拉高 SS循环读状态寄存器等 BUSY 0擦除一个 4KB 扇区大概需要 50 到 100 毫秒取决于 Flash 型号。这段时间 CPU 可以干别的但 SPI 控制器不能发其他指令。4.4 页编程写入数据写入数据的指令是 0x02后面跟 3 个字节地址然后是要写入的数据。W25Q128 的页大小是 256 字节所以一次最多写 256 字节而且不能跨页。操作步骤发 0x06 写使能发 0x02 地址连续写数据字节拉高 SS等 BUSY 0注意页编程只能把 1 变成 0不能把 0 变成 1。所以写入之前必须先擦除擦除之后所有位都是 1写入才能正确。如果你往一个没擦除的地址写数据结果会是旧数据和新数据的按位与大概率是错的。4.5 Quad 模式读数据速度提升的关键Quad 模式读数据的指令是 0xEBFast 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 提到 80MHzQuad 模式能到 140 微秒左右。5. 常见问题与排查技巧实录5.1 读回来全是 0xFF 怎么办这是最常见的问题原因通常有三个第一MISO 没接对。检查 Flash 的 DO 引脚是不是接到了 FPGA 的 MISO。有些板子把 DO 和 IO1 复用Standard 模式下 DO 是 MISOQuad 模式下 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.3VFlash 的 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: QuadData Width: 8Frequency Ratio: 根据 AXI 时钟和期望 SCK 计算FIFO Depth: 64Enable 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。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑