基于FPGA的SPI读写flash

        本项目所用开发板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

 

 


 

 

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值