news 2026/4/23 13:32:44

别再死记硬背SPI时序了!用Verilog手搓一个APB转SPI桥接IP(附源码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再死记硬背SPI时序了!用Verilog手搓一个APB转SPI桥接IP(附源码)

从零构建APB-SPI控制器:Verilog实战指南与状态机设计精髓

在FPGA和ASIC设计中,SPI(Serial Peripheral Interface)作为最常用的串行通信协议之一,其简单高效的特性使其成为芯片间通信的首选方案。然而,对于许多初学者而言,理解SPI协议与将其转化为可工作的RTL代码之间存在着巨大的鸿沟。本文将带你从AMBA APB总线接口出发,完整实现一个可配置的SPI Master控制器,过程中不仅会深入解析SPI协议的核心机制,更会分享实际工程中的设计技巧与调试方法。

1. SPI协议深度解析与APB接口设计

SPI协议的本质是一个同步串行全双工的通信机制,其核心在于主从设备间的时钟同步与数据交换。与UART等异步协议不同,SPI的通信完全由主设备(Master)驱动的时钟信号(SCLK)控制,这使得数据传输具有确定性的时序关系。

1.1 SPI关键参数解析

SPI协议的可配置性主要体现在两个关键参数上:

  • CPOL(Clock Polarity):决定时钟空闲状态
    • CPOL=0:SCLK空闲时为低电平
    • CPOL=1:SCLK空闲时为高电平
  • CPHA(Clock Phase):决定数据采样边沿
    • CPHA=0:在时钟的第一个边沿采样数据
    • CPHA=1:在时钟的第二个边沿采样数据

这两个参数的组合形成了四种SPI工作模式:

模式CPOLCPHA数据采样边沿数据变化边沿
000上升沿下降沿
101下降沿上升沿
210下降沿上升沿
311上升沿下降沿

1.2 APB总线接口设计

AMBA APB(Advanced Peripheral Bus)是ARM公司提出的低功耗外设总线标准,其特点是接口简单、功耗低,非常适合连接低速外设。我们的APB-SPI控制器需要实现标准的APB接口:

module apb_spi ( // APB接口信号 input PCLK, // APB时钟 input PRESETn, // APB复位(低有效) input PSEL, // 外设选择 input PENABLE, // 传输使能 input PWRITE, // 读写控制 input [31:0] PADDR, // 地址总线 input [31:0] PWDATA, // 写数据 output [31:0] PRDATA, // 读数据 output PREADY, // 传输完成指示 // SPI接口信号 output SCLK, // SPI时钟 output MOSI, // 主出从入 input MISO, // 主入从出 output [3:0] SS_n // 从设备选择(低有效) );

APB接口的关键点在于其两周期传输机制:第一个周期用于地址和控制的建立(PSEL有效),第二个周期完成数据传输(PENABLE有效)。我们的设计需要正确处理这种时序,确保寄存器读写操作的正确性。

2. SPI控制器架构设计与状态机实现

一个完整的SPI控制器需要包含多个功能模块,各模块协同工作才能实现可靠的通信功能。下图展示了典型的SPI控制器架构:

APB接口 → 寄存器组 → 控制逻辑 → SPI状态机 ↓ 时钟分频器 ↓ 移位寄存器 ↔ 数据缓冲区

2.1 核心状态机设计

SPI控制器的核心是一个有限状态机(FSM),它负责协调各个模块的工作。以下是状态机的Verilog实现示例:

typedef enum logic [2:0] { IDLE, // 空闲状态,等待传输启动 START, // 传输开始,拉低SS_n SHIFT, // 数据移位状态 END, // 传输结束,拉高SS_n UPDATE // 更新寄存器状态 } spi_state_t; // 状态寄存器 spi_state_t current_state, next_state; // 状态转移逻辑 always_comb begin case (current_state) IDLE: next_state = (start_transfer) ? START : IDLE; START: next_state = SHIFT; SHIFT: next_state = (bit_count == DATA_WIDTH-1) ? END : SHIFT; END: next_state = UPDATE; UPDATE: next_state = IDLE; default:next_state = IDLE; endcase end // 状态寄存器更新 always_ff @(posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin current_state <= IDLE; end else begin current_state <= next_state; end end

2.2 时钟分频与生成

SPI时钟(SCLK)的频率通常远低于系统时钟(PCLK),因此需要设计可编程的时钟分频器。分频比可以通过APB接口配置:

// 时钟分频计数器 reg [15:0] clk_div_counter; reg [15:0] clk_div_ratio; // 通过APB配置 // SCLK生成逻辑 always_ff @(posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin clk_div_counter <= 0; SCLK <= CPOL; // 初始化为空闲状态 end else if (current_state == SHIFT) begin if (clk_div_counter == clk_div_ratio) begin clk_div_counter <= 0; SCLK <= ~SCLK; // 翻转SCLK end else begin clk_div_counter <= clk_div_counter + 1; end end else begin SCLK <= CPOL; // 非SHIFT状态保持空闲电平 clk_div_counter <= 0; end end

3. 数据通路与移位寄存器实现

SPI的核心操作是数据的串行化与反串行化,这通过移位寄存器实现。设计时需要考虑以下几点:

  1. 支持可配置的数据宽度(4-32位)
  2. 支持MSB-first或LSB-first传输
  3. 正确处理CPHA参数对数据采样/变化边沿的影响

3.1 双向移位寄存器设计

reg [31:0] shift_reg; // 移位寄存器 reg [4:0] bit_count; // 已传输位数计数 reg lsb_first; // 传输顺序配置位 // 移位操作 always_ff @(posedge PCLK or negedge PRESETn) begin if (!PRESETn) begin shift_reg <= 0; bit_count <= 0; end else begin case (current_state) START: begin shift_reg <= tx_data; // 加载发送数据 bit_count <= 0; end SHIFT: begin if (sclk_edge) begin // 根据CPHA确定的边沿 if (lsb_first) begin shift_reg <= {MISO, shift_reg[31:1]}; MOSI <= shift_reg[0]; end else begin shift_reg <= {shift_reg[30:0], MISO}; MOSI <= shift_reg[31]; end bit_count <= bit_count + 1; end end UPDATE: begin rx_data <= shift_reg; // 保存接收数据 end endcase end end

3.2 片选信号生成逻辑

多从机支持是SPI的重要特性,我们的设计需要提供可配置的片选信号生成

// 片选寄存器 reg [3:0] ss_reg; // 片选生成逻辑 always_comb begin SS_n = 4'b1111; // 默认全部不选中 if (current_state != IDLE) begin SS_n[slave_select] = 1'b0; // 选中指定从机 end end

4. 验证策略与调试技巧

设计完成后,全面的验证是确保IP核可靠性的关键。我们采用自顶向下的验证策略:

4.1 测试平台架构

测试平台(testbench) ├── APB总线模型 ├── SPI从设备模型 ├── 参考模型(golden model) └── 记分板(scoreboard)

4.2 关键测试用例

  1. 寄存器读写测试:验证APB接口功能

    • 写入配置寄存器并回读验证
    • 测试非法地址访问
  2. SPI模式覆盖测试

    // 测试所有SPI模式组合 for (int cpol = 0; cpol <= 1; cpol++) begin for (int cpha = 0; cpha <= 1; cpha++) begin test_spi_mode(cpol, cpha); end end
  3. 边界条件测试

    • 最小/最大时钟分频比
    • 不同数据宽度(4/8/16/32位)
    • 连续背靠背传输

4.3 调试技巧与常见问题

在实际项目中,SPI控制器常见的调试问题包括:

  • 时钟相位问题:如果采样边沿设置错误,会导致数据错位。解决方法:

    • 使用逻辑分析仪捕获SCLK和MOSI/MISO信号
    • 检查CPOL/CPHA是否与从设备匹配
  • 片选信号抖动:在高速传输时可能出现。解决方法:

    • 在片选变化处添加时钟周期延迟
    • 使用同步电路处理片选信号
  • 时钟分频误差:实际SCLK频率与预期不符。解决方法:

    • 验证分频比计算公式
    • 检查计数器位宽是否足够

提示:在FPGA开发中,使用ILA(Integrated Logic Analyzer)可以实时捕获内部信号,大幅提高调试效率。设置触发条件为状态机转换或特定数据传输时刻,可以快速定位问题。

5. 性能优化与高级功能扩展

基础功能实现后,我们可以进一步优化设计并添加高级功能:

5.1 FIFO接口优化

为提高吞吐量,可以添加双缓冲或FIFO结构:

// FIFO接口示例 module spi_fifo #( parameter DEPTH = 8, parameter WIDTH = 32 )( input wire clk, input wire reset_n, // 写入接口 input wire [WIDTH-1:0] wdata, input wire winc, output wire wfull, // 读出接口 output wire [WIDTH-1:0] rdata, input wire rinc, output wire rempty ); reg [WIDTH-1:0] mem [0:DEPTH-1]; reg [3:0] wptr, rptr; reg [3:0] count; always_ff @(posedge clk or negedge reset_n) begin if (!reset_n) begin wptr <= 0; rptr <= 0; count <= 0; end else begin if (winc && !wfull) begin mem[wptr] <= wdata; wptr <= wptr + 1; count <= count + 1; end if (rinc && !rempty) begin rptr <= rptr + 1; count <= count - 1; end end end assign wfull = (count == DEPTH); assign rempty = (count == 0); assign rdata = mem[rptr]; endmodule

5.2 DMA支持

对于大数据量传输,可以集成DMA控制器实现自动数据传输:

  1. 添加DMA接口寄存器(源地址、目的地址、长度)
  2. 实现DMA状态机控制传输过程
  3. 添加中断信号通知传输完成

5.3 低功耗设计

针对移动设备应用,可加入以下低功耗特性:

  • 时钟门控:当SPI空闲时关闭内部时钟
  • 电源域隔离:将不使用的模块断电
  • 动态频率调整:根据负载调整SCLK频率

6. 实际应用案例:Flash存储器读写

以常见的SPI Flash(如Winbond W25Q系列)为例,演示控制器的实际应用:

6.1 Flash命令序列

典型的Flash读取操作包括:

  1. 拉低片选(CS)
  2. 发送读命令(0x03)
  3. 发送24位地址
  4. 连续读取数据
  5. 拉高片选
// Flash读取任务 task automatic read_flash; input [23:0] addr; input [7:0] len; output [7:0] data[]; begin // 发送读命令和地址 spi_start(); spi_transfer(8'h03); // 读命令 spi_transfer(addr[23:16]); // 地址高位 spi_transfer(addr[15:8]); // 地址中位 spi_transfer(addr[7:0]); // 地址低位 // 连续读取数据 for (int i = 0; i < len; i++) begin data[i] = spi_transfer(8'hFF); // 哑数据 end spi_end(); end endtask

6.2 性能优化技巧

  1. 快速读取模式:使用0x0B命令,支持更高时钟频率
  2. 双线/四线模式:提升数据传输带宽
  3. 页编程:批量写入提高写效率
  4. 状态轮询:避免忙等待,提高系统效率

在完成基本功能后,建议将常用的Flash操作封装为任务或函数,方便上层应用调用。同时,针对特定Flash芯片的特性(如页大小、扇区大小等)进行优化,可以显著提升实际性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 13:32:04

深度技术解析:OpenCore Legacy Patcher如何让老旧Mac焕发新生

深度技术解析&#xff1a;OpenCore Legacy Patcher如何让老旧Mac焕发新生 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher技术揭秘&a…

作者头像 李华
网站建设 2026/4/23 13:31:42

Process Explorer进阶实战指南:从进程管理到恶意行为深度分析

1. Process Explorer&#xff1a;不只是任务管理器的替代品 第一次接触Process Explorer时&#xff0c;我以为它只是个"加强版任务管理器"。直到有次服务器出现异常进程导致CPU飙高&#xff0c;系统自带的任务管理器只能看到进程名和资源占用&#xff0c;而Process E…

作者头像 李华