1、理论知识
SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于EEPROM、Flash、RTC(实时时钟)、ADC(数模转换器)、DSP (数字信号处理器)以及数字信号解码器上,是常用的也是较为重要的通讯协议之一。
SPI通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;缺点是没有指定的流控制,没有应答机制确认数据是否接收,与IIC总线通讯协议相比,在数据可靠性上有一定缺陷,IIC总线通讯协议的相关内容会在后面章节进行讲解。
对于SPI通讯协议的相关内容我们分为物理层、协议层两部分进行讲解,具体内容如下。
1.1 物理层
SPI通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分,根据从机设备的个数,SPI通讯设备之间的连接方式可分为一主一从和一主多从
1.2 协议层
1.2.1 CPOL/CPHA及通讯模式
CPOL:时钟极性(clock polarity)状态 0 1
CPHA:时钟相位(clock phase)状态 0 1
SPI通讯协议一共有四种通讯模式,模式0、模式1、模式2以及模式3,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中)时SCK时钟信号的电平状态,CPHA规定了数据采样是在SCK时钟的奇数边沿还是偶数边沿。
模式0:CPOL= 0,CPHA=0。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的奇数边沿,本模式中,奇数边沿为上升沿;数据更新在SCK时钟的偶数边沿,本模式中,偶数边沿为下降沿。
模式1:CPOL= 0,CPHA=1。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的偶数边沿,本模式中,偶数边沿为下降沿;数据更新在SCK时钟的奇数边沿,本模式中,偶数边沿为上升沿。
模式2:CPOL= 1,CPHA=0。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的奇数边沿,本模式中,奇数边沿为下降沿;数据更新在SCK时钟的偶数边沿,本模式中,偶数边沿为上升沿。
模式3:CPOL= 1,CPHA=1。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的偶数边沿,本模式中,偶数边沿为上升沿;数据更新在SCK时钟的奇数边沿,本模式中,偶数边沿为下降沿。
对于4种通讯模式中,CPOL比较好理解,就是表示设备未被选中的空闲状态时,串行时钟SCK的电平状态,CPOL = 0,空闲状态时SCK为低电平,CPOL = 1,空闲状态时SCK为高电平;CPHA的不同参数则规定了数据采样是在SCK时钟的奇数边沿还是偶数边沿,CPHA = 0,数据采样是在SCK时钟的奇数边沿,CPHA = 1,数据采样是在SCK时钟的偶数边沿,这里不使用上升沿或下降沿表示,是因为不同模式下,奇数边沿或偶数边沿与上升沿或下降沿的对应不是固定的。
首先,根据SCK在空闲状态时的电平,分为两种情况。CPOL = 0,SCK信号线在空闲状态为低电平; CPOL = 1,SCK信号线在空闲状态为高电平。
无论CPOL = 0还是1,我们配置的时钟相位CPHA = 0,在图中可以看到,采样时刻都是在SCK的奇数边沿。注意当CPOL=0的时候,时钟的奇数边沿是上升沿,而CPOL=1的时候,时钟的奇数边沿是下降沿。所以SPI的采样时刻不是由上升/下降沿决定的。MOSI和MISO数据线的有效信号在SCK的奇数边沿保持不变,数据信号将在SCK奇数边沿时被采样,在非采样时刻,MOSI和MISO的有效信号才发生切换。
类似地,当CPHA=1时,不受CPOL的影响,数据信号在SCK的偶数边沿被采样,具体见图 。
1.2.2 SPI通讯过程
上文中,我们详细介绍了SPI通讯协议的4中通讯模式,其中模式0和模式3比较常用,下面我们以模式0为例,为大家讲解一下SPI基本的通讯过程。SPI模式0通讯时序图,即空闲时为低电平,采样点为奇数边沿。具体见图。
此图表示的是主机视角的通讯时序。SCK、MOSI、CS_N信号均由主机控制产生,SCK是时钟信号,用以同步数据,MOSI是主机输出从机输入信号,主机通过此信号线传输数据给从机,CS_N为片选信号,用以选定从机设备,低电平有效;而MISO的信号由从机产生,主机通过该信号线读取从机的数据。MOSI与MI SO的信号只在CS_N为低电平的时候才有效,在SCK的每个时钟周期MOSI和MISO传输一位数据。
CS_N信号线由高变低,是SPI通讯的起始信号。CS_N是每个从机各自独占的信号线,当从机在自己的CS_N线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号处,CS_N信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行并没有作硬性规定,但要保证两个SPI通讯设备之间使用同样的协定,一般都会采图 4中的MSB先行模式。
观察图中的标号处,MOSI及MISO的数据在SCK的下降沿期间变化输出,在SCK的上升沿时被采样。即在SCK的上升沿时刻,MOSI及MISO的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI及MISO为下一次表示数据做准备。
SPI每次数据传输可以8位或16位为单位,每次传输的单位数不受限制。
2 SPI-Flash全擦除实验
2.1 flash芯片原理图
W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为64Mb(8M Bytes)。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。
W25Q64的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80Mhz。
——https://www.cnblogs.com/gongchuangsu/p/4850223.html
我们在平时对工程进行上板验证的时候,可以通过两种方式烧录程序:一种是将程序下载到FPGA内部的SRAM之中,这种方式烧录过程耗时较短,但缺点是掉电后程序会丢失,再次上电后要重新烧录程序;另外一种就是将程序固化到FPGA外部挂载的Flash芯片中,Flash芯片是非易失性存储器,程序掉电后不会丢失,重 新上电后会执行掉电前烧录到Flash中的程序,但是烧录程序耗时较长。
如果我们对程序验证完成后,想要将固化到Flash中的程序删除时,可以通过两种方式,分别是全擦除和扇区擦除。
本文将带领读者分别编写全擦除工程和扇区擦除工程,使读者对这两种擦除方式有清醒的认识,让读者掌握两种擦除方式的实现方法。
Flash的全擦除,顾名思义就是将Flash所有的存储空间都进行擦除操作,使各存储空间内存储数据恢复到初始值。FPGA要实现Flash的全擦除也有有两种方式。
方式一:利用FPGA编译软件,通过Quartus软件的“programmer”窗口,将烧录到Flash的*.jic文件擦除,具体见图 ;
方式二:编写全擦除程序,实现Flash芯片的全擦除,就是我们下面要进行的实验。
2.2 实验目标
事先向Flash芯片中烧录流水灯程序,FPGA上电执行流水灯程序,下载Flash芯片全擦除程序到FPGA内部SRAM并执行,擦除Flash芯片中烧录的流水灯程序,FPGA重新上电后,无程序执行。
2.2.1 操作时序
W25Q64的全擦除(Bulk Erase)操作,简称BE,操作指令为8’b1100_0111(C7h)
在Flash芯片写入全擦出指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态;随后要拉低片选信号,写入全擦除指令,在指令写入过程中,片选信号始终保持低电平,待指令被芯片锁存后,将片选信号拉高;全擦除指令被锁存并执行后,需要等待一个完整的全擦除周期(tBE),才能完成Flash芯片的全擦除操作。全擦除操作的详细介绍及时序图,具体见图
上文全擦除操作中我们提到,全擦除(BE)指令写入前必须先对Flash芯片写入写使能(WREN)指令,使芯片处于写使能锁存(WEL)状态。此状态下写入全擦除指令才会被Flash芯片响应,否则,全擦除指令无效。
写使能(Write Enable)指令,简称WREN,操作指令为8’b0000_0110(06h)
由数据手册中写使能介绍部分可知,写使能指令可将Flash芯片设置为写使能锁存(WEL)状态;在每一次页写操作(PP)、扇区擦除(SE)、全擦除(BE)和写状态寄存器(WRSR)操作之前,都需要先进行写使能指令写入操作。操作时序为先拉低片选信号,写入写使能指令,在指令写入过程中,片选信号始终保持低电平 ,指令写入完成后,将片选信号拉高。写使能指令的详细介绍及时序图,具体见图 :
写使能指令、全擦除指令以及其它操作指令在写入Flash芯片时要严格遵循芯片的串行输入时序。串行输入时序图,具体见图
如图所示,相关操作指令在写入芯片之前需要先拉低片选信号,在片选信号保持低电平时将指令写入数据输入端口,指令写入完毕,拉高片选信号,数据输出端口在指令写入过程中始终保持高阻态。图中定义了许多时间参数,其中有三个我们需要格外注意,分别是tSLCH、tCHSH和tSHSL。时间参数参考数值,具体见图:
片选信号自下降沿始到第一个有效数据写入时止,这一段等待时间定义为片选信号有效建立时间 tSLCH,由图可知,这一时间段必须大于等于5ns;片选信号自最后一个有效数据写入时始到片选信号上升沿止,这一段等待时间定义为片选信号有效保持时间tCHSH,由图可知,这一时间段必须大于等于5ns;片选信号自上一个上升沿始到下一个下降沿止,这一段等待时间定义为片选信号高电平等待时间tSHSL,由图可知,这一时间段必须大于等于100ns。
综上所述,绘制完整全擦除操作时序图如图:
2.3 程序设计
整个全擦除工程调用3个模块,按键消抖模块(key_filter), Flash全擦除模块(flash_be_ctrl)和顶层模块(spi_flash_be)。模块框图,具体见图 46‑17;模块简介,具体见表格 46‑1。
模块名称 | 功能描述 |
---|---|
spi_flsah_be | 全擦除工程顶层模块 |
key_filter | 按键消抖模块 |
flash_be_ctrl | 全擦除模块 |
外部按键负责产生全擦除触发信号,信号由外部进入FPGA,经顶层模块(spi_flash_be)进入按键消抖模块(key_filter),触发信号经消抖处理后输出进入Flash全擦除模块(spi_flash_be),触发信号有效,Flash全擦除模块工作, 生成并输出串行时钟信号(sck)、片选信号(cs_n)和主输出从输入信号(mosi),3路信号输入外部挂载的Flash芯片,Flash芯片接收到全擦除指令,实现Flash芯片全擦除。
Flash全擦除模块是本实验工程的核心模块,其生成并输出时钟、片选和数据信号,向Flash芯片发送全擦除指令,控制Flash芯片实现全擦除,本模块的模块框图,具体见图以及表格。
信号 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
sys_clk | 1Bit | Input | 系统时钟50MHz |
sys_rst_n | 1Bit | Input | 复位信号,低有效 |
key | 1Bit | Input | 全擦除触发信号 |
sck | 1Bit | Output | Flash串行时钟 |
cs_n | 1Bit | Output | Flash片选信号 |
mosi | 1Bit | Output | Flash主输出从输入信号 |
由前文可知,一个完整的全擦除操作需要对Flash芯片执行两次指令的写入,分别为写使能指令和全擦除指令,而且在片选信号拉低后指令写入前、指令写入完成后片选信号拉高前,以及两指令写入之间都需要做规定时间的等待。
对于这一流程操作,我们可以使用状态机来实现。在模块内部声明状态机状态变量state,定义状态机各状态分别为:初始状态(IDLE)、写使能状态(WR_EN)、两指令间等待状态(DELAY)、全擦除状态(BE)。
状态机状态跳转流程如下:系统上电后,状态机状态变量state一直处于初始状态(IDLE);当传入的全擦除触发信号key有效时,表示实验工程开始执行对Flash芯片的全擦除操作,状态机跳转到写使能状态(WR_EN),同时片选信号拉低,选中要进行全擦除操作的Flash芯片;状态跳转到写使能状态且片选信号拉低后,要进行tSLCH≥5ns的等待时间,等待时间过后对主输出从输入信号写入写使能指令,指令写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对Flash芯片的选择,同时状态机跳转到两指令间等待状态(DELAY);在此状态等待时间tSHSL≥ 100ns后,状态机跳转到全擦除状态(BE),同时片选信号拉低,选中已写入写使能指令的Flash芯片;状态机跳转到全擦除状态且片选信号拉低后,要进行tSLCH≥5ns的等待时间,等待时间过后对主输出从输入信号写入全擦除指令,指令写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对Flash芯片的选择,同时状态机跳回初始状态(IDLE),一次完整的全擦除操作完成。
第一个问题:片选信号的等待时间tSHSL、 tCHSH、tSHSL的参数确定。
在状态机的状态跳转过程中,片选信号在某些位置需要做规定时间的等待,但在各状态的等待时间参数是不同的,如果声明多个计数器对各等待时间分别计数或者使用一个计数器、多个等待结束标志的话,虽然能够实现,但较为麻烦,且声明信号较多。不如声明一个通用计数器,以最长等待时间为下限进行等待时间的计数,且各等待时间没有上限约束,更容易实现。
由Flash芯片数据手册可知,Flash芯片数据读操作的时钟频率(SCK)上限为20MHz,除数据读操作之外的其他操作频率上限为50MHz,为了后续数据读操作不再进行时钟的更改,本实验工程的所有实验的时钟均使用12.5MHz,因为晶振传入时钟为50MHz,通过四分频生成12.5MHz较为方便,且满足 Flash芯片时钟要求。
Flash芯片的指令为串行传输,每个时钟周期只能写入1比特数据,要写入一个完整的单字节指令需要8个完整的SCK时钟周期,即32个完整的系统时钟,系统时钟频率为50MHz,完整指令的写入需要640ns。
这个时间大于片选信号最长等待时间的下限100ns。所以将片选信号的各等待时间的时间参数统一设置为640ns,即32个系统时钟周期。这样声明的计数器不仅可以用作片选信号等待时间计数,也可以用做指令信号写入时间计数,可节省寄存器资源。
所以声明计数器cnt_clk,初值为0,在0-31计数范围内循环计数,在状态机处于初始状态时,始终保持为0;在状态机处于初始状态之外的其他状态时,每个系统时钟周期自加1,计到最大值清0,重新计数。
第二个问题:状态机状态跳转约束条件的确定。
状态机在系统上电之后处于初始状态(IDLE),待输入的全擦除触发信号有效时,状态机由初始状态跳转到写使能状态(WR_EN),但写使能状态后的各状态跳转应该如何进行,跳转条件又是什么?
可以使用刚刚声明的计数器cnt_clk作为状态跳转的约束条件,但条件并不充分,因为计数器cnt_clk为0-31循环计数,使用其单独作为约束条件的话,状态机在每个cnt_clk的计数周期都会存在满足跳转条件的计数值,所以需要声明一个新的计数器来对计数器cnt_clk的计数周期进行计数,使用两个计数器作为约束条件可以实现状态机的状态跳转。
声明计数器cnt_byte对计数器cnt_clk的计数周期进行计数。对cnt_byte赋初值为0,当状态机处于初始状态(IDLE)时,计数器cnt_byte始终保持初值0;当状态机处于除初始状态外的其他状态时,计数器cnt_byte开始对计数器cnt_clk的计数周期进行计数,cnt_clk每完成一 个完整的循环计数,即cnt_clk = 31时,计数器cnt_byte自加1,其他时刻保持当前值不变。
使用这两个计数器作为约束条件就可以实现状态机的状态跳转,当状态机跳转到写使能状态时,同时片选信号拉低,在cnt_byte = 0、计数器cnt_clk的第1个计数周期,是对片选信号等待时间tSLCH = 640ns的计数;在cnt_byte = 1、计数器cnt_clk的第2个计数周期,是对写使能指令写入时间进行计数;在cnt_byte = 2、计数器cnt_clk的第3个计数周期,是对片选信号等待时间tCHSH = 640ns的计数,第3个周期的计数完成后状态机跳转到两指令间等待状态(DELAY),同时片选信号拉高,计数器开始进行第4个计数周期的计数;此时cnt_byte = 3,这一计数周期是对片选信号两指令之间的等待时间tSHSL = 640ns的计数,计数完成后状态机跳转到全擦除状态(BE),片选信号再次拉低;在cnt_byte = 4、计数器cnt_clk的第5个计数周期,是对片选信号等待时间tSLCH = 640ns的计数;在cnt_byte = 5、计数器cnt_clk的第6个计数周期,是对全擦除指令写入时间进行计数;在cnt_byte = 6、计数器cnt_clk的第7个计数周期,是对片选信号等待时间tCHSH = 640ns的计数,第7个周期的计数完成后状态机跳回到初始状态(IDLE),Flash芯片的全擦除操作完成。
本实验使用的Flash芯片使用的是SPI通讯协议的模式0,即CPOL= 0,CPHA=0。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的奇数边沿,本模式中,奇数边沿为上升沿;数据更新在SCK时钟的偶数边沿,本模式中,偶数边沿为下降沿。
2.3 程序部分
flash_be_ctrl.v
module flash_be_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 define
parameter IDLE = 4'b0001 , //初始状态
WR_EN = 4'b0010 , //写状态
DELAY = 4'b0100 , //等待状态
BE = 4'b1000 ; //全擦除状态
parameter WR_EN_INST = 8'b0000_0110, //写使能指令
BE_INST = 8'b1100_0111; //全擦除指令
//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 ; //比特计数器
//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 <= 3'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == 3'd6))
cnt_byte <= 3'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 == BE) && (cnt_byte == 3'd5))
cnt_sck <= cnt_sck + 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;
//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 == 3'd2)&&(cnt_clk == 5'd31)&&(state == WR_EN))
cs_n <= 1'b1;
else if((cnt_byte == 3'd3)&&(cnt_clk == 5'd31)&&(state == DELAY))
cs_n <= 1'b0;
else if((cnt_byte == 3'd6)&&(cnt_clk == 5'd31)&&(state == BE))
cs_n <= 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 == 3'd2) && (cnt_clk == 5'd31))
state <= DELAY;
DELAY: if((cnt_byte == 3'd3) && (cnt_clk == 5'd31))
state <= BE;
BE: if((cnt_byte == 3'd6) && (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 == 3'd2))
mosi <= 1'b0;
else if((state == BE) && (cnt_byte == 3'd6))
mosi <= 1'b0;
else if((state == WR_EN)&&(cnt_byte == 3'd1)&&(cnt_sck == 2'd0))
mosi <= WR_EN_INST[7 - cnt_bit]; //写使能指令
else if((state == BE) && (cnt_byte == 3'd5) && (cnt_sck == 2'd0))
mosi <= BE_INST[7 - cnt_bit]; //全擦除指令
endmodule
tb_flash_be_ctrl.v
`timescale 1ns/1ns
module tb_flash_be_ctrl();
//wire define
wire cs_n ; //Flash片选信号
wire sck ; //Flash串行时钟
wire mosi ; //Flash主输出从输入信号
//reg define
reg sys_clk; //模拟时钟信号
reg sys_rst_n; //模拟复位信号
reg key; //模拟全擦除触发信号
//时钟、复位信号、模拟按键信号
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
key <= 1'b0;
#30
sys_rst_n <= 1'b1;
#1000
key <= 1'b1;
#20
key <= 1'b0;
end
always #10 sys_clk <= ~sys_clk; //模拟时钟,频率50MHz
//写入Flash仿真模型初始值(全F)
defparam memory.mem_access.initfile = "initmemory.txt";
//------------- flash_be_ctrl_inst -------------
flash_be_ctrl flash_be_ctrl_inst
(
.sys_clk (sys_clk ), //输入系统时钟,频率50MHz,1bit
.sys_rst_n (sys_rst_n), //输入复位信号,低电平有效,1bit
.key (key ), //按键输入信号,1bit
.sck (sck ), //输出串行时钟,1bit
.cs_n (cs_n ), //输出片选信号,1bit
.mosi (mosi ) //输出主输出从输入数据,1bit
);
//------------- 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
顶层:
module spi_flash_be
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire pi_key , //按键输入信号
output wire cs_n , //片选信号
output wire sck , //串行时钟
output wire mosi //主输出从输入数据
);
//parameter define
parameter CNT_MAX = 20'd999_999; //计数器计数最大值
//wire define
wire po_key ;
//------------- 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_be_ctrl_inst -------------
flash_be_ctrl flash_be_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
m25p16仿真模型文件已置顶,本文程序均通过仿真,上板验证,可放心食用。