一、设计规范
1、设计框图
为了实现对M25P16(FLASH)的相关配置,设计了上图的相关的设计思路框图。
key_seshake:实现按键的消抖模块,在本次的设计中,使用了3个按键,key_in[0]控制数据的写入;key_in[1]控制数据的读取;key_in[2]控制扇区擦除。
uart_rx:串口数据接收模块,接收上位机发送来的数据,然后将 接收到的数据在发送的fifo中进行缓存。
fifo_rx:将uart_rx接收到的数据进行缓存,为FLASH的数据读取做准备。
spi_ctrl:主要是实现对M25P16的驱动,使用状态机进行驱动,主要实现了状态的转移。
spi:spi_ctrl模块的子模块,主要实现使用SPI协议对相关状态下数据的写入与读取。
fifo_tx:将从M25P16中读取到的数据缓存到该fifo中,为串口进行数据的发送做准备。
uart_tx:串口数据发送模块,主要是将fifo_tx模块中的数据发送给上位机。
2、spi_ctrl状态机
二、设计输入
1、顶层模块信号列表
信号 | I/O | BIT | 功能 |
clk | input | 1 | 系统时钟 |
rst_n | input | 1 | 复位 |
miso | input | 1 | 主机输入,从机输出 |
uart_rx | input | 1 | Rx端口 |
key_in | input | 3 | 按键输入 |
cs_n | output | 1 | 片选 |
mosi | output | 1 | 主机输出,从机输入 |
sclk | output | 1 | spi时钟 |
uart_tx | output | 1 | Tx端口 |
2、key_shake模块信号列表
信号 | I/O | BIT | 功能 |
clk | input | 1 | 时钟信号 |
rst_n | input | 1 | 复位信号 |
key_in | input | 3 | 按键输入信号 |
key_out | output | 3 | 按键输出信号 |
3、uart_rx模块信号列表
信号 | I/O | BIT | 功能 |
clk | input | 1 | 系统时钟信号 |
rst_n | input | 1 | 系统复位信号 |
uart_rx | input | 1 | 数据接收 |
full_rx | input | 1 | fifo_rx的满信号 |
wrreq_rx | output | 1 | 向fifo_rx发送写请求 |
rx_data | output | 8 | 写入fifo_rx中的数据 |
4、fifo_rx模块信号列表(IP核)
信号 | I/O | BIT | 功能 |
aclr | input | 1 | fifo复位 |
clock | input | 1 | fifo时钟 |
data | input | 8 | fifo缓存uart_rx接收到的数据 |
rdreq | input | 1 | spi_ctrl读请求 |
wrreq | input | 1 | uart_rx写请求 |
empty | output | 1 | spi_ctrl读数据空标志 |
full | output | 1 | uart_rx写数据满标志 |
q | output | 8 | fifo输出给spi_ctrl,将数据存储入flash |
usedw | output | 8 |
5、spi_ctrl模块信号列表
信号 | I/O | BIT | 功能 |
clk | input | 1 | 系统 |
rst_n | input | 1 | 复位 |
key_in | input | 3 | 控制读、写 00:停止工作 01:写 10:读 |
data_rx | input | 8 | 需要写入的数据(串口接收的数据) |
miso | input | 1 | 主机接收,从机输出 |
full_tx | input | 1 | fifo_tx的满信号 |
empty_rx | input | 1 | fifo_rx的空信号 |
mosi | output | 1 | 主机输出,从机接收 |
cs_n | output | 1 | 片选 |
sclk | output | 1 | SPI 时钟 |
wrreq_tx | output | 1 | 向fifo_tx发送写请求 |
rdreq_rx | output | 1 | 向fifo_rx发送读请求 |
data_tx | output | 8 | 读取到的数据(串口发送的数据) |
6、spi模块信号列表
信号 | I/O | BIT | 功能 |
clk | input | 1 | 系统 |
rst_n | input | 1 | 复位 |
miso | input | 1 | 主机接收,从机输出 |
data_wr | input | 8 | 写入的数据或指令 |
req | input | 1 | 读写请求 |
rden | input | 1 | 读数据使能信号 |
cs_n | output | 1 | 片选 |
mosi | output | 1 | 主机输出,从机接收 |
sclk | output | 1 | SPI 时钟 |
done | output | 1 | 读写完1字节 |
data_rd | output | 8 | 读取到的数据 |
7、fifo_tx模块信号列表(IP核)
信号 | I/O | BIT | 功能 |
aclr | input | 1 | fifo复位 |
clock | input | 1 | fifo时钟 |
data | input | 8 | 从flash中将数据读取出来缓存进fifo |
rdreq | input | 1 | uart_tx读请求 |
wrreq | input | 1 | spi_ctrl写请求 |
empty | output | 1 | uart_tx读数据空标志 |
full | output | 1 | spi_ctrl写数据满标志 |
q | output | 8 | 将fifo中的数据发送给uart_tx进行输出 |
usedw | output | 8 |
8、uart_tx模块信号列表
信号 | I/O | BIT | 功能 |
clk | input | 1 | 时钟信号 |
rst_n | input | 1 | 复位信号 |
tx_data | input | 8 | 需要发送的数据 |
empty_tx | input | 1 | fifo_tx的空信号 |
rdreq_tx | output | 1 | 向fifo_tx发送读请求 |
uart_tx | output | 1 | 数据输出 |
-
三、代码片段
-
1、M23P16顶层模块
-
module M25P16 #(parameter KEY_W = 3)( //按键位宽 input clk ,//系统时钟 input rst_n ,//复位 input miso ,//主机输入,从机输出 input uart_rx ,//Rx端口 input [KEY_W - 1:0] key_in ,//按键 output cs_n ,//片选 output mosi ,//主机输出,从机输入 output sclk ,//spi时钟 output uart_tx //Tx端口 ); wire [KEY_W - 1:0] key ; wire [7:0] rx_data ; wire [7:0] data_rx ; wire wrreq_rx; wire full_rx ; wire empty_rx; wire [7:0] tx_data ; wire [7:0] data_tx ; wire full_tx ; wire empty_tx; key_deshake #(.KEY_W(KEY_W))key_deshake_inst( /* input */.clk (clk ),//时钟信号 /* input */.rst_n (rst_n ),//复位信号 /* input [KEY_W - 1:0] */.key_in (key_in ),//按键输入信号 /* output reg [KEY_W - 1:0] */.key_out (key ) //按键输出信号 ); uart_rx uart_rx_inst( /* input */.clk (clk ),//系统时钟信号 /* input */.rst_n (rst_n ),//系统复位信号 /* input */.uart_rx (uart_rx ),//数据接收 /* input */.full_rx (full_rx ),//fifo_rx的满信号 /* output reg */.wrreq_rx(wrreq_rx),//向fifo_rx发送写请求 /* output [7:0] */.rx_data (rx_data ) //数据串行接并行出给fifo_rx ); fifo_rx fifo_rx_inst ( .aclr (!rst_n ),//fifo复位 .clock (clk ),//fifo时钟 .data (rx_data ),//fifo缓存uart_rx接收到的数据 .rdreq (rdreq_rx ),//spi_ctrl读请求 .wrreq (wrreq_rx ),//uart_rx写请求 .empty (empty_rx ),//spi_ctrl读数据空标志 .full (full_rx ),//uart_rx写数据满标志 .q (data_rx ),//fifo输出给spi_ctrl,将数据存储入flash .usedw ( ) ); spi_ctrl spi_ctrl_inst( /* input */.clk (clk ),//系统 /* input */.rst_n (rst_n ),//复位 /* input [01:0] */.key_in (key ),//控制读、写 00:停止工作 01:写 10:读 /* input [07:0] */.data_rx (data_rx ),//需要写入的数据(串口接收的数据) /* input */.miso (miso ),//主机接收,从机输出 /* input */.empty_rx(empty_rx),//fifo_rx的空信号 /* input */.full_tx (full_tx ),//fifo_tx的满信号 /* output */.mosi (mosi ),//主机输出,从机接收 /* output */.cs_n (cs_n ),//片选 /* output */.sclk (sclk ),//SPI 时钟 /* output reg [07:0] */.data_tx (data_tx ),//读取到的数据(串口发送的数据) /* output */.rdreq_rx(rdreq_rx),//向fifo_rx发送读请求 /* output */.wrreq_tx(wrreq_tx) //向fifo_tx发送写请求 ); fifo_tx fifo_tx_inst ( .aclr (!rst_n ),//fifo复位 .clock (clk ),//fifo时钟 .data (data_tx ),//从flash中将数据读取出来缓存进fifo .rdreq (rdreq_tx ),//uart_tx读请求 .wrreq (wrreq_tx ),//spi_ctrl写请求 .empty (empty_tx ),//uart_tx读数据空标志 .full (full_tx ),//spi_ctrl写数据满标志 .q (tx_data ),//将fifo中的数据发送给uart_tx进行输出 .usedw ( ) ); uart_tx uart_tx_inst( /* input */.clk (clk ),//时钟信号 /* input */.rst_n (rst_n ),//复位信号 /* input [07:0] */.tx_data (tx_data ),//需要发送的数据 /* input */.empty_tx(empty_tx),//fifo_tx的空信号 /* output reg */.rdreq_tx(rdreq_tx),//向fifo_tx发送读请求 /* output reg */.uart_tx (uart_tx ) //数据输出 ); endmodule
2、key_shake模块
-
//状态机实现按键消抖 module key_deshake #(parameter KEY_W = 3)//按键位宽 ( input clk ,//时钟信号 input rst_n ,//复位信号 input [KEY_W - 1:0] key_in ,//按键输入信号 output reg [KEY_W - 1:0] key_out //按键输出信号 ); parameter TIME_20ms = 1_000_000; //20ms延时常量声明 parameter IDLE = 4'b0001, //初始状态 DOWN = 4'b0010, //检测到按键按下 HOLD = 4'b0100, //按键按下稳定的状态 UP = 4'b1000; //按键弹起的状态 reg [19:0] cnt; wire add_cnt; wire end_cnt; reg [KEY_W - 1:0] key_in_ff0; //同步打拍 reg [KEY_W - 1:0] key_in_ff1; reg [KEY_W - 1:0] key_in_ff2; wire posdge; //上升沿 wire negdge; //下降沿 reg [3:0] state_c ; //现态 reg [3:0] state_n ; //次态 wire idle2down; //IDLE跳转到DOWN的转移条件 wire down2hold; //DOWN跳转到HOLD的转移条件 wire hold2up ; //HOLD跳转到UP的转移条件 wire up2idle ; //UP跳转到IDLE的转移条件 //同步 打拍 检测上升沿、下降沿 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin key_in_ff0 <= {KEY_W{1'b1}}; key_in_ff1 <= {KEY_W{1'b1}}; key_in_ff2 <= {KEY_W{1'b1}}; end else begin key_in_ff0 <= key_in; key_in_ff1 <= key_in_ff0; key_in_ff2 <= key_in_ff1; end end assign posdge = (&key_in_ff1) && ~(&key_in_ff2);//检测按键上升沿 assign negdge = ~(&key_in_ff1) && (&key_in_ff2);//检测按键下降沿 //消抖20ms计数器 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 20'b0; end else if(add_cnt) begin if(end_cnt) begin cnt <= 20'b0; end else begin cnt <= cnt + 20'b1; end end else cnt <= 20'b0; //只有在DOWN和UP状态进行计数,其他状态清零 end assign add_cnt = state_c == DOWN || state_c == UP; //计数器使能信号 assign end_cnt = add_cnt && cnt == TIME_20ms - 1; //状态机描述按键消抖 //同步时序描述状态转移 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(idle2down) begin //当满足条件时进行状态转移 state_n = DOWN; end else begin state_n = state_c; end end DOWN:begin if(down2hold) begin state_n = HOLD; end else begin state_n = state_c; end end HOLD:begin if(hold2up) begin state_n = UP; end else begin state_n = state_c; end end UP :begin if(up2idle) begin state_n = IDLE; end else begin state_n = state_c; end end endcase end //状态转移的条件 assign idle2down = state_c == IDLE && negdge ; //在初始状态IDLE检测到下降沿时跳转到DOWN assign down2hold = state_c == DOWN && end_cnt ; //在DOWN下20ms计数器开始计数,计数到最大值时跳转到HOLD assign hold2up = state_c == HOLD && posdge ; //在HOLD下检测到上升沿时跳转到UP assign up2idle = state_c == UP && end_cnt ; //在UP下20ms计数器开始计数,计数到最大值时跳转到IDLE //时序逻辑描述输出 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin key_out <= {KEY_W{1'b0}}; end else if(hold2up) begin //在DOWN跳转到HOLD或者在HOLD跳转到UP时进行输出,输出对输入进行取反 key_out <= ~key_in_ff2; end else begin key_out <= {KEY_W{1'b0}}; end end endmodule
3、uart_rx模块
-
module uart_rx( input clk , //系统时钟信号 input rst_n , //系统复位信号 input uart_rx , //数据接收 input full_rx , //fifo_rx的满信号 output wrreq_rx, //向fifo_rx发送写请求 output [7:0] rx_data //数据串行接并行出 ); /**********************************相关常量定义*********************************** *********************************************************************************/ parameter COUNT = 50_000_000; //系统时钟频率 周期20ns parameter BPS = 115200; //波特率 parameter CNT = COUNT / BPS; parameter BIT = 10 ; //接收1帧的bit数 parameter IDLE = 4'b0001, START = 4'b0010, DATA = 4'b0100, DONE = 4'b1000; /**********************************相关变量定义*********************************** *********************************************************************************/ reg [3:0] state_c; reg [3:0] state_n; reg [15:0] cnt_clk; wire add_cnt_clk; wire end_cnt_clk; reg [03:0] cnt_bit; wire add_cnt_bit; wire end_cnt_bit; wire rx_start; //rx数据接收开始信号 reg rx_done ; reg data_reg; //寄存uart_rx接收的数据 reg [7:0] data_out_reg; reg [7:0] data ; reg uart_rx_reg0;//同步打拍 reg uart_rx_reg1; reg uart_rx_reg2; reg check ;//判断接收到的数据是否正确 /******************************向fifo_rx发送写请求******************************** *********************************************************************************/ assign wrreq_rx = full_rx ? 'd0 : rx_done; /****************************对发送的数据进行同步打拍****************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin uart_rx_reg0 <= 1'b1; uart_rx_reg1 <= 1'b1; uart_rx_reg2 <= 1'b1; end else begin uart_rx_reg0 <= uart_rx; uart_rx_reg1 <= uart_rx_reg0; uart_rx_reg2 <= uart_rx_reg1; end end assign rx_start = (~uart_rx_reg1 && uart_rx_reg2) && cnt_bit == 0; //检测到下降沿 /**********************************相关计数器设计********************************* *********************************************************************************/ //发送1bit所需要的周期数cnt_clk always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_clk <= 0; end else if(state_c == IDLE) 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 == DATA; assign end_cnt_clk = add_cnt_clk && cnt_clk == CNT - 1; //发送一字节所需要的bit数cnt_bit always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_bit <= 0; end else if(state_c == IDLE) 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 = end_cnt_clk; assign end_cnt_bit = add_cnt_bit && cnt_bit == BIT - 1; /**********************************rx_done信号设计******************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rx_done <= 'd0; end else if(state_c == DATA && end_cnt_bit) begin rx_done <= 'd1; end else begin rx_done <= 'd0; end end /*********************************uart_rx状态机设计******************************* *********************************************************************************/ //状态机第一段 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(rx_start) begin state_n = START; end else begin state_n = IDLE; end end START: begin state_n = DATA; end DATA: begin if(end_cnt_bit) begin state_n = DONE; end else if(check) begin state_n = IDLE; end else begin state_n = state_c; end end DONE: begin state_n = IDLE; end default:state_n = IDLE; endcase end //第三段 //对接收到的数据进行寄存 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin data_reg <= 'd0; end else if(state_c == DATA) begin data_reg <= uart_rx_reg2; end else begin data_reg <= 'd0; end end /*********************************检测数据是否是误接******************************* *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin check <= 'd0; end else if(state_c == DATA && cnt_clk == CNT / 2 && cnt_bit == 0) begin check <= data_reg; end else if(state_c == DATA && cnt_clk == CNT / 2 && cnt_bit == 9)begin check <= !data_reg; end else begin check <= 'd0; end end /********************************接收到的数据寄存发送****************************** *********************************************************************************/ //对接收到的数据进行发送的寄存器 逐位寄存 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin data_out_reg <= 8'h0; end else if(state_c == DATA && cnt_clk == CNT / 2 && cnt_bit > 0 && cnt_bit < 9)begin //只有在数据位才接收数据 data_out_reg <= {data_reg,data_out_reg[7:1]}; //并且在每位数据传输时间的一般进行接收 end else if(state_c == DONE || state_c == IDLE) begin //当接收数据完成时,对接收寄存器进行清零操作 data_out_reg <= 8'h0; end else begin data_out_reg <= data_out_reg; end end //对接收到的数据进行发送 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin data <= 8'h0; end else if(state_c == DATA && end_cnt_bit)begin //当bit计数器计数到最大值时,将接收到完整的数据赋值给数据发送端 data <= data_out_reg; end else begin data <= data; end end assign rx_data = data; endmodule
4、spi_ctrl模块
-
module spi_ctrl( input clk ,//系统 input rst_n ,//复位 input [02:0] key_in ,//控制读、写 00:停止工作 01:写 10:读 input [07:0] data_rx ,//需要写入的数据(串口接收的数据) input miso ,//主机接收,从机输出 input full_tx ,//fifo_tx的满信号 input empty_rx ,//fifo_rx的空信号 output mosi ,//主机输出,从机接收 output cs_n ,//片选 output sclk ,//SPI 时钟 output reg wrreq_tx ,//向fifo_tx发送写请求 output rdreq_rx ,//向fifo_rx发送读请求 output reg [07:0] data_tx //读取到的数据(串口发送的数据) ); parameter WAIT_WR = 100 , //两个指令间等待100ns WAIT_PP = 250_000 , //页编程所需时间5ms WAIT_SE = 150_000_000 ; //扇区擦除所需时间3s parameter BYTE_CNT_MAX = 'd260; parameter IDLE = 'd1, WREN = 'd2, WAIT = 'd3, SEPP = 'd4, READ = 'd5, DONE = 'd6; reg [03:0] state_c ; reg [03:0] state_n ; reg [11:0] BYTE_CNT ;//字节数 reg [11:0] cnt_byte ;//字节计数器 wire add_cnt_byte; wire end_cnt_byte; reg [27:0] WAIT_CNT ;//等待时间 reg [27:0] cnt_wait ;//等待计数器 wire add_cnt_wait; wire end_cnt_wait; reg sepp_flag ;//0:执行D8h扇区擦除 1:执行02h页面写入 reg [01:0] wr_rd ;//读写判断 reg req ;//读写请求 相当于片选 wire done ;//读写完一个字节 reg wr_rd_done ;//读、写数据结束 wire[07:0] data_rd ;//读取到的数据 reg [07:0] data_wr ;//数据写入 reg rden ;//读数据使能信号 reg wrreq_tx_reg; reg [07:0] wr_addr ; spi spi_inst( /* input */.clk (clk ),//系统 /* input */.rst_n (rst_n ),//复位 /* input */.miso (miso ),//主机接收,从机输出 /* input [07:0] */.data_wr (data_wr ),//写入的数据或指令 /* input */.req (req ),//读写请求 /* input */.rden (rden ),//读数据使能信号 /* output */.cs_n (cs_n ),//片选 /* output */.mosi (mosi ),//主机输出,从机接收 /* output */.sclk (sclk ),//SPI 时钟 /* output */.done (done ),//读写完1字节 /* output [07:0] */.data_rd (data_rd ) //读取到的数据 ); /********************************fifo_rx/tx的读写请求***************************** *********************************************************************************/ assign rdreq_rx = empty_rx ? 'd0 : state_c == SEPP && sepp_flag == 'd1 && cnt_byte >= 'd4 && done; // assign wrreq_tx = full_tx ? 'd0 : state_c == READ && cnt_byte >= 'd4 && done; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin wrreq_tx_reg <= 'd0; end else if(!full_tx && state_c == READ && cnt_byte >= 'd4 && done) begin wrreq_tx_reg <= 'd1; end else begin wrreq_tx_reg <= 'd0; end end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin wrreq_tx <= 'd0; end else begin wrreq_tx <= wrreq_tx_reg; end end /*************************************字节计数器********************************** *********************************************************************************/ always @(posedge clk or negedge rst_n)begin if(!rst_n) begin cnt_byte <= 'd0; end else if(state_c == SEPP && sepp_flag == 'd1 && empty_rx || wr_addr == 'd255 && rdreq_rx) begin cnt_byte <= 'd0; end else if(add_cnt_byte) begin if(end_cnt_byte) begin cnt_byte <= 'd0; end else begin cnt_byte <= cnt_byte + 'd1; end end end assign add_cnt_byte = done && (state_c == WREN || state_c == SEPP || state_c == READ); assign end_cnt_byte = add_cnt_byte && cnt_byte == BYTE_CNT - 'd1; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin wr_addr <= 'd0; end else if(wr_addr == 'd255 && rdreq_rx) begin wr_addr <= 'd0; end else if(rdreq_rx) begin wr_addr <= wr_addr + 'd1; end else begin wr_addr <= wr_addr; end end always @(*) begin if(!rst_n) begin BYTE_CNT <= 'd0; end else begin case(state_c) WREN :BYTE_CNT = 'd1; SEPP :if(sepp_flag) BYTE_CNT = BYTE_CNT_MAX; //1字节指令 + 3字节地址 + 最多256的写入数据 else BYTE_CNT = 'd4; //1字节指令 + 3字节地址 READ :BYTE_CNT = BYTE_CNT_MAX; //1字节指令 + 3字节知道 + 最多256的读取数据 default:BYTE_CNT = 'd0; endcase end end /***********************************延迟等待计数器********************************* *********************************************************************************/ always @(posedge clk or negedge rst_n)begin if(!rst_n) begin cnt_wait <= 'd0; end else if(add_cnt_wait) begin if(end_cnt_wait) begin cnt_wait <= 'd0; end else begin cnt_wait <= cnt_wait + 'd1; end end end assign add_cnt_wait = state_c == WAIT; assign end_cnt_wait = add_cnt_wait && cnt_wait == WAIT_CNT - 1; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin WAIT_CNT <= 'd0; end else begin case(state_c) WREN :WAIT_CNT <= WAIT_WR; SEPP :if(sepp_flag) WAIT_CNT <= WAIT_PP; else WAIT_CNT <= WAIT_SE; default:WAIT_CNT <= WAIT_CNT; endcase end end /**********************************读/写判断及请求******************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin wr_rd <= 2'b0; end else if(key_in == 3'b001) begin wr_rd <= 2'b01; end else if(key_in == 3'b010) begin wr_rd <= 2'b10; end else if(wr_rd_done) begin wr_rd <= 2'b00; end else begin wr_rd <= wr_rd; end end always @(*) begin if(!rst_n) begin req <= 'd1; end else if(state_c == WREN || state_c == SEPP || state_c == READ) begin //读、写状态 req <= 'd0; end else if(state_c == IDLE || state_c == DONE || state_c == WAIT) begin //初始、等待、接收状态 req <= 'd1; end else begin req <= req; end end /***********************************读/写结束信号********************************* *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin wr_rd_done <= 'd0; end else if((state_c == SEPP && sepp_flag == 'd1 && (end_cnt_byte || empty_rx)) || (state_c == READ && end_cnt_byte)) begin wr_rd_done <= 'd1; end else begin wr_rd_done <= 'd0; end end /*************************************SE/PP选择*********************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sepp_flag <= 'd0; end else if(state_c == WAIT && WAIT_CNT == WAIT_SE && end_cnt_wait) begin //擦除结束,sepp_flag转为pp模式 sepp_flag <= 'd1; end else if(key_in == 3'b100/* state_c == WAIT && WAIT_CNT == WAIT_PP && end_cnt_wait */) begin //写数据结束,sepp_flag转化为SE模式 sepp_flag <= 'd0; end else begin sepp_flag <= sepp_flag; end end /***************************************状态机************************************ *********************************************************************************/ 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(wr_rd == 2'b01) state_n = WREN; else if(wr_rd == 2'b10) state_n = READ; else state_n = IDLE; end WREN :begin if(done) state_n = WAIT; else state_n = WREN; end WAIT :begin if(end_cnt_wait && WAIT_CNT == WAIT_WR) state_n = SEPP; else if(end_cnt_wait && (WAIT_CNT == WAIT_PP || WAIT_SE)) state_n = DONE; else state_n = WAIT; end SEPP :begin if(sepp_flag == 'd0 && end_cnt_byte && BYTE_CNT == 'd4) //扇区擦除 state_n = WAIT; else if(sepp_flag == 'd1 && BYTE_CNT == BYTE_CNT_MAX && (end_cnt_byte || empty_rx)) //数据写入 state_n = WAIT; else state_n = SEPP; end READ :begin if(end_cnt_byte && BYTE_CNT == BYTE_CNT_MAX) state_n = DONE; else state_n = READ; end DONE :begin state_n = IDLE; end default:state_n = IDLE; endcase end /***********************************写入接口的数据******************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin data_wr <= 'h0; end else if(state_c == WREN) begin data_wr <= 8'h06; end else if(state_c == SEPP && sepp_flag == 'd0)begin //扇区擦除 case(cnt_byte) 0:data_wr <= 8'hD8; 1:data_wr <= 8'h00; 2:data_wr <= 8'h00; 3:data_wr <= 8'h00; endcase end else if(state_c == SEPP && sepp_flag == 'd1) begin //写数据 case(cnt_byte) 0:data_wr <= 8'h02; 1:data_wr <= 8'h00; 2:data_wr <= 8'h00; 3:data_wr <= wr_addr; default:data_wr <= data_rx; endcase end else if(state_c == READ) begin case(cnt_byte) 0:data_wr <= 8'h03; 1:data_wr <= 8'h00; 2:data_wr <= 8'h00; 3:data_wr <= 8'h00; default:data_wr <= 8'h0; endcase end else begin data_wr <= 'h0; end end /***********************************读取接口的数据******************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rden <= 'd0; end else if(state_c == READ) begin case(cnt_byte) 0,1,2,3:begin rden <= 'd0;end default:begin rden <= 'd1;end endcase end else begin rden <= 'h0; end end always @(*) begin if(!rst_n) begin data_tx <= 'd0; end else if(wrreq_tx) begin data_tx <= data_rd; end else begin data_tx <= 'd0; end end endmodule
5、spi模块
-
module spi ( input clk ,//系统 input rst_n ,//复位 input miso ,//主机接收,从机输出 input [07:0] data_wr ,//写入的数据或指令 input req ,//读写请求,低电平有效 input rden ,//读数据使能信号 output reg cs_n ,//片选 output reg mosi ,//主机输出,从机接收 output reg sclk ,//SPI 时钟 output reg done ,//读写完1字节 output reg [07:0] data_rd //读取到的数据 ); reg [01:0] cnt_sclk; reg [03:0] cnt_bit ; reg [07:0] data_rd_reg; /**************************************sclk时钟*********************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_sclk <= 'd0; end else if(req) begin cnt_sclk <= 'd0; end else if(cnt_sclk == 4 - 1) begin cnt_sclk <= 'd0; end else if(!req)begin cnt_sclk <= cnt_sclk + 'd1; end else begin cnt_sclk <= cnt_sclk; end end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sclk <= 'd1; end else if(cs_n) begin sclk <= 'd1; end else if(cnt_sclk == 1) begin sclk <= 'd0; end else if(cnt_sclk == 3)begin sclk <= 'd1; end else begin sclk <= sclk; end end /**************************************cs_n片选*********************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cs_n <= 'd0; end else begin cs_n <= req; end end /***********************************数据--位计数器******************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_bit <= 'd0; end else if(cnt_bit == 8 - 1 && cnt_sclk == 'd3) begin cnt_bit <= 'd0; end else if(cnt_sclk == 'd3)begin cnt_bit <= cnt_bit + 'd1; end else begin cnt_bit <= cnt_bit; end end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin done <= 'd0; end else if(cnt_bit == 8 - 1 && cnt_sclk == 'd2) begin done <= 'd1; end else begin done <= 'd0; end end /**************************************mosi信号*********************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin mosi <= 'd0; end else if(cnt_sclk == 'd1) begin mosi <= data_wr[7 - cnt_bit]; end else begin mosi <= mosi; end end /**************************************data_rd*********************************** *********************************************************************************/ always @(posedge clk or negedge rst_n) begin if(!rst_n) begin data_rd_reg <= 'd0; end else if(rden && cnt_sclk == 'd3) begin data_rd_reg[7 - cnt_bit] <= miso; end else begin data_rd_reg <= data_rd_reg; end end always @(*) begin if(!rst_n) begin data_rd <= 'd0; end else if(cnt_bit == 'd0) begin data_rd <= data_rd_reg; end else begin data_rd <= 'd0; end end endmodule
6、uart_tx模块
-
//UART发送端 无校验位 结束为1位 module uart_tx( input clk ,//时钟信号 input rst_n ,//复位信号 input [07:0] tx_data ,//需要发送的数据 input empty_tx,//fifo_tx的空信号 output rdreq_tx,//向fifo_tx发送读请求 output reg uart_tx //数据输出 ); /**********************************相关常量定义*********************************** *********************************************************************************/ parameter COUNT = 50_000_000; //系统时钟频率 周期20ns parameter BPS = 115200; //波特率 parameter CNT = COUNT / BPS;//传输1bit所需计数次数 COUNT / BPS; parameter BIT = 10 ; //传输一个字节(1位起始位,8位数据位,1位结束为) 从低位开始发 parameter IDLE = 4'b0001, START = 4'b0010, DATA = 4'b0100, DONE = 4'b1000; /**********************************相关信号定义*********************************** *********************************************************************************/ reg [15:0] cnt_clk; wire add_cnt_clk; wire end_cnt_clk; reg [3:0] cnt_bit; wire add_cnt_bit; wire end_cnt_bit; reg [3:0] state_c; reg [3:0] state_n; reg [7:0] tx_data_reg ; reg [7:0] data ; wire tx_start; wire tx_done ; /*******************************向fifo_tx发送读请求******************************** *********************************************************************************/ assign rdreq_tx = ~empty_tx && tx_done; assign tx_start = tx_done ? ~empty_tx && tx_done : ~empty_tx; /*****************************对要输出的数据进行寄存******************************* *********************************************************************************/ //tx_data_reg 对要输出的数据进行打拍 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin tx_data_reg <= 'd0; end else if(tx_start) begin tx_data_reg <= tx_data; end else begin tx_data_reg <= 'd0; end end //data 对打拍后的数据进行寄存,为后续数据的输出做准备 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin data <= 'd0; end else if(state_c == DONE) begin data <= 'd0; end else if(state_c == START)begin data <= tx_data_reg; end else begin data <= data; end end /**********************************相关计数器设计********************************* *********************************************************************************/ //发送1bit所需要的周期数cnt_clk always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_clk <= 13'b0; end else if(add_cnt_clk) begin if(end_cnt_clk) begin cnt_clk <= 13'b0; end else begin cnt_clk <= cnt_clk + 13'b1; end end else begin cnt_clk <= 13'b0; end end assign add_cnt_clk = state_c == DATA; assign end_cnt_clk = add_cnt_clk && cnt_clk == CNT - 1; //发送一字节所需要的bit数cnt_bit always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_bit <= 4'b0; end else if(add_cnt_bit) begin if(end_cnt_bit) begin cnt_bit <= 4'b0; end else begin cnt_bit <= cnt_bit + 4'b1; end end else begin cnt_bit <= cnt_bit; end end assign add_cnt_bit = end_cnt_clk; assign end_cnt_bit = add_cnt_bit && cnt_bit == BIT - 1; //tx_done assign tx_done = end_cnt_bit; /**********************************Tx状态机设计*********************************** *********************************************************************************/ //状态机第一段 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(tx_start) begin state_n = START; end else begin state_n =IDLE; end end START: begin state_n = DATA; end DATA : begin if(end_cnt_bit) begin state_n = DONE; end else begin state_n = state_c; end end DONE : begin state_n = IDLE; end default:state_n <= IDLE; endcase end //第三段 uart_tx输出 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin uart_tx <= 'd1; end else if(state_c == DATA) begin case(cnt_bit) 0:uart_tx <= 1'b0 ; 1:uart_tx <= data[0]; 2:uart_tx <= data[1]; 3:uart_tx <= data[2]; 4:uart_tx <= data[3]; 5:uart_tx <= data[4]; 6:uart_tx <= data[5]; 7:uart_tx <= data[6]; 8:uart_tx <= data[7]; 9:uart_tx <= 1'b1 ; default:uart_tx <= 1'b1; endcase end else begin uart_tx <= 1'b1; end end endmodule
-
四、仿真波形
五、上板测试
在串口调试助手发送“成功驱动M25P16!(含回车)”,然后按下key_in[0],等待3s先进行扇区擦除,然后才进行数据的写入。
再按下key_in[1],读取FLASH中的数据。
再按下key_in[0],写入数据“再次写入数据成功!(含回车)”,再按下key_in[1]读取FLASH中的数据。
按下key_in[2],进行扇区擦除,在按下key_in[1]读取数据全为FF,说明擦除成功。