S25FL256S flash 读写实现 —— 基于Genesys2

RDID实现、顶层文件及管脚约束等可参考上一篇文章:

S25FL256S flash 读取ID实现 ------ 基于Genesys2-CSDN博客

相关说明:

  • flash在spi_sck上升沿采样mosi,在下降沿输出miso
  • spi_sck为主时钟频率一半,这里为40MHz

接下来介绍FLASH 读写实现思路。状态转换图如下,其中we为外部读写使能信号,start为一次读写操作开始信号:

写操作vio设置如下,we=0,start由0变为1,触发一次写操作:

检测到start有效后从IDLE状态(00)进入READ_STATUS状态(01);首次读状态寄存器1得到全0输出,检测到flash_we为写状态,之后进入WR_ENABLE状态(04),发送写使能命令;8bit计数结束后再次读取状态寄存器1,可以看到寄存器的WEL位已被置为1,因此进入WR_READY状态(08);在写准备状态判断是否已经完成扇区擦除,检测到erase_done信号为0进入BLOCK_ERASE状态(20):

在BLOCK_ERASE状态发送64KB扇区擦除命令0xd8以及24位写地址a00000,擦除结束后将erase_done信号置1,再次读状态寄存器1,得到miso=8'b00000010,WEL位有效,因此进入写准备状态;在写准备状态检测到擦除完成,进入PAGE_PROGRAM状态(0x10),开始对FLASH进行写入:

在页编程状态依此发送页写命令0x02、24位写地址a00000以及写数据0x55,发送完成后拉高spi_cs,一次写入结束。

读操作vio设置如下,we=1,start由0变为1,触发一次读操作:

检测到start有效后从IDLE状态(00)进入READ_STATUS状态(01);检测到flash_we为读状态且WIP=0后进入READ_DATA状态(02),发送读命令0x03以及24位读地址a00000后得到读数据0x55,并在LED上显示:

成功读出写入数据。

通过串口向flash地址 0xA1000 连续写入0x01~0x08共8个数据:

读出写入数据并写入FIFO:

检测到FIFO非空时将数据发送到串口:

成功读取写入数据。


完整代码:

cpp 复制代码
`timescale 1ns / 1ps

module SPI_FLASH #(
    parameter BLOCK_SIZE = 64 * 1024,  //一块的字节数量
    parameter PAGE_SIZE = 512,  // 一页的字节数量
    parameter BLOCK_WIDTH = $clog2(
        BLOCK_SIZE
    )  //根据一个Black大小,计算出数据位宽    
) (
                            input      clk,
    (*MARK_DEBUG = "TRUE"*) input      reset,
    (*MARK_DEBUG = "TRUE"*) output reg spi_sck,
    (*MARK_DEBUG = "TRUE"*) output     spi_cs_n,
    (*MARK_DEBUG = "TRUE"*) output     spi_mosi,
    (*MARK_DEBUG = "TRUE"*) input      spi_miso,

    (*MARK_DEBUG = "TRUE"*) input flash_start,  //拉高一拍,发起一次读写请求
    (*MARK_DEBUG = "TRUE"*) input flash_we,  //0为写,1为读
    (*MARK_DEBUG = "TRUE"*)
    input    [BLOCK_WIDTH-1:0] flash_length, //读写长度,如果一个块block为64KB,则取值范围为0~65536,最多读写一个block
    (*MARK_DEBUG = "TRUE"*)
    input      [23:0]    flash_addr,   //起始地址须为每一块起始地址,地址最后16位为0
    (*MARK_DEBUG = "TRUE"*) output reg flash_wr_req,  //用户写请求
    input [7:0] flash_wr_data,  //用户写数据,晚写请求一拍
    (*MARK_DEBUG = "TRUE"*) output reg flash_rd_vld,  //用户读数据有效
    (*MARK_DEBUG = "TRUE"*) output reg [7:0] flash_rd_data,  //用户读数据
    (*MARK_DEBUG = "TRUE"*) output flash_busy  //正在进行读写过程

);
  /*--------------------------------------------------*\
            FLASH操作命令
\*--------------------------------------------------*/
  localparam WR_EN_CMD = 8'h06;  //写使能命令
  localparam RD_STATUS_CMD = 8'h05;  //读状态寄存器命令
  localparam RD_DATA_CMD = 8'h03;  //读数据命令
  localparam PP_WR_CMD = 8'h02;  //页写命令
  localparam BERASE_CMD = 8'hd8;  //块擦除命令
  localparam RDID_CMD = 8'h9F;  //块擦除命令
  /*--------------------------------------------------*\
                    状态机定义
\*--------------------------------------------------*/
  (*MARK_DEBUG = "TRUE"*)reg [6:0] cur_status;
  (*MARK_DEBUG = "TRUE"*)reg [6:0] nxt_status;
  localparam IDLE = 7'h0;
  localparam READ_STATUS = 7'h1;
  localparam READ_DATA = 7'h2;
  localparam WR_ENABLE = 7'h4;
  localparam WR_READY = 7'h8;
  localparam PAGE_PROGRAM = 7'h10;
  localparam BLOCK_ERASE = 7'h20;
  localparam END = 7'h40;
  /*--------------------------------------------------*\
                    其他信号定义
\*--------------------------------------------------*/
  (*MARK_DEBUG = "TRUE"*)reg                   wr_busy;
  (*MARK_DEBUG = "TRUE"*)reg                   rd_busy;
  (*MARK_DEBUG = "TRUE"*)reg                   block_erase_done;  //块擦除完成
  reg [BLOCK_WIDTH-1:0] flash_length_r;
  reg [           23:0] flash_addr_r;
  reg [           23:0] flash_wr_addr;

  (*MARK_DEBUG = "TRUE"*)reg [            2:0] bit_cnt;
  (*MARK_DEBUG = "TRUE"*)reg [BLOCK_WIDTH-1:0] byte_cnt;
  reg [BLOCK_WIDTH-1:0] wr_length;
  (*MARK_DEBUG = "TRUE"*)reg [           31:0] wr_data;


  (*MARK_DEBUG = "TRUE"*)reg [            7:0] shift_reg;


  (*MARK_DEBUG = "TRUE"*)reg                   spi_cs;
  (*MARK_DEBUG = "TRUE"*)reg                   spi_cs_d;
  (*MARK_DEBUG = "TRUE"*)reg [            3:0] cnt_value;
  (*MARK_DEBUG = "TRUE"*)reg [            7:0] byte_cnt_d;

  assign spi_cs_n   = spi_cs;

  assign flash_busy = wr_busy | rd_busy;

  always @(posedge clk) begin  //锁存地址和长度
    if (flash_start && ~flash_busy) begin
      flash_length_r <= flash_length;
      flash_addr_r   <= flash_addr;
    end
  end

  /*--------------------------------------------------*\
            FLASH读写状态机
\*--------------------------------------------------*/
  always @(posedge clk) begin
    if (reset) cur_status <= IDLE;
    else cur_status <= nxt_status;
  end

  //状态转换组合逻辑
  always @(*) begin
    if (reset) nxt_status = IDLE;
    else begin
      case (cur_status)
        IDLE: begin
          if (flash_start && ~flash_busy) nxt_status = READ_STATUS;
          else nxt_status = cur_status;
        end
        READ_STATUS: begin
          if (byte_cnt > 0 && ~spi_miso && rd_busy && bit_cnt == 7 && !cnt_value[0])     //读状态寄存器最低位为0
            nxt_status = READ_DATA;
          else if (byte_cnt > 0 && spi_miso && wr_busy && bit_cnt == 7 && cnt_value[0]) //读状态寄存器WEL为1
            nxt_status = WR_READY;
          else if (byte_cnt > 0 && !spi_miso && wr_busy && bit_cnt == 7 && cnt_value[0])
            nxt_status = WR_ENABLE;
          else nxt_status = cur_status;
        end
        READ_DATA: begin
          if (byte_cnt == flash_length_r + 3 && bit_cnt == 7 && !cnt_value[0])  //读完所有的数据
            nxt_status = END;
          else nxt_status = cur_status;
        end
        WR_ENABLE: begin
          if (bit_cnt == 7 && cnt_value[0])  
            nxt_status = READ_STATUS;
          else nxt_status = cur_status;
        end
        WR_READY: begin
          if (~block_erase_done)  //块擦除未完成
            nxt_status = BLOCK_ERASE;
          else  //块擦除完成
            nxt_status = PAGE_PROGRAM;
        end
        BLOCK_ERASE: begin
          if (bit_cnt == 7 && byte_cnt == 3 && !cnt_value[0]) nxt_status = READ_STATUS;
          else nxt_status = cur_status;
        end
        PAGE_PROGRAM: begin
          if (bit_cnt == 7 && byte_cnt == wr_length + 3 && !cnt_value[0])  //全部写完
            nxt_status = END;
          else if (bit_cnt == 7 && byte_cnt == PAGE_SIZE + 3 && !cnt_value[0])  // 写完一页
            nxt_status = READ_STATUS;
          else nxt_status = cur_status;
        end
        END: begin
          nxt_status = IDLE;
        end
        default: nxt_status = IDLE;
      endcase
    end
  end

  always @(posedge clk) begin
    if (reset) block_erase_done <= 0;
    else if (cur_status == BLOCK_ERASE)  //块擦除完成
      block_erase_done <= 1;
    else if (cur_status == END) block_erase_done <= 0;
  end

  always @(posedge clk) begin
    if (flash_start && ~flash_we) wr_busy <= 1'b1;
    else if (flash_start && flash_we) rd_busy <= 1'b1;
    else if (cur_status == IDLE) begin
      wr_busy <= 1'b0;
      rd_busy <= 1'b0;
    end
  end
  (*MARK_DEBUG = "TRUE"*)wire flag = cur_status != nxt_status;
  (*MARK_DEBUG = "TRUE"*)reg  flag_d;
  always @(posedge clk) begin
    if (reset) flag_d <= 0;
    else flag_d <= flag;
  end
  (*MARK_DEBUG = "TRUE"*)wire spi_cs_down = flag && !flag_d;
  (*MARK_DEBUG = "TRUE"*)reg  spi_cs_down_d;
  always @(posedge clk) begin
    if (reset) spi_cs_down_d <= 0;
    else spi_cs_down_d <= spi_cs_down;
  end
  (*MARK_DEBUG = "TRUE"*) reg spi_cs_down_d1;
  always @(posedge clk) begin
    if (reset) spi_cs_down_d1 <= 0;
    else spi_cs_down_d1 <= spi_cs_down_d;
  end
  (*MARK_DEBUG = "TRUE"*) reg spi_cs_down_d2;
  always @(posedge clk) begin
    if (reset) spi_cs_down_d2 <= 0;
    else spi_cs_down_d2 <= spi_cs_down_d1;
  end
  (*MARK_DEBUG = "TRUE"*) reg spi_cs_down_d3;
  always @(posedge clk) begin
    if (reset) spi_cs_down_d3 <= 0;
    else spi_cs_down_d3 <= spi_cs_down_d2;
  end
  always @(posedge clk) begin
    if (reset) spi_cs <= 1;
    else if (cur_status == IDLE || cur_status == END) spi_cs <= 1;
    else if (spi_cs_down_d1) spi_cs <= 1;  //
    else if (spi_cs_down_d3) spi_cs <= 0;  //tcs=20ns
  end

  /*--------------------------------------------------*\
              计数器
\*--------------------------------------------------*/
  always @(posedge clk) begin
    if (reset) bit_cnt <= 0;
    else if (cur_status == IDLE || cur_status == END) bit_cnt <= 0;
    else if (spi_cs) bit_cnt <= 0;
    else if (!cnt_value[0])  //原!spi_cs
      bit_cnt <= bit_cnt + 1;
  end

  always @(posedge clk) begin
    if (reset) byte_cnt <= 0;
    else if (spi_cs) byte_cnt <= 0;
    else if (bit_cnt == 7 && !cnt_value[0]) byte_cnt <= byte_cnt + 1;
  end

  /*--------------------------------------------------*\
              FLASH写数据
\*--------------------------------------------------*/
  always @(posedge clk) begin
    if (flash_start && ~flash_we) begin
      flash_wr_addr <= flash_addr;
      wr_length     <= flash_length;
    end else if (cur_status == PAGE_PROGRAM && bit_cnt == 7 && byte_cnt == PAGE_SIZE + 3 && !cnt_value[0]) begin
      flash_wr_addr <= flash_wr_addr + PAGE_SIZE;
      wr_length     <= wr_length - PAGE_SIZE;
    end
  end

  always @(posedge clk) begin
    if (cur_status == READ_STATUS && spi_cs)
      wr_data <= {RD_STATUS_CMD, 24'h0};  //读状态寄存器命令
    else if (cur_status == READ_DATA && spi_cs)
      wr_data <= {RD_DATA_CMD, flash_addr_r};  //读数据命令 + 24位地址
    else if (cur_status == WR_ENABLE && spi_cs) wr_data <= {WR_EN_CMD, 24'h0};  //写使能命令
    else if (cur_status == BLOCK_ERASE && spi_cs)
      wr_data <= {BERASE_CMD, flash_addr_r};  //块擦除命令 + 24位地址
    else if (cur_status == PAGE_PROGRAM && spi_cs)
      wr_data <= {PP_WR_CMD, flash_wr_addr};  //PP写命令 + 24位地址
    else if (flash_wr_req) wr_data <= {flash_wr_data, 24'h0};  //用户写数据
    else if (!spi_cs && cnt_value[0]) wr_data <= wr_data << 1;
  end

  always @(posedge clk) begin
    if (reset) flash_wr_req <= 0;
    else if (cur_status == PAGE_PROGRAM && byte_cnt >= 3 && byte_cnt != wr_length + 3 && byte_cnt != PAGE_SIZE + 3 && bit_cnt == 7 && !cnt_value[0])
      flash_wr_req <= 1;
    else flash_wr_req <= 0;
  end

  /*--------------------------------------------------*\
          FLASH读数据
\*--------------------------------------------------*/
  always @(posedge clk) begin
    if (reset) shift_reg <= 0;
    else if (!spi_cs && cnt_value[0]) shift_reg <= {shift_reg[6:0], spi_miso};
  end

  assign spi_mosi = !spi_cs ? wr_data[31] : 0;

  always @(posedge clk) begin
    if (cur_status == READ_DATA && bit_cnt == 7 && byte_cnt > 3 && !cnt_value[0])
      flash_rd_vld <= 1'b1;
    else flash_rd_vld <= 0;
  end
  (*MARK_DEBUG = "TRUE"*) reg flash_rd_vld_d;
  always @(posedge clk) begin
    if (reset) flash_rd_vld_d <= 0;
    else flash_rd_vld_d <= flash_rd_vld;
  end
  always @(posedge clk) begin
    if (reset) flash_rd_data <= 0;
    else if (flash_rd_vld_d) flash_rd_data <= shift_reg;
  end

  always @(posedge clk) begin
    if (reset) spi_sck <= 1;
    else if (spi_cs_down_d3) spi_sck <= 0;  //与spi_cs同时为0
    else if (!spi_cs) begin
      if (!cnt_value[0]) spi_sck <= 1;
      else spi_sck <= 0;
    end else spi_sck <= 1;
  end

  always @(posedge clk) begin
    if (reset) spi_cs_d <= 1;
    else spi_cs_d <= spi_cs;
  end

  always @(posedge clk) begin
    if (reset) cnt_value <= 0;
    else if (!spi_cs) cnt_value <= cnt_value + 1;
    else cnt_value <= 0;
  end

  always @(posedge clk) begin
    if (reset) byte_cnt_d <= 0;
    else byte_cnt_d <= byte_cnt;
  end


endmodule
相关推荐
stay_cloud7 天前
S25FL256S flash 驱动学习与 RDID 实现
spi flash·genesys2
stay_cloud14 天前
HDMI字符显示 —— 基于Genesys2
fpga·hdmi·genesys2
人才程序员2 年前
【STM32 CubeMX】SPI W25Q64功能实现
c语言·stm32·单片机·嵌入式硬件·mcu·c·spi flash
萧长生2 年前
FPGA解析串口指令控制spi flash完成连续写、读、擦除数据
fpga开发·串口·flash·spi·spi flash