通过全擦除和扇区擦除可将固化到Flash芯片中的程序擦除。
一 SPI_Flash全擦除
全擦除就是将Flash芯片所有的存储空间都进行擦除,使各个存储空间的内存数据都恢复到初始值,Flash全擦除有两种方式:
(1)利用FPGA编译软件,通过Quartus软件中的“programmer”可将烧录到Flash中的*.jic文件擦除,如下图所示。
(2)通过编写全擦除程序,将Flash芯片进行全擦除。
1.1实验内容
编写全擦除程序,将Flash芯片事先烧录的程序擦除,使FPGA重新上电后,程序无法自启动。
全擦除指令和时序如下图所示:
由手册可知,全擦除指令是将Flash芯片中的所有存储单元置1,在写入全擦除指令之前,需要写入写使能(WREN)指令,将芯片置为写使能锁存(WEL)状态。拉低片选信号,写入全擦除指令,在指令写入过程中片选信号保持低电平,当指令被芯片所存后,将片选信号拉高;全擦除指令被锁存并执行后,需要等待一个完整的全擦除周期(tBE),才能完成Flash芯片的全擦除操作。
写使能指令和时序如下图所示:
写使能指令、全擦除指令以及其它操作指令写入Flash之前需要遵循芯片的串行输入时序,如下图所示:
需要注意tSLCH、tCHSH、tSHSL这三个参数值:
tSLCH:片选信号拉低到第一个有效数据写入,需要等待的时间,必须大于等于5ns。
tCHSH:最后一个有效数据写入到片选信号拉高,需要等待的时间,必须大于等于5ns。
tSHSL:片选信号拉高到再次拉低,需要等待的时间,必须大于等于100ns。
因此,完整的全擦除操作时序图如下所示,为了方便起见,每个等待时间统一为32个时钟周期,即640ns。
1.2工程代码
全擦除工程主要包含顶层例化flash_be_top、按键消抖key_filter、擦除控制spi_flash_be三个子模块。
spi_flash_be模块代码如下:
`timescale 1ns/1ps
module spi_flash_be(
input clk,
input rst_n,
input key_in,
output reg cs_n,
output reg sck,
output reg mosi
);
parameter WR_EN_INST = 8'h06; //写使能指令
parameter BE_INST = 8'hc7; //全擦除指令
parameter IDLE = 4'b0001; //空闲状态
parameter WR_EN = 4'b0010; //写使能状态
parameter DELAY = 4'b0100; //片选拉高等待状态
parameter BE = 4'b1000; //全擦除状态
reg [3:0] curr_state;
reg [3:0] next_state;
reg [4:0] clk_cnt; //系统时钟计数器
reg [2:0] byte_cnt; //字节计数器
reg [1:0] sck_cnt; //sck时钟计数器
reg [2:0] bit_cnt; //数据位计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
curr_state <= IDLE;
else
curr_state <= next_state;
end
always @(*)
begin
next_state <= IDLE;
case(curr_state)
IDLE:begin
if(key_in)
next_state <= WR_EN;
else
next_state <= IDLE;
end
WR_EN:begin
if(byte_cnt == 3'd1 && clk_cnt == 5'd31)
next_state <= DELAY;
else
next_state <= WR_EN;
end
DELAY:begin
if(byte_cnt == 3'd2 && clk_cnt == 5'd31)
next_state <= BE;
else
next_state <= DELAY;
end
BE:begin
if(byte_cnt == 3'd6 && clk_cnt == 5'd31)
next_state <= IDLE;
else
next_state <= BE;
end
default: next_state <= IDLE;
endcase
end
//clk_Cnt:系统时钟计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_cnt <= 5'd0;
else if(curr_state != IDLE)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 5'd0;
end
//byte_cnt:字节计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
byte_cnt <= 3'd0;
else if(byte_cnt == 3'd6 && clk_cnt == 5'd31)
byte_cnt <= 3'd0;
else if(clk_cnt == 5'd31)
byte_cnt <= byte_cnt + 1'b1;
else
byte_cnt <= byte_cnt;
end
//sck_cnt:sck时钟计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sck_cnt <= 2'd0;
else if(curr_state == WR_EN && byte_cnt == 3'd1)
sck_cnt <= sck_cnt + 1'b1;
else if(curr_state == BE && byte_cnt == 3'd5)
sck_cnt <= sck_cnt + 1'b1;
else
sck_cnt <= 2'd0;
end
//bit_cnt:数据位计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
bit_cnt <= 3'd0;
else if(sck_cnt == 2'd1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
//cs_n:片选信号
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cs_n <= 1'b1;
else if(key_in)
cs_n <= 1'b0;
else if(byte_cnt == 3'd2 && clk_cnt == 5'd31)
cs_n <= 1'b1;
else if(byte_cnt == 3'd3 && clk_cnt == 5'd31)
cs_n <= 1'b0;
else if(byte_cnt == 3'd6 && clk_cnt == 5'd31)
cs_n <= 1'b1;
else
cs_n <= cs_n;
end
//sck:sck时钟生成
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sck <= 1'b0;
else if(sck_cnt == 2'd1)
sck <= 1'b1;
else if(sck_cnt == 2'd3)
sck <= 1'b0;
else
sck <= sck;
end
//mosi:
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
mosi <= 1'b0;
else if(byte_cnt == 3'd1 && clk_cnt == 5'd31)
mosi <= 1'b0;
else if(byte_cnt == 3'd5 && clk_cnt == 5'd31)
mosi <= 1'b0;
else if(byte_cnt == 3'd1 && sck_cnt == 2'd0) //写入写使能指令
mosi <= WR_EN_INST[7 - bit_cnt];
else if(byte_cnt == 3'd5 && sck_cnt == 2'd0) //写入全擦除指令
mosi <= BE_INST[7 - bit_cnt];
else
mosi <= mosi;
end
endmodule
顶层模块flash_be_top代码如下:
`timescale 1ns/1ps
module flash_be_top(
input sys_clk,
input rst_n,
input key_in,
output cs_n,
output sck,
output mosi
);
wire key_flag;
spi_flash_be u_spi_flash_be(
.clk(sys_clk),
.rst_n(rst_n),
.key_in(key_flag),
.cs_n(cs_n),
.sck(sck),
.mosi(mosi)
);
key_filter u_key_filter(
.clk(sys_clk),
.rst_n(rst_n),
.key_in(key_in), //按键输入
.key_flag(key_flag) //检测到按键按下标志信号
);
endmodule
仿真代码flsh_be_top_tb如下:
`timescale 1ns/1ps
module flsh_be_top_tb();
reg sys_clk;
reg rst_n;
reg key_in;
wire cs_n;
wire sck;
wire mosi;
initial
begin
sys_clk = 1'b1;
rst_n = 1'b0;
key_in = 1'b1;
#200
rst_n = 1'b1;
key_in = 1'b0;
#21_000_000
key_in = 1'b1;
end
always #10 sys_clk <= ~sys_clk;
defparam memory.mem_access.initfile = "initmemory.txt";
flash_be_top u_flash_be_top(
.sys_clk(sys_clk),
.rst_n(rst_n),
.key_in(key_in),
.cs_n(cs_n),
.sck(sck),
.mosi(mosi)
);
m25p16 memory
(
.c (sck ), //输入串行时钟,频率12.5Mhz,1bit
.data_in (mosi ), //输入串行指令或数据,1bit
.s (cs_n ), //输入片选信号,1bit
.w (1'b1 ), //输入写保护信号,低有效,1bit
.hold (1'b1 ), //输入hold信号,低有效,1bit
.data_out ( ) //输出串行数据
);
endmodule
仿真结果如下:
仿真结果显示,当byte_cnt 为3’d1时完成写使能指令8’h06的写入,当字节计数器byte_cnt为3’d5时完成全擦除指令的写入。之后等待40s后,仿真结果提示全擦除操作完成。
1.3 板级验证
首先将流水灯程序烧录到Flash芯片中,断电后重新上电检测Flash芯片中的流水灯程序可以自启动,然后将全擦除程序下载到FPGA开发板中,之后断电后重新上电,发现流水灯程序无法自启动,可以说明全擦除程序板级验证成功。
二 SPI_flash扇区擦除
M25P16 Flash芯片总存储容量为16Mbit,分为32个扇区,每个扇区容量为512Kbit,扇区地址如下图所示:
2.1实验内容
通过扇区擦除工程将实现烧录到Flash芯片中的流水灯程序所在的某个扇区进行擦除,使流水灯程序上电之后无法自启动,本次选择擦除第0个扇区,扇区地址为24’h00_05_20。
扇区擦除指令和时序如下所示:
从数据手册可以看出,扇区擦除指令是将Flash芯片中的被选中扇区的所有存储单元置为1,在Flash芯片写入扇区擦除指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态。然后拉低片选信号,写入扇区擦除指令、扇区地址、页地址和字节地址,在写入过程中,片选信号一直保持低电平状态,当指令和地址被芯片锁存后,将片选信号拉高。扇区擦除指令、地址被锁存并执行后,需要等待一个完整的扇区擦除周期(tSE),才能完成Flash芯片的扇区擦除操作。
完整的扇区擦除操作时序如下图所示,为了方便起见,每个等待时间统一为32个时钟周期,即640ns。
2.2工程代码
扇区擦除工程主要包括顶层例化flash_se_top、按键消抖key_filter、扇区擦除控制spi_flash_se三个子模块。
扇区擦除控制spi_flash_se模块如下:
`timescale 1ns/1ps
module spi_flash_se
#(
parameter SE_ADDR = 8'h00, //扇区地址
parameter PP_ADDR = 8'h05, //页地址
parameter BYTE_ADDR = 8'h20 //字节地址
)
(
input clk,
input rst_n,
input key_in,
output reg cs_n,
output reg sck,
output reg mosi
);
localparam WR_EN_INST = 8'h06; //写使能指令
localparam SE_INST = 8'hd8; //扇区擦除指令
localparam IDLE = 4'b0001;
localparam WR_EN = 4'b0010;
localparam DELAY = 4'b0100;
localparam SE = 4'b0000;
reg [3:0] curr_state;
reg [3:0] next_state;
reg [4:0] clk_cnt;
reg [3:0] byte_cnt;
reg [2:0] bit_cnt;
reg [1:0] sck_cnt;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
curr_state <= IDLE;
else
curr_state <= next_state;
end
always @(*)
begin
next_state <= IDLE;
case(curr_state)
IDLE:begin
if(key_in)
next_state <= WR_EN;
else
next_state <= IDLE;
end
WR_EN:begin
if(byte_cnt == 4'd2 && clk_cnt == 5'd31)
next_state <= DELAY;
else
next_state <= WR_EN;
end
DELAY:begin
if(byte_cnt == 4'd3 && clk_cnt == 5'd31)
next_state <= SE;
else
next_state <= DELAY;
end
SE:begin
if(byte_cnt == 4'd9 && clk_cnt == 5'd31)
next_state <= IDLE;
else
next_state <= SE;
end
default: next_state <= IDLE;
endcase
end
//clk_cnt:系统时钟计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_cnt <= 5'd0;
else if(curr_state != IDLE)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 5'd0;
end
//byte_cnt:字节计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
byte_cnt <= 4'd0;
else if(byte_cnt == 4'd9 && clk_cnt == 5'd31)
byte_cnt <= 4'd0;
else if(clk_cnt == 5'd31)
byte_cnt <= byte_cnt + 1'b1;
else
byte_cnt <= byte_cnt;
end
//sck_cnt:sck时钟计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sck_cnt <= 2'd0;
else if(curr_state == WR_EN && byte_cnt == 4'd1)
sck_cnt <= sck_cnt + 1'b1;
else if(byte_cnt >= 4'd5 && byte_cnt <= 4'd8)
sck_cnt <= sck_cnt + 1'b1;
else
sck_cnt <= 2'd0;
end
//bit_cnt:数据位计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
bit_cnt <= 1'b0;
else if(sck_cnt == 2'd1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
//sck:sck时钟生成
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sck <= 1'b0;
else if(sck_cnt == 2'd1)
sck <= 1'b1;
else if(sck_cnt == 2'd3)
sck <= 1'b0;
else
sck <= sck;
end
//cs_n:片选信号
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cs_n <= 1'b1;
else if(key_in)
cs_n <= 1'b0;
else if(curr_state == WR_EN && byte_cnt == 4'd2 && clk_cnt == 5'd31)
cs_n <= 1'b1;
else if(curr_state == DELAY && byte_cnt == 4'd3 && clk_cnt == 5'd31)
cs_n <= 1'b0;
else if(curr_state == SE && byte_cnt == 4'd9 && clk_cnt == 5'd31)
cs_n <= 1'b1;
else
cs_n <= cs_n;
end
//mosi:数据输出
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
mosi <= 1'b0;
else if(byte_cnt == 4'd1 && clk_cnt == 5'd31)
mosi <= 1'b0;
else if(byte_cnt == 4'd8 && clk_cnt == 5'd31)
mosi <= 1'b0;
else if(byte_cnt == 4'd1 && sck_cnt == 2'd0)
mosi <= WR_EN_INST[7 - bit_cnt];
else if(byte_cnt == 4'd5 && sck_cnt == 2'd0)
mosi <= SE_INST[7 - bit_cnt];
else if(byte_cnt == 4'd6 && sck_cnt == 2'd0)
mosi <= SE_ADDR[7 - bit_cnt];
else if(byte_cnt == 4'd7 && sck_cnt == 2'd0)
mosi <= PP_ADDR[7 - bit_cnt];
else if(byte_cnt == 4'd8 && sck_cnt == 2'd0)
mosi <= BYTE_ADDR[7 - bit_cnt];
else
mosi <= mosi;
end
endmodule
顶层例化flash_se_top模块如下:
`timescale 1ns/1ps
module flash_se_top(
input sys_clk,
input rst_n,
input key_in,
output cs_n,
output sck,
output mosi
);
parameter SE_ADDR = 8'h00; //扇区擦除地址
parameter PP_ADDR = 8'h05; //页地址
parameter BYTE_ADDR = 8'h20; //字节地址
wire key_flag;
//spi_flash_se模块例化
spi_flash_se
#(
.SE_ADDR (SE_ADDR),
.PP_ADDR (PP_ADDR),
.BYTE_ADDR (BYTE_ADDR)
)
u_spi_flash_se(
.clk(sys_clk),
.rst_n(rst_n),
.key_in(key_flag),
.cs_n(cs_n),
.sck(sck),
.mosi(mosi)
);
//key_filter 模块例化
key_filter u_key_filter(
.clk(sys_clk),
.rst_n(rst_n),
.key_in(key_in), //按键输入
.key_flag(key_flag) //检测到按键按下标志信号
);
endmodule
仿真代码flash_se_top_tb如下:
`timescale 1ns/1ps
module flash_se_top_tb();
reg sys_clk;
reg rst_n;
reg key_in;
wire cs_n;
wire sck;
wire mosi;
initial
begin
sys_clk = 1'b1;
rst_n = 1'b0;
key_in = 1'b1;
#200
rst_n = 1'b1;
key_in = 1'b0;
#21_000_000
key_in = 1'b1;
end
always #10 sys_clk <= ~sys_clk;
defparam memory.mem_access.initfile = "initmemory.txt";
flash_se_top u_flash_se_top(
.sys_clk(sys_clk),
.rst_n(rst_n),
.key_in(key_in),
.cs_n(cs_n),
.sck(sck),
.mosi(mosi)
);
m25p16 memory
(
.c (sck ), //输入串行时钟,频率12.5Mhz,1bit
.data_in (mosi ), //输入串行指令或数据,1bit
.s (cs_n ), //输入片选信号,1bit
.w (1'b1 ), //输入写保护信号,低有效,1bit
.hold (1'b1 ), //输入hold信号,低有效,1bit
.data_out ( ) //输出串行数据
);
endmodule
仿真结果如下:
仿真结果显示,在byte_cnt为4’d1时,完成写使能指令写入,当byte_cnt为4’d5时,完成扇区擦除指令写入,随后发送需要擦除的扇区地址、页地址和字节地址三个字节数据,经过3s后,仿真结果提示扇区擦除操作完成。
2.3 板级验证
将流水灯程固化到Flash芯片中,断电后上电验证流水灯程序可以自启动。然后下载扇区擦除工程到Cyclone IV 开发板中,按下按键,执行扇区擦除操作。最后,重新断电后上电,发现流水灯程序无法自启动,证明扇区擦除程序板级验证成功。