大侠好,欢迎来到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 代码:
代码语言:javascript复制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 代码:
代码语言:javascript复制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)
begin
state_s <= 0;
data <= 8'd0;
addr <= 24'd0;
command <= 8'd0;
state <= 0;
count <= 0;
end
else
case (state_s)
0 : begin
if(count < 200) //延迟一段时间
count <= count 1;
else
begin //发送读厂商ID的命令
command <= 8'h90;
addr <= 24'd0;
state <= 1;
count <= 1;
end
if(flag_done) //检查是否完成
state_s <= 1;
end
1 : begin
if(count < 200) //延迟一段时间
count <= count 1;
else
begin //写使能
command <= 8'h06;
state <= 3;
count <= 0;
end
if(flag_done) //检查是否完成
state_s <= 2;
end
2 : begin
if(count < 200) //延迟一段时间
count <= count 1;
else
begin //页操作
command <= 8'h02;
addr <= 24'd0;
state <= 4;
data <= 8'haa;
count <= 0;
end
if(flag_done) //检查是否完成
state_s <= 3;
end
3 : begin
if(count < 200) //延迟一段时间
count <= count 1;
else
begin //读寄存器
command <= 8'h05;
count <= 0;
state <= 5;
end
if(flag_done) //检查是否完成
state_s <= 4;
end
4 : begin
if(count < 200) //延迟一段时间
count <= count 1;
else
begin //读数据
command <= 8'h03;
addr <= 24'd0;
state <= 2;
count <= 0;
end
end
default: state_s <= 0;
endcase
endmodule
中间模块flash代码:
代码语言:javascript复制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)
begin
sclk <= 1;
count_s <= 0;
end
else if(cs)
begin
count_s <= 0;
sclk <= 1;
end
else
begin
if(count_s == 25 - 1) //产生1M的时钟
begin
count_s <= 0;
sclk <= ~sclk;
end
else
count_s <= count_s 1;
end
reg [1:0] signle_s;
//边沿检测电路
always @ (posedge clk or negedge rst_n)
if(!rst_n)
begin
signle_s <= 2'b11;
end
else
begin
signle_s[0] <= sclk;
signle_s[1] <= signle_s[0];
end
assign 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)
begin
q1 <= 0;
count <= 0;
cs <= 1;
temp <= 0;
d <= 0;
cnt <= 0;
s <= 0;
s1 <= 0;
s2 <= 0;
s3 <= 0;
flag_done <= 0;
s4 <= 0;
end
else
begin
if (state == 1) //state == 1进入读芯片的厂商和ID
case (s)
0: begin cs <= 0; temp <= {command,addr}; s <= 1; end
1 : begin
if(nege_dge) //下降沿发送数据
begin
if(count < 32)
begin
q1 <= temp[31];
count <= count 1;
temp <= {temp[30:0],temp[31]};
end
else
begin
count <= 0;
s <= 2;
end
end
else
q1 <= q1;
end
2 : begin
if(pose_dge) //上升沿采集数据
begin
if(count < 16)
begin
count <= count 1;
d <= {d[14:0],q0};
end
else
begin
s <= 3;
cs <= 1;
count <= 0;
flag_done <= 1;
show_data <= d;
end
end
else
begin
s <= 2;
end
end
3 : begin
flag_done <= 0;
end
endcase
else if(state == 2) //state == 2进入读模式
case (s1)
0: begin cs <= 0; temp <= {command,addr}; s1 <= 1; end
1 :begin
if(nege_dge)
begin
if(count < 32)
begin
q1 <= temp[31];
count <= count 1;
temp <= {temp[30:0],temp[31]};
end
else
begin
count <= 0;
s1 <= 2;
end
end
else
q1 <= q1;
end
2 : begin
if(pose_dge)
begin
if(count < 8)
begin
count <= count 1;
dou <= {dou[6:0],q0};
s1 <= 2;
end
else
begin
s1 <= 3;
cs <= 1;
count <= 0;
flag_done <= 1;
show_data <= dou;
end
end
else
begin
s1 <= 2;
end
end
3 : begin
flag_done <= 0;
end
endcase
else if(state == 3) //state == 3 进入写使能模式
case (s2)
0: begin cs <= 0; temp <= {command,addr}; s2 <= 1; end
1 :begin
if(nege_dge)
begin
if(count < 8)
begin
q1 <= temp[31];
count <= count 1;
temp <= {temp[30:0],temp[31]};
end
else
begin
count <= 0;
s2 <= 2;
cs <= 1;
flag_done <= 1;
end
end
else
q1 <= q1;
end
2 : flag_done <= 0;
endcase
else if(state == 4) //state == 4 进入页写操作
case (s3)
0: begin cs <= 0; xie <= {command,addr,data}; s3 <= 1; end
1 :begin
if(nege_dge)
begin
if(count < 40)
begin
q1 <= xie[39];
count <= count 1;
xie <= {xie[38:0],xie[39]};
end
else
begin
count <= 0;
s3 <= 2;
cs <= 1;
flag_done <= 1;
end
end
else
q1 <= q1;
end
2 : flag_done <= 0;
endcase
else if(state == 5) //state == 5 进入读第一个状态寄存器操作
case (s4)
0: begin cs <= 0; r_reg <= command; s4 <= 1; end
1 :begin
if(nege_dge)
begin
if(count < 8)
begin
q1 <= r_reg[7];
count <= count 1;
r_reg <= {r_reg[6:0],r_reg[7]};
end
else
begin
count <= 0;
s4 <= 2;
end
end
else
q1 <= q1;
end
2 : begin
if(pose_dge)
begin
if(count < 8)
begin
count <= count 1;
d <= {d[14:0],q0};
end
else
begin
cs <= 1;
count <= 0;
if(!d[8]) //判断BUSY位忙不忙,不忙进入下个状态
begin
flag_done <= 1;
s4 <= 3;
end
else //忙继续读第一个寄存器
s4 <= 0;
end
end
else
begin
s4 <= 2;
end
end
3 : flag_done <= 0;
endcase
end
endmodule
数码管模块seg代码:
代码语言:javascript复制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 5
reg [15:0] count;
reg flag;
always @ (posedge clk or negedge rst_n)
if(!rst_n)
begin
count <= 16'd0;
flag <= 1;
end
else
if(count == (`T1ms / 2 - 1))
begin
count <= 16'd0;
flag <= ~ flag;
end
else
begin
count <= count 1'b1;
end
reg [2:0] state;
reg [3:0] num;
always @ (posedge flag or negedge rst_n)
if(!rst_n)
begin
sel <= 3'b0;
state <= 3'b0;
num <= 4'b0;
end
else
begin
case (state)
s0:begin
state <= s1;
sel <= 6'b011111;
num <= data_in[23:20];
end
s1:begin
state <= s2;
sel <= 6'b101111;
num <= data_in[19:16];
end
s2:begin
state <= s3;
sel <= 6'b110111;
num <= data_in[15:12];
end
s3:begin
state <= s4;
sel <= 6'b111011;
num <= data_in[11:8];
end
s4:begin
state <= s5;
sel <= 6'b111101;
num <= data_in[7:4];
end
s5:begin
state <= s0;
sel <= 6'b111110;
num <= data_in[3:0];
end
default:state <= s0;
endcase
end
always @ (*)
begin
case (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:;
endcase
end
endmodule
SignalTap 采集图
图中显示的和我们的设计一样,发送的各个命令也是一样的,我们写入的是AA然后接收的也是AA,设计正确。
END
后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!