news 2026/4/16 6:27:43

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/4/16 7:56:31

Windows DLL注入新利器Xenos:揭秘进程操控的底层艺术

Windows DLL注入新利器Xenos&#xff1a;揭秘进程操控的底层艺术 【免费下载链接】Xenos Windows dll injector 项目地址: https://gitcode.com/gh_mirrors/xe/Xenos 在Windows系统开发与安全研究领域&#xff0c;进程间通信与功能扩展始终是技术探索的重要方向。Xenos作…

作者头像 李华
网站建设 2026/4/14 2:28:50

AI驱动的文献管理革新:zotero-gpt智能筛选工具终极指南

AI驱动的文献管理革新&#xff1a;zotero-gpt智能筛选工具终极指南 【免费下载链接】zotero-gpt GPT Meet Zotero. 项目地址: https://gitcode.com/gh_mirrors/zo/zotero-gpt 在当今信息爆炸的时代&#xff0c;科研工作者每天都要面对海量的学术文献&#xff0c;如何快速…

作者头像 李华
网站建设 2026/4/15 20:32:10

阴阳师自动化脚本百鬼夜行AI智能撒豆终极指南

阴阳师自动化脚本百鬼夜行AI智能撒豆终极指南 【免费下载链接】OnmyojiAutoScript Onmyoji Auto Script | 阴阳师脚本 项目地址: https://gitcode.com/gh_mirrors/on/OnmyojiAutoScript 在阴阳师这款深受玩家喜爱的日式和风手游中&#xff0c;百鬼夜行作为获取式神碎片的…

作者头像 李华
网站建设 2026/4/16 12:27:51

Page Assist:本地AI助手重新定义网页浏览体验

在信息爆炸的时代&#xff0c;我们每天都要面对海量的网页内容。你是否曾经为以下问题感到困扰&#xff1a;阅读长篇技术文档时理解困难&#xff0c;面对复杂表格数据时无从下手&#xff0c;或者需要在多个网页间来回切换寻找关键信息&#xff1f;Page Assist正是为解决这些痛点…

作者头像 李华
网站建设 2026/4/16 5:09:56

飞书文档批量导出工具:高效解决企业文档迁移难题

飞书文档批量导出工具&#xff1a;高效解决企业文档迁移难题 【免费下载链接】feishu-doc-export 项目地址: https://gitcode.com/gh_mirrors/fe/feishu-doc-export feishu-doc-export是一款专为飞书用户设计的跨平台文档批量导出工具&#xff0c;能够快速将飞书知识库…

作者头像 李华