10_SPI_Flash 连续写实验
- 1. 实验目标
- 2. 连续写方法
- 3. 操作时序
- 4. 流程框图
- 4.1 顶层模块
- 4.2 连续写模块
- 5. 波形图
- 6. RTL
- 6.1 flash_seq_wr_ctrl
- 6.2 spi_flash_seq_wr
- 7. Testbench
1. 实验目标
使用页写指令,将串口发送过来的连续不定量数据写入 Flash。本实验中,我们发送数据为 100 字节,串口波特率位 9600。
注意:在向 Flash 芯片写入数据之前,先要对芯片执行全擦除操作。
2. 连续写方法
3. 操作时序
和页写操作的操作时序一样。
4. 流程框图
4.1 顶层模块
4.2 连续写模块
5. 波形图
6. RTL
6.1 flash_seq_wr_ctrl
`timescale 1ns/1ns
module flash_seq_wr_ctrl(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire pi_flag , //数据标志信号
input wire [7:0] pi_data , //写入数据
output reg sck , //串行时钟
output reg cs_n , //片选信号
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 ADDR = 24'h00_04_25; //数据写入地址
//reg define
reg [23:0] addr_reg; //数据写入地址寄存器
reg [23:0] addr ; //数据写入地址
reg [4:0] cnt_clk ; //系统时钟计数器
reg [3:0] state ; //状态机状态
reg [3:0] cnt_byte; //字节计数器
reg [1:0] cnt_sck ; //串行时钟计数器
reg [2:0] cnt_bit ; //比特计数器
//********************************************************************//
//***************************** 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 <= 4'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == 4'd10))
cnt_byte <= 4'd0;
else if(cnt_clk == 31)
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 == 1'b1))
cnt_sck <= cnt_sck + 1'b1;
else if((state == PP) && (cnt_byte >= 4'd5) && (cnt_byte <= 4'd9))
cnt_sck <= cnt_sck + 1'b1;
//addr_reg:数据写入地址寄存器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr_reg <= ADDR;
else if(pi_flag == 1'b1)
addr_reg <= addr_reg + 1'b1 ;
//addr:数据写入地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr <= 24'd0;
else if(pi_flag == 1'b1)
addr <= addr_reg;
//cs_n:片选信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cs_n <= 1'b1;
else if(pi_flag == 1'b1)
cs_n <= 1'b0;
else if((cnt_byte == 4'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
cs_n <= 1'b1;
else if((cnt_byte == 4'd3) && (cnt_clk == 5'd31) && (state == DELAY))
cs_n <= 1'b0;
else if((cnt_byte == 4'd10) && (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;
//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE: if(pi_flag == 1'b1)
state <= WR_EN;
WR_EN: if((cnt_byte == 4'd2) && (cnt_clk == 5'd31))
state <= DELAY;
DELAY: if((cnt_byte == 4'd3) && (cnt_clk == 5'd31))
state <= PP;
PP: if((cnt_byte == 4'd10) && (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 == 4'd2))
mosi <= 1'b0;
else if((state == PP) && (cnt_byte == 4'd10))
mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte == 4'd1) && (cnt_sck == 5'd0))
mosi <= WR_EN_INST[7 - cnt_bit]; //写使能指令
else if((state == PP) && (cnt_byte == 4'd5) && (cnt_sck == 5'd0))
mosi <= PP_INST[7 - cnt_bit]; //扇区擦除指令
else if((state == PP) && (cnt_byte == 4'd6) && (cnt_sck == 5'd0))
mosi <= addr[23 - cnt_bit]; //扇区地址
else if((state == PP) && (cnt_byte == 4'd7) && (cnt_sck == 5'd0))
mosi <= addr[15 - cnt_bit]; //页地址
else if((state == PP) && (cnt_byte == 4'd8) && (cnt_sck == 5'd0))
mosi <= addr[7 - cnt_bit]; //字节地址
else if((state == PP) && (cnt_byte == 4'd9) && (cnt_sck == 5'd0))
mosi <= pi_data[7 - cnt_bit]; //写入数据
endmodule
6.2 spi_flash_seq_wr
`timescale 1ns/1ns
module spi_flash_seq_wr(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire rx , //串口接收数据
output wire cs_n , //片选信号
output wire sck , //串行时钟
output wire mosi , //主输出从输入数据
output wire tx //串口发送数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter UART_BPS = 14'd9600 , //比特率
CLK_FREQ = 26'd50_000_000 ; //时钟频率
//wire define
wire po_flag ;
wire [7:0] po_data ;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//-------------uart_rx_inst-------------
uart_rx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_rx_inst(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n), //全局复位
.rx (rx ), //串口接收数据
.po_data (po_data ), //串转并后的数据
.po_flag (po_flag ) //串转并后的数据有效标志信号
);
//-------------flash_seq_wr_ctrl_inst-------------
flash_seq_wr_ctrl flash_seq_wr_ctrl_inst(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.pi_flag (po_flag ), //数据标志信号
.pi_data (po_data ), //写入数据
.sck (sck ), //片选信号
.cs_n (cs_n ), //串行时钟
.mosi (mosi ) //主输出从输入数据
);
//-------------uart_tx_inst-------------
uart_tx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_tx_inst
(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n), //全局复位
.pi_data (po_data ), //并行数据
.pi_flag (po_flag ), //并行数据有效标志信号
.tx (tx ) //串口发送数据
);
endmodule
7. Testbench
`timescale 1ns/1ns
module tb_spi_flash_seq_wr();
//wire define
wire tx ;
wire cs_n;
wire sck ;
wire mosi;
wire miso;
//reg define
reg clk ;
reg rst_n ;
reg rx ;
reg [7:0] data_mem [299:0] ; //data_mem是一个存储器,相当于一个ram
//读取sim文件夹下面的data.txt文件,并把读出的数据定义为data_mem
initial
$readmemh("E:/base_code/10_spi_flash/spi_flash_write/spi_flash_seq_wr/sim/spi_flash.txt",data_mem);
//时钟、复位信号
initial
begin
clk = 1'b1 ;
rst_n <= 1'b0 ;
#200
rst_n <= 1'b1 ;
end
always #10 clk = ~clk;
initial
begin
rx <= 1'b1;
#200
rx_byte();
end
task rx_byte();
integer j;
for(j=0;j<300;j=j+1)
rx_bit(data_mem[j]);
endtask
task rx_bit(input[7:0] data); //data是data_mem[j]的值。
integer i;
for(i=0;i<10;i=i+1)
begin
case(i)
0: rx <= 1'b0 ; //起始位
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7]; //上面8个发送的是数据位
9: rx <= 1'b1 ; //停止位
endcase
#1040; //一个波特时间=sclk周期*波特计数器
end
endtask
//重定义defparam,用于修改参数,缩短仿真时间
defparam spi_flash_seq_wr_inst.uart_rx_inst.CLK_FREQ = 500000;
defparam spi_flash_seq_wr_inst.uart_tx_inst.CLK_FREQ = 500000;
defparam memory.mem_access.initfile = "initmemory.txt";
//-------------spi_flash_seq_wr_inst-------------
spi_flash_seq_wr spi_flash_seq_wr_inst(
.sys_clk (clk ), //input sys_clk
.sys_rst_n (rst_n ), //input sys_rst_n
.rx (rx ), //input rx
.cs_n (cs_n ), //output cs_n
.sck (sck ), //output sck
.mosi (mosi ), //output mosi
.tx (tx ) //output tx
);
m25p16 memory (
.c (sck ),
.data_in (mosi ),
.s (cs_n ),
.w (1'b1 ),
.hold (1'b1 ),
.data_out (miso )
);
endmodule