Verilog implements SPI protocol

Posted by rane500 on Thu, 25 Jun 2020 05:54:50 +0200

There are many tutorials about SPI. Here is a summary of my study of SPI protocol.

What is SPI?

SPI is the abbreviation of Serial Peripheral Interface Bus, which means serial peripheral interface. It is a synchronous serial communication interface standard for short distance communication, mainly used in embedded system. This interface is Motorola Developed in late 1980, it became a conventional communication standard. SPI protocol uses the Master slave structure of a single Master to work in a full duplex way. The Master device controls reading and writing, and multiple slave devices are connected through chip selection signal (SS).

Interface

 
SPI structure

SPI communication equipment usually only needs four lines to complete the data transmission. Therefore, this advantage of taking up less port resources is also known as a bright spot of SPI protocol.

  • SCLK: serial clock, output by Master, receive SCLK signal from slave. It controls the beat of data transmission, and then affects the speed of data exchange.

  • MOSI: (Master output Slave input) literally, this line is the data output port of the host and the data input port of the slave. (in fact, I think it's better to divide the MOSI into MO and SI)

  • MISO: (Master input Slave output) Master in and slave out, that is, master in and slave out.

  • SS: (Slave Select) chip select signal. Only when the SS signal on the Slave is valid, the Slave is selected.

 
Typical master-slave structure

working process

In essence, SPI communication process is data exchange. In the process of data exchange, data transmission and reception are completed.
The host controls the generation of SS signal and SCLK signal. When the SS signal is valid, the corresponding slave is selected. Complete the data exchange under the rhythm of SCLK.

 
SPI data exchange

Because of the different forms of SCLK, SPI can be divided into four working modes, which are controlled by CPOL and CPHA. That is, the polarity and phase of the serial clock SCLK.

SPI mode Clock polarity (CPOL) Clock phase (CPHA)
0 0 0
1 0 1
2 1 0
3 1 1

For the convenience of discussion, a mode is given to explain how SPI works.

The following is the sequence diagram of SPI protocol to complete data exchange.

 
Timing

In the case of effective SS, the host outputs data (write) through MOSI at the front edge of SCLK and samples data (read) through MISO at the back edge of SCLK. For slave, similarly, the leading edge of SCLK outputs data through MISO. The back edge of SCLK samples the data through the MOSI.

In this way, a SCLK clock cycle can complete 1 bit of data output and 1 bit of data read in, making efficient use of the clock resources.

In fact, the Verilog code can be written according to the sequence diagram, and the data transmission of the master can be completed after some setbacks. At the same time, through the test bench, the simulation of SPI protocol is completed. Some codes give some explanation.

spi_master.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date:    10:41:00 07/29/2017 
// Design Name: 
// Module Name:    spi_master 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module spi_master(
    input wire[7:0] in_data,
    input wire clk,
    input wire[1:0] addr, // commonds
    input wire wr,
    input wire rd,
    input wire cs,
    output reg[7:0] out_data,
    inout mosi,
    input miso,
    inout sclk
    );
    
    // --------define internal register and buffer--------
    // output buffer stage
    reg sclk_buf = 0;
    reg mosi_buf = 0;
    // idle flag , busy = 0 if no data to receive or send , or else set busy = 1
    reg busy = 0;
    // shift register
    reg[7:0] in_buf = 0;
    reg[7:0] out_buf = 0;

    reg[7:0] clk_cnt = 0;
    // division of clk , clk_div=0 means that clk is not be divide , and modify it could implement corresponding sck for device
    reg[7:0] clk_div = 0;
    
    reg[4:0] cnt = 0;
    // --------------------------------------------------

    // the port of module links internal buffer
    assign sclk = sclk_buf;
    assign mosi = mosi_buf;

    //sclk positive edge read data into out-shift register from miso , implement read operation
    always @(posedge sclk_buf) begin
        out_buf[0] <= miso;
        out_buf <= out_buf << 1;
    end 

    // read data (combinatorial logic that level sensitive , detect all input)
    always @(cs or wr or rd or addr or out_buf or busy or clk_div) begin
        out_data = 8'bx;
        if (cs && rd) begin
            case(addr)
                2'b00 : out_data = out_buf;
                2'b01 : out_data = {7'b0 , busy}; // when send data encounter spi is busy , return busy singal 
                2'b10 : out_data = clk_div;
                default : out_data = out_data;
            endcase
        end
    end
    
    // sclk negitive edge write data to mosi
    always @(posedge clk) begin
        if (!busy) begin // idle state load data into send buffer
            if(cs && wr) begin
                case(addr) // commonds
                    2'b00 : begin
                        in_buf <= in_data;
                        busy <= 1;
                        cnt <= 0;
                    end
                    2'b10 : begin
                        in_buf <= clk_div; // load number of division to slave for implement sync of sclk
                    end
                    default : in_buf <= in_buf; 
                endcase
            end
            else if(cs && rd) begin
                busy <= 1;
                cnt <= 0;
            end
        end
        else begin // when 8-bits data write into buffer ,  begin send with bit by bit
            clk_cnt <= clk_cnt + 1;
            if (clk_cnt >= clk_div) begin // divide clk
                clk_cnt <= 0;

                if (cnt % 2 == 0) begin // when csk_buf is negitive , shift data into mosi buffer
                    mosi_buf <= in_buf[7];
                    in_buf <= in_buf << 1;
                end 
                else begin
                    mosi_buf <= mosi_buf;
                end

                if (cnt > 0 && cnt < 17) begin
                    sclk_buf <= ~sclk_buf;
                end

                // 8-bits had sent over , spi regain idle
                if (cnt >= 17) begin 
                    cnt <= 0;
                    busy <= 0;
                end
                else begin
                    cnt <= cnt;
                    busy <= busy;
                end

                cnt <= cnt + 1;
            end
        end
    end


    


endmodule

testbench.v

`timescale 1ns / 1ps

////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer:
//
// Create Date:   13:12:38 07/29/2017
// Design Name:   spi_master
// Module Name:   E:/ISEProjece/SPI/spi_master_tb.v
// Project Name:  SPI
// Target Device:  
// Tool versions:  
// Description: 
//
// Verilog Test Fixture created by ISE for module: spi_master
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
////////////////////////////////////////////////////////////////////////////////

module spi_master_tb;

    // Inputs
    reg [7:0] in_data;
    reg clk;
    reg [1:0] addr;
    reg wr;
    reg rd;
    reg cs;
    reg miso;

    // Outputs
    wire [7:0] out_data;

    // Bidirs
    wire mosi;
    wire sclk;

    // Instantiate the Unit Under Test (UUT)
    spi_master uut (
        .in_data(in_data), 
        .clk(clk), 
        .addr(addr), 
        .wr(wr), 
        .rd(rd), 
        .cs(cs), 
        .out_data(out_data), 
        .mosi(mosi), 
        .miso(miso), 
        .sclk(sclk)
    );

    initial begin
        // Initialize Inputs
        in_data = 0;
        clk = 0;
        addr = 0;
        wr = 0;
        rd = 0;
        cs = 0;
        miso = 0;

        // set clk_div , and out by out_data
        #40;
        addr = 0;
        in_data = 8'haa;
        wr = 1;
        cs = 1;
        
        // write data 
        #20 ;
        wr = 0;
        cs = 0;

        #360 ;
        wr = 1;
        cs = 1;
        in_data = 8'h91;

        #20 ;
        wr = 0;
        cs = 0;
    end

    // define clock
    initial begin
        clk = 0;
        forever #10 clk = ~clk;
    end
endmodule

For SPI details, see SPI protocol.
Code hosted in https://github.com/caxElva/SPI.



Author: Mr. an_
Link: https://www.jianshu.com/p/9f011f119198
Source: Jianshu
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.

Topics: Programming Verilog less github