SPI接口驱动模块设计
- 一、功能分析
 - 二、状态机设计
 - 三、信号说明
 - 四、代码实现
 - 五、仿真测试
 
写在前面:
FPGA实现SPI协议读写FLASH系列相关文章:
SPI通信协议
【FPGA】FPGA实现SPI协议读写FLASH(一)----- M25P16操作概述
在上篇文章中对FLASH(M25P16)读写操作及指令等做了详细介绍,本文将通过SPI协议原理,设计SPI通信接口,实现FPGA与FLASH进行通信。
本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),FLASH型号:M25P16。
一、功能分析
SPI接口驱动模块(spi_interface)主要根据SPI协议原理架起FPGA与FLASH数据传输的“桥梁”,将从SPI读写控制模块接收到的指令、地址、数据字节严格按照SPI协议传输给FLASH,并接收从FLASH读回的数据,发送给SPI读写控制模块。
二、状态机设计
状态转移图如下:
 
 状态说明:
 IDLE:空闲状态,等待读写控制模块发起数据传输请求。
 READY:准备传输数据。
 TRANS:数据传输状态,根据SPI协议发送或接收数据。
 DONE:数据传输结束。
三、信号说明
| Port | I/O Type | Description | 
|---|---|---|
| clk | input | 时钟信号 | 
| rst_n | input | 复位信号 | 
| req | input | 读/写数据请求 | 
| din[7:0] | input | 要传输给FLASH的数据 | 
| rdout[7:0] | output | 从FLASH读回的数据 | 
| rw_done | output | 读/写一字节数据完成标志 | 
| sclk | output | 同步时钟 | 
| cs_n | output | 片选信号 | 
| mosi | output | 主机输出,从机输入信号 | 
| miso | input | 主机输入,从机输出信号 | 
四、代码实现
//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: spi_flash    
//  Module Name: spi_interface         
//  Target Device: Cyclone IV E (EP4CE6F17C8), FLASH(M25P16)                
//  Tool versions: Quartus Prime 18.1             
//  Description: SPI读写FLASH工程SPI接口模块
//  **************************************************************
`include "param.v"
module spi_interface (
    input               clk             ,
    input               rst_n           ,
    //  flash_control
    input               req             ,       //  读/写数据请求
    input   [7:0]       din             ,       //  要写入的数据
    output  [7:0]       rdout           ,       //  读回的数据
    output              rw_done         ,       //  读写一字节数据完成标志
    //  M25P16
    output              sclk            ,
    output              cs_n            ,
    output              mosi            ,
    input               miso               
);
    //  参数定义
    localparam  IDLE    =   4'b0001 ,
                READY   =   4'b0010 ,
                TRANS   =   4'b0100 ,
                DONE    =   4'b1000 ;
    
    //  信号定义
    reg     [3:0]       state_c         ;
    reg     [3:0]       state_n         ; 
    reg     [4:0]       cnt_sclk        ;       //  sclk计数器
    wire                add_cnt_sclk    ;
    wire                end_cnt_sclk    ;
    reg     [4:0]       cnt_bit         ;       //  sclk计数器
    wire                add_cnt_bit     ;
    wire                end_cnt_bit     ;
    reg                 sclk_reg        ;       //  输出sclk寄存
    reg                 tx_data         ;       //  数据输出寄存
    reg     [7:0]       rx_data         ;       //  读回数据寄存
    //  状态转移条件
    wire                idle2ready      ;  
    wire                ready2trans     ;  
    wire                trans2done      ;  
    wire                done2idle       ;  
    //  状态机
    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(idle2ready)
                    state_n = READY ;
                else
                    state_n = state_c ;
            end
            READY: begin
                if(ready2trans)
                    state_n = TRANS ;
                else
                    state_n = state_c ;
            end
            TRANS: begin
                if(trans2done)
                    state_n = DONE ;
                else
                    state_n = state_c ;
            end
            DONE: begin
                if(done2idle)
                    state_n = IDLE ;
                else
                    state_n = state_c ;
            end
            default: state_n = IDLE ;
        endcase
    end 
    //  状态转移条件
    assign idle2ready   = state_c == IDLE   && req          ;
    assign ready2trans  = state_c == READY  && (1'b1)       ;
    assign trans2done   = state_c == TRANS  && end_cnt_bit  ;
    assign done2idle    = state_c == DONE   && (1'b1)       ; 
    //  cnt_sclk
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_sclk <= 0 ;
        end
        else if(add_cnt_sclk)begin
            if(end_cnt_sclk)begin
                cnt_sclk <= 0 ;
            end				
            else begin	    
                cnt_sclk <= cnt_sclk + 1 ;
            end 		    
        end
    end                  
    assign	add_cnt_sclk = state_c == TRANS ;
    assign	end_cnt_sclk = add_cnt_sclk && (cnt_sclk == `SCLK_CYCLE - 1) ;
    //  sclk_reg
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sclk_reg <= 1'b1 ;
        end
        else if(add_cnt_sclk && cnt_sclk == `SCLK_FALL)begin
            sclk_reg <= 1'b0 ;
        end
        else if(add_cnt_sclk && cnt_sclk == `SCLK_RISE)begin
            sclk_reg <= 1'b1 ;
        end
    end
    //  cnt_bit
    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 =  end_cnt_sclk ;
    assign	end_cnt_bit = add_cnt_bit && (cnt_bit == 7) ;
    //  tx_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            tx_data <= 1'b1 ;
        end
        else if(state_c == TRANS && cnt_sclk == `TRANS_S)begin
            tx_data <= din[7 - cnt_bit] ;
        end
        else if(trans2done)begin
            tx_data <= 1'b1 ;
        end
    end
    //  rx_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rx_data <= 0 ;
        end
        else if(state_c == TRANS && cnt_sclk == `SAMP_S)begin
            rx_data[7 - cnt_bit] <= miso ;
        end
    end
    //  输出
    assign rdout = rx_data ;   
    assign rw_done = trans2done ; 
    assign sclk = sclk_reg ;   
    assign cs_n = ~req ;  
    assign mosi = tx_data ;    
   
    
endmodule
 
五、仿真测试
SPI接口模块发送数据:
 
 M25P16从机模型接收数据:
 
 通过仿真分析,M25P16从机模型接收数据正确,SPI接口模块功能已实现。



















