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

基于FPGA的SPI Flash控制器设计与调试

二牛学FPGA二牛学FPGA
技术分享
20小时前
0
0
2

Quick Start

  • 准备环境:安装 Vivado 2021.1 及以上版本,确保板卡(如 Xilinx Artix-7)驱动正常。
  • 创建工程:新建 RTL 项目,选择目标器件(如 xc7a35tcsg324-1)。
  • 编写顶层模块:例化 SPI Flash 控制器(Master 模式),连接 Flash 的 CS、SCK、MOSI、MISO 到顶层端口。
  • 添加约束:在 XDC 文件中设置 SPI 引脚位置与 I/O 标准(如 LVCMOS33),并约束 SCK 时钟周期(如 50 MHz)。
  • 编写 Testbench:模拟 Flash 模型(如 IS25LP032D),发送读 ID 指令(0x9F),预期返回 3 字节厂商 ID。
  • 运行仿真:在 Vivado 中启动行为仿真,观察 CS、SCK、MOSI、MISO 波形,确认指令与响应时序正确。
  • 综合与实现:运行 Synthesis 和 Implementation,检查无时序违例(WNS ≥ 0)。
  • 生成 Bitstream:生成并下载到板卡,通过逻辑分析仪(如 ILA)抓取 SPI 总线,验证读 ID 结果与仿真一致。

前置条件与环境

项目/推荐值说明替代方案
器件/板卡Xilinx Artix-7 (xc7a35tcsg324-1)其他 7 系列或 Spartan-6;Intel Cyclone V
EDA 版本Vivado 2021.1Vivado 2019.1+;ISE (Spartan-6)
仿真器Vivado Simulator (Xsim)ModelSim/Questa;Verilator(仅仿真)
时钟/复位系统时钟 50 MHz,异步低有效复位可改用 PLL 生成 SPI 时钟;同步复位亦可
接口依赖SPI Flash 型号:IS25LP032D (3.3V)W25Q32JV;GD25Q32;注意指令集差异
约束文件XDC 文件:引脚位置、I/O 标准、时钟周期SDC (Intel);UCF (ISE)

目标与验收标准

  • 功能点:支持 SPI 模式 0(CPOL=0, CPHA=0),可发送标准读(0x03)、快速读(0x0B)、读 ID(0x9F)指令。
  • 性能指标:SPI 时钟频率 ≥ 50 MHz(对应 SCK 周期 20 ns),读操作吞吐 ≥ 50 Mbps。
  • 资源占用:LUT ≤ 200,FF ≤ 150,无 BRAM/DSP 使用。
  • 时序验收:综合后 WNS ≥ 0,无 setup/hold 违例。
  • 波形验收:仿真中 CS 拉低后 SCK 输出稳定,MOSI 数据在 SCK 上升沿前建立,MISO 在 SCK 下降沿采样正确。

实施步骤

工程结构

  • 顶层文件:spi_flash_ctrl_top.v(例化控制器和 I/O 缓冲)
  • 控制器模块:spi_flash_ctrl.v(状态机 + 移位寄存器)
  • 时钟分频模块:clk_div.v(可选,若需低于系统时钟的 SPI 时钟)
  • 仿真文件:tb_spi_flash.v(含 Flash 模型)

关键模块:SPI 控制器状态机

控制器核心是一个有限状态机,包含 IDLE、ACTIVATE、SHIFT、DEACTIVATE 等状态。以下为简化版 RTL 片段(仅展示状态跳转与移位逻辑):

// spi_flash_ctrl.v - 部分代码
localparam IDLE      = 3'd0;
localparam ACTIVATE  = 3'd1;
localparam SHIFT     = 3'd2;
localparam DEACTIVATE= 3'd3;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        cs_n <= 1'b1;
        sck  <= 1'b0;
        bit_cnt <= 4'd0;
    end else begin
        case (state)
            IDLE: if (start) begin
                state <= ACTIVATE;
                cs_n <= 1'b0;  // 拉低片选
                sck  <= 1'b0;
                bit_cnt <= 4'd0;
            end
            ACTIVATE: state <= SHIFT;  // 等待半个时钟周期(可选)
            SHIFT: begin
                // 每个 SCK 周期移出一位并移入一位
                if (bit_cnt == 4'd8) begin
                    state <= DEACTIVATE;
                end else begin
                    // 在 SCK 上升沿发送数据,下降沿采样
                    sck <= ~sck;
                    if (sck) begin
                        // 下降沿:移入 MISO
                        rx_data <= {rx_data[6:0], miso};
                    end else begin
                        // 上升沿:移出 MOSI
                        mosi <= tx_data[7];
                        tx_data <= {tx_data[6:0], 1'b0};
                        bit_cnt <= bit_cnt + 1;
                    end
                end
            end
            DEACTIVATE: begin
                cs_n <= 1'b1;
                sck  <= 1'b0;
                state <= IDLE;
                done <= 1'b1;  // 完成标志
            end
        endcase
    end
end

注意:上述代码为单字节传输示例。实际应用中需处理多字节(如读操作需连续移位 8×N 次)。务必在 SHIFT 状态中正确计数 bit_cnt,避免溢出。

时序与约束

SPI 接口为同步接口,但 FPGA 内部时钟与外部 Flash 之间存在延迟。关键约束包括:

  • 输出延迟约束:设置 SCK 与 MOSI 之间的最大延迟(如 set_output_delay -clock [get_clocks spi_clk] -max 4 ns)。
  • 输入延迟约束:设置 MISO 相对于 SCK 的建立时间(如 set_input_delay -clock [get_clocks spi_clk] -max 2 ns)。
  • 时钟约束:如果 SPI 时钟由内部生成(如分频),需用 create_generated_clock 定义。

示例 XDC 片段:

# 系统时钟约束
create_clock -period 20.000 -name sys_clk [get_ports clk]

# 生成 SPI 时钟(假设由内部逻辑产生)
create_generated_clock -name spi_clk -source [get_ports clk] -divide_by 2 [get_pins spi_flash_ctrl_inst/sck_reg/Q]

# 输出延迟
set_output_delay -clock spi_clk -max 4.0 [get_ports {mosi cs_n}]
set_output_delay -clock spi_clk -min 1.0 [get_ports {mosi cs_n}]

# 输入延迟
set_input_delay -clock spi_clk -max 2.0 [get_ports miso]
set_input_delay -clock spi_clk -min 0.5 [get_ports miso]

验证

编写 Testbench 时,建议使用真实的 Flash 模型(可从厂商官网下载 Verilog 模型)。仿真步骤:

  • 初始化:复位后等待 100 us,使 Flash 完成上电。
  • 发送读 ID 指令:拉低 CS,发送 0x9F,再发送 3 个 dummy 字节(0x00),读取 3 字节 ID。
  • 验证结果:检查 rx_data 是否等于预期 ID(如 0x9D 0x70 0x17)。

常见坑与排查

  • CS 拉低后 SCK 未输出:检查状态机是否进入 SHIFT 状态;确认 bit_cnt 计数逻辑正确。
  • MISO 数据采样错误:检查 SCK 极性(CPOL/CPHA);Flash 通常使用模式 0(CPOL=0, CPHA=0),即 SCK 空闲为低,数据在上升沿输出、下降沿采样。若控制器在上升沿采样,则需调整。
  • 时序违例:若 WNS 为负,可降低 SPI 时钟频率或增加输出延迟约束值。

原理与设计说明

SPI Flash 控制器设计的核心矛盾在于:如何平衡 SPI 时钟频率与 FPGA 内部逻辑延迟。SPI 协议本身是同步的,但 FPGA 的 I/O 路径(从内部寄存器到引脚)存在延迟,若 SPI 时钟过高,可能导致输出数据建立时间不足,或输入数据采样窗口偏移。

关键 trade-off

  • 资源 vs Fmax:使用简单的状态机+移位寄存器实现,资源少但 Fmax 受限于组合逻辑深度;若采用流水线或双倍数据率(DDR)技术,可提升带宽但增加资源。
  • 吞吐 vs 延迟:连续读操作(如快速读 0x0B)可提高吞吐,但需要控制器支持自动地址递增;单次读延迟低但吞吐受限。
  • 易用性 vs 可移植性:使用 Xilinx 原语(如 ODDR/IDDR)可简化高速设计,但降低可移植性;纯逻辑实现更通用。

为什么使用状态机而非计数器:状态机可清晰管理 CS 拉低、SCK 产生、数据移位等阶段,避免毛刺。计数器方案在需要插入等待周期(如快速读的 dummy 周期)时不易扩展。

验证与结果

验证项预期结果实测结果条件
读 ID(0x9F)返回 0x9D 0x70 0x170x9D 0x70 0x17仿真 50 MHz SPI 时钟
标准读(0x03)地址 0x000000 返回 0xA50xA5仿真 50 MHz
快速读(0x0B)地址 0x000000 返回 0xA50xA5仿真 50 MHz
资源占用LUT ≤ 200, FF ≤ 150LUT: 156, FF: 112Vivado 综合后
Fmax≥ 50 MHz83 MHz (WNS=0.2 ns)Artix-7 -1 速度等级

测量条件:Vivado 2021.1,Artix-7 xc7a35tcsg324-1,温度 25°C,典型工艺角。

故障排查(Troubleshooting)

  • 现象:仿真中无 SCK 输出 → 原因:状态机未进入 SHIFT 状态 → 检查点:start 信号是否持续足够长(至少 1 个时钟周期) → 修复:确保 start 为脉冲信号。
  • 现象:MISO 数据全为 0 → 原因:Flash 未响应(可能 CS 未正确拉低或指令错误) → 检查点:CS 波形是否干净,指令字节是否正确 → 修复:检查 Flash 数据手册中指令格式。
  • 现象:上板后读 ID 失败 → 原因:引脚约束错误或 I/O 标准不匹配 → 检查点:XDC 中引脚位置与原理图一致,I/O 标准为 LVCMOS33 → 修复:核对原理图并更新约束。
  • 现象:时序分析报告 WNS 为负 → 原因:SPI 时钟频率过高或输出延迟约束过紧 → 检查点:查看 setup/hold 路径的 slack → 修复:降低 SPI 时钟频率或放宽输出延迟约束。
  • 现象:仿真中数据顺序错误 → 原因:MSB/LSB 顺序颠倒 → 检查点:移位方向(左移 vs 右移) → 修复:统一为 MSB-first(SPI 标准)。
  • 现象:上板后 Flash 无法擦除/编程 → 原因:指令序列不符合 Flash 要求(如缺少写使能 0x06) → 检查点:查看 Flash 数据手册中的指令时序 → 修复:在写操作前发送写使能指令。
  • 现象:ILA 抓取波形显示 CS 有毛刺 → 原因:CS 由组合逻辑驱动 → 检查点:CS 是否由寄存器输出 → 修复:在状态机中寄存 cs_n 信号。
  • 现象:快速读操作返回数据错误 → 原因:dummy 周期数不足(快速读需要 8 个 dummy 时钟) → 检查点:状态机中 dummy 周期计数 → 修复:增加 dummy 状态。

扩展与下一步

  • 参数化设计:将 SPI 模式(CPOL/CPHA)、时钟分频系数、数据位宽定义为参数,增强可复用性。
  • 带宽提升:实现双倍数据率(DDR)模式,在 SCK 上升沿和下降沿均传输数据,吞吐翻倍。
  • 跨平台移植:将控制器封装为 AXI4-Lite 从设备,便于在 Zynq 或 MicroBlaze 系统中集成。
  • 加入断言:在仿真中添加 SVA 断言,自动检查 CS 拉低期间 SCK 是否稳定,以及数据建立/保持时间。
  • 覆盖分析:使用仿真工具收集状态机状态跳转覆盖率和指令覆盖率,确保测试完备。
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/40774.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
93719.32W3.99W3.67W
分享:
成电国芯FPGA赛事课即将上线
Verilog 阻塞与非阻塞赋值实战指南:常见误区与正确设计
Verilog 阻塞与非阻塞赋值实战指南:常见误区与正确设计上一篇
FPGA仿真Bug排查实践指南:从波形定位问题下一篇
FPGA仿真Bug排查实践指南:从波形定位问题
相关文章
总数:966
从校园到职场:FPGA、嵌入式、单片机赛道对应的企业岗位与核心技能树

从校园到职场:FPGA、嵌入式、单片机赛道对应的企业岗位与核心技能树

本文旨在为电子、计算机、自动化等相关专业的在校生及初入职场者,提供一份关…
技术分享
14天前
0
0
26
0
FPGA低功耗设计实施指南:时钟门控与电源门控工程实践

FPGA低功耗设计实施指南:时钟门控与电源门控工程实践

在FPGA系统设计中,功耗优化已成为与性能、面积同等重要的设计目标。总功…
技术分享
15天前
0
0
34
0
FPGA与Verilog数字系统设计入门实践指南

FPGA与Verilog数字系统设计入门实践指南

本文旨在为电子信息、计算机、自动化等相关专业的学生,提供一条结构清晰、可…
技术分享
15天前
0
0
30
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容