【FPGA】SPI协议详解及对flash读写操作

FPGA基于SPI实现对flash读写操作

  • 概括
  • 一、SPI协议、flash讲解
    • 1、SPI协议
    • 2、flash
      • (1)WREN
      • (2)RDID
      • (3)WRSR
      • (4)READ
      • (5)PP
      • (6)SE
  • 二、状态图
  • 三、代码
    • 1、代码分析
    • 2、全部代码
      • (1)param
      • (2)test
      • (3)spi_master
      • (4)wr_control
      • (5)flash_write
      • (6)flash_write
      • (7)key_filter_fsm
      • (8)uart
  • 四、验证

概括

内容用FPGA芯片Cylone IV E:EP4CE6F17C8 实现对flash读写操作,数据通过uart寄存在FIFO_0,按键_0按下读取数据通过SPI协议写入flash,再按键_1按下通过SPI协议读出数据寄存在FIFO_2经uart协议输出

一、SPI协议、flash讲解

1、SPI协议

SPI(Serial Peripheral interface)是由摩托罗拉公司定义的一种串行外围设备接口, 是一种全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于 PCB 的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了 SPI 通信协议,如 FLASH、AD 转换器等。
【FPGA】SPI协议详解及对flash读写操作_第1张图片
SPI 协议支持一主多从、全双工、半双工模式。
SPI 通信需要四根线,分别为 cs_n、sclk、mosi 和 miso。其中 cs_n、sclk、mosi 是由主机输出给从机,而 miso 由从机输出给主机。cs_n 用于控制从机是否被选中,也就是说只有片选信号有效时,对从机的操作才有效;sclk 是由主机产生的同步时钟,用于同步数据;mosi 和 miso 是主机发送和接收的数据脚。传输数据时,以 MSB 的形式传输

SPI 通信一般有 4 种不同的模式,不同的从设备在出厂时被厂家配置为其中几种通信模式,通信模式是不允许用户修改的。主设备和从设备必须在同一模式下进行通信,否则数据会接收错误。SPI 的通信模式是由 CPOL(时钟极性)和 CPHA(时钟相位)来决定的;
模式 0:CPOL = 0,CPHA = 0;
模式 1:CPOL = 0,CPHA = 1;
模式 2:CPOL = 1,CPHA = 0;
模式 3:CPOL = 1,CPHA = 1。
【FPGA】SPI协议详解及对flash读写操作_第2张图片
当 CPOL = 1 时,sclk 在空闲时为高电平,发起通信后的第一个时钟沿为下降沿;
当 CPOL = 0 时,SPI 时钟信号 sclk 空闲时为低电平,发起通信后的第一个时钟沿为上升沿
当 CPHA = 1 时,时钟的第一个变化沿(上升沿或者下降沿)数据开始改变, 那么也就意味着时钟的第 2 个变化沿(与第一个变化沿相反)锁存数据;
当 CPHA = 0 时,数据在时钟的第一个变化沿之前就已经改变,并且保持稳定,也就意味着在时钟的第一个变化沿锁存数据。

2、flash

M25P16 是一款带有写保护机制的串行 Flash 存储芯片,容量为 16Mbit(2MB);一共
分为 32 个 sector,每个 sector 共 256B 存储容量,每个 sector 擦写次数为 10 万次、数
据保存期限至少 20 年。支持 SPI 通信模式 0 和模式 3。在对 Flash 芯片写数据之前,需要先执行擦除操作,将所有数据变为 FF。

flash指令表
【FPGA】SPI协议详解及对flash读写操作_第3张图片
实现读写操作只用了WREN、RDID、WRSR、READ、PP、SE命令

(1)WREN

在手册已经说了写使能必须在每个PP、SE、BE、WRSR指令前设置,在写使能发送结束后片选信号拉高
【FPGA】SPI协议详解及对flash读写操作_第4张图片

(2)RDID

读取标识(RDID)指令允许读取8位的制造商标识,然后是两个字节的设备标识。制造商标识由JEDEC指定,意法半导体的识别值为20小时。设备标识由设备制造商指定,表示第一个字节的内存类型(20小时),以第二个字节表示设备的内存容量(15小时)。同样数据读取结束后片选信号拉高
【FPGA】SPI协议详解及对flash读写操作_第5张图片

(3)WRSR

读取状态寄存器(RDSR)指令允许读取状态寄存器。状态寄存器可以在任何时候被读取,即使在一个程序、擦除或写状态寄存器周期正在进行中。这个命令我认为是在读取其状态的一个命令可有可无有需要可以去看手册,其时序图如下
【FPGA】SPI协议详解及对flash读写操作_第6张图片

(4)READ

首先通过驱动芯片选择低来选择设备。读取数据字节(READ)指令的指令代码后面是一个3字节地址,每个位在串行时钟的上升边缘期间被锁定然后,在该地址的内存内容在串行数据输出上移出,在串行时钟的下降边缘期间
这里指出下flash只支持SPI的模式0、模式3,
时序图如下
【FPGA】SPI协议详解及对flash读写操作_第7张图片

(5)PP

页面程序(PP)指令允许在内存中编程字节(将位从1改为0)当页面程序周期正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在自定时页面程序周期中,正在写入(WIP)位为1,完成时为0。
【FPGA】SPI协议详解及对flash读写操作_第8张图片

(6)SE

扇区擦除(SE)指令将选定扇区内的所有位设置为1(FFh)。在接受它之前,必须先执行了写入启用(WREN)指令。在写入启用(WREN)指令被解码后,设备将设置写入启用锁存器(WEL)。扇区擦除(SE)指令通过驱动芯片选择低输入,然后是指令代码和串行数据输入.
【FPGA】SPI协议详解及对flash读写操作_第9张图片
下表有时钟频率、擦除、写入、片选信号拉高再次拉低的时间
【FPGA】SPI协议详解及对flash读写操作_第10张图片
【FPGA】SPI协议详解及对flash读写操作_第11张图片

二、状态图

【FPGA】SPI协议详解及对flash读写操作_第12张图片
【FPGA】SPI协议详解及对flash读写操作_第13张图片

三、代码

1、代码分析

always @(*)begin
        case (spi_mode)
           0 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 0;
           end
           else if(cnt_sck > `SCK_HALF-1)begin
                sck <= 1;
           end 
           else begin
              sck <= 0; 
           end  
           end
           1 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 1;
           end
           else if(cnt_sck > `SCK_HALF-1)begin
                sck <= 0;
           end 
           else begin
               sck <= 0; 
           end 
           end
           2 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1 ))begin
               sck <= 1;
           end
           else if(cnt_sck > `SCK_HALF-1 )begin
                sck <= 0;
           end  
           else begin
               sck <= 1;
           end
           end
           3 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 0;
           end
           else if(cnt_sck > `SCK_HALF-1 )begin
                sck <= 1;
           end  
           else begin
               sck <= 1;
           end
           end 
            default: sck <= sck;
        endcase
    end

SPI时钟的产生思路是以时钟去对齐数据,这里SPI四种模式都写出来了的再数据读取只写了模式0
【FPGA】SPI协议详解及对flash读写操作_第14张图片
数据传输时序图
在这里插入图片描述
因为这次没画完时序图,代码写的时候有点小乱,整里了下

2、全部代码

(1)param

//参数定义
//spi时钟参数
`define  SCK_PERIOD  20
`define  SCK_HALF    10



//spi command
`define  CMD_WREN       8'h06               // 
`define  CMD_WRDI       8'h04               // 
`define  CMD_RDID       8'h9f               // 
`define  CMD_RDSR       8'h05               // 
`define  CMD_WRSR       8'h01               // 
`define  CMD_READ       8'h03               // 
`define  CMD_FAST_READ  8'h0b               // 
`define  CMD_PP         8'h02               // 
`define  CMD_SE         8'hd8               // 
`define  CMD_BE         8'hc7               // 
`define  CMD_DP         8'hb9               // 
`define  CMD_RES        8'hab               //  

//spi byte
`define  CMD_BYTE        1               // 命令1字节 
`define  ADDR_BYTE       3               // 地址3字节 
`define  ID_BYTE         3               // id3字节 
`define  DATA_BYTE       1               // id3字节 
`define  RDSR_BYTE       100             // 读状态寄存器最大可读1000次
`define  DELAY_5MS       250_000         // 5ms 数据写入后需要等待5ms才能读出

//波特率
`define  BAUD_9600   5208
`define  BAUD_19200  2604
`define  BAUD_38400  1302
`define  BAUD_115200 434

`define STOP_BIT  1'b1      //数据停止位
`define START_BIT 1'b0      //数据开始位

(2)test

module test( 
    input				clk		,
    input				rst_n	,
    input   [2:0]       key_in  ,
    input               uart_rxd,
    input               miso    ,
    output              sck     ,
    output              mosi    ,
    output              cs_n    ,
    output              uart_txd
);								 
                        
    //中间信号定义
    wire        [2:0]           key_out             ;

    wire        [7:0]           rx_byte             ;
    wire                        rx_byte_vld         ;

    wire        [7:0]           dout_m              ;
    wire        [7:0]           dout                ;
    wire                        dout_vld            ;
    wire                        spi_vld             ;
    wire                        en                  ;

    wire        [7:0]           din_m               ; 
    wire                        busy_m              ; 
    wire                        wr_done             ; 

    wire                        busy_t              ;



    //模块例化
key_filter_fsm # (.KEY_W(3),.TIME_20MS(1_000_000))u_key_filter_fsm
(
    /*input 			            */.clk		(clk            ),
    /*input 			            */.rst_n	(rst_n          ),
    /*input 		[KEY_W - 1:0]	*/.key_in	(key_in         ),
    /*output 		[KEY_W - 1:0]	*/.key_out	(key_out        )
);

uart_rx u_uart_rx
(
    /*input                       */.clk         (clk           ),
    /*input                       */.rst_n       (rst_n         ),
    /*input                       */.rx_din      (uart_rxd      ),
    /*input           [1:0]       */.baud_sel    (2'b0          ),
    /*output          [7:0]       */.rx_byte     (rx_byte       ),
    /*output                      */.rx_byte_vld (rx_byte_vld   )
);

wr_control u_wr_control( 
    /*input				  */.clk		  (clk              ),
    /*input				  */.rst_n	      (rst_n            ),
    /*input       [2:0]   */.key_in       (key_out          ),
    /*input               */.busy_m       (busy_m           ),
    /*input               */.busy_t       (busy_t           ),
    /*input               */.wr_done      (wr_done          ),
    /*input		[7:0]     */.din		  (rx_byte          ),
    /*input		          */.din_vld	  (rx_byte_vld      ),
    /*input		[7:0]	  */.din_m 	      (din_m            ),
    /*output	reg [7:0] */.dout_m	      (dout_m           ),
    /*output      [7:0]   */.dout         (dout             ),
    /*output              */.dout_vld     (dout_vld         ),
    /*output              */.en           (en               ),
    /*output              */.spi_vld      (spi_vld          ) 

);

spi_master u_spi_master( 
    /*input				  */.clk		(clk            ),
    /*input				  */.rst_n	    (rst_n          ),
    /*input               */.en         (en             ),
    /*input       [1:0]   */.spi_mode   (2'b00          ),
    /*input               */.spi_vld    (spi_vld        ),
    /*input		[7:0]	  */.din		(dout_m         ),
    /*input               */.miso       (miso           ),
    /*output	reg		  */.sck	    (sck            ),
    /*output  reg         */.cs_n       (cs_n           ),
    /*output			  */.mosi	    (mosi           ),
    /*output  reg [7:0]   */.dout       (din_m          ),
    /*output  reg         */.busy       (busy_m         ),
    /*output              */.wr_done    (wr_done        ) 
);								 

uart_tx u_uart_tx
(
    /*input                       */.clk         (clk           ),
    /*input                       */.rst_n       (rst_n         ),
    /*input           [1:0]       */.baud_sel    (2'b00         ),
    /*input           [7:0]       */.tx_byte     (dout          ),
    /*input                       */.tx_byte_vld (dout_vld      ),
    /*output                      */.tx_dout     (uart_txd      ),
    /*output                      */.busy        (busy_t        )
);
                        
endmodule

(3)spi_master

`include "param.v"
module spi_master( 
    input				clk		,
    input				rst_n	,
    input               en      ,
    input       [1:0]   spi_mode,
    input               spi_vld ,
    input		[7:0]	din		,
    input               miso    ,
    output	reg		    sck	    ,
    output  reg         cs_n    ,
    output			    mosi	,
    output  reg [7:0]   dout    ,
    output  reg         busy    ,
    output              wr_done 
);								 
                
                        
    //中间信号定义		
    
    reg         [4:0]                   cnt_bit             ;
    wire                                add_cnt_bit         ;
    wire                                end_cnt_bit         ;

    reg         [8:0]                   cnt_sck             ;
    wire                                add_cnt_sck         ;
    wire                                end_cnt_sck         ;
    reg                                 end_cnt_sck_r       ;


    //计数器 
    //cnt_sck
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_sck <= 0;
        end 
        else if(add_cnt_sck)begin 
                if(end_cnt_sck | en)begin 
                    cnt_sck <= 0;
                end
                else begin 
                    cnt_sck <= cnt_sck + 1;
                end 
        end
       else  begin
           cnt_sck <= cnt_sck;
        end
    end 
    
    assign add_cnt_sck = spi_vld;
    assign end_cnt_sck = add_cnt_sck && cnt_sck == `SCK_PERIOD-1;

    //cnt_bit
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_bit <= 0;
        end 
        else if(add_cnt_bit )begin 
                if(end_cnt_bit | en)begin 
                    cnt_bit <= 0;
                end
                else begin 
                    cnt_bit <= cnt_bit + 1;
                end 
        end
       else  begin
           cnt_bit <= cnt_bit;
        end
    end 
    
    assign add_cnt_bit =  (cs_n == 0)&& end_cnt_sck;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == 8-1;

    //sck
    always @(*)begin
        case (spi_mode)
           0 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 0;
           end
           else if(cnt_sck > `SCK_HALF-1)begin
                sck <= 1;
           end 
           else begin
              sck <= 0; 
           end  
           end
           1 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 1;
           end
           else if(cnt_sck > `SCK_HALF-1)begin
                sck <= 0;
           end 
           else begin
               sck <= 0; 
           end 
           end
           2 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1 ))begin
               sck <= 1;
           end
           else if(cnt_sck > `SCK_HALF-1 )begin
                sck <= 0;
           end  
           else begin
               sck <= 1;
           end
           end
           3 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 0;
           end
           else if(cnt_sck > `SCK_HALF-1 )begin
                sck <= 1;
           end  
           else begin
               sck <= 1;
           end
           end 
            default: sck <= sck;
        endcase
    end

    //end_cnt_sck_r
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            end_cnt_sck_r <= 0;
        end 
        else begin 
            end_cnt_sck_r <= end_cnt_sck;
        end 
    end
    
    //输出
    //cs_n
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cs_n <= 1;
        end 
        else if(en)begin 
           cs_n <= 0; 
        end 
        else if(cs_n == 0 && (!spi_vld) && end_cnt_sck_r)begin 
            cs_n <= 1;
        end 
    end

    //mosi
    assign mosi = din[7-cnt_bit]; 

    //dout    
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            dout <= 0;
        end 
        else if(cs_n == 0 && cnt_sck == `SCK_PERIOD-1)begin 
            dout[7-cnt_bit] <= miso;
        end 
        else begin 
           dout <= dout; 
        end 
    end
 

//wr_done
    assign  wr_done = end_cnt_bit;

//busy
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            busy <= 0;
        end 
        else if(add_cnt_bit && end_cnt_bit == 0)begin 
            busy <= 1;
        end 
        else if(end_cnt_bit)begin 
            busy <= 0;
        end 
    end










                        
endmodule

(4)wr_control

`include "param.v"
module wr_control( 
    input				  clk		  ,
    input				  rst_n	      ,
    input       [2:0]     key_in      ,
    input                 busy_m      ,
    input                 busy_t      ,
    input                 wr_done     ,
    input		[7:0]     din		  ,
    input		          din_vld	  ,
    input		[7:0]	  din_m 	  ,
    output	reg [7:0]     dout_m	  ,
    output      [7:0]     dout        ,
    output                dout_vld    ,
    output                en          ,
    output  reg           spi_vld      

);								 
    //参数定义	

                        
    //中间信号定义	
    reg                 flag            ;
    reg                 flag0           ;

    reg                 en_r            ;
    wire                en_dge_n        ;
    reg                 data_vld        ;



//例化中间信号
    wire                data_req        ;
    wire    [7:0]       dout1           ;
    wire    [7:0]       dout2           ;
    wire    [1:0]       en_             ;
    wire    [1:0]       data_m_vld      ;
    wire                write_done      ;
    wire                read_done       ;
    wire                busy_write      ;


    wire                rxf_rdreq       ;
    wire                rxf_wrreq       ;
    wire                rxf_empty       ;
    wire                rxf_full        ;
    wire    [7:0]       rxf_dout        ;
    wire    [8:0]       rxf_usedw       ; 

    wire    [7:0]       txf_din         ;
    wire                txf_rdreq       ;
    wire                txf_wrreq       ;
    wire                txf_empty       ;
    wire                txf_full        ;
    wire    [8:0]       txf_usedw       ;            

    //标志信号
    //flag 1: uart 发送数据 0:停止发送数据
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            flag <= 0;
        end 
        else if(txf_usedw >= 1)begin 
            flag <= 1;
        end 
        else if(txf_empty)begin 
            flag <= 0;
        end 
    end

    //flag0 0:fish读模块输出数据(命令、地址)1:fish写模块发送数据(命令、地址、写入数据)
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            flag0 <= 0;
        end 
        else if(en_[0])begin 
            flag0 <= 1;
        end 
        else if(en_[1])begin 
            flag0 <= 0;
        end 
    end

    //en_r 打拍 检测en信号边沿
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            en_r <= 0;
        end 
        else begin 
            en_r <= en;
        end 
    end

    assign en_dge_n = !en & en_r;
    
    //data_vld
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data_vld <= 0;
        end 
        else begin 
            data_vld <= (data_m_vld[0] | data_m_vld[1]);
        end 
    end

    //输出
    //dout_m  数据输出给spi_master模块
    always @(*)begin 
         if(flag0)begin
            dout_m = dout1;
         end 
         else if(flag0 == 0)begin 
            dout_m = dout2; 
         end 
         else begin 
            dout_m = dout_m;
         end 
    end
    
    //en
    assign en = en_[0] | en_[1];

    //spi_vld
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            spi_vld <= 0;
        end 
        else if(en_dge_n)begin 
            spi_vld <= 1;
        end 
        else if(write_done | read_done)begin 
           spi_vld <= 0; 
        end 
        else begin
            spi_vld <= spi_vld; 
        end
    end


flash_write u_flash_write
( 
    /*input				 */.clk		    (clk            ),
    /*input				 */.rst_n	    (rst_n          ),
    /*input		[2:0]    */.key_in	    (key_in         ),
    /*input              */.done_m      (wr_done        ),
    /*input     [7:0]    */.din         (rxf_dout       ),
    /*output             */.data_req    (data_req       ),           //写入flash数据请求
    /*output             */.en          (en_[0]         ), 
    /*output             */.done        (write_done     ),   
    /*output             */.data_m_vld  (data_m_vld[0]  ), 
    /*output             */.busy        (busy_write     ), 
    /*output reg [7:0]   */.dout        (dout1          )    

);

flash_read u_flash_read
( 
    /*input				  */.clk        (clk            ),
    /*input				  */.rst_n	    (rst_n          ),
    /*input       [2:0]   */.key_in     (key_in         ),
    /*input               */.busy       (busy_write     ),
    /*input               */.done_m     (wr_done        ),
    /*output              */.en         (en_[1]         ),
    /*output              */.done       (read_done      ),
    /*output              */.data_m_vld (data_m_vld[1]  ),
    /*output  reg [7:0]   */.dout       (dout2          )
);	

    
//fifo例化
rx_fifo	rx_fifo_inst (
	.aclr  ( !rst_n     ),
	.clock ( clk        ),
	.data  ( din        ),                                          //uart输入数据
	.rdreq ( rxf_rdreq  ),
	.wrreq ( rxf_wrreq  ),
	.empty ( rxf_empty  ),
	.full  ( rxf_full   ),
	.q     ( rxf_dout   ),
	.usedw ( rxf_usedw  )
	);


    assign rxf_wrreq = !rxf_full && din_vld;
    assign rxf_rdreq = !rxf_empty && data_req;

fifo_tx	fifo_tx_inst (
	.aclr  ( !rst_n     ),
	.clock ( clk        ),
	.data  ( din_m      ),
	.rdreq ( txf_rdreq  ),
	.wrreq ( txf_wrreq  ),
	.empty ( txf_empty  ),
	.full  ( txf_full   ),
	.q     ( dout       ),                                           //uart输出数据
	.usedw ( txf_usedw  )
	);

    assign txf_wrreq = !txf_full && data_vld ;
    assign txf_rdreq = flag && (!txf_empty) && (!busy_t);
    assign dout_vld = txf_rdreq;                                    //uart数据输出有效
                        
endmodule

(5)flash_write

`include "param.v"
module flash_read( 
    input				clk		    ,
    input				rst_n	    ,
    input       [2:0]   key_in      ,
    input               busy        ,
    input               done_m      ,
    output              en          ,
    output              done        ,
    output              data_m_vld  ,
    output  reg [7:0]   dout    
);								 
    //参数定义		
    parameter IDLE      =7'b000_0001,
              RDID_CMD  =7'b000_0010, 
              RDID_DATA =7'b000_0100, 
              RDAD_CMD  =7'b000_1000, 
              RDAD_ADDR =7'b001_0000, 
              RDAD_DATA =7'b010_0000, 
              RD_DONE   =7'b100_0000;

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

    reg	    [2:0]               cnt_byte                ;
    wire                        add_cnt_byte            ;
    wire                        end_cnt_byte            ;

    reg     [2:0]               byte_num                ;

    reg     [23:0]              addr_rd                 ;

    wire                        idle2rdid_cmd           ;
    wire                        idle2rdad_cmd           ;
    wire                        rdid_cmd2rdid_data      ;
    wire                        rdad_cmd2rdad_addr      ;
    wire                        rdad_addr2rdad_data     ;
    wire                        rdid_data2rd_done       ;
    wire                        rdad_data2rd_done       ;
    wire                        rd_done2idle            ;


    //状态机
    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 (idle2rdid_cmd) begin
            state_n <= RDID_CMD;
        end
        else if(idle2rdad_cmd) begin
            state_n <= RDAD_CMD;
        end
        else begin
            state_n <= state_c;
        end  
        end
        RDID_CMD  : begin if(rdid_cmd2rdid_data)begin
            state_n <= RDID_DATA;
        end
        else begin
            state_n <= state_c;
        end 
        end
        RDID_DATA : begin if (rdid_data2rd_done) begin
            state_n <= RD_DONE;
        end
        else begin
            state_n <= state_c;
        end    
        end
        RDAD_CMD  : begin if (rdad_cmd2rdad_addr) begin
            state_n <= RDAD_ADDR;
        end
        else begin
            state_n <= state_c;
        end
        end
        RDAD_ADDR : begin if (rdad_addr2rdad_data ) begin
            state_n <= RDAD_DATA;
        end
        else begin
            state_n <= state_c;
        end  
        end
        RDAD_DATA : begin if(rdad_data2rd_done)begin
            state_n <= RD_DONE;
        end
        else begin
            state_n <= state_c;
        end
        end
        RD_DONE   : begin if(rd_done2idle)begin
            state_n <= IDLE;
        end
        else begin
            state_n <= state_c;
        end
        end
             default: state_n <= state_c;
         endcase
    end

    assign idle2rdid_cmd       = state_c == IDLE      && key_in[0] && busy == 0;   
    assign idle2rdad_cmd       = state_c == IDLE      && key_in[1] && busy == 0; 
    assign rdid_cmd2rdid_data  = state_c == RDID_CMD  && end_cnt_byte;     
    assign rdad_cmd2rdad_addr  = state_c == RDAD_CMD  && end_cnt_byte;     
    assign rdad_addr2rdad_data = state_c == RDAD_ADDR && end_cnt_byte;      
    assign rdid_data2rd_done   = state_c == RDID_DATA && end_cnt_byte;      
    assign rdad_data2rd_done   = state_c == RDAD_DATA && end_cnt_byte; 
    assign rd_done2idle        = state_c == RD_DONE   && 1'b1;

//计数器
    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
       else  begin
           cnt_byte <= cnt_byte;
        end
    end 

    assign add_cnt_byte = (state_c == RDID_CMD | state_c == RDID_DATA | state_c == RDAD_CMD | state_c == RDAD_ADDR |state_c == RDAD_DATA) && done_m;
    assign end_cnt_byte = add_cnt_byte && cnt_byte == byte_num-1;

    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            byte_num <= 0;
        end 
        else if(state_c == RDID_CMD | state_c == RDAD_CMD )begin 
           byte_num <= `CMD_BYTE; 
        end 
        else if(state_c == RDID_DATA)begin 
            byte_num <= `ID_BYTE; 
        end 
        else  if (state_c == RDAD_ADDR) begin
            byte_num <= `ADDR_BYTE;
        end
        else if(state_c == RDAD_DATA)begin
            byte_num <= `DATA_BYTE;
        end
    end

    //addr_rd
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            addr_rd <= 24'h100;
        end 
        // else if(rdad_data2rd_done)begin 
            // addr_rd <= addr_rd + 1;
        // end 
        else begin 
            addr_rd <= addr_rd;
        end 
    end

    //输出 
    //dout
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            dout <= 0;
        end 
        else if(state_c == RDID_CMD)begin 
            dout <= `CMD_RDID;
        end 
        else if(state_c == RDAD_CMD)begin 
            dout <= `CMD_READ;
        end
        else if(state_c == RDAD_ADDR)begin
            if(cnt_byte == 0)
            begin
               dout <= addr_rd[23:16];
            end
            else if(cnt_byte == 1)
            begin
                dout <= addr_rd[15:8];
            end
            else if(cnt_byte == 2)
            begin
                dout <= addr_rd[7:0];
            end
        end
        else begin
            dout <= dout;
        end 
    end
    
    //en done dtat_m_vld
    assign en =(key_in[0] | key_in[1]) & state_c == IDLE;
    assign done = rdid_data2rd_done | rdad_data2rd_done;
    assign data_m_vld = done_m && (state_c == RDID_DATA | state_c == RDAD_DATA);

endmodule

(6)flash_write

`include "param.v"
module flash_write( 
    input				clk		    ,
    input				rst_n	    ,
    input		[2:0]   key_in	    ,
    input               done_m      ,
    input       [7:0]   din         ,
    output              data_req    ,           //写入flash数据请求
    output              en          ,    
    output              done        ,  
    output              busy        ,
    output              data_m_vld  ,   
    output  reg [7:0]   dout    

);								 
    //参数定义	
    parameter M_IDLE =5'b0_0001,
              M_WREN =5'b0_0010,
              M_SE   =5'b0_0100, 
              M_RDSR =5'b0_1000,
              M_PP   =5'b1_0000;      

    parameter 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     [2:0]               cnt_100ns           ;
    wire                        add_cnt_100ns       ;
    wire                        end_cnt_100ns       ;

    reg     [18:0]              cnt_pp              ;
    wire                        add_cnt_pp          ;
    wire                        end_cnt_pp          ;
    reg                         end_cnt_pp_r        ;

    reg     [10:0]              cnt_se              ;
    wire                        add_cnt_se          ;
    wire                        end_cnt_se          ;
    reg                         end_cnt_se_r        ;    





    reg     [2:0]               cnt_byte            ;
    wire                        add_cnt_byte        ;
    wire                        end_cnt_byte        ;

    reg     [2:0]               byte_num            ;


    reg     [23:0]              addr_wr             ;
    reg     [1:0]               flag                ;
    reg                         req_r               ;

    //主
    wire                        m_idle2m_wren       ;
    wire                        m_wren2m_rdsr       ;
    wire                        m_rdsr2m_wren       ;
    wire                        m_wren2m_se         ;
    wire                        m_wren2m_pp         ;
    wire                        m_se2m_wren         ;
    wire                        m_pp2m_idle         ;
    //从
    wire                        s_idle2s_cmd        ;
    wire                        s_cmd2s_addr        ;
    wire                        s_cmd2s_data        ;
    wire                        s_cmd2s_delay       ;
    wire                        s_addr2s_data       ;
    wire                        s_addr2s_delay      ;
    wire                        s_data2s_idle       ;
    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)begin
                m_state_n = M_WREN;
            end
            else begin
                m_state_n = m_state_c;
            end
            end
            M_WREN : begin if (m_wren2m_rdsr) begin
                m_state_n = M_RDSR;
            end
            else if (m_wren2m_se) begin
                m_state_n = M_SE;
            end    
            else if (m_wren2m_pp) begin
                m_state_n = M_PP;
            end
            else begin
                m_state_n = m_state_c;
            end
            end
            M_SE   : begin if (m_se2m_wren) begin
                m_state_n = M_WREN;
            end
            else begin
                m_state_n = m_state_c;
            end    
            end
            M_RDSR : begin if (m_rdsr2m_wren) begin
                m_state_n = M_WREN;
            end
            else begin
                m_state_n = m_state_c;
            end    
            end
            M_PP   : begin if (m_pp2m_idle) begin
                m_state_n = M_IDLE;
            end
            else begin
                m_state_n = m_state_c;
            end    
            end
             default: m_state_n = m_state_c;
         endcase
    end

    //从状态机
    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) begin
                s_state_n = S_CMD;
            end
            else begin
               s_state_n = s_state_c; 
            end    
            end
            S_CMD   : begin if (s_cmd2s_addr) begin
                s_state_n = S_ADDR;
            end
            else if (s_cmd2s_data) begin
                s_state_n = S_DATA;
            end
            else if (s_cmd2s_delay) begin
                s_state_n = S_DELAY;
            end
            else begin
                s_state_n = s_state_c;
            end    
            end
            S_ADDR  : begin if (s_addr2s_data) begin
                s_state_n = S_DATA;
            end
            else if(s_addr2s_delay) begin
                s_state_n = S_DELAY;
            end
            else begin
                s_state_n = s_state_c;
            end   
            end
            S_DATA  : begin if (s_data2s_idle) begin
                s_state_n = S_IDLE;
            end
            else if(s_data2s_delay)begin
                s_state_n = S_DELAY;
            end
            else begin
                s_state_n = s_state_c;
            end    
            end
            S_DELAY : begin if(s_delay2s_idle)begin
                s_state_n = S_IDLE;
            end
            else begin
                s_state_n = s_state_c;
            end    
            end
             default: s_state_n = s_state_c;
         endcase
    end
 
    //主状态机转换条件
    assign m_idle2m_wren   = m_state_c == M_IDLE && key_in[2];
    assign m_wren2m_rdsr  = m_state_c == M_WREN && s_delay2s_idle && flag == 2'b00;
    assign m_rdsr2m_wren = m_state_c == M_RDSR && s_data2s_idle;
    assign m_wren2m_se   = m_state_c == M_WREN && s_delay2s_idle && flag == 2'b01;
    assign m_wren2m_pp   = m_state_c == M_WREN && s_delay2s_idle && flag == 2'b10;
    assign m_se2m_wren   = m_state_c == M_SE   && s_delay2s_idle;
    assign m_pp2m_idle   = m_state_c == M_PP   && s_delay2s_idle;  

    //从状态机转换条件
    assign s_idle2s_cmd   = s_state_c == S_IDLE  && (!(m_state_c == M_IDLE) );
    assign s_cmd2s_addr   = s_state_c == S_CMD   && end_cnt_byte && (m_state_c == M_SE | m_state_c == M_PP);
    assign s_cmd2s_data   = s_state_c == S_CMD   && end_cnt_byte && m_state_c == M_RDSR;
    assign s_addr2s_data  = s_state_c == S_ADDR  && end_cnt_byte && m_state_c == M_PP;
    assign s_addr2s_delay = s_state_c == S_ADDR  && end_cnt_byte && m_state_c == M_SE ;
    assign s_data2s_idle  = s_state_c == S_DATA  && end_cnt_byte && m_state_c == M_RDSR;
    assign s_data2s_delay = s_state_c == S_DATA  && end_cnt_byte && (m_state_c == M_PP);
    assign s_cmd2s_delay  = s_state_c == S_CMD   && end_cnt_byte && m_state_c == M_WREN;
    assign s_delay2s_idle = s_state_c == S_DELAY && ((( (m_state_c == M_SE)?end_cnt_se_r : end_cnt_pp_r)
                     && (m_state_c == M_SE || m_state_c == M_PP)) || (end_cnt_100ns && m_state_c == M_WREN));


    //计数器
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_100ns <= 0;
        end 
        else if(add_cnt_100ns)begin 
                if(end_cnt_100ns)begin 
                    cnt_100ns <= 0;
                end
                else begin 
                    cnt_100ns <= cnt_100ns + 1;
                end 
        end
       else  begin
           cnt_100ns <= cnt_100ns;
        end
    end 
    
    assign add_cnt_100ns = s_state_c == S_DELAY && m_state_c == M_WREN;
    assign end_cnt_100ns = add_cnt_100ns && cnt_100ns == 5;

    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
       else  begin
           cnt_byte <= cnt_byte;
        end
    end 
    
    assign add_cnt_byte = done_m && (s_state_c == S_CMD | s_state_c == S_ADDR | s_state_c == S_DATA );
    assign end_cnt_byte = add_cnt_byte && cnt_byte == byte_num-1; 


    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_pp <= 0;
        end 
        else if(add_cnt_pp)begin 
                if(end_cnt_pp | s_delay2s_idle)begin 
                    cnt_pp <= 0;
                end
                else begin 
                    cnt_pp <= cnt_pp + 1;
                end 
        end
       else  begin
           cnt_pp <= cnt_pp;
        end
    end 
    
    assign add_cnt_pp = s_state_c == S_DELAY && (m_state_c == M_PP || m_state_c == M_SE);
    assign end_cnt_pp = add_cnt_pp && cnt_pp == `DELAY_5MS-1;


    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_se <= 0;
        end 
        else if(add_cnt_se)begin 
                if(end_cnt_se)begin 
                    cnt_se <= 0;
                end
                else begin 
                    cnt_se <= cnt_se + 1;
                end 
        end
       else  begin
           cnt_se <= cnt_se;
        end
    end 
    
    assign add_cnt_se= s_state_c == S_DELAY && end_cnt_pp && m_state_c == M_SE;
    assign end_cnt_se = add_cnt_se && cnt_se == 600-1;

    //flag
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            flag <= 2'b00;
        end 
        else if(m_state_c == M_RDSR)begin 
           flag <=2'b01 ;
        end 
        else if(m_state_c == M_SE)begin 
            flag <= 2'b10;
        end 
        else if(m_state_c == M_PP)begin
            flag <= 2'b00;
        end
    end


    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
        end_cnt_pp_r <= 0;
        end_cnt_se_r <= 0;
        end 
        else begin 
            end_cnt_pp_r <= end_cnt_pp;
            end_cnt_se_r <= end_cnt_se;
        end 
    end

    //byte_num
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            byte_num <= 0;
        end 
        else if(s_state_c == S_CMD)begin 
            byte_num <= `CMD_BYTE;
        end 
        else if(s_state_c == S_ADDR)begin 
            byte_num <= `ADDR_BYTE;
        end 
        else if(s_state_c == S_DATA)begin
            byte_num <= `DATA_BYTE;
        end
        else begin
            byte_num <= byte_num;
        end
    end

    //addr_wr
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            addr_wr <= 24'h100;
        end 
        // else if(m_pp2m_idle)begin 
            // addr_wr <= addr_wr +1;
        // end 
        else begin 
            addr_wr <= addr_wr;
        end 
    end

    // always @(posedge clk or negedge rst_n)begin 
        // if(!rst_n)begin
            // req_r <= 0;
        // end 
        // else begin 
            // req_r <= done_m;
        // end 
    // end

    //输出
    //dout
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            dout <= 0;
        end 
        else if(s_state_c == S_CMD)begin 
            if(m_state_c == M_WREN)begin
                dout <= `CMD_WREN;
            end
            else if(m_state_c == M_SE ) begin
                dout <= `CMD_SE;
            end
            else if(m_state_c == M_RDSR)begin
                dout <= `CMD_RDSR;
            end
            else if (m_state_c == M_PP ) begin
                dout <= `CMD_PP;
            end
            else begin
                dout <= dout;
            end
        end 
        else if(s_state_c == S_ADDR)begin 
            dout <=addr_wr[23-cnt_byte*8 -:8];
        end 
        else if (s_state_c == S_DATA && m_state_c == M_PP) begin
            dout <=din;
        end
    end

    //data_req en done data_m_vld busy
    assign data_req = m_state_c == M_PP && done_m  && s_state_c == S_DATA; 
    assign en =(key_in[2] & m_state_c == M_IDLE) | (s_delay2s_idle && m_state_c == M_SE) | (s_delay2s_idle && m_state_c == M_WREN);
    assign done = (s_data2s_delay && m_state_c == M_PP) | s_addr2s_delay | s_cmd2s_delay;
    assign data_m_vld = done_m && s_state_c == S_DATA && m_state_c == M_RDSR;
    assign busy= !(m_state_c==M_IDLE);

endmodule

(7)key_filter_fsm

https://blog.csdn.net/li_lys/article/details/121849916

(8)uart

https://blog.csdn.net/li_lys/article/details/122887251?spm=1001.2014.3001.5502

四、验证

读ID时需
【FPGA】SPI协议详解及对flash读写操作_第15张图片
读时序

【FPGA】SPI协议详解及对flash读写操作_第16张图片
写时序,写没截完
【FPGA】SPI协议详解及对flash读写操作_第17张图片

读ID
【FPGA】SPI协议详解及对flash读写操作_第18张图片
单字节读数据
【FPGA】SPI协议详解及对flash读写操作_第19张图片
写数据,读数据
【FPGA】SPI协议详解及对flash读写操作_第20张图片
工程链接:
https://blog.csdn.net/li_lys/article/details/124870664?utm_source=app&app_version=5.4.0&code=app_1562916241&uLinkId=usr1mkqgl919blen

你可能感兴趣的:(FPGA,fpga开发,嵌入式硬件)