FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

Verilog 实现 SPI 主设备接口:从零开始的设计与验证指南

二牛学FPGA二牛学FPGA
技术分享
4小时前
0
0
1

Quick Start

安装 Vivado 或 Quartus(推荐 Vivado 2020.1+),新建一个 RTL 工程。创建一个顶层模块 spi_master,定义端口:clkrst_nstartdata_in[7:0]sclkmosics_nbusy。编写一个简单的 SPI 主设备控制状态机(IDLE → LOAD → SHIFT → DONE),实现 8 位数据发送。编写 testbench,实例化 spi_master,驱动 clk 50 MHz、rst_n 低电平复位,然后置高并触发 start 脉冲。在 testbench 中模拟 SPI 从设备(例如用 miso 回环到 mosi 或固定输入)。运行仿真(Vivado Simulator 或 Modelsim),观察 sclk、cs_n、mosi、busy 波形。验证 cs_n 在传输期间为低,sclk 产生 8 个脉冲,mosi 按 MSB-first 输出 data_in。若波形正确,将设计综合并查看资源与 Fmax 报告;若需要上板,连接 SPI 从设备(如 ADC/DAC)并观察通信。

前置条件与环境

项目/推荐值说明替代方案
器件/板卡Xilinx Artix-7 (XC7A35T) 或等效Intel Cyclone IV / Lattice iCE40
EDA 版本Vivado 2020.1 或更高Quartus Prime 18.0+ / Modelsim
仿真器Vivado Simulator 或 Modelsim SE-64Verilator (仅仿真)
时钟/复位50 MHz 系统时钟,异步低电平复位100 MHz 时钟需调整分频系数
接口依赖SPI 从设备(如 AD7940)或回环测试无外设时用 testbench 模拟 MISO
约束文件XDC 或 SDC:时钟周期 20 ns,I/O 标准 LVCMOS33无约束也可仿真,但上板必须

目标与验收标准

  • 功能点:SPI 主设备能发送 8 位数据,产生正确的 CS、SCLK、MOSI 时序(模式 0:CPOL=0, CPHA=0)。
  • 性能指标:SCLK 频率 ≤ 系统时钟/4(例如 50 MHz 时钟下 SCLK=12.5 MHz);传输完成后 busy 信号拉低。
  • 资源/Fmax:综合后 LUT ≤ 50,FF ≤ 40;Fmax ≥ 150 MHz(以 Artix-7 为例)。
  • 关键波形/日志:仿真中 cs_n 在传输期间保持低电平,结束后拉高;sclk 在传输期间连续 8 个脉冲,空闲时为低。
  • 验收方式:运行 testbench 并通过 $display 打印发送与接收数据,比对一致;上板后用逻辑分析仪抓取 SPI 波形。

实施步骤

工程结构与模块划分

创建工程目录:spi_master/rtlspi_master/simspi_master/constr。顶层模块 spi_master 包含:时钟分频器(产生 SCLK)、状态机控制器、移位寄存器。建议将分频器与状态机分开为两个模块(clk_divspi_controller),便于复用。

关键模块:时钟分频器

module clk_div #(
    parameter DIV = 4 // 分频系数,SCLK = clk / DIV
) (
    input wire clk,
    input wire rst_n,
    output reg sclk_en // 高电平有效使能,用于状态机
);
    reg [7:0] cnt;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt <= 0;
            sclk_en <= 0;
        end else if (cnt == (DIV/2 - 1)) begin
            cnt <= 0;
            sclk_en <= 1;
        end else begin
            cnt <= cnt + 1;
            sclk_en <= 0;
        end
    end
endmodule

用途:产生 SCLK 的使能信号,状态机在 sclk_en 有效时进行移位。注意 DIV 必须是偶数,否则占空比偏差。

关键模块:SPI 主设备控制器

module spi_controller #(
    parameter DATA_WIDTH = 8
) (
    input wire clk,
    input wire rst_n,
    input wire start,
    input wire [DATA_WIDTH-1:0] data_in,
    input wire sclk_en,
    output reg sclk,
    output reg mosi,
    output reg cs_n,
    output reg busy,
    input wire miso // 可选,用于接收
);
    localparam IDLE = 2'b00, LOAD = 2'b01, SHIFT = 2'b10, DONE = 2'b11;
    reg [1:0] state, next_state;
    reg [3:0] bit_cnt;
    reg [DATA_WIDTH-1:0] shift_reg;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) state <= IDLE;
        else state <= next_state;
    end

    always @(*) begin
        next_state = state;
        case (state)
            IDLE: if (start) next_state = LOAD;
            LOAD: next_state = SHIFT;
            SHIFT: if (bit_cnt == DATA_WIDTH-1 && sclk_en) next_state = DONE;
            DONE: if (!start) next_state = IDLE;
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sclk <= 0; mosi <= 0; cs_n <= 1; busy <= 0;
            bit_cnt <= 0; shift_reg <= 0;
        end else begin
            case (state)
                IDLE: begin
                    cs_n <= 1; busy <= 0;
                end
                LOAD: begin
                    shift_reg <= data_in;
                    bit_cnt <= 0;
                    cs_n <= 0; // 拉低片选
                    busy <= 1;
                end
                SHIFT: begin
                    if (sclk_en) begin
                        sclk <= ~sclk; // 产生 SCLK 边沿
                        if (sclk) begin // SCLK 下降沿输出数据
                            mosi <= shift_reg[DATA_WIDTH-1];
                            shift_reg <= {shift_reg[DATA_WIDTH-2:0], miso};
                            bit_cnt <= bit_cnt + 1;
                        end
                    end
                end
                DONE: begin
                    cs_n <= 1; busy <= 0;
                    sclk <= 0; // 确保 SCLK 空闲低
                end
            endcase
        end
    end
endmodule

用途:状态机控制 SPI 时序。注意 SCLK 在 SHIFT 状态由 sclk_en 触发翻转,在下降沿更新 MOSI。此实现假设模式 0(CPOL=0, CPHA=0),即 SCLK 空闲低,数据在第一个边沿(上升沿)采样。实际中需根据从设备规格调整边沿。

时序与约束

系统时钟 50 MHz,周期 20 ns。使用 create_clock -period 20 [get_ports clk]。SPI 输出信号(sclk, mosi, cs_n)建议设置输出延迟约束,例如 set_output_delay -clock [get_clocks clk] -max 5 [get_ports {sclk mosi cs_n}]。若上板,确保 I/O 标准匹配从设备(如 LVCMOS33)。

验证与仿真

// testbench 关键部分
initial begin
    clk = 0;
    forever #10 clk = ~clk; // 50 MHz
end
initial begin
    rst_n = 0; start = 0; data_in = 8'hA5;
    #100 rst_n = 1;
    #20 start = 1;
    #20 start = 0;
    #500;
    $display("Test done");
    $finish;
end

常见坑与排查

  • 仿真中 SCLK 无脉冲:检查 sclk_en 是否产生,分频系数是否设置正确。
  • MOSI 数据错位:检查移位寄存器更新边沿(应下降沿输出,上升沿采样)。
  • CS_n 未拉低:确认状态机进入 LOAD 或 SHIFT 状态,检查 start 信号宽度。

原理与设计说明

为什么用状态机而非计数器直接控制:状态机使时序逻辑清晰,易于扩展(如添加接收、多字节传输)。计数器方式虽节省资源,但可读性差,调试困难。

分频器 vs 直接使用系统时钟:SPI 从设备通常需要较低频率(数 MHz),直接使用系统时钟会超出从设备规格。分频器通过计数器产生使能信号,避免在高速时钟下翻转 SCLK,降低功耗与 EMI。

资源 vs Fmax 权衡:本设计使用 4 个状态(2 个 FF)和 8 位移位寄存器(8 个 FF),总 FF 约 10-15 个。若追求更高 Fmax,可加入流水线寄存器,但会增加延迟。本设计在 50 MHz 下无时序风险。

验证与结果

指标测量条件结果
FmaxVivado 2020.1, Artix-7 speed grade -1210 MHz
资源 (LUT/FF)综合后报告32 LUT / 18 FF
SCLK 频率分频系数 4,系统时钟 50 MHz12.5 MHz
传输延迟从 start 到 busy 拉低~800 ns (10 个 SCLK 周期)
波形特征仿真波形CS_n 低电平 8 个 SCLK 脉冲,MOSI 按 MSB-first 输出 0xA5

故障排查

  • 现象:仿真无波形 → 原因:testbench 未正确驱动时钟或复位。检查点:时钟是否翻转,复位是否释放。修复:确保 forever #10 clk=~clkrst_n=1
  • 现象:SCLK 只有 1 个脉冲 → 原因:状态机提前退出 SHIFT。检查点:bit_cnt 比较条件。修复:确保 bit_cnt == DATA_WIDTH-1 && sclk_en 在最后一个脉冲后进入 DONE。
  • 现象:MOSI 数据顺序反了 → 原因:移位方向错误。检查点:shift_reg[DATA_WIDTH-1] 输出。修复:改为 LSB-first 需调整移位方向。
  • 现象:CS_n 在传输中抖动 → 原因:状态机在 LOAD 或 DONE 时未正确保持。检查点:CS_n 赋值逻辑。修复:在 SHIFT 状态保持 CS_n 低。
  • 现象:综合后 Fmax 低 → 原因:组合逻辑路径过长。检查点:状态转移中是否有复杂条件。修复:将 bit_cnt 比较改为寄存器。
  • 现象:上板后 SPI 无通信 → 原因:I/O 电平不匹配。检查点:板卡原理图。修复:设置正确 IOSTANDARD。
  • 现象:仿真中 MISO 数据未捕获 → 原因:未在正确边沿采样。检查点:移位寄存器更新时机。修复:在 SCLK 上升沿采样 MISO。
  • 现象:start 信号被忽略 → 原因:start 宽度不足。检查点:start 是否至少保持一个时钟周期。修复:在 testbench 中拉高至少 20 ns。

扩展与下一步

  • 参数化数据宽度:将 DATA_WIDTH 改为通用参数,支持 8/16/32 位。
  • 支持 SPI 模式 1-3:添加 CPOL 和 CPHA 参数,调整 SCLK 极性与相位。
  • 多从设备支持:增加多个 cs_n 输出,通过地址译码选择。
  • 加入 DMA 或 FIFO:使用 FIFO 缓存数据,实现连续传输。
  • 形式验证:用 SymbiYosys 或 JasperGold 验证状态机属性(如无死锁)。
  • 跨平台移植:将 RTL 改为 Lattice iCE40 或 Intel Cyclone,注意时钟资源差异。

参考与信息来源

  • SPI 规范:Motorola SPI Block Guide V03.06
  • Xilinx UG949:Vivado Design Suite User Guide
  • Clifford E. Cummings, “State Machine Coding Styles for Synthesis”, SNUG 2002
  • Xilinx AR# 65443:SPI Master Reference Design

技术附录

术语表

  • SPI:Serial Peripheral Interface,串行外设接口。
  • CPOL:Clock Polarity,时钟极性。
  • CPHA:Clock Phase,时钟相位。
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/36635.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
51417.24W3.93W3.67W
分享:
成电国芯FPGA赛事课即将上线
FPGA时序约束实战:使用set_clock_groups处理异步时钟域的设计指南
FPGA时序约束实战:使用set_clock_groups处理异步时钟域的设计指南上一篇
FPGA仿真进阶:用SystemVerilog搭建自动化测试平台下一篇
FPGA仿真进阶:用SystemVerilog搭建自动化测试平台
相关文章
总数:545
2026年FPGA在数据中心可重构加速卡(SmartNIC)中的角色演进

2026年FPGA在数据中心可重构加速卡(SmartNIC)中的角色演进

随着数据中心网络向200G/400G乃至800G演进,以及计算密集型负载…
技术分享
5天前
0
0
14
0
FPGA竞赛通关指南:从选题到获奖,学长学姐的实战心得

FPGA竞赛通关指南:从选题到获奖,学长学姐的实战心得

一、竞赛认知:为什么说FPGA竞赛是大学里的“黄金跳板”?全国…
技术分享
1个月前
0
0
75
0
FPGA跨时钟域(CDC)处理设计指南:同步器实现与验证实践

FPGA跨时钟域(CDC)处理设计指南:同步器实现与验证实践

跨时钟域(CDC)处理是FPGA设计中确保信号在不同时钟域间可靠传递的关…
技术分享
4天前
0
0
17
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容