_TOP_MENU

Feb 18, 2025

SPI controller verilog code

 Creating an SPI (Serial Peripheral Interface) controller in Verilog requires the design of a master or slave SPI interface that allows communication with peripheral devices. In this example, I'll focus on creating a simple SPI master controller, which can communicate with an SPI slave device.

The basic functionality of an SPI master includes:

  • Clock generation: A clock signal (SCK) is used to synchronize data transfer between the master and slave.
  • Data transfer: Data is transmitted and received using two signals: MOSI (Master Out Slave In) for sending data and MISO (Master In Slave Out) for receiving data.
  • Chip Select: A signal (CS) that activates the slave device for communication.

SPI Master Controller in Verilog

module spi_master(
    input wire clk,         // System clock
    input wire rst,         // Reset
    input wire start,       // Start signal to initiate SPI transfer
    input wire [7:0] data_in,  // 8-bit data to send to the slave
    output reg mosi,        // Master Out Slave In
    output reg sck,         // SPI Clock
    output reg cs,          // Chip Select
    output reg [7:0] data_out, // Data received from the slave
    output reg busy         // Busy signal indicating SPI is active
);

    reg [7:0] shift_reg;  // Shift register to hold data for transmission
    reg [3:0] bit_count;  // Bit counter (counts up to 8 bits)
    reg [7:0] rx_shift_reg;  // Shift register to store received data

    // SPI state machine
    typedef enum reg [1:0] {
        IDLE = 2'b00,
        TRANSFER = 2'b01,
        FINISH = 2'b10
    } state_t;
    
    state_t state, next_state;

    // SPI clock divider (Optional for controlling SPI speed)
    reg [7:0] clk_div_counter;
    reg clk_div;

    // Clock division for SPI clock generation (assuming 8x clock speed)
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            clk_div_counter <= 8'b0;
            clk_div <= 0;
        end else begin
            if (clk_div_counter == 8'b11111111) begin
                clk_div <= ~clk_div;
                clk_div_counter <= 0;
            end else begin
                clk_div_counter <= clk_div_counter + 1;
            end
        end
    end

    // State machine for SPI operation
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            state <= IDLE;
            bit_count <= 0;
            shift_reg <= 0;
            rx_shift_reg <= 0;
            mosi <= 0;
            sck <= 0;
            cs <= 1;
            busy <= 0;
        end else begin
            state <= next_state;
        end
    end

    // Next state logic
    always @(*) begin
        case(state)
            IDLE: begin
                if (start) begin
                    next_state = TRANSFER;
                end else begin
                    next_state = IDLE;
                end
            end
            TRANSFER: begin
                if (bit_count == 8) begin
                    next_state = FINISH;
                end else begin
                    next_state = TRANSFER;
                end
            end
            FINISH: begin
                next_state = IDLE;
            end
            default: begin
                next_state = IDLE;
            end
        endcase
    end

    // SPI data transfer and clock generation
    always @(posedge clk_div or posedge rst) begin
        if (rst) begin
            shift_reg <= 0;
            bit_count <= 0;
            mosi <= 0;
            sck <= 0;
            cs <= 1;
            busy <= 0;
        end else begin
            case (state)
                IDLE: begin
                    cs <= 1;  // Chip Select is high (inactive)
                    busy <= 0;
                    mosi <= 0;
                    sck <= 0;
                end

                TRANSFER: begin
                    cs <= 0;  // Chip Select is low (active)
                    busy <= 1;
                    mosi <= shift_reg[7];  // Transmit the most significant bit first
                    shift_reg <= {shift_reg[6:0], 1'b0}; // Shift left by 1 bit
                    sck <= ~sck;  // Toggle the clock
                    if (sck) begin
                        // Capture the MISO data when clock goes high
                        rx_shift_reg <= {rx_shift_reg[6:0], mosi}; 
                        bit_count <= bit_count + 1;
                    end
                end

                FINISH: begin
                    cs <= 1;  // Deassert chip select
                    busy <= 0;
                    data_out <= rx_shift_reg;  // Capture received data
                    bit_count <= 0;
                    shift_reg <= 0;
                    rx_shift_reg <= 0;
                end

                default: begin
                    cs <= 1;
                    busy <= 0;
                    mosi <= 0;
                    sck <= 0;
                end
            endcase
        end
    end

    // Assign the final received data to data_out
    always @(posedge sck or posedge rst) begin
        if (rst) begin
            data_out <= 8'b0;
        end else if (bit_count == 8) begin
            data_out <= rx_shift_reg;  // Store the received data when transfer is complete
        end
    end

endmodule

Click here to download the verilog RTL code

Explanation of the Code:

  1. Inputs and Outputs:

    • clk: System clock.
    • rst: Reset signal.
    • start: A signal to start the SPI communication (initiates the transfer).
    • data_in: 8-bit data to send to the SPI slave.
    • mosi: Master Out Slave In signal (used to send data to the slave).
    • sck: SPI Clock (generated by the master to synchronize data transfer).
    • cs: Chip Select (selects the SPI slave for communication).
    • data_out: 8-bit data received from the SPI slave.
    • busy: Indicates that the SPI controller is in the process of communication.
  2. Shift Register:

    • The shift_reg holds the data to be transmitted, and rx_shift_reg holds the data received from the slave.
    • Data is transmitted/received bit by bit with each clock cycle.
  3. State Machine:

    • The state machine has three states:
      • IDLE: No communication is happening, waiting for the start signal.
      • TRANSFER: The actual data transfer occurs here; the data is shifted out and received in.
      • FINISH: The transfer is complete, and chip select is deactivated.
  4. Clock Divider (optional):

    • The SPI clock (sck) is generated by dividing the system clock (clk), which controls the speed of the SPI interface.
    • clk_div_counter counts cycles of the system clock to toggle the sck signal, which controls data synchronization between master and slave.
  5. Data Transfer:

    • On each clock cycle, the most significant bit (MSB) of the shift_reg is shifted out via mosi, and incoming data on miso is shifted into rx_shift_reg.
    • The sck signal toggles every clock cycle, ensuring the data is shifted on both edges of the clock.
  6. Chip Select and Busy Signals:

    • The chip select (cs) is active low during the transfer. It is asserted (cs = 0) during the TRANSFER state and deasserted (cs = 1) once the communication is complete.
    • The busy signal is used to indicate that the SPI controller is actively transmitting or receiving data.

How it works:

  • When the start signal is asserted, the SPI master will begin the transfer.
  • The mosi line transmits the data_in 8 bits serially.
  • The sck toggles during the transfer, and the slave responds on the miso line.
  • After 8 clock cycles (one byte), the received data is stored in data_out.

SPI Master Controller Features:

  • Supports full-duplex data transfer.
  • SPI clock (sck) is generated internally.
  • Chip select (cs) is managed to select the slave.
  • Handles 8-bit data transfer.

This basic SPI master controller can be further extended to support features like variable data widths, multiple slaves, or different clock polarity (CPOL) and phase (CPHA) settings, depending on the specific application requirements.

No comments:

Post a Comment