SPI串口
SPI通信协议
SPI(串行外围设备接口)通讯协议,是MOTOROLA公司提供的一种同步串行接口技术,是一种高速,全双工,同步通信总线,在芯片中只占有四根管脚用来控制及数据传输。
应用:EEPROM、Flash、RTC、ADC、DSP等。
优缺点:全双工通信,通讯方式较为简单,相对数据传输速率较快;没有应答机制确认数据是否接收,在数据可靠性上有一定缺陷(与I2C相比)。
物理层
SCK:时钟信号线,用于同步通讯数据;
MOSI:主设备输出/从设备输入引脚;
MISO:主设备输入/从设备输出引脚;
-CS:片选信号线,月称为CS_N;
一主一从连接方式
一主多从连接方式
使用片选信号线进行寻址,有多少个从设备就需要有多少个片选信号线 。
当主设备想要和某一个从设备进行一个通讯时,那么就会将需要通讯的这个从设备的片选信号线由高电平置为低电平。片选信号线高电平为无效,低电平为有效。主设备将某一从设备的片选信号线由高电平置为低电平就意味着选中了这个从设备;随后主机就可以与这个被选中的从设备进行SPI通信;SPI通信是以片选信号线由高电平置为低电平为开始信号。以片选信号线被拉高作为通信的一个结束信号。
协议层
CPOL:时钟极性:从设备没有被选中时,片选信号为高电平,处于空闲状态时,串行时钟SCK的电平状态。
CPHA:时钟相位 :决定数据采样是在时钟信号线的奇数边沿,还是偶数边沿进行采样。
通信模式
模式0:空闲状态下,sck为低电平,时钟相位为0,数据采样在时钟的奇数沿进行采样,奇数沿为上升沿,在偶数沿进行数据更新,偶数沿为下降沿。
模式1:空闲状态下,sck为低电平,时钟相位为1,数据采样在时钟的偶数沿进行采样,偶数沿为下降沿,在奇数沿进行数据更新,奇数沿为上升沿。
模式2:空闲状态下,sck为高电平,时钟相位为0,数据采样在时钟的奇数沿进行采样,奇数沿为下降沿,在偶数沿进行数据更新,偶数沿为上升沿。
模式3:空闲状态下,sck为高电平,时钟相位为1,数据采样在时钟的偶数沿进行采样,偶数沿为上升沿,在奇数沿进行数据更新,奇数沿为下降沿。
通信过程
模式0:空闲状态,SCK为低电平,奇数沿(上升沿)采样,偶数沿(下降沿)更新。
主机视角
片选信号由高电平变为低电平,表示SPI通信的起始信号,当从设备在字节的片选信号,检测到下降沿起始信号之后,就知道自己被主机选中,就可以开始准备与主机他们之间的一个通讯,当片选信号为低电平时,就进行一个通信;片选信号由低电平变为高电平上升沿,这个上升沿表示SPI通信的一个停止信号,表示本次通信结束,从设备从被选中状态变为空闲状态,结束信号。
当片选信号拉低之后,就可以开始进行一个通信,SPI通信协议就使用MOSI MISO进行数据的交互,使用SCK这个信号箱进行数据的同步;MOSI MISO这两个数据线在SCK它的每一个使用周期传输一位数据,传输1bit数据;数据的输入与输出是同时进行的,数据传输时,高位在前还是低位在前没有硬性规定,但是需要保证两个设备之间使用同一个协定。就是保证两个都是高位在前,或者两个都是低位在前。
SPI协议在模式0的通讯模式下,MOSI,MISO的数据在时钟信号的一个下降沿进行输出变化,在上升沿被采样。其他时刻数据是无效的。SPI通信协议每次在进行一个数据传输的时候,可以以八位或者十六位为单位,每次传输的一个单位数是不受限制的。
全檫除
对工程进行上板验证的时候,可以通过两种方式来烧录程序
第一种方式:将生成的sof文件,将sof文件下载到fpga内部的SRAM当中,然后再进行功能的验证,掉电易丢失。
第二种方式:将程序固化到fpga外部的flash芯片当中,flash芯片是一个非易失性存储器,程序掉电之后不会丢失。 开发板重新上电之后,fpga会从flash当中读取烧录的程序,然后写到自己的SRAM当中。然后再进行程序的执行。但是flash烧录的程序不再是sof文件是jic文件。
通过sof文件生成jic文件
1.选中文件选项卡
2.找到转换程序文件打开
3.找到jic文件类型
4.选择芯片
5.选择主设备
6.选择主设备型号
7.添加sof文件
8.点击生成
9.使用jic文件,添加jic文件之后需要勾选,然后再进行下载
jic文件烧录下载之后不会显示效果,需要重新上电才会显示效果。
jic文件的优点:断电之后程序不丢失,上电之后可以直接执行固化到flash当中的一个程序。
如果想要将固化到flash芯片当中的程序删除,有两种方式。
一种方式:全檫除,flash的全檫除操作,就是将flash当中到的所有的存储空间都进行一个檫除操作,使得各个存储空间,它内部存储的一个数据都恢复到一个初始的状态。fpga要实现flash的全檫除,也可以通过两种方式。
一种方式:利用开发软件当中的下载界面,进行全檫除。
1.去掉程序下载的勾选
2.勾选檫除选项
3.然后点击start,就完成了软件进行的全檫除
另一种全檫除的方式:编写全檫除的一个程序,实现flash芯片的全檫除。
flash芯片指令
全檫除指令(BE)二进制的指令是1100_0111,十六进制C7h ,全檫除操作不需要地址字节,也不需要虚拟的一个字节,也不需要写入数据,只需要写入指令就可以了。
全檫除指令是将所有的比特位都设置为1。flash芯片在没有进行程序烧录之前,内部的数据是全1,如果烧录了一个程序,如果jic文件烧录到flash芯片当中,如果某些位置应该设置为0,就会将1设置为0,如果某些位置应该是1,就会将1保持,这样就实现了jic文件的烧录。如果要对flash芯片进行全檫除操作,需要将0为变为初始的状态全1。这样就实现了全擦除操作,全檫除操作就是将所有的比特位都设置为1。
在全檫除指令写入之前,要写入一个指令,这个指令叫作写使能指令(WREN)。首先要写入写使能指令,然后才能写入这个全檫除指令。在写使能指令被执行之后,flash芯片会被设置成写锁存的状态。写使能指令之后,就处于写锁存状态,然后处于这个状态下才能够写入全檫除指令。
全檫除指令再被写入的时候,要将片选信号拉低,然后这个指令要从MOSI(主设备输出从设备输入)端口写入,而且指令写入过程中,片选信号要始终保持低电平。在第八个比特位被锁存之后(指令写入完成),片选信号拉高。如果不执行这个操作,写完之后不拉高,全檫除指令就不会被执行。只要片选信号被拉高了,芯片就会进行全檫除的一个周期,这个周期的时间是tBE,单位是s,最大是40s,标准是17s。
执行全檫除,首先要确定写入,写使能指令;然后写使能指令执行完成之后,器件会处于写锁存状态,然后处于这个状态下才能进行一个全檫除指令的写入,而且全檫除指令在写入的时候,首先需要拉低片选信号,然后指令在写入的过程中,片选信号时钟保持低电平,指令写入完成之后并且被锁存之后,还需要拉高片选信号,片选信号拉高之后还需要等待一段时间才能完全檫除芯片,等待时间叫作全檫除的周期。
写使能指令(WREN)二进制指令是0000_0110,十六进制06h;这个指令的执行不需要地址,不需要虚拟字节,也不需要具体的数据。
写使能指令可以将器件设置为写使能锁存状态(WEL),在每一次页写操作,扇区擦除,全檫除,写状态寄存器的指令写入操作之前,都需要先进行写使能指令的写入。写使能指令写入之前,需要将片选信号拉低,然后发送指令,然后指令写入完成之后,再将片选信号拉高。
对于flash芯片来说,不论是写使能指令还是全檫除指令以及其他的一些操作指令,这些指令在写入flash芯片的时候,需要严格遵循芯片的串行输入时序。
tslh参数,tchsh参数,tshsl参数
tslch参数:片选信号激活设置的时间,表示片选信号从拉低到第一个比特位输入的这段时间,这段时间是有要求的,它的最小值是5ns,没有最大值,这个时间必须大于等于5ns。
tchsh参数:表示最后一个比特位写入完成,然后到片选信号拉高这段时间,时间参数要大于等于5ns。
tshsl参数: 表示两个指令之间的等待时间,比如全檫除操作,首先要写入一个写使能指令,写使能指令写入的时候,首先要拉低片选信号,然后指令写入完成,指令写入完成之后拉高片选信号,然后等待一段时间,然后再拉低片选信号,再写入全檫除指令。指令写入完成之后,再拉高片选信号。tshsl就是两个指令之间的等待时间,时间参数要大于等于100ns。
串行输入时序图
时钟信号(SCK)的频率
fR这个参数表示进行读操作的时候,它的使用频率最大值是20MHz,fC这个参数是除了读操作,普通读操作之外,其他的各种操作的使用频率最大只能是50MHz。
全檫除操作完整的时序图
全檫除时序
扇区
M25P16
16Mbit = 2097152 BYTE
32个扇区,每个扇区是512kbit(65536byte),32个扇区共8192页,每个扇区256页,每一页是256byte
地址举例,27扇区,起始地址1B0000h 到 1BFFFFh;S(扇区地址)+P(页地址)+B(字节)
三字节地址数据,1B(27)扇区地址,00(00)页地址,00(00)字节 到 1B(27)扇区地址,FF(255)页地址,FF(255)字节地址。
每一页都可以被单独的进行写,写的时候是将这个比特位从1编码到0,就是需要写入0,原来默认值是1,写入0就需要将1变为0;如果要写入1,就不需要改变,因为原来就是1。
这个器件只能被进行一个扇区檫除或者全檫除,檫除的时候是比特位从0变为1,就是所数据写入的时候写入了0,写入0的时候是将1变为了0,然后1还是1不变;进行檫除的时候变为0的就要檫除为1,变回原来的1,原来为1的就不需要改变。 不能进行页檫除,檫除的最小单位就是扇区檫除。
全檫除范围为16Mbit,会将芯片所有的存储空间进行檫除;扇区檫除范围为512Kbit,一个扇区就是512kbit。
扇区檫除会将选择的扇区,所有的比特位都设置为1,就是全f;在这个指令写入之前,需要写入一个写使能指令,在写使能指令被传入之后,设备(flash芯片)会被设置为写使能锁存状态,指令被写入的时候,首先要将片选信号拉低,随后写入指令的编码,还要再写三个字节的地址,从mosi写入。地址是扇区内的任意地址就可以;指令写入和地址写入的时候,片选信号要拉低。地址全部写入完成之后,拉高片选信号,如果不拉高片选信号,扇区檫除的指令就不会被执行,只要片选信号被拉高了,这时候就进入一个周期,这个周期就是扇区檫除的一个周期,时间为tse。扇区檫除的时间是1s到3s。
扇区檫除时序图
扇区檫除实验的完整时序
扇区檫除的实验目标:首先向flash当中烧录一个流水灯程序,程序在烧录过程中,它会按照flash的地址顺序进行烧录,然后在编写扇区檫除工程,然后使用扇区檫除工程檫除掉流水灯程序所占用的某一个扇区这样这个程序就不完整了,就不能进行正常的工作了。重新上电之后,流水灯不工作,就表示程序檫除成功。
代码模块
控制模块代码
module flash_be (
input clk ,
input rst_n ,
input key_flag ,
output sck ,
output cs_n ,
output mosi
);
// 定义参数
parameter TIME_CLK = 6'd32;
parameter TIME_BYTE = 3'd7;
parameter TIME_SCK = 3'd4;
parameter TIME_BIT = 4'd8;
// 定义状态机
parameter IDLE = 4'B0001,
WREN = 4'B0010,
DELAY = 4'B0100,
BE = 4'B1000;
// 定义指令
parameter WREN_IN = 8'b0000_0110;
parameter BE_IN = 8'b1100_0111;
// 定义状态机跳转条件
wire idle2wren ;
wire wren2delay ;
wire delay2be ;
wire be2idle ;
// 状态机
reg[3:0] state_c;
reg[3:0] state_n;
// 参数计数器,计数每个字节参数的时间
reg[5:0] cnt_clk;
wire add_cnt_clk;
wire end_cnt_clk;
// 字节计数器,计数发送多少个字节
reg[2:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
// 时钟计数器,计数sck时钟跳转
reg[2:0] cnt_sck;
wire add_cnt_sck;
wire end_cnt_sck;
// 比特计数器,计数传输多少个比特数
reg[2:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
// 输出
reg cs_n_r;
reg mosi_r;
reg sck_r;
// 现态次态
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(idle2wren)begin
state_n = WREN;
end
else begin
state_n = IDLE;
end
end
WREN :begin
if(wren2delay)begin
state_n = DELAY;
end
else begin
state_n = WREN;
end
end
DELAY:begin
if(delay2be)begin
state_n = BE;
end
else begin
state_n = DELAY;
end
end
BE :begin
if(be2idle)begin
state_n = IDLE;
end
else begin
state_n = BE;
end
end
default: state_n = IDLE;
endcase
end
// 状态机跳转条件
assign idle2wren = (state_c == IDLE) && (key_flag);// 初始状态下 ,按键按下
assign wren2delay = (state_c == WREN) && (cnt_byte == 2 && cnt_clk == 31);// 写使能状态下,字节计数器计数到2,时钟参数计数到最大值
assign delay2be = (state_c == DELAY) && (cnt_byte == 3 && cnt_clk == 31);// 等待状态下,字节计数器计数到3,时钟参数计数到最大值
assign be2idle = (state_c == BE) && (cnt_byte == 6 && cnt_clk == 31);// 檫除指令状态下,字节计数器计数到6,时钟参数计数到最大值
//时钟参数计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_clk <= 0;
end
else if(add_cnt_clk)begin
if(end_cnt_clk)begin
cnt_clk <= 0;
end
else begin
cnt_clk <= cnt_clk + 1;
end
end
end
assign add_cnt_clk = state_c != IDLE; // 不在初始状态下,开始计数
assign end_cnt_clk = add_cnt_clk && cnt_clk == TIME_CLK - 1; // 计数到最大值
// 字节计数器
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 = end_cnt_clk;// 时钟参数计数到最大值
assign end_cnt_byte = add_cnt_byte && cnt_byte == TIME_BYTE - 1; // 字节计数器计数到最大值
// 时钟计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_sck <= 0;
end
else if(add_cnt_sck)begin
if(end_cnt_sck)begin
cnt_sck <= 0;
end
else begin
cnt_sck <= cnt_sck + 1;
end
end
end
assign add_cnt_sck = (state_c == WREN && cnt_byte == 1) || (state_c == BE && cnt_byte == 5);// 在写使能状态下,字节计数器到1,檫除状态下,字节计数到5
assign end_cnt_sck = add_cnt_sck && cnt_sck == TIME_SCK - 1;// 时钟计数器计数到最大值
// 比特计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = cnt_sck == 2;// 时钟计数器等于2
assign end_cnt_bit = add_cnt_bit && cnt_bit == TIME_BIT - 1;// 比特计数到最大值
// 片选信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cs_n_r <= 1;
end
else if(key_flag)begin
cs_n_r <= 0;// 按键按下,片选信号拉低
end
else if(state_c == WREN && cnt_byte == 2 && cnt_clk == 31)begin
cs_n_r <= 1; // 写使能状态下,字节计时器计数到2,时钟参数计时器到最大值,片选信号拉高
end
else if(state_c == DELAY && cnt_byte == 3 && cnt_clk == 31)begin
cs_n_r <= 0;// 等待状态下,字节计数到3,时钟参数计数器到最大值,片选信号拉低
end
else if(state_c == BE && cnt_byte == 6 && cnt_clk == 31)begin
cs_n_r <= 1;// 全檫除状态下,字节计数器到6,时钟参数计数器到最大值,片选信号拉高
end
end
// fpga发送给flash的数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
mosi_r <= 0;
end
else if(state_c == WREN && cnt_byte == 2)begin
mosi_r <= 0; // 写使能状态下,字节字节计数器到2,输出0
end
else if(state_c == BE && cnt_byte == 6)begin
mosi_r <= 0;// 檫除状态下,字节计数到6,输出0
end
else if(state_c == WREN && cnt_byte == 1 && cnt_sck == 0)begin
mosi_r <= WREN_IN[7 - cnt_bit];// 写使能状态下,字节计数到1,时钟参数为0,发送写使能指令
end
else if(state_c == BE && cnt_byte == 5 && cnt_sck == 0)begin
mosi_r <= BE_IN[7 - cnt_bit];// 檫除状态下,字节计数到5,时钟参数为0,发送檫除指令
end
end
// 时钟线信号,时钟频率为12.5mhz
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sck_r <= 0;
end
else if(cnt_sck == 0)begin
sck_r <= 0;
end
else if(cnt_sck == 2)begin
sck_r <= 1;
end
end
// 输出信号赋值
assign cs_n = cs_n_r;
assign mosi = mosi_r;
assign sck = sck_r;
endmodule
按键消抖模块
module key_filter (
input sys_clk,
input sys_rst_n,
input key_in,
output reg key_flag
);
// 定义20ns计数器
reg [19:0] cnt;
// 定义20ns参数
parameter MAX =20'd999_999 ;
// 20ns计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
cnt <= 20'd0;
end
else if(key_in == 1'b1)begin
cnt <= 20'b0;
end
else if(cnt == MAX)begin
cnt <= MAX;
end
else begin
cnt <= cnt+20'b1;
end
end
// 输出信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
key_flag <= 1'b0;
end
else if(cnt == MAX-20'b1) begin
key_flag <= 1'b1;
end
else begin
key_flag <= 1'b0;
end
end
endmodule
顶层模块
module top (
input clk ,
input rst_n ,
input key_in ,
input miso ,
output cs_n ,
output mosi ,
output sck
);
wire key_flag;
// 模块例化
key_filter u_key_filter(
/* input */ .sys_clk (sys_clk ) ,
/* input */ .sys_rst_n (sys_rst_n) ,
/* input */ .key_in (key_in ) ,
/* output reg */ .key_flag (key_flag )
);
flash_be u_flash_be(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input */ .key_flag (key_flag) ,
/* output */ .sck (sck ) ,
/* output */ .cs_n (cs_n ) ,
/* output */ .mosi (mosi )
);
endmodule
仿真模块
`timescale 1ns/1ns
module flash_be_tb ();
reg clk;
reg rst_n;
reg key_flag;
wire sck;
wire cs_n;
wire mosi;
// 模块例化
flash_be u_flash_be(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input */ .key_flag (key_flag) ,
/* output */ .sck (sck ) ,
/* output */ .cs_n (cs_n ) ,
/* output */ .mosi (mosi )
);
parameter CLKPERO = 20;
// 模拟复位信号,按键输入信号
initial begin
rst_n <= 0;
key_flag <= 0;
#(CLKPERO * 5);
rst_n = 1;
#(CLKPERO * 50);
key_flag = 1;
#(CLKPERO);
key_flag = 0;
end
// 模拟时钟
initial clk = 1;
always #(CLKPERO / 2) clk = ~clk;
endmodule
仿真验证
flash数据读操作
flash数据读操作有两种方式
一种是普通读操作
一种是快读操作:比普通读操作读数据更快些。
要执行数据读操作,首先需要将片选信号拉低,片选信号拉低之后,需要写入读操作命令,在读指令写入之后,后面还要跟上三个字节的地址,这个地址是你想要读取那个位置的地址。指令和地址的每一个比特位都会被锁存,在串行时钟信号的上升沿。存储在改地址的数据会输出,在miso端口输出。每一个比特位被输出的时候,都需要在时钟的同步下,这个时钟的频率最高是fR,fR不能超过20Mhz。每一个比特位都会在串行时钟的下降沿进行更新。
要执行数据读操作,首先拉低片选信号,然后写入读指令,跟随读指令写入三个字节的地址,这个地址是想要读取数据的地址,然后指令和地址会在串行时钟的上升沿被锁存,当指令和地址被锁存之后,然后再Q端口会输出想要读取的数据。普通读模式的时钟频率是要小于等于20Mhz,然后每一个比特位会在时钟信号的下降沿进行数据更新。
读操作命令(READ):二进制0000_0011,十六进制03h,后面需要三个字节地址,数据读操作最低要读出一个字节的数据。
读数据时序
这个地址可以是芯片内部的任何一个有效地址,当上一个地址数据被读取完成之后,这个地址会自动增加到下一个更高的一个地址;这样就表示,使用读操作命令可以读取整个存储器的数据,当最高位的一个地址被读取完成之后,这个地址会归0,回到最初的一个地址,回到首地址;开始重新进行数据读取,这个循环会一直延续下去,如果想要停止数据读取的操作,就需要将片选信号最次拉高,再次拉高片选信号就可以停止数据的读取。否则数据读操作会对这个芯片进行无限循环的读操作。
可以随时拉高片选信号来停止对数据的读取,任何一个数据的读命令,在进行全檫除或者编程或者写操作的时候,任何读操作都不能影响上面正在执行的这个循环,也就是说当正在进行全檫除,编程,写操作的时候,这个读操作是不能进行的。
实验目标:结合数据读操作的相关内容以及时序图,来设计一个flash数据读控制器来读取,烧录到flash芯片当中的一个流水灯的程序,读取一部分内容,与jic文件进行对比,来验证flash读数据工程能否正常运行。
整体框图
代码编写
读控制模块代码
module flash_read_ctrl (
input clk ,
input rst_n ,
input key_flag ,
input miso ,
output cs_n ,
output sck ,
output mosi ,
output[7:0] pi_data ,
output pi_flag
);
parameter WAIT_MAX = 16'd60_000;
parameter DATA_NUM = 'd256;
parameter TIME_CLK = 5'd31;
parameter TIME_SCK = 2'd3;
parameter TIME_BIT = 3'd7;
// 定义指令参数
parameter READ_INST = 8'b0000_0011,
S_ADDR = 8'b0000_0000,
P_ADDR = 8'b0000_0000,
B_ADDR = 8'b0000_0000;
parameter IDLE = 3'b001,
READ = 3'b010,
SEND = 3'b100;
wire idle2read;
wire read2send;
wire send2idle;
reg[2:0] state_c;
reg[2:0] state_n;
reg[4:0] cnt_clk;
wire add_cnt_clk;
wire end_cnt_clk;
reg[15:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
reg[1:0] cnt_sck;
wire add_cnt_sck;
wire end_cnt_sck;
reg[2:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
reg miso_flag;
reg[7:0] data;
reg[7:0] data_r;
reg flag_r;
reg wr_en;
reg rd_en;
reg[15:0] cnt_wait;
wire add_cnt_wait;
wire end_cnt_wait;
reg[15:0] rd_num;
reg vld;
wire[8:0] fifo_num;
// 输出信号
reg cs_n_r;
reg mosi_r;
reg sck_r;
reg pi_flag_r;
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(idle2read)begin
state_n = READ;
end
else begin
state_n = IDLE;
end
end
READ:begin
if(read2send)begin
state_n = SEND;
end
else begin
state_n = READ;
end
end
SEND:begin
if(send2idle)begin
state_n = IDLE;
end
else begin
state_n = SEND;
end
end
default: state_n = IDLE;
endcase
end
assign idle2read = (state_c == IDLE) && key_flag;
assign read2send = (state_c == READ) && (cnt_byte == DATA_NUM + 3) && (cnt_clk == 31);
assign send2idle = (state_c == SEND) && (rd_num == DATA_NUM) && (cnt_wait == WAIT_MAX - 1);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_clk <= 0;
end
else if(add_cnt_clk)begin
if(end_cnt_clk)begin
cnt_clk <= 0;
end
else begin
cnt_clk <= cnt_clk + 1;
end
end
end
assign add_cnt_clk = (state_c == READ);
assign end_cnt_clk = add_cnt_clk && cnt_clk == TIME_CLK;
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 = end_cnt_clk;
assign end_cnt_byte = add_cnt_byte && cnt_byte == DATA_NUM + 3;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_sck <= 0;
end
else if(add_cnt_sck)begin
if(end_cnt_sck)begin
cnt_sck <= 0;
end
else begin
cnt_sck <= cnt_sck + 1;
end
end
end
assign add_cnt_sck = state_c == READ;
assign end_cnt_sck = add_cnt_sck && cnt_sck == TIME_SCK;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = cnt_sck == 2;
assign end_cnt_bit = add_cnt_bit && cnt_bit == TIME_BIT;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
miso_flag <= 0;
end
else if(cnt_byte >= 4 && cnt_sck ==1)begin
miso_flag <= 1;
end
else begin
miso_flag <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data <= 0;
end
else if(miso_flag)begin
// data <= {data[6:0],miso};
data <= {miso,data[7:1]};
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
flag_r <= 0;
end
else if(cnt_bit == 7 && miso_flag)begin
flag_r <= 1;
end
else begin
flag_r <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data_r <= 0;
end
else if(flag_r) begin
data_r <= data;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
wr_en <= 0;
end
else begin
wr_en <= flag_r;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_en <= 0;
end
else if(cnt_wait == WAIT_MAX -1 && rd_num < DATA_NUM)begin
rd_en<= 1;
end
else begin
rd_en <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_wait <= 0;
end
else if(!vld)begin
cnt_wait <= 0;
end
else if(add_cnt_wait)begin
if(end_cnt_wait)begin
cnt_wait <= 0;
end
else begin
cnt_wait <= cnt_wait + 1;
end
end
end
assign add_cnt_wait = vld;
assign end_cnt_wait = add_cnt_wait && cnt_wait == WAIT_MAX -1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rd_num <= 0;
end
else if(vld ==0)begin
rd_num <= 0;
end
else if(rd_en)begin
rd_num <= rd_num + 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
vld <= 0;
end
else if((rd_num == DATA_NUM) && (cnt_wait == WAIT_MAX-1))begin
vld <= 0;
end
else if(fifo_num == DATA_NUM)begin
vld <= 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cs_n_r <= 1;
end
else if(key_flag)begin
cs_n_r <= 0;
end
else if((cnt_clk == 31) && (cnt_byte == DATA_NUM +3) && (state_c == READ))begin
cs_n_r <= 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
mosi_r <= 0;
end
else if((state_c == READ) && (cnt_byte == 4))begin
mosi_r <= 0;
end
else if((state_c == READ) && (cnt_byte == 0) && (cnt_sck == 0))begin
mosi_r <= READ_INST[7 - cnt_bit];
end
else if((state_c == READ) && (cnt_byte == 1) && (cnt_sck == 0))begin
mosi_r <= S_ADDR[7 - cnt_bit];
end
else if((state_c == READ) && (cnt_byte == 2) && (cnt_sck == 0))begin
mosi_r <= P_ADDR[7 - cnt_bit];
end
else if((state_c == READ) && (cnt_byte == 3) && (cnt_sck == 0))begin
mosi_r <= B_ADDR[7 - cnt_bit];
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sck_r <= 0;
end
else if(cnt_sck == 0)begin
sck_r <= 0;
end
else if(cnt_sck == 2)begin
sck_r <= 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
pi_flag_r <= 0;
end
else begin
pi_flag_r <= rd_en;
end
end
assign cs_n = cs_n_r;
assign mosi = mosi_r;
assign sck = sck_r;
assign pi_flag = pi_flag_r;
fifo_data fifo_data_inst (
.clock ( clk ),
.data ( data_r ),
.rdreq ( rd_en ),
.wrreq ( wr_en ),
.q ( pi_data ),
.usedw ( fifo_num )
);
endmodule
按键消抖代码
module key_filter (
input sys_clk,
input sys_rst_n,
input key_in,
output reg key_flag
);
reg [19:0] cnt;
parameter MAX =20'd999_999 ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
cnt <= 20'd0;
end
else if(key_in == 1'b1)begin
cnt <= 20'b0;
end
else if(cnt == MAX)begin
cnt <= MAX;
end
else begin
cnt <= cnt+20'b1;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
key_flag <= 1'b0;
end
else if(cnt == MAX-20'b1) begin
key_flag <= 1'b1;
end
else begin
key_flag <= 1'b0;
end
end
endmodule
uart接收模块代码
module uart_tx(
input clk,
input rst_n,
input[7:0] tx_data,
input tx_data_vld,
// output busy,
output txd
);
// 定义一个在9600波特率下传输1比特所需要的5208周期
parameter BUAD = 'd5208;
// 定义传输10bit数据
parameter BYTE = 'd10;
// 定义波特计数器,计数1bit传输所需要的5028周期
reg[12:0] cnt_buad;
wire add_cnt_baud;
wire end_cnt_baud;
// 将传入的数据加上起始位和结束位进行寄存
reg[9:0] tx_data_r;
// 定义比特计数器,计数传输了几比特数据
reg[3:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
// 使能拉高的区间,采集下一个周期到下一帧数据传输的开始
reg tx_flag;
// 将接收到的并行数据转成串行传出
reg txd_r;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_flag <= 0;
end
else if(tx_data_vld)begin // 当数据有效使能拉高时,数据采集区间拉高
tx_flag <= 1;
end
else if(end_cnt_byte)begin // 当数据采集结束之后,数据采集区间拉低
tx_flag <= 0;
end
end
assign busy = tx_flag; // 将数据采集区间赋值给busy,传给控制模块,作为fifo读数据的条件,当不采集数据,并且数据不为空时,读请求拉高
// 波特计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_buad <= 0;
end
else if(add_cnt_baud)begin
if(end_cnt_baud)begin
cnt_buad <= 0;
end
else begin
cnt_buad <= cnt_buad + 1;
end
end
end
assign add_cnt_baud = tx_flag;// 在数据采集区间,波特计数器开始计数
assign end_cnt_baud = add_cnt_baud && cnt_buad == BUAD - 1;// 当计数到5208个周期之后,结束计数
// 比特计数器
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 = end_cnt_baud;// 当波特计数器完成计数时,比特计数器,开始计数
assign end_cnt_byte = add_cnt_byte && cnt_byte == BYTE - 1;// 当比特计数器计数到10bit时,结束计数
// 将传入的数据进行起始位和结束位的拼接
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data_r <= 10'hfff;
end
else if(tx_data_vld)begin // 当对一帧数据采集完成之后,对那一帧数据添加起始位和结束位
tx_data_r <= {1'b1,tx_data,1'b0};
end
end
// 将传入的并行数据转成串行数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
txd_r <= 1;
end
else if(tx_flag) begin
txd_r <= tx_data_r[cnt_byte];
end
else if(end_cnt_byte)begin
txd_r <= 0;
end
end
assign txd = txd_r;// 将转换成的串行数据输出
endmodule
// module uart_tx
// #(
// parameter UART_BPS = 'd9600, //串口波特率
// parameter CLK_FREQ = 'd50_000_000 //时钟频率
// )
// (
// input wire sys_clk , //系统时钟50MHz
// input wire sys_rst_n , //全局复位
// input wire [7:0] pi_data , //模块输入的8bit数据
// input wire pi_flag , //并行数据有效标志信号
// output reg tx //串转并后的1bit数据
// );
//
// //\* Parameter and Internal Signal \//
//
// //localparam define
// localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
// //reg define
// reg [12:0] baud_cnt;
// reg bit_flag;
// reg [3:0] bit_cnt ;
// reg work_en ;
//
// //\* Main Code \//
//
// //work_en:接收数据工作使能信号
// always@(posedge sys_clk or negedge sys_rst_n)
// if(sys_rst_n == 1'b0)
// work_en <= 1'b0;
// else if(pi_flag == 1'b1)
// work_en <= 1'b1;
// else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
// work_en <= 1'b0;
// //baud_cnt:波特率计数器计数,从0计数到5207
// always@(posedge sys_clk or negedge sys_rst_n)
// if(sys_rst_n == 1'b0)
// baud_cnt <= 13'b0;
// else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
// baud_cnt <= 13'b0;
// else if(work_en == 1'b1)
// baud_cnt <= baud_cnt + 1'b1;
// //bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
// always@(posedge sys_clk or negedge sys_rst_n)
// if(sys_rst_n == 1'b0)
// bit_flag <= 1'b0;
// else if(baud_cnt == 13'd1)
// bit_flag <= 1'b1;
// else
// bit_flag <= 1'b0;
// //bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
// always@(posedge sys_clk or negedge sys_rst_n)
// if(sys_rst_n == 1'b0)
// bit_cnt <= 4'b0;
// else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
// bit_cnt <= 4'b0;
// else if((bit_flag == 1'b1) && (work_en == 1'b1))
// bit_cnt <= bit_cnt + 1'b1;
// //tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
// always@(posedge sys_clk or negedge sys_rst_n)
// if(sys_rst_n == 1'b0)
// tx <= 1'b1; //空闲状态时为高电平
// else if(bit_flag == 1'b1)
// case(bit_cnt)
// 0 : tx <= 1'b0;
// 1 : tx <= pi_data[0];
// 2 : tx <= pi_data[1];
// 3 : tx <= pi_data[2];
// 4 : tx <= pi_data[3];
// 5 : tx <= pi_data[4];
// 6 : tx <= pi_data[5];
// 7 : tx <= pi_data[6];
// 8 : tx <= pi_data[7];
// 9 : tx <= 1'b1;
// default : tx <= 1'b1;
// endcase
// endmodule
顶层模块代码
module top (
input clk ,
input rst_n ,
input key_in ,
input miso ,
output cs_n ,
output mosi ,
output sck ,
output txd
);
wire key_flag;
wire[7:0] pi_data;
wire pi_flag;
key_filter u_key_filter(
/* input */ .sys_clk (clk),
/* input */ .sys_rst_n (rst_n),
/* input */ .key_in (key_in),
/* output reg */ .key_flag (key_flag)
);
flash_read_ctrl u_flash_read_ctrl(
/* input */ .clk (clk ),
/* input */ .rst_n (rst_n ),
/* input */ .key_flag (key_flag),
/* input */ .miso (miso ),
/* output */ .cs_n (cs_n ),
/* output */ .sck (sck ),
/* output */ .mosi (mosi ),
/* output[7:0] */ .pi_data (pi_data ),
/* output */ .pi_flag (pi_flag )
);
uart_tx u_uart_tx(
/* input */ .clk (clk ),
/* input */ .rst_n (rst_n ),
/* input[7:0] */ .tx_data (pi_data ),
/* input */ .tx_data_vld (pi_flag),
// /* output */ .busy (busy ),
/* output */ .txd (txd )
);
endmodule
仿真模块代码
`timescale 1ns/1ns
module flash_read_ctrl_tb ();
reg clk;
reg rst_n;
reg key_flag;
wire sck;
wire cs_n;
wire mosi;
wire[7:0] pi_data;
wire pi_flag;
wire miso;
defparam memory.mem_access.initfile = "initmemory.txt";
defparam u_flash_read_ctrl.WAIT_MAX = 100;
flash_read_ctrl u_flash_read_ctrl(
/* input */ .clk (clk ),
/* input */ .rst_n (rst_n ),
/* input */ .key_flag (key_flag),
/* input */ .miso (miso ),
/* output */ .cs_n (cs_n ),
/* output */ .sck (sck ),
/* output */ .mosi (mosi ),
/* output[7:0] */ .pi_data (pi_data ),
/* output */ .pi_flag (pi_flag )
);
m25p16 memory(
.c (sck),
.data_in (mosi),
.s (cs_n),
.w (1'b1),
.hold (1'b1),
.data_out (miso)
);
parameter CLKPERO = 20;
initial begin
rst_n <= 0;
key_flag <= 0;
#(CLKPERO * 5);
rst_n = 1;
#(CLKPERO * 50);
key_flag = 1;
#(CLKPERO);
key_flag = 0;
end
initial clk = 1;
always #(CLKPERO / 2) clk = ~clk;
endmodule
上板验证实验效果
数据写操作
flash 数据写操作有两种方式
一种是页写操作(页编程)
一种是连续写
页写操作(页编程)
页编程允许将字节编程到存储器当中,它编程的方式是将比特位从1变为0;在进行页编程的操作的时候,首先要进行一个全檫除或者扇区檫除,就是把需要写入那个位置,它的一个存储位置进行檫除,因为只有经过檫除之后,那么内部的数据会变为全1,变为全1之后,随后进行页编程(页写),页编程操作会将1变为0;如果这个位置需要写入0,就会把原来的1变为0,如果想要写入1,就保持原来的1不变,如果之前的flash芯片没有经过檫除,那么它有的位置存储的是0,有的位置存储的是1,因为之前已经写入过数据了;如果这个情况下执行页写操作,原来为0的地方,要写入0没问题,但是原来为0要写入1就不行。因为页写操作只能将1变为0,或者保持1的状态,不能将0变为1,这样就会造成数据写入错误。所以在进行页写操作的时候,一定要写入的这个区域进行檫除操作,把数据全部变为1。这样才能进行数据写入。
在进行页写操作之前,首先要进行一个指令的写入,要写入一个写使能指令,这个写使能指令写入之后,它会将这个flash设备变为一个写使能锁存状态,wel写使能锁存状态。页编程指令被写入的时候,首先把片选信号拉低,要进行页编程指令的写入,第一步要拉低片选信号,随后写入指令的编码,然后指令编码后面有三个字节的地址,这个地址是扇区地址,页地址,字节地址;地址之后还要跟着至少一个字节的数据,这个数据就是想要写入的数据。写入的位置就是刚刚写入的地址位置。指令以及地址以及数据是从串行数据输入端口输入(mosi端口),如果八个最低的有效地址比特位并不是全0,因为地址比特位是24个比特位A23-A0,A23-A16扇区地址,A15-A8页地址,A7-A0字节地址。八个最低的有效地址位并不是全0,就是字节地址不是全0,这时候如果说传输的数据,它超过了最后得一个地址位,就是本页的最后一个地址位,这时候就需要从最初的一个地址,在同一页的最初的一个地址进行数据的编程,最初的地址就是八个最低有效地址位的数据都为0的时候的地址位。超出最高地址的数据,它会按照顺序以本页的0地址位起始,按顺序将超出的数据写入。
如果超过256字节的数据被发送给设备,也就是发送了超过256个字节的数据来个设备,因为页写操作只能对每一页进行数据的操作,但是每一页最多写入256个数据,如果发送的数据超过了256个数据;最初被锁存的数据将会被丢弃,并且最后得256个数据,会被正确的编程写入到存储器当中,在同一页。数据溢出的部分会覆盖之前被锁存的数据。 如果传输到设备的数据少于256这时候数据可以正确被编程在它所请求的地址上,不会有任何的影响。对其他本页的其他地址的字节。数据少于256,这时候,这个数据会被正确的写入,想要请求的那个地址当中,并且对其他的位置的数据没有任何影响。
片选信号一定要拉高,在最后一个数据第八位已经被锁存的时候,一定要拉高。数据写入完成之后,一定要拉高片选信号,否则页编程指令将不会被执行。数据写入完成之后,要拉高片选信号,否则指令不会被执行,数据不会被写入 。片选信号被拉高了,这个时候就会进入一个页编程周期,这个周期的参数是tpp,标准值是1.4ms,最大值为5ms。
页编程操作时序图
在进行页编程操作之前,首先要对想要写入的位置进行檫除,页编程操作之前也要写入写使能指令,使设备处于写使能锁存状态,才能进行指令的写入。
页编程操作整体时序图
实验目标
使用页写指令,向flash当中写入数据,数据个数定义为n,n设置为100或270两种情况。写入地址也有两种情况,一种是当前页首地址(00_04_00),一种是当前页的中间位置的地址(00_04_c8),然后使用读控制器来读出写入数据,观察这个数据是否正确写入。
代码模块
页编程控制模块
module flash_pp (
input clk ,
input rst_n ,
input key_flag ,
output sck ,
output cs_n ,
output mosi
);
parameter TIME_CLK = 6'd31;
parameter TIME_BYTE = 4'd9;
parameter TIME_SCK = 3'd3;
parameter TIME_BIT = 4'd7;
parameter DATA_NUM = 'd100;
parameter IDLE = 4'B0001,
WREN = 4'B0010,
DELAY = 4'B0100,
PP = 4'B1000;
parameter WREN_IN = 8'b0000_0110;
parameter PP_IN = 8'b0000_0010;
parameter S_ADDR = 8'b0000_0000;
parameter P_ADDR = 8'b0000_0000;
parameter B_ADDR = 8'b0000_0000;
wire idle2wren ;
wire wren2delay ;
wire delay2pp ;
wire pp2idle ;
// 状态机
reg[3:0] state_c;
reg[3:0] state_n;
// 参数计数器,计数每个字节参数的时间
reg[5:0] cnt_clk;
wire add_cnt_clk;
wire end_cnt_clk;
// 字节计数器,计数发送多少个字节
reg[15:0] cnt_byte;
wire add_cnt_byte;
wire end_cnt_byte;
// 时钟计数器,计数sck时钟跳转
reg[2:0] cnt_sck;
wire add_cnt_sck;
wire end_cnt_sck;
// 比特计数器,计数传输多少个比特数
reg[2:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
reg[7:0] data;
// 输出
reg cs_n_r;
reg mosi_r;
reg sck_r;
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(idle2wren)begin
state_n = WREN;
end
else begin
state_n = IDLE;
end
end
WREN :begin
if(wren2delay)begin
state_n = DELAY;
end
else begin
state_n = WREN;
end
end
DELAY:begin
if(delay2pp)begin
state_n = PP;
end
else begin
state_n = DELAY;
end
end
PP :begin
if(pp2idle)begin
state_n = IDLE;
end
else begin
state_n = PP;
end
end
default: state_n = IDLE;
endcase
end
assign idle2wren = (state_c == IDLE) && (key_flag);
assign wren2delay = (state_c == WREN) && (cnt_byte == 2 && cnt_clk == 31);
assign delay2pp = (state_c == DELAY) && (cnt_byte == 3 && cnt_clk == 31);
assign pp2idle = (state_c == PP) && (cnt_byte == DATA_NUM + 9 && cnt_clk == 31);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_clk <= 0;
end
else if(add_cnt_clk)begin
if(end_cnt_clk)begin
cnt_clk <= 0;
end
else begin
cnt_clk <= cnt_clk + 1;
end
end
end
assign add_cnt_clk = state_c != IDLE;
assign end_cnt_clk = add_cnt_clk && cnt_clk == TIME_CLK;
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 = end_cnt_clk;
assign end_cnt_byte = add_cnt_byte && cnt_byte == TIME_BYTE + DATA_NUM;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_sck <= 0;
end
else if(add_cnt_sck)begin
if(end_cnt_sck)begin
cnt_sck <= 0;
end
else begin
cnt_sck <= cnt_sck + 1;
end
end
end
assign add_cnt_sck = (state_c == WREN && cnt_byte == 1) || (state_c == PP && cnt_byte >= 5 && (cnt_byte <= 9 + DATA_NUM - 1));
assign end_cnt_sck = add_cnt_sck && cnt_sck == TIME_SCK;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
end
assign add_cnt_bit = cnt_sck == 2;
assign end_cnt_bit = add_cnt_bit && cnt_bit == TIME_BIT;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cs_n_r <= 1;
end
else if(key_flag)begin
cs_n_r <= 0;
end
else if(state_c == WREN && cnt_byte == 2 && cnt_clk == 31)begin
cs_n_r <= 1;
end
else if(state_c == DELAY && cnt_byte == 3 && cnt_clk == 31)begin
cs_n_r <= 0;
end
else if(state_c == PP && cnt_byte == (9 + DATA_NUM) && cnt_clk == 31)begin
cs_n_r <= 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
mosi_r <= 0;
end
else if(state_c == WREN && cnt_byte == 2)begin
mosi_r <= 0;
end
else if(state_c == PP && cnt_byte == (9 + DATA_NUM))begin
mosi_r <= 0;
end
else if(state_c == WREN && cnt_byte == 1 && cnt_sck == 0)begin
mosi_r <= WREN_IN[7 - cnt_bit];
end
else if(state_c == PP && cnt_byte == 5 && cnt_sck == 0)begin
mosi_r <= PP_IN[7 - cnt_bit];
end
else if(state_c == PP && cnt_byte == 6 && cnt_sck == 0)begin
mosi_r <= S_ADDR[7 - cnt_bit];
end
else if(state_c == PP && cnt_byte == 7 && cnt_sck == 0)begin
mosi_r <= P_ADDR[7 - cnt_bit];
end
else if(state_c == PP && cnt_byte == 8 && cnt_sck == 0)begin
mosi_r <= B_ADDR[7 - cnt_bit];
end
else if(state_c == PP && (cnt_byte >= 9) && (cnt_byte <= 9 + DATA_NUM - 1) && cnt_sck == 0)begin
mosi_r <= data[7 - cnt_bit];
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data <= 0;
end
else if( (cnt_byte > 9 + 256 -1) && (cnt_clk == 1))begin
data <= 16'haa;
end
else if((cnt_byte >= 9) && (cnt_byte < 9 + DATA_NUM -1) && (cnt_clk == 1))begin
data <= data + 1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
sck_r <= 0;
end
else if(cnt_sck == 0)begin
sck_r <= 0;
end
else if(cnt_sck == 2)begin
sck_r <= 1;
end
end
assign cs_n = cs_n_r;
assign mosi = mosi_r;
assign sck = sck_r;
endmodule
按键消抖模块
module key_filter (
input sys_clk,
input sys_rst_n,
input key_in,
output reg key_flag
);
reg [19:0] cnt;
parameter MAX =20'd999_999 ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
cnt <= 20'd0;
end
else if(key_in == 1'b1)begin
cnt <= 20'b0;
end
else if(cnt == MAX)begin
cnt <= MAX;
end
else begin
cnt <= cnt+20'b1;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
key_flag <= 1'b0;
end
else if(cnt == MAX-20'b1) begin
key_flag <= 1'b1;
end
else begin
key_flag <= 1'b0;
end
end
endmodule
顶层模块
module top (
input clk ,
input rst_n ,
input key_in ,
input miso ,
output cs_n ,
output mosi ,
output sck
);
wire key_flag;
key_filter u_key_filter(
/* input */ .sys_clk (sys_clk ) ,
/* input */ .sys_rst_n (sys_rst_n) ,
/* input */ .key_in (key_in ) ,
/* output reg */ .key_flag (key_flag )
);
flash_pp u_flash_pp (
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input */ .key_flag (key_flag) ,
/* output */ .sck (sck ) ,
/* output */ .cs_n (cs_n ) ,
/* output */ .mosi (mosi )
);
endmodule
仿真模块
module top (
input clk ,
input rst_n ,
input key_in ,
input miso ,
output cs_n ,
output mosi ,
output sck
);
wire key_flag;
key_filter u_key_filter(
/* input */ .sys_clk (sys_clk ) ,
/* input */ .sys_rst_n (sys_rst_n) ,
/* input */ .key_in (key_in ) ,
/* output reg */ .key_flag (key_flag )
);
flash_pp u_flash_pp (
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input */ .key_flag (key_flag) ,
/* output */ .sck (sck ) ,
/* output */ .cs_n (cs_n ) ,
/* output */ .mosi (mosi )
);
endmodule