写在前面:
FPGA实现通过SPI协议读写FLASH系列相关文章:
SPI通信协议
【FPGA】FPGA实现SPI协议读写FLASH(一)----- M25P16操作概述
【FPGA】FPGA实现SPI协议读写FLASH(二)----- SPI接口驱动模块设计
上篇文章介绍了SPI接口驱动模块的实现,本文将对SPI读写控制模块设计进行详细介绍;本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),FLASH型号:M25P16。
一、功能分析
本项目中SPI读写控制模块(flash_control)包含两个模块:flash写操作模块(flash_write)和flash读操作模块(flash_read),主要实现如下功能:
- 接收PC端通过串口传输的数据,用FIFO缓存。
- 接收从SPI接口驱动模块读会的数据,用FIFO缓存,并通过串口发送至PC端显示。
- 控制SPI接口驱动模块进行读写操作以及数据传输顺序。
- 实现8字节数据页编程操作。
二、模块划分
SPI读写控制模块(flash_control)包含flash写操作模块(flash_write)和flash读操作模块(flash_read),模块划分如下:
三、状态机设计
由于写操作过程有点复杂,如果用一个状态机实现,不利于代码阅读以及维护,因此写操作采用主从状态机方式实现。
1.flash_write模块
主状态机状态转移图:
主状态机状态说明:
IDLE:空闲状态,等待写请求。
WREN:写使能状态,发送写使能指令。
SE:擦除状态,发送擦除指令及地址。
RDSR:读状态寄存器状态,发送读状态寄存器指令,并判断返回的数据。
PP:页编程状态,发送页编程指令、地址和数据。
从状态机状态转移图:
从状态机状态说明:
IDLE:空闲状态,等待请求。
CMD:发送指令字节状态。
ADDR:发送地址字节状态。
DATA:发送数据字节状态。
DELAY:延时状态。
2.flash_read模块
状态转移图:
状态说明:
IDLE:空闲状态,等待读请求。
RDID_CMD:发送读ID指令。
RDID_DATA:接收读回的ID数据。
RDDA_CMD:发送读数据指令。
RDDA_ADDR:发送读数据地址字节。
RDDA_DATA:接收读回的数据。
RD_DONE:读ID或读数据完成。
四、信号说明
1、flash_control模块信号说明
Port | I/O Type | Description |
---|---|---|
rdid_req | input | 读ID请求 |
rdda_req | input | 读数据请求 |
wr_req | input | 写数据请求 |
rx_data[7:0] | input | 串口接收要写入FLASH的数据 |
rx_data_vld | input | 串口接收数据有效标志 |
ready | input | uart_tx模块可以传输数据标志 |
tx_data[7:0] | output | 串口发送从FLASH读取的数据 |
tx_data_vld | output | 串口发送数据有效标志 |
rdin | input | SPI接口模块读回的数据 |
rw_done | input | 读写一字节数据完成标志 |
dout | output | 发送给SPI接口模块要写的数据 |
req | output | 读写请求 |
display_id[23:0] | output | 数码管显示读ID数据 |
display_id_vld | output | 显示数据有效标志 |
modle[2:0] | output | 读写模式 001:读ID 010:读数据 100:写数据 |
2、flash_write模块信号说明
Port | I/O Type | Description |
---|---|---|
wr_req] | input | 写数据请求 |
wr_data[7:0] | input | 要写入的数据 |
rw_done | input | SPI接口模块读写一字节完成标志 |
rdsr_data[7:0] | input | SPI接口模块读回的状态寄存器数据 |
dout[7:0] | output | 输出给SPI接口模块要发送的数据 |
req | output | 写请求 |
getdata_flag | output | 取数据标志,输出给flash_control模块,标志拉高时从fifo中取要写入flash的数据 |
3、flash_read模块信号说明
Port | I/O Type | Description |
---|---|---|
rdid_req | input | 读ID请求 |
rdda_req | input | 读数据请求 |
rw_done | input | SPI接口模块读写一字节完成标志 |
rdin[7:0] | input | SPI接口模块读回的数据 |
dout[7:0] | output | 输出给SPI接口模块要发送的数据 |
req | output | 读写请求 |
rddata[23:0] | output | 数码管显示ID |
rddata_vld | output | 显示数据有效标志 |
五、代码实现
1、flash_control模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: spi_flash
// Module Name: flash_control
// Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)
// Tool versions: Quartus Prime 18.1
// Description: FLASH读写控制模块
// **************************************************************
`include "param.v"
module flash_control (
input clk ,
input rst_n ,
// key_filter
input rdid_req , // 读ID请求
input rdda_req , // 读数据请求
input wr_req , // 写数据请求
// uart_rx
input [7:0] rx_data , // 串口接收要写入FLASH的数据
input rx_data_vld ,
// uart_tx
input ready , // ready
output [7:0] tx_data , // 串口发送从FLASH读取的数据
output tx_data_vld ,
// spi_interface
input [7:0] rdin , // SPI接口模块读回的数据
input rw_done , // 读写一字节数据完成标志
output [7:0] dout , // 发送给SPI接口模块要写的数据
output req , // 读写请求
// seg_driver
output [23:0] display_id , // 数码管显示读ID
output display_id_vld ,
output [2:0] modle // 读写模式 001:读ID 010:读数据 100:写数据
);
// 信号定义
// u_flash_read
wire [23:0] rddata ;
wire rddata_vld ;
reg [23:0] read_data ; // 从SPI接口读回的数据寄存
reg read_data_vld ;
reg [2:0] rw_flag ;
reg [7:0] rd_data ; // 读回的8bit数据寄存
reg rd_data_vld ;
reg [7:0] write_data ; // fifo读出的将要写入FLASH的数据
wire read_done ;
wire write_done ;
wire read_req ;
wire write_req ;
wire [7:0] read_rdin ;
wire [7:0] write_rdin ;
wire [7:0] read_dout ;
wire [7:0] write_dout ;
wire getdata_flag ;
// wrfifo
wire [7:0] wrfifo_datain ;
wire wrfifo_rdreq ;
wire wrfifo_wrreq ;
wire wrfifo_empty ;
wire wrfifo_full ;
wire [7:0] wrfifo_q ;
wire [2:0] wrfifo_usedw ;
// rdfifo
wire [7:0] rdfifo_datain ;
wire rdfifo_rdreq ;
wire rdfifo_wrreq ;
wire rdfifo_empty ;
wire rdfifo_full ;
wire [7:0] rdfifo_q ;
wire [2:0] rdfifo_usedw ;
// 防止信号冲突
assign read_done = (rw_flag[0] | rw_flag[1]) ? rw_done : 1'b0 ;
assign write_done = rw_flag[2] ? rw_done : 1'b0 ;
assign req = (rw_flag[0] | rw_flag[1]) ? read_req : write_req ;
assign read_rdin = (rw_flag[0] | rw_flag[1]) ? rdin : 0 ;
assign write_rdin = rw_flag[2] ? rdin : 0 ;
assign dout = (rw_flag[0] | rw_flag[1]) ? read_dout : write_dout ;
// 模块例化
flash_read u_flash_read (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*// key_filter*/
/*input */.rdid_req (rdid_req ), // 读ID请求
/*input */.rdda_req (rdda_req ), // 读数据请求
/*// spi_interface*/
/*input */.rw_done (read_done ), // SPI接口模块读写一字节完成标志
/*input [7:0] */.rdin (read_rdin ), // SPI接口模块读回的数据
/*output [7:0] */.dout (read_dout ), // 输出给SPI接口模块要发送的数据
/*output */.req (read_req ), // 读写请求
/*output [23:0] */.rddata (rddata ), // 数码管显示ID数据
/*output */.rddata_vld (rddata_vld ) // 数据有效标志
);
flash_write u_flash_write (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*// key_filter*/
/*input */.wr_req (wr_req ), // 写数据请求
/*input [7:0] */.wr_data (write_data), // 写数据
/*//spi_interface*/
/*input */.rw_done (write_done), // SPI接口模块读写一字节完成标志
/*input [7:0] */.rdsr_data (write_rdin), // SPI接口模块读回的状态寄存器数据
/*output [7:0] */.dout (write_dout), // 输出给SPI接口模块要发送的数据
/*output */.req (write_req ), // 写请求
/*// flash_control*/
/*output */.getdata_flag (getdata_flag)
);
// read_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
read_data <= 0 ;
read_data_vld <= 0 ;
end
else if(rw_flag == 3'b001 && rddata_vld)begin
read_data <= rddata ;
read_data_vld <= rddata_vld ;
end
else if(rw_flag == 3'b010 && rddata_vld)begin
read_data[7:0] <= rddata ;
read_data_vld <= rddata_vld ;
end
else begin
read_data <= 0 ;
read_data_vld <= 0 ;
end
end
// rw_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rw_flag <= 0 ;
end
else if(rdid_req)begin
rw_flag <= 3'b001 ;
end
else if(rdda_req)begin
rw_flag <= 3'b010 ;
end
else if(wr_req)begin
rw_flag <= 3'b100 ;
end
end
// rd_data rd_data_vld
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_data <= 0 ;
rd_data_vld <= 0 ;
end
else if(rw_flag[1])begin
rd_data <= read_data[7:0] ;
rd_data_vld <= read_data_vld ;
end
end
// write_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
write_data <= 0 ;
end
else if(wrfifo_empty)begin
write_data <= 0 ;
end
else begin
write_data <= wrfifo_q ;
end
end
// wrfifo
wrfifo wrfifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( wrfifo_datain ),
.rdreq ( wrfifo_rdreq ),
.wrreq ( wrfifo_wrreq ),
.empty ( wrfifo_empty ),
.full ( wrfifo_full ),
.q ( wrfifo_q ),
.usedw ( wrfifo_usedw )
);
assign wrfifo_datain = rx_data ;
assign wrfifo_wrreq = rx_data_vld && ~wrfifo_full ;
assign wrfifo_rdreq = getdata_flag && write_done && ~wrfifo_empty ; // getdata_flag取数据标志拉高时,写完一字节数据再从FIFO中读取下一字节数据
// rdfifo
rdfifo rdfifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( rdfifo_datain ),
.rdreq ( rdfifo_rdreq ),
.wrreq ( rdfifo_wrreq ),
.empty ( rdfifo_empty ),
.full ( rdfifo_full ),
.q ( rdfifo_q ),
.usedw ( rdfifo_usedw )
);
assign rdfifo_datain = rd_data ;
assign rdfifo_wrreq = rd_data_vld ;
assign rdfifo_rdreq = ~rdfifo_empty && ready ;
// 输出
assign display_id = (rw_flag[0] && read_data_vld) ? read_data : 0 ;
assign display_id_vld = read_data_vld ;
assign modle = rw_flag ;
assign tx_data = rdfifo_q ;
assign tx_data_vld = rdfifo_rdreq ;
endmodule
2、flash_write模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: spi_flash
// Module Name: flash_write
// Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)
// Tool versions: Quartus Prime 18.1
// Description: SPI写FLASH模块
// **************************************************************
`include "param.v"
module flash_write (
input clk ,
input rst_n ,
// key_filter
input wr_req , // 写数据请求
input [7:0] wr_data , // 写数据
// flash_control
input rw_done , // SPI接口模块读写一字节完成标志
input [7:0] rdsr_data , // SPI接口模块读回的状态寄存器数据
output [7:0] dout , // 输出给SPI接口模块要发送的数据
output req , // 写请求
output getdata_flag // 取数据标志,输出给flash_control模块,标志拉高时从fifo中取要写入flash的数据
);
// 参数定义
// 主状态机状态
localparam M_IDLE = 5'b00001 , // 空闲状态
M_WREN = 5'b00010 , // 写使能
M_SE = 5'b00100 , // 擦除
M_RDSR = 5'b01000 , // 读状态寄存器
M_PP = 5'b10000 ; // 页编程
// 从状态机状态
localparam S_IDLE = 5'b00001 , // 空闲状态
S_CMD = 5'b00010 , // 发送指令
S_ADDR = 5'b00100 , // 发送地址
S_DATA = 5'b01000 , // 发送/接收数据
S_DELAY = 5'b10000 ; // 延时
// 信号定义
reg [4:0] m_state_c ;
reg [4:0] m_state_n ;
reg [4:0] s_state_c ;
reg [4:0] s_state_n ;
reg [3:0] byte_num ; // 字节数
reg [3:0] cnt_byte ; // 字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [27:0] delay ; // 延时
reg [27:0] cnt_delay ; // 延时计数器
wire add_cnt_delay ;
wire end_cnt_delay ;
reg [7:0] wr_data_r ; // 写数据寄存
reg [7:0] command ; // 指令寄存
reg wip ; // 设置为1时,表示设备处于工作状态,当设置为0时,表示设备处于空闲状态
reg wel ; // 当设置为1时,内部写使能被设置,当设置为0时,表示没有接收写状态寄存器,页编程或擦除指令
reg [7:0] dout_data ; // 输出数据寄存
reg wr_flag ; // 输出给SPI接口模块请求信号寄存
reg [1:0] wren_flag ; // 写使能标志,区分m_rdsr2m_idle和m_rdsr2m_wren
reg data_flag ; // 从flash_control模块取数据标志寄存
// 主状态机状态转移条件
wire m_idle2m_wren ;
wire m_wren2m_se ;
wire m_wren2m_pp ;
wire m_se2m_rdsr ;
wire m_rdsr2m_pp ;
wire m_rdsr2m_idle ;
wire m_rdsr2m_wren ;
wire m_pp2m_rdsr ;
// 从状态机状态转移条件
wire s_idle2s_cmd ;
wire s_cmds_addr ;
wire s_cmd2s_data ;
wire s_cmd2s_delay ;
wire s_addr2s_data ;
wire s_addr2s_delay ;
wire s_data2s_delay ;
wire s_delay2s_idle ;
/************************主状态机开始****************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
m_state_c <= M_IDLE ;
end
else begin
m_state_c <= m_state_n ;
end
end
always @(*)begin
case (m_state_c)
M_IDLE: begin
if(m_idle2m_wren)
m_state_n = M_WREN ;
else
m_state_n = m_state_c ;
end
M_WREN: begin
if(m_wren2m_se)
m_state_n = M_SE ;
else if(m_wren2m_pp)
m_state_n = M_PP ;
else
m_state_n = m_state_c ;
end
M_SE: begin
if(m_se2m_rdsr)
m_state_n = M_RDSR ;
else
m_state_n = m_state_c ;
end
M_RDSR: begin
if(m_rdsr2m_pp)
m_state_n = M_PP ;
else if(m_rdsr2m_idle)
m_state_n = M_IDLE ;
else if(m_rdsr2m_wren)
m_state_n = M_WREN ;
else
m_state_n = m_state_c ;
end
M_PP: begin
if(m_pp2m_rdsr)
m_state_n = M_RDSR ;
else
m_state_n = m_state_c ;
end
default: m_state_n = M_IDLE ;
endcase
end
// 状态转移条件
assign m_idle2m_wren = m_state_c == M_IDLE && wr_req ; // 收到写请求,进入写使能状态
assign m_wren2m_se = m_state_c == M_WREN && s_delay2s_idle && ~wren_flag[0] ; // 写使能延时结束,进入擦除状态
assign m_wren2m_pp = m_state_c == M_WREN && s_delay2s_idle && wren_flag[0] ; // 写使能延时结束,进入页编程状态
assign m_se2m_rdsr = m_state_c == M_SE && s_delay2s_idle ; // 擦除延时结束,进入读状态寄存器状态
assign m_rdsr2m_pp = m_state_c == M_RDSR && s_delay2s_idle && wip == 1'b0 && wel == 1'b1 ; // 读状态寄存器wel位为高电平,直接跳转到页编程状态
assign m_rdsr2m_idle = m_state_c == M_RDSR && s_delay2s_idle && wel == 1'b0 && wip == 1'b0 && wren_flag[1] ; // 一次写操作结束,回到空闲状态
assign m_rdsr2m_wren = m_state_c == M_RDSR && s_delay2s_idle && wel == 1'b0 && wip == 1'b0 && wren_flag[0] ; // wel为低电平,进入写使能状态
assign m_pp2m_rdsr = m_state_c == M_PP && s_delay2s_idle ; // 页编程延时结束,进入读状态寄存器状态
/************************主状态机结束****************************/
/************************从状态机开始****************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
s_state_c <= S_IDLE ;
end
else begin
s_state_c <= s_state_n ;
end
end
always @(*)begin
case (s_state_c)
S_IDLE: begin
if(s_idle2s_cmd)
s_state_n = S_CMD ;
else
s_state_n = s_state_c ;
end
S_CMD: begin
if(s_cmds_addr)
s_state_n = S_ADDR ;
else if(s_cmd2s_data)
s_state_n = S_DATA ;
else if(s_cmd2s_delay)
s_state_n = S_DELAY ;
else
s_state_n = s_state_c ;
end
S_ADDR: begin
if(s_addr2s_data)
s_state_n = S_DATA ;
else if(s_addr2s_delay)
s_state_n = S_DELAY ;
else
s_state_n = s_state_c ;
end
S_DATA: begin
if(s_data2s_delay)
s_state_n = S_DELAY ;
else
s_state_n = s_state_c ;
end
S_DELAY: begin
if(s_delay2s_idle)
s_state_n = S_IDLE ;
else
s_state_n = s_state_c ;
end
default: s_state_n = S_IDLE ;
endcase
end
assign s_idle2s_cmd = s_state_c == S_IDLE && m_state_c != M_IDLE ; // 主状态机非空闲状态下,从状态机进入发送指令状态
assign s_cmds_addr = s_state_c == S_CMD && (m_state_c == M_SE || m_state_c == M_PP) && cnt_byte == 1 ; // 主状态机擦除和页编程状态下,指令发送完成,进入发送地址状态
assign s_cmd2s_data = s_state_c == S_CMD && m_state_c == M_RDSR && cnt_byte == 1 ; // 主状态机读状态寄存器状态下,指令发送完成,进入接收状态寄存器数据状态
assign s_cmd2s_delay = s_state_c == S_CMD && m_state_c == M_WREN && end_cnt_byte ; // 主状态机写使能指令发送完成,进入延时状态
assign s_addr2s_data = s_state_c == S_ADDR && m_state_c == M_PP && cnt_byte == 4 ; // 主状态机页编程状态下,地址发送完成,进入发送数据状态
assign s_addr2s_delay = s_state_c == S_ADDR && m_state_c == M_SE && end_cnt_byte ; // 主状态机擦除状态下,地址发送完成,进入延时状态
assign s_data2s_delay = s_state_c == S_DATA && (m_state_c == M_PP || m_state_c == M_RDSR) && end_cnt_byte ; // 主状态机页编程状态下,数据发送完成,进入延时状态
assign s_delay2s_idle = s_state_c == S_DELAY && end_cnt_delay ; // 延时结束,回到空闲状态
/************************从状态机结束****************************/
// wr_data_r
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_data_r <= 0 ;
end
else if(data_flag)begin // 取数据标志拉高,寄存要写入flash的数据
wr_data_r <= wr_data ;
end
else if(m_rdsr2m_idle)begin
wr_data_r <= 0 ;
end
end
// command
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
command <= 0 ;
end
else if(m_idle2m_wren | m_rdsr2m_wren)begin
command <= `CMD_WREN ; // 写使能指令
end
else if(m_wren2m_se)begin
command <= `CMD_SE ; // 擦除指令
end
else if(m_se2m_rdsr | m_pp2m_rdsr)begin
command <= `CMD_RDSR ; // 读状态寄存器指令
end
else if(m_wren2m_pp | m_rdsr2m_pp)begin
command <= `CMD_PP ; // 页编程指令
end
end
// byte_num
always @(*)begin
case (m_state_c)
M_WREN: byte_num = 1 ;
M_SE : byte_num = 4 ;
M_RDSR: byte_num = 2 ;
M_PP : byte_num = 12 ; // 页编程8byte数据 1bytePP指令字节+3byte地址字节+8byte数据字节
default: byte_num = 0 ;
endcase
end
// cnt_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0 ;
end
else begin
cnt_byte <= cnt_byte + 1 ;
end
end
end
assign add_cnt_byte = rw_done ; // 读写一字节数据完成,字节计数器+1
assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1) ;
// delay
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
delay <= 0 ;
end
else if(m_wren2m_se)begin
delay <= `DELAY_SE ; // 擦除延时
end
else if(m_wren2m_pp | m_rdsr2m_pp)begin
delay <= `DELAY_PP ; // 页编程延时
end
else if(m_idle2m_wren | m_se2m_rdsr | m_rdsr2m_wren | m_pp2m_rdsr)begin
delay <= `DELAY_CSH ; // 指令间延时
end
else if(m_rdsr2m_idle)begin
delay <= 0 ;
end
end
// cnt_delay
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 0 ;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 0 ;
end
else begin
cnt_delay <= cnt_delay + 1 ;
end
end
end
assign add_cnt_delay = s_state_c == S_DELAY ;
assign end_cnt_delay = add_cnt_delay && (cnt_delay == delay - 1) ;
// wip,wel
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wip <= 0 ;
wel <= 0 ;
end
else begin
wip <= rdsr_data[0] ; // 设置为1时,表示设备处于工作状态,当设置为0时,表示设备处于空闲状态
wel <= rdsr_data[1] ; // 当设置为1时,内部写使能被设置,当设置为0时,表示没有接收写状态寄存器,页编程或擦除指令
end
end
// dout_data
always @(*)begin
if(m_state_c == M_WREN)begin // 发送写使能指令
dout_data = `CMD_WREN ;
end
else if(m_state_c == M_SE && s_state_c != S_DELAY)begin // 发送擦除指令和地址
case (cnt_byte)
0: dout_data = `CMD_SE ;
1: dout_data = `ADDR_HIGH ;
2: dout_data = `ADDR_MID ;
3: dout_data = `ADDR_LOW ;
default: dout_data = 0 ;
endcase
end
else if(m_state_c == M_RDSR)begin // 发送读状态寄存器指令
dout_data = `CMD_RDSR ;
end
else if(m_state_c == M_PP&& s_state_c != S_DELAY)begin // 发送页编程指令、地址、数据
case (cnt_byte)
0: dout_data = `CMD_PP ;
1: dout_data = `ADDR_HIGH ;
2: dout_data = `ADDR_MID ;
3: dout_data = `ADDR_LOW ;
// 4: dout_data = wr_data_r ; // 单字节写操作
default: dout_data = wr_data_r ;// dout_data = 0 ;
endcase
end
else begin
dout_data = 0 ;
end
end
// wr_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_flag <= 1'b0 ;
end
else if(s_state_c == S_CMD || s_state_c == S_ADDR || s_state_c == S_DATA)begin
wr_flag <= 1'b1 ;
end
else begin
wr_flag <= 1'b0 ;
end
end
// wren_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wren_flag <= 2'b00 ;
end
else if(m_wren2m_se)begin
wren_flag <= 2'b01 ;
end
else if(m_pp2m_rdsr)begin
wren_flag <= 2'b10 ;
end
end
// data_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_flag <= 1'b0 ;
end
else if(m_state_c == M_PP && cnt_byte > 3)begin // 页编程状态下,发送完指令和地址字节后,取数据标志拉高
data_flag <= 1'b1 ;
end
else begin
data_flag <= 1'b0 ;
end
end
// 输出
assign dout = dout_data ;
assign req = wr_flag ;
assign getdata_flag = data_flag ;
endmodule
3、flash_read模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: spi_flash
// Module Name: flash_read
// Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)
// Tool versions: Quartus Prime 18.1
// Description: SPI读FLASH模块
// **************************************************************
`include "param.v"
module flash_read (
input clk ,
input rst_n ,
// key_filter
input rdid_req , // 读ID请求
input rdda_req , // 读数据请求
// flash_control
input rw_done , // SPI接口模块读写一字节完成标志
input [7:0] rdin , // SPI接口模块读回的数据
output [7:0] dout , // 输出给SPI接口模块要发送的数据
output req , // 读写请求
output [23:0] rddata , // 数码管显示ID
output rddata_vld // 数据有效标志
);
// 参数定义
// 读数据状态机参数定义
parameter IDLE = 7'b000_0001 ,
RDID_CMD = 7'b000_0010 , // 发送读ID命令
RDID_DATA = 7'b000_0100 , // 接收读回ID数据
RDDA_CMD = 7'b000_1000 , // 发送读数据命令
RDDA_ADDR = 7'b001_0000 , // 发送读数据地址
RDDA_DATA = 7'b010_0000 , // 接收读回数据
RD_DONE = 7'b100_0000 ;
// 信号定义
reg [6:0] state_c ;
reg [6:0] state_n ;
reg [3:0] cnt_byte ; // 字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [3:0] byte_num ; // 字节数
reg [7:0] dout_data ; // 输出数据寄存
reg rw_req ; // 读写请求
reg rd_flag ; // 读数据模式 0:读ID 1:读数据
reg [23:0] rd_data ; // 读回的数据寄存
reg rd_data_vld ; // 读回的数据有效标志寄存
reg done ; // rw_done寄存
// 状态转移条件
wire idle2rdidcmd ;
wire idle2rddacmd ;
wire rdidcmd2rdiddata ;
wire rddacmd2rddaaddr ;
wire rddaaddr2rddadata ;
wire rdiddata2rddone ;
wire rddadata2rddone ;
wire rddone2idle ;
// 状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
always @(*)begin
case (state_c)
IDLE: begin
if(idle2rdidcmd)
state_n = RDID_CMD ;
else if(idle2rddacmd)
state_n = RDDA_CMD ;
else
state_n = state_c ;
end
RDID_CMD: begin
if(rdidcmd2rdiddata)
state_n = RDID_DATA ;
else
state_n = state_c ;
end
RDID_DATA: begin
if(rdiddata2rddone)
state_n = RD_DONE ;
else
state_n = state_c ;
end
RDDA_CMD: begin
if(rddacmd2rddaaddr)
state_n = RDDA_ADDR ;
else
state_n = state_c ;
end
RDDA_ADDR: begin
if(rddaaddr2rddadata)
state_n = RDDA_DATA ;
else
state_n = state_c ;
end
RDDA_DATA: begin
if(rddadata2rddone)
state_n = RD_DONE ;
else
state_n = state_c ;
end
RD_DONE: begin
if(rddone2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE ;
endcase
end
// 状态转移条件
assign idle2rdidcmd = state_c == IDLE && rdid_req ; // 读ID请求拉高,进入发送读ID指令状态
assign idle2rddacmd = state_c == IDLE && rdda_req ; // 读数据请求拉高,进入发送读数据指令状态
assign rdidcmd2rdiddata = state_c == RDID_CMD && end_cnt_byte ; // 读ID指令发送完成,进入接收ID数据状态
assign rddacmd2rddaaddr = state_c == RDDA_CMD && end_cnt_byte ; // 读数据指令发送完成,进入发送读地址状态
assign rddaaddr2rddadata = state_c == RDDA_ADDR && end_cnt_byte ; // 读地址发送完成,进入接收数据状态
assign rdiddata2rddone = state_c == RDID_DATA && end_cnt_byte ; // 读ID数据完成
assign rddadata2rddone = state_c == RDDA_DATA && end_cnt_byte ; // 读数据完成
assign rddone2idle = state_c == RD_DONE && (1'b1) ;
// rd_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_flag <= 1'b1 ;
end
else if(rdid_req)begin
rd_flag <= 1'b0 ;
end
else if(rdda_req | rddone2idle)begin
rd_flag <= 1'b1 ;
end
end
// byte_num
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
byte_num <= 0 ;
end
else if(rd_flag)begin // 读数据模式
case (state_c)
RDDA_CMD : byte_num <= 1 ;
RDDA_DATA : byte_num <= 8 ;
RDDA_ADDR : byte_num <= 3 ;
default: byte_num <= 0 ;
endcase
// if(state_c == RDDA_CMD)begin
// byte_num <= 1 ;
// end
// else if(state_c == RDDA_DATA)begin
// byte_num <= 8 ;
// end
// else if(state_c == RDDA_ADDR)begin
// byte_num <= 3 ;
// end
// else begin
// byte_num <= 0 ;
// end
end
else if(~rd_flag)begin // 读ID模式
case (state_c)
RDID_CMD : byte_num <= 1 ;
RDID_DATA : byte_num <= 3 ;
default: byte_num <= 0 ;
endcase
// if(state_c == RDID_CMD)begin
// byte_num <= 1 ;
// end
// else if(state_c == RDID_DATA)begin
// byte_num <= 3 ;
// end
// else begin
// byte_num <= 0 ;
// end
end
end
// cnt_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0 ;
end
else begin
cnt_byte <= cnt_byte + 1 ;
end
end
end
assign add_cnt_byte = done ; // 读写一字节数据完成,字节计数器+1
assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1) ;
// dout_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_data <= 0 ;
end
else if(state_c == RDID_CMD)begin // 输出读ID指令
dout_data <= `CMD_RDID ;
end
else if(state_c == RDDA_CMD)begin
dout_data <= `CMD_RDDA ; // 输出读数据指令
end
else if(state_c == RDDA_ADDR)begin // 输出地址字节
case (cnt_byte)
0: dout_data <= `ADDR_HIGH ;
1: dout_data <= `ADDR_MID ;
2: dout_data <= `ADDR_LOW ;
default: dout_data <= dout_data ;
endcase
end
else begin
dout_data <= 0 ;
end
end
// rw_req
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rw_req <= 1'b0 ;
end
else if(idle2rdidcmd | idle2rddacmd)begin
rw_req <= 1'b1 ;
end
else if(rdiddata2rddone | rddadata2rddone)begin
rw_req <= 1'b0 ;
end
end
// rd_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_data <= 0 ;
end
else if((state_c == RDID_DATA || state_c == RD_DONE) && done)begin // 接收ID数据
rd_data[23 - 8 * cnt_byte -:8] <= rdin ;
end
else if((state_c == RDDA_DATA || state_c == RD_DONE) && done)begin // 接收数据
rd_data <= rdin ;
end
else if(rddone2idle)begin
rd_data <= 0 ;
end
else begin
rd_data <= rd_data ;
end
end
// done
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
done <= 1'b0 ;
end
else if(rw_done)begin
done <= 1'b1 ;
end
else begin
done <= 1'b0 ;
end
end
// rd_data_vld
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_data_vld <= 1'b0 ;
end
else if(state_c == RDDA_DATA && done)begin
rd_data_vld <= 1'b1 ;
end
else begin
rd_data_vld <= 1'b0 ;
end
end
// 输出
assign dout = dout_data ;
assign req = rw_req ;
assign rddata = rd_data ;
assign rddata_vld = (state_c == RDDA_DATA) ? rd_data_vld : rddone2idle ;
endmodule
六、仿真测试
1、写数据仿真
2、读ID仿真
3、读数据仿真