Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Design and Implementation of SPI Interface Using Verilog

Tech 1

Overview of SPI Communication Protocol

The Serial Peripheral Interface (SPI) is a synchronous serial communication protocol developed by Motorola. It facilitates high-speed data exchange between a master device and one or more slave devices in full-duplex mode. The interface requires only four signal lines for operation: serial clock (SCLK), master out slave in (MOSI), master in slave out (MISO), and chip select (CS).

SPI Communication Fundamentals

Master-Slave Architecture

SPI operates under a master-slave configuration where one device acts as the master and controls communication with multiple slave devices. The master generates the clock signal (SCLK) that synchronizes data transfers. Slaves cannot produce their own clock and must rely on the master's clock signal.

Synchronous Data Transfer

Data transmission occurs synchronized with the clock signal. Each bits transferred on either the rising or falling edge of the clock depending on the clock polarity (CPOL) and clock phase (CPHA) settings. This ensures proper timing alignment between communicating devices.

Data Exchange Mechanism

During each clock cycle, both devices simultaneously transmit and receive one bit of data. The master selects a specific slave using the CS signal before initiating communication. All received data must be read promptly to prevent loss during subsequent transfers.

SPI Signal Definitions

  • SCLK: Clock signal generated by the master
  • MOSI: Master output, slave input - data transmission from master to slave
  • MISO: Master input, slave output - data trensmission from slave to master
  • CS: Chip select - enables communication with a specific slave device

Clock Configuration Parameters

Clock polarity (CPOL) defines the idle state of the clock:

  • CPOL = 0: Clock idles low
  • CPOL = 1: Clock idles high

Clock phase (CPHA) determines when data is sampled:

  • CPHA = 0: Data sampled on the first clock edge
  • CPHA = 1: Data sampled on the second clock edge

Hardware Implementation Example

The following Verilog module implements an SPI interface capable of handling 24-bit transactions (16-bit command + 8-bit data):

module spi (
    input      reset,
    input      cs,
    input      sclk,
    input      sdi,
    output reg   sdo,
    output reg   wr_rd,
    output reg [14:0] addr,
    input  [7:0] rdata,
    output reg [7:0] wdata
);

reg [2:0] cnt_bit;

always @(posedge sclk or negedge reset) begin
    if (!reset)
        cnt_bit <= 3'b0;
    else if (!cs)
        cnt_bit <= cnt_bit + 3'b1;
    else
        cnt_bit <= 3'b0;
end

wire cnt_full = (cnt_bit == 3'd7);

parameter IDLE    = 2'd0;
parameter INSTR_H = 2'd1;
parameter INSTR_L = 2'd2;
parameter DATA    = 2'd3;

reg [1:0] current_state, next_state;

always @(posedge sclk or negedge reset) begin
    if (!reset)
        current_state <= IDLE;
    else if (cs)
        current_state <= IDLE;
    else
        current_state <= next_state;
end

always @(*) begin
    case (current_state)
        IDLE: begin
            if (cs)
                next_state = IDLE;
            else
                next_state = INSTR_H;
        end
        INSTR_H: begin
            if (cnt_full)
                next_state = INSTR_L;
            else
                next_state = INSTR_H;
        end
        INSTR_L: begin
            if (cnt_full)
                next_state = DATA;
            else
                next_state = INSTR_L;
        end
        DATA: begin
            if (cs)
                next_state = IDLE;
            else
                next_state = DATA;
        end
        default: next_state = IDLE;
    endcase
end

reg [15:0] shift_reg_din;

always @(posedge sclk or negedge reset) begin
    if (!reset)
        shift_reg_din <= 15'd0;
    else if (cs)
        shift_reg_din <= 15'd0;
    else
        shift_reg_din <= {shift_reg_din[14:0], sdi};
end

wire cstate = (current_state == INSTR_L);

reg cnt_full_d, cstate_d;
always @(posedge sclk or negedge reset) begin
    if (!reset) begin
        cnt_full_d <= 0;
        cstate_d <= 0;
    end else begin
        cnt_full_d <= cnt_full;
        cstate_d <= cstate;
    end
end

always @(negedge sclk or negedge reset) begin
    if (!reset)
        addr <= 15'd0;
    else if (cnt_full_d && cstate_d)
        addr <= shift_reg_din[14:0];
    else
        addr <= addr;
end

always @(negedge sclk or negedge reset) begin
    if (!reset)
        wr_rd <= 1'b0;
    else if (cnt_full_d && cstate_d)
        wr_rd <= shift_reg_din[15];
end

always @(posedge cs or negedge reset) begin
    if (!reset)
        wdata <= 8'd0;
    else
        wdata <= shift_reg_din[7:0];
end

reg [7:0] shift_reg_dout;

always @(negedge sclk or negedge reset) begin
    if (!reset)
        shift_reg_dout <= 8'b0;
    else if ((cnt_bit == 3'd0) && (current_state == DATA))
        shift_reg_dout <= rdata;
    else
        shift_reg_dout <= {shift_reg_dout[6:0], 1'b0};
end

assign sdo = ((!wr_rd) && (!cs)) ? shift_reg_dout[7] : 1'b0;

endmodule

Testbench Implementation

The testbench simulates both read and write operations:

module spi_tb();
reg reset;
reg cs;
reg sclk;
reg sdi;
wiresdo;
reg [7:0] rdata;
wires wr_rd;
wires [14:0] addr;
wires [7:0] wdata;

spi u0(reset, cs, sclk, sdi, sdo, wr_rd, addr, rdata, wdata);

always #5 sclk = ~sclk;

initial begin
    sclk = 0;
    reset = 0;
    cs = 1;
    sdi = 0;
    rdata = 8'b00001111;
    
    #27 reset = 1;
    #14 cs = 0;
    sdi = 1; // Write operation
    
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(11) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(1) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(2) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(1) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(1) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(1) @(negedge sclk);
    sdi = 1;
    #11 cs = 1;
    
    repeat(5) @(negedge sclk);
    cs = 0;
    sdi = 0; // Read operation
    
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(11) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(1) @(negedge sclk);
    sdi = 1;
    repeat(1) @(negedge sclk);
    sdi = 0;
    repeat(1) @(negedge sclk);
    sdi = 1'bz;
    repeat(7) @(negedge sclk);
    #11 cs = 1;
    
    #200 $stop;
end

endmodule

The simulation demonstrates successful execution of both read and write operations through proper state machine control and bit-level data handling.

Tags: SPI

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.