【FPGA】FPGA实现SPI协议读写FLASH(三)----- SPI读写控制模块设计


写在前面:
FPGA实现通过SPI协议读写FLASH系列相关文章:
SPI通信协议
【FPGA】FPGA实现SPI协议读写FLASH(一)----- M25P16操作概述
【FPGA】FPGA实现SPI协议读写FLASH(二)----- SPI接口驱动模块设计
上篇文章介绍了SPI接口驱动模块的实现,本文将对SPI读写控制模块设计进行详细介绍;本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),FLASH型号:M25P16。

一、功能分析

本项目中SPI读写控制模块(flash_control)包含两个模块:flash写操作模块(flash_write)和flash读操作模块(flash_read),主要实现如下功能:

  1. 接收PC端通过串口传输的数据,用FIFO缓存。
  2. 接收从SPI接口驱动模块读会的数据,用FIFO缓存,并通过串口发送至PC端显示。
  3. 控制SPI接口驱动模块进行读写操作以及数据传输顺序。
  4. 实现8字节数据页编程操作。

二、模块划分

SPI读写控制模块(flash_control)包含flash写操作模块(flash_write)和flash读操作模块(flash_read),模块划分如下:
在这里插入图片描述

三、状态机设计

由于写操作过程有点复杂,如果用一个状态机实现,不利于代码阅读以及维护,因此写操作采用主从状态机方式实现。

1.flash_write模块

主状态机状态转移图:
在这里插入图片描述
主状态机状态说明:
IDLE:空闲状态,等待写请求。
WREN:写使能状态,发送写使能指令。
SE:擦除状态,发送擦除指令及地址。
RDSR:读状态寄存器状态,发送读状态寄存器指令,并判断返回的数据。
PP:页编程状态,发送页编程指令、地址和数据。

从状态机状态转移图:
在这里插入图片描述
从状态机状态说明:
IDLE:空闲状态,等待请求。
CMD:发送指令字节状态。
ADDR:发送地址字节状态。
DATA:发送数据字节状态。
DELAY:延时状态。

2.flash_read模块

状态转移图:
在这里插入图片描述

状态说明:
IDLE:空闲状态,等待读请求。
RDID_CMD:发送读ID指令。
RDID_DATA:接收读回的ID数据。
RDDA_CMD:发送读数据指令。
RDDA_ADDR:发送读数据地址字节。
RDDA_DATA:接收读回的数据。
RD_DONE:读ID或读数据完成。

四、信号说明

1、flash_control模块信号说明

PortI/O TypeDescription
rdid_reqinput读ID请求
rdda_reqinput读数据请求
wr_reqinput写数据请求
rx_data[7:0]input串口接收要写入FLASH的数据
rx_data_vldinput串口接收数据有效标志
readyinputuart_tx模块可以传输数据标志
tx_data[7:0]output串口发送从FLASH读取的数据
tx_data_vldoutput串口发送数据有效标志
rdininputSPI接口模块读回的数据
rw_doneinput读写一字节数据完成标志
doutoutput发送给SPI接口模块要写的数据
reqoutput读写请求
display_id[23:0]output数码管显示读ID数据
display_id_vldoutput显示数据有效标志
modle[2:0]output读写模式 001:读ID 010:读数据 100:写数据

2、flash_write模块信号说明

PortI/O TypeDescription
wr_req]input写数据请求
wr_data[7:0]input要写入的数据
rw_doneinputSPI接口模块读写一字节完成标志
rdsr_data[7:0]inputSPI接口模块读回的状态寄存器数据
dout[7:0]output输出给SPI接口模块要发送的数据
reqoutput写请求
getdata_flagoutput取数据标志,输出给flash_control模块,标志拉高时从fifo中取要写入flash的数据

3、flash_read模块信号说明

PortI/O TypeDescription
rdid_reqinput读ID请求
rdda_reqinput读数据请求
rw_doneinputSPI接口模块读写一字节完成标志
rdin[7:0]inputSPI接口模块读回的数据
dout[7:0]output输出给SPI接口模块要发送的数据
reqoutput读写请求
rddata[23:0]output数码管显示ID
rddata_vldoutput显示数据有效标志

五、代码实现

1、flash_control模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: spi_flash    
//  Module Name: flash_control        
//  Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)                
//  Tool versions: Quartus Prime 18.1             
//  Description: FLASH读写控制模块
//  **************************************************************
`include "param.v"
module flash_control (
    input           clk             ,
    input           rst_n           ,
    //  key_filter
    input           rdid_req        ,       //  读ID请求
    input           rdda_req        ,       //  读数据请求
    input           wr_req          ,       //  写数据请求
    //  uart_rx
    input   [7:0]   rx_data         ,       //  串口接收要写入FLASH的数据
    input           rx_data_vld     ,
    //  uart_tx
    input           ready           ,       //  ready
    output  [7:0]   tx_data         ,       //  串口发送从FLASH读取的数据
    output          tx_data_vld     ,
    //  spi_interface
    input   [7:0]   rdin            ,       //  SPI接口模块读回的数据
    input           rw_done         ,       //  读写一字节数据完成标志
    output  [7:0]   dout            ,       //  发送给SPI接口模块要写的数据
    output          req             ,       //  读写请求
    //  seg_driver
    output  [23:0]  display_id      ,       //  数码管显示读ID
    output          display_id_vld  ,
    output  [2:0]   modle                   //  读写模式 001:读ID 010:读数据  100:写数据
);
    //  信号定义
    //  u_flash_read
    wire    [23:0]  rddata          ;
    wire            rddata_vld      ;

    reg     [23:0]  read_data       ;       //  从SPI接口读回的数据寄存
    reg             read_data_vld   ;

    reg     [2:0]   rw_flag         ;

    reg     [7:0]   rd_data         ;       //  读回的8bit数据寄存
    reg             rd_data_vld     ;

    reg     [7:0]   write_data      ;       //  fifo读出的将要写入FLASH的数据

    wire            read_done       ;
    wire            write_done      ;
    wire            read_req        ;
    wire            write_req       ;
    wire    [7:0]   read_rdin       ;
    wire    [7:0]   write_rdin      ;
    wire    [7:0]   read_dout       ;
    wire    [7:0]   write_dout      ; 
    wire            getdata_flag    ;
    //  wrfifo
    wire    [7:0]   wrfifo_datain   ;
    wire            wrfifo_rdreq    ;
    wire            wrfifo_wrreq    ;
    wire            wrfifo_empty    ;
    wire            wrfifo_full     ;
    wire    [7:0]   wrfifo_q        ;
    wire    [2:0]   wrfifo_usedw    ;
    //  rdfifo
    wire    [7:0]   rdfifo_datain   ;
    wire            rdfifo_rdreq    ;
    wire            rdfifo_wrreq    ;
    wire            rdfifo_empty    ;
    wire            rdfifo_full     ;
    wire    [7:0]   rdfifo_q        ;
    wire    [2:0]   rdfifo_usedw    ;

    //  防止信号冲突
    assign  read_done = (rw_flag[0] | rw_flag[1]) ? rw_done : 1'b0 ;
    assign  write_done = rw_flag[2] ? rw_done : 1'b0 ;
    assign  req = (rw_flag[0] | rw_flag[1]) ? read_req : write_req ;
    assign  read_rdin = (rw_flag[0] | rw_flag[1]) ? rdin : 0 ;
    assign  write_rdin = rw_flag[2] ? rdin : 0 ;
    assign  dout = (rw_flag[0] | rw_flag[1]) ? read_dout : write_dout ;

    //  模块例化
    flash_read u_flash_read (
    /*input           */.clk             (clk           ),
    /*input           */.rst_n           (rst_n         ),
    /*//  key_filter*/  
    /*input           */.rdid_req        (rdid_req      ),       //  读ID请求
    /*input           */.rdda_req        (rdda_req      ),       //  读数据请求
    /*//  spi_interface*/   
    /*input           */.rw_done         (read_done     ),       //  SPI接口模块读写一字节完成标志
    /*input   [7:0]   */.rdin            (read_rdin     ),       //  SPI接口模块读回的数据
    /*output  [7:0]   */.dout            (read_dout     ),       //  输出给SPI接口模块要发送的数据
    /*output          */.req             (read_req      ),       //  读写请求
    
    /*output  [23:0]  */.rddata          (rddata        ),       //  数码管显示ID数据
    /*output          */.rddata_vld      (rddata_vld    )        //  数据有效标志
    );

    flash_write u_flash_write (
        /*input           */.clk             (clk       ),
        /*input           */.rst_n           (rst_n     ),
        /*//  key_filter*/
        /*input           */.wr_req          (wr_req    ),       //  写数据请求
        /*input   [7:0]   */.wr_data         (write_data),       //  写数据
        /*//spi_interface*/
        /*input           */.rw_done         (write_done),       //  SPI接口模块读写一字节完成标志
        /*input   [7:0]   */.rdsr_data       (write_rdin),       //  SPI接口模块读回的状态寄存器数据
        /*output  [7:0]   */.dout            (write_dout),       //  输出给SPI接口模块要发送的数据
        /*output          */.req             (write_req ),       //  写请求
        /*//  flash_control*/
        /*output          */.getdata_flag    (getdata_flag)
    );

    //  read_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            read_data <= 0 ;
            read_data_vld <= 0 ;
        end
        else if(rw_flag == 3'b001 && rddata_vld)begin
            read_data <= rddata ;
            read_data_vld <= rddata_vld ;
        end
        else if(rw_flag == 3'b010 && rddata_vld)begin
            read_data[7:0] <= rddata ;
            read_data_vld <= rddata_vld ;
        end
        else begin
            read_data <= 0 ;
            read_data_vld <= 0 ;
        end
    end

    //  rw_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rw_flag <= 0 ;
        end
        else if(rdid_req)begin
            rw_flag <= 3'b001 ;
        end
        else if(rdda_req)begin
            rw_flag <= 3'b010 ;
        end
        else if(wr_req)begin
            rw_flag <= 3'b100 ;
        end
    end

    //  rd_data rd_data_vld
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rd_data <= 0 ;
            rd_data_vld <= 0 ;
        end
        else if(rw_flag[1])begin
            rd_data <= read_data[7:0] ;
            rd_data_vld <= read_data_vld ;
        end
    end

    //  write_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            write_data <= 0 ;
        end
        else if(wrfifo_empty)begin
            write_data <= 0 ;
        end
        else begin
            write_data <= wrfifo_q ;
        end
    end

    //  wrfifo
    wrfifo	wrfifo_inst (
	    .aclr       ( ~rst_n        ),
	    .clock      ( clk           ),
	    .data       ( wrfifo_datain ),
	    .rdreq      ( wrfifo_rdreq  ),
	    .wrreq      ( wrfifo_wrreq  ),

	    .empty      ( wrfifo_empty  ),
	    .full       ( wrfifo_full   ),
	    .q          ( wrfifo_q      ),
	    .usedw      ( wrfifo_usedw  )
	);
    assign  wrfifo_datain = rx_data ;
    assign  wrfifo_wrreq = rx_data_vld && ~wrfifo_full ;
    assign  wrfifo_rdreq = getdata_flag && write_done && ~wrfifo_empty ;   // getdata_flag取数据标志拉高时,写完一字节数据再从FIFO中读取下一字节数据
    // rdfifo
    rdfifo	rdfifo_inst (
	    .aclr       ( ~rst_n        ),
	    .clock      ( clk           ),
	    .data       ( rdfifo_datain ),
	    .rdreq      ( rdfifo_rdreq  ),
	    .wrreq      ( rdfifo_wrreq  ),

	    .empty      ( rdfifo_empty  ),
	    .full       ( rdfifo_full   ),
	    .q          ( rdfifo_q      ),  
	    .usedw      ( rdfifo_usedw  )
	);
    assign  rdfifo_datain = rd_data ;
    assign  rdfifo_wrreq = rd_data_vld ;
    assign  rdfifo_rdreq =  ~rdfifo_empty && ready ;

    //  输出
    assign  display_id = (rw_flag[0] && read_data_vld) ? read_data : 0 ;
    assign  display_id_vld = read_data_vld ;
    assign  modle = rw_flag ;
    assign  tx_data = rdfifo_q ;
    assign  tx_data_vld = rdfifo_rdreq ;

endmodule

2、flash_write模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: spi_flash    
//  Module Name: flash_write         
//  Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)                
//  Tool versions: Quartus Prime 18.1             
//  Description: SPI写FLASH模块
//  **************************************************************
`include "param.v"
module flash_write (
    input           clk             ,
    input           rst_n           ,
    //  key_filter
    input           wr_req          ,       //  写数据请求
    input   [7:0]   wr_data         ,       //  写数据
    //  flash_control
    input           rw_done         ,       //  SPI接口模块读写一字节完成标志
    input   [7:0]   rdsr_data       ,       //  SPI接口模块读回的状态寄存器数据
    output  [7:0]   dout            ,       //  输出给SPI接口模块要发送的数据
    output          req             ,       //  写请求
    output          getdata_flag            //  取数据标志,输出给flash_control模块,标志拉高时从fifo中取要写入flash的数据
);
    //  参数定义
    //  主状态机状态
    localparam  M_IDLE  =   5'b00001 ,      //  空闲状态
                M_WREN  =   5'b00010 ,      //  写使能
                M_SE    =   5'b00100 ,      //  擦除
                M_RDSR  =   5'b01000 ,      //  读状态寄存器
                M_PP    =   5'b10000 ;      //  页编程
    //  从状态机状态
    localparam  S_IDLE  =   5'b00001 ,      //  空闲状态
                S_CMD   =   5'b00010 ,      //  发送指令
                S_ADDR  =   5'b00100 ,      //  发送地址
                S_DATA  =   5'b01000 ,      //  发送/接收数据
                S_DELAY =   5'b10000 ;      //  延时

    //  信号定义
    reg     [4:0]       m_state_c       ;
    reg     [4:0]       m_state_n       ;

    reg     [4:0]       s_state_c       ;
    reg     [4:0]       s_state_n       ;

    reg     [3:0]       byte_num        ;       //  字节数
    reg     [3:0]       cnt_byte        ;       //  字节计数器
    wire                add_cnt_byte    ;
    wire                end_cnt_byte    ;

    reg     [27:0]      delay           ;       //  延时
    reg     [27:0]      cnt_delay       ;       //  延时计数器
    wire                add_cnt_delay   ;
    wire                end_cnt_delay   ;

    reg     [7:0]       wr_data_r       ;       //  写数据寄存
    reg     [7:0]       command         ;       //  指令寄存
    reg                 wip             ;       //  设置为1时,表示设备处于工作状态,当设置为0时,表示设备处于空闲状态
    reg                 wel             ;       //  当设置为1时,内部写使能被设置,当设置为0时,表示没有接收写状态寄存器,页编程或擦除指令
    reg     [7:0]       dout_data       ;       //  输出数据寄存
    reg                 wr_flag         ;       //  输出给SPI接口模块请求信号寄存
    reg     [1:0]       wren_flag       ;       //  写使能标志,区分m_rdsr2m_idle和m_rdsr2m_wren
    reg                 data_flag       ;       //  从flash_control模块取数据标志寄存

    //  主状态机状态转移条件
    wire                m_idle2m_wren   ; 
    wire                m_wren2m_se     ; 
    wire                m_wren2m_pp     ; 
    wire                m_se2m_rdsr     ; 
    wire                m_rdsr2m_pp     ; 
    wire                m_rdsr2m_idle   ; 
    wire                m_rdsr2m_wren   ; 
    wire                m_pp2m_rdsr     ; 
    //  从状态机状态转移条件
    wire                s_idle2s_cmd    ;
    wire                s_cmds_addr     ;
    wire                s_cmd2s_data    ;
    wire                s_cmd2s_delay   ;
    wire                s_addr2s_data   ;
    wire                s_addr2s_delay  ;
    wire                s_data2s_delay  ;
    wire                s_delay2s_idle  ;

/************************主状态机开始****************************/
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            m_state_c <= M_IDLE ;
        end
        else begin
            m_state_c <= m_state_n ;
        end
    end
    always @(*)begin
        case (m_state_c)
            M_IDLE: begin
                if(m_idle2m_wren)
                    m_state_n = M_WREN ;
                else
                    m_state_n = m_state_c ;
            end
            M_WREN: begin
                if(m_wren2m_se)
                    m_state_n = M_SE ;
                else if(m_wren2m_pp)
                    m_state_n = M_PP ;
                else
                    m_state_n = m_state_c ;
            end
            M_SE: begin
                if(m_se2m_rdsr)
                    m_state_n = M_RDSR ;
                else
                    m_state_n = m_state_c ;
            end
            M_RDSR: begin
                if(m_rdsr2m_pp)
                    m_state_n = M_PP ;
                else if(m_rdsr2m_idle)
                    m_state_n = M_IDLE ;
                else if(m_rdsr2m_wren)
                    m_state_n = M_WREN ;
                else
                    m_state_n = m_state_c ;
            end
            M_PP: begin
                if(m_pp2m_rdsr)
                    m_state_n = M_RDSR ;
                else
                    m_state_n = m_state_c ;
            end
            default: m_state_n = M_IDLE ;
        endcase
    end
    //  状态转移条件
    assign  m_idle2m_wren = m_state_c == M_IDLE && wr_req ;       //  收到写请求,进入写使能状态
    assign  m_wren2m_se   = m_state_c == M_WREN && s_delay2s_idle && ~wren_flag[0] ;    //   写使能延时结束,进入擦除状态 
    assign  m_wren2m_pp   = m_state_c == M_WREN && s_delay2s_idle && wren_flag[0] ;     //   写使能延时结束,进入页编程状态 
    assign  m_se2m_rdsr   = m_state_c == M_SE   && s_delay2s_idle ;     //   擦除延时结束,进入读状态寄存器状态
    assign  m_rdsr2m_pp   = m_state_c == M_RDSR && s_delay2s_idle && wip == 1'b0 && wel == 1'b1 ;   //   读状态寄存器wel位为高电平,直接跳转到页编程状态
    assign  m_rdsr2m_idle = m_state_c == M_RDSR && s_delay2s_idle && wel == 1'b0 && wip == 1'b0 && wren_flag[1] ;   //  一次写操作结束,回到空闲状态
    assign  m_rdsr2m_wren = m_state_c == M_RDSR && s_delay2s_idle && wel == 1'b0 && wip == 1'b0 && wren_flag[0] ;   //  wel为低电平,进入写使能状态
    assign  m_pp2m_rdsr   = m_state_c == M_PP   && s_delay2s_idle ;     //   页编程延时结束,进入读状态寄存器状态  
/************************主状态机结束****************************/

/************************从状态机开始****************************/
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            s_state_c <= S_IDLE ;
        end
        else begin
            s_state_c <= s_state_n ;
        end
    end
    always @(*)begin
        case (s_state_c)
            S_IDLE: begin
                if(s_idle2s_cmd)
                    s_state_n = S_CMD ;
                else
                    s_state_n = s_state_c ;
            end
            S_CMD: begin
                if(s_cmds_addr)
                    s_state_n = S_ADDR ;
                else if(s_cmd2s_data)
                    s_state_n = S_DATA ;
                else if(s_cmd2s_delay)
                    s_state_n = S_DELAY ;
                else
                    s_state_n = s_state_c ;
            end
            S_ADDR: begin
                if(s_addr2s_data)
                    s_state_n = S_DATA ;
                else if(s_addr2s_delay)
                    s_state_n = S_DELAY ;
                else
                    s_state_n = s_state_c ;
            end
            S_DATA: begin
                if(s_data2s_delay)
                    s_state_n = S_DELAY ;
                else
                    s_state_n = s_state_c ;
            end
            S_DELAY: begin
                if(s_delay2s_idle)
                    s_state_n = S_IDLE ;
                else
                    s_state_n = s_state_c ;
            end
            default: s_state_n = S_IDLE ;
        endcase
    end
    assign  s_idle2s_cmd    =   s_state_c == S_IDLE  && m_state_c != M_IDLE ;       //  主状态机非空闲状态下,从状态机进入发送指令状态
    assign  s_cmds_addr     =   s_state_c == S_CMD   && (m_state_c == M_SE || m_state_c == M_PP) && cnt_byte == 1 ;      //     主状态机擦除和页编程状态下,指令发送完成,进入发送地址状态  
    assign  s_cmd2s_data    =   s_state_c == S_CMD   && m_state_c == M_RDSR && cnt_byte == 1 ;      //      主状态机读状态寄存器状态下,指令发送完成,进入接收状态寄存器数据状态
    assign  s_cmd2s_delay   =   s_state_c == S_CMD   && m_state_c == M_WREN && end_cnt_byte ;       //      主状态机写使能指令发送完成,进入延时状态
    assign  s_addr2s_data   =   s_state_c == S_ADDR  && m_state_c == M_PP && cnt_byte == 4 ;        //      主状态机页编程状态下,地址发送完成,进入发送数据状态
    assign  s_addr2s_delay  =   s_state_c == S_ADDR  && m_state_c == M_SE && end_cnt_byte ;     //     主状态机擦除状态下,地址发送完成,进入延时状态
    assign  s_data2s_delay  =   s_state_c == S_DATA  && (m_state_c == M_PP || m_state_c == M_RDSR) && end_cnt_byte ;     //     主状态机页编程状态下,数据发送完成,进入延时状态
    assign  s_delay2s_idle  =   s_state_c == S_DELAY && end_cnt_delay ;     //  延时结束,回到空闲状态
/************************从状态机结束****************************/

    //  wr_data_r
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            wr_data_r <= 0 ;
        end
        else if(data_flag)begin     //  取数据标志拉高,寄存要写入flash的数据
            wr_data_r <= wr_data ;
        end
        else if(m_rdsr2m_idle)begin
            wr_data_r <= 0 ;
        end
    end

    //  command
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            command <= 0 ;
        end
        else if(m_idle2m_wren | m_rdsr2m_wren)begin     
            command <= `CMD_WREN ;      //  写使能指令
        end
        else if(m_wren2m_se)begin
            command <= `CMD_SE ;        //  擦除指令
        end
        else if(m_se2m_rdsr | m_pp2m_rdsr)begin
            command <= `CMD_RDSR ;      //  读状态寄存器指令
        end
        else if(m_wren2m_pp | m_rdsr2m_pp)begin
            command <= `CMD_PP ;        //  页编程指令
        end
    end

    //  byte_num
    always @(*)begin
        case (m_state_c)
            M_WREN: byte_num = 1 ; 
            M_SE  : byte_num = 4 ; 
            M_RDSR: byte_num = 2 ; 
            M_PP  : byte_num = 12 ;     //  页编程8byte数据 1bytePP指令字节+3byte地址字节+8byte数据字节
            default: byte_num = 0 ;
        endcase
    end

    //  cnt_byte
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_byte <= 0 ;
        end
        else if(add_cnt_byte)begin
            if(end_cnt_byte)begin
                cnt_byte <= 0 ;
            end				
            else begin	    
                cnt_byte <= cnt_byte + 1 ;
            end 		    
        end
    end
    assign	add_cnt_byte = rw_done ;    //  读写一字节数据完成,字节计数器+1
    assign	end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1) ;

    //  delay
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            delay <= 0 ;
        end
        else if(m_wren2m_se)begin
            delay <= `DELAY_SE ;    //  擦除延时
        end
        else if(m_wren2m_pp | m_rdsr2m_pp)begin
            delay <= `DELAY_PP ;    //  页编程延时
        end
        else if(m_idle2m_wren | m_se2m_rdsr | m_rdsr2m_wren | m_pp2m_rdsr)begin
            delay <= `DELAY_CSH ;   //  指令间延时
        end
        else if(m_rdsr2m_idle)begin
            delay <= 0 ;
        end
    end

    //  cnt_delay
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_delay <= 0 ;
        end
        else if(add_cnt_delay)begin
            if(end_cnt_delay)begin
                cnt_delay <= 0 ;
            end				
            else begin	    
                cnt_delay <= cnt_delay + 1 ;
            end 		    
        end
    end
    assign	add_cnt_delay = s_state_c == S_DELAY ;
    assign	end_cnt_delay = add_cnt_delay && (cnt_delay == delay - 1) ;

    //  wip,wel
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            wip <= 0 ;
            wel <= 0 ;
        end
        else begin
            wip <= rdsr_data[0] ;   //  设置为1时,表示设备处于工作状态,当设置为0时,表示设备处于空闲状态
            wel <= rdsr_data[1] ;   //  当设置为1时,内部写使能被设置,当设置为0时,表示没有接收写状态寄存器,页编程或擦除指令
        end
    end

    //  dout_data
    always @(*)begin
        if(m_state_c == M_WREN)begin   //  发送写使能指令
            dout_data = `CMD_WREN ;
        end
        else if(m_state_c == M_SE && s_state_c != S_DELAY)begin   //  发送擦除指令和地址
            case (cnt_byte)
                0: dout_data = `CMD_SE ;
                1: dout_data = `ADDR_HIGH ;
                2: dout_data = `ADDR_MID ;
                3: dout_data = `ADDR_LOW ;
                default: dout_data = 0 ;
            endcase
        end
        else if(m_state_c == M_RDSR)begin   //  发送读状态寄存器指令
            dout_data = `CMD_RDSR ;
        end
        else if(m_state_c == M_PP&& s_state_c != S_DELAY)begin   //  发送页编程指令、地址、数据
            case (cnt_byte)
                0: dout_data = `CMD_PP ;
                1: dout_data = `ADDR_HIGH ;
                2: dout_data = `ADDR_MID ;
                3: dout_data = `ADDR_LOW ;
                // 4: dout_data = wr_data_r ;   //  单字节写操作
                default: dout_data = wr_data_r ;// dout_data = 0 ;
            endcase
        end
        else begin
            dout_data = 0 ;
        end
    end

    //  wr_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            wr_flag <= 1'b0 ;
        end
        else if(s_state_c == S_CMD || s_state_c == S_ADDR || s_state_c == S_DATA)begin
            wr_flag <= 1'b1 ;
        end
        else begin
            wr_flag <= 1'b0 ;
        end
    end

    //  wren_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            wren_flag <= 2'b00 ;
        end
        else if(m_wren2m_se)begin
            wren_flag <= 2'b01 ;
        end
        else if(m_pp2m_rdsr)begin
            wren_flag <= 2'b10 ;
        end
    end

    //  data_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            data_flag <= 1'b0 ;
        end
        else if(m_state_c == M_PP && cnt_byte > 3)begin     //  页编程状态下,发送完指令和地址字节后,取数据标志拉高
            data_flag <= 1'b1 ;
        end
        else begin
            data_flag <= 1'b0 ;
        end
    end

    //  输出
    assign dout = dout_data ;
    assign req = wr_flag ;
    assign getdata_flag = data_flag ;


endmodule

3、flash_read模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: spi_flash    
//  Module Name: flash_read         
//  Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)                
//  Tool versions: Quartus Prime 18.1             
//  Description: SPI读FLASH模块
//  **************************************************************
`include "param.v"
module flash_read (
    input           clk             ,
    input           rst_n           ,
    //  key_filter
    input           rdid_req        ,       //  读ID请求
    input           rdda_req        ,       //  读数据请求
    //  flash_control
    input           rw_done         ,       //  SPI接口模块读写一字节完成标志
    input   [7:0]   rdin            ,       //  SPI接口模块读回的数据   
    output  [7:0]   dout            ,       //  输出给SPI接口模块要发送的数据
    output          req             ,       //  读写请求
    output  [23:0]  rddata          ,       //  数码管显示ID
    output          rddata_vld              //  数据有效标志
);

    //  参数定义
    //  读数据状态机参数定义
    parameter   IDLE        = 7'b000_0001   ,
                RDID_CMD    = 7'b000_0010   ,   //  发送读ID命令
                RDID_DATA   = 7'b000_0100   ,   //  接收读回ID数据
                RDDA_CMD    = 7'b000_1000   ,   //  发送读数据命令    
                RDDA_ADDR   = 7'b001_0000   ,   //  发送读数据地址
                RDDA_DATA   = 7'b010_0000   ,   //  接收读回数据
                RD_DONE     = 7'b100_0000   ;

    //  信号定义
    reg     [6:0]       state_c             ;
    reg     [6:0]       state_n             ;

    reg     [3:0]       cnt_byte            ;       //  字节计数器
    wire                add_cnt_byte        ;
    wire                end_cnt_byte        ;

    reg     [3:0]       byte_num            ;       //  字节数
    reg     [7:0]       dout_data           ;       //  输出数据寄存
    reg                 rw_req              ;       //  读写请求
    reg                 rd_flag             ;       //  读数据模式 0:读ID  1:读数据
    reg     [23:0]      rd_data             ;       //  读回的数据寄存
    reg                 rd_data_vld         ;       //  读回的数据有效标志寄存
    reg                 done                ;       //  rw_done寄存
    //  状态转移条件
    wire                idle2rdidcmd        ;
    wire                idle2rddacmd        ;
    wire                rdidcmd2rdiddata    ;
    wire                rddacmd2rddaaddr    ;
    wire                rddaaddr2rddadata   ;
    wire                rdiddata2rddone     ;
    wire                rddadata2rddone     ;
    wire                rddone2idle         ;

    //  状态机
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n ;
        end
    end
    always @(*)begin
        case (state_c)
            IDLE: begin
                if(idle2rdidcmd)
                    state_n = RDID_CMD ;
                else if(idle2rddacmd)
                    state_n = RDDA_CMD ;
                else 
                    state_n = state_c ;
            end
            RDID_CMD: begin
                if(rdidcmd2rdiddata)
                    state_n = RDID_DATA ;
                else 
                    state_n = state_c ;
            end
            RDID_DATA: begin
                if(rdiddata2rddone)
                    state_n = RD_DONE ;
                else 
                    state_n = state_c ;
            end
            RDDA_CMD: begin
                if(rddacmd2rddaaddr)
                    state_n = RDDA_ADDR ;
                else 
                    state_n = state_c ;
            end 
            RDDA_ADDR: begin
                if(rddaaddr2rddadata)
                    state_n = RDDA_DATA ;
                else 
                    state_n = state_c ;
            end
            RDDA_DATA: begin
                if(rddadata2rddone)
                    state_n = RD_DONE ;
                else 
                    state_n = state_c ;
            end
            RD_DONE: begin
                if(rddone2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            default: state_n = IDLE ;
        endcase
    end
    //  状态转移条件
    assign idle2rdidcmd        = state_c == IDLE        && rdid_req     ;   //  读ID请求拉高,进入发送读ID指令状态
    assign idle2rddacmd        = state_c == IDLE        && rdda_req     ;   //  读数据请求拉高,进入发送读数据指令状态
    assign rdidcmd2rdiddata    = state_c == RDID_CMD    && end_cnt_byte ;   //  读ID指令发送完成,进入接收ID数据状态
    assign rddacmd2rddaaddr    = state_c == RDDA_CMD    && end_cnt_byte ;   //  读数据指令发送完成,进入发送读地址状态
    assign rddaaddr2rddadata   = state_c == RDDA_ADDR   && end_cnt_byte ;   //  读地址发送完成,进入接收数据状态
    assign rdiddata2rddone     = state_c == RDID_DATA   && end_cnt_byte ;   //  读ID数据完成
    assign rddadata2rddone     = state_c == RDDA_DATA   && end_cnt_byte ;   //  读数据完成
    assign rddone2idle         = state_c == RD_DONE     && (1'b1)       ;   

    //  rd_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rd_flag <= 1'b1 ;
        end
        else if(rdid_req)begin
            rd_flag <= 1'b0 ;
        end
        else if(rdda_req | rddone2idle)begin
            rd_flag <= 1'b1 ;
        end    
    end

    //  byte_num
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            byte_num <= 0 ;
        end
        else if(rd_flag)begin       //  读数据模式
            case (state_c)
                RDDA_CMD : byte_num <= 1 ;
                RDDA_DATA : byte_num <= 8 ;
                RDDA_ADDR : byte_num <= 3 ;
                default: byte_num <= 0 ;
            endcase
            // if(state_c == RDDA_CMD)begin
            //     byte_num <= 1 ;
            // end
            // else if(state_c == RDDA_DATA)begin
            //     byte_num <= 8 ;
            // end
            // else if(state_c == RDDA_ADDR)begin
            //     byte_num <= 3 ;
            // end
            // else begin
            //     byte_num <= 0 ;
            // end
        end
        else if(~rd_flag)begin       //  读ID模式
            case (state_c)
                RDID_CMD : byte_num <= 1 ;
                RDID_DATA : byte_num <= 3 ;
                default: byte_num <= 0 ;
            endcase
            // if(state_c == RDID_CMD)begin
            //     byte_num <= 1 ;
            // end
            // else if(state_c == RDID_DATA)begin
            //     byte_num <= 3 ;
            // end
            // else begin
            //     byte_num <= 0 ;
            // end
        end
    end

    //  cnt_byte
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_byte <= 0 ;
        end
        else if(add_cnt_byte)begin
            if(end_cnt_byte)begin
                cnt_byte <= 0 ;
            end
            else begin
                cnt_byte <= cnt_byte + 1 ;
            end
        end
    end
    assign add_cnt_byte = done ;    //  读写一字节数据完成,字节计数器+1
    assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1) ;

    //  dout_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            dout_data <= 0 ;
        end
        else if(state_c == RDID_CMD)begin   //  输出读ID指令
            dout_data <= `CMD_RDID ;    
        end
        else if(state_c == RDDA_CMD)begin
            dout_data <= `CMD_RDDA ;        //  输出读数据指令
        end
        else if(state_c == RDDA_ADDR)begin  //  输出地址字节
            case (cnt_byte)
                0: dout_data <= `ADDR_HIGH ;
                1: dout_data <= `ADDR_MID  ;
                2: dout_data <= `ADDR_LOW  ;
                default: dout_data <= dout_data ;
            endcase
        end
        else begin
            dout_data <= 0 ;
        end
    end

    //  rw_req
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rw_req <= 1'b0 ;
        end
        else if(idle2rdidcmd | idle2rddacmd)begin
            rw_req <= 1'b1 ;
        end
        else if(rdiddata2rddone | rddadata2rddone)begin
            rw_req <= 1'b0 ;
        end
    end

    //  rd_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rd_data <= 0 ;
        end
        else if((state_c == RDID_DATA || state_c == RD_DONE) && done)begin  //  接收ID数据
            rd_data[23 - 8 * cnt_byte -:8] <= rdin ;
        end
        else if((state_c == RDDA_DATA || state_c == RD_DONE) && done)begin  //  接收数据
            rd_data <= rdin ;
        end
        else if(rddone2idle)begin
            rd_data <= 0 ;
        end
        else begin
            rd_data <= rd_data ;
        end
    end

    //  done
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            done <= 1'b0 ;
        end
        else if(rw_done)begin
            done <= 1'b1 ;
        end
        else begin
            done <= 1'b0 ;
        end
    end

    //  rd_data_vld
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rd_data_vld <= 1'b0 ;
        end
        else if(state_c == RDDA_DATA && done)begin
            rd_data_vld <= 1'b1 ;
        end
        else begin
            rd_data_vld <= 1'b0 ;
        end
    end

    //  输出
    assign  dout = dout_data ;
    assign  req  = rw_req ;
    assign  rddata = rd_data ;
    assign  rddata_vld = (state_c == RDDA_DATA) ? rd_data_vld : rddone2idle ;

endmodule

六、仿真测试

1、写数据仿真
在这里插入图片描述
在这里插入图片描述

2、读ID仿真
在这里插入图片描述
3、读数据仿真
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值