news 2026/6/10 14:47:10

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/6/10 11:17:02

Bypass Paywalls Clean终极指南:解锁全球付费内容的技术方案

在信息获取日益重要的今天&#xff0c;付费墙已成为阻碍知识传播的主要障碍。Bypass Paywalls Clean作为一款专业级浏览器扩展&#xff0c;通过创新的技术手段帮助用户突破这一限制&#xff0c;实现全球优质内容的无障碍访问。本指南将深入解析其工作原理、安装配置及最佳实践。…

作者头像 李华
网站建设 2026/6/10 11:26:02

Excalidraw核心功能实现原理揭秘

Excalidraw 核心功能实现原理揭秘 在数字白板工具层出不穷的今天&#xff0c;大多数产品追求的是精准、规整与自动化。而 Excalidraw 却反其道而行之——它用“不完美”的手绘风格&#xff0c;还原了人类最原始的创作直觉&#xff1a;草图、涂鸦、即兴表达。这种看似简单的视觉…

作者头像 李华
网站建设 2026/6/10 7:49:29

树莓派4B能跑LobeChat吗?极限低配环境尝试

树莓派4B能跑LobeChat吗&#xff1f;极限低配环境尝试 在智能家居设备日益复杂的今天&#xff0c;越来越多的极客开始思考&#xff1a;我们能否拥有一台完全属于自己的AI助手——不依赖云端、不上传数据、24小时静音运行&#xff0c;还能用语音对话控制家里的灯和温湿度&#x…

作者头像 李华
网站建设 2026/6/9 15:49:43

从图文到视频,如何用Coze跑通“小红书儿童绘本”的商业闭环?

大家好&#xff0c;我是小肥肠&#xff01;上周的儿童绘本图文教程大家反响热烈&#xff0c;但我也听到了大家的呼声&#xff1a;视频才是现在的流量密码&#xff01;没问题&#xff0c;今天直接安排&#xff01;本期教程教你用 Coze Nano Banana Pro 搭建全自动视频绘本工作流…

作者头像 李华
网站建设 2026/6/9 18:49:49

MQTT网络传输协议巩固知识基础题(1)

1. MQTT 是什么类型的协议? A. 请求-响应协议 B. 发布-订阅协议 C. 点对点协议 D. 广播协议 答案:B 解析: MQTT(Message Queuing Telemetry Transport)是基于发布-订阅模式的轻量级消息传输协议。 2. MQTT 主要设计用于哪种场景? A. 高带宽网络环境 B. 低带宽、高延迟…

作者头像 李华
网站建设 2026/6/10 12:01:41

AI绘画不止“出图快”:它正让文化活起来、让普通人赚到钱

前几天在南宁举办的中国—东盟博览会上&#xff0c;一个“AI非遗服饰体验区”被围得水泄不通。外国游客对着屏幕输入自己的照片&#xff0c;一键选择印尼纱笼、泰国宋干节服饰或是广西壮锦盛装&#xff0c;十秒不到&#xff0c;一张融合了个人特质与传统纹样的合影就生成了。我…

作者头像 李华