本项目所用开发板FPGA芯片型号为:EP4CE6F17C8
Flash芯片型号:M25P16
注意:虽然M25P64支持50mhz,但是这款FPGA芯片仅有50MHZ,为此进行了分频至12.5MHZ处理。
一、SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串行扩展接口,是一种同步、高速、全双工通信总线,所以可以在同一时间发送和接收数据,SPI是一种事实标准因此并没有定义速度限制,本次项目所用的Flash芯片支持的最高速率达到了50Mb/s(页编程,扇区擦除等操作)。
SPI 设备间的数据传输之所以又被称为数据交换,是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”。在每个 Clock 周期内,SPI 设备都会发送并接收一个 bit 大小的数据(不管主设备好还是从设备),相当于该设备有一个 bit 大小的数据被交换了。一个 Slave 设备要想能够接收到 Master 发过来的控制信号,必须在此之前能够被 Master 设备进行访问 。所以,Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。 在数据传输的过程中,每次接收到的数据必须在下一次数据传输之前被采样。如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致 SPI 物理模块最终失效。因此,在程序中一般都会在 SPI 传输完数据后,去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的(虽然发送后紧接着的读取是无意义的,但仍然需要从寄存器中读出来)。这一点后续会在代码的滤除无效数据以及发送指令后发送无效数据占用CS片选信号线中体现出来。
1.1 SPI引脚介绍
SPI协议共有四根线,分别为:C(时钟线)、D(主机输入,从机输出)、Q(主机输出、从机输入)、S'(片选信号)。
C(SCLK) : 主要的作用是 Master(主)设备往 Slave(从)设备传输时钟信号, 控制数据交换的时机以及速率。
S'(CS) : 用于 Master(主)设备片选 Slave (从)设备,使被选中的 Slave(从)设备能够被 Master(主)设备所访问。
D( MISO) : 在 Master(主)上面也被称为 Rx-Channel,作为数据的入口,主要用于SPI 设备接收数据。
Q(MOSI ): 在 Master(主)上面也被称为 Tx-Channel,作为数据的出口,主要用于 SPI 设备发送数据。
1.2 时钟极性和时钟相位
(1)时钟极性(CPOL):时钟极性通常写为CKP或CPOL。CPOL决定空闲时时钟为高还是低电平。
CPOL = 0:时钟空闲IDLE为低电平 0
CPOL = 1:时钟空闲IDLE为高电平1
(2)时钟相位(CPHA):时钟相位通常写为CKE或CPHA。时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿。
CPHA = 0:在时钟信号SCK的第一个跳变沿采样
CPHA = 1:在时钟信号SCK的第二个跳变沿采样
SPI一共有四种传输模式,如下:
①CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
②CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
③CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
④CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
二、M25P16简介和分析
2.1 flash介绍:
M25P16是一款带有先进写保护机制和高速SPI总线访问的串行Flash(闪存)存储器。M25P16特点如下:
①存储结构:16M Bit(2M Byte)的存储空间,一共32个扇区(sector),每个扇区256页,每页256字节。
②SPI总线兼容的串行接口。
③可以单扇区擦除,也可以整块擦除。
④可以同时编程1~256字节,页编程速率高达256Byte/1.4ms,即写入一页数据需要1.4ms。
⑤数据保存至少20年。
⑥支持SPI工作模式0和3
2.2 分析
由上图可以看出:
①在发出页编程指令前需要先发送写使能(WREN)指令。
②页编程只将1重置为0,因此页编程之前需要先发送一次扇区擦除指令,将存储数据全部置为1。
③同时在后续编程中,在发出扇区擦除指令前同样需要发送一次写使能指令。
④改芯片有一个状态寄存器,可以通过读取状态寄存器的WIP位获取芯片是否处于忙状态,WEL位获取芯片是否处于写锁存。通过读状态寄存器可以在后续页编程和扇区擦除时不必强行等待手册所要求的操作最大时间。
为简写程序,未用到读状态寄存器的命令,而是在SE(擦除)和PP(页编程)指令发送后强行等待了所规定的最大等待时间。状态寄存器格式如下图:
2.2.1操作流程如下图:
可以看出 页编程(PP)、扇区擦除(SE)、读(READ)指令均需要三个字节的地址数据(扇区地址+页地址+数据地址)。
2.2.2 重要的时间:
由上图可以看出M25P16芯片要求片选信号要求在数据到来之前提前拉低至少5ns,同时在数据传输结束后至少继续拉低100ns才能被拉高。
页编程指令发送后等待5ms;扇区擦除指令发送后等待3s。
2.2.3 时钟频率的选择:
由上图可以看出,除了读数据指令时钟最大速度为20MHz外其余指令均能达到50MHz,为了便于代码编写,本次项目在开发板50MHz的基础上进行了四分频,12.5MHz的时钟能够满足所用的所有指令。
2.3 项目所用指令时序
2.3.1 写使能(06h)----- Write Enable (WREN):1字节指令
在进行页编程、扇区擦除、整块擦除或写状态寄存器等操作之前,必须先激活一个写使能步骤。这个过程开始于片选信号的降低,标志着写使能指令的执行开始。一旦指令传输完成,片选信号会恢复到高电平状态,表明写使能过程以及后续的编程或擦除指令传输已经结束
2.3.2 读ID(9Fh) ---- Read Identification (RDID):1字节的指令、3字节的ID数据
读ID指令一共会返回三个字节的数据,第一个数据为厂商ID(如下图20h代表的是意法半导体的厂商ID),后两个数据可以理解为器件ID。发送1字节指令,接收3字节数。
2.3.3 读数据(03h) ---- Read Data Bytes (READ):1字节指令+3字节地址+1字节数据
首先,通过拉低片选信号来选中Flash芯片,接着发送一个数据读取(READ)的指令。紧接着,紧随指令之后,写入一个3字节的数据读取起始地址(范围从A23到A0)。这些指令和地址信息会在串行时钟的上升沿被Flash芯片所捕获并锁存。然后,在串行时钟的下降沿,与该存储地址相对应的存储单元中的数据将被依次通过串行数据总线输出。数据读取首地址可以为芯片中的任何一个有效地址,使用数据读(READ)指令可以对芯 片内数据连续读取,当首地址数据读取完成,会自动对首地址的下一个地址进行数据读取。
时序如下:
2.3.4 页写(02h)---- Page Program (PP):1字节指令+3字节地址+1字节数据
对同一页(256 byte)进行操作;
在发送一个数据字节到Flash芯片时,通常遵循两个关键指令步骤:首先是写使能指令(WREN),用于激活芯片的写入功能;紧接着是页编程指令(PP),它标志着实际数据写入过程的开始。在这两个指令之后,芯片会进入一个内部页编程周期,这个周期的时间长度为tPP,期间芯片会执行数据写入操作。
对于Flash芯片而言,每一页的最大存储容量是256字节。利用Page Program(PP)指令,用户可以一次性对最多256字节的数据进行编程,但前提是这些数据必须位于芯片同一内存页上的连续地址中。换句话说,页写操作允许我们一次性地向Flash芯片写入最多256字节的数据。重要的是要注意,如果尝试写入超过256字节的数据,那么多余的字节将会覆盖当前页内原有的数据,可能会导致数据丢失或损坏。时序如下:
2.3.5 擦除(D8h)---- Sector Erase (SE):1字节指令+3字节地址
在 Flash 芯片的操作流程中,如果要擦除某个被选中的扇区,使其所有存储单元的内容变为全1,那么在执行这个扇区擦除指令之前,必须先执行一个写使能(WREN)指令来授权写操作。只有在成功发送了写使能指令之后,才可以向 Flash 芯片发送扇区擦除指令。一旦扇区擦除指令被发送,Flash 芯片将立即进入擦除周期,这个周期的时间长度由特定的参数(如tSE或tBE)定义。简而言之,擦除扇区之前必须先通过写使能指令来解锁写操作权限。
三、SPI读写Flash程序
在 Flash 芯片的数据传输过程中,可以将其分为四个关键状态来描述:
①CS片选信号预拉低状态(DELAY1):在正式开始数据传输之前,CS片选信号需要提前拉低一段时间,这个时间段被称为DELAY1,其最小值为tSHSH(5ns),以确保芯片正确进入准备接收指令的状态。
②指令发送状态(DATA):在CS片选信号稳定拉低之后,Flash 芯片进入指令发送状态。在这个状态下,按照预定的序列向芯片发送特定的指令或数据。
③指令发送后片选信号延迟状态(DELAY2):当指令或数据发送完毕后,CS片选信号不会立即恢复高电平,而是会保持一段时间的低电平状态,这段时间被称为DELAY2。这一延迟是为了确保芯片有足够的时间处理完刚刚接收的指令或数据。
④片选信号恢复状态(HOLD):经过DELAY2的延迟后,CS片选信号需要按照手册规定的时间tSHSL(100ns)来恢复高电平状态。这个恢复过程被称为HOLD状态,标志着一次指令或数据传输周期的结束。
这四个状态共同构成了 Flash 芯片在数据传输过程中的完整流程,确保了数据传输的准确性和稳定性。
程序有以下部分:
3.1 控制模块
/**************************************************************
@File : flash_control.v
@Time : 2024年5月21日21:58:25
@Author :
@EditTool: VS Code
@Font : UTF-8
@Function:
**************************************************************/
module flash_control(
input clk ,
input rst_n ,
input rdid_req , //读ID请求
input read_req , //读数据
input [23:0] read_addr ,
input [8:0] read_num , //需要读出数据的个数 0~512
output [7:0] read_data , //返回数据或者ID
output read_data_vld ,
input write_req ,
input [23:0] write_addr ,
input [8:0] write_num , //写数据个数 0~256
input [7:0] write_data ,
input write_data_vld ,
output write_ready ,
//spi接口模块
output reg [7:0] tx_data ,
output tx_data_vld ,
input tx_ready ,
input [7:0] rx_data ,
input rx_data_vld ,
input done
);
parameter TSE = 3_000_000_000 / 20;
parameter TPP = 5_000_000 / 20;
localparam IDLE = 0 ,
RDID = 1 ,
READ = 2 ,
WAIT = 3 , //等待接口模块处理完成
WEN_1 = 4 , //扇区擦除需要的写使能
SE = 5 ,
DEL_SE = 6 ,
WEN_2 = 7 , //PP指令需要的写使能
PP = 8 ,
DEL_PP = 9 ;
reg [4:0] state ;
wire idle2rdid ;
wire idle2read ;
wire idle2wen_1 ;
wire rdid2wait ;
wire read2wait ;
wire wait2idle ;
wire wen_12se ;
wire se2del_se ;
wire del_se2wen_2 ;
wire wen_22pp ;
wire pp2del_pp ;
wire del_pp2idle ;
reg [9:0] cnt_byte ;
wire add_byte_cnt ;
wire end_byte_cnt ;
reg [9:0] byte_max ;
reg [23:0] read_addr_r ;
reg [8:0] read_num_r ;
reg done_flag ; //辅助状态机数据发送和done信号
reg rdid_flag ;
reg read_flag ;
reg write_flag ;
reg [27:0] cnt_delay ;
wire add_delay_cnt ;
wire end_delay_cnt ;
reg [27:0] delay_max ;
reg [23:0] write_addr_r ;
reg [8:0] write_num_r ;
wire [7:0] fifo_rd_data ;
wire fifo_rd_req ;
wire fifo_empty ;
wire fifo_full ;
wire [8:0] fifo_num ;
reg fifo_wr_end ; //需要写入的数据与fifo中存入的数据个数一致
/**************************************************************
输入命令寄存
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
read_addr_r <= 0;
read_num_r <= 0;
end
else if(read_req) begin
read_addr_r <= read_addr;
read_num_r <= read_num ;
end
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
write_addr_r <= 1;
write_num_r <= 1; //不能为0,存储发送数据的fifo复位之后,userdw信号就是0,会导致状态机误判
end
else if(write_req) begin
write_addr_r <= write_addr;
write_num_r <= write_num ;
end
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(idle2rdid)
state <= RDID;
else if(idle2read)
state <= READ;
else if(idle2wen_1)
state <= WEN_1;
//读数据部分
RDID : if(rdid2wait)
state <= WAIT;
READ : if(read2wait)
state <= WAIT;
WAIT : if(wait2idle)
state <= IDLE;
//写数据部分
WEN_1 : if(wen_12se) //写使能
state <= SE;
SE : if(se2del_se) //执行SE
state <= DEL_SE;
DEL_SE : if(del_se2wen_2) //SE执行时间
state <= WEN_2;
WEN_2 : if(wen_22pp) //写使能
state <= PP;
PP : if(pp2del_pp) //PP指令
state <= DEL_PP;
DEL_PP : if(del_pp2idle) //PP执行时间
state <= IDLE;
default : state <= IDLE;
endcase
assign idle2rdid = state == IDLE && rdid_req;
assign idle2read = state == IDLE && read_req;
assign idle2wen_1 = state == IDLE && fifo_wr_end; //fifo中的数据量已经达到了写的数据量
assign rdid2wait = state == RDID && end_byte_cnt; //一次读ID需要发送的数据个数已经发送完成了
assign read2wait = state == READ && end_byte_cnt;
assign wait2idle = state == WAIT && done; //接口模块完成了需要进行的操作
//写数据
assign wen_12se = state == WEN_1 && done_flag && done;
assign se2del_se = state == SE && done_flag && done;
assign del_se2wen_2= state == DEL_SE && end_delay_cnt;
assign wen_22pp = state == WEN_2 && done_flag && done;
assign pp2del_pp = state == PP && done_flag && done;
assign del_pp2idle = state == DEL_PP && end_delay_cnt;
/**************************************************************
字节计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_byte <= 'd0;
else if(add_byte_cnt) begin
if(end_byte_cnt)
cnt_byte <= 'd0;
else
cnt_byte <= cnt_byte + 1'b1;
end
assign add_byte_cnt = (state == RDID || state == READ || state == WEN_1 || state == WEN_2 || state == SE || state == PP) && tx_ready && ~done_flag; //使用~done_flag 是为了控制在当前状态下是否需要发送数据
assign end_byte_cnt = add_byte_cnt && cnt_byte == byte_max - 1;
always@(*)
case(state)
READ : byte_max = 4 + read_num_r; //1个指令,3个地址,读取数据量
RDID,SE : byte_max = 4;
PP : byte_max = 4 + write_num_r;
default : byte_max = 1;
endcase
/**************************************************************
状态跳转控制
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
done_flag <= 0;
else if(end_byte_cnt) //当前状态已经传输完了
done_flag <= 1;
else if(done) //完成了当前状态需要发送的数据
done_flag <= 0;
/**************************************************************
接口数据传递
**************************************************************/
always@(*)
case(state)
RDID : case(cnt_byte)
0 : tx_data = 8'h9f; //读ID指令
default : tx_data = 8'hff;
endcase
READ : case(cnt_byte)
0 : tx_data = 8'h03; //读数据指令
1 : tx_data = read_addr_r[16+: 8];
2 : tx_data = read_addr_r[8 +: 8];
3 : tx_data = read_addr_r[0 +: 8];
default : tx_data = 8'hff;
endcase
WEN_1 ,
WEN_2 : case(cnt_byte)
0 : tx_data = 8'h06; //写使能指令
default : tx_data = 8'hff;
endcase
SE : case(cnt_byte)
0 : tx_data = 8'hd8; //扇区擦除指令
1 : tx_data = write_addr_r[16+: 8];
2 : tx_data = write_addr_r[8 +: 8];
3 : tx_data = write_addr_r[0 +: 8];
default : tx_data = 8'hff;
endcase
PP : case(cnt_byte)
0 : tx_data = 8'h02; //PP指令
1 : tx_data = write_addr_r[16+: 8];
2 : tx_data = write_addr_r[8 +: 8];
3 : tx_data = write_addr_r[0 +: 8];
default : tx_data = fifo_rd_data; //需要写入的数据
endcase
default : tx_data = 8'hff;
endcase
assign tx_data_vld = add_byte_cnt;
/**************************************************************
记录当前执行的什么操作
**************************************************************/
reg [1:0] op_flag ;
`define OP_RDID 2'b01
`define OP_READ 2'b10
always@(posedge clk or negedge rst_n)
if(!rst_n)
op_flag <= `OP_RDID;
else if(idle2read)
op_flag <= `OP_READ;
else if(idle2rdid)
op_flag <= `OP_RDID;
else if(idle2wen_1)
op_flag <=2'b00;
/**************************************************************
处理接口模块返回的数据
**************************************************************/
reg [2:0] cnt_num;
wire add_num_cnt,end_num_cnt;
reg [2:0] num_max;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_num <= 'd0;
else if(add_num_cnt) begin
if(end_num_cnt)
cnt_num <= 'd0;
else
cnt_num <= cnt_num + 1'b1;
end
assign add_num_cnt = rx_data_vld && ~rdid_flag && ~read_flag;
assign end_num_cnt = add_num_cnt && cnt_num == num_max - 1; //滤除不需要的数据
always@(*)
if(op_flag == `OP_READ)
num_max = 4; //需要滤除1个指令和3个地址
else
num_max = 1;
/**************************************************************
接收Flash读回来的数据
**************************************************************/
//处理ID数据
always@(posedge clk or negedge rst_n)
if(!rst_n)
rdid_flag <= 0;
else if(end_num_cnt && op_flag == `OP_RDID)
rdid_flag <= 1;
else if(wait2idle)
rdid_flag <= 0;
//处理读取的数据
always@(posedge clk or negedge rst_n)
if(!rst_n)
read_flag <= 0;
else if(end_num_cnt && op_flag == `OP_READ)
read_flag <= 1;
else if(wait2idle)
read_flag <= 0;
assign read_data = rx_data;
assign read_data_vld = (read_flag || rdid_flag) && rx_data_vld;
/**************************************************************
写数据延时
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_delay <= 'd0;
else if(add_delay_cnt) begin
if(end_delay_cnt)
cnt_delay <= 'd0;
else
cnt_delay <= cnt_delay + 1'b1;
end
assign add_delay_cnt = (state == DEL_PP) || (state == DEL_SE);
assign end_delay_cnt = add_delay_cnt && cnt_delay == delay_max - 1;
always@(*)
if(state == DEL_SE)
delay_max = TSE;
else if(state == DEL_PP)
delay_max = TPP;
else
delay_max = 1;
/**************************************************************
写数据控制
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
write_flag <= 0;
else if(write_req)
write_flag <= 1;
else if(idle2wen_1) //真正进入写数据状态之后,拉低flag
write_flag <= 0;
/**************************************************************
缓存需要写的数据
**************************************************************/
fifo fifo_inst (
.aclr ( ~rst_n || del_pp2idle ),
.clock ( clk ),
.data ( write_data ),
.rdreq (fifo_rd_req ),
.wrreq ( write_data_vld && ~fifo_full),
.empty (fifo_empty ),
.full (fifo_full ),
.q (fifo_rd_data ),
.usedw (fifo_num )
);
assign fifo_rd_req = state == PP && cnt_byte > 3 && tx_ready && ~fifo_empty;
assign write_ready = ~fifo_full;
always@(posedge clk or negedge rst_n)
if(!rst_n)
fifo_wr_end <= 0;
else if(fifo_num == write_num_r)
fifo_wr_end <= 1;
else if(pp2del_pp) //数据写入完成
fifo_wr_end <= 0;
endmodule
3.2 SPI接口模块
/**************************************************************
@File : spi_master.v
@Time :
@Author :
@EditTool: VS Code
@Font : UTF-8
@Function: 实现SPI的接口,完成数据/指令的发送
SPI时钟速率为输入时钟的1/4
只支持
CPOL = 0 CPHA = 1 模式1
CPOL = 1 CPHA = 1 模式3
默认使用模式3
**************************************************************/
module spi_master #(
parameter CPOL = 1 //时钟极性
//parameter CPHA = 1 //时钟相位
)(
input clk ,
input rst_n ,
input [7:0] tx_data ,
input tx_data_vld ,
output tx_ready ,
output reg [7:0] rx_data ,
output rx_data_vld ,
output done , //一次操作完成
output reg sclk ,
output reg mosi ,
input miso ,
output reg cs_n
);
wire fifo_empty ;
wire fifo_full ;
wire fifo_rd_req ;
wire [7:0] fifo_rd_data;
parameter IDLE = 0 ,//空闲
DLY_1 = 1 ,//片选提前拉低延时
DATA = 2 ,//数据传输
DLY_2 = 3 ,//片选延后拉高延时
HOLD = 4 ;//片选拉高后保持时间
reg [2:0] state ;
wire idle2dly_1 ;
wire dly_12data ;
wire data2dly_2 ;
wire dly_22hold ;
wire hold2idle ;
reg [3:0] cnt_div ;
wire add_div_cnt ;
wire end_div_cnt ;
parameter DIV_MAX = 4 ; //时钟分频倍数
reg [3:0] cnt_bit ;
wire add_bit_cnt ;
wire end_bit_cnt ;
reg [2:0] cnt_delay ;
wire add_delay_cnt;
wire end_delay_cnt;
parameter DELAY_MAX = 100 / 20; //100ns的保持时间
/**************************************************************
缓存需要发送的数据
**************************************************************/
tx_fifo tx_fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.wrreq ( tx_data_vld && ~fifo_full),
.data ( tx_data ),
.rdreq (fifo_rd_req ),
.q (fifo_rd_data ),
.empty (fifo_empty ),
.full (fifo_full ),
.usedw ( )
);
//提前一点读取新数据,让fifo的empty信号早点更新
//(根据仿真调整的,仿真体现出来的结果是,fifo的empty信号晚了一个时钟周期,导致在DATA状态多待了一个时钟周期,
//从而导致SCLK信号有毛刺,以及div计数器未归0)
assign fifo_rd_req = ~fifo_empty && cnt_bit == 7 && (cnt_div == DIV_MAX - 2); //end_bit_cnt; //发送完了一个字节的数据,就会读取新的数据
assign tx_ready = ~fifo_full;
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(idle2dly_1)
state <= DLY_1;
DLY_1 : if(dly_12data)
state <= DATA;
DATA : if(data2dly_2)
state <= DLY_2;
DLY_2 : if(dly_22hold)
state <= HOLD;
HOLD : if(hold2idle)
state <= IDLE;
default : state <= IDLE;
endcase
assign idle2dly_1 = state == IDLE && ~fifo_empty;
assign dly_12data = state == DLY_1 && 1; //延时20ns的时间
assign data2dly_2 = state == DATA && fifo_empty; //fifo中没有数据需要传输了,表示这一次需要传输的数据传完了
assign dly_22hold = state == DLY_2 && 1; //延时20ns的时间
assign hold2idle = state == HOLD && end_delay_cnt; //片选信号保持时间结束
/**************************************************************
SPI SCLK
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_div <= 'd0;
else if(add_div_cnt) begin
if(end_div_cnt)
cnt_div <= 'd0;
else
cnt_div <= cnt_div + 1'b1;
end
assign add_div_cnt = state == DATA;
assign end_div_cnt = add_div_cnt && cnt_div == DIV_MAX - 1; //时钟分频,产生SCLK
generate
if(CPOL == 1) begin
always@(posedge clk or negedge rst_n)
if(!rst_n)
sclk <= 1; //模式3,空闲为高电平
else if(state == DATA && (cnt_div < (DIV_MAX >> 1)))
sclk <= 0;
else
sclk <= 1;
end
else begin //CPOL = 0
always@(posedge clk or negedge rst_n)
if(!rst_n)
sclk <= 0; //模式1,空闲为低电平
else if(state == DATA && (cnt_div < (DIV_MAX >> 1)))
sclk <= 1;
else
sclk <= 0;
end
endgenerate
/**************************************************************
bit控制
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_bit <= 'd0;
else if(add_bit_cnt) begin
if(end_bit_cnt)
cnt_bit <= 'd0;
else
cnt_bit <= cnt_bit + 1'b1;
end
assign add_bit_cnt = end_div_cnt;
assign end_bit_cnt = add_bit_cnt && cnt_bit == 8 - 1;
/**************************************************************
SPI MOSI
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
mosi <= 0;
else if(state == DATA && (cnt_div == 0)) //模式1和3,都是在第一个边沿传输数据
mosi <= fifo_rd_data[7-cnt_bit]; //MSB,先发送数据的高位
/**************************************************************
片选信号保持时间
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_delay <= 'd0;
else if(add_delay_cnt) begin
if(end_delay_cnt)
cnt_delay <= 'd0;
else
cnt_delay <= cnt_delay + 1'b1;
end
assign add_delay_cnt = state == HOLD;
assign end_delay_cnt = add_delay_cnt && cnt_delay == DELAY_MAX - 1;
/**************************************************************
片选信号控制
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cs_n <= 1;
else if(state == HOLD || state == IDLE)
cs_n <= 1;
else
cs_n <= 0;
/**************************************************************
数据接收,MISO
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
rx_data <= 0;
else if(state == DATA && (cnt_div == (DIV_MAX >> 1))) //sclk第二个边沿接收数据
rx_data <= {rx_data[6:0],miso};
assign rx_data_vld = end_bit_cnt; //接收完8bit数据
assign done = hold2idle; //完成了一次完整的数据传递
endmodule
3.3 按键模块
module key#(
parameter KEY_WIDTH = 3'd4
)(
input sys_clk ,
input sys_rst_n ,
input [KEY_WIDTH - 1:0] key_in ,
output reg [KEY_WIDTH - 1:0] key_flag
);
parameter CNT_MAX = 20'd1000_000; //20ms消抖时间
localparam IDLE = 4'b0001,
DONE = 4'b0010,
HOLD = 4'b0100,
UP = 4'b1000;
reg [3:0] state;
reg [KEY_WIDTH - 1:0] key_in_reg0,
key_in_reg1,
key_in_reg2;
reg [KEY_WIDTH - 1:0] key_reg;
wire key_in_negedge,
key_in_posedge;
/* 打拍 */
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n) begin
key_in_reg0 <= {KEY_WIDTH{1'd1}}; /* 同步 */
key_in_reg1 <= {KEY_WIDTH{1'd1}}; /* 同步 */
key_in_reg2 <= {KEY_WIDTH{1'd1}}; /* 边沿 */
end
else begin
key_in_reg0 <= key_in;
key_in_reg1 <= key_in_reg0;
key_in_reg2 <= key_in_reg1;
end
/* 按位与,只要有一位出现了下降沿/上升沿,就会将该信号拉高 */
assign key_in_negedge = ((key_in_reg2 & ~key_in_reg1) != 'd0)? 1'b1 : 1'b0;
assign key_in_posedge = ((~key_in_reg2 & key_in_reg1) != 'd0)? 1'b1 : 1'b0;
reg start_cnt;
reg cnt_flag;
reg [19:0] cnt;
wire add_cnt,end_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
start_cnt <= 1'b0;
else if(end_cnt)
start_cnt <= 1'b0;
else if(key_in_negedge || key_in_posedge)
start_cnt <= 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt <= 20'd0;
else if(add_cnt) begin
if(end_cnt)
cnt <= 20'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 20'd0;
assign add_cnt = start_cnt;
assign end_cnt = add_cnt && ((cnt == CNT_MAX - 1) || (key_in_negedge));
/* 触发缓存 */
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_reg <= 'd0;
else if(key_in_negedge)
key_reg <= key_in_reg1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
state <= IDLE;
else case(state)
IDLE : if(key_in_negedge)
state <= DONE;
DONE : if(end_cnt) begin
if(key_in == key_reg)
state <= HOLD;
else
state <= IDLE;
end
HOLD : if(key_in_posedge)
state <= UP;
UP : if(end_cnt)
state <= IDLE;
default : state <= IDLE;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_flag <= 'd0;
else if(state == HOLD && key_in_posedge)
key_flag <= ~key_in_reg2;
else
key_flag <= 'd0;
endmodule
3.4 顶层模块
/**************************************************************
@File : top.v
@Time :
@Author :
@EditTool: VS Code
@Font : UTF-8
@Function: 按键2将数据AC写入到地址0中,按键1从地址0读取一个字节的数据,按键0读取flash的ID
**************************************************************/
module top(
input clk ,
input rst_n ,
input rx ,
output tx ,
input [2:0] key ,
output sclk ,
output mosi ,
input miso ,
output cs_n
);
wire [7:0] uart_tx_data ;
wire uart_tx_data_vld;
wire [7:0] uart_rx_data ;
wire uart_rx_data_vld;
wire uart_rx_ready ;
wire spi_tx_ready ;
wire [7:0] tx_data ;
wire tx_data_vld ;
wire tx_ready ;
wire [7:0] rx_data ;
wire rx_data_vld ;
wire done ;
wire [2:0] key_flag ;
key #(.KEY_WIDTH(3)) key_inst(
/*input */.sys_clk (clk ),
/*input */.sys_rst_n (rst_n ),
/*input [KEY_WIDTH - 1:0] */.key_in (key ),
/*output reg [KEY_WIDTH - 1:0] */.key_flag (key_flag )
);
my_uart my_uart (
.uart_clk (clk ), // clk.clk
.rst_n (rst_n ), // rst_n.reset_n
.user_tx_clk (clk ), // user.user_tx_clk
.user_tx_data (uart_tx_data ), // .user_tx_data
.user_tx_data_vld (uart_tx_data_vld ), // .user_tx_data_vld
.user_tx_ready ( ), // .user_tx_ready
.user_rx_clk (clk ), // .user_rx_clk
.user_rx_ready (uart_rx_ready ), // .user_rx_ready
.user_rx_rd_req (uart_rx_ready && spi_tx_ready), // .user_rx_rd_req
.user_rx_data (uart_rx_data ), // .user_rx_data
.user_rx_data_vld (uart_rx_data_vld ), // .user_rx_data_vld
.uart_rxd (rx ), // uart.uart_rxd
.uart_txd (tx ) // .uart_txd
);
flash_control flash_control(
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input */.rdid_req (key_flag[0] ), //读ID请求
/* input */.read_req (key_flag[1] ), //读数据
/* input [23:0] */.read_addr (0 ),
/* input [7:0] */.read_num (1 ), //需要读出数据的个数 0~255
/* output [7:0] */.read_data (uart_tx_data),
/* output */.read_data_vld (uart_tx_data_vld),
/* input */.write_req (key_flag[2] ),
/* input [23:0] */.write_addr (0 ),
/* input [8:0] */.write_num (1 ), //写数据个数 0~256
/* input [7:0] */.write_data (8'hac ),
/* input */.write_data_vld (key_flag[2] ),
/* output */.write_ready ( ),
/* output reg [7:0] */.tx_data (tx_data ),
/* output */.tx_data_vld (tx_data_vld ),
/* input */.tx_ready (tx_ready ),
/* input [7:0] */.rx_data (rx_data ),
/* input */.rx_data_vld (rx_data_vld ),
/* input */.done (done )
);
spi_master spi_master (
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [7:0] */.tx_data (tx_data ),
/* input */.tx_data_vld (tx_data_vld),
/* output */.tx_ready (tx_ready ),
/* output reg [7:0] */.rx_data (rx_data ),
/* output */.rx_data_vld (rx_data_vld),
/* output */.done (done ), //一次操作完成
/* output reg */.sclk (sclk ),
/* output reg */.mosi (mosi ),
/* input */.miso (miso ),
/* output reg */.cs_n (cs_n )
);
endmodule
3.6 顶层模块
`timescale 1ns/1ps
module flash_control_tb ();
parameter CLK_CYCLE = 20;
reg sys_clk,sys_rst_n;
always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#(CLK_CYCLE*2);
sys_rst_n = 1'b1;
end
wire [7:0] tx_data ;
wire tx_data_vld ;
wire tx_ready ;
wire [7:0] rx_data ;
wire rx_data_vld ;
wire done ;
wire sclk ;
wire mosi ;
wire miso ;
wire cs_n ;
reg read_req ;
reg [23:0] read_addr ;
reg [8:0] read_num ;
reg rdid_req ;
reg write_req ;
reg [23:0] write_addr ;
reg [8:0] write_num ;
reg [7:0] write_data ;
reg write_data_vld ;
wire write_ready ;
initial begin
read_req = 0;
read_addr = 0;
read_num = 0;
rdid_req = 0;
write_req = 0;
write_addr = 0;
write_num = 0;
write_data = 0;
write_data_vld = 0;
#100;
//写数据
write_req = 1;
write_addr = 0;
write_num = 256;
#20;
write_req = 0;
repeat(256) begin
write_data = {$random};
write_data_vld = 1;
#20;
end
write_data_vld = 0;
wait (flash_control.del_pp2idle == 1);
wait (flash_control.state == 0); //等待写数据完成 状态机为IDLE
#200;
//读数据
read_req = 1;
read_addr = 0;
read_num = 256;
#20
read_req = 0;
wait (flash_control.wait2idle == 1);
#200;
rdid_req = 1;
#20;
rdid_req = 0;
wait (flash_control.wait2idle == 1);
#3000;
$stop;
end
defparam flash_control.TSE = 4000 / 20;
defparam flash_control.TPP = 600 / 20;
flash_control flash_control(
/* input */.clk (sys_clk ),
/* input */.rst_n (sys_rst_n ),
/* input */.rdid_req (rdid_req ), //读ID请求
/* input */.read_req (read_req ), //读数据
/* input [23:0] */.read_addr (read_addr ),
/* input [8:0] */.read_num (read_num ), //需要读出数据的个数 0~255
/* output [7:0] */.read_data ( ),
/* output */.read_data_vld ( ),
/* input */.write_req (write_req ),
/* input [23:0] */.write_addr (write_addr ),
/* input [8:0] */.write_num (write_num ), //写数据个数 0~256
/* input [7:0] */.write_data (write_data ),
/* input */.write_data_vld (write_data_vld),
/* output */.write_ready (write_ready ),
/* output reg [7:0] */.tx_data (tx_data ),
/* output */.tx_data_vld (tx_data_vld ),
/* input */.tx_ready (tx_ready ),
/* input [7:0] */.rx_data (rx_data ),
/* input */.rx_data_vld (rx_data_vld ),
/* input */.done (done )
);
spi_master spi_master (
/* input */.clk (sys_clk ),
/* input */.rst_n (sys_rst_n ),
/* input [7:0] */.tx_data (tx_data ),
/* input */.tx_data_vld (tx_data_vld),
/* output */.tx_ready (tx_ready ),
/* output reg [7:0] */.rx_data (rx_data ),
/* output */.rx_data_vld (rx_data_vld),
/* output */.done (done ), //一次操作完成
/* output reg */.sclk (sclk ),
/* output reg */.mosi (mosi ),
/* input */.miso (miso ),
/* output reg */.cs_n (cs_n )
);
m25p16 m25p16_inst(
.c (sclk ),
.data_in (mosi ),
.s (cs_n ),
.w (1'b1 ),
.hold (1'b1 ),
.data_out (miso )
);
endmodule