Verilog实现的SPI Master-Slave联合协议

SPI协议介绍

spi是serial peripheral interface的缩写,即串行扩展总线。SPI是单主设备通信,总线中只有一个主设备发起通信,能发起通信的设备称为主设备。当SPI主设备想读写从设备时,首先拉低对应从设备的ss线(低电平有效)。然后发送工作麦种到时钟线上,在相应的脉冲时间上,主设备把信号发送到MOSI实现读写,同时又可以对MISO采样实现读。一般SPI通信涉及到一下术语:

SCLK serial clock (来自主设备)
MOSI Master Output Slave Input(来自主设备)
MISO Master Input Slave Output(来自从设备)
SS Slave Select(低电平有效,来自主设备)

主设备和从设备的两种链接方式

一主一从

一主一从模式表明有一个主机和一个从机,如下所示:(其中还有SCK信号,由主机到从机)

一主多从

一主多从表示有一个主机和一个从机,唯一不同的点是要为每个从机配备一个选择信号。

SPI协议的工作模式

SPI有四种工作模式,主要由时钟极性CPOL(Clock Polarity),时钟相位CPHA(Clock Phase)的组合决定。

  • CPOL为0,表示SCK在空闲状态为0,为1,则表示SCK在空闲状态为1
  • CPHA为0,表示在SCK的第一个边沿时输出输出数据有效,CPHA为1时,表示在SCK的第二个边沿输入输出数据有效
  • CPOL = 0, CPHA = 0

    CPOL = 0, CPHA = 1

    CPOL = 1. CPHA = 0

    CPOL = 1, CPHA = 1

    SPI MASTER 的verilog设计思路

    设计的引脚说明:

    信号名 方向 +解释
    clk 输入,时钟信号
    rst_n 输入,复位信号
    miso 输入,从机输入到主机
    data_i 输入,主机发送从机的数据(一定比特位宽)
    start 输入,开始的使能信号
    mosi 输出,主机到从机
    sclk 输出,时钟信号
    ss_n 输出,从机的选择信号
    finish 一次传输完成信号

    首先将需要的宏定义收录在一个文件defines.v中,其中的代码如下所示:

    `define CPOL 0      //clock polarity
    `define CPHA 0      //clock phase
    `define CLK_FREQ 50_000_000  // input clk frequency
    `define SCLK_FREQ  5_000_000  // sclk frequency
    `define DATA_WIDTH 8            // a word width
    `define CLK_CYCLE 20
    

    其次时SPI MASTER的设计思路,总体思路时采用一个状态机,首先状态机是在IDLE状态,然后接收开始信号后,会将寄存器data_I中数据一个一个的发送出去,当指定宽距的比特位发送完成后。此时有两种选择,一种是跳转到FINISH状态,另一种是跳转到EXTRA状态,在跳转到FINISH状态。主要是由于当数据发送完成之后要判断此时的SCLK状态是不是空闲下的默认状态,如果不是,则需要跳转到EXTRA状态。结束状态下一个状态回到IDLE状态,得到start命令。

    IDLE 空闲状态
    DATA 发送数据状态
    EXTRA 额外状态
    FINISH 结束状态

    verilog的代码如下:

    `include "defines.v"
    
    module SPI_MASTER(
        input   wire                        clk     ,
        input   wire                        rst_n   ,
        input   wire                        miso    ,
        input   wire    [`DATA_WIDTH-1:0]   data_i  ,
        input   wire                        start   ,
    
        output  wire                        mosi    ,
        output  reg                         sclk    ,
        output  reg                         ss_n    ,
        output  wire                        finish    
    );
        parameter       IDLE    =   5'b00001  ,
                        //CHOOSE  =   5'b00010  ,
                        DATA    =   5'b00100  ,
                        EXTRA   =   5'b01000  ,
                        FINISH  =   5'b10000  ;
        
        parameter       CNT_MAX =   `CLK_FREQ / `SCLK_FREQ - 1;
        
        reg     [31:0]      cnt                     ;   //sclk的时钟周期的计数器
        reg     [4:0]       state                   ;   
        reg     [4:0]       nx_state                ;
        wire    [3:0]       cnt_data                ;   //输出的数据计数器
        reg                 sclk_dly                ;   //sclk的打一拍信号
        reg     [3:0]       cnt_sclk_pos            ;   //sclk的上升沿计数器信号
        reg     [3:0]       cnt_sclk_neg            ;   //sclk的下降沿计数器信号
        reg                 start_dly               ;   //start的打一拍信号
        reg     [3:0]       cnt_data_dly            ;
    
        wire                cnt_max_flag            ;   //计数器cnt达到最大值的信号
        wire                dec_pos_or_neg_sample   ;   //1 posedge sample, 0 negedge sample
        wire                sclk_posedge            ;   //sclk的上升沿
        wire                sclk_negedge            ;   //sclk的下降沿
    
    
        assign  dec_pos_or_neg_sample = (`CPOL == `CPHA) ? 1'b1 : 1'b0;
    
        assign cnt_max_flag = (cnt == CNT_MAX ) ? 1'b1 : 1'b0;
        assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
        assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
        assign cnt_data = dec_pos_or_neg_sample ? cnt_sclk_pos : cnt_sclk_neg;
    
        always @(posedge clk or negedge rst_n) begin
            sclk_dly <= sclk;
            start_dly <= start;
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                cnt_sclk_pos <= 4'd0;
            end
            else if(state == FINISH) begin
                cnt_sclk_pos <= 4'd0;
            end
            //else if((sclk_posedge) && (cnt_sclk_pos == `DATA_WIDTH - 1)) begin
            //    cnt_sclk_pos <= `DATA_WIDTH - 1;
            //end
            else if(sclk_posedge) begin
                cnt_sclk_pos <= cnt_sclk_pos + 1'b1;
            end
            else begin
                cnt_sclk_pos <= cnt_sclk_pos;
            end
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                cnt_sclk_neg <= 4'd0;
            end
            else if(state == FINISH) begin
                cnt_sclk_neg <= 4'd0;
            end
            //else if((sclk_negedge) && (cnt_sclk_neg == `DATA_WIDTH - 1)) begin
            //    cnt_sclk_neg <= `DATA_WIDTH - 1;
            //end
            else if(sclk_negedge) begin
                cnt_sclk_neg <= cnt_sclk_neg + 1'b1;
            end
            else begin
                cnt_sclk_neg <= cnt_sclk_neg;
            end
        end
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                state <= IDLE;
            end
            else begin
                state <= nx_state;
            end
        end
    
        always @(*) begin
            nx_state <= IDLE;
            case(state)
                IDLE:   nx_state <= start_dly ? DATA : IDLE;
                DATA:   nx_state <= (cnt_data == `DATA_WIDTH) ? (`CPHA == 0) ? EXTRA : FINISH : DATA;
                EXTRA:  nx_state <= cnt_max_flag ? FINISH : EXTRA ;
                FINISH: nx_state <= IDLE;
                default:nx_state <= IDLE;
            endcase
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                cnt <= 'd0;
            end
            else if((state == DATA) && (nx_state == FINISH) && (cnt == CNT_MAX)) begin
                cnt <= 'd0;
            end
            else if((state == DATA) && (nx_state == EXTRA) && (cnt == CNT_MAX)) begin
                cnt <= 'd0;
            end
            else if((state == DATA) && (cnt == CNT_MAX)) begin
                cnt <= 'd0;
            end
            else if((state == EXTRA) && (cnt == CNT_MAX)) begin
                cnt <= 'd0;
            end
            else if(state == DATA) begin
                cnt <= cnt + 1'b1;
            end
            else if(state == EXTRA) begin
                cnt <= cnt + 1'b1;
            end
            else begin
                cnt <= 'd0;
            end
        end 
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                sclk <= (`CPOL) ? 1'b1 : 1'b0;
            end
            else if(start_dly) begin
                sclk <= ~sclk;
            end
            else if((state == DATA) && (cnt_max_flag) && (cnt_data < `DATA_WIDTH) ) begin
                sclk <= ~sclk;
            end
            else if((state == DATA) && (cnt_max_flag) && (cnt_data == `DATA_WIDTH) && (nx_state == EXTRA)) begin
                sclk <= ~sclk;
            end
            else if((state == EXTRA) && (cnt_max_flag) && (nx_state == FINISH)) begin
                sclk <= ~sclk;
            end
            else begin
                sclk <= sclk;
            end
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                ss_n <= 1'b1;
            end
            else if(start) begin
                ss_n <= 1'b0;
            end
            else if(state == FINISH) begin
                ss_n <= 1'b1;
            end
            else begin
                ss_n <= ss_n;
            end
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                cnt_data_dly <= 'd0;
            end
            else begin
                cnt_data_dly <= cnt_data;
            end
        end
    
        assign finish = (state == FINISH) ? 1'b1 : 1'b0;
    
        assign mosi  = (state == DATA) ? ((cnt_data_dly < `DATA_WIDTH) ? data_i[cnt_data_dly] : data_i[`DATA_WIDTH-1]) : data_i[0]; 
    endmodule
    
    
    

    仿真的testbench如下:

    `timescale  1ns/1ns
    `include "defines.v"
    
    module tb_master_slave();
    
        reg                         clk         ;
        reg                         rst_n       ;
        //reg                         mosi        ;
        reg     [`DATA_WIDTH-1:0]   data_i      ;
        reg                         start       ;
        reg                         miso        ;
    
        wire                        mosi        ;
        wire                        sclk        ;
        wire                        finish      ;
        wire                        ss_n        ;
        wire    [`DATA_WIDTH-1:0]   data_o      ;
        wire                        r_finish    ;
    
        SPI_MASTER u_spi_master (
            .clk    (clk)       ,
            .rst_n  (rst_n)     ,
            .miso   (miso)      ,
            .data_i (data_i)    ,
            .start  (start)     ,
    
            .mosi   (mosi)      ,
            .sclk   (sclk)      ,
            .finish (finish)    ,
            .ss_n   (ss_n)
        );
    
        SPI_SLAVE u_spi_slave(
            .clk    (clk)       ,
            .rst_n  (rst_n)     ,
            .mosi   (mosi)      ,
            .sclk   (sclk)      ,
            .tx_finish(finish),
            .start  (start)     ,
            .ss_n   (ss_n)      ,
    
            .data_o (data_o)    ,
            .r_finish(r_finish)
        );
    
        initial begin
             clk = 1'b0;
             rst_n = 1'b0;
             start = 1'b0;
             data_i = 8'h35;
             miso = 1'b0;
             #30
             rst_n = 1'b1;
             #10;
             @(posedge clk);
             start <= 1'b1;
             @(posedge clk);
             start <= 1'b0;
             @(negedge finish);
             data_i = 8'h44;
             repeat(2) @(posedge clk);
             start = 1'b1;
             @(posedge clk);
             start = 1'b0;
        end
    
        always #(`CLK_CYCLE / 2) clk = ~clk;
    
    endmodule
    
    

    仿真波形如下,可以通过mosi成功发出了数据。

    SPI SLAVE设计思路

    SPI SPLAVE的设计思路大体如master类似,端口说明如下:

    信号名 方向 +解释
    clk 输入,时钟信号
    rst_n 输入,复位信号
    miso 输入,从机输入到主机
    data_i 输入,主机发送从机的数据(一定比特位宽)
    start 输入,开始的使能信号
    mosi 输出,主机到从机
    sclk 输入,时钟信号
    ss_n 输入,从机的选择信号
    r_finish 一次传输完成信号
    data_o 输出,收集到的数据

    依然采用的是状态机思路,首先在IDLE状态,当开始信号使能之后,会跳转到RV_DATA接收到数据状态。RV_DATA数据接收完成,之后回跳转到FINISH状态,表明此次读取完成。

    IDLE 空闲状态
    RV_ DATA 接收数据状态
    FINISH 结束状态
    spi slave的代码如下所示:
    `include "defines.v"
    module SPI_SLAVE(
        input   wire                        clk         ,
        input   wire                        rst_n       ,
        input   wire                        mosi        ,
        input   wire                        sclk        ,
        input   wire                        tx_finish   ,
        input   wire                        start       ,
        input   wire                        ss_n        ,
    
        output  wire    [`DATA_WIDTH-1:0]   data_o      ,
        //output  wire                        miso        ,
        output  wire                        r_finish
    );
        parameter       IDLE    =   4'b0001     ,
                        RV_DATA =   4'b0010     ,
                        FINISH  =   4'b0100     ;
    
        wire                        sclk_posedge        ;
        wire                        sclk_negedge        ;
        wire                        dec_pos_or_neg_sample;
        //wire                        sclk_posedge        ;
        //wire                        sclk_negedge        ;
    
    
        reg                         sclk_dly            ;
        reg     [`DATA_WIDTH-1:0]   data_shift_pos      ;
        reg     [`DATA_WIDTH-1:0]   data_shift_neg      ;
        reg     [3:0]               state               ;
        reg     [3:0]               nx_state            ;
        reg     [3:0]               cnt_sclk_pos        ;
        reg     [3:0]               cnt_sclk_neg        ;
        wire    [3:0]               num_sample_data     ;
    
        assign  sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
        assign  sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
        assign  dec_pos_or_neg_sample = (`CPOL == `CPHA) ? 1'b1 : 1'b0;
        //assign  sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
        //assign  sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
        assign  num_sample_data = (dec_pos_or_neg_sample) ? cnt_sclk_pos : cnt_sclk_neg;
        
        always @(posedge clk or negedge rst_n) begin
            sclk_dly <= sclk;
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                state <= IDLE;
            end
            else begin
                state <= nx_state;
            end
        end
    
        always @(*) begin
            nx_state <= IDLE;
            case(state)
                IDLE: nx_state <= start ? RV_DATA :IDLE;
                RV_DATA: begin
                    if((num_sample_data == 7) && (dec_pos_or_neg_sample) && (sclk_posedge) && (!ss_n)) begin
                        nx_state <= FINISH;
                    end
                    else if((num_sample_data == 7) && (~dec_pos_or_neg_sample) && (sclk_negedge) && (!ss_n)) begin
                        nx_state <= FINISH;
                    end
                    else begin
                        nx_state <= RV_DATA;
                    end
                end
                FINISH: nx_state <= IDLE;
            endcase
        end
    
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                cnt_sclk_pos <= 4'd0;
            end
            else if((state == FINISH)) begin
                cnt_sclk_pos <= 4'd0;
            end
            else if(sclk_posedge) begin
                cnt_sclk_pos <= cnt_sclk_pos + 1'b1;
            end
            else begin
                cnt_sclk_pos <= cnt_sclk_pos;
            end
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                cnt_sclk_neg <= 4'd0;
            end
            else if (state == FINISH) begin
                cnt_sclk_neg <= 4'd0;
            end
            else if (sclk_negedge) begin
                cnt_sclk_neg <= cnt_sclk_neg + 1'b1;
            end
            else begin
                cnt_sclk_neg <= cnt_sclk_neg;
            end
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                data_shift_pos <= {`DATA_WIDTH{1'b0}};
            end
            else if((state == RV_DATA) && (sclk_posedge)) begin
                data_shift_pos <= {mosi, data_shift_pos[`DATA_WIDTH-1:1]};
            end
            else if (state == FINISH) begin
                data_shift_pos <= {`DATA_WIDTH{1'b0}};
            end
            else begin
                data_shift_pos <= data_shift_pos;
            end
        end
    
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n) begin
                data_shift_neg <= {`DATA_WIDTH{1'b0}};
            end
            else if((state == RV_DATA) && (sclk_negedge)) begin
                data_shift_neg <= {mosi, data_shift_neg[`DATA_WIDTH-1:1]};
            end
            else if(state == FINISH) begin
                data_shift_neg <= {`DATA_WIDTH{1'b0}};
            end
            else begin
                data_shift_neg <= data_shift_neg;
            end
        end
    
        //assign data_o = dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg;
    
        assign  data_o = (state == FINISH) ? (dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg): {`DATA_WIDTH{1'b0}};
        assign  r_finish = (state == FINISH); 
    
    
    
    
    endmodule
    

    SPI MASTRT 和 SLAVE联合仿真

    testbench如下所示:

    `timescale  1ns/1ns
    `include "defines.v"
    
    module tb_master_slave();
    
        reg                         clk         ;
        reg                         rst_n       ;
        //reg                         mosi        ;
        reg     [`DATA_WIDTH-1:0]   data_i      ;
        reg                         start       ;
        reg                         miso        ;
    
        wire                        mosi        ;
        wire                        sclk        ;
        wire                        finish      ;
        wire                        ss_n        ;
        wire    [`DATA_WIDTH-1:0]   data_o      ;
        wire                        r_finish    ;
    
        SPI_MASTER u_spi_master (
            .clk    (clk)       ,
            .rst_n  (rst_n)     ,
            .miso   (miso)      ,
            .data_i (data_i)    ,
            .start  (start)     ,
    
            .mosi   (mosi)      ,
            .sclk   (sclk)      ,
            .finish (finish)    ,
            .ss_n   (ss_n)
        );
    
        SPI_SLAVE u_spi_slave(
            .clk    (clk)       ,
            .rst_n  (rst_n)     ,
            .mosi   (mosi)      ,
            .sclk   (sclk)      ,
            .tx_finish(finish),
            .start  (start)     ,
            .ss_n   (ss_n)      ,
    
            .data_o (data_o)    ,
            .r_finish(r_finish)
        );
    
        initial begin
             clk = 1'b0;
             rst_n = 1'b0;
             start = 1'b0;
             data_i = 8'h35;
             miso = 1'b0;
             #30
             rst_n = 1'b1;
             #10;
             @(posedge clk);
             start <= 1'b1;
             @(posedge clk);
             start <= 1'b0;
             @(negedge finish);
             data_i = 8'h44;
             repeat(2) @(posedge clk);
             start = 1'b1;
             @(posedge clk);
             start = 1'b0;
        end
    
        always #(`CLK_CYCLE / 2) clk = ~clk;
    
    endmodule
    
    

    仿真的波形如下,可知成功的读取到了8’h35和8’h44。验证成功。

    总结

    通过这次spi的协议的编写,感觉自己对于状态机的掌握,以及脑子里可以浮现波形。加油加油!!!

    物联沃分享整理
    物联沃-IOTWORD物联网 » Verilog实现的SPI Master-Slave联合协议

    发表评论