news 2026/8/26 21:42:38

S25FL256S flash 读写实现 —— 基于Genesys2

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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上显示:

成功读出写入数据。


完整代码:

`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, //起始地址有要求,必须为每一块起始地址!!!,比如10000H,地址最后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
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 9:49:43

5分钟学会Bypass Paywalls Clean:终极免费阅读指南

你是否曾经因为付费墙而无法阅读知名媒体的深度报道&#xff1f;在信息爆炸的时代&#xff0c;Bypass Paywalls Clean正是你需要的解决方案&#xff0c;这款神奇工具能够轻松突破全球100多家主流媒体的付费限制&#xff0c;让你畅享免费阅读体验。 【免费下载链接】bypass-payw…

作者头像 李华
网站建设 2026/8/26 0:54:07

什么是“本地永久云手机”,真正独享的云端体验!

你可能听说过“云手机”&#xff0c;也能想象到它是一个能在云端运行的“虚拟手机”&#xff0c;不用耗自己手机的电和内存&#xff0c;适合挂游戏、多开应用。但最近挺火的的这个VMOS Edge本地永久云手机又是什么&#xff1f;不是说“云手机”在云端运行吗&#xff1f;怎么又到…

作者头像 李华
网站建设 2026/8/26 6:00:08

VMOS Edge与魔云腾Q1对比评测:谁才是本地永久云手机最优选?

从按月付费的云端服务变成可以买回家的硬件&#xff0c;“云手机”火爆的同时“本地云手机”也慢慢进入了更多消费者的视野。但面对不同的品牌和型号&#xff0c;消费者们往往困惑&#xff1a;这些“本地云手机”看起来性能相似&#xff0c;差别到底在哪里呢&#xff1f;谁才更…

作者头像 李华
网站建设 2026/8/26 2:47:18

HC32L130 MCU 片内 OPA(运算放大器)全解析与应用指南

目录 一、OPA 核心电气特性&#xff08;基础必知&#xff09; 二、OPA 硬件架构与管脚映射 1. 硬件框图&#xff08;核心逻辑&#xff09; 2. 管脚复用&#xff08;关键&#xff01;需查手册确认&#xff09; 三、OPA 软件配置通用流程&#xff08;基于标准库&#xff09;…

作者头像 李华
网站建设 2026/8/25 2:54:31

leetcode 763. Partition Labels 划分字母区间-耗时100%

Problem: 763. Partition Labels 划分字母区间 解题过程 耗时100%&#xff0c;首先统计每个字母的最小最大索引&#xff0c;然后合并所有字母的区间&#xff0c;可以合并的全部合并起来&#xff0c;不能合并的就放在那里&#xff0c;得到合并以后的区间&#xff0c;最后根据最小…

作者头像 李华