The verilog implementation of the SPI protocol (spi master slave joint implementation)

SPI protocol introduction

SPI is the abbreviation of serial peripheral interface, which is the serial expansion bus. SPI is a single-master device communication. There is only one master device in the bus that initiates communication. The device that can initiate communication is called the master device. When the SPI master device wants to read and write from the slave device, it first pulls down the ss line of the corresponding slave device (low level is active). Then send the working wheat seed to the clock line. At the corresponding pulse time, the master device sends the signal to MOSI to realize reading and writing, and at the same time, it can sample MISO to realize reading. Generally, SPI communication involves the following terms:

SCLK serial clock (from master device)
MOSI Master Output Slave Input (from master device)
MISO Master Input Slave Output (from slave device)
SS Slave Select (active low, from master device)

Two link methods of master device and slave device

One master and one slave

A master-slave mode indicates that there is a master and a slave, as shown below: (there is also an SCK signal, from the master to the slave)
Insert image description here

One master, many slaves

One master and multiple slaves means that there is one master and one slave. The only difference is that each slave should be equipped with a selection signal.
Insert image description here

Working mode of SPI protocol

SPI has four working modes, which are mainly determined by the combination of clock polarity CPOL (Clock Polarity) and clock phase CPHA (Clock Phase).

  • If CPOL is 0, it means that SCK is 0 in the idle state. If it is 1, it means SCK is 1 in the idle state.
  • When CPHA is 0, it means that the output data is valid on the first edge of SCK, and when CPHA is 1, it means that the input and output data are valid on the second edge of SCK

CPOL = 0, CPHA = 0

Insert image description here

CPOL = 0, CPHA = 1

Insert image description here

CPOL = 1. CPHA = 0

Insert image description here

CPOL = 1, CPHA = 1

Insert image description here

Verilog design ideas of SPI MASTER

Design pin description:

Signal name directions + explanation
clk input, clock signal
rst_n input, reset signal
miso Input, slave input to host
data_i Input, the master sends data from the slave (certain bit width)
start Input, enable signal to start
mosi Output, master to slave
sclk output, clock signal
ss_n Output, slave selection signal
finish One transfer complete signal

First, include the required macro definitions in a file defines.v, the code in which is as follows:

`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

The second is the design idea of ​​SPI MASTER. The general idea is to use a state machine. First, the state machine is in the IDLE state. Then after receiving the start signal, the data in the register data_I will be sent out one by one. When the specified wide-spaced bits are sent after finishing. There are two options at this time, one is to jump to the FINISH state, the other is to jump to the EXTRA state, and then jump to the FINISH state. Mainly because after the data transmission is completed, it is necessary to determine whether the SCLK state at this time is the default state in idle mode. If not, it needs to jump to the EXTRA state. After the end state, the next state returns to the IDLE state and the start command is obtained.

IDLE idle state
DATA Send data status
EXTRA extra status
FINISH end state

The verilog code is as follows:

`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


The simulated testbench is as follows:

`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

The simulation waveform is as follows, and the data can be successfully sent through mosi.
Insert image description here

SPI SLAVE design ideas

The design idea of ​​SPI SPLAVE is generally similar to that of master. The port description is as follows:

Signal name directions + explanation
clk input, clock signal
rst_n input, reset signal
miso Input, slave input to host
data_i Input, the master sends data from the slave (certain bit width)
start Input, enable signal to start
mosi Output, master to slave
sclk input, clock signal
ss_n Input, slave selection signal
r_finish One transfer complete signal
date_o Output, collected data

The state machine idea is still adopted. First, in the IDLE state, when the start signal is enabled, it will jump to the RV_DATA state of receiving data. The RV_DATA data reception is completed, and then jumps back to the FINISH state, indicating that the reading is complete.

IDLE idle state
RV_ DATA Receive data status
FINISH end state
The code of spi slave is as follows:
`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 and SLAVE joint simulation

The testbench looks like this:

`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

The simulated waveform is as follows, it can be seen that 8'h35 and 8'h44 are successfully read. Verification successful.
Insert image description here
Insert image description here

Summarize

Through the writing of the spi protocol this time, I feel that I have mastered the state machine, and the waveform can emerge in my mind. come on! Come on! ! !

Guess you like

Origin blog.csdn.net/weixin_45614076/article/details/126887159
SPI