0
0
VerilogHow-ToBeginner · 4 min read

Verilog Code for SPI Slave: Syntax and Example

An SPI slave in Verilog listens to the SPI clock (SCLK) and chip select (CS) signals to receive and send data on the MOSI and MISO lines. The code typically uses a shift register triggered on clock edges when CS is active to capture incoming bits and send outgoing bits.
📐

Syntax

The SPI slave module usually has inputs for SCLK (clock), CS (chip select), and MOSI (master out slave in), and an output for MISO (master in slave out). Inside, it uses a shift register to receive bits on the rising or falling edge of SCLK when CS is low (active). The module counts bits to know when a full byte is received.

  • SCLK: SPI clock from master
  • CS: Chip select, active low
  • MOSI: Data from master to slave
  • MISO: Data from slave to master
  • Shift register: Stores incoming/outgoing bits
verilog
module spi_slave(
    input wire clk,          // System clock
    input wire rst_n,        // Active low reset
    input wire sclk,         // SPI clock from master
    input wire cs_n,         // Chip select, active low
    input wire mosi,         // Master Out Slave In
    output reg miso,         // Master In Slave Out
    output reg [7:0] data_out, // Received byte
    output reg data_ready    // Flag when byte received
);

    reg [2:0] bit_cnt;       // Counts bits received
    reg [7:0] shift_reg;     // Shift register for input data
    reg sclk_prev;           // Previous SCLK state for edge detection

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bit_cnt <= 0;
            shift_reg <= 0;
            data_out <= 0;
            data_ready <= 0;
            miso <= 0;
            sclk_prev <= 0;
        end else begin
            sclk_prev <= sclk;
            data_ready <= 0; // Clear flag by default

            if (!cs_n) begin // Active when CS is low
                // Detect rising edge of SCLK
                if (sclk_prev == 0 && sclk == 1) begin
                    shift_reg <= {shift_reg[6:0], mosi}; // Shift in MOSI bit
                    bit_cnt <= bit_cnt + 1;

                    if (bit_cnt == 7) begin
                        data_out <= {shift_reg[6:0], mosi};
                        data_ready <= 1; // Byte received
                        bit_cnt <= 0;
                    end
                end
                // Prepare MISO bit (example: send MSB first)
                miso <= shift_reg[7];
            end else begin
                bit_cnt <= 0;
            end
        end
    end
endmodule
💻

Example

This example shows a simple SPI slave that receives 8 bits from the master on the MOSI line and sets a flag data_ready when a full byte is received. It also outputs the received byte on data_out. The MISO line sends back the most significant bit of the shift register.

verilog
module spi_slave_example(
    input wire clk,
    input wire rst_n,
    input wire sclk,
    input wire cs_n,
    input wire mosi,
    output wire miso,
    output wire [7:0] data_out,
    output wire data_ready
);

    reg [7:0] shift_reg = 8'b0;
    reg [2:0] bit_cnt = 3'b0;
    reg sclk_prev = 1'b0;
    reg data_ready_reg = 1'b0;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            shift_reg <= 8'b0;
            bit_cnt <= 3'b0;
            data_ready_reg <= 1'b0;
            sclk_prev <= 1'b0;
        end else begin
            sclk_prev <= sclk;
            data_ready_reg <= 1'b0;

            if (!cs_n) begin
                if (sclk_prev == 0 && sclk == 1) begin
                    shift_reg <= {shift_reg[6:0], mosi};
                    bit_cnt <= bit_cnt + 1;
                    if (bit_cnt == 7) begin
                        data_ready_reg <= 1'b1;
                        bit_cnt <= 3'b0;
                    end
                end
            end else begin
                bit_cnt <= 3'b0;
            end
        end
    end

    assign miso = shift_reg[7];
    assign data_out = shift_reg;
    assign data_ready = data_ready_reg;

endmodule
⚠️

Common Pitfalls

Common mistakes when writing SPI slave code include:

  • Not properly detecting clock edges, causing missed bits.
  • Failing to reset bit counters when CS goes high, leading to incorrect data framing.
  • Not handling the MISO line timing correctly, which can cause data corruption.
  • Ignoring asynchronous signals and not synchronizing them to the system clock, causing metastability.

Always use edge detection on SCLK and reset counters on CS deassertion.

verilog
/* Wrong approach: sampling MOSI without edge detection */
always @(posedge clk) begin
    if (!cs_n) begin
        shift_reg <= {shift_reg[6:0], mosi}; // No clock edge check
    end
end

/* Correct approach: sample MOSI on SCLK rising edge */
always @(posedge clk) begin
    sclk_prev <= sclk;
    if (!cs_n && sclk_prev == 0 && sclk == 1) begin
        shift_reg <= {shift_reg[6:0], mosi};
    end
end
📊

Quick Reference

  • SCLK edge detection: Sample data on rising or falling edge as per SPI mode.
  • CS active low: Only shift data when CS is low.
  • Bit counter: Count bits to know when a full byte is received.
  • Shift register: Use to store incoming and outgoing bits.
  • MISO timing: Prepare output bit before clock edge.

Key Takeaways

Detect SPI clock edges to correctly sample incoming bits.
Reset bit counters when chip select (CS) is inactive.
Use a shift register to collect bits and form bytes.
Drive MISO line with the correct bit timing.
Synchronize asynchronous inputs to avoid timing issues.