FPGA_Verilog实现QSPI驱动,完成FLASH程序固化_qspi flash verilog
FPGA_Verilog实现QSPI驱动,完成FLASH程序固化
操作提要
使用此操作模式实现远程升级的原因是当前的FLASH的管脚直接与FPGA相连接,SPI总线并未直接与CPU相连接,那么则需要CPU下发升级指令与将要升级的文件给FPGA,然后在FPGA内部产生对应的SPI时序给FLASH.
我们最终的要求是能够实现将需要升级的数据文件写入到FLASH内,并在断电重启后FPGA可以自己正常启动,所以我们将操作流程进行简化,这里提供两种升级思路:
1;主机CPU通过与FPGA连接的控制
总线发送FLASH所需的命令码与指令,然后FLASH反馈的状态再通过对应总线送给CPU,在CPU内部判断flash当前读写的状态,FPGA仅作为驱动用来进行指令和数据的传输。
2;主机CPU只发送控制指令不发送命令码,且在此基础上再简化CPU的工作量,此模式CPU只需发送开始升级指令,升级文件的长度以及所需升级的文件数据流给FPGA,其余FLASH的读写与状态判断工作全部在FPAG内部进行,所有操作完成后返回状态给CPU。
方式1应该是推荐的远程升级方式,对FPGA来说只需要关注自己的驱动层是否正确即可,且当遇到不同的flash芯片,操作CPU来改写命令配置码比操作FPGA快捷方便许多,且整体结构清晰重构性较好。
方式2在FPGA内进行FLASH的读写状态判断与操作,CPU端工作量较小。
因为当前板卡CPU端修改困难,当前使用方式2,但方式2和方式1共用相同的驱动。
环境说明
// Creat Time : 2025-04-29// Devices : Xilinx xc7vx690tffg1761-3// Tool : vivado 2018.3// Author : // Revision : ver01// Encode Save : UTF-8
调试开发的硬件环境为:优数科技的UD PCIe-404 信号处理板卡(V7-690T),该板卡对外支持PCIe3.0×8通信,也可以采用千兆以太网(RJ45连接器)、万兆以太网(或RapidIO、Aurora,QSFP+连接器)接口进行通信,支持多板级联,模块的FPGA芯片可选国产或进口芯片(可选100%国产化)。
硬件设计与分析
Flash
当前板卡使用的FLASH芯片为国产GD25芯片(对于镁光或其他系列芯片来说其实操作基本一致),使用的是QSPI x1(standard-spi)模式,即当前的WP#和HOLD#不用来做数据传输,5脚DI对FLASH端为数据输入,2脚DO对FLASH为数据输出。
因为后续也需要使用镁光的flash,同样也查看了镁光的手册,对与Nor_Flash类型(包括Winbond的W25Q128BV)其手册内的QSPI总线格式是一样的,所以后续开发的QSPI驱动是通用的,不需要根据芯片的类型来修改驱动总线,但是经过查看对比这三个类型的芯片手册的命令码可能存在个别的不一样,所以仅需要查看对应的芯片手册,只更改对应的命令码字即可。下面以国产的GD25来说明:

片选信号Chip Select(/CS)的作用是使能或者不使能设备的操作,当CS为高时,表示设备未被选中,串行数据输出线(DO或IO0,IO1,IO2,IO3)均处于高阻态,当CS为低时,表示设备被选中,FPGA可以给QSPI Flash发送数据或从QSPI Flash接收数据。
2、串行数据输入信号SI(IO0)以及串行输出信号SO(IO1)
GD25LQ256D支持标准SPI协议,双线SPI(Dual SPI)协议与四线SPI(Quad SPI)协议。标准的SPI协议在串行时钟信号(SCLK)的上升沿把串行输入信号SI上的数据存入QSPI Flash中,在串行时钟信号(SCLK)的下降沿把QSPI Flash中的数据串行化通过单向的SO引脚输出。而在Dual SPI与Quad SPI中,SI与SO均为双向信号(既可以作为输入,也可以作为输出)。
3、Write Project(/WP)
写保护信号的作用是防止QSPI Flash的状态寄存器被写入错误的数据,WP信号低电平有效,但是当状态寄存器2的QE位被置1时,WP信号失去写保护功能,它变成Quad SPI的一个双向数据传输信号。
4、HOLD(/HOLD)
HOLD信号的作用是暂停QSPI Flash的操作。当HOLD信号为低,并且CS也为低时,串行输出信号DO将处于高阻态,串行输入信号DI与串行时钟信号SCLK将被QSPI Flash忽略。当HOLD拉高以后,QSPI Flash的读写操作能继续进行。当多个SPI设备共享同一组SPI总线相同的信号的时候,可以通过HOLD来切换信号的流向。和WP信号一样,当当状态寄存器2的QE位被置1时,HOLD信号失去保持功能,它也变成Quad SPI的一个双向数据传输信号。
5、串行时钟线
串行时钟线用来提供串行输入输出操作的时钟。四线快读模式最大速率为102M,但普通读写时钟在60M,推荐时钟使用50M足够。
芯片架构
内存大小与结构
如上图所示:当前flash内存大小为32M。
Flash write 每次page_program写入数据最多可以写256Bytes,256次page_program会写入64K的数据,即手册里面的一个sector,32M大小的flash由512个sector构成。
大小计算:256 *256 * 512 = 32M
第一个256:此处的256 是每次page_program最多写入的字节数为256字节
第二个256:此处的256 是256次page_program写满一个sector
第三个512:此处的512是512个sector会将32M的flash写满
模式选择与配置
写保护
最重要的两个状态寄存器
此处最重要!!!!
此处最重要!!!!
此处最重要!!!!
不论是GD25还是镁光或Winbond,均有两个或一对状态反馈寄存器,见对应手册描述。
这两个寄存器的所用是用来表示当前flash芯片所处的状态,以GD25举例:他的状态寄存器为05H和35H,这两个是用来读芯片状态的,与之对应的是01H,我们可以通过配置他来修改一些状态的值。
如:可以通过读35H的S11来判断当前Flash的地址字节模式,他通常默认的3地址字节模式,后续我们将会配置他为4地址字节模式,则需要来写01H的第11bit,将芯片配置为4地址字节模式。
另外可以通过读05H的bit0来判断当前FLASH是否处于BUSY模式,如果为1则不能进行读,写,擦除指令,只有当它为0时才允许此前的操作。所以在每次读、写、擦除指令进行完后必须要回读一下此状态。
可以通过读05H的bit1来判断是否打开了写使能,如果写使能未打开便开始进行写操作,则指令是无法被flash有效写入的;
还有一个重要的一点,在每次进行完一次页编程后,写使能会自动关闭(原因见手册),所以在后续的程序中会出现频繁的去打开写使能的操作。
最终因为上述的FLASH都是NOR_FLASH,其物理特性决定了他在每次进行写操作之前都需要进行擦除操作,他的物理特性决定了它无法直接将1变为0,所以只可以进行整个区域擦除后再写入,无法进行写覆盖。
对其他状态寄存器功能与描述见下方手册:
3/4地址字节模式
此处使用镁光的手册截图,来看他的描述
对flash芯片默认的是3字节模式,因为我要加载的文件远大于16M,所以使用4字节模式。
指令码
GD25有近40条指令,但是对于我们远程升级来说很多指令我们完全不要去使用,上图中加黄的是筛选出来的将在远程升级中使用的指令码;
详细的指令码描述与使用见芯片命令码区分与总线解析
芯片ID
GD25LQ256D:0xC86019
镁光(MT25QU256)的芯片ID为:0x20BB19
芯片命令码区分与总线解析
仅命令型
仅命令型即只需要发送配置的命令码即可,无需数据,无需地址。
如我们配置写使能打开,只需要发送一个06H即可。
//1. 仅命令型 (Instruction Only)// - WREN (06h) : 写使能// - WRDI (04h) : 写禁止// - RSTEN (66h) : 复位使能// - RST (99h) : 复位// - BE (60h/C7h): 全片擦除// CS# ˉ\\________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\___// MOSI ==________====// MISO ==============================================
命令+读数据类型
// 2. 命令+读数据型 (Instruction + Read Data)// - RDID (9Fh) : 读取ID,读3字节// - RDSR (05h) : 读状态寄存器,读1字节// - RDCR (35h) : 读配置寄存器,读1字节// - RDFSR (70h) : 读标志状态寄存器,读1字节// CS# ˉ\\____________________________________________________________________________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__...__/ˉ\\____// MOSI ==_________________________________// MISO ===========================================______________...<=======// ||||// 时序举例(RDID-9Fh):// CS# ˉ\\_________________________________________________________________________________________________________________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\_________// MOSI(9F) ___/ˉ\\____________/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\_________________________________________________________________________________________________________________________________// MISO ==============================================--------// 注意事项:// 1. MOSI在命令阶段发送指令,之后保持高阻态// 2. MISO在命令阶段之后开始输出有效数据// 3. 读取多个字节时连续进行// 4. 每个字节都是MSB优先传输
命令+地址类型
// 3. 命令+地址型 (Instruction + Address)// - SE (D8h) : 扇区擦除,3字节地址// - BE32K (52h) : 32KB块擦除,3字节地址// 命令阶段(8 clk) + 地址阶段(24 clk)// CS# ˉ\\_________________________________________________________________________________________________________________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\_________// MOSI ==________________________________==========// |||// MISO ============================================================================================================================================================================// 时序举例(扇区擦除-SE-D8h):// CS# ˉ\\________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__...__/ˉ\\____/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__// MOSI =====__...========================// MISO =======================================================// | 扇区擦除指令 + 24位扇区地址 |// 注意事项:// 1. 命令阶段发送8位指令码// 2. 地址阶段发送24位地址(A23-A0)// 3. 地址按MSB优先发送// 4. MISO在整个过程保持高阻态
命令+地址+读数据
// 4. 命令+地址+读数据型 (Instruction + Address + Read Data)// - READ (03h) : 读数据,3字节地址// - DREAD (3Bh) : 双输出读,3字节地址// - QREAD (6Bh) : 四输出读,3字节地址// 命令阶段(8 clk) + 地址阶段(24 clk) + 读数据阶段(8*N clk)// CS# ˉ\\___________________________________________________________________________________________________________________________________________________________________________________________________________________________________..........______/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__.............................................// MOSI ==________________________________======================================================================================// MISO ===================================================================================================================================================================_______________.......// |||||// 注意事项:// 1. 命令阶段发送8位指令码// 2. 地址阶段发送24位地址(A23-A0)// 3. MOSI在地址发送后保持高阻态// 4. MISO从地址后开始输出有效数据// 5. 所有传输都是MSB优先// 时序举例(READ-03h):// CS# ˉ\\_________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__...__/ˉ\\____/ˉ\\__/ˉ\\__...__/ˉ\\__/ˉ\\__...__/ˉ\\_// MOSI ========================================// MISO ================================================// | 读取指令 | | 连续读取数据 |
命令+地址+空时钟+读数据
// 5. 命令+地址+空周期+读数据型 (Instruction + Address + Dummy + Read Data)// - FAST_READ (0Bh) : 快速读,3字节地址,8个空周期// - DOFR (3Bh) : 双输出快速读,3字节地址,8个空周期// - QOFR (6Bh) : 四输出快速读,3字节地址,8个空周期// 命令阶段(8 clk) + 地址阶段(24 clk) + 空周期(8 clk) + 读数据阶段(8*N clk)//// CS# ˉ\\___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________.........._____________________/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__............// MOSI ==________________________________________========================================================================================================// MISO ==========================================================================================================================================================================================================_______________...........................// ||||||// 注意事项:// 0. 此命令只适用于快读,必须添加dummy_clock,dunny_clock的周期数可以配置,默认8bit// 1. 命令阶段发送8位指令码// 2. 地址阶段发送24位地址(A23-A0)// 3. 空周期阶段MOSI和MISO都保持高阻态// 4. MISO在空周期后开始输出有效数据// 5. 所有传输都是MSB优先// 时序举例(FAST_READ-0Bh):// CS# ˉ\\________________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__...__/ˉ\\____/ˉ\\__/ˉ\\__...__/ˉ\\____/ˉ\\__/ˉ\\__...__/ˉ\\_// MOSI ==============================================// MISO ======================================================// | 快速读指令 | 24位地址 | 8空周期 | 连续读取数据 |
命令+地址+写数据
// 6. 命令+地址+写数据型 (Instruction + Address + Write Data)// - PP (02h) : 页编程,3字节地址+数据(1-256字节)// - DPP (A2h) : 双输入页编程// - QPP (32h) : 四输入页编程// 命令阶段(8 clk) + 地址阶段(24 clk) + 写数据阶段(8*N clk)// CS# ˉ\\_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________..........______/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__.............................................// MOSI ==________________________________________________==============================================// MISO ==========================================================================================================================================================================================================================================================.......// |||||// 时序举例(PP-02h):// CS# ˉ\\_________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__...__/ˉ\\____/ˉ\\__/ˉ\\__...__/ˉ\\__/ˉ\\__...__/ˉ\\_// MOSI =============================// MISO ===========================================================// | 页编程 | 24位地址 | 最多256字节数据 |
命令+写配置
// 7. 命令+写配置型 (Instruction + Write Data)// - WRSR (01h) : 写状态寄存器,写1字节// - WRCR (3Eh) : 写配置寄存器,写1字节// 命令阶段(8 clk) + 配置数据(8 clk)// CS# ˉ\\_________________________________________________________________________________/ˉ// CLK ___/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__/ˉ\\__// MOSI ==_________________==// |||// MISO ======================================================================================
常用命令码说明及ILA时序抓取
读芯片ID
说明
时序
当前芯片是GD25,回读出来的芯片ID为C86019,与手册一致。
如果此处可以正确读出FLASH的芯片ID,那么其实工作已经完成一半。
第一个图为100M的时钟来抓取的数据,第二个图为50M的时钟来抓取的数据
关闭写使能,并查看是否关闭
说明
时序
第一个图为100M的时钟来抓取的数据,第二个图为50M的时钟来抓取的数据
写使能打开,并查看是否打开
说明
时序
4地址字节模式配置
说明
时序
50M的时钟来抓取的数据
芯片数据擦除
说明
时序
50M的时钟来抓取的数据
状态寄存器回读
说明
时序
50M的时钟来抓取的数据
page_program
说明
时序
50M的时钟来抓取的数据
数据回读监视
说明
时序
使用vio控制读取的数据长度,再将Vio_start_boot先置1再置0,便开始回读flash内的数据。
回读的数据结果,当前设置的ILA深度为64K,打开capture mod。
程序架构与实现
整体架构描述
flash升级的顶层模块包括4个基本模块
第一个模块mnc_flash用来与CPU交互,所需要的CPU配置指令只有两个,即开始固化升级、固化的数据字节长度,给CPU的反馈只有一个信号即固化成功,如果需要别的状态反馈也可以自己添加。
第二个模块是升级数据转换与缓冲控制单元,一个功能是将PCIE送来的数据进行位宽和大小端格式转换,另一个作用是用来控制数据流速缓冲
第三个模块是升级主控制模块,里面是控制升级的主状态机,并例化QSPI的驱动。
第四个模块是读FLASH控制,这个模块使用vio来控制去读flash某个区域的数据,该模块的作用是在调试的过程中数据写入会出错,用于将写入flash的数据都会对照验证,后续可能移除。此处的读写调用的是同一个驱动。
顶层模块
//输入输出说明//clk_50m : 输入,主时钟50M//clk_100m : 输入,flash升级数据的随路时钟100M//sys_rst,hard_rst : 输入,复位控制,高电平复位//ddr_flash_data: 输入,flash升级数据//ddr_flash_out_vld: 输入,flash升级数据的使能控制//ddr_flash_out_rdy: 输出,flash升级数据的ready返回//o_spi_cs_n : 输出,QSPI的片选,不工作时为1,写数据有效时为0//o_spi_mosi: 输出,QSPI的数据输出//i_spi_miso: 输入,QSPI的数据输出//CPU_....: CPU交互总线
//////////////////////////////////////////////////////////////////////// FileName : flash_boot_top// Author : // Creat Time : 2025-04-14// Encode Save : UTF-8// Device : // Design : // Note : // CopyRight(c) 2016, Chengdu universal_data Technology Co. Ltd.//////////////////////////////////////////////////////////////////////`timescale 1ns / 1ps //`define FLASH_READ_ENmodule flash_boot_top #( parameter U_DLY= 1 , parameter ADDR_W = 26 , parameter DATA_W = 16 , parameter CPU_ADDR_W = 8 )( //sdomain_clk input clk_50m ,//clock 50M input clk_100m ,//clock 100M input sys_rst , input hard_rst , //flash_data input [511: 0] ddr_flash_data , input ddr_flash_out_vld , output ddr_flash_out_rdy , // QSPI接口 output wire o_spi_cs_n ,// 片选信号,低电平有效 output wire o_spi_mosi ,// 主机输出 input wire i_spi_miso ,// 主机输入 //CPU_BUS input cpu_cs , input cpu_we , input cpu_rd , input [CPU_ADDR_W - 1:0] cpu_addr , input [ 31: 0] cpu_wdata , output wire [ 31: 0] cpu_rdata );// /////////////////////////////////////////////////////////////////////////////////// // Module