【国产FPGA高云GW1N-4系列开发板测评】FLASH操作
[复制链接]
本帖最后由 怀揣少年梦 于 2021-12-28 14:12 编辑
一、目的
使用高云GW1N-4系列开发板来擦除、页写FLASH,FLASH使用的型号为W25Q64JV。
二、W25Q64JVSSIQ的特征
1、支持3种通信方式,SPI、Dual SPI和Quad SPI。FLASH的存储单元无法写入bit 1,只能写入bit 0,所以写入数据之前要将原来的数据擦除(FFh),遇到写入bit 1的情况不作处理。W25QQ64JV的特征为如下图所示:
2、FLASH的存储结构
W25Q64JV 由Block0~Block127共128个Block组成,容量大小为64M-bit,即64M=128*64*1024*8bit/1024/1024。每个Block由Sector0~Sector15共16个Sector组成,每个Sector大小为4KB,由16个Page组成。以第一个Sector为例,第1个Page地址从xx0000h~xx00FF开始,第2个Page地址从xx0100~xx01FF开始,第3个Page地址从xx0200~xx02FF开始,以此类推...,第16个Page地址从xx0F00~xx0FFF开始。每个Page的大小为256个Byte组成,后面会看到Page Programd最大支持256个Byte。说明框图如下:
- FLASH的相关操作时序图
1)写使能时序
2)全擦除时序
3)页写时序
根据以上的时序绘制波形图,也就是SPI模拟时序波形图。因为FPGA时钟50MHz,所以把50M进行四分频,并且32个时钟周期延时640ns。正好可以作为两次数据间隔的延时周期。
三、程序编写
1、全擦除程序
1)全擦除波形图绘制
2)编写代码
//`timescale 1ns/1ns
/*
Module Name : flash_ce
Project Name : flash
Description : flash全擦除模块
把主时钟四分频成12.5MHz,作为spi的输入时钟。
写一次数据与下一次数据之间必须等待50ns,每个时钟周期只能写入1bit数据,写一个字节即需要8个spi时钟周期,即32个系统时钟周期,说明一条完整指令需要640ns
*/
module flash_ce(
input wire sys_clk, //系统时钟,频率50MHz
input wire sys_rst_n, //复位信号,低电平有效
input wire key, //按键输入信号
output reg spi_cs_n, //片选信号
output reg spi_clk, //串行时钟
output reg spi_mosi //主输出从输入数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter IDLE = 4'b0001 , //初始状态 状态机四个状态
WR_EN = 4'b0010 , //写状态
DELAY = 4'b0100 , //等待状态
CE = 4'b1000 ; //全擦除状态
parameter WR_EN_INST = 8'b0000_0110, //写使能指令 06
CE_INST = 8'b1100_0111; //全擦除指令 C7
//reg define
reg [2:0] cnt_byte; //字节计数器 记录输出字节个数和等待时间
reg [3:0] state ; //状态机状态
reg [4:0] cnt_clk ; //系统时钟计数器,用以记录单子字节
reg [1:0] cnt_sck ; //串行时钟计数器,生成系统时钟
reg [2:0] cnt_bit ; //比特计数器,产生高低位,控制MOSI输出
//除了在空闲状态,其他状态都进行系统时钟计数器,如果只使用cnt_clk作为状态跳转的约束条件不充分,
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt_clk <= 5'd0;
else if(state != IDLE)
cnt_clk <= cnt_clk + 1'b1;
end
//由于状态跳转的约束条件不充分,所以使用cnt_byte 计数器对cnt_clk的计数周期进行计数,同时使用cnt_byte增加一次表示延时640ns
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt_byte <= 3'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == 3'd6))//当写完擦除指令后,等待640ns,片选拉高,此时cnt_byte = 6,并且刚好cnt_clk进行一个计数循环,就清零
cnt_byte <= 3'd0;
else if(cnt_clk == 5'd31)//cnt_clk每完成一个循环周期cnt_byte就加1,其他不变
cnt_byte <= cnt_byte + 1'b1;
end
//在进行写使能指令以及全擦除指令,并且已经发送一个字节时,记录系统时钟的个数
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt_sck <= 2'd0;
else if((state == WR_EN) && (cnt_byte == 1'b1)) // 当进行写使能时,先等待640ns,即cnt_byte =1
cnt_sck <= cnt_sck + 1'b1;
else if((state == CE) && (cnt_byte == 3'd5)) //当写完使能信号,等待640ns,再片选拉高,继续等待640ns,再片选拉低,继续等待640ns,此时cnt_byte =5,此时又开始写擦除指令
cnt_sck <= cnt_sck + 1'b1;
end
//进行四分频,产生12.5MHz时钟.根据cnt_sck 记录系统时钟的个数,2个cnt_sck为低,2个cnt_sck为高
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
spi_clk <= 1'b0;
else if(cnt_sck == 2'd0)
spi_clk <= 1'b0;
else if(cnt_sck == 2'd2)
spi_clk <= 1'b1;
end
//片选信号输出
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
spi_cs_n <= 1;
else if(key == 1'b1)
spi_cs_n <= 0;
else if((cnt_byte == 3'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
spi_cs_n <= 1;
else if((cnt_byte == 3'd3) && (cnt_clk == 5'd31) && (state == DELAY))
spi_cs_n <= 0;
else if((cnt_byte == 3'd6) && (cnt_clk == 5'd31) && (state == CE))
spi_cs_n <= 1;
end
//cnt_bit 高低位对调,控制MOSI输出。用于输出MOSI的哪一位
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
cnt_bit <= 3'd0;
else if(cnt_clk == 2'd2)
cnt_bit <= cnt_bit + 1'b1;
end
//state 状态机跳转
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
state <= IDLE;
else
case(state)
IDLE:if(key == 1'b1)
state <= WR_EN;
WR_EN:if((cnt_byte == 3'd2) && (cnt_clk == 5'd31))
state <= DELAY;
DELAY:if((cnt_byte == 3'd3) && (cnt_clk == 5'd31))
state <= CE;
CE:if((cnt_byte == 3'd6) && (cnt_clk == 5'd31))
state <= IDLE;
default:
state <= IDLE;
endcase
end
//MOSI输出
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
spi_mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte == 3'd2))
spi_mosi <= 1'b0;
else if((state == CE) && (cnt_byte == 3'd6))
spi_mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte == 3'd1) && (cnt_sck == 5'd0))
spi_mosi <= WR_EN_INST[7-cnt_bit];
else if((state == CE) && (cnt_byte == 3'd5) && (cnt_sck == 5'd0))
spi_mosi <= CE_INST[7-cnt_bit];
end
endmodule
3)和Modelsim联合仿真。(联合仿真视频可以参考B站高云半导体视频教程高云半导体FPGA课程 - 软件篇_哔哩哔哩_bilibili
创建工程文件,把相关文件添加工程,取消优化。这个仿真使用了M25P16仿真模块进行仿真。仿真波形如图:
写使能
擦除指令
擦除完成
4)在线逻辑仪波形
a、设置引脚时,使用的是MSPI引脚,所以注意在Process页面右键选中configraion,如图,在Dual-purpose 中选择MSPI 作为常规引脚。否则编译时就会出现如图示错误。
编译错误
b、建立GAO文件;详细步骤参考论坛大佬的教程。链接如下:
【国产FPGA高云GW1N-4系列开发板测评】——4、内嵌逻辑分析仪的使用 - 国产芯片交流 - 电子工程世界-论坛 (eeworld.com.cn)
c、在线逻辑分析页面,在这个自己犯了一个打错,就是提前把跳线帽跳到JTAG模式,而此时还是使用USB下载,结果出现下载不了程序,并且提示code 不匹配。实际上GWIN1-4B MINI开发板已经有JTAG下载,就是使用FT232H芯片来支持JTAG下载模式。这个错误让自己停滞了几天。感谢论坛大佬@gs001588 提供了帮助。多谢大佬。
d、在线逻辑波形由于没有截图就不上传了
2、页写代码编写
1)页写波形图绘制,参考时序绘制
2)根据波形图进行代码编写
module flash_pp_ctrl(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire key , //按键输入信号
output reg cs_n , //片选信号
output reg sck , //串行时钟
output reg mosi //主输出从输入数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter IDLE = 4'b0001 , //初始状态
WR_EN = 4'b0010 , //写状态
DELAY = 4'b0100 , //等待状态
PP = 4'b1000 ; //页写状态
parameter WR_EN_INST = 8'b0000_0110, //写使能指令
PP_INST = 8'b0000_0010; //页写指令
parameter SECTOR_ADDR = 8'b0000_0000, //扇区地址
PAGE_ADDR = 8'b0000_0100, //页地址
BYTE_ADDR = 8'b0010_0101; //字节地址
parameter NUM_DATA = 8'd100 ; //页写数据个数(0-99)
//reg define
reg [7:0] cnt_byte ; //字节计数器
reg [3:0] state ; //状态机状态
reg [4:0] cnt_clk ; //系统时钟计数器
reg [1:0] cnt_sck ; //串行时钟计数器
reg [2:0] cnt_bit ; //比特计数器
reg [7:0] data ; //页写入数据
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 5'd0;
else if(state != IDLE)
cnt_clk <= cnt_clk + 1'b1;
//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_byte <= 8'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == NUM_DATA + 8'd9))
cnt_byte <= 8'd0;
else if(cnt_clk == 5'd31)
cnt_byte <= cnt_byte + 1'b1;
//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sck <= 2'd0;
else if((state == WR_EN) && (cnt_byte == 8'd1))
cnt_sck <= cnt_sck + 1'b1;
else if((state == PP) && (cnt_byte >= 8'd5)
&& (cnt_byte <= NUM_DATA + 8'd9 - 1'b1))
cnt_sck <= cnt_sck + 1'b1;
//cs_n:片选信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cs_n <= 1'b1;
else if(key == 1'b1)
cs_n <= 1'b0;
else if((cnt_byte == 8'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
cs_n <= 1'b1;
else if((cnt_byte == 8'd3) && (cnt_clk == 5'd31) && (state == DELAY))
cs_n <= 1'b0;
else if((cnt_byte == NUM_DATA + 8'd9) && (cnt_clk == 5'd31) && (state == PP))
cs_n <= 1'b1;
//sck:输出串行时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sck <= 1'b0;
else if(cnt_sck == 2'd0)
sck <= 1'b0;
else if(cnt_sck == 2'd2)
sck <= 1'b1;
//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 3'd0;
else if(cnt_sck == 2'd2)
cnt_bit <= cnt_bit + 1'b1;
//data:页写入数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 8'd0;
else if((cnt_clk == 5'd31) && ((cnt_byte >= 8'd9)
&& (cnt_byte < NUM_DATA + 8'd9 - 1'b1)))
data <= data + 1'b1;
//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE: if(key == 1'b1)
state <= WR_EN;
WR_EN: if((cnt_byte == 8'd2) && (cnt_clk == 5'd31))
state <= DELAY;
DELAY: if((cnt_byte == 8'd3) && (cnt_clk == 5'd31))
state <= PP;
PP: if((cnt_byte == NUM_DATA + 8'd9) && (cnt_clk == 5'd31))
state <= IDLE;
default: state <= IDLE;
endcase
//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte== 8'd2))
mosi <= 1'b0;
else if((state == PP) && (cnt_byte == NUM_DATA + 8'd9))
mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte == 8'd1) && (cnt_sck == 5'd0))
mosi <= WR_EN_INST[7 - cnt_bit]; //写使能指令
else if((state == PP) && (cnt_byte == 8'd5) && (cnt_sck == 5'd0))
mosi <= PP_INST[7 - cnt_bit]; //页写指令
else if((state == PP) && (cnt_byte == 8'd6) && (cnt_sck == 5'd0))
mosi <= SECTOR_ADDR[7 - cnt_bit]; //扇区地址
else if((state == PP) && (cnt_byte == 8'd7) && (cnt_sck == 5'd0))
mosi <= PAGE_ADDR[7 - cnt_bit]; //页地址
else if((state == PP) && (cnt_byte == 8'd8) && (cnt_sck == 5'd0))
mosi <= BYTE_ADDR[7 - cnt_bit]; //字节地址
else if((state == PP) && ((cnt_byte >= 8'd9)
&& (cnt_byte <= NUM_DATA + 8'd9 - 1'b1)) && (cnt_sck == 5'd0))
mosi <= data[7 - cnt_bit]; //页写入数据
endmodule
3)在线逻辑图波形
写使能
写指令
写地址
|