返回模板库
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