09_SPI-Flash 页写实验
- 1. 实验目标
- 2. 操作时序
- 3. 模块框图
- 3.1 顶层模块
- 3.2 页写模块
- 4. 波形图
- 5. RTL
- 5.1 flash_pp_ctrl
- 5.2 spi_flash_pp
- 6. Testbench
- 6.1 tb_flash_pp_ctrl
- 6.2 tb_spi_flash_pp
1. 实验目标
使用页写指令,向 Flash 中写入 N 字节数据,N 为整数,且大于 0 小于等于 256。在本 实 验 中 我 们 向 Flash 芯 片 中 写 入 0-99 , 共 100 字 节 数 据 , 数 据 初 始 地 址 为24’h00_04_25。
注意:在向 Flash 芯片写入数据之前,先要对芯片执行全擦除操作。
2. 操作时序
写满不支持跨页写,在这一页刚开始的地方写。
扇区地址 s 页地址 p 字节地址 b
3. 模块框图
3.1 顶层模块
3.2 页写模块
4. 波形图
5. RTL
5.1 flash_pp_ctrl
`timescale 1ns/1ns
module flash_pp_ctrl(input wire sys_clk , //系统时钟,频率50MHzinput 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;elsecase(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
5.2 spi_flash_pp
`timescale 1ns/1ns
module spi_flash_pp
(input wire sys_clk , //系统时钟,频率50MHzinput wire sys_rst_n , //复位信号,低电平有效input wire pi_key , //按键输入信号output wire cs_n , //片选信号output wire sck , //串行时钟output wire mosi //主输出从输入数据
);//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter CNT_MAX = 20'd999_999; //计数器计数最大值//wire define
wire po_key ;//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------- key_filter_inst -------------
key_filter
#(.CNT_MAX (CNT_MAX ) //计数器计数最大值
)
key_filter_inst
(.sys_clk (sys_clk ), //系统时钟,频率50MHz.sys_rst_n (sys_rst_n ), //复位信号,低电平有效.key_in (pi_key ), //按键输入信号.key_flag (po_key ) //消抖后信号
);//------------- flash_pp_ctrl_inst -------------
flash_pp_ctrl flash_pp_ctrl_inst
(.sys_clk (sys_clk ), //系统时钟,频率50MHz.sys_rst_n (sys_rst_n ), //复位信号,低电平有效.key (po_key ), //按键输入信号.sck (sck ), //片选信号.cs_n (cs_n ), //串行时钟.mosi (mosi ) //主输出从输入数据
);endmodule
6. Testbench
6.1 tb_flash_pp_ctrl
`timescale 1ns/1ns
module tb_flash_pp_ctrl();//wire define
wire cs_n;
wire sck ;
wire mosi;//reg define
reg sys_clk ;
reg sys_rst_n ;
reg key ;//时钟、复位信号、模拟按键信号
initialbeginsys_clk = 0;sys_rst_n <= 0;key <= 0;#100sys_rst_n <= 1;#1000key <= 1;#20key <= 0;endalways #10 sys_clk <= ~sys_clk;//写入Flash仿真模型初始值(全F)
defparam memory.mem_access.initfile = "initmemory.txt";//------------- flash_pp_ctrl_inst -------------
flash_pp_ctrl flash_pp_ctrl_inst
(.sys_clk (sys_clk ), //系统时钟,频率50MHz.sys_rst_n (sys_rst_n ), //复位信号,低电平有效.key (key ), //按键输入信号.sck (sck ), //串行时钟.cs_n (cs_n ), //片选信号.mosi (mosi ) //主输出从输入数据
);//------------- memory -------------
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
6.2 tb_spi_flash_pp
`timescale 1ns/1ns
module tb_spi_flash_pp();//wire define
wire cs_n;
wire sck ;
wire mosi;
wire [7:0] cmd_data;//reg define
reg clk ;
reg rst_n ;
reg key ;//时钟、复位信号、模拟按键信号
initialbeginclk = 0;rst_n <= 0;key <= 0;#100rst_n <= 1;#1000key <= 1;#20key <= 0;endinitialbegin$timeformat(-9, 0, "ns", 16);$monitor("@time %t: key=%b ", $time, key );$monitor("@time %t: cs_n=%b", $time, cs_n);$monitor("@time %t: cmd_data=%d", $time, cmd_data);endalways #10 clk <= ~clk;defparam memory.mem_access.initfile = "initmemory.txt";//-------------spi_flash_pp-------------
spi_flash_pp spi_flash_pp_inst
(.sys_clk (clk ), //input sys_clk.sys_rst_n (rst_n ), //input sys_rst.pi_key (key ), //input key.sck (sck ), //output sck.cs_n (cs_n ), //output cs_n.mosi (mosi ) //output mosi
);m25p16 memory
(.c (sck ), .data_in (mosi ), .s (cs_n ), .w (1'b1 ), .hold (1'b1 ), .data_out ( )
); endmodule