FPGA_Verilog实现QSPI驱动,完成FLASH程序固化

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共用相同的驱动。

环境说明

verilog 复制代码
// 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来说明:

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/892c6cf4b5234b9fa73d8314d0a9a1d3.png#pic_center


加载方式

当前拨码开关:FPGA_MODE_M0为1 ,其余均为0

当前加载方式:001 :Master SPI

此处也需要注意配置FPGA的加载模式,如果此处的模式不正确,会出现FLASH数据正确写入,但是断电后仍无法启动的问题。

此处选择的Master_SPI模式。即FPGA上电后会自动去读挂载的FLASH,进行上电加载。

FLASH芯片手册

管脚说明


1、Chip Select(/CS)

片选信号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即可。

verilog 复制代码
//1. 仅命令型 (Instruction Only)

// - WREN (06h)  : 写使能

// - WRDI (04h)  : 写禁止

// - RSTEN (66h) : 复位使能

// - RST (99h)  : 复位

// - BE (60h/C7h): 全片擦除

// CS#  ˉ\________________________________________/ˉ

// CLK  ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\___

// MOSI  ==_<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>====

// MISO  ==============================================

命令+读数据类型

verilog 复制代码
// 2. 命令+读数据型 (Instruction + Read Data)
// - RDID (9Fh)  : 读取ID,读3字节
// - RDSR (05h)  : 读状态寄存器,读1字节
// - RDCR (35h)  : 读配置寄存器,读1字节
// - RDFSR (70h) : 读标志状态寄存器,读1字节
// CS#   ˉ\____________________________________________________________________________________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__...__/ˉ\____
// MOSI  ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>_<Z>__<Z>__<Z>__<Z>_<Z>__<Z>__<Z>__<Z>_<Z>__<Z>__<Z>__<Z>_<Z>__<Z>__
// MISO  ===========================================<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D1>_...<=======
//       |<--------------命令阶段-------------------->|<--------------读第1字节-------------->|<----------------读第N字节--------------->|
// 时序举例(RDID-9Fh):
// CS#       ˉ\_________________________________________________________________________________________________________________________________________________________________/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\_________
// MOSI(9F)  ___/ˉ\____________/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\_________________________________________________________________________________________________________________________________
// MISO  ==============================================<===============MF======================><====================ID================><================DC===================>--------
// 注意事项:
// 1. MOSI在命令阶段发送指令,之后保持高阻态
// 2. MISO在命令阶段之后开始输出有效数据
// 3. 读取多个字节时连续进行
// 4. 每个字节都是MSB优先传输

命令+地址类型

verilog 复制代码
// 3. 命令+地址型 (Instruction + Address)
// - SE (D8h)    : 扇区擦除,3字节地址
// - BE32K (52h) : 32KB块擦除,3字节地址
// 命令阶段(8 clk) + 地址阶段(24 clk)
// CS#       ˉ\_________________________________________________________________________________________________________________________________________________________________/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\_________
// MOSI      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>==========
//           |<------------命令阶段------------------->|<-----------------------------------------------------地址阶段(24位)------------------------------------------------------------->|
// MISO      ============================================================================================================================================================================
// 时序举例(扇区擦除-SE-D8h):
// CS#   ˉ\________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__...__/ˉ\____/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__
// MOSI  ===<D>==<8>__...===========<扇区地址(24位)>=============
// MISO  =======================================================
//      | 扇区擦除指令 + 24位扇区地址             |
// 注意事项:
// 1. 命令阶段发送8位指令码
// 2. 地址阶段发送24位地址(A23-A0)
// 3. 地址按MSB优先发送
// 4. MISO在整个过程保持高阻态

命令+地址+读数据

verilog 复制代码
// 4. 命令+地址+读数据型 (Instruction + Address + Read Data)
// - READ (03h)  : 读数据,3字节地址
// - DREAD (3Bh) : 双输出读,3字节地址
// - QREAD (6Bh) : 四输出读,3字节地址
// 命令阶段(8 clk) + 地址阶段(24 clk) + 读数据阶段(8*N clk)
// CS#       ˉ\___________________________________________________________________________________________________________________________________________________________________________________________________________________________________..........______/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__.............................................
// MOSI      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>======================================================================================
// MISO      ===================================================================================================================================================================<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>.......
//           |<-----------------命令----------------->|<----------------------------------------------地址-------------------------------------------------------------------->|<---------------读第1字节---------------->|<--读第N字节-->|
// 注意事项:
// 1. 命令阶段发送8位指令码
// 2. 地址阶段发送24位地址(A23-A0)
// 3. MOSI在地址发送后保持高阻态
// 4. MISO从地址后开始输出有效数据
// 5. 所有传输都是MSB优先
// 时序举例(READ-03h):
// CS#   ˉ\_________________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__...__/ˉ\____/ˉ\__/ˉ\__...__/ˉ\__/ˉ\__...__/ˉ\_
// MOSI  ==<0><3><地址[23:0]>======================================
// MISO  ================================<数据字节流>================
//           |    读取指令   |        |      连续读取数据          |

命令+地址+空时钟+读数据

verilog 复制代码
// 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      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>========================================================================================================
// MISO      ==========================================================================================================================================================================================================<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>...........................
//           |<-----------------命令----------------->|<----------------------------------------------地址-------------------------------------------------------------------->|<---------------Dummy_clock------------->|<---------------读第1字节----------->|<---------------读第N字节---------------->|
// 注意事项:
// 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  ==<0><B><地址[23:0]>============================================
// MISO  ================================================<数据字节流>======
//           |  快速读指令  |  24位地址  |  8空周期  |    连续读取数据    |

命令+地址+写数据

verilog 复制代码
// 6. 命令+地址+写数据型 (Instruction + Address + Write Data)
// - PP (02h)    : 页编程,3字节地址+数据(1-256字节)
// - DPP (A2h)   : 双输入页编程
// - QPP (32h)   : 四输入页编程
// 命令阶段(8 clk) + 地址阶段(24 clk) + 写数据阶段(8*N clk)
// CS#       ˉ\_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________..........______/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__.............................................
// MOSI      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>==============================================
// MISO      ==========================================================================================================================================================================================================================================================.......
//           |<-----------------命令----------------->|<----------------------------------------------写地址(3Bytes Mode)----------------------------------------------------------->|<---------------写第1字节---------------->|<--写第N字节-->|
// 时序举例(PP-02h):
// CS#   ˉ\_________________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__...__/ˉ\____/ˉ\__/ˉ\__...__/ˉ\__/ˉ\__...__/ˉ\_
// MOSI  ==<0><2><地址[23:0]><数据字节流>===========================
// MISO  ===========================================================
//           |  页编程  | 24位地址 |       最多256字节数据         |

命令+写配置

verilog 复制代码
// 7. 命令+写配置型 (Instruction + Write Data)
// - WRSR (01h)  : 写状态寄存器,写1字节
// - WRCR (3Eh)  : 写配置寄存器,写1字节
// 命令阶段(8 clk) + 配置数据(8 clk)
// CS#       ˉ\_________________________________________________________________________________/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__
// MOSI      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<R7>_<R6>_<R5>_<R4>_<R3>_<R2>_<R1>_<R0>_==
//           |<------------命令阶段------------------->|<-----------地址阶段(24位)--------------->|
// 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的数据都会对照验证,后续可能移除。此处的读写调用的是同一个驱动。

顶层模块

verilog 复制代码
//输入输出说明
//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交互总线
verilog 复制代码
//
// 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_EN

module 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    : ila_flash_monitor
// // Note      : 信号监视
// /
// ila_flash_monitor u_ila_flash_monitor
// (
//     .clk                                (clk_50m                  ),
//     .probe0                             (cpu_cs                    ),//1
//     .probe1                             (cpu_we                    ),//1
//     .probe2                             (cpu_rd                    ),//1
//     .probe3                             (cpu_addr                  ),//8
//     .probe4                             (cpu_wdata                 ),//32
//     .probe5                             (cpu_wdata                 ),//32
//     .probe6                             (write_data                ),//16
//     .probe7                             (write_vld                 ),//1
//     .probe8                             (FLASH_ID                  ),//24
//     .probe9                             (flash_boot_done           ),//1 
//     .probe10                            (mnc_start_boot            ),//1 
//     .probe11                            (mnc_boot_length           ),//32
//     .probe12                            (almost_full               ),//1
//     .probe13                            (fifo_empty                ),//1
//     .probe14                            (write_rdy                 ),//1
//     .probe15                            (flash_data_ready          ),//1
//     .probe16                            (count_length              ),//32
//     .probe17                            (flash_read_en             ),//1
//     .probe18                            (o_spi_cs_n                ),//1
//     .probe19                            (o_spi_clk                 ),//1
//     .probe20                            (o_spi_mosi                ),//1
//     .probe21                            (i_spi_miso                ),//1
//     .probe22                            (boot_in_process           ), //1
//     .probe23                            (wr_en                     ), //1
//     .probe24                            (write_data                ), //8
//     .probe25                            (dummy_read_en             ), //1
//     .probe26                            (fifo_read_en              ), //1
//     .probe27                            (flash_data_out            )  //8
// );

/
// Module    : vio_0
// Note      : use vio control start_start simulation flash boot start
/
// wire                                     vio_start_test                  ;
// vio_trigger vio_start
// (
//     .clk                                (clk_50m                   ),// input wire clk
//     .probe_out0                         (vio_start_test            ) // output wire [0 : 0] probe_out0
// );

//     wire               [31: 0]        vio_boot_length             ;
// vio_cntr u_vio_cntr
// (
//     .clk                                (clk_50m                   ),// input wire clk
//     .probe_out0                         (vio_boot_length           ) // output wire [0 : 0] probe_out0
// );



/
// Module    : mnc_flash
// Note      : CPU关于flash的配置解析
/
    wire                                mnc_start_boot              ;
    wire               [  31: 0]        mnc_boot_length             ;

    mnc_flash # (
        .U_DLY                              (U_DLY                     ),
        .CPU_ADDR_W                         (CPU_ADDR_W                ) 
    )
    mnc_flash_inst (
        .clk                                (clk_100m                  ),//input
        .rst                                (hard_rst                  ),//input
        .cpu_cs                             (cpu_cs                    ),//input                    
        .cpu_we                             (cpu_we                    ),//input                    
        .cpu_rd                             (cpu_rd                    ),//input                    
        .cpu_addr                           (cpu_addr                  ),//input  [7:0]
        .cpu_wdata                          (cpu_wdata                 ),//input  [31: 0]         
        .cpu_rdata                          (cpu_rdata                 ),//output [31: 0]         
        //input
        .flash_id                           ( FLASH_ID                 ),//input  [7:0]
        .flash_boot_finish                  ( flash_boot_done          ),//input  
        //output
        .mnc_start_boot                     ( mnc_start_boot           ),//output  
        .mnc_boot_length                    ( mnc_boot_length          ) //output [31: 0] 
    );


/
// Module    : pcie_to_mefc_patch
// Note      : ddr数据位宽转换,512---->16(异步时钟)
/
    wire             [15:0]               write_data                 ;
    wire                                  write_vld                  ;
    wire                                  write_rdy                  ;

    pcie_to_mefc_patch u_pcie_to_mefc_patch
    (
        .clk                                (clk_100m                  ),//input
        .rst                                (sys_rst                   ),//input

        .ddr_out_data                       (ddr_flash_data            ),//input        [512-1:0]
        .ddr_out_vld                        (ddr_flash_out_vld         ),//input                           
        .ddr_out_rdy                        (ddr_flash_out_rdy         ),//output                          

        .mefc_odata                         (write_data                ),//output       [16-1:0]
        .mefc_ovld                          (write_vld                 ),//output  reg                      
        .mefc_ordy                          (write_rdy                 ) //input                            
    );


/
// Module    : flash_data_fifo_control
// Note      : 使用fifo缓冲数据
/
    wire                              flash_read_en               ;
    wire               [ 7: 0]        flash_data_out              ;
    wire                              fifo_full                   ;
    wire                              fifo_empty                  ;
    wire                              almost_full                 ;
    wire               [13: 0]        rd_data_count               ;
    wire               [12: 0]        wr_data_count               ;
    reg                [31: 0]        count_length                ;
    wire                              wr_rst_busy                 ;
    wire                              rd_rst_busy                 ;
    wire                              flash_data_ready            ;
    wire                              fifo_reset                  ;
    wire                              wr_en                       ;
    reg                               dummy_read_en               ;
    wire                              fifo_read_en                ;

    always @(posedge clk_100m) 
    begin
        if(mnc_start_boot)begin
            count_length <= 32'b0;
        end
        else begin
            if(write_rdy )begin
                count_length <= count_length + 1;  
            end
        end
    end

    assign                              write_rdy                   = (~almost_full && count_length <= mnc_boot_length) ?  1'b1 : 1'b0;
    assign                              flash_data_ready            = fifo_empty ? 1'b0 : 1'b1;
    assign                              fifo_reset                  = mnc_start_boot;
    assign                              wr_en                       = write_vld & write_rdy;

    fifo_generator_0 fifo_buffer_inst (
        .rst                                (fifo_reset                ),// input wire rst
        .wr_clk                             (clk_100m                  ),// input wire wr_clk
        .rd_clk                             (clk_50m                   ),// input wire rd_clk
        .din                                (write_data                ),// input wire [15 : 0] din
        .wr_en                              (wr_en                     ),// input wire wr_en
        .rd_en                              (flash_read_en             ),// input wire rd_en
        .dout                               (flash_data_out            ),// output wire [7 : 0] dout
        .full                               (fifo_full                 ),// output wire full
        .almost_full                        (almost_full               ),// output wire almost_full
        .empty                              (fifo_empty                ),// output wire empty
        .rd_data_count                      (rd_data_count             ),// output wire [13 : 0] rd_data_count
        .wr_data_count                      (wr_data_count             ),// output wire [12 : 0] wr_data_count
        .wr_rst_busy                        (                          ),// output wire wr_rst_busy
        .rd_rst_busy                        (                          ) // output wire rd_rst_busy
    );

/
// Module    : qspi_top
// Note      : flash升级流程控制单元
/
    wire                                flash_boot_done             ;
    wire                                flash_boot_error            ;
    wire               [  23: 0]        FLASH_ID                    ;
    wire                                start_boot                  ;
    wire                                boot_in_process             ;

    qspi_top  qspi_top_inst
    (
        .i_clk_100m                         (clk_100m                  ),//input
        .i_clk_50m                          (clk_50m                   ),//input
        .i_rst_n                            (~hard_rst                 ),//input
        .i_start_boot                       (mnc_start_boot            ),//input
        .i_flash_data_ready                 (flash_data_ready          ),//input
        .o_spi_cs_n                         (o_spi_cs_n_w              ),//output
        .o_spi_clk                          (o_spi_clk_w               ),//output
        .o_spi_mosi                         (o_spi_mosi_w              ),//output
        .i_spi_miso                         (i_spi_miso                ),//input 
        .i_flash_data_length                (mnc_boot_length           ),//input  [31:0]
        .i_flash_data                       (flash_data_out            ),//input  [7:0] 
        .o_flash_data_en                    (flash_read_en             ),//output 
        .o_boot_in_process                  (boot_in_process           ),//output 
        .FLASH_ID                           (FLASH_ID                  ),//output [23:0]
        .o_test_done                        (flash_boot_done           ),//output      
        .o_test_error                       (flash_boot_error          ),//output      
        .o_error_code                       (                          ),//output [7:0]
        .o_sector_cnt                       (                          ),//output [8:0]
        .o_page_cnt                         (                          ) //output [7:0]
    );

/
// Module    : qspi_read_monitor
// Note      : 操控fpga回读flash的数据
/
    wire                              o_spi_cs_n_w                  ;
    wire                              o_spi_clk_w                   ;
    wire                              o_spi_mosi_w                  ;
    wire                              o_spi_cs_n_r                  ;
    wire                              o_spi_clk_r                   ;
    wire                              o_spi_mosi_r                  ;

    `ifdef FLASH_READ_EN
        qspi_read_monitor  qspi_read_monitor_inst (
            .i_clk_100m                         (clk_100m                  ),
            .i_clk_50m                          (clk_50m                   ),
            .i_rst_n                            (~hard_rst                 ),

            .o_spi_cs_n                         (o_spi_cs_n_r              ),//output
            .o_spi_clk                          (o_spi_clk_r               ),//output
            .o_spi_mosi                         (o_spi_mosi_r              ),//output
            .i_spi_miso                         (i_spi_miso                ) //input 
        );
    `else
        assign                              o_spi_cs_n_r                = 1'b0;
        assign                              o_spi_clk_r                 = 1'b0;
        assign                              o_spi_mosi_r                = 1'b0;
    `endif


    assign o_spi_cs_n =  boot_in_process ? o_spi_cs_n_w : o_spi_cs_n_r;
    assign o_spi_clk  =  boot_in_process ? o_spi_clk_w : o_spi_clk_r;
    assign o_spi_mosi =  boot_in_process ? o_spi_mosi_w : o_spi_mosi_r;


/
// Module    : flash_clk_convert
// Note      : flash的SPI时钟必须转化
/
    STARTUPE2 #(
        .PROG_USR                           ("FALSE"                   ),// Activate program event security feature. Requires encrypted bitstreams.
        .SIM_CCLK_FREQ                      (0.0                       ) // Set the Configuration Clock Frequency(ns) for simulation.
        )
        STARTUPE2_inst (
        .CFGCLK                             (                          ),// 1-bit output: Configuration main clock output
        .CFGMCLK                            (                          ),// 1-bit output: Configuration internal oscillator clock output
        .EOS                                (eos                       ),// 1-bit output: Active high output signal indicating the End Of Startup.
        .PREQ                               (preq                      ),// 1-bit output: PROGRAM request to fabric output
        .CLK                                (1'b0                      ),// 1-bit input: User start-up clock input
        .GSR                                (1'b0                      ),// 1-bit input: Global Set/Reset input (GSR cannot be used for the port name)
        .GTS                                (1'b0                      ),// 1-bit input: Global 3-state input (GTS cannot be used for the port name)
        .KEYCLEARB                          (1'b1                      ),// 1-bit input: Clear AES Decrypter Key input from Battery-Backed RAM (BBRAM)
        .PACK                               (1'b1                      ),// 1-bit input: PROGRAM acknowledge input
        .USRCCLKO                           (o_spi_clk                 ),// 1-bit input: User CCLK input
        .USRCCLKTS                          (1'b0                      ),// 1-bit input: User CCLK 3-state enable input
        .USRDONEO                           (1'b1                      ),// 1-bit input: User DONE pin output control
        .USRDONETS                          (1'b1                      ) // 1-bit input: User DONE 3-state enable output
    );


endmodule

mnc_flash

CPU配置解析单元,所需要的CPU配置指令只有两个,即开始固化升级、固化的数据字节长度,给CPU的反馈只有一个信号即固化成功,如果需要别的状态反馈也可以自己添加。

基地址
起始地址 结束地址 容量 模块名称 片选 内容说明
0x400 0x4FF 1024byte u_mefc_top 4 mefc_cs nor_flash模块
偏移地址说明
复制代码
//              1;flash新修改cpu读写版本,原本寄存器偏移地址到0x20,保持0x20之前的寄存器定义不做任何修改,后续新增寄存器在此基础上往后加
//              2;新修改增加 0x21  的bit0,  启动flash固化     (WR/RD)
//              3;          0x22 的bit31, 返回flash ID     (WR/RD)
//              4;          0x23          固化的文件字节数   (WR/RD)
//              5;          0x24 的bit0,  返回固化是否完成   (WR/RD) 
FLASH固化启动(0x21)

寄存器数据结构如下表所示。

地址 名称 简述 默认值 类型 备注
0x21 0 mnc_start_boot f'lash固化启动 0x0 W 先置1再置0后FPGA内部开始初始化flash芯片固化流程
读FLASH芯片ID(0x22)

寄存器数据结构如下表所示。

地址 名称 简述 默认值 类型 备注
0x22 [23:0] mnc_flash_id 返回flash_id 0x0 R GD25LQ256 :C86019MT25QU256 :20BA19
FLASH固化的字节数(0x23)

寄存器数据结构如下表所示。

地址 名称 简述 默认值 类型 备注
0x23 [31:0] mnc_boot_length 固化程序的字节数 0x0 R/W 在固化配置开始前要给FPGA配置一下固化文件的字节数目,当前flash的最大容量为32M,所以最大字节数目为33554431
FLASH固化完成状态

寄存器数据结构如下表所示。

地址 名称 简述 默认值 类型 备注
0x24 [0] mnc_boot_done 返回固化完成的状态 0x0 R 未完成时置0;完成时置1;
CPU总线解析
verilog 复制代码
//
// FileName    : 
// Author      : 
// Creat Time  : 2025-04-17
// Encode Save : UTF-8
// Device      : 
// Design      : 
// Note        : 
//              1;flash新修改cpu读写版本,原本寄存器偏移地址到0x20,保持0x20之前的寄存器定义不做任何修改,后续新增寄存器在此基础上往后加
//              2;新修改增加 0x21 的bit0,  启动flash固化     (WR/RD)
//              3;          0x22 的bit31, 返回flash ID      (WR/RD)
//              4;          0x23          固化的文件字节数   (WR/RD)
//              5;          0x24 的bit0,  返回固化是否完成   (WR/RD) 

// CopyRight(c) 2016, Chengdu universal_data Technology Co. Ltd.
//

`timescale 1ns / 1ps

module mnc_flash
#(
    parameter                                    U_DLY = 1,
    parameter                                    CPU_ADDR_W = 8
)
(
    input                                        clk                        ,
    input                                        rst                        ,
    //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 reg         [  31: 0]                 cpu_rdata                  ,
    //input
    input              [  23:0 ]                 flash_id,
    input                                        flash_boot_finish,
    //output
    output                                       mnc_start_boot,
    output             [  31: 0]                 mnc_boot_length              //字节数
);

reg [31:0] reg400_00;
reg [31:0] reg400_01;
reg [31:0] reg400_02;
reg [31:0] reg400_03;
reg [31:0] reg400_04;
reg [31:0] reg400_05;
reg [31:0] reg400_06;
reg [31:0] reg400_07;
reg [31:0] reg400_08;
reg [31:0] reg400_09;
reg [31:0] reg400_0A;
reg [31:0] reg400_0B;
reg [31:0] reg400_0C;
reg [31:0] reg400_10;
reg [31:0] reg400_11;
reg [31:0] reg400_12;
reg [31:0] reg400_13;
reg [31:0] reg400_18;
reg [31:0] reg400_20;
reg [31:0] reg400_21;
reg [31:0] reg400_22;
reg [31:0] reg400_23;
reg [31:0] reg400_24;

reg cpu_we_dly;
reg cpu_rd_dly;

assign mnc_start_boot  = reg400_21[0];
assign mnc_boot_length = reg400_23;

always @(posedge clk or posedge rst)
begin
    if(rst == 1'b1)
        begin
            cpu_we_dly <= 1'b1;
            cpu_rd_dly <= 1'b1;
        end
    else
        begin
            cpu_we_dly <= cpu_we;
            cpu_rd_dly <= cpu_rd;
        end
end

always @(posedge clk or posedge rst)
begin
    if(rst == 1'b1) begin
            reg400_00 <= 32'h0000_0000;
            reg400_01 <= 32'h0000_0000;
            reg400_02 <= 32'h0000_0000;
            reg400_03 <= 32'h0000_0000;
            reg400_04 <= 32'h0000_0000;
            reg400_05 <= 32'h0000_0000;
            reg400_06 <= 32'h0000_0000;
            reg400_07 <= 32'h0000_0000;
            reg400_08 <= 32'h0000_0000;
            reg400_09 <= 32'h0000_0000;
            reg400_0A <= 32'h0000_0000;
            reg400_0B <= 32'h0000_0000;
            reg400_0C <= 32'h0000_0000;
            reg400_10 <= 32'h0000_0000;
            reg400_11 <= 32'h0000_0000;
            reg400_12 <= 32'h0000_0000;
            reg400_13 <= 32'h0000_0000;
            reg400_18 <= 32'h0000_0000;
            reg400_20 <= 32'h0000_0000;
            reg400_21 <= 32'h0000_0000;
            reg400_22 <= 32'h0000_0000;
            reg400_23 <= 32'h0000_0000;
            reg400_24 <= 32'h0000_0000;
    end
    else begin
        if({cpu_we_dly,cpu_we} == 2'b10 && cpu_cs == 1'b0) begin
            case(cpu_addr)               
                8'h00 : reg400_00 <= cpu_wdata;     
                8'h01 : reg400_01 <= cpu_wdata;  
                8'h02 : reg400_02 <= cpu_wdata;                          
                8'h03 : reg400_03 <= cpu_wdata;                          
                8'h04 : reg400_04 <= cpu_wdata;                          
                8'h05 : reg400_05 <= cpu_wdata;                          
                8'h06 : reg400_06 <= cpu_wdata;                          
                8'h07 : reg400_07 <= cpu_wdata;                          
                8'h08 : reg400_08 <= cpu_wdata;                          
                8'h09 : reg400_09 <= cpu_wdata;                          
                8'h0A : reg400_0A <= cpu_wdata;                          
                8'h0B : reg400_0B <= cpu_wdata;                          
                8'h0C : reg400_0C <= cpu_wdata;                          
                8'h10 : reg400_10 <= cpu_wdata;                          
                8'h11 : reg400_11 <= cpu_wdata;                          
                8'h12 : reg400_12 <= cpu_wdata;                          
                8'h13 : reg400_13 <= cpu_wdata;                          
                8'h18 : reg400_18 <= cpu_wdata;                          
                8'h20 : reg400_20 <= cpu_wdata;                          
                8'h21 : reg400_21 <= cpu_wdata; 
                8'h22 : reg400_22 <= cpu_wdata; 
                8'h23 : reg400_23 <= cpu_wdata; 
                8'h24 : reg400_24 <= cpu_wdata; 
                default:;
            endcase
        end
    end
end


always @(posedge clk or posedge rst)
begin
    if(rst == 1'b1)
        cpu_rdata <= 'd0;
    else
        begin
            if({cpu_rd_dly,cpu_rd} == 2'b10 && cpu_cs == 1'b0)
                begin
                    case(cpu_addr)
                        8'h00   :  cpu_rdata <= reg400_00;     
                        8'h01   :  cpu_rdata <= reg400_01;  
                        8'h02   :  cpu_rdata <= reg400_02;                          
                        8'h03   :  cpu_rdata <= reg400_03;                          
                        8'h04   :  cpu_rdata <= reg400_04;                          
                        8'h05   :  cpu_rdata <= reg400_05;                          
                        8'h06   :  cpu_rdata <= reg400_06;                          
                        8'h07   :  cpu_rdata <= reg400_07;                          
                        8'h08   :  cpu_rdata <= reg400_08;                          
                        8'h09   :  cpu_rdata <= reg400_09;                          
                        8'h0A   :  cpu_rdata <= reg400_0A;                          
                        8'h0B   :  cpu_rdata <= reg400_0B;                          
                        8'h0C   :  cpu_rdata <= reg400_0C;                          
                        8'h10   :  cpu_rdata <= reg400_10;                          
                        8'h11   :  cpu_rdata <= reg400_11;                          
                        8'h12   :  cpu_rdata <= reg400_12;                          
                        8'h13   :  cpu_rdata <= reg400_13;                          
                        8'h18   :  cpu_rdata <= reg400_18;                          
                        8'h20   :  cpu_rdata <= reg400_20;   
                        8'h21   :  cpu_rdata <= reg400_21;                          
                        8'h22   :  cpu_rdata <= {reg400_22[31:24] ,flash_id};                          
                        8'h23   :  cpu_rdata <= reg400_23;                          
                        8'h24   :  cpu_rdata <= {reg400_24[31:1] ,flash_boot_finish};                          
                        default :  cpu_rdata <= 32'd0;
                    endcase
                end
        end
end
                                                                   
endmodule

升级数据的位宽与速率控制

当前我的数据文件来自PCIE接口传输过来,位宽为512bit,时钟速度为100M,因为每个项目的数据传输方式与位宽可能都不相同,需要根据自己的项目来实际修改代码。

但是后面的驱动关于数据写入是8bit的,所以这里将数据位宽由100M的512bit缓冲到50M的8bit。

这里的操作一方面是位宽转换,一方面是数据流速控制。使用一个fifo控制即可。

数据文件的写入(大小端问题)

此处我已经经过读写验证,是大端数据写入。

如下截图中需要升级的bin文件,则进行page_program是写入数据的格式即(无需数据颠倒):

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000BB11220044FFFFFFFFFFFFFFFFAA995566200000003003E001000000003000800100000012200000002000000030022001000000003002000100000000300080010000000020000000300080010000000720000000200000003001A001E23339B4300020010000000030026001000000003001200138103FE53001C001004000003001800104B510933000800100000009200000003000C001000000013000A001000001013000C00100001000300300010000100020000000

固化升级控制单元

固化流程设计

​ 1;上位机发送开始固化使能,使能拉高时清空所有信号量,状态机处于IDLE状态

​ 2;检测到固化使能的下降沿,开始读芯片的ID,命令码为9F,数据长度3字节,并发送qspi_start驱动产生QSPI时序,

​ 3; 当读芯片的QSPI时序产生完成,qspi_driver驱动层会产生一个qspi_done信号反馈,检测到qspi_done后开始跳转下一个状态,打开写使能(06H)

​ 4;打开写使能的QSPI时序产生完成,检测到qspi_done后开始跳转下一个状态,打开4地址字节模式(B7H)

​ 5; 4字节模式时序发送完成,检测到qspi_done后开始跳转下一个状态,64K数据擦除,因为数据是按页写入,256次page_program才可以完成64K的数据写入,所以无需在每次page_program前都进行一次擦除操作,只需要再开始时擦除一次即可。

​ 6;64K的数据擦除需要一定的时间,在此期间我们不能进行写操作,但是读芯片的状态寄存器确是可以随时进行,所以在擦除状态时,我们要一直轮询的去读芯片的状态寄存器(35H,05H)

​ 7; 只有当05H的bit0为0时,才表明擦除状态完成,可以进行写page_program,在page_program前需要再拉高一次写使能(06H)

​ 8; 写使能拉高后,进行page_program。

​ 9;page_program完成后,地址计数器(current_addr)加0x100,因为写入的数据时256字节,然后跳转到下一页数据写入。

​ 10;轮询上述过程,知道地址大于CPU配置的字节长度。

​ 11;补充:查看状态寄存器是一个只读命令,在命令码发送完成后返回回读数据(qspi_read_data)和使能(qspi_read_valid),该使能的产生在qspi_done之前

​ 12;补充:

​ 开始固化 -----> 读芯片ID(9FH) -----> 打开写使能1(06H) -----> 打开4字节模式(B7H) ----->

​ 芯片擦除(D8H) -----> 读擦除状态(35H/05H) -----> 页写入(02H) -----> 查询页写入是否完成(05H) -----> ...(轮询)

State machines
程序设计
verilog 复制代码
//
// FileName    : 
// Author      : 
// Creat Time  : 2025-04-07
// Encode Save : UTF-8
// Device      : 
// Design      : 
// Note        : 
                //固化流程设计:
                //1;上位机发送开始固化使能,使能拉高时清空所有信号量,状态机处于IDLE状态
                //2;检测到固化使能的下降沿,开始读芯片的ID,命令码为9F,数据长度3字节,并发送qspi_start驱动产生QSPI时序,
                //3; 当读芯片的QSPI时序产生完成,qspi_driver驱动层会产生一个qspi_done信号反馈,检测到qspi_done后开始跳转下一个状态,打开写使能(06H)
                //4;打开写使能的QSPI时序产生完成,检测到qspi_done后开始跳转下一个状态,打开4地址字节模式(B7H)
                //5; 4字节模式时序发送完成,检测到qspi_done后开始跳转下一个状态,64K数据擦除,因为数据是按页写入,256次page_program才可以完成64K的数据写入,所以无需在每次page_program前都进行一次擦除操作,只需要再开始时擦除一次即可。
                //6;64K的数据擦除需要一定的时间,在此期间我们不能进行写操作,但是读芯片的状态寄存器确是可以随时进行,所以在擦除状态时,我们要一直轮询的去读芯片的状态寄存器(35H,05H)
                //7; 只有当05H的bit0为0时,才表明擦除状态完成,可以进行写page_program,在page_program前需要再拉高一次写使能(06H)
                //8; 写使能拉高后,进行page_program。
                //9;page_program完成后,地址计数器(current_addr)加0x100,因为写入的数据时256字节,然后跳转到下一页数据写入。
                //10;轮询上述过程,知道地址大于CPU配置的字节长度。
                //11;补充:查看状态寄存器是一个只读命令,在命令码发送完成后返回回读数据(qspi_read_data)和使能(qspi_read_valid),该使能的产生在qspi_done之前
                //12;补充:
                // 开始固化          ----->   读芯片ID(9FH)         ----->  打开写使能1(06H)  ----->  打开4字节模式(B7H)       ----->
                // 芯片擦除(D8H)    ----->   读擦除状态(35H/05H)   ----->   页写入(02H)      ----->  查询页写入是否完成(05H)   ----->    .........(轮询)  


// CopyRight(c) 2016, Chengdu universal_data Technology Co. Ltd.
//



`timescale 1ns / 1ps
module qspi_top (
    input  wire                         i_clk_100m                 ,// 100MHz系统时钟
    input  wire                         i_clk_50m                  ,// 50MHz系统时钟
    input  wire                         i_rst_n                    ,// 低电平复位
    input  wire                         i_start_boot               ,// 测试启动触发
    input  wire                         i_flash_data_ready         ,// flash固化数据准备完成
    
    // QSPI接口
    output wire                         o_spi_cs_n                 ,
    output wire                         o_spi_clk                  ,
    output wire                         o_spi_mosi                 ,
    input  wire                         i_spi_miso                 ,

    //flash_data
    input  wire        [  31: 0]        i_flash_data_length        ,
    input  wire        [   7: 0]        i_flash_data               ,
    output wire                         o_flash_data_en            ,
    output reg                          o_boot_in_process          ,
    
    // 测试状态输出
    output reg         [  23: 0]        FLASH_ID                   ,// 测试完成标志
    output reg                          o_test_done                ,// 测试完成标志
    output reg                          o_test_error               ,// 测试错误标志
    output reg         [   7: 0]        o_error_code               ,// 错误代码
    output reg         [   8: 0]        o_sector_cnt               ,// 当前扇区计数
    output reg         [   7: 0]        o_page_cnt                  // 当前页计数
);

    // 测试数据生成和验证
    wire                                data_gen_en                 ;// 数据生成使能
    wire               [   7: 0]        test_data                   ;// 测试数据
    reg                [   7: 0]        verify_data                 ;// 验证数据
    reg                                 data_error                  ;// 数据错误标志
    reg                [   8: 0]        sector_cnt                  ;// 扇区计数器 (0-511)
    reg                [   7: 0]        page_cnt                    ;// 页计数器 (0-255)
    reg                [  31: 0]        current_addr                ;// 当前写入地址

    // QSPI驱动接口信号
    reg                                 qspi_start                  ;
    reg                [   7: 0]        qspi_cmd                    ;
    reg                [  31: 0]        qspi_addr                   ;
    wire                                qspi_write_allow            ;
    reg                [   7: 0]        qspi_write_data             ;
    reg                [   8: 0]        qspi_byte_cnt               ;
    wire                                qspi_busy                   ;
    wire                                qspi_done                   ;
    wire                                qspi_read_valid             ;
    wire               [   7: 0]        qspi_read_data              ;
    // states
    reg                [   4: 0]        boot_states                 ;//主状态机
    reg                [  23: 0]        flash_id                    ;// 存储读取的Flash ID
    reg                [   7: 0]        status_reg                  ;// 状态寄存器值

    // 状态机状态定义
    localparam                          ST_IDLE                    = 5'd0  ;
    localparam                          ST_READ_ID                 = 5'd1  ;
    localparam                          ST_READ_REGSTATES1         = 5'd2  ;
    localparam                          ST_READ_REGSTATES0         = 5'd3  ;     
    localparam                          ST_WRITE_ENABLE            = 5'd4  ;
    localparam                          ST_EN4B_ADDR               = 5'd5  ;
    localparam                          ST_WRITE_REGSTATE          = 5'd6  ;
    localparam                          ST_CHIP_ERASE              = 5'd7  ;           
    localparam                          ST_READ_REGSTATES1_AG      = 5'd8  ;
    localparam                          ST_READ_REGSTATES0_AG      = 5'd9  ;
    localparam                          ST_PAGE_WRITE_ENABLE       = 5'd10 ;
    localparam                          ST_PAGE_PROGRAM            = 5'd11 ;
    localparam                          ST_CHECK_PROGRAM           = 5'd12 ;
    localparam                          ST_READ_DATA               = 5'd13 ;
    localparam                          ST_CHECK_DATA              = 5'd14 ;
    localparam                          ST_NEXT_PAGE               = 5'd15 ;
    localparam                          ST_NEXT_SECTOR             = 5'd16 ;
    localparam                          ST_TEST_DONE               = 5'd17 ;
    localparam                          ST_ERROR                   = 5'd18 ;
    // 错误码定义
    localparam                          ERR_WRITE_DISABLE          = 8'h01 ;  // 写禁止失败
    localparam                          ERR_WRITE_ENABLE           = 8'h02 ;  // 写使能失败
    localparam                          ERR_DATA_VERIFY            = 8'h03 ;  // 数据验证失败
    // reset
    wire                                vio_rst_n                   ;
    wire                                reset_n                     ;


    /
    // Module    : Test_data
    // Note      : 累加计数器
    /
    // 测试数据生成模块
    // always @(posedge i_clk_50m or negedge reset_n) begin
    //     if (!reset_n) begin
    //         test_data <= 8'd0;
    //     end
    //     else if (data_gen_en ) begin  // 简化条件,确保连续
    //         test_data <= test_data + 1'b1;
    //     end
    //     else if (boot_states == ST_WRITE_ENABLE) begin
    //         test_data <= 8'd0;       // 重置
    //     end
    // end
    // assign data_gen_en = ( boot_states == ST_PAGE_PROGRAM) ? qspi_write_allow : 1'b0;
    
    assign o_flash_data_en = ( boot_states == ST_PAGE_PROGRAM) ? qspi_write_allow : 1'b0;
    
    assign test_data = i_flash_data;


    /
    // Module    : reset
    // Note      : 
    /
    // vio_0 vio_reset (
    //     .clk                                (i_clk_50m             ),// input wire clk
    //     .probe_out0                         (vio_rst_n             ) // output wire [0 : 0] probe_out0
    // );
    assign  reset_n = i_rst_n;
    reg i_start_boot_d1;
    always @(posedge i_clk_50m or negedge reset_n) 
    begin
        if(!reset_n)begin
            i_start_boot_d1 <= 1'b0;
        end
        else begin
            i_start_boot_d1 <= i_start_boot;
        end

    end

    /
    // Module    : 固化流程主控状态机
    // Note      : 
    /
    reg [7:0] read_state1;
    reg [7:0] read_state0;
    reg [15:0] reg_states;
    reg [15:0] error_cnt;
    reg [2:0] chip_protect_mode;
    reg addr_4byte_en;
    reg chip_cmp;

    // 状态机
    always @(posedge i_clk_50m or negedge reset_n) begin
        if (!reset_n ) begin
            boot_states <= ST_IDLE;
            qspi_start <= 1'b0;
            qspi_cmd <= 8'h00;
            qspi_addr <= 32'h000000;
            qspi_byte_cnt <= 9'd0;
            sector_cnt <= 9'd0;
            page_cnt <= 8'd0;
            current_addr <= 32'd0;
            o_test_done <= 1'b0;
            o_test_error <= 1'b0;
            o_error_code <= 8'h00;
            o_sector_cnt <= 9'd0;
            o_page_cnt <= 8'd0;
            flash_id <= 24'd0;
            reg_states <= 16'd0;
            o_boot_in_process <= 1'b0;

        end
        else begin
            if (i_start_boot_d1) begin
                boot_states <= ST_IDLE;
                qspi_start <= 1'b0;
                qspi_cmd   <= 8'h00;
                qspi_addr <= 32'h000000;
                qspi_byte_cnt <= 9'd0;
                sector_cnt <= 9'd0;
                page_cnt <= 8'd0;
                current_addr <= 32'd0;
                o_test_done <= 1'b0;
                o_test_error <= 1'b0;
                o_error_code <= 8'h00;
                o_sector_cnt <= 9'd0;
                o_page_cnt <= 8'd0;
                flash_id <= 24'd0;
                reg_states <= 16'd0;
                error_cnt <= 16'd0;
                o_boot_in_process <= 1'b0;

            end

            case (boot_states)
                ST_IDLE: begin
                    if (~i_start_boot && i_start_boot_d1 )
                    begin
                        boot_states <= ST_READ_ID;
                        qspi_cmd   <= 8'h9F;                        // 先读取芯片ID
                        qspi_byte_cnt <= 9'd3;                      // 读3字节ID
                        qspi_start <= 1'b1;
                        flash_id <= 24'd0;
                        reg_states <= 16'd0;
                        error_cnt <= 16'd0;
                        o_boot_in_process <= 1'b1;
                    end
                end

                ST_READ_ID : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin                            //获取芯片初始化状态,读芯片状态寄存器1
                        FLASH_ID    <= flash_id;
                        boot_states <= ST_WRITE_ENABLE;
                        qspi_cmd <= 8'h06;                          //打开写使能
                        qspi_byte_cnt <= 9'd0;
                        qspi_start <= 1'b1;
                    end
                    else if(qspi_read_valid )begin
                        flash_id <= {flash_id[23:8],qspi_read_data};
                    end
                end

                ST_WRITE_ENABLE : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        boot_states <= ST_EN4B_ADDR;                //打开4字节地址模式
                        qspi_cmd <= 8'hB7;                          
                        qspi_byte_cnt <= 9'd2;
                        qspi_start <= 1'b1;
                    end   

                end

                ST_EN4B_ADDR : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        if (page_cnt == 8'd0) begin
                            boot_states <= ST_CHIP_ERASE;                 //64K擦除
                            qspi_cmd <= 8'hD8;    
                            qspi_addr <= current_addr;
                            qspi_byte_cnt <= 9'd4;
                            qspi_start <= 1'b1;
                        end
                        else begin
                            boot_states <= ST_PAGE_PROGRAM;
                            qspi_cmd <= 8'h02;                            //进行页数据写入
                            qspi_addr <= current_addr;
                            qspi_byte_cnt <= 9'd256;
                            qspi_start <= 1'b1;
                        end
                    end
                end


                ST_CHIP_ERASE : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin                            //擦除完成后回读两个芯片状态寄存器,确保当前处于4地址字节模式,写保护关闭状态
                        boot_states <= ST_READ_REGSTATES1_AG;
                        qspi_cmd <= 8'h35;                          //芯片状态寄存器1
                        qspi_byte_cnt <= 9'd1;
                        qspi_start <= 1'b1;
                    end
                end
            

                ST_READ_REGSTATES1_AG : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        boot_states <= ST_READ_REGSTATES0_AG;          //芯片状态寄存器0      
                        qspi_cmd <= 8'h05;                  
                        qspi_byte_cnt <= 9'd1;
                        qspi_start <= 1'b1;
                    end
                    else if(qspi_read_valid )begin
                        read_state1   <= qspi_read_data;
                        addr_4byte_en <= qspi_read_data[3];
                        chip_cmp      <= qspi_read_data[6];
                    end
                end


                ST_READ_REGSTATES0_AG: begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        if(read_state0[0] == 1'b0)begin
                            boot_states <= ST_PAGE_WRITE_ENABLE;      
                            qspi_cmd <= 8'h06;                              //打开写使能
                            qspi_byte_cnt <= 9'd0;
                            qspi_start <= 1'b1;
                        end
                        else begin
                            boot_states    <= ST_READ_REGSTATES0_AG;          //芯片擦除未完成,继续读芯片状态寄存器0      
                            qspi_cmd      <= 8'h05;                  
                            qspi_byte_cnt <= 9'd1;
                            qspi_start    <= 1'b1;
                        end
                    end
                    else if(qspi_read_valid )begin
                        read_state0   <= qspi_read_data;
                        chip_protect_mode <= qspi_read_data[4:2];           //写保护控制,关闭写保护要置0
                    end
                end


                ST_PAGE_WRITE_ENABLE : begin                                //在每次PAGE_PROGRAM前都要打开一次写使能,因为flash在完成PAGE_PROGRAM会将写使能关闭
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        boot_states <= ST_PAGE_PROGRAM;
                        qspi_cmd <= 8'h02;                                  //进行页数据写入
                        qspi_addr <= current_addr;
                        qspi_byte_cnt <= 9'd256;
                        qspi_start <= 1'b1;
                    end
                end

                ST_PAGE_PROGRAM: begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin                                    //页数据写入完成,查询页数据写入完成状态
                        boot_states <= ST_CHECK_PROGRAM;                     
                        qspi_cmd <= 8'h05;                                  
                        qspi_byte_cnt <= 9'd1;
                        qspi_start <= 1'b1;
                    end
                    else if(qspi_write_allow )begin
                        qspi_write_data <=  test_data;
                    end
                end


                ST_CHECK_PROGRAM: begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        if (status_reg[0]  == 1'b0) begin                   //未处于BUSY状态,回读数据
                            boot_states <= ST_CHECK_DATA;
                        end
                        else begin
                            qspi_cmd <= 8'h05;                              // 数据写入未完成,继续查询编程状态
                            qspi_byte_cnt <= 9'd1;
                            qspi_start <= 1'b1;
                        end
                    end
                    else if(qspi_read_valid )begin
                        status_reg <= qspi_read_data;   
                    end
                end

                ST_CHECK_DATA: begin
                    if(current_addr >= i_flash_data_length)begin
                        boot_states <= ST_TEST_DONE;
                    end
                    else begin 
                        if (page_cnt == 8'd255) begin                       //256次Page_program可以写满一个sector区域,32M大小的flash由512个sector
                            boot_states <= ST_NEXT_SECTOR;
                            sector_cnt <= sector_cnt + 1'b1;
                            page_cnt <= 8'd0;
                            current_addr <= current_addr + 32'h100;
                            o_sector_cnt <= sector_cnt + 1'b1;
                        end
                        else begin
                            boot_states <= ST_NEXT_PAGE;
                            page_cnt <= page_cnt + 1'b1;
                            current_addr <= current_addr + 32'h100;            //Page_program每次至多写256Bytes,当缓冲的数据多于256Bytes则数据覆盖,少于256则填0    
                            o_page_cnt <= page_cnt + 1'b1;
                        end
                    end
                end


                ST_NEXT_PAGE: begin
                    boot_states <= ST_WRITE_ENABLE;
                    qspi_cmd <= 8'h06;
                    qspi_byte_cnt <= 9'd0;
                    qspi_start <= 1'b1;
                end


                ST_NEXT_SECTOR: begin
                    boot_states <= ST_WRITE_ENABLE;
                    qspi_cmd <= 8'h06;
                    qspi_byte_cnt <= 9'd0;
                    qspi_start <= 1'b1;
                end


                ST_TEST_DONE: begin
                    o_test_done <= 1'b1;    
                    o_boot_in_process <= 1'b0;                
                    if (i_start_boot == 1'b1) begin
                        boot_states <= ST_IDLE;
                        o_test_done <= 1'b0;
                        sector_cnt <= 9'd0;
                        page_cnt <= 8'd0;
                        current_addr <= 32'd0;
                        o_sector_cnt <= 9'd0;
                        o_page_cnt <= 8'd0;
                    end
                end


                ST_ERROR: begin
                    o_test_error <= 1'b1;
                    if (i_start_boot == 1'b0) begin
                        boot_states <= ST_IDLE;
                        o_test_error <= 1'b0;
                        o_error_code <= 8'h00;
                    end
                end

                default: boot_states <= ST_IDLE;
            endcase
        end
    end




    /
    // Module    : ila检测
    // Note      : 
    /
    // ila_0  u_ila_inst
    // (
    // .clk                                (i_clk_50m                ),
    // .probe0                             (i_start_boot              ),//1
    // .probe1                             (boot_states               ),//5
    // .probe2                             (qspi_start                ),//1
    // .probe3                             (qspi_cmd                  ),//8
    // .probe4                             (qspi_write_data           ),//8
    // .probe5                             (qspi_write_allow          ),//1
    // .probe6                             (o_spi_cs_n                ),//1
    // .probe7                             (o_spi_clk                 ),//1
    // .probe8                             (o_spi_mosi                ),//1
    // .probe9                             (i_spi_miso                ),//1
    // .probe10                            (o_test_done               ),//1
    // .probe11                            (current_addr              ),//32
    // .probe12                            (i_flash_data_ready        ),//1
    // .probe13                            (page_cnt                  ),//8
    // .probe14                            (sector_cnt                ) //9
    // );

    /
    // Module    :  实例化QSPI驱动模块
    // Note      : 
    /
    qspi_driver u_qspi_driver (
    .i_clk100                           (i_clk_100m                ),
    .i_clk                              (i_clk_50m                 ),
    .i_rst_n                            (reset_n                   ),
    .i_start                            (qspi_start                ),
    .i_cmd                              (qspi_cmd                  ),
    .i_addr                             (qspi_addr                 ),
    .o_write_allow                      (qspi_write_allow          ),
    .i_write_data                       (qspi_write_data           ),
    .i_byte_cnt                         (qspi_byte_cnt             ),
    .o_busy                             (qspi_busy                 ),
    .o_done                             (qspi_done                 ),
    .o_read_valid                       (qspi_read_valid           ),
    .o_read_data                        (qspi_read_data            ),
    .o_spi_cs_n                         (o_spi_cs_n                ),
    .o_spi_clk                          (o_spi_clk                 ),
    .o_spi_mosi                         (o_spi_mosi                ),
    .i_spi_miso                         (i_spi_miso                ) 
    );

endmodule

QSPI驱动

驱动说明

1;因为硬件接线原因,当前的驱动为SPIx1模式,即standard_spi,如果需要spix2或spix4模式只需要稍微修改代码的数据输出接口,使用一个数据转换即可完成。

2;此驱动当前为GD国产Flash的命令码,对于别的芯片来说应该命令大部分一致,但还是要对照手册查看。

3;可以打开里面的ila检测总线时序

驱动代码
verilog 复制代码
//
// FileName    : 
// Author      : 
// Creat Time  : 2025-04-07
// Encode Save : UTF-8
// Device      : 
// Design      : 
// Note        : NOR-Flash-GD25LQ256DWIGR 命令格式分类

// 1. 仅命令型 (Instruction Only)
// - WREN (06h)  : 写使能
// - WRDI (04h)  : 写禁止
// - RSTEN (66h) : 复位使能
// - RST (99h)   : 复位
// - BE (60h/C7h): 全片擦除
// CS#   ˉ\________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\___
// MOSI  ==_<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>====
// MISO  ==============================================

// 2. 命令+读数据型 (Instruction + Read Data)
// - RDID (9Fh)  : 读取ID,读3字节
// - RDSR (05h)  : 读状态寄存器,读1字节
// - RDCR (35h)  : 读配置寄存器,读1字节
// - RDFSR (70h) : 读标志状态寄存器,读1字节
// CS#   ˉ\____________________________________________________________________________________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__...__/ˉ\____
// MOSI  ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>_<Z>__<Z>__<Z>__<Z>_<Z>__<Z>__<Z>__<Z>_<Z>__<Z>__<Z>__<Z>_<Z>__<Z>__
// MISO  ===========================================<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D1>_...<=======
//       |<--------------命令阶段-------------------->|<--------------读第1字节-------------->|<----------------读第N字节--------------->|
// 时序举例(RDID-9Fh):
// CS#       ˉ\_________________________________________________________________________________________________________________________________________________________________/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\_________
// MOSI(9F)  ___/ˉ\____________/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\_________________________________________________________________________________________________________________________________
// MISO  ==============================================<===============MF======================><====================ID================><================DC===================>--------
// 注意事项:
// 1. MOSI在命令阶段发送指令,之后保持高阻态
// 2. MISO在命令阶段之后开始输出有效数据
// 3. 读取多个字节时连续进行
// 4. 每个字节都是MSB优先传输



// 3. 命令+地址型 (Instruction + Address)
// - SE (D8h)    : 扇区擦除,3字节地址
// - BE32K (52h) : 32KB块擦除,3字节地址
// 命令阶段(8 clk) + 地址阶段(24 clk)
// CS#       ˉ\_________________________________________________________________________________________________________________________________________________________________/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\_________
// MOSI      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>==========
//           |<------------命令阶段------------------->|<-----------------------------------------------------地址阶段(24位)------------------------------------------------------------->|
// MISO      ============================================================================================================================================================================
// 时序举例(扇区擦除-SE-D8h):
// CS#   ˉ\________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__...__/ˉ\____/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__
// MOSI  ===<D>==<8>__...===========<扇区地址(24位)>=============
// 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      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>======================================================================================
// MISO      ===================================================================================================================================================================<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>.......
//           |<-----------------命令----------------->|<----------------------------------------------地址-------------------------------------------------------------------->|<---------------读第1字节---------------->|<--读第N字节-->|
// 注意事项:
// 1. 命令阶段发送8位指令码
// 2. 地址阶段发送24位地址(A23-A0)
// 3. MOSI在地址发送后保持高阻态
// 4. MISO从地址后开始输出有效数据
// 5. 所有传输都是MSB优先
// 时序举例(READ-03h):
// CS#   ˉ\_________________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__...__/ˉ\____/ˉ\__/ˉ\__...__/ˉ\__/ˉ\__...__/ˉ\_
// MOSI  ==<0><3><地址[23:0]>======================================
// 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      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>_<Dc>========================================================================================================
// MISO      ==========================================================================================================================================================================================================<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>...........................
//           |<-----------------命令----------------->|<----------------------------------------------地址-------------------------------------------------------------------->|<---------------Dummy_clock------------->|<---------------读第1字节----------->|<---------------读第N字节---------------->|
// 注意事项:
// 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  ==<0><B><地址[23:0]>============================================
// 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      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<A7>_<A6>_<A5>_<A4>_<A3>_<A2>_<A1>_<A0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>_<D7>_<D6>_<D5>_<D4>_<D3>_<D2>_<D1>_<D0>==============================================
// MISO      ==========================================================================================================================================================================================================================================================.......
//           |<-----------------命令----------------->|<----------------------------------------------写地址(3Bytes Mode)----------------------------------------------------------->|<---------------写第1字节---------------->|<--写第N字节-->|
// 时序举例(PP-02h):
// CS#   ˉ\_________________________________________________________/ˉ
// CLK   ___/ˉ\__/ˉ\__...__/ˉ\____/ˉ\__/ˉ\__...__/ˉ\__/ˉ\__...__/ˉ\_
// MOSI  ==<0><2><地址[23:0]><数据字节流>===========================
// MISO  ===========================================================
//           |  页编程  | 24位地址 |       最多256字节数据         |


// 7. 命令+写配置型 (Instruction + Write Data)
// - WRSR (01h)  : 写状态寄存器,写1字节
// - WRCR (3Eh)  : 写配置寄存器,写1字节
// 命令阶段(8 clk) + 配置数据(8 clk)
// CS#       ˉ\_________________________________________________________________________________/ˉ
// CLK       ___/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__/ˉ\__
// MOSI      ==<C7>_<C6>_<C5>_<C4>_<C3>_<C2>_<C1>_<C0>__<R7>_<R6>_<R5>_<R4>_<R3>_<R2>_<R1>_<R0>_==
//           |<------------命令阶段------------------->|<-----------地址阶段(24位)--------------->|
// MISO      ======================================================================================


// CopyRight(c) 2016, Chengdu universal_data Technology Co. Ltd.
//


`timescale 1ns / 1ps



module qspi_driver 
(
    // 系统接口
    input  wire         i_clk100,       // 系统时钟
    input  wire         i_clk,          // 系统时钟
    input  wire         i_rst_n,        // 低电平复位
    
    // 控制接口
    input  wire         i_start,        // 启动传输
    input  wire [7:0]   i_cmd,          // 命令码
    input  wire [31:0]  i_addr,         // Flash地址
    output reg          o_write_allow,  // 允许产生数据
    input  wire [7:0]   i_write_data,   // 写数据
    input  wire [8:0]   i_byte_cnt,     // 传输字节数
    
    // 状态接口
    output reg          o_busy,         // 忙状态标志
    output reg          o_done,         // 操作完成标志
    output reg          o_read_valid,   // 读数据有效
    output reg  [7:0]   o_read_data,    // 读出数据
    
    // QSPI接口
    output reg                          o_spi_cs_n                 ,// 片选信号,低电平有效
    output wire                         o_spi_clk                  ,// SPI时钟
    output reg                          o_spi_mosi                 ,// 主机输出
    input  wire                         i_spi_miso                  // 主机输入
);

    // 命令码定义
    // 仅命令型
    localparam CMD_WREN     = 8'h06;    // 写使能
    localparam CMD_WRDI     = 8'h04;    // 写禁止
    localparam CMD_RSTEN    = 8'h66;    // 复位使能
    localparam CMD_RST      = 8'h99;    // 复位
    localparam CMD_BE       = 8'hC7;    // 全片擦除

    // 命令+读数据型
    localparam CMD_RDID     = 8'h9F;    // 读取ID
    localparam CMD_RDSR     = 8'h05;    // 读状态寄存器
    localparam CMD_RDCR     = 8'h35;    // 读配置寄存器

    // 命令+地址型
    localparam CMD_SE       = 8'hD8;    // 扇区擦除
    localparam CMD_BE32K    = 8'h52;    // 32KB块擦除

    // 命令+地址+读数据型
    localparam CMD_READ     = 8'h03;    // 读数据
    localparam CMD_DREAD    = 8'h3B;    // 双输出读
    localparam CMD_QREAD    = 8'h6B;    // 四输出读

    // 命令+地址+空周期+读数据型
    localparam CMD_FAST_READ = 8'h0B;   // 快速读

    // 命令+地址+写数据型
    localparam CMD_PP       = 8'h02;    // 页编程
    localparam CMD_DPP      = 8'hA2;    // 双输入页编程
    localparam CMD_QPP      = 8'h32;    // 四输入页编程
    
    //命令 + 写指令
    localparam CMD_WRSR        = 8'h01;     // 写状态寄存器
    

    // 状态机状态定义
    localparam ST_IDLE      = 4'd0;     // 空闲状态
    localparam ST_CMD       = 4'd1;     // 发送命令
    localparam ST_ADDR      = 4'd2;     // 发送地址
    localparam ST_DUMMY     = 4'd3;     // 空周期
    localparam ST_READ      = 4'd4;     // 读数据
    localparam ST_WRITE     = 4'd5;     // 写数据
    localparam ST_INSTRUCT  = 4'd6;     // 写指令
    localparam ST_END       = 4'd7;     // 结束状态

    // 内部寄存器
    reg [3:0]  state;          // 状态机状态
    reg [2:0]  bit_cnt;        // 位计数器
    reg [8:0]  byte_cnt;       // 字节计数器
    reg [7:0]  clk_div_cnt;    // 时钟分频计数器
    reg [7:0]  shift_reg;      // 移位寄存器
    reg        spi_clk_en;     // SPI时钟使能
    reg [2:0]  cmd_type;       // 命令类型

    reg read_data_come;
    reg read_finish;
    
    assign o_spi_clk = spi_clk_en ? i_clk : 0;

    // 命令类型判断函数
    function [2:0] get_cmd_type;
        input [7:0] cmd;
        begin
            casez (cmd)
                CMD_WREN,  CMD_WRDI, CMD_RSTEN, 
                CMD_RST,   CMD_BE:              get_cmd_type = 3'd0;  // 仅命令
                CMD_RDID,  CMD_RDSR, CMD_RDCR:  get_cmd_type = 3'd1;  // 命令+读
                CMD_SE,    CMD_BE32K:           get_cmd_type = 3'd2;  // 命令+地址
                CMD_READ,  CMD_DREAD, CMD_QREAD: get_cmd_type = 3'd3;  // 命令+地址+读
                CMD_FAST_READ:                   get_cmd_type = 3'd4;  // 命令+地址+空周期+读
                CMD_PP,    CMD_DPP,  CMD_QPP:    get_cmd_type = 3'd5;  // 命令+地址+写
                CMD_WRSR                    :    get_cmd_type = 3'd6;  // 命令 + 写指令
                default:                         get_cmd_type = 3'd7;  // 无效命令
            endcase
        end
    endfunction

    // 主状态机
    //下降沿发送数据,上升沿读取数据
    always @(negedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            state <= ST_IDLE;
            o_spi_cs_n <= 1'b1;
            o_spi_mosi <= 1'b0;
            o_busy <= 1'b0;
            o_done <= 1'b0;
            bit_cnt <= 3'd0;
            byte_cnt <= 9'd0;
            spi_clk_en <= 1'b0;
            cmd_type <= 3'd7;
            shift_reg <= 8'd0;
            read_data_come <= 8'd0;
            o_write_allow <= 1'b0;
        end
        else begin
            case (state)
                ST_IDLE: begin
                    o_done <= 1'b0;
                    if (i_start) begin
                        state <= ST_CMD;
                        o_busy <= 1'b1;
                        spi_clk_en <= 1'b1;
                        cmd_type <= get_cmd_type(i_cmd);
                        shift_reg <= i_cmd;
                        byte_cnt <= i_byte_cnt;
                        bit_cnt       <= 3'd0;
                        read_data_come <= 3'd0;
                        o_write_allow <= 1'b0;
                    end
                end
                ST_CMD: begin
                    o_spi_cs_n <= 1'b0;
                    o_spi_mosi <= shift_reg[7-bit_cnt];
                    if (bit_cnt == 3'd7) begin
                        bit_cnt <= 3'd0;
                        case (cmd_type)
                            3'd0: state <= ST_END;                // 仅命令型
                            3'd1: begin                           
                                state <= ST_READ;                 // 命令+读数据型
                                shift_reg <= 8'hFF;               // 读取时MOSI保持高
                                byte_cnt <= i_byte_cnt;           // 设置读取字节数
                            end
                            3'd2: begin                           
                                state <= ST_ADDR;                 // 命令+地址型
                                shift_reg <= i_addr[31:24];       // 准备发送地址高8位
                                byte_cnt <= 9'd4;                 // 
                            end
                            3'd3: begin                           // 命令+地址+读数据型
                                state <= ST_ADDR;
                                shift_reg <= i_addr[31:24];
                                byte_cnt <= 9'd4;
                            end
                            3'd4: begin                           // 命令+地址+空周期+读数据型
                                state <= ST_ADDR;
                                shift_reg <= i_addr[31:24];
                                byte_cnt <= 9'd4;
                            end
                            3'd5: begin                           // 命令+地址+写数据型
                                state <= ST_ADDR;
                                shift_reg <= i_addr[31:24];
                                byte_cnt <= 9'd4;
                            end

                            3'd6: begin                           // 命令+ 写指令
                                state <= ST_INSTRUCT;
                                bit_cnt <= 3'd0;
                                shift_reg <= i_write_data;
                                byte_cnt <= 9'd2;
                                o_write_allow <= 1'b0;
                            end

                            default: state <= ST_END;
                        endcase
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1'b1;

                        if(cmd_type == 3'd6 && bit_cnt == 3'd6)begin
                            o_write_allow <= 1'b1;
                        end
                        else begin
                            o_write_allow <= 1'b0;
                        end

                    end
                end


                ST_ADDR: begin
                        // 发送当前地址位
                    o_spi_mosi <= shift_reg[7-bit_cnt];   
                    if (bit_cnt == 3'd7) begin  // 一个字节发送完成
                        bit_cnt <= 3'd0;
                        
                        // 根据当前发送的字节,准备下一个字节
                        case (byte_cnt)
                            9'd4: begin  // 发送完地址高字节
                                shift_reg <= i_addr[23:16];  // 准备第3字节
                                byte_cnt <= byte_cnt - 1'b1;
                            end
                            9'd3: begin  // 发送完地址高字节
                                shift_reg <= i_addr[15:8];  // 准备第2字节
                                byte_cnt <= byte_cnt - 1'b1;
                            end
                            9'd2: begin  // 发送完地址中字节
                                shift_reg <= i_addr[7:0];   // 准备第1字节
                                byte_cnt <= byte_cnt - 1'b1;
                            end
                            9'd1: begin  // 发送完地址低字节
                                byte_cnt <= byte_cnt - 1'b1;
                                // 根据命令类型决定下一个状态
                                case (cmd_type)
                                    3'd2: begin  // 命令+地址
                                        state <= ST_END;
                                    end
                                    3'd3: begin  // 命令+地址+读
                                        state <= ST_READ;
                                        shift_reg <= 8'hFF;  // 读取时MOSI保持高
                                        byte_cnt <= i_byte_cnt;  // 设置读取字节数
                                    end
                                    3'd4: begin  // 命令+地址+空周期+读
                                        state <= ST_DUMMY;
                                        bit_cnt <= 3'd0;  // 重置bit计数器
                                    end
                                    3'd5: begin  // 命令+地址+写
                                        o_write_allow <= 1'b0;
                                        state <= ST_WRITE;
                                        shift_reg <= i_write_data;  // 准备第一个写数据
                                        byte_cnt <= i_byte_cnt;     // 设置写入字节数
                                    end
                                    default: state <= ST_END;
                                endcase
                            end
                            default: state <= ST_END;
                        endcase
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1'b1;  // 继续发送当前字节的下一位

                        // if(cmd_type == 3'd5 && byte_cnt == 9'd1 && bit_cnt == 3'd6)begin
                        //     o_write_allow <= 1'b1;
                        // end
                        // else begin
                        //     o_write_allow <= 1'b0;
                        // end

                    end
                end

                ST_DUMMY: begin
                    o_spi_mosi <= 1'b0;      // 空周期保持高电平
                    if (bit_cnt == 3'd7) begin      // 修改为bit计数
                        state <= ST_READ;
                        shift_reg <= 8'hFF;
                        byte_cnt <= i_byte_cnt;      // 准备读数据的字节数
                        bit_cnt <= 3'd0;
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1'b1;   // 计数8个时钟周期
                    end
                end

                ST_READ: begin
                    o_spi_mosi <= 1'b0;      // 读取时MOSI保持高  
                    if(read_finish)begin
                        state <= ST_END;
                        spi_clk_en <= 1'b0;
                        read_data_come  <=  1'b0;
                    end
                    else begin
                        read_data_come  <=  1'b1; // 单线模式下读数据标志信号,此信号为高标志正在接收数据
                    end
                end


                ST_WRITE: begin
                    o_spi_mosi <= shift_reg[7-bit_cnt];
                    if (bit_cnt == 3'd7) begin
                        bit_cnt <= 3'd0;
                        o_write_allow <= 1'b0;
                        if (byte_cnt == 9'd1)
                            state <= ST_END;
                        else begin
                            byte_cnt <= byte_cnt - 1'b1;
                            shift_reg <= i_write_data;
                        end
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1'b1;
                        if(bit_cnt == 3'd6)begin
                            o_write_allow <= 1'b1;
                        end
                        else begin
                            o_write_allow <= 1'b0;
                        end
                    end
                end




                ST_INSTRUCT : begin
                    o_spi_mosi <= shift_reg[7-bit_cnt];
                    if (bit_cnt == 3'd7) begin
                        o_write_allow <= 1'b0;
                        bit_cnt <= 3'd0;
                        if (byte_cnt == 9'd1)
                            state <= ST_END;
                        else begin
                            byte_cnt <= byte_cnt - 1'b1;
                            shift_reg <= i_write_data;
                        end
                    end
                    else begin
                        bit_cnt <= bit_cnt + 1'b1;

                        if(byte_cnt == 9'd2 && bit_cnt == 3'd6)begin
                            o_write_allow <= 1'b1;
                        end
                        else begin
                            o_write_allow <= 1'b0;
                        end
                    end
                end


                ST_END: begin
                    o_spi_mosi <= 1'b0;      
                    o_spi_cs_n <= 1'b1;
                    o_busy <= 1'b0;
                    o_done <= 1'b1;
                    spi_clk_en <= 1'b0;
                    state <= ST_IDLE;
                end

                default: begin
                    state <= ST_IDLE;
                end
            endcase
        end
    end


    // ila_qspi_monitor  u_ila_inst
    // (
    // .clk                                (i_clk                     ),
    // .probe0                             (i_start                   ),//1
    // .probe1                             (cmd_type                  ),//3
    // .probe2                             (state                     ),//4
    // .probe3                             (byte_cnt                  ),//9
    // .probe4                             (bit_cnt                   ),//3
    // .probe5                             (o_spi_cs_n                ),//1 
    // .probe6                             (o_spi_clk                 ),//1 
    // .probe7                             (o_spi_mosi                ),//1
    // .probe8                             (i_spi_miso                ),//1 
    // .probe9                             (i_cmd                     ),//8 
    // .probe10                            (i_addr                    ),//32 
    // .probe11                            (i_write_data              ),//8
    // .probe12                            (i_byte_cnt                ),//9
    // .probe13                            (read_data_come            ),//1
    // .probe14                            (read_byte_cnt             ),//9
    // .probe15                            (read_bit_cnt              ),//4
    // .probe16                            (o_read_valid              ),//1
    // .probe17                            (read_reg                  ),//8
    // .probe18                            (read_finish               ),//1
    // .probe19                            (o_read_data               ),//8
    // .probe20                            (o_write_allow             ) //1
    // );


    //
    // 功能:接收QSPI Flash发送过来的数据    
    //
        reg [7:0] read_reg;
        reg [3:0] read_bit_cnt;
        reg [8:0]  read_byte_cnt;  // 读字节计数器

        always @(posedge i_clk)
        begin
            if(!i_rst_n)
                begin
                    o_read_valid  <= 1'b0;
                    read_byte_cnt <= 9'b0;
                    read_bit_cnt  <= 4'd0;
                    read_reg      <= 8'd0;
                    read_finish   <= 1'b0;
                    o_read_data   <= 8'b0;
                end
            else begin
                if(read_data_come)begin// 此信号为高表示接收数据从QSPI Flash发过来的数据
                    if(read_byte_cnt < byte_cnt)begin
                        if(read_bit_cnt < 8) begin      //接收一个Byte的bit0~bit6                   
                            o_read_valid        <=  1'b0                       ;
                            read_reg            <=  {read_reg[6:0],i_spi_miso} ;
                            read_bit_cnt        <=  read_bit_cnt +   1'b1      ;
                        end
                        else begin
                            o_read_valid        <=  1'b1                        ;  //一个byte数据有效
                            o_read_data         <=  {read_reg[6:0],i_spi_miso}  ;  //接收bit7
                            read_reg            <=  8'b0;
                            read_bit_cnt        <=  4'b1                        ;
                            read_byte_cnt       <=  read_byte_cnt    +   1'b1   ;
                        end   

                        if((read_byte_cnt == byte_cnt -1) &&  (read_bit_cnt == 4'd8))begin
                            read_finish         <=  1'b1    ;   
                        end

                    end
                    else begin
                        read_byte_cnt       <=  0       ;
                        read_finish         <=  1'b1    ;
                        o_read_valid        <=  1'b0    ;
                    end
                end  
                else begin
                    read_byte_cnt <= 9'b0;
                    read_bit_cnt  <= 4'd0; 
                    read_finish   <= 1'b0;
                    read_reg      <= 8'd0;
                    o_read_valid  <= 1'b0;
                end
            end    
        end    

endmodule

数据回读监视

说明

此模块仅用来进行数据监视,可以正确回读。

回读的控制使用vio来控制读取的数据长度,使用ILA来抓取数据。

代码
verilog 复制代码
//
// FileName    : 
// Author      : 
// Creat Time  : 2025-04-07
// Encode Save : UTF-8
// Device      : 
// Design      : 
// Note        : 

// CopyRight(c) 2016, Chengdu universal_data Technology Co. Ltd.
//



`timescale 1ns / 1ps
module qspi_read_monitor (
    input  wire                         i_clk_100m                 ,// 100MHz系统时钟
    input  wire                         i_clk_50m                  ,// 50MHz系统时钟
    input  wire                         i_rst_n                    , // 低电平复位

    // QSPI接口
    output wire                         o_spi_cs_n                 ,
    output wire                         o_spi_clk                  ,
    output wire                         o_spi_mosi                 ,
    input  wire                         i_spi_miso                 
    

);

    // 测试数据生成和验证
    wire                                data_gen_en                 ;// 数据生成使能
    wire               [   7: 0]        test_data                   ;// 测试数据
    reg                [   7: 0]        verify_data                 ;// 验证数据
    reg                                 data_error                  ;// 数据错误标志
    reg                [   8: 0]        sector_cnt                  ;// 扇区计数器 (0-511)
    reg                [   7: 0]        page_cnt                    ;// 页计数器 (0-255)
    reg                [  31: 0]        current_addr                ;// 当前写入地址

    // QSPI驱动接口信号
    reg                                 qspi_start                  ;
    reg                [   7: 0]        qspi_cmd                    ;
    reg                [  31: 0]        qspi_addr                   ;
    wire                                qspi_write_allow            ;
    reg                [   7: 0]        qspi_write_data             ;
    reg                [   8: 0]        qspi_byte_cnt               ;
    wire                                qspi_busy                   ;
    wire                                qspi_done                   ;
    wire                                qspi_read_valid             ;
    wire               [   7: 0]        qspi_read_data              ;
    // states
    reg                [   4: 0]        boot_states                 ;//主状态机
    reg                [  23: 0]        flash_id                    ;// 存储读取的Flash ID
    reg                [   7: 0]        status_reg                  ;// 状态寄存器值

    // 状态机状态定义
    localparam                          ST_IDLE                    = 5'd0  ;
    localparam                          ST_READ_ID                 = 5'd1  ;
    localparam                          ST_READ_REGSTATES1         = 5'd2  ;
    localparam                          ST_READ_REGSTATES0         = 5'd3  ;     
    localparam                          ST_WRITE_ENABLE            = 5'd4  ;
    localparam                          ST_EN4B_ADDR               = 5'd5  ;
    localparam                          ST_WRITE_REGSTATE          = 5'd6  ;
    localparam                          ST_CHIP_ERASE              = 5'd7  ;           
    localparam                          ST_READ_REGSTATES1_AG      = 5'd8  ;
    localparam                          ST_READ_REGSTATES0_AG      = 5'd9  ;
    localparam                          ST_PAGE_WRITE_ENABLE       = 5'd10 ;
    localparam                          ST_PAGE_PROGRAM            = 5'd11 ;
    localparam                          ST_CHECK_PROGRAM           = 5'd12 ;
    localparam                          ST_READ_DATA               = 5'd13 ;
    localparam                          ST_CHECK_DATA              = 5'd14 ;
    localparam                          ST_NEXT_PAGE               = 5'd15 ;
    localparam                          ST_NEXT_SECTOR             = 5'd16 ;
    localparam                          ST_READ_DONE               = 5'd17 ;
    localparam                          ST_ERROR                   = 5'd18 ;
    // 错误码定义
    localparam                          ERR_WRITE_DISABLE          = 8'h01 ;  // 写禁止失败
    localparam                          ERR_WRITE_ENABLE           = 8'h02 ;  // 写使能失败
    localparam                          ERR_DATA_VERIFY            = 8'h03 ;  // 数据验证失败
    // reset
    wire                              vio_rst_n                   ;
    wire                              reset_n                     ;
    wire                              vio_start_boot              ;
    wire               [31: 0]        vio_flash_data_length       ;

    reg         [  23: 0]        FLASH_ID                   ;// 测试完成标志
    reg                          o_test_done                ;// 测试完成标志
    reg                          o_test_error               ;// 测试错误标志
    reg         [   7: 0]        o_error_code               ;// 错误代码
    reg         [   8: 0]        o_sector_cnt               ;// 当前扇区计数
    reg         [   7: 0]        o_page_cnt                 ;// 当前页计数

    //flash_data
     wire        [  31: 0]        i_flash_data_length        ;
     wire        [   7: 0]        i_flash_data               ;
     wire                         o_flash_data_en            ;


    /
    // Module    : Test_data
    // Note      : 累加计数器
    /
    // 测试数据生成模块
    // always @(posedge i_clk_50m or negedge reset_n) begin
    //     if (!reset_n) begin
    //         test_data <= 8'd0;
    //     end
    //     else if (data_gen_en ) begin  // 简化条件,确保连续
    //         test_data <= test_data + 1'b1;
    //     end
    //     else if (boot_states == ST_WRITE_ENABLE) begin
    //         test_data <= 8'd0;       // 重置
    //     end
    // end
    // assign data_gen_en = ( boot_states == ST_PAGE_PROGRAM) ? qspi_write_allow : 1'b0;
    
    assign o_flash_data_en = ( boot_states == ST_PAGE_PROGRAM) ? qspi_write_allow : 1'b0;
    
    assign test_data = test_data;


    /
    // Module    : reset
    // Note      : 
    /
    vio_0 vio_reset (
        .clk                                (i_clk_50m             ),// input wire clk
        .probe_out0                         (vio_start_boot        ), // output wire [0 : 0] probe_out0
        .probe_out1                         (vio_flash_data_length        ) // output wire [31 : 0] probe_out0
    );
    assign  reset_n = i_rst_n;
    reg i_start_boot_d1;
    always @(posedge i_clk_50m or negedge reset_n) 
    begin
        if(!reset_n)begin
            i_start_boot_d1 <= 1'b0;
        end
        else begin
            i_start_boot_d1 <= vio_start_boot;
        end

    end

    /
    // Module    : 固化流程主控状态机
    // Note      : 
    /
    reg [7:0] read_state1;
    reg [7:0] read_state0;
    reg [15:0] error_cnt;
    reg [2:0] chip_protect_mode;
    reg addr_4byte_en;
    reg chip_cmp;

    // 状态机
    always @(posedge i_clk_50m or negedge reset_n) begin
        if (!reset_n ) begin
            boot_states <= ST_IDLE;
            qspi_start <= 1'b0;
            qspi_cmd <= 8'h00;
            qspi_addr <= 32'h000000;
            qspi_byte_cnt <= 9'd0;
            sector_cnt <= 9'd0;
            page_cnt <= 8'd0;
            current_addr <= 32'd0;
            o_test_done <= 1'b0;
            o_test_error <= 1'b0;
            o_error_code <= 8'h00;
            o_sector_cnt <= 9'd0;
            o_page_cnt <= 8'd0;
            flash_id <= 24'd0;
        end
        else begin
            if (i_start_boot_d1) begin
                boot_states <= ST_IDLE;
                qspi_start <= 1'b0;
                qspi_cmd   <= 8'h00;
                qspi_addr <= 32'h000000;
                qspi_byte_cnt <= 9'd0;
                sector_cnt <= 9'd0;
                page_cnt <= 8'd0;
                current_addr <= 32'd0;
                o_test_done <= 1'b0;
                o_test_error <= 1'b0;
                o_error_code <= 8'h00;
                o_sector_cnt <= 9'd0;
                o_page_cnt <= 8'd0;
                flash_id <= 24'd0;
                error_cnt <= 16'd0;
            end

            case (boot_states)
                ST_IDLE: begin
                    if (~vio_start_boot && i_start_boot_d1 )
                    begin
                        boot_states <= ST_READ_ID;
                        qspi_cmd   <= 8'h9F;                        // 先读取芯片ID
                        qspi_byte_cnt <= 9'd3;                      // 读3字节ID
                        qspi_start <= 1'b1;
                        flash_id <= 24'd0;
                        error_cnt <= 16'd0;
                    end
                end

                ST_READ_ID : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin                            //获取芯片初始化状态,读芯片状态寄存器1
                        boot_states <= ST_WRITE_ENABLE;      
                        qspi_cmd <= 8'h06;                          //打开写使能
                        qspi_byte_cnt <= 9'd0;
                        qspi_start <= 1'b1;
                    end
                    else if(qspi_read_valid )begin
                        flash_id <= {flash_id[23:8],qspi_read_data};
                    end
                end

                ST_WRITE_ENABLE : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        boot_states <= ST_EN4B_ADDR;                //打开4字节地址模式
                        qspi_cmd <= 8'hB7;                          
                        qspi_byte_cnt <= 9'd2;
                        qspi_start <= 1'b1;
                    end   

                end

                ST_EN4B_ADDR : begin
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        boot_states <= ST_READ_DATA;
                        qspi_cmd <= 8'h03;                              // READ命令
                        qspi_addr <= current_addr;                      // 读取当前页
                        qspi_byte_cnt <= 9'd256;                        // 读取整页数据
                        verify_data <= 8'd0;                            // 重置验证数据
                        qspi_start <= 1'b1;
                    end
                end


                ST_READ_DATA: begin                                     
                    qspi_start <= 1'b0;
                    if (qspi_done) begin
                        boot_states <= ST_CHECK_DATA;
                    end
                end

                ST_CHECK_DATA: begin
                    if(current_addr >= vio_flash_data_length)begin
                        boot_states <= ST_READ_DONE;
                    end
                    else begin 
                        if (page_cnt == 8'd255) begin                       //256次Page_program可以写满一个sector区域,32M大小的flash由512个sector
                            boot_states <= ST_NEXT_SECTOR;
                            sector_cnt <= sector_cnt + 1'b1;
                            page_cnt <= 8'd0;
                            current_addr <= current_addr + 32'h100;
                            o_sector_cnt <= sector_cnt + 1'b1;
                        end
                        else begin
                            boot_states <= ST_NEXT_PAGE;
                            page_cnt <= page_cnt + 1'b1;
                            current_addr <= current_addr + 32'h100;            //Page_program每次至多写256Bytes,当缓冲的数据多于256Bytes则数据覆盖,少于256则填0    
                            o_page_cnt <= page_cnt + 1'b1;
                        end
                    end
                end


                ST_NEXT_PAGE: begin
                    boot_states <= ST_READ_DATA;
                    qspi_cmd <= 8'h03;                              // READ命令
                    qspi_addr <= current_addr;                      // 读取当前页
                    qspi_byte_cnt <= 9'd256;                        // 读取整页数据
                    qspi_start <= 1'b1;
                end


                ST_NEXT_SECTOR: begin
                    boot_states <= ST_READ_DATA;
                    qspi_cmd <= 8'h03;                              // READ命令
                    qspi_addr <= current_addr;                      // 读取当前页
                    qspi_byte_cnt <= 9'd256;                        // 读取整页数据
                    qspi_start <= 1'b1;
                end


                ST_READ_DONE: begin
                    o_test_done <= 1'b1;                    
                    if (vio_start_boot == 1'b1) begin
                        boot_states <= ST_IDLE;
                        o_test_done <= 1'b0;
                        sector_cnt <= 9'd0;
                        page_cnt <= 8'd0;
                        current_addr <= 32'd0;
                        o_sector_cnt <= 9'd0;
                        o_page_cnt <= 8'd0;
                    end
                end


                ST_ERROR: begin
                    o_test_error <= 1'b1;
                    if (vio_start_boot == 1'b0) begin
                        boot_states <= ST_IDLE;
                        o_test_error <= 1'b0;
                        o_error_code <= 8'h00;
                    end
                end

                default: boot_states <= ST_IDLE;
            endcase
        end
    end


    /
    // Module    : ila检测
    // Note      : 
    /
    ila_1  u_ila_inst
    (
        .clk                                (i_clk_50m                ),
        .probe0                             (vio_start_boot            ),// 1
        .probe1                             (qspi_done                 ),// 1
        .probe2                             (boot_states               ),//8 
        .probe3                             (qspi_cmd                  ),//8 
        .probe4                             (current_addr              ),//32 
        .probe5                             (sector_cnt                ),// 9
        .probe6                             (page_cnt                  ),// 8
        .probe7                             (qspi_start                ),// 1
        .probe8                             (o_test_done               ),// 1
        .probe9                             (qspi_read_valid           ),// 1
        .probe10                            (qspi_read_data            ) // 8
    );

    /
    // Module    :  实例化QSPI驱动模块
    // Note      : 
    /
    qspi_driver u_qspi_driver (
    .i_clk100                           (i_clk_100m                ),
    .i_clk                              (i_clk_50m                 ),
    .i_rst_n                            (reset_n                   ),
    .i_start                            (qspi_start                ),
    .i_cmd                              (qspi_cmd                  ),
    .i_addr                             (qspi_addr                 ),
    .o_write_allow                      (qspi_write_allow          ),
    .i_write_data                       (qspi_write_data           ),
    .i_byte_cnt                         (qspi_byte_cnt             ),
    .o_busy                             (qspi_busy                 ),
    .o_done                             (qspi_done                 ),
    .o_read_valid                       (qspi_read_valid           ),
    .o_read_data                        (qspi_read_data            ),
    .o_spi_cs_n                         (o_spi_cs_n                ),
    .o_spi_clk                          (o_spi_clk                 ),
    .o_spi_mosi                         (o_spi_mosi                ),
    .i_spi_miso                         (i_spi_miso                ) 
    );  

endmodule

如何验证数据写入的正确性

方法1

使用程序内的回读模块,并使用vio配置需要回读的长度,配置ILA配置需要抓取的地址。将ILA抓取的CSV数据导入到Matlab内进行数据分析与比对。

matlab 复制代码
close all;
clear all; 
clc;

%% READ DATA ANALYSIS
% 读取CSV文件
data = readtable('C:\Users\PC\Desktop\data\iladata_read_1.csv');

% 获取第5列和第6列数据
enable = data{:, 16};    % 使能列
raw_data = data{:, 17};  % 数据列

% 提取使能为1的有效数据
valid_data = raw_data(enable == 1);

% % 将16进制字符串转换为数值
valid_data_dec = hex2dec(valid_data);
% if(mod (length(valid_data_dec),2) ~= 0)
%     length_data = length(valid_data_dec) - 1;
% else
%     length_data = length(valid_data_dec);
% end
% 
% flash_data = reshape(valid_data_dec(1:length_data),[2,length_data/2]);
% flash_data = flash_data(1,:).';

flash_data = valid_data_dec;


fid = fopen('flash_read_data.bin','wb');
fwrite(fid,uint8(flash_data),'uint8');
fclose(fid);
fprintf('Read data operate finish !!!\n');

%% WRITE DATA ANALYSIS
% 读取CSV文件
data = readtable('C:\Users\PC\Desktop\data\iladata_write_1.csv');

% 获取第5列和第6列数据
enable = data{:, 9};    % 使能列
raw_data = data{:, 8};  % 数据列

% 提取使能为1的有效数据
valid_data = raw_data(enable == 1);

% % 将16进制字符串转换为数值
valid_data_dec = hex2dec(valid_data);
% if(mod (length(valid_data_dec),2) ~= 0)
%     length_data = length(valid_data_dec) - 1;
% else
%     length_data = length(valid_data_dec);
% end
% 
% flash_data = reshape(valid_data_dec(1:length_data),[2,length_data/2]);
% flash_data = flash_data(1,:).';

flash_data = valid_data_dec;

fid = fopen('flash_write_data.bin','wb');
fwrite(fid,uint8(flash_data),'uint8');
fclose(fid);
fprintf('Write data operate finish !!!\n');

方式2

使用vivado内置的readback功能直接将整个flash数据全部读出来,存储文件,然后将读出来的数据文件与加载的文件进行对照。

如下所示:

上下对照我们发现回读回来的数据开始的位置多了两个字节0x0000,出现这个问题的原因是我在读fifo时开始有两个无效数据0x0000,导致被写入到了flash中,但因为vivado的bin文件有内置的校验,所以当我们在保证数据的完整性时,开始多的两个字节并不会影响FPGA的上电启动。

如果需要删除此两个无效字节,可以在读数据fifo时将开始的两个数据扔掉。

数据文件计算分析

文件大小


如何验证加载功能正常

方法1

在即将升级的程序文件内写一个流水灯给某个测试点或某个测试状态灯,当升级完成后设备完全断电,拔掉JTAG烧录器,再重启设备或板卡,可以看到测试的状态灯按照程序设置一样正确闪烁即说明升级成功。

方法2

在即将升级的程序文件内添加ILA文件监视某个信号,,当升级完成后设备完全断电,再重启设备或板卡,直接打开vivado的烧录管理界面,然后将刚产生的LTX文件refresh进去,如果成功refresh进去,则升级成功。

后续完善

这里提一种更为完善的FLASH升级办法,也是我在写完驱动调试过程中思考后发现的,也是当前设计的一种缺陷

当前的数据写入与读取是分开进行的,且flash数据读取还需要配合vio与ILA抓取才可以分析写入的数据是否正确,此过程仍需要自己进行数据比对与分析。

正确的过程应该是写完一个sector便开始读取此sector,并进行数据比对。如果正确再进行下一个sector的写入,再回读直到数据写完,然后返回boot_done给CPU。

如果使用此方式,则需要在数据进入模块内时使用一个静态RAM来存储,不使用fifo,当数据写满RAM或到一个sector量时进行数据写入flash,当一个sector写完成,我们可以进行回读然后将回读的数据与RAM对应地址的数据进行比对,如果正确则清空该RAM再写入下一个Secotr的数据,循环此过程直到数据写完。

相关推荐
一条九漏鱼3 小时前
Verilog Test Fixture 时钟激励
fpga开发
ThreeYear_s7 小时前
基于FPGA婴儿安全监护系统(蓝牙小程序监测)
fpga开发·小程序
吸纹鸽7 小时前
蓝桥杯FPGA赛道第二次模拟题代码
fpga开发·蓝桥杯
9527华安10 小时前
Altera系列FPGA实现图像视频采集转HDMI/LCD输出,提供4套Quartus工程源码和技术支持
fpga开发·ov5640·quartus·altera
MVP-curry-萌神14 小时前
FPGA图像处理(四)------ 图像裁剪
图像处理·fpga开发
平凡灵感码头15 小时前
基于智能家居项目 RGB彩灯(P9813)
单片机·fpga开发·智能家居
szxinmai主板定制专家1 天前
基于RK3568多功能车载定位导航智能信息终端
大数据·arm开发·人工智能·计算机视觉·fpga开发
9527华安1 天前
紫光同创FPGA实现HSSTHP光口视频传输+图像缩放,基于Aurora 8b/10b编解码架构,提供3套PDS工程源码和技术支持
fpga开发·aurora·8b/10b·图像缩放·紫光同创·hssthp
每月一号准时摆烂1 天前
数字电子技术基础(五十五)——D触发器
嵌入式硬件·fpga开发