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来说明:

片选信号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的数据,循环此过程直到数据写完。