大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、”行侠仗义”栏里获取其他感兴趣的资源,或者一起煮酒言欢。
今天给大侠带来基于FPGA的内存128M flash芯片控制器设计,话不多说,上货。
设计原理及思路
FLASH闪存 闪存的英文名称是”Flash Memory”,一般简称为”Flash”,它属于内存器件的一种,是一种不挥发性( Non-Volatile )内存。
闪存的物理特性与常见的内存有根本性的差异:目前各类 DDR 、 SDRAM 或者 RDRAM 都属于挥发性内存,只要停止电流供应内存中的数据便无法保持,因此每次电脑开机都需要把数据重新载入内存;闪存在没有电流供应的条件下也能够长久地保持数据,其存储特性相当于硬盘,这项特性正是闪存得以成为各类便携型数字设备的存储介质的基础。
本次设计使用的是 W25Q128FV 内存128M的flash芯片,大家可以自己在官网上下载器件手册。在这里为了方便,也提供给各位,需要使用的可以在公众号内部回复“W25Q128FV手册资料”,各位可以根据实际项目应用灵活设计。
这款flash芯片的的存储是一个扇区4KB,一个扇区可以存256个字,一个字是8位,一个块是64KB,一共有256个块组成一个存储flash内存。
在下面的讲解中,将主要讲实现一下字节的读写,本次设计使用的协议是SPI协议,这个芯片支持QSPI,双端口SPI等。flash有三个状态寄存器,每一个状态寄存器的每一位都有各自的功能。大家可以具体的看器件手册,首先给大家简单的讲一下第一个状态寄存器。

这个状态寄存器第一位是可读、忙和不忙的标志位,大家可以在我们的设计中判断芯片是否忙和不忙来是否进行下一步的操作。第二位是一个写标志的信号,当写使能打开的时候它为1,只有它为1的时候我们才可以进行写,值得一说的不管是页操作,还是擦除等命令后都会使这个标志位变成0。然后前面的命令算的上的是保护命令,具体有使用的逻辑功能。
在flash中,写数据前先要擦除数据(想要擦除的地方),然后进行写,如果没有用过的flash芯片的话那么可以不用擦除,因为flash掉电不丢失数据。
设计思路大概是先读出器件厂商和芯片ID,然后写命令,写使能打开,页操作写入数据(值得说明的是我们FLASH是新的所以没进行擦除命令,建议擦除—关闭写使能 — 打开写使能),然后读第一个寄存器判断芯片的第一位是否忙,不忙然后进行读操作之后再数码管上显示出我们写入的数据。
部分操作命令如下:

我们的发送格式为在时钟的上升沿写入命令,在时钟的下降沿读出命令,用的是标准的SPI协议,端口IO0,和IO1,都是单向的。
写使能时序:

读使能时序:

其他的时序在这里就不分别列举出来了,大家可以参考器件手册。
设计架构
本次的设计是用一个FSM控制器来控制发送什么命令,flash模块判断FSM发送过来的state信号来选择应该执行什么操作,当命令写入或者读出后,会发送一个flag_done命令,这个命令让我们判断上个指令是否完成,如果完成后FAM将发送下一个命令。总体架构图如下:

设计代码
顶层模块 flash_top 代码:
module flash_top(clk , rst_n, q0, q1, sclk, cs, seg, sel);input clk, rst_n;input q0;output q1;output sclk;output cs;output [5:0] sel;output [7:0] seg;wire [7:0] command;wire [23:0] addr;wire [2:0] state;wire [7:0] data;wire [23:0] show_data;wire flag_done;flash flash_dut(.clk(clk) ,.rst_n(rst_n),.q0(q0),.q1(q1),.sclk(sclk),.cs(cs),.command(command),.addr(addr),.state(state),.data(data),.show_data(show_data),.flag_done(flag_done));fsm fsm_dut(.clk(clk),.rst_n(rst_n),.flag_done(flag_done),.command(command),.addr(addr),.state(state),.data(data));seg seg_dut(.clk(clk),.rst_n(rst_n),.sel(sel),.seg7(seg),.data_in(show_data));endmodule
设计模块 fsm 代码:
module fsm(clk, rst_n, flag_done, command, addr, state, data);input clk, rst_n;input flag_done; //输入标志位output reg [7:0] command; //输出命令output reg [23:0] addr; //输出地址output reg [2:0] state; //输出状态模式output reg [7:0] data; //输出写入数据reg [2:0] state_s;reg [20:0] count;always @ (posedge clk)if(!rst_n)beginstate_s <= 0;data <= 8'd0;addr <= 24'd0;command <= 8'd0;state <= 0;count <= 0;endelsecase (state_s)0 : beginif(count < 200) //延迟一段时间count <= count + 1;elsebegin //发送读厂商ID的命令command <= 8'h90;addr <= 24'd0;state <= 1;count <= 1;endif(flag_done) //检查是否完成state_s <= 1;end1 : beginif(count < 200) //延迟一段时间count <= count + 1;elsebegin //写使能command <= 8'h06;state <= 3;count <= 0;endif(flag_done) //检查是否完成state_s <= 2;end2 : beginif(count < 200) //延迟一段时间count <= count + 1;elsebegin //页操作command <= 8'h02;addr <= 24'd0;state <= 4;data <= 8'haa;count <= 0;endif(flag_done) //检查是否完成state_s <= 3;end3 : beginif(count < 200) //延迟一段时间count <= count + 1;elsebegin //读寄存器command <= 8'h05;count <= 0;state <= 5;endif(flag_done) //检查是否完成state_s <= 4;end4 : beginif(count < 200) //延迟一段时间count <= count + 1;elsebegin //读数据command <= 8'h03;addr <= 24'd0;state <= 2;count <= 0;endenddefault: state_s <= 0;endcaseendmodule
中间模块flash代码:
module flash (clk , rst_n, q0, q1, sclk, cs, command, addr, state, data, show_data, flag_done);input clk, rst_n;input q0;output reg q1;output reg sclk;output reg cs;input [7:0] command; //输入命令input [23:0] addr; //地址input [2:0] state; //状态input [7:0] data; //数据output reg [23:0] show_data; //显示output reg flag_done; //命令完成标志reg [5:0] count;reg [5:0] cnt;reg [31:0] temp;reg [15:0] d;reg [5:0] count_s;reg [7:0] dou;reg [39:0] xie;reg [7:0] r_reg;always @ (posedge clk)if(!rst_n)beginsclk <= 1;count_s <= 0;endelse if(cs)begincount_s <= 0;sclk <= 1;endelsebeginif(count_s == 25 - 1) //产生1M的时钟begincount_s <= 0;sclk <= ~sclk;endelsecount_s <= count_s + 1;endreg [1:0] signle_s;//边沿检测电路always @ (posedge clk or negedge rst_n)if(!rst_n)beginsignle_s <= 2'b11;endelsebeginsignle_s[0] <= sclk;signle_s[1] <= signle_s[0];endassign pose_dge = signle_s[0] && ~signle_s[1]; //上升沿脉冲assign nege_dge = ~signle_s[0] && signle_s[1]; //下降沿脉冲reg [1:0] s;reg [1:0] s1,s2,s3,s4;always @ (posedge clk or negedge rst_n)if(!rst_n)beginq1 <= 0;count <= 0;cs <= 1;temp <= 0;d <= 0;cnt <= 0;s <= 0;s1 <= 0;s2 <= 0;s3 <= 0;flag_done <= 0;s4 <= 0;endelsebeginif (state == 1) //state == 1进入读芯片的厂商和IDcase (s)0: begin cs <= 0; temp <= {command,addr}; s <= 1; end1 : beginif(nege_dge) //下降沿发送数据beginif(count < 32)beginq1 <= temp[31];count <= count + 1;temp <= {temp[30:0],temp[31]};endelsebegincount <= 0;s <= 2;endendelseq1 <= q1;end2 : beginif(pose_dge) //上升沿采集数据beginif(count < 16)begincount <= count + 1;d <= {d[14:0],q0};endelsebegins <= 3;cs <= 1;count <= 0;flag_done <= 1;show_data <= d;endendelsebegins <= 2;endend3 : beginflag_done <= 0;endendcaseelse if(state == 2) //state == 2进入读模式case (s1)0: begin cs <= 0; temp <= {command,addr}; s1 <= 1; end1 :beginif(nege_dge)beginif(count < 32)beginq1 <= temp[31];count <= count + 1;temp <= {temp[30:0],temp[31]};endelsebegincount <= 0;s1 <= 2;endendelseq1 <= q1;end2 : beginif(pose_dge)beginif(count < 8)begincount <= count + 1;dou <= {dou[6:0],q0};s1 <= 2;endelsebegins1 <= 3;cs <= 1;count <= 0;flag_done <= 1;show_data <= dou;endendelsebegins1 <= 2;endend3 : beginflag_done <= 0;endendcaseelse if(state == 3) //state == 3 进入写使能模式case (s2)0: begin cs <= 0; temp <= {command,addr}; s2 <= 1; end1 :beginif(nege_dge)beginif(count < 8)beginq1 <= temp[31];count <= count + 1;temp <= {temp[30:0],temp[31]};endelsebegincount <= 0;s2 <= 2;cs <= 1;flag_done <= 1;endendelseq1 <= q1;end2 : flag_done <= 0;endcaseelse if(state == 4) //state == 4 进入页写操作case (s3)0: begin cs <= 0; xie <= {command,addr,data}; s3 <= 1; end1 :beginif(nege_dge)beginif(count < 40)beginq1 <= xie[39];count <= count + 1;xie <= {xie[38:0],xie[39]};endelsebegincount <= 0;s3 <= 2;cs <= 1;flag_done <= 1;endendelseq1 <= q1;end2 : flag_done <= 0;endcaseelse if(state == 5) //state == 5 进入读第一个状态寄存器操作case (s4)0: begin cs <= 0; r_reg <= command; s4 <= 1; end1 :beginif(nege_dge)beginif(count < 8)beginq1 <= r_reg[7];count <= count + 1;r_reg <= {r_reg[6:0],r_reg[7]};endelsebegincount <= 0;s4 <= 2;endendelseq1 <= q1;end2 : beginif(pose_dge)beginif(count < 8)begincount <= count + 1;d <= {d[14:0],q0};endelsebegincs <= 1;count <= 0;if(!d[8]) //判断BUSY位忙不忙,不忙进入下个状态beginflag_done <= 1;s4 <= 3;endelse //忙继续读第一个寄存器s4 <= 0;endendelsebegins4 <= 2;endend3 : flag_done <= 0;endcaseendendmodule
数码管模块seg代码:
module seg(clk,rst_n,sel,seg7,data_in);input clk;input rst_n;input [23:0] data_in;output reg [5:0] sel;output reg [7:0] seg7;parameter s0 = 3'b000;parameter s1 = 3'b001;parameter s2 = 3'b010;parameter s3 = 3'b011;parameter s4 = 3'b100;parameter s5 = 3'b101;`define T1ms 50_000//`define T1ms 5reg [15:0] count;reg flag;always @ (posedge clk or negedge rst_n)if(!rst_n)begincount <= 16'd0;flag <= 1;endelseif(count == (`T1ms / 2 - 1))begincount <= 16'd0;flag <= ~ flag;endelsebegincount <= count + 1'b1;endreg [2:0] state;reg [3:0] num;always @ (posedge flag or negedge rst_n)if(!rst_n)beginsel <= 3'b0;state <= 3'b0;num <= 4'b0;endelsebegincase (state)s0:beginstate <= s1;sel <= 6'b011111;num <= data_in[23:20];ends1:beginstate <= s2;sel <= 6'b101111;num <= data_in[19:16];ends2:beginstate <= s3;sel <= 6'b110111;num <= data_in[15:12];ends3:beginstate <= s4;sel <= 6'b111011;num <= data_in[11:8];ends4:beginstate <= s5;sel <= 6'b111101;num <= data_in[7:4];ends5:beginstate <= s0;sel <= 6'b111110;num <= data_in[3:0];enddefault:state <= s0;endcaseendalways @ (*)begincase (num)0:seg7 = 8'b1100_0000;1:seg7 = 8'b1111_1001;2:seg7 = 8'b1010_0100;3:seg7 = 8'b1011_0000;4:seg7 = 8'b1001_1001;5:seg7 = 8'b1001_0010;6:seg7 = 8'b1000_0010;7:seg7 = 8'b1111_1000;8:seg7 = 8'b1000_0000;9:seg7 = 8'b1001_0000;10:seg7 = 8'b1000_1000;11:seg7 = 8'b1000_0011;12:seg7 = 8'b1100_0110;13:seg7 = 8'b1010_0001;14:seg7 = 8'b1000_0110;15:seg7 = 8'b1000_1110;default:;endcaseendendmodule
SignalTap 采集图

图中显示的和我们的设计一样,发送的各个命令也是一样的,我们写入的是AA然后接收的也是AA,设计正确。