SPI Flash的原理与Verilog实现(一)

647 阅读13分钟

一、 软件平台与硬件平台

软件平台:

1、操作系统:Windows-8.1

2、开发套件:ISE14.7

3、仿真工具:ModelSim-10.4-SE

4、Matlab版本:Matlab2014b/Matlab2016a

硬件平台:

1、 [FPGA]型号:Xilinx公司的XC6SLX45-2CSG324

2、 Flash型号:WinBond公司的W25Q128BV Quad SPI Flash存储器
二、 原理介绍

本节主要是讨论[QSPI](Quad SPI,四线SPI总线)的相关内容。我的开发板上有一片型号是W25Q128BV的Quad SPI Flash存储器,本文将以它为例子来说明QSPI操作的一些内容。

image.png 这块芯片一共有8个有用的管脚

image.png 2号管脚DO(IO1),3号管脚 /WP(IO2),5号管脚DI(IO0)以及7号管脚/HOLD(IO3)均为双向IO口,所以在编写Verilog代码的时候要把它们定义为inout类型,inout类型的信号既可以做输出也可以作为输入,具体在代码里面如何处理后文会有介绍。

QSPI Flash每个引脚的详细描述如下:

1、Chip Select(/CS)   片选信号Chip Select(/CS)的作用是使能或者不使能设备的操作,当CS为高时,表示设备未被选中,串行数据输出线(DO或IO0,IO1,IO2,IO3)均处于高阻态,当CS为低时,表示设备被选中,FPGA可以给QSPI Flash发送数据或从QSPI Flash接收数据。 2、串行数据输入信号DI以及串行输出信号DO

  W25Q128BV支持标准SPI协议,双线SPI(Dual SPI)协议与四线SPI(Quad SPI)协议。标准的SPI协议在串行时钟信号(SCLK)的上升沿把串行输入信号DI上的数据存入QSPI Flash中,在串行时钟信号(SCLK)的下降沿把QSPI Flash中的数据串行化通过单向的DO引脚输出。而在Dual SPI与Quad SPI中,DI与DO均为双向信号(既可以作为输入,也可以作为输出)。

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、串行时钟线

   串行时钟线用来提供串行输入输出操作的时钟。

image.png

三、 目标任务

1、编写标准SPI 协议 Verilog代码来操作QSPI Flash,并用ChipScope抓出各个指令的时序与芯片手册提供的时序进行对比

2、在标准SPI协议的基础上增加Quad SPI的功能,并用ChipScope抓出Quad SPI的读写数据的时序

3、对比标准SPI与Quad SPI读写W25Q128BV的ChipScope时序,感受二者的效率差距

四、 设计思路与Verilog代码编写 4.1、 命令类型的定义

W25Q128BV一共有35条命令,这里不可能把所有命令的逻辑都写出来,所以截取了一部分常用的命令作为示例来说明QSPI Flash的操作方法。由于命令数目很多,所以在这个部分先对各个命令类型做一个初步定义,下文的代码就是按照这个定义来编写的。

image.png

读数据操作(四线模式)

说明:

1、命令类型是我自己随便定义的,可以随便修改。命令码是芯片手册上定义好,不能修改,更详细的内容请参考W25Q128芯片手册。

2、命令类型的最高位是使能位,只有当最高位为1时,命令才有效(在代码里面写的就是只有当最高位为1时才能进入SPI操作的状态机)。

3、进行四线读写操作之前,一定要把四线读写功能的使能位打开,方法是通过写状态寄存器命令把状态寄存器2的QE位(倒数第二位)置1。

4.2、 如何用Matlab产生存放在ROM中的.coe文件格式的数据 `width=8; %rom中数据的宽度 depth=256; %rom的深度 y=0:255;
y=fliplr(y); %产生要发送的数据,255,254,253,...... ,2,1,0 fid = fopen('test_data.coe', 'w'); % 打开一个.coe文件

% 存放在ROM中的.coe文件第一行必须是这个字符串,16表示16进制,可以改成其他进制 fprintf(fid,'memory_initialization_radix=16;\n');

% 存放在ROM中的.coe文件第二行必须是这个字符串 fprintf(fid,'memory_initialization_vector=\n');

% 把前255个数据写入.coe文件中,并用逗号隔开,为了方便知道数据的个数,每行只写一个数据 fprintf(fid,'%x,\n',y(1:end-1));

% 把最后一个数据写入.coe文件中,并用分号结尾 fprintf(fid,'%x;\n',y(end)); fclose(fid); % 关闭文件指针`

上一节设计了一个把存放在ROM中的数据用SPI总线发出来的例子,ROM里面只存放了10个数据,所以可以直接把这10个数据填写到.coe文件就可以了,由于QSPI Flash的页编程(写数据)指令最大支持256字节的写操作,所以下面的例子的功能是把ROM中存放的256个字节(8-bit)数据先写入QSPI Flash中,然后在读出来。由于数据太多(256个),所以一个一个填写肯定不现实,所以可以利用Matlab来直接产生.coe文件,Matlab的完整代码如下:

depth=256; %rom的深度
y=0:255;   
y=fliplr(y); %产生要发送的数据,255,254,253,...... ,2,1,0
fid = fopen('test_data.coe', 'w'); % 打开一个.coe文件

% 存放在ROM中的.coe文件第一行必须是这个字符串,16表示16进制,可以改成其他进制
fprintf(fid,'memory_initialization_radix=16;\n'); 

% 存放在ROM中的.coe文件第二行必须是这个字符串
fprintf(fid,'memory_initialization_vector=\n'); 

% 把前255个数据写入.coe文件中,并用逗号隔开,为了方便知道数据的个数,每行只写一个数据
fprintf(fid,'%x,\n',y(1:end-1));
 
% 把最后一个数据写入.coe文件中,并用分号结尾
fprintf(fid,'%x;\n',y(end)); 
fclose(fid);  % 关闭文件指针

用Matlab2014b运行上面的代码以后会在与这个.m文件相同的目录下产生一个.coe文件,这个.coe文件可以导入到ROM中。

4.3、 标准SPI总线操作QSPI Flash思路与代码编写

  上一篇博客《SPI总线的原理与FPGA实现》已经介绍过用spi_module这个模块去读取QSPI Flash的Manufacturer/Device  ID,事实上除了上篇博客提供的那种方法以外,还可以直接在时钟信号的下降沿发送数据,时钟信号的上升沿来接受数据来完成读ID的操作,当FPGA在时钟的下降沿发送数据的时候,那么时钟的上升沿刚好在数据的正中间,QSPI Flash刚好可以在这个上升沿把数据读进来,读操作则正好相反。但是有很多有经验的人告诉我在设计中如非必要最好不要使用时钟下降沿触发的设计方法,可能是因为大多数FPGA里面的Flip Flops资源都是上升沿触发的,如果在Verilog代码采用下降沿触发的话 ,综合的时候会在CLK输入信号前面综合出一个反相器,这个反相器可能会对时钟信号的质量有影响,具体的原因等我再Google上继续搜索一段时间在说。这个例子由于状态机相较前几篇博客来说相对复杂,所以接下来写代码我还是采用下降沿发送数据,上升沿接收数据的方式来描述这个状态机。

  接下来的任务就是抽象出一个状态机。上一篇博客仅仅读一个ID就用了6个状态,所以采用上一篇博客的设计思路显然不太现实,但对于初学者而言,上一篇博客仍然有一个基本的指引作用。通过阅读QSPI Flash的芯片手册,可以发现,所有的命令其实至多由以下三个部分组成:

image.png

所以按照这个思路来思考的话抽象出来的状态机的状态并不多。单线模式的状态为以下几个:

    1、空闲状态:用来初始化各个寄存器的值

    2、发送命令状态:用来发送8-bit的命令码

    3、发送地址状态:用来发送24-bit的地址码

    4、读等待状态:当读数据操作正在进行的时候进入此状态等待读数据完毕

    5、写数据状态(单线模式):在这个状态FPGA往QSPI Flash里面写数据

    6、结束状态:一条指令操作结束,并给出一个结束标志

  完整的代码如下:


module qspi_driver
(
output                  O_qspi_clk          , // SPI总线串行时钟线
output reg              O_qspi_cs           , // SPI总线片选信号
output reg              O_qspi_mosi         , // SPI总线输出信号线,也是QSPI Flash的输入信号线
input                   I_qspi_miso         , // SPI总线输入信号线,也是QSPI Flash的输出信号线
                                            
input                   I_rst_n             , // 复位信号

input                   I_clk_25M           , // 25MHz时钟信号
input       [4:0]       I_cmd_type          , // 命令类型
input       [7:0]       I_cmd_code          , // 命令码
input       [23:0]      I_qspi_addr         , // QSPI Flash地址

output reg              O_done_sig          , // 指令执行结束标志
output reg  [7:0]       O_read_data         , // 从QSPI Flash读出的数据
output reg              O_read_byte_valid   , // 读一个字节完成的标志
output reg  [3:0]       O_qspi_state          // 状态机,用于在顶层调试用
);


parameter   C_IDLE            =   4'b0000  ; // 空闲状态
parameter   C_SEND_CMD        =   4'b0001  ; // 发送命令码
parameter   C_SEND_ADDR       =   4'b0010  ; // 发送地址码
parameter   C_READ_WAIT       =   4'b0011  ; // 读等待
parameter   C_WRITE_DATA      =   4'b0101  ; // 写数据
parameter   C_FINISH_DONE     =   4'b0110  ; // 一条指令执行结束

reg         [7:0]   R_read_data_reg     ; // 从Flash中读出的数据用这个变量进行缓存,等读完了在把这个变量的值给输出
reg                 R_qspi_clk_en       ; // 串行时钟使能信号
reg                 R_data_come_single  ; // 单线操作读数据使能信号,当这个信号为高时
            
reg         [7:0]   R_cmd_reg           ; // 命令码寄存器
reg         [23:0]  R_address_reg       ; // 地址码寄存器 
reg         [7:0]   R_write_bits_cnt    ; // 写bit计数器,写数据之前把它初始化为7,发送一个bit就减1
reg         [8:0]   R_write_bytes_cnt   ; // 写字节计数器,发送一个字节数据就把它加1
reg         [7:0]   R_read_bits_cnt     ; // 写bit计数器,接收一个bit就加1
reg         [8:0]   R_read_bytes_cnt    ; // 读字节计数器,接收一个字节数据就把它加1
reg         [8:0]   R_read_bytes_num    ; // 要接收的数据总数
reg                 R_read_finish       ; // 读数据结束标志位

wire        [7:0]   W_rom_addr          ;  
wire        [7:0]   W_rom_out           ;  

assign O_qspi_clk = R_qspi_clk_en ? I_clk_25M : 0   ; // 产生串行时钟信号
assign W_rom_addr = R_write_bytes_cnt               ;

////////////////////////////////////////////////////////////////////////////////////////////
// 功能:用时钟的下降沿发送数据
////////////////////////////////////////////////////////////////////////////////////////////
always @(negedge I_clk_25M)
begin
    if(!I_rst_n)
        begin
            O_qspi_cs           <=  1'b1   ;        
            O_qspi_state        <=  C_IDLE ;
            R_cmd_reg           <=  0      ;
            R_address_reg       <=  0      ;
            R_qspi_clk_en       <=  1'b0   ;  //SPI clock输出不使能
            R_write_bits_cnt    <=  0      ;
            R_write_bytes_cnt   <=  0      ;
            R_read_bytes_num    <=  0      ;    
            R_address_reg       <=  0      ;
            O_done_sig          <=  1'b0   ;
            R_data_come_single  <=  1'b0   ;           
        end
    else
        begin
            case(O_qspi_state)
                C_IDLE:  // 初始化各个寄存器,当检测到命令类型有效(命令类型的最高位位1)以后,进入发送命令码状态
                    begin                              
                        R_qspi_clk_en  <=   1'b0         ;
                        O_qspi_cs      <=   1'b1         ;
                        O_qspi_mosi    <=   1'b0         ;    
                        R_cmd_reg      <=   I_cmd_code   ;
                        R_address_reg  <=   I_qspi_addr  ;
                        O_done_sig     <=   1'b0         ;            
                        if(I_cmd_type[4] == 1'b1) 
                            begin                //如果flash操作命令请求
                                O_qspi_state        <=  C_SEND_CMD  ;
                                R_write_bits_cnt    <=  7           ;        
                                R_write_bytes_cnt   <=  0           ;
                                R_read_bytes_num    <=  0           ;                    
                            end
                    end
                C_SEND_CMD: // 发送8-bit命令码状态 
                    begin
                        R_qspi_clk_en       <=  1'b1    ; // 打开SPI串行时钟SCLK的使能开关
                        O_qspi_cs           <=  1'b0    ; // 拉低片选信号CS
                        if(R_write_bits_cnt > 0) 
                            begin                           //如果R_cmd_reg还没有发送完
                                O_qspi_mosi        <=  R_cmd_reg[R_write_bits_cnt] ;         //发送bit7~bit1位
                                R_write_bits_cnt   <=  R_write_bits_cnt-1'b1       ;
                            end                            
                        else 
                            begin                                 //发送bit0
                                O_qspi_mosi <=  R_cmd_reg[0]    ;
                                if ((I_cmd_type[3:0] == 4'b0001) | (I_cmd_type[3:0] == 4'b0100)) 
                                    begin    //如果是写使能指令(Write Enable)或者写不使能指令(Write Disable)
                                        O_qspi_state    <=  C_FINISH_DONE   ;
                                    end                          
                                else if (I_cmd_type[3:0] == 4'b0011) 
                                    begin    //如果是读状态寄存器指令(Read Register)
                                        O_qspi_state        <=  C_READ_WAIT ;
                                        R_write_bits_cnt    <=  7           ;
                                        R_read_bytes_num    <=  1           ;//读状态寄存器指令需要接收一个数据 
                                    end                             
                                else if( (I_cmd_type[3:0] == 4'b0010) || (I_cmd_type[3:0] == 4'b0101) || (I_cmd_type[3:0] == 4'b0111) || (I_cmd_type[3:0] == 4'b0000) ) 
                                    begin // 如果是扇区擦除(Sector Erase),页编程指令(Page Program),读数据指令(Read Data),读设备ID指令(Read Device ID)                          
                                        O_qspi_state        <=  C_SEND_ADDR ;
                                        R_write_bits_cnt    <=  23          ; // 这几条指令后面都需要跟一个24-bit的地址码
                                    end
                            end
                    end
                C_SEND_ADDR: // 发送地址状态
                    begin
                        if(R_write_bits_cnt > 0)  //如果R_cmd_reg还没有发送完
                            begin                                 
                                O_qspi_mosi        <=  R_address_reg[R_write_bits_cnt] ; //发送bit23~bit1位
                                R_write_bits_cnt   <=  R_write_bits_cnt    -   1       ;    
                            end                                 
                        else 
                            begin 
                                O_qspi_mosi <=  R_address_reg[0]    ;   //发送bit0
                                if(I_cmd_type[3:0] == 4'b0010) // 扇区擦除(Sector Erase)指令
                                    begin  //扇区擦除(Sector Erase)指令发完24-bit地址码就执行结束了,所以直接跳到结束状态
                                        O_qspi_state <= C_FINISH_DONE   ;    
                                    end
                                else if (I_cmd_type[3:0] == 4'b0101) // 页编程(Page Program)指令
                                    begin                              
                                        O_qspi_state        <=  C_WRITE_DATA    ;
                                        R_write_bits_cnt    <=  7               ;                       
                                    end
                                else if (I_cmd_type[3:0] == 4'b0000) // 读Device ID指令
                                    begin             
                                        O_qspi_state        <=  C_READ_WAIT     ;
                                        R_read_bytes_num    <=  2               ; //接收2个数据的Device ID
                                    end                                                         
                                else if (I_cmd_type[3:0] == 4'b0111) // 读数据(Read Data)指令
                                    begin
                                        O_qspi_state        <=  C_READ_WAIT     ;
                                        R_read_bytes_num    <=  256             ;   //接收256个数据        
                                    end                                        
                            end
                    end                  
                C_READ_WAIT: // 读等待状态
                    begin
                        if(R_read_finish)  
                            begin
                                O_qspi_state        <=  C_FINISH_DONE   ;
                                R_data_come_single  <=  1'b0            ;
                            end
                        else
                            begin
                                R_data_come_single  <=  1'b1            ; // 单线模式下读数据标志信号,此信号为高标志正在接收数据
                            end
                    end
                C_WRITE_DATA: // 写数据状态
                    begin
                        if(R_write_bytes_cnt < 256) // 往QSPI Flash中写入 256个数据
                            begin                       
                                if(R_write_bits_cnt > 0) //如果数据还没有发送完
                                    begin                           
                                        O_qspi_mosi         <=  W_rom_out[R_write_bits_cnt] ; //发送bit7~bit1位
                                        R_write_bits_cnt    <=  R_write_bits_cnt  - 1'b1    ;                        
                                    end                 
                                else 
                                    begin                                 
                                        O_qspi_mosi         <=  W_rom_out[0]                ; //发送bit0
                                        R_write_bits_cnt    <=  7                           ;
                                        R_write_bytes_cnt   <=  R_write_bytes_cnt + 1'b1    ;
                                    end
                            end
                        else 
                            begin
                                O_qspi_state    <=  C_FINISH_DONE   ;
                                R_qspi_clk_en   <=  1'b0            ;
                            end
                    end
                C_FINISH_DONE:
                    begin
                        O_qspi_cs           <=  1'b1    ;
                        O_qspi_mosi         <=  1'b0    ;
                        R_qspi_clk_en       <=  1'b0    ;
                        O_done_sig          <=  1'b1    ;
                        R_data_come_single  <=  1'b0    ;
                        O_qspi_state        <=  C_IDLE  ;
                    end
                default:O_qspi_state    <=  C_IDLE      ;
            endcase         
        end
end

//////////////////////////////////////////////////////////////////////////////
// 功能:接收QSPI Flash发送过来的数据    
//////////////////////////////////////////////////////////////////////////////
always @(posedge I_clk_25M)
begin
    if(!I_rst_n)
        begin
            R_read_bytes_cnt    <=  0       ;
            R_read_bits_cnt     <=  0       ;
            R_read_finish       <=  1'b0    ;
            O_read_byte_valid   <=  1'b0    ;
            R_read_data_reg     <=  0       ;
            O_read_data         <=  0       ;
        end
    else if(R_data_come_single)   // 此信号为高表示接收数据从QSPI Flash发过来的数据
        begin
            if(R_read_bytes_cnt < R_read_bytes_num) 
                begin            
                    if(R_read_bits_cnt < 7)  //接收一个Byte的bit0~bit6    
                        begin                         
                            O_read_byte_valid   <=  1'b0                               ;
                            R_read_data_reg     <=  {R_read_data_reg[6:0],I_qspi_miso} ;
                            R_read_bits_cnt     <=  R_read_bits_cnt +   1'b1           ;
                        end
                    else  
                        begin
                            O_read_byte_valid   <=  1'b1                               ;  //一个byte数据有效
                            O_read_data         <=  {R_read_data_reg[6:0],I_qspi_miso} ;  //接收bit7
                            R_read_bits_cnt     <=  0                                  ;
                            R_read_bytes_cnt    <=  R_read_bytes_cnt    +   1'b1       ;
                        end
                end                               
            else 
                begin 
                    R_read_bytes_cnt    <=  0       ;
                    R_read_finish       <=  1'b1    ;
                    O_read_byte_valid   <=  1'b0    ;
                end
        end                               
    else 
        begin
            R_read_bytes_cnt    <=  0       ;
            R_read_bits_cnt     <=  0       ;
            R_read_finish       <=  1'b0    ;
            O_read_byte_valid   <=  1'b0    ;
            R_read_data_reg     <=  0       ;
        end
end         

rom_data rom_data_inst (
  .clka(I_clk_25M), // input clka
  .addra(W_rom_addr), // input [7 : 0] addra
  .douta(W_rom_out) // output [7 : 0] douta
);

endmodule
```
接下来就是写一个测试代码对这个单线模式SPI驱动,为了保证把上面的所有指令都测试一遍,测试代码如下:


```module qspi_top
(
    input         I_clk         ,
    input         I_rst_n       ,

    output        O_qspi_clk    , // SPI总线串行时钟线
    output        O_qspi_cs     , // SPI总线片选信号
    output        O_qspi_mosi   , // SPI总线输出信号线,也是QSPI Flash的输入信号线
    input         I_qspi_miso     // SPI总线输入信号线,也是QSPI Flash的输出信号线
     
);
     
reg [3:0]   R_state             ;
reg [7:0]   R_flash_cmd         ;
reg [23:0]  R_flash_addr        ;
reg         R_clk_25M           ;
reg [4:0]   R_cmd_type          ;
                                
wire        W_done_sig          ;
wire [7:0]  W_read_data         ;
wire        W_read_byte_valid   ;
wire [2:0]  R_qspi_state        ;


////////////////////////////////////////////////////////////////////          
//功能:二分频逻辑          
////////////////////////////////////////////////////////////////////          
always @(posedge I_clk or negedge I_rst_n)
begin
    if(!I_rst_n) 
        R_clk_25M   <=  1'b0        ;
    else 
        R_clk_25M   <=  ~R_clk_25M  ;
end
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
//功能:测试状态机
////////////////////////////////////////////////////////////////////
always @(posedge R_clk_25M or negedge I_rst_n)
begin
    if(!I_rst_n) 
        begin
            R_state         <=  4'd0        ;
            R_flash_addr    <=  24'd0       ;
            R_flash_cmd     <=  8'h00       ;
            R_cmd_type      <=  5'b0_0000   ;
        end
     else 
        begin
            case(R_state)           
                4'd0://读Device ID指令
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ; 
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin 
                                R_flash_cmd  <= 8'h90           ; 
                                R_flash_addr <= 24'd0           ; 
                                R_cmd_type   <= 5'b1_0000       ; 
                            end     
                    end 
                4'd1://写Write disable instruction
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ;
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin 
                                R_flash_cmd <= 8'h04            ; 
                                R_cmd_type  <= 5'b1_0100        ; 
                            end     
                    end                
                4'd2://写使能(Write Enable)指令
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ; 
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin
                                R_flash_cmd <= 8'h06            ; 
                                R_cmd_type  <= 5'b1_0001        ; 
                            end
                    end         
                4'd3:// 扇区擦除(Sector Erase)指令
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ; 
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin 
                                R_flash_cmd <= 8'h20            ; 
                                R_flash_addr<= 24'd0            ; 
                                R_cmd_type  <= 5'b1_0010        ; 
                            end
                    end
            
                4'd4://读状态寄存器1, 当Busy位(状态寄存器1的最低位)为0时表示擦除操作完成
                    begin
                        if(W_done_sig) 
                            begin 
                                if(W_read_data[0]==1'b0) 
                                    begin 
                                        R_flash_cmd <= 8'h00            ; 
                                        R_state     <= R_state + 1'b1   ;
                                        R_cmd_type  <= 5'b0_0000        ; 
                                    end
                                else 
                                    begin 
                                        R_flash_cmd <= 8'h05        ; 
                                        R_cmd_type  <= 5'b1_0011    ; 
                                    end
                            end
                        else 
                            begin 
                                R_flash_cmd <= 8'h05        ; 
                                R_cmd_type  <= 5'b1_0011    ; 
                            end
                    end
                4'd5://写使能(Write Enable)指令
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ; 
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin
                                R_flash_cmd <= 8'h06            ; 
                                R_cmd_type  <= 5'b1_0001        ; 
                            end
                    end             
                4'd6: //页编程操作(Page Program): 把存放在ROM中的数据写入QSPI Flash中
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ; 
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin 
                                R_flash_cmd <= 8'h02            ; 
                                R_flash_addr<= 24'd0            ; 
                                R_cmd_type  <= 5'b1_0101        ; 
                            end
                    end
                4'd7://读状态寄存器1, 当Busy位(状态寄存器1的最低位)为0时表示写操作完成
                    begin
                        if(W_done_sig) 
                            begin 
                                if(W_read_data[0]==1'b0) 
                                    begin 
                                        R_flash_cmd <= 8'h00            ; 
                                        R_state     <= R_state + 1'b1   ;
                                        R_cmd_type  <= 5'b0_0000        ; 
                                    end
                                else 
                                    begin 
                                        R_flash_cmd <= 8'h05        ; 
                                        R_cmd_type  <= 5'b1_0011    ; 
                                    end
                            end
                        else 
                            begin 
                                R_flash_cmd <= 8'h05        ; 
                                R_cmd_type  <= 5'b1_0011    ; 
                            end
                    end           
                4'd8://读256 Bytes
                    begin
                        if(W_done_sig) 
                            begin 
                                R_flash_cmd <= 8'h00            ; 
                                R_state     <= R_state + 1'b1   ;
                                R_cmd_type  <= 5'b0_0000        ; 
                            end
                        else 
                            begin 
                                R_flash_cmd <= 8'h03            ; 
                                R_flash_addr<= 24'd0            ; 
                                R_cmd_type  <= 5'b1_0111        ; 
                            end
                    end
            
                4'd9:// 空闲状态
                    begin
                        R_flash_cmd <= 8'h00            ; 
                        R_state     <= 4'd9             ;
                        R_cmd_type  <= 5'b0_0000        ; 
                    end
                default :   R_state     <= 4'd0         ;
            endcase
        end           
end 
qspi_driver U_qspi_driver
(
.O_qspi_clk          (O_qspi_clk        ), // SPI总线串行时钟线
.O_qspi_cs           (O_qspi_cs         ), // SPI总线片选信号
.O_qspi_mosi         (O_qspi_mosi       ), // SPI总线输出信号线,也是QSPI Flash的输入信号线
.I_qspi_miso         (I_qspi_miso       ), // SPI总线输入信号线,也是QSPI Flash的输出信号线
                   
.I_rst_n             (I_rst_n           ), // 复位信号

.I_clk_25M           (R_clk_25M         ), // 25MHz时钟信号
.I_cmd_type          (R_cmd_type        ), // 命令类型
.I_cmd_code          (R_flash_cmd       ), // 命令码
.I_qspi_addr         (R_flash_addr      ), // QSPI Flash地址

.O_done_sig          (W_done_sig        ), // 指令执行结束标志
.O_read_data         (W_read_data       ), // 从QSPI Flash读出的数据
.O_read_byte_valid   (W_read_byte_valid ), // 读一个字节完成的标志
.O_qspi_state        (R_qspi_state      )  // 状态机,用于在顶层调试用
);
     
wire [35:0]     CONTROL0    ;
wire [69:0]     TRIG0       ;
icon icon_inst (
    .CONTROL0(CONTROL0) // INOUT BUS [35:0]
);

ila ila_inst (
    .CONTROL(CONTROL0)  , // INOUT BUS [35:0]
    .CLK(I_clk)           ,      // IN
    .TRIG0(TRIG0)      // IN BUS [255:0]
);                                                     

assign  TRIG0[7:0]      =   W_read_data         ;                                               
assign  TRIG0[8]        =   W_read_byte_valid   ;   
assign  TRIG0[12:9]     =   R_state             ;        
assign  TRIG0[16:13]    =   R_qspi_state        ;   
assign  TRIG0[17]       =   W_done_sig          ;    
assign  TRIG0[18]       =   I_qspi_miso         ;  
assign  TRIG0[19]       =   O_qspi_mosi         ;  
assign  TRIG0[20]       =   O_qspi_cs           ;  
assign  TRIG0[21]       =   O_qspi_clk          ; 
assign  TRIG0[26:22]    =   R_cmd_type          ; 
assign  TRIG0[34:27]    =   R_flash_cmd         ; 
assign  TRIG0[58:35]    =   R_flash_addr        ; 
assign  TRIG0[59]       =   I_rst_n             ; 


endmodule
```