跳到主要内容
返回模板库

SPI 主机控制器SPI Master Controller

基于 janschiefer/verilog_spi 的简化 SPI 主机 FSM,支持 CPOL/CPHA 四种模式。

SPImasterverilog
开源标注
许可证: LGPL-2.1
Author: Jan Schiefer
Copyright (C) 2018 Jan Schiefer

Key Points

  • - Supports SPI Mode 0/1/2/3 via CPOL/CPHA parameters
  • - Clock divider generates SCLK from system clock
  • - MSB-first shift register for TX/RX
  • - Active-low slave select (SS_N)

RTL Source

// Copyright (C) 2018 Jan Schiefer
// SPDX-License-Identifier: LGPL-2.1
// Source: https://github.com/janschiefer/verilog_spi
// Simplified SPI Master FSM for educational use
// Based on spi_module from verilog_spi project
// 中文:简化 SPI 主机 FSM,基于 janschiefer/verilog_spi 项目

`timescale 1ns / 1ps

// SPI Master FSM Module
// 中文:SPI 主机控制器,支持 CPOL/CPHA 四种模式
// Supports all 4 SPI modes via CPOL/CPHA parameters:
//   Mode 0: CPOL=0, CPHA=0 - sample on rising edge
//   Mode 1: CPOL=0, CPHA=1 - sample on falling edge
//   Mode 2: CPOL=1, CPHA=0 - sample on rising edge
//   Mode 3: CPOL=1, CPHA=1 - sample on falling edge
module spi_master_fsm #(
    parameter CPOL = 1'b0,          // Clock polarity (idle level)
    parameter CPHA = 1'b0,          // Clock phase (sample edge select)
    parameter SPI_WORD_LEN = 8      // Data word width
)(
    input  wire                  clk,        // System clock
    input  wire                  rst,        // Synchronous reset
    // Control interface
    input  wire                  start,      // Start SPI transaction
    input  wire [SPI_WORD_LEN-1:0] tx_data,  // Data to transmit
    output reg  [SPI_WORD_LEN-1:0] rx_data,  // Received data
    output reg                   busy,       // Transaction in progress
    output reg                   done,       // Transaction complete
    // SPI bus interface
    output reg                   sclk,       // SPI clock output
    output reg                   ss_n,       // Slave select (active low)
    output reg                   mosi,       // Master Out Slave In
    input  wire                  miso        // Master In Slave Out
);

    // Clock divider for SPI frequency
    localparam CLK_DIV = 4;  // SCLK = clk / (2 * CLK_DIV)

    // FSM state encoding
    localparam [2:0] S_IDLE   = 3'd0,  // Idle, SPI bus inactive
                     S_START  = 3'd1,  // Assert SS, prepare first edge
                     S_SHIFT  = 3'd2,  // Shift data bits
                     S_SAMPLE = 3'd3,  // Sample MISO
                     S_DONE   = 3'd4;  // Deassert SS, signal done

    reg [2:0]  state, next_state;
    reg [3:0]  bit_cnt;          // Bit counter (0 to SPI_WORD_LEN-1)
    reg [3:0]  clk_cnt;          // Clock divider counter
    reg        clk_toggle;       // SCLK edge toggle flag
    reg [SPI_WORD_LEN-1:0] shift_reg;  // TX shift register

    // Determine sampling edge based on CPOL/CPHA
    // CPOL XOR CPHA = 0: sample on rising edge (Mode 0, 2)
    // CPOL XOR CPHA = 1: sample on falling edge (Mode 1, 3)
    wire sample_on_rising = (CPOL ^ CPHA);

    // Sequential: state register and counters
    always @(posedge clk) begin
        if (rst) begin
            state      <= S_IDLE;
            bit_cnt    <= 0;
            clk_cnt    <= 0;
            clk_toggle <= 0;
            shift_reg  <= 0;
            rx_data    <= 0;
            busy       <= 0;
            done       <= 0;
            sclk       <= CPOL;   // Idle clock level = CPOL
            ss_n       <= 1;      // SS inactive high
            mosi       <= 0;
        end else begin
            state <= next_state;

            // Clock divider counter
            if (state == S_SHIFT || state == S_SAMPLE) begin
                if (clk_cnt >= CLK_DIV - 1) begin
                    clk_cnt    <= 0;
                    clk_toggle <= ~clk_toggle;
                end else begin
                    clk_cnt <= clk_cnt + 1;
                end
            end else begin
                clk_cnt <= 0;
            end
        end
    end

    // Combinational: next-state and output logic
    always @(*) begin
        next_state = state;
        case (state)
            S_IDLE: begin
                if (start)
                    next_state = S_START;
            end

            S_START: begin
                // Assert SS and begin clocking
                if (clk_cnt >= CLK_DIV - 1)
                    next_state = S_SHIFT;
            end

            S_SHIFT: begin
                // Shift out TX data on appropriate edge
                if (clk_toggle) begin
                    if (bit_cnt >= SPI_WORD_LEN)
                        next_state = S_DONE;
                    else
                        next_state = S_SAMPLE;
                end
            end

            S_SAMPLE: begin
                // Sample MISO on appropriate edge
                if (clk_toggle) begin
                    next_state = S_SHIFT;
                end
            end

            S_DONE: begin
                next_state = S_IDLE;
            end

            default: next_state = S_IDLE;
        endcase
    end

    // Output control based on state
    always @(posedge clk) begin
        if (rst) begin
            ss_n  <= 1;
            sclk  <= CPOL;
            mosi  <= 0;
            busy  <= 0;
            done  <= 0;
        end else begin
            case (state)
                S_IDLE: begin
                    ss_n <= 1;
                    sclk <= CPOL;
                    busy <= 0;
                    done <= 0;
                end

                S_START: begin
                    ss_n <= 0;       // Assert slave select
                    busy <= 1;
                    // Set first clock edge based on CPHA
                    if (CPHA)
                        sclk <= ~CPOL;
                    else
                        sclk <= CPOL;
                end

                S_SHIFT: begin
                    if (clk_toggle) begin
                        // Shift out next bit (MSB first)
                        mosi    <= shift_reg[SPI_WORD_LEN - 1 - bit_cnt];
                        sclk    <= ~sclk;
                        bit_cnt <= bit_cnt + 1;
                    end
                end

                S_SAMPLE: begin
                    if (clk_toggle) begin
                        // Sample MISO
                        rx_data <= {rx_data[SPI_WORD_LEN-2:0], miso};
                        sclk    <= ~sclk;
                    end
                end

                S_DONE: begin
                    ss_n <= 1;       // Deassert slave select
                    done <= 1;
                    busy <= 0;
                end

                default: begin
                    ss_n <= 1;
                    sclk <= CPOL;
                end
            endcase
        end
    end

    // Load TX data into shift register on start
    always @(posedge clk) begin
        if (start && state == S_IDLE)
            shift_reg <= tx_data;
    end

endmodule