FPGA SPI Communication Example

“SPI is a synchronous serial communication interface that achieves full-duplex transmission with 4 lines. FPGA can flexibly adapt to different rates and communication modes. This article first explains its core principles and four protocol modes, then provides a loopback test scheme for Mode 0, including sending, receiving, and top-level module design and constraints, simulating to verify timing compliance and data consistency, providing a reliable reference for SPI applications.”

01

SPI Communication Principles

SPI (Serial Peripheral Interface) is a synchronous serial communication interface that relies on the master device (such as FPGA) to generate a synchronous clock SCLK, controlling full-duplex transmission of data between the master output (MOSI, Master Out Slave In) and the slave output (MISO, Master In Slave Out). The chip select signal SS (active low) selects the target slave device, avoiding data interference among multiple slave devices. The SPI controller implemented in FPGA consists of a clock divider unit, a serial-to-parallel shift module for sending, and a parallel-to-serial shift module for receiving. It requires only 4 signal lines (SCLK, MOSI, MISO, SS) to complete data interaction, supporting low-speed peripheral control (such as driving a digital tube) and high-speed data acquisition (such as ADC sampling), and can flexibly adapt to different transmission rates and communication modes through logic code.

FPGA SPI Communication Example

02

SPI Communication Protocol

The SPI protocol defines four communication modes through the combination of clock polarity (CPOL) and clock phase (CPHA), ensuring timing synchronization between the master and slave devices. CPOL determines the idle state level of SCLK (0 for low, 1 for high), while CPHA determines the data sampling edge (0 for the first clock edge, 1 for the second clock edge); the communication process is initiated by pulling SS low, sampling/updating data on the specified edge of SCLK, and ends when SS is pulled high. A single byte transmission includes 1 start bit (SS low), 8 data bits, and 1 stop bit (SS high), supporting continuous multi-byte transmission.

FPGA SPI Communication ExampleFPGA SPI Communication ExampleFPGA SPI Communication Example

03

SPI Loopback Test Case

This case implements reliable communication verification conforming to SPI Mode 0 (CPOL=0, CPHA=0): the top-level module precisely controls the chip select timing through a state machine (pulling spi_ss_out low to select the slave device before sending, and pulling it high to deselect after sending), avoiding interference from unrelated data; the sending module converts incrementing data (0→1→2…) into serial signals output from spi_mosi_out, while the receiving module receives data through spi_miso_in in a loopback manner, activating the receiving logic only when the chip select is low, and finally indicating valid data reception through the spi_rx_valid_out signal.

FPGA SPI Communication Example

1. SPI Sending Module (ui_mspi_tx.v)

module uimspi_tx#(    parameter CLK_DIV = 100,  // System clock division factor: CLK_DIV = System clock/SPI clock - 1    parameter CPOL    = 1'b0, // SPI clock polarity: 0=idle low, 1=idle high    parameter CPHA    = 1'b0  // SPI clock phase: 0=sample on first edge, 1=sample on second edge)(    input  wire        clk_in,         // System clock input    input  wire        rstn,          // Reset signal, active low    output reg         spi_mosi_out,  // SPI data output (MOSI)    output reg         spi_sclk_out,  // SPI clock output    input  wire        spi_tx_req_in, // SPI send request input    input  wire [7:0]  spi_tx_data_in,// SPI parallel send data input    output reg         spi_busy_out   // SPI send busy signal output);localparam LP_SPI_DIV    = CLK_DIV;       // Total SPI clock division factorlocalparam LP_SPI_DIV_HALF = LP_SPI_DIV / 2; // SPI clock half-cycle division factorlocalparam LP_TX_BIT_CNT_MAX = 4'd8;     // Maximum number of bits to send (8 bits data)reg [9:0]  clk_div_r;       // Clock division registerreg        spi_en_r;        // SPI send enable registerreg [3:0]  tx_bit_cnt_r;    // Bit count register for sending (0~8)reg [7:0]  spi_tx_data_r;   // SPI send data buffer registerwire       clk_en_half;     // Half-cycle clock enable (rising edge)wire       clk_en_full;     // Full-cycle clock enable (falling edge)wire       clk_end;         // Send end flagreg        spi_strobe_en_r; // SPI data shift enable registerwire       spi_strobe;      // SPI data shift trigger signalassign clk_en_half = (clk_div_r == LP_SPI_DIV_HALF);assign clk_en_full = (clk_div_r == LP_SPI_DIV);assign clk_end = clk_en_half && (tx_bit_cnt_r == LP_TX_BIT_CNT_MAX);// Clock division logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        clk_div_r <= 10'd0;    end else if (spi_en_r) begin        clk_div_r <= (clk_div_r < LP_SPI_DIV) ? (clk_div_r + 1'b1) : 10'd0;    end else begin        clk_div_r <= 10'd0;    endend// SPI clock generation logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_sclk_out <= CPOL;    end else if (spi_en_r) begin        spi_sclk_out <= clk_en_full ? 1'b0 : (clk_en_half && (tx_bit_cnt_r < LP_TX_BIT_CNT_MAX)) ? 1'b1 : spi_sclk_out;    end else begin        spi_sclk_out <= CPOL;    endend// Bit count logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn || !spi_en_r) begin        tx_bit_cnt_r <= 4'd0;    end else if (clk_en_half) begin        tx_bit_cnt_r <= tx_bit_cnt_r + 1'b1;    endend// Data shift enable logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_strobe_en_r <= 1'b0;    end else if (tx_bit_cnt_r < LP_TX_BIT_CNT_MAX) begin        spi_strobe_en_r <= (clk_en_half) ? 1'b1 : spi_strobe_en_r;    end else begin        spi_strobe_en_r <= 1'b0;    endend// SPI data shift logicassign spi_strobe = CPHA ? (clk_en_half && spi_strobe_en_r) : (clk_en_full && spi_strobe_en_r);always @(posedge clk_in or negedge rstn) begin    if (!rstn || clk_end) begin        spi_en_r <= 1'b0;        spi_tx_data_r <= 8'h00;    end else if (spi_tx_req_in && !spi_en_r) begin        spi_en_r <= 1'b1;        spi_tx_data_r <= spi_tx_data_in;    end else if (spi_en_r && spi_strobe) begin        spi_tx_data_r <= {spi_tx_data_r[6:0], 1'b1};    endend// Busy signal output logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_busy_out <= 1'b0;    end else begin        spi_busy_out <= spi_en_r;    endend// Data output driveralways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_mosi_out <= 1'b1;    end else begin        spi_mosi_out <= spi_tx_data_r[7];    endendendmodule

2. SPI Receiving Module (uispi_rx.v)

module uispi_rx#(    parameter BITS_LEN = 8,  // Receiving data width (default 8 bits)    parameter CPOL    = 1'b0, // SPI clock polarity (consistent with sender)    parameter CPHA    = 1'b0  // SPI clock phase (consistent with sender))(    input  wire                 clk_in,         // System clock input    input  wire                 rstn,          // Reset signal, active low    input  wire                 spi_clk_in,    // SPI clock input (from master device)    input  wire                 spi_rx_in,     // SPI serial data input (MISO)    input  wire                 spi_ss_in,     // SPI chip select input (active low)    output reg                  spi_rvalid_out,// SPI received data valid output    output reg [BITS_LEN-1:0]   spi_rdata_out  // SPI parallel received data output);reg         spi_cap_en_r;    // SPI data sampling enable registerreg [3:0]   spi_clk_sync_r;  // SPI clock synchronization register (4-stage sync)reg [4:0]   rx_bit_cnt_r;    // Bit count register for receiving (0~BITS_LEN)reg [BITS_LEN-1:0] spi_rx_buf_r; // SPI received data buffer registerreg [3:0]   spi_ss_sync_r;   // SPI chip select synchronization register (4-stage sync)wire        spi_rx_en;       // SPI receive enable (active low after chip select sync)wire        spi_clk_pos;     // SPI clock rising edge detectionwire        spi_clk_neg;     // SPI clock falling edge detection// Asynchronous signal synchronization logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_clk_sync_r <= 4'd0;        spi_ss_sync_r <= 4'd0;    end else begin        spi_clk_sync_r <= {spi_clk_sync_r[2:0], spi_clk_in};        spi_ss_sync_r <= {spi_ss_sync_r[2:0], spi_ss_in};    endendassign spi_clk_pos = (spi_clk_sync_r[3:2] == 2'b01);assign spi_clk_neg = (spi_clk_sync_r[3:2] == 2'b10);assign spi_rx_en = ~spi_ss_sync_r[3];// Sampling enable generation logicalways @(*) begin    if (CPHA) begin        spi_cap_en_r = (CPOL) ? spi_clk_pos : spi_clk_neg;    end else begin        spi_cap_en_r = (CPOL) ? spi_clk_neg : spi_clk_pos;    endend// Receiving bit count logicalways @(posedge clk_in) begin    if (!spi_rx_en || (rx_bit_cnt_r == BITS_LEN)) begin        rx_bit_cnt_r <= 5'd0;    end else if (spi_rx_en && spi_cap_en_r) begin        rx_bit_cnt_r <= rx_bit_cnt_r + 1'b1;    endend// Data serial-to-parallel conversion logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_rx_buf_r <= {BITS_LEN{1'b0}};    end else if (spi_rx_en && spi_cap_en_r) begin        spi_rx_buf_r <= {spi_rx_buf_r[BITS_LEN-2:0], spi_rx_in};    end else if (!spi_rx_en) begin        spi_rx_buf_r <= {BITS_LEN{1'b0}};    endend// Received data output logicalways @(posedge clk_in or negedge rstn) begin    if (!rstn) begin        spi_rdata_out <= {BITS_LEN{1'b0}};        spi_rvalid_out <= 1'b0;    end else begin        if (rx_bit_cnt_r == BITS_LEN) begin            spi_rdata_out <= spi_rx_buf_r;            spi_rvalid_out <= 1'b1;        end else begin            spi_rvalid_out <= 1'b0;        end    endendendmodule

3. Top-Level Module (spi_loop_top.v)

module spi_master_top#(    parameter CLK_DIV = 10  // SPI clock division factor, System clock/CLK_DIV=SPI clock frequency)(    input  wire sysclk_in,       // System clock input, recommended 100MHz    input  wire rstn,            // Global reset input, active low    output wire spi_sclk_out,    // SPI master clock output    input  wire spi_sclk_in,     // SPI slave clock input (loopback connects to spi_sclk_out)    output wire spi_mosi_out,    // SPI master data output (Master Out Slave In)    input  wire spi_miso_in,     // SPI master data input (Master In Slave Out)    output wire spi_ss_out       // SPI master chip select output, active low (new signal));// -------------------------- Internal Signal Declaration --------------------------wire        spi_busy;          // SPI send busy signal (from sending module)reg         spi_tx_req;        // SPI send request (drives sending module)reg [7:0]   spi_tx_data_r;     // SPI send data register (stores data to be sent)reg [1:0]   state_spi_tx;      // SPI sending state machinereg         spi_ss_r;          // SPI chip select register (active low to select slave device), synthesis keepwire        spi_rx_valid;      // SPI received data valid signal (from receiving module), synthesis keepwire [7:0]  spi_rx_data;       // SPI received data (from receiving module), synthesis keepreg [10:0]  delay_cnt_r;       // Reset delay counter (to avoid unstable power-on reset)wire        delay_done;        // Delay completion flag (delay_cnt_r[10] set to 1 when complete)// -------------------------- Reset Delay Logic --------------------------assign delay_done = delay_cnt_r[10];  // Delay is complete when the 10th bit of the counter is set to 1always @(posedge sysclk_in or negedge rstn) begin    if (!rstn) begin        delay_cnt_r <= 11'd0;  // Clear counter when reset is active    end else if (!delay_done) begin        delay_cnt_r <= delay_cnt_r + 1'b1;  // Increment counter until delay is complete    end else begin        delay_cnt_r <= delay_cnt_r;  // Keep counter at maximum value after delay is complete    endend// -------------------------- SPI Sending State Machine (including SPI_SS logic) --------------------------// State definitions: S_IDLE(0)→S_SS_LOW(1)→S_TX_START(2)→S_SS_HIGH(3)→S_TX_WAIT(4)localparam LP_STATE_IDLE    = 2'd0;  // Idle state: waiting for delay to complete and SPI not busylocalparam LP_STATE_SS_LOW  = 2'd1;  // Chip select low: pull spi_ss_out low to select slave devicelocalparam LP_STATE_TX_START= 2'd2;  // Send start: generate send request, send incrementing datalocalparam LP_STATE_SS_HIGH = 2'd3;  // Chip select high: pull spi_ss_out high after sendinglocalparam LP_STATE_TX_WAIT = 2'd4;  // Wait for sending: wait for sending module to set busy signal before clearing requestalways @(posedge sysclk_in or negedge rstn) begin    if (!rstn) begin  // Initialize state and registers when reset is active        spi_ss_r     <= 1'b1;        // Chip select default high (not selecting slave device)        spi_tx_req   <= 1'b0;        // Send request default low        spi_tx_data_r <= 8'd0;       // Initialize send data to 0        state_spi_tx <= LP_STATE_IDLE;// Initialize state machine to idle state    end else begin        case (state_spi_tx)            LP_STATE_IDLE: begin                // Enter chip select low state when delay is complete and SPI sending module is not busy                if (delay_done && !spi_busy) begin                    spi_ss_r     <= 1'b0;        // Pull chip select low, select slave device                    state_spi_tx <= LP_STATE_SS_LOW;                end            end            LP_STATE_SS_LOW: begin                // After chip select is low, directly enter send start state (ensure chip select is stable before sending)                spi_tx_req   <= 1'b1;        // Set send request                spi_tx_data_r <= spi_tx_data_r + 1'b1;  // Increment send data (0→1→2...)                state_spi_tx <= LP_STATE_TX_START;            end            LP_STATE_TX_START: begin                // Clear send request when busy signal from sending module is detected, enter chip select high state                if (spi_busy) begin                    spi_tx_req   <= 1'b0;        // Clear send request                    state_spi_tx <= LP_STATE_SS_HIGH;                end            end            LP_STATE_SS_HIGH: begin                // After chip select is high, return to idle state, prepare for next send                spi_ss_r     <= 1'b0;                        state_spi_tx <= LP_STATE_TX_WAIT;            end            LP_STATE_TX_WAIT: begin                // After sending module is idle, return to initial idle state                if (!spi_busy) begin                    state_spi_tx <= LP_STATE_IDLE;                    spi_ss_r     <= 1'b1;        // Pull chip select high, deselect slave device                end            end            default: begin  // Return to idle state in case of abnormal state                state_spi_tx <= LP_STATE_IDLE;            end        endcase    endend// -------------------------- SPI_SS Output Driver (new) --------------------------// Function: Output chip select register value to spi_ss_out pin, active lowassign spi_ss_out = spi_ss_r;// -------------------------- Instantiate SPI Sending Module --------------------------uimspi_tx#(    .CLK_DIV(CLK_DIV),  // Clock division factor, pass top-level parameters    .CPOL(1'b0),        // SPI clock polarity: 0 (idle low)    .CPHA(1'b0)         // SPI clock phase: 0 (sample on first rising edge)) uimspi_tx_inst(    .clk_in(sysclk_in),         // System clock input    .rstn(rstn),                // Reset signal (active low)    .spi_mosi_out(spi_mosi_out),// SPI data output (MOSI)    .spi_sclk_out(spi_sclk_out),// SPI clock output    .spi_tx_req_in(spi_tx_req), // SPI send request input    .spi_tx_data_in(spi_tx_data_r),// SPI send data input    .spi_busy_out(spi_busy)     // SPI send busy signal output);// -------------------------- Instantiate SPI Receiving Module --------------------------uispi_rx#(    .BITS_LEN(8),       // Receiving data width: 8 bits    .CPOL(1'b0),        // SPI clock polarity: consistent with sender (0)    .CPHA(1'b0)         // SPI clock phase: consistent with sender (0)) uispi_rx_inst(    .clk_in(sysclk_in),         // System clock input    .rstn(rstn),                // Reset signal (active low)    .spi_clk_in(spi_sclk_in),   // SPI clock input (loopback connects to sending clock)    .spi_rx_in(spi_miso_in),    // SPI data input (MISO)    .spi_ss_in(spi_ss_out),     // SPI chip select input (consistent with sending chip select, new connection)    .spi_rvalid_out(spi_rx_valid),// SPI received data valid output    .spi_rdata_out(spi_rx_data) // SPI received data output);endmodule

4. Timing Constraint File (fpga_pin.xdc)

# -------------------------- System Clock Constraints --------------------------create_clock -period 10.000 -name sysclk -waveform {0.000 5.000} [get_ports sysclk_in]set_property PACKAGE_PIN L5 [get_ports sysclk_in]set_property IOSTANDARD LVCMOS33 [get_ports sysclk_in]# -------------------------- Reset Signal Constraints --------------------------set_property PACKAGE_PIN H5 [get_ports rstn]set_property IOSTANDARD LVCMOS18 [get_ports rstn]# -------------------------- SPI Signal Constraints --------------------------# SPI_MISO input pin: AB12, LVCMOS18set_property PACKAGE_PIN AB12 [get_ports spi_miso_in]set_property IOSTANDARD LVCMOS18 [get_ports spi_miso_in]# SPI_MOSI output pin: AA12, LVCMOS18set_property PACKAGE_PIN AA12 [get_ports spi_mosi_out]set_property IOSTANDARD LVCMOS18 [get_ports spi_mosi_out]# SPI_SCLK_IN input pin: AB11, LVCMOS18set_property PACKAGE_PIN AB11 [get_ports spi_sclk_in]set_property IOSTANDARD LVCMOS18 [get_ports spi_sclk_in]# SPI_SCLK_OUT output pin: AA11, LVCMOS18set_property PACKAGE_PIN AA11 [get_ports spi_sclk_out]set_property IOSTANDARD LVCMOS18 [get_ports spi_sclk_out]# SPI_SS_OUT output pin: AA10, LVCMOS18 (active low)set_property PACKAGE_PIN AA10 [get_ports spi_ss_out]set_property IOSTANDARD LVCMOS18 [get_ports spi_ss_out]# -------------------------- BIT File Compression Constraints --------------------------set_property CFGBVS VCCO [current_design]set_property CONFIG_VOLTAGE 3.3 [current_design]set_property BITSTREAM.GENERAL.COMPRESS true [current_design]

5. Simulation Test File (tb_spi_loop.v)

`timescale 1ns / 1ps  // Simulation time scale: 1ns unit, 1ps precisionmodule tb_spi_loop;localparam LP_CLK_PERIOD = 10;  // System clock period, 10ns (100MHz)localparam LP_CLK_DIV    = 100; // SPI clock division factor, consistent with top-level module// Simulation signal declaration (new spi_ss_out)reg         sysclk_in;       // Simulation system clockreg         rstn;            // Simulation reset signalwire        spi_sclk_out;    // Simulation SPI clock outputwire        spi_sclk_in;     // Simulation SPI clock input (loopback)wire        spi_mosi_out;    // Simulation SPI data output (MOSI)wire        spi_miso_in;     // Simulation SPI data input (MISO, loopback)wire        spi_ss_out;      // Simulation SPI chip select output (new observation signal)// Loopback connectionassign spi_miso_in = spi_mosi_out;  // MOSI→MISO loopbackassign spi_sclk_in = spi_sclk_out;  // SCLK_OUT→SCLK_IN loopback// Top-level module instantiation (new spi_ss_out port)spi_master_top#(    .CLK_DIV(LP_CLK_DIV)) spi_master_top_inst(    .sysclk_in(sysclk_in),    .rstn(rstn),    .spi_sclk_out(spi_sclk_out),    .spi_sclk_in(spi_sclk_in),    .spi_mosi_out(spi_mosi_out),    .spi_miso_in(spi_miso_in),    .spi_ss_out(spi_ss_out)  // New chip select output port);// Simulation clock generationalways #(LP_CLK_PERIOD / 2) sysclk_in = ~sysclk_in;// Simulation stimulus generationinitial begin    sysclk_in = 1'b0;    rstn = 1'b0;    #100;  // Hold reset for 100ns    rstn = 1'b1;    #2000000;  // Simulation lasts for 2ms    $finish;  // End simulationendendmodule

04

Simulation Results

The system clock sysclk_in outputs stably at 100MHz (period 10ns), the SPI clock spi_sclk_out is at 1MHz (100 division) and is low when idle (consistent with CPOL=0); spi_ss_out is pulled low before sending (selecting the slave device), remains low during the transmission of 8 bits of data, and is immediately pulled high after sending, with no glitches in timing; spi_mosi_out outputs incrementing serial data, and after receiving through the loopback, the parallel data output from spi_rx_data_out is completely consistent with the sent data, with spi_rx_valid_out set high for one clock cycle at the end of each byte reception.

FPGA SPI Communication Example

Leave a Comment