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