Verilog Functional Module – SPI Master and Slave (03) – Design Ideas and Code Analysis for SPI Slave

Verilog Functional Module - SPI Master and Slave (03) - Design Ideas and Code Analysis for SPI Slave

Introduction

The previous article introduced the Verilog functional module – SPI Master, including design ideas and usage methods.

This article designs a fully functional 4-wire SPI Slave using pure Verilog. Unlike some online simulations of slaves with high-frequency clock signals, the SPI Slave in this article derives its working clock from the master’s sclk, adhering to the principles of SPI synchronous communication.

This article details the module coding ideas and usage precautions, and finally shares the source code.

1. Module Functionality

This Verilog functional module – SPI Slave implements the complete timing control required by the SPI protocol, with the following specific functionalities:

  1. Synchronous communication, with the working clock sourced from the master’s sclk
  2. Supports all 4 SPI modes, configurable via the SPI_MODE parameter;
  3. Data width is configurable through the DATA_WIDTH parameter (1-32 bits);
  4. Uses asynchronous reset design.

2. Module Block Diagram

Verilog Functional Module - SPI Master and Slave (03) - Design Ideas and Code Analysis for SPI Slave

3. Signal Interfaces

3.1 Parameter List

Parameter Name Type Default Value Description
SPI_MODE integer 3 SPI mode, options are 0, 1, 2, 3 (default)
DATA_WIDTH integer 16 Bit width for data sent or received in a single communication, minimum is 2, common values are 8/16

3.2 Interface Signal List

Signal Group Signal Name Direction Description
External Control SPI Signals spi_slave_tx_is_busy output Indicates SPI is busy, high level means SPI is working
spi_slave_tx_data[DATA_WIDTH-1:0] input Data to be sent via SPI, data is always sent with the highest bit first
spi_slave_rx_data[DATA_WIDTH-1:0] output Data received via SPI, the first data read is the highest bit
spi_slave_rx_data_valid output Indicates received data is valid, high level is valid
SPI Hardwired Connections spi_cs_n input Chip select, low level is valid
spi_sclk input SPI clock, provided by the slave
spi_mosi input Slave output to slave input
spi_miso output Slave input to slave output
Reset arstn input Asynchronous reset, low level is valid

4. Coding Ideas

  1. Control SPI mode, data width, clock frequency, and three timing parameters through parameters

    parameter integer SPI_MODE   = 3, // SPI mode, options are 0, 1, 2, 3 (default)
    parameter integer DATA_WIDTH = 16 // Bit width for data sent or received in a single communication, minimum is 1, common values are 8/16
    
  2. Perform validity checks on parameters, restricting parameter assignments

    //++ Parameter validity check ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    initial begin
      if (SPI_MODE != 0 && SPI_MODE != 1 && SPI_MODE != 2 && SPI_MODE != 3)
        $error("SPI_MODE must be 0, 1, 2, 3");
      if (DATA_WIDTH <= 0)
        $error("DATA_WIDTH must be >= 1");
    end
    //-- Parameter validity check ------------------------------------------------------------
    
  3. Store the previous state of cs_n signal to control the loading of sent data

    //++ Chip select state tracking ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    reg spi_cs_n_old; // Previous state of chip select before transition, will change to new value after transition ends
    always @(posedge spi_cs_n or negedge spi_cs_n or negedge arstn) begin
    if (~arstn)
        spi_cs_n_old <= 1'b1;
    else
        spi_cs_n_old <= spi_cs_n;
    end
    //-- Chip select state tracking ------------------------------------------------------------
    
    
    //++ Sending data ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    /*
    The principle is to sample first and then shift
    */
    reg [$clog2(DATA_WIDTH+1)-1:0] sample_cnt; // Sample count
    reg [DATA_WIDTH-1:0] tx_data_lsfr;// Shift register
    
    generate
    if (SPI_MODE == 0 || SPI_MODE == 3) begin// Shift on falling edge, first shift on the first falling edge
    always @(negedge spi_sclk or negedge spi_cs_n) begin
        if (spi_cs_n_old)
          tx_data_lsfr <= spi_slave_tx_data;  // Load sent data on falling edge of chip select
        elseif (~spi_cs_n && sample_cnt != 'd0)
          tx_data_lsfr <= tx_data_lsfr << 1;
        else
          tx_data_lsfr <= tx_data_lsfr;
    end
    end else begin// Shift on rising edge, do not shift on the first rising edge
    always @(posedge spi_sclk or negedge spi_cs_n) begin
        if (spi_cs_n_old)
          tx_data_lsfr <= spi_slave_tx_data;  // Load sent data on falling edge of chip select
        elseif (~spi_cs_n && sample_cnt != 'd0)
          tx_data_lsfr <= tx_data_lsfr << 1;
        else
          tx_data_lsfr <= tx_data_lsfr;
    end
    end
    endgenerate
    
    assign spi_miso = spi_slave_tx_is_busy ? tx_data_lsfr[DATA_WIDTH-1] : 1'bz; // Tri-state output control
    //-- Sending data ------------------------------------------------------------
    

    Note here the spi_cs_n_old, it is the most critical code for the SPI synchronous slave. If you use <span>~spi_cs_n</span>, then during the entire transmission, spi_cs_n will be low, and subsequent code cannot execute. Therefore, you can only use spi_cs_n_old, which only executes once on the falling edge of spi_cs_n.

5. Usage Instructions

// External control signals for SPI Slave
output wire spi_slave_tx_is_busy, // Indicates SPI Slave is sending, high level is valid
input  wire [DATA_WIDTH-1:0] spi_slave_tx_data,        // Data to be sent
output reg  [DATA_WIDTH-1:0] spi_slave_rx_data,        // Data received
output reg                   spi_slave_rx_data_valid,  // Indicates received data is valid

The external module only needs to control spi_slave_tx_data, this signal will be latched on each falling edge of spi_cs_n.

6. Simulation Verification

See this series of articles – Verilog Functional Module – SPI Slave and Slave (04) – SPI Slave Loopback Simulation and Measurement.

7. Source Code Sharing

The source code is open source on Gitee and Github, synchronized on both platforms:

Gitee: Verilog Functional Module – SPI Master and Slave: Verilog Functional Module – SPI Master and Slave https://gitee.com/xuxiaokang/verilog-function-module–SPI-Master-Slave

Github: zhengzhideakang/Verilog–SPI-Master-Slave: verilog-function-module–SPI-Master-Slave https://github.com/zhengzhideakang/Verilog–SPI-Master-Slave

If this article has been helpful to you, feel free to like, share, bookmark, and comment to let more people see it. Support through appreciation is even better.

If you have any questions about the content of the article, please be sure to describe the issue clearly, leave a comment or message me, and I will reply when I see it.

Verilog Functional Module - SPI Master and Slave (03) - Design Ideas and Code Analysis for SPI Slave

Xu Xiaokang’s Blog continues to share high-quality hardware, FPGA, and embedded knowledge, software, tools, and more. Everyone is welcome to follow.

Leave a Comment