《深入解析SPI协议及其FPGA高效实现》-- 第二篇:SPI控制器FPGA架构设计

第二篇:SPI控制器FPGA架构设计

聚焦模块化设计、时序优化与资源管理

1. 系统级架构设计

1.1 模块化硬件架构

verilog

复制代码
module spi_controller (
  input  wire        clk,          // 系统时钟 (100 MHz)
  input  wire        rst_n,        // 异步复位
  // 配置接口
  input  wire [15:0] clk_div,      // 时钟分频系数
  input  wire [1:0]  spi_mode,     // CPOL/CPHA模式
  input  wire        lsb_first,    // 位传输顺序
  // 数据接口
  input  wire [7:0]  tx_data,      // 发送数据
  output reg  [7:0]  rx_data,      // 接收数据
  output reg         busy,         // 传输状态
  // SPI物理接口
  output wire        sck,          // SPI时钟
  output wire        mosi,         // 主出从入
  input  wire        miso,         // 主入从出
  output wire        cs_n          // 片选 (低有效)
);
// 核心子模块实例化
clock_gen     u_clk_gen(.*);      // 时钟生成
fsm_controller u_fsm(.*);         // 状态机
data_path     u_datapath(.*);     // 数据路径
cs_decoder    u_cs_dec(.*);       // 片选译码
endmodule

关键模块说明

  • 时钟域划分
    高速域 (clk) :配置寄存器访问 (100MHz)
    SPI域 (spi_clk) :SCK同步逻辑 (≤50MHz)
    跨时钟域同步 :TX/RX数据通过异步FIFO交换
1.2 寄存器映射表
地址偏移 寄存器名 位定义 功能
0x00 CTRL [0]:使能 [1]:LSB [3:2]:模式 控制寄存器
0x04 CLK_DIV 16位分频系数 时钟分频
0x08 TX_DATA 8位发送数据 发送缓冲区
0x0C RX_DATA 8位接收数据 接收缓冲区
0x10 STATUS [0]:忙 [1]:传输完成 状态寄存器

2. 时钟生成电路

2.1 可编程分频器设计

verilog

复制代码
module clock_gen (
  input  wire clk,
  input  wire [15:0] div_value,
  output reg  spi_clk
);
  reg [15:0] counter = 0;
  reg clk_phase = 0;  // 相位控制

  always @(posedge clk) begin
    if (counter >= div_value) begin
      counter <= 0;
      clk_phase <= ~clk_phase;  // 翻转相位
    end else begin
      counter <= counter + 1;
    end
  end

  // CPOL控制输出极性
  assign sck = (spi_mode[1]) ? ~clk_phase : clk_phase;
endmodule

关键技术

  • 小数分频实现 :通过累加器实现N.5分频(如3.5分频)
    verilog

    复制代码
    reg [3:0] acc = 0;
    always @(posedge clk) begin
      acc <= acc + 4'd2;         // 步进值=2 (2/4=0.5)
      spi_clk <= (acc < 4'd4);   // 50%占空比
    end
  • 动态时钟切换 :CPOL变化时插入死区时间避免毛刺
    verilog

    复制代码
    always @(posedge clk) begin
      if (mode_changed) begin
        sck <= 1'bz;            // 高阻态保持10ns
        #10; 
        sck <= new_polarity;    // 应用新极性
      end
    end

3. 核心状态机设计

3.1 状态转移图
3.2 Verilog实现

verilog

复制代码
typedef enum {IDLE, START, TX_RX, STOP} spi_state;
spi_state current_state = IDLE;
reg [2:0] bit_cnt = 0;  // 位计数器

always @(posedge spi_clk) begin
  case(current_state)
    IDLE: 
      if (cs_active) begin
        current_state <= START;
        sck <= 1'b0;  // Mode 0初始化
      end
    
    START: 
      current_state <= TX_RX;
  
    TX_RX:
      if (bit_cnt == 7) 
        current_state <= STOP;
      else
        bit_cnt <= bit_cnt + 1;
  
    STOP:
      if (!cs_hold) 
        current_state <= IDLE;
  endcase
end

关键特性

  • CPHA自适应 :通过状态机控制采样沿
    verilog

    复制代码
    // CPHA=0: 上升沿采样,下降沿切换数据
    wire sample_edge = (spi_mode[0]) ? negedge sck : posedge sck;
    wire shift_edge  = (spi_mode[0]) ? posedge sck : negedge sck;
  • 连续传输支持cs_hold信号保持状态机在STOP状态


4. 数据路径优化

4.1 双缓冲机制

verilog

复制代码
reg [7:0] tx_buffer, tx_shift;
reg [7:0] rx_shift, rx_buffer;

// 发送双缓冲
always @(posedge clk) begin
  if (!busy) 
    tx_shift <= tx_buffer;  // 空闲时加载新数据
end

// 接收双缓冲
always @(posedge spi_clk) begin
  if (bit_cnt == 0 && current_state == TX_RX)
    rx_buffer <= rx_shift;  // 传输完成锁存数据
end
4.2 循环移位寄存器

verilog

复制代码
// LSB优先传输实现
always @(shift_edge) begin
  if (current_state == TX_RX) begin
    tx_shift <= lsb_first ? 
                {1'b0, tx_shift[7:1]} : 
                {tx_shift[6:0], 1'b0};
              
    rx_shift <= lsb_first ? 
                {miso, rx_shift[7:1]} : 
                {rx_shift[6:0], miso};
  end
end
4.3 多从机数据隔离

verilog

复制代码
// 基于CS的三态控制
assign mosi = (cs_active) ? tx_shift[7] : 1'bz;
assign miso = (cs_active) ? slave_miso : 1'bz;

// 从设备选择译码
module cs_decoder (
  input  wire [3:0] slave_sel,   // 4位从机选择
  output reg  [7:0] cs_n         // 8个CS信号
);
  always @(*) begin
    cs_n = 8'hFF;              // 默认全关
    if (slave_sel < 8) 
      cs_n[slave_sel] = 1'b0;  // 激活选中从机
  end
endmodule

5. 时序收敛关键策略

5.1 多周期路径约束

tcl

复制代码
# XDC约束示例 (Vivado)
set_multicycle_path 2 -setup -from [get_clocks clk] -to [get_clocks spi_clk]
set_multicycle_path 1 -hold -end
5.2 跨时钟域同步链

verilog

复制代码
// 异步信号三级同步
reg [2:0] sync_miso;
always @(posedge spi_clk) begin
  sync_miso <= {sync_miso[1:0], miso};
end
5.3 关键路径流水化

verilog

复制代码
// 添加流水线提升时序
always @(posedge spi_clk) begin
  // 第1拍:计算下一状态
  next_state <= fsm_logic(current_state); 
  
  // 第2拍:更新状态
  current_state <= next_state;  
end

6. 资源优化技术

6.1 动态部分重配置(Xilinx FPGA)

tcl

复制代码
# 重配置命令(切换SPI模式)
write_cfgmem -format BIN -interface SPIx4 -size 8 -loadbit "up 0x0 new_mode.bin"
6.2 引脚复用技术
复用方案 实现方式 节省IO
QSPI模式 将WP/HOLD引脚用作数据线 2根
三线SPI MOSI/MISO共享SIO线 1根
分时复用 用同一组引脚驱动多组SPI外设 50%

附录:Artix-7资源占用报告

模块 LUTs FFs 最大频率 功耗
时钟生成器 42 32 450 MHz 8 mW
状态机 78 64 350 MHz 12 mW
数据路径 105 80 400 MHz 15 mW
总计 225 176 350 MHz 35 mW
相关推荐
小白到大佬4 小时前
High Speed SelectIO Wizard ip使用记录
fpga开发·lvds·高速接口
嵌入式-老费6 小时前
再谈fpga开发(fpga开发的几个特点)
fpga开发
范纹杉想快点毕业16 小时前
基于C语言的Zynq SOC FPGA嵌入式裸机设计和开发教程
c语言·开发语言·数据库·嵌入式硬件·qt·fpga开发·嵌入式实时数据库
YONYON-R&D1 天前
LAYOUT 什么时候需要等长布线?
嵌入式硬件·fpga开发
我不是程序猿儿1 天前
【Servo】裸机还是RTOS驱动架构如何选?
驱动开发·fpga开发·架构·伺服驱动器·伺服
乌恩大侠1 天前
USRP X440
fpga开发
minglie11 天前
PetaLinux 使用技巧与缓存配置
fpga开发
青春猪头ic少年梦不到兔女郎师姐2 天前
软硬件协同仿真和验证的标准接口协议SCE-MI简介
fpga开发·uvm验证·软硬件协同仿真验证
集芯微电科技有限公司2 天前
12V/500mA低IQ高PSRR快速瞬态线性稳压器/LDO
c语言·数据结构·单片机·嵌入式硬件·fpga开发
千宇宙航2 天前
闲庭信步使用图像验证平台加速FPGA的开发:第三十二课——车牌识别的FPGA实现(4)车牌字符的分割定位
图像处理·计算机视觉·fpga开发·车牌识别·图像腐蚀·图像膨胀