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:
- Synchronous communication, with the working clock sourced from the master’s sclk
- Supports all 4 SPI modes, configurable via the SPI_MODE parameter;
- Data width is configurable through the DATA_WIDTH parameter (1-32 bits);
- Uses asynchronous reset design.
2. Module Block Diagram
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
-
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 -
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 ------------------------------------------------------------ -
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.

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