news 2026/5/2 0:23:41

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/5/1 16:07:11

百度网盘直链解析终极指南:轻松突破限速实现高速下载

百度网盘直链解析终极指南&#xff1a;轻松突破限速实现高速下载 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 还在为百度网盘的龟速下载而烦恼吗&#xff1f;baidu-wangpan…

作者头像 李华
网站建设 2026/5/2 2:18:56

2000元 =10次酒店入住+受益权?酒店 RWA 破解重资产困局

Hallo&#xff0c;我是老叶。“每月房租 30 万、人工 20 万&#xff0c;空置率一高就亏&#xff0c;重资产压得喘不过气……” 这是 90% 酒店老板的日常焦虑。现在&#xff0c;区块链技术带来的RWA&#xff08;真实世界资产&#xff09;数字化方案&#xff0c;正通过一种叫“NF…

作者头像 李华
网站建设 2026/5/1 6:27:46

Unity游戏自动翻译终极完整指南:从零配置到实战精通

Unity游戏自动翻译终极完整指南&#xff1a;从零配置到实战精通 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator XUnity.AutoTranslator是一款专为Unity游戏打造的自动翻译解决方案&#xff0c;支持多种翻…

作者头像 李华
网站建设 2026/4/25 14:55:26

纪念币预约自动化工具:5步教你轻松搞定预约难题

纪念币预约自动化工具&#xff1a;5步教你轻松搞定预约难题 【免费下载链接】auto_commemorative_coin_booking 项目地址: https://gitcode.com/gh_mirrors/au/auto_commemorative_coin_booking 还在为纪念币预约失败而烦恼吗&#xff1f;&#x1f914; 这款自动化预约…

作者头像 李华
网站建设 2026/4/25 6:08:04

创意无限:slick轮播dots个性化设计完全指南

创意无限&#xff1a;slick轮播dots个性化设计完全指南 【免费下载链接】slick the last carousel youll ever need 项目地址: https://gitcode.com/GitHub_Trending/sl/slick 在网页设计的视觉叙事中&#xff0c;轮播分页指示器往往被忽视&#xff0c;却承载着引导用户…

作者头像 李华
网站建设 2026/4/27 5:02:43

写了这么多年 Java,这几个神仙技巧你真的用过吗?

沉默是金&#xff0c;总会发光大家好&#xff0c;我是沉默如果你也是从 public static void main(String[] args) 和 System.out.println() 开始 Java 生涯的&#xff0c;那八成已经是“老 Java 人”了。上班这些年&#xff0c;我们每天都在写业务代码&#xff1a; CRUD、DTO、…

作者头像 李华