🔄 第12课:SPI通信

四线同步协议 + CPOL/CPHA模式

🏆 成就:SPI骑士 ✅ Verilator验证

🔄
SPI骑士
Verilator仿真SPI主机读写从机

🔌 SPI四线

SCLK(时钟)+MOSI(主出从入)+MISO(主入从出)+CS_N(片选)。Mode 0最常用:SCLK空闲低,上升沿采样,MSB first。

Verilogspi_master.v核心
localparam SPI_DIV=25; // 50MHz→1MHz SPI
always @(posedge clk) case(state)
    0: if(spi_start) begin cs_n<=0; tx_shift<=tx_data; state<=1; end
    1: if(clk_div==SPI_DIV-1) begin
        clk_div<=0; sclk_reg<=~sclk_reg; sclk<=~sclk_reg;
        if(sclk_reg) begin mosi<=tx_shift[7]; tx_shift<=tx_shift<<1; end
        else begin // 上升沿采样MISO
            rx_shift<={rx_shift[6:0],miso};
            if(bit_cnt==7) state<=2; else bit_cnt<=bit_cnt+1;
        end
    end else clk_div<=clk_div+1;
    2: begin cs_n<=1; done_pulse<=1; state<=0; end
endcase
协议线数时钟双工速率
SPI4同步全双工
UART2异步全双工
I2C2同步半双工

📐 SPI Mode 0 时序图

Mode 0 (CPOL=0, CPHA=0) — 最常用: CS_N ──┐ ┌── │ │ SCLK │ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ └──┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──┘ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 采样 采样 采样 采样 采样 采样 采样 采样 (上升沿) MOSI ──┤D7│D6│D5│D4│D3│D2│D1│D0├── MISO ──┤D7│D6│D5│D4│D3│D2│D1│D0├── MSB ←────── 8位数据 ──────→ LSB 全双工!主机发MOSI同时读MISO 一次传输完成读写!

📐 SPI四种模式

模式CPOLCPHASCLK空闲采样边沿常用设备
Mode 000上升沿SPI Flash
Mode 101下降沿部分ADC
Mode 210下降沿少数
Mode 311上升沿SD卡

🧪 Verilator测试台

SystemVerilogspi_master_tb.sv
module spi_master_tb;
    logic clk,rst_n; logic [7:0] tx_data,rx_data;
    logic spi_start,spi_busy,spi_done;
    logic sclk,mosi,miso,cs_n;
    spi_master uut(.*);
    initial clk=0; always #10 clk=~clk;

    // 从机回环
    reg [7:0] slave_shift;
    always @(posedge sclk) slave_shift<={slave_shift[6:0],mosi};
    assign miso=slave_shift[7];

    task spi_transfer(input [7:0] data);
        @(posedge clk); tx_data=data; spi_start=1;
        @(posedge clk); spi_start=0;
        wait(spi_done);
        $display("  发送:0x%02H, 接收:0x%02H",data,rx_data);
        #1000;
    endtask

    initial begin
        rst_n=0;#500;rst_n=1; slave_shift=8'hAB;
        $display("========== SPI主机验证 ==========");
        spi_transfer(8'hA5);
        spi_transfer(8'h3C);
        spi_transfer(8'hFF);
        $display("==========================================");
        $finish;
    end
    initial #10_000_000 $finish;
endmodule

📊 SPI vs UART vs I2C 对比

特性SPIUARTI2C
线数422
时钟同步异步同步
双工全双工全双工半双工
速率高(10MHz+)中(~1Mbps)低(~3.4Mbps)
多从机每从机1CS点对点7/10位地址
适用Flash/ADC调试/蓝牙传感器/EEPROM

📐 SPI vs I2C vs UART 全面对比

SPI: 4线, 全双工, 同步, 10MHz+ ┌──────┐SCLK MOSI MISO CS┌──────┐ │Master├──→──→──→──→──→──┤Slave │ │(FPGA)│←──←──←──←──←──←─┤(Flash)│ └──────┘ └──────┘ 最快!但线多 I2C: 2线, 半双工, 同步, 400Kbps~3.4Mbps ┌──────┐SCL SDA┌──────┐ │Master├──→────┤Slave │ │(FPGA)│←──────┤(传感器)│ └──────┘ └──────┘ 最省线!但速度慢 UART: 2线, 全双工, 异步, ~1Mbps ┌──────┐TX RX┌──────┐ │Device├──→───┤Device│ │ A │←─────┤ B │ └──────┘ └──────┘ 无时钟线!双方约定波特率

🎯 SPI实际应用

1. SPI Flash — W25Q系列,存储固件/数据

2. ADC/DAC — MCP3008(ADC), MCP4922(DAC)

3. OLED显示 — SSD1306 SPI模式

4. SD卡 — SPI模式初始化

5. 无线模块 — nRF24L01

🛠️ 编译运行

Bash编译运行步骤
verilator --binary -j 0 --trace spi_master.v spi_master_tb.sv
./obj_dir/Vspi_master_tb

# 预期输出:
# ========== SPI主机验证 ==========
#   发送:0xA5, 接收:0x?? (从机回环数据)
#   发送:0x3C, 接收:0x??
#   发送:0xFF, 接收:0x??
# ==========================================

# 提示:在GTKWave中查看SCLK, MOSI, MISO, CS_N四条信号线
# 可以清楚看到SPI Mode 0的完整时序

🧠 概念检查清单

✅ SPI的四根信号线分别起什么作用?

✅ CPOL和CPHA如何影响时钟极性和采样边沿?

✅ Mode 0的空闲SCLK状态和采样边沿?

✅ SPI是MSB first而UART是LSB first——为什么?

✅ CS_N片选信号的作用?

✅ SPI vs UART vs I2C各自适用场景?

✅ 为什么SPI速度最快?

🔮 下一课预告

下一课学习I2C通信——只需要2根线!开漏输出+上拉电阻=线与逻辑,7位地址寻址,ACK握手。

🐛 常见问题排查

Q: 从机无响应? 检查CS_N是否为低(选中),SCLK频率是否在从机支持范围内。

Q: 数据移位方向错误? SPI是MSB first!tx_shift[7]先发,左移。和UART的LSB first相反。

Q: Mode不对? 确认设备数据手册指定CPOL/CPHA。Mode 0最常见。

📏 SPI关键参数

参数说明
信号线4根SCLK+MOSI+MISO+CS
数据顺序MSB first最高位先发
双工方式全双工同时收发
常见速率1~50MHz远快于I2C
CS极性低有效CS=0选中
空闲SCLK(Mode0)CPOL=0
采样边沿(Mode0)上升沿CPHA=0

💡 SPI为什么最快?:①全双工——同时收发数据 ②推挽输出驱动——比I2C开漏快得多 ③无需地址/ACK开销——CS直接选设备 ④时钟可达50MHz+。代价是线多——每加一个从设备就要多一根CS线。

📐 SPI四种模式

模式CPOLCPHASCLK空闲采样边沿
Mode 000上升沿
Mode 101下降沿
Mode 210下降沿
Mode 311上升沿

🧪 Verilator测试台

SystemVerilogspi_master_tb.sv片段
// 简单SPI从机回环
reg [7:0] slave_shift;
always @(posedge sclk) slave_shift<={slave_shift[6:0],mosi};
assign miso=slave_shift[7];

task spi_transfer(input [7:0] data);
    @(posedge clk); tx_data=data; spi_start=1;
    @(posedge clk); spi_start=0;
    wait(spi_done);
    $display("发送:0x%02H, 接收:0x%02H",data,rx_data);
endtask

📐 SPI Mode 0 时序图

Mode 0 (CPOL=0, CPHA=0): CS_N ──┐ ┌── │ │ SCLK │ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │ └──┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──┘ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 采样 采样 采样 采样 采样 采样 采样 采样 (上升沿) MOSI ──┤D7│D6│D5│D4│D3│D2│D1│D0├── MISO ──┤D7│D6│D5│D4│D3│D2│D1│D0├── MSB ←────── 8位数据 ──────→ LSB 全双工!主机发MOSI同时读MISO 一次传输完成读写!