0
0
VerilogHow-ToBeginner · 4 min read

Verilog Code for SPI Master: Syntax, Example, and Tips

An SPI master in Verilog controls the clock, data out (MOSI), and chip select signals to communicate with SPI slaves. Use always blocks to generate the clock and shift data bits out on MOSI, while reading data on MISO. The chip select (CS) signal enables the slave device during communication.
📐

Syntax

The SPI master module typically includes inputs for clock and reset, outputs for MOSI (Master Out Slave In), SCLK (SPI Clock), and CS (Chip Select), and input for MISO (Master In Slave Out). Inside, always blocks generate the SPI clock and control data shifting.

  • clk: System clock input.
  • rst: Reset signal to initialize states.
  • mosi: Data output to slave.
  • miso: Data input from slave.
  • sclk: SPI clock generated by master.
  • cs: Chip select to enable slave.
verilog
module spi_master(
    input wire clk,       // System clock
    input wire rst,       // Reset
    input wire miso,      // Master In Slave Out
    output reg mosi,      // Master Out Slave In
    output reg sclk,      // SPI Clock
    output reg cs         // Chip Select
);

// SPI clock divider and control logic here

endmodule
💻

Example

This example shows a simple SPI master that sends 8 bits of data to a slave device. It generates the SPI clock, controls chip select, and shifts data out on MOSI. It also reads data from MISO during the transfer.

verilog
module spi_master(
    input wire clk,       // System clock
    input wire rst,       // Reset
    input wire miso,      // Master In Slave Out
    output reg mosi,      // Master Out Slave In
    output reg sclk,      // SPI Clock
    output reg cs         // Chip Select
);

    reg [7:0] data_to_send = 8'b10101010; // Example data
    reg [7:0] received_data = 8'b0;
    reg [2:0] bit_cnt = 3'd7;
    reg [1:0] clk_div = 2'd0;
    reg spi_clk_en = 0;

    // SPI clock generation (divide system clock by 4)
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            clk_div <= 0;
            sclk <= 0;
        end else if (spi_clk_en) begin
            clk_div <= clk_div + 1;
            if (clk_div == 2) sclk <= ~sclk;
        end else begin
            sclk <= 0;
            clk_div <= 0;
        end
    end

    // SPI state machine
    reg [1:0] state = 0;
    localparam IDLE = 2'd0, TRANSFER = 2'd1, DONE = 2'd2;

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            cs <= 1;
            mosi <= 0;
            bit_cnt <= 7;
            spi_clk_en <= 0;
            state <= IDLE;
            received_data <= 0;
        end else begin
            case(state)
                IDLE: begin
                    cs <= 1;          // Disable slave
                    spi_clk_en <= 0;  // Stop SPI clock
                    sclk <= 0;
                    if (1) begin      // Start condition (could be a trigger signal)
                        cs <= 0;      // Enable slave
                        spi_clk_en <= 1;
                        state <= TRANSFER;
                        bit_cnt <= 7;
                        mosi <= data_to_send[7];
                    end
                end
                TRANSFER: begin
                    // On falling edge of sclk, shift data
                    if (clk_div == 2 && sclk == 1) begin
                        received_data[bit_cnt] <= miso; // Sample MISO
                        if (bit_cnt == 0) begin
                            state <= DONE;
                            spi_clk_en <= 0;
                            cs <= 1; // Disable slave
                        end else begin
                            bit_cnt <= bit_cnt - 1;
                            mosi <= data_to_send[bit_cnt - 1];
                        end
                    end
                end
                DONE: begin
                    // Transfer complete, stay here or go to IDLE
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule
Output
No direct console output; SPI signals generated on outputs: sclk toggles, cs goes low during transfer, mosi sends bits, received_data captures MISO bits.
⚠️

Common Pitfalls

Common mistakes when writing SPI master code include:

  • Not properly controlling the chip select (cs) signal, which must be low during the entire data transfer.
  • Incorrect SPI clock generation causing timing errors.
  • Shifting data on the wrong clock edge, leading to data corruption.
  • Not sampling miso at the correct time (usually on the opposite clock edge of data output).

Always verify clock polarity and phase match the slave device requirements.

verilog
/* Wrong: Shifting data on rising edge but sampling MISO on same edge */
always @(posedge sclk) begin
    mosi <= data_to_send[bit_cnt];
    received_data[bit_cnt] <= miso; // Sampling on same edge as output
end

/* Right: Shift MOSI on one edge, sample MISO on opposite edge */
always @(negedge sclk) begin
    mosi <= data_to_send[bit_cnt];
end

always @(posedge sclk) begin
    received_data[bit_cnt] <= miso;
end
📊

Quick Reference

  • CS (Chip Select): Active low signal to enable slave device.
  • SCLK (SPI Clock): Generated by master, controls timing.
  • MOSI: Data sent from master to slave.
  • MISO: Data received from slave to master.
  • Clock edges: Data is usually changed on one clock edge and sampled on the opposite edge.
  • Reset: Initialize all signals and counters before starting transfer.

Key Takeaways

Control chip select (CS) carefully to enable the slave only during data transfer.
Generate SPI clock (SCLK) by dividing system clock and toggle it properly.
Shift data out on MOSI on one clock edge and sample MISO on the opposite edge.
Use a state machine to manage SPI transfer phases: idle, transfer, done.
Reset all signals and counters to known states before starting communication.