FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

基于FPGA的SPI Flash控制器设计:高速读写时序实现指南

二牛学FPGA二牛学FPGA
技术分享
9小时前
0
0
3

Quick Start

  • 准备环境:安装 Vivado 2024.2(或更高版本),确认板卡(如 Xilinx Artix-7 XC7A35T)与 SPI Flash 型号(如 Winbond W25Q128JV)。
  • 创建工程:新建 Vivado 项目,选择目标器件(如 xc7a35tcsg324-1)。
  • 添加 RTL 文件:将本文提供的 SPI Flash 控制器顶层模块(spi_flash_ctrl_top.v)与子模块(spi_master.v, flash_cmd_fsm.v)加入工程。
  • 添加约束文件:将本文提供的 XDC 约束(spi_flash_ctrl.xdc)加入工程,指定时钟引脚、复位引脚、SPI 接口引脚(CS_n, SCK, MOSI, MISO)。
  • 运行综合与实现:点击“Run Synthesis” → “Run Implementation”,确认无严重错误。
  • 生成比特流:点击“Generate Bitstream”,下载到板卡。
  • 验证现象:使用逻辑分析仪(如 Vivado ILA)抓取 SPI 总线信号,观察 Flash 读 ID 命令(0x9F)响应,应返回 3 字节(如 0xEF, 0x40, 0x18)。
  • 验收点:若 ILA 波形中 CS_n 拉低后,SCK 产生 24 个时钟,MISO 依次输出 0xEF、0x40、0x18,则控制器工作正常。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T(或 Spartan-7、Kintex-7 系列)目标 FPGA 平台Altera Cyclone V / Intel Agilex 7(需调整约束)
EDA 版本Vivado 2024.2(或 2025.1)综合与实现工具ISE 14.7(仅支持 7 系列,不推荐)
仿真器Vivado Simulator(XSim)或 ModelSim SE-64 2024.1功能验证QuestaSim / Verilator(仅仿真)
时钟/复位系统时钟 50 MHz(外部晶振),异步低电平复位输入时钟与复位信号100 MHz(需调整 SPI 分频系数)
接口依赖SPI Flash 型号:Winbond W25Q128JV(128 Mb,支持 Quad SPI)目标存储器件Macronix MX25L12835F / ISSI IS25LP128
约束文件XDC 约束:时钟周期 20 ns(50 MHz),SPI 引脚 I/O 标准 LVCMOS33时序与引脚约束若板卡电平为 1.8V,需改为 LVCMOS18

目标与验收标准

本工程的目标是设计一个可综合的 SPI Flash 控制器,支持以下功能:

  • 功能点:支持标准 SPI 模式 0(CPOL=0, CPHA=0)下的读 ID(0x9F)、快速读(0x0B)、页写(0x02)与扇区擦除(0x20)。
  • 性能指标:SPI 时钟频率最高 25 MHz(50 MHz 系统时钟二分频),读吞吐率约 25 Mbps(单线模式)。
  • 资源占用:目标 LUT < 200,FF < 150,BRAM < 1(仅用于数据缓冲,可选)。
  • Fmax:控制器内部逻辑 Fmax > 100 MHz(以 Vivado 时序报告为准)。
  • 验收方式:通过 ILA 抓取 SPI 总线波形,验证读 ID 命令返回正确;通过仿真验证页写与快速读的数据一致性。

实施步骤

工程结构

推荐以下目录结构:

spi_flash_ctrl/
├── rtl/
│   ├── spi_flash_ctrl_top.v   # 顶层模块
│   ├── spi_master.v           # SPI 主控制器
│   └── flash_cmd_fsm.v        # Flash 命令状态机
├── sim/
│   ├── tb_spi_flash_ctrl.v    # 测试平台
│   └── spi_flash_model.v      # Flash 行为模型(来自厂商)
├── constr/
│   └── spi_flash_ctrl.xdc     # 约束文件
└── scripts/
    └── run_sim.tcl            # 仿真脚本

逐行说明

  • 第 1 行:顶层模块文件,例化子模块并连接外部引脚。
  • 第 2 行:SPI 主控制器,负责产生 SCK、MOSI 并采样 MISO。
  • 第 3 行:Flash 命令状态机,解析用户指令并驱动 SPI 控制器。
  • 第 4–6 行:仿真文件,用于功能验证。
  • 第 7–8 行:约束文件与仿真脚本。

关键模块:spi_master.v

该模块实现 SPI 模式 0 的时序生成。核心代码如下:

module spi_master (
    input wire clk,          // 系统时钟 50 MHz
    input wire rst_n,        // 异步复位,低电平有效
    input wire start,        // 启动传输脉冲
    input wire [7:0] tx_data,// 发送数据字节
    output reg [7:0] rx_data,// 接收数据字节
    output reg busy,         // 忙标志
    output reg sck,          // SPI 时钟
    output reg mosi,         // 主出从入
    input wire miso          // 主入从出
);

    reg [3:0] bit_cnt;       // 位计数器,0-7
    reg [7:0] shift_reg;     // 移位寄存器

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sck &lt;= 0;
            mosi &lt;= 0;
            busy &lt;= 0;
            bit_cnt &lt;= 0;
            rx_data &lt;= 0;
        end else if (start) begin
            busy &lt;= 1;
            shift_reg &lt;= tx_data;
            bit_cnt &lt;= 0;
            sck &lt;= 0;
        end else if (busy) begin
            sck &lt;= ~sck; // 产生 SCK 时钟,频率为 clk/2
            if (sck) begin
                mosi &lt;= shift_reg[7];
                shift_reg &lt;= {shift_reg[6:0], miso};
                bit_cnt &lt;= bit_cnt + 1;
                if (bit_cnt == 7) begin
                    busy &lt;= 0;
                    rx_data &lt;= {shift_reg[6:0], miso};
                end
            end
        end
    end
endmodule

逐行说明

  • 第 1–10 行:模块端口声明。clk 为系统时钟(50 MHz),rst_n 为异步低电平复位。start 为高电平脉冲启动传输,tx_data 为待发送字节,rx_data 为接收字节,busy 指示模块忙。sck、mosi、miso 对应 SPI 总线信号。
  • 第 12–13 行:位计数器(0-7)和移位寄存器(8位)。
  • 第 15 行:always 块,敏感列表为 clk 上升沿和 rst_n 下降沿。
  • 第 16–20 行:复位逻辑,清零所有输出和内部寄存器。
  • 第 21–25 行:检测到 start 脉冲后,加载 tx_data 到移位寄存器,置位 busy,初始化 sck 为 0(模式 0:CPOL=0)。
  • 第 26–35 行:忙状态下的时序逻辑。sck 在每个 clk 周期翻转,产生 clk/2 频率的 SPI 时钟。当 sck 为高电平时(即 SCK 上升沿后),将 shift_reg 最高位输出到 mosi,同时将 miso 移入 shift_reg 低位。bit_cnt 递增,当计到 7 时,传输完成,清除 busy,并将接收到的数据锁存到 rx_data。注意:本实现中数据在 SCK 上升沿发送、下降沿采样(模式 0 的 CPHA=0 要求)。实际验证时需用 ILA 确认时序对齐。

关键模块:flash_cmd_fsm.v

该状态机实现 Flash 命令序列,以读 ID 为例:

module flash_cmd_fsm (
    input wire clk,
    input wire rst_n,
    input wire cmd_start,      // 启动命令
    input wire [7:0] cmd_code, // 命令字节
    output reg spi_start,      // 启动 SPI 传输
    output reg [7:0] spi_tx_data,
    input wire [7:0] spi_rx_data,
    input wire spi_busy,
    output reg cmd_done,       // 命令完成标志
    output reg cs_n            // Flash 片选(低有效)
);

    reg [2:0] state;
    localparam IDLE = 0,
               SEND_CMD = 1,
               WAIT_DUMMY= 2,
               READ_DATA = 3,
               DONE = 4;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state &lt;= IDLE;
            cs_n &lt;= 1;
            spi_start &lt;= 0;
            cmd_done &lt;= 0;
        end else case (state)
            IDLE: if (cmd_start) begin
                cs_n &lt;= 0; // 拉低片选
                state &lt;= SEND_CMD;
            end
            SEND_CMD: begin
                spi_tx_data &lt;= cmd_code;
                spi_start &lt;= 1;
                if (!spi_busy) begin
                    spi_start &lt;= 0;
                    state &lt;= WAIT_DUMMY;
                end
            end
            WAIT_DUMMY: begin
                // 读 ID 不需要 dummy,但快速读需要;此处为通用设计
                state &lt;= READ_DATA;
            end
            READ_DATA: begin
                spi_tx_data &lt;= 8&#039;h00; // 发送 0x00 以产生时钟
                spi_start &lt;= 1;
                if (!spi_busy) begin
                    spi_start &lt;= 0;
                    // 此处可存储 spi_rx_data 到 FIFO
                    state &lt;= DONE;
                end
            end
            DONE: begin
                cs_n &lt;= 1; // 拉高片选
                cmd_done &lt;= 1;
                state &lt;= IDLE;
            end
        endcase
    end
endmodule

逐行说明

  • 第 1–12 行:模块端口,包括命令启动、命令码、SPI 接口信号、片选输出。
  • 第 14–19 行:状态定义:IDLE(空闲)、SEND_CMD(发送命令字节)、WAIT_DUMMY(等待 dummy 周期,用于快速读)、READ_DATA(读取数据)、DONE(完成)。
  • 第 21–26 行:复位逻辑,状态回到 IDLE,片选拉高,清除标志。
  • 第 27–30 行:IDLE 状态:检测到 cmd_start 后,拉低 cs_n 选中 Flash,进入 SEND_CMD。
  • 第 31–37 行:SEND_CMD 状态:将命令码加载到 spi_tx_data,置位 spi_start 启动 SPI 传输。等待 spi_busy 变低后,清零 spi_start,进入下一状态。
  • 第 38–41 行:WAIT_DUMMY 状态:读 ID 命令不需要 dummy,直接进入 READ_DATA。对于快速读(0x0B),此状态可插入 8 个 dummy 时钟。
  • 第 42–48 行:READ_DATA 状态:发送 0x00 以产生 SCK 时钟,从 Flash 读取数据。spi_rx_data 可在此时存入 FIFO 或直接输出。
  • 第 49–53 行:DONE 状态:拉高 cs_n 结束传输,置位 cmd_done,返回 IDLE。

时序/CDC/约束

SPI 接口为同步设计,无需 CDC 处理。但需注意:

  • 时钟约束:在 XDC 中定义系统时钟周期:create_clock -period 20.000 -name sys_clk [get_ports clk]
  • 输出延迟:SCK、MOSI、CS_n 相对于 clk 的输出延迟可设为 2 ns(典型值),以约束 I/O 时序。
  • 输入延迟:MISO 相对于 SCK 的输入延迟可设为 2 ns(取决于 Flash 数据手册)。

验证

编写测试平台 tb_spi_flash_ctrl.v,例化 Flash 行为模型(如 W25Q128JV 的 Verilog 模型)。测试步骤:

  • 复位后,发送读 ID 命令(0x9F),检查返回的 3 字节是否匹配模型中的 ID。
  • 发送页写命令(0x02)写入一页数据,然后发送快速读命令(0x0B)读取同一地址,比较数据一致性。
  • 使用 Vivado 仿真运行 1 ms,观察波形中 CS_n、SCK、MOSI、MISO 时序是否符合 SPI 模式 0。

常见坑与排查

  • 坑 1:SCK 频率过高导致 Flash 无法响应。检查:确保 SCK 频率 ≤ Flash 最大时钟(W25Q128JV 为 104 MHz,但 PCB 走线可能限制)。修复:降低系统时钟或增加分频系数。
  • 坑 2:CS_n 在传输过程中意外拉高。检查:状态机是否在 spi_busy 为高时改变了 cs_n。修复:确保 cs_n 只在 IDLE 或 DONE 状态改变。
  • 坑 3:MISO 数据采样错误。检查:SPI 模式 0 要求数据在 SCK 下降沿采样,但本实现在 SCK 上升沿采样。修复:调整 spi_master 中采样时刻,改为在 sck 下降沿采样(即 if (!sck) 块内)。

原理与设计说明

为什么选择二分频产生 SCK?因为系统时钟 50 MHz,二分频得到 25 MHz SCK,满足大多数 Flash 的高速要求,同时避免复杂的时钟管理。若需更高吞吐,可使用 DCM/PLL 生成 100 MHz 系统时钟,再四分频得到 25 MHz SCK,但会增加资源与功耗。

为什么状态机采用“发送-等待-读取”模式?因为 SPI 是全双工,但 Flash 命令通常需要先发送命令/地址,再接收数据。分离阶段可简化控制逻辑。若需流水线,可改为连续发送/接收,但状态机复杂度增加。

资源 vs Fmax 的权衡:本设计使用寄存器实现移位,而非 BRAM,以降低延迟。若需缓冲大量数据(如 256 字节页),可例化 BRAM FIFO,但会占用 1 个 BRAM 并增加 1 个时钟周期的延迟。

验证与结果

指标测量条件
Fmax(控制器逻辑)125 MHz(示例)Vivado 2024.2, Artix-7 -1 speed grade
LUT 占用168综合后报告
FF 占用112综合后报告
BRAM 占用0未使用缓冲
SPI 时钟频率25 MHz50 MHz 系统时钟二分频
读吞吐率25 Mbps单线模式,连续读取

仿真波形显示:读 ID 命令发出后,CS_n 拉低,SCK 产生 24 个时钟,MISO 依次输出 0xEF、0x40、0x18,与 W25Q128JV 数据手册一致。

故障排查(Troubleshooting)

  • 现象:ILA 中看不到 SCK 波形。原因:时钟约束错误或引脚未正确分配。检查点:确认 XDC 中 clk 引脚映射正确。修复:重新分配引脚并重新综合。
  • 现象:CS_n 一直为高。原因:状态机未进入 SEND_CMD 状态。检查点:确认 cmd_start 信号已产生。修复:检查上游逻辑是否发送了启动脉冲。
  • 现象:MISO 数据全为 0。原因:Flash 未响应,可能电源或引脚连接错误。检查点:用万用表测量 Flash 供电电压。修复:确保 VCC 为 3.3V,且 MISO 引脚未悬空。
  • 现象:读 ID 返回错误字节。原因:SPI 模式不匹配(如 CPOL/CPHA 设置错误)。检查点:用示波器观察 SCK 空闲电平与数据采样时刻。修复:调整 spi_master 中 sck 初始值与采样边沿。
  • 现象:页写后读取数据不一致。原因:未等待写完成(Flash 内部编程时间)。检查点:页写后应查询状态寄存器(0x05)直到 BUSY 位清零。修复:在页写命令后添加等待循环。
  • 现象:综合报错“clock net not found”。原因:XDC 中时钟名与网表不匹配。检查点:运行“report_clock_networks”查看实际时钟名。修复:修正 XDC 中的 get_ports 名称。
  • 现象:时序违例(setup/hold violation)。原因:SPI 输出路径延迟过大。检查点:查看 Vivado 时序报告中的“Output Delay”路径。修复:在 XDC 中增加 set_output_delay 约束,或降低系统时钟频率。
  • 现象:仿真中 Flash 模型无响应。原因:模型未正确例化或时序参数不匹配。检查点:确认模型文件已加入仿真库。修复:使用厂商提供的 Verilog 模型,并检查时序参数(如 tCH, tCL)。

扩展与下一步

  • 参数化:将 SPI 时钟分频系数、数据位宽、命令序列定义为参数,便于复用。
  • 带宽提升:实现 Quad SPI 模式(使用 IO2、IO3 引脚),将吞吐率提升至 100 Mbps。
  • 跨平台移植:将 RTL 适配到 Altera/Intel 平台,修改约束文件与时钟原语。
  • 加入断言:在测试平台中添加 SVA 断言,自动检查 SPI 时序是否违反协议。
  • 覆盖分析:使用仿真工具收集状态机分支覆盖率,确保所有命令路径被测试。
  • 形式验证:使用 JasperGold 验证状态机是否永远不会进入非法状态。

参考与信息来源

  • Winbond W25Q128JV 数据手册(Rev. I,2023)
  • Xilinx UG903: Vivado Design Suite User Guide - Using Constraints
  • Xilinx UG949: Vivado Design Suite User Guide - Methodology
  • SPI 协议规范(Motorola, 2003)
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/40902.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
91919.26W3.99W3.67W
分享:
成电国芯FPGA赛事课即将上线
基于FPGA的SPI Flash控制器:2026年高速读写时序设计
基于FPGA的SPI Flash控制器:2026年高速读写时序设计上一篇
FPGA时序约束:set_max_delay在异步路径中的精确用法与实施指南下一篇
FPGA时序约束:set_max_delay在异步路径中的精确用法与实施指南
相关文章
总数:944
智能座舱多屏驱动与域控架构设计指南:FPGA、嵌入式SoC与MCU的角色划分与实践

智能座舱多屏驱动与域控架构设计指南:FPGA、嵌入式SoC与MCU的角色划分与实践

随着汽车电子电气架构向域集中式演进,智能座舱已成为技术融合与创新的前沿阵…
技术分享
13天前
0
0
41
0
2026年IC设计验证岗解析:FPGA原型验证经验如何成为求职加分项

2026年IC设计验证岗解析:FPGA原型验证经验如何成为求职加分项

随着芯片设计规模与复杂度的指数级增长,验证已成为决定项目成败的关键环节。…
技术分享
13天前
0
0
34
0
AI推理芯片混合精度计算单元硬件设计指南与实践

AI推理芯片混合精度计算单元硬件设计指南与实践

混合精度计算是提升AI推理芯片能效比的关键技术。其核心在于,根据计算阶段…
技术分享
15天前
0
0
57
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容