【数字IC】Verilog SPI实战:从协议到可配置主从控制器

张开发
2026/4/19 1:02:08 15 分钟阅读

分享文章

【数字IC】Verilog SPI实战:从协议到可配置主从控制器
1. SPI协议基础与可配置控制器设计需求SPISerial Peripheral Interface作为嵌入式系统中最常用的串行通信协议之一其四线制设计SCK、MOSI、MISO、NSS在传感器、Flash存储等场景中广泛应用。我在实际项目中遇到过这样的需求同一个硬件平台需要适配不同厂商的SPI设备而这些设备对CPOL/CPHA、波特率的要求各不相同。这时候就需要一个可配置的SPI控制器来解决问题。传统固定模式的SPI控制器存在三个主要痛点时钟极性/相位兼容性差不同设备可能要求CPOL0/CPHA1或CPOL1/CPHA0等组合波特率调整困难Flash写入需要低速如1MHz而传感器读取可能需要高速如10MHz数据位宽不灵活8位设备与16位设备混用时需要重新设计以常见的W25Q128 Flash芯片为例其规格书明确要求标准模式CPOL0, CPHA0 50MHz双线模式CPOL1, CPHA1 108MHz四线模式CPOL0, CPHA1 133MHz2. 可配置SPI控制器的参数化建模2.1 时钟模式配置实现CPOLClock Polarity和CPHAClock Phase的组合决定了数据采样时机。通过参数化设计我们可以用Verilog的parameter实现模式切换module spi_controller #( parameter CPOL 0, // 0:空闲低电平 1:空闲高电平 parameter CPHA 0 // 0:第一个边沿采样 1:第二个边沿采样 )( input clk, input rst_n, // ...其他端口 );实际工程中我推荐使用状态机管理时钟相位always (posedge clk or negedge rst_n) begin if(!rst_n) begin sck CPOL; // 初始化为空闲状态 state IDLE; end else begin case(state) IDLE: begin if(enable) begin sck CPOL ^ ~CPHA; // 根据CPHA调整第一个边沿 state TRANSFER; end end TRANSFER: begin sck ~sck; // 翻转时钟 if(transfer_done) state IDLE; end endcase end end2.2 可编程波特率生成器波特率分频器设计需要考虑两个关键点分频系数动态配置通过寄存器接口实时修改占空比保持50%特别是奇数分频场景这里给出一个支持2/4/8/16/32分频的通用设计module baudrate_gen ( input clk, input rst_n, input [2:0] prescale, // 分频系数选择 output reg sck_out ); reg [4:0] counter; wire [4:0] limit {prescale, 1b0}; // 实际分频数为limit*2 always (posedge clk or negedge rst_n) begin if(!rst_n) begin counter 0; sck_out 0; end else begin if(counter limit-1) begin counter 0; sck_out ~sck_out; end else begin counter counter 1; end end end endmodule实测发现当系统时钟为100MHz时该设计可以实现从3.125MHz32分频到50MHz2分频的连续分频。3. 主从控制器状态机设计3.1 主控制器状态机优化工业级SPI主控制器通常包含以下状态IDLE等待传输请求PREPARE拉低NSS初始化移位寄存器SHIFT数据移位阶段HOLD保持NSS为低某些设备需要FINISH释放NSS一个支持流水线操作的状态机设计示例localparam [2:0] IDLE 3b000, PREPARE 3b001, SHIFT 3b010, HOLD 3b011, FINISH 3b100; always (posedge clk or negedge rst_n) begin if(!rst_n) begin state IDLE; shift_cnt 0; end else begin case(state) IDLE: if(start) state PREPARE; PREPARE: begin nss 1b0; shift_reg tx_data; state SHIFT; end SHIFT: begin if(shift_cnt DATA_WIDTH-1) begin state HOLD; shift_cnt 0; end else begin shift_cnt shift_cnt 1; shift_reg {shift_reg[DATA_WIDTH-2:0], 1b0}; end end HOLD: if(hold_done) state FINISH; FINISH: begin nss 1b1; state IDLE; end endcase end end3.2 从设备控制器的同步设计从设备需要特别注意跨时钟域同步问题。我在一次实际调试中发现当主设备时钟频率超过从设备系统时钟时直接采样MOSI信号会导致数据错误。解决方案是采用双级触发器同步// 时钟域同步模块 module sync_signal ( input clk, input async_signal, output reg sync_signal ); reg meta_stable; always (posedge clk) begin meta_stable async_signal; sync_signal meta_stable; end endmodule // 在从控制器中的应用 sync_signal sck_sync ( .clk(sys_clk), .async_signal(sck_in), .sync_signal(sck_synced) );4. 仿真验证策略与实战技巧4.1 自动化测试平台搭建一个完整的测试平台应该包含主设备行为模型模拟不同CPOL/CPHA组合从设备行为模型支持错误注入测试协议检查器自动验证时序合规性// 主设备测试模型示例 task automatic spi_master_test; input [7:0] data; input cpol, cpha; begin // 配置时钟模式 cfg_cpol cpol; cfg_cpha cpha; // 生成激励 fork begin : sck_gen sck cpol; while(!done) #(period/2) sck ~sck; end begin : data_gen (negedge sck iff cpha0 or posedge sck iff cpha1); for(int i7; i0; i--) begin mosi data[i]; (negedge sck iff cpha0 or posedge sck iff cpha1); end end join end endtask4.2 覆盖率驱动验证建议收集以下覆盖率指标协议时序覆盖率所有CPOL/CPHA组合波特率覆盖率边界值测试最大/最小波特率数据模式覆盖率包括全0、全1、交替模式等// 覆盖率组示例 covergroup spi_cg (posedge sck); option.per_instance 1; cp_cpol: coverpoint cpol; cp_cpha: coverpoint cpha; cp_baud: coverpoint baud_rate { bins min {MIN_BAUD}; bins max {MAX_BAUD}; bins others default; } cr_mode: cross cp_cpol, cp_cpha; endgroup5. 时序收敛与物理实现考量在FPGA实现时遇到过时钟偏移问题特别是当SPI时钟频率超过50MHz时。解决方案包括时钟约束示例create_generated_clock -name spi_sck -source [get_pins clk_gen/CLKOUT] \ -divide_by 4 [get_ports sck] set_clock_groups -asynchronous -group [get_clocks spi_sck] \ -group [get_clocks -include_generated_clocks [get_clocks sys_clk]]IO延迟约束set_input_delay -clock spi_sck -max 2.0 [get_ports miso] set_output_delay -clock spi_sck -max 3.0 [get_ports mosi]布局约束set_property PACKAGE_PIN F3 [get_ports sck] set_property IOSTANDARD LVCMOS33 [get_ports {mosi miso nss}]6. 性能优化实战技巧6.1 双缓冲设计提升吞吐量在高速SPI通信中如QSPI Flash访问采用双缓冲机制可以隐藏数据传输延迟// 双缓冲寄存器实现 always (posedge clk) begin if(buf_sel) begin buf1 next_data; current_data buf2; end else begin buf2 next_data; current_data buf1; end buf_sel ~buf_sel; end6.2 动态时钟门控技术对于低功耗应用可以动态关闭SPI时钟assign sck_gated sck_en ? sck : CPOL;实测在智能手表项目中这项技术使SPI接口功耗降低了37%。7. 常见问题排查指南根据调试经验SPI通信故障通常表现为以下现象及解决方法数据错位检查CPOL/CPHA设置确认MSB/LSB传输顺序使用逻辑分析仪捕获实际波形时钟抖动过大缩短走线长度添加端接电阻通常33Ω降低波特率测试从设备无响应验证NSS信号极性检查上电时序某些设备要求电源稳定后延迟初始化测量供电电压是否达标8. 进阶设计支持DMA的多通道控制器对于需要高速批量传输的场景如图像传感器可以扩展DMA功能module spi_dma_engine ( input clk, input rst_n, // ...常规SPI接口 input [31:0] dma_src_addr, input [31:0] dma_dst_addr, input [15:0] dma_len, input dma_start, output dma_done ); // 状态机包含 // - 地址初始化 // - 突发长度配置 // - 自动递增传输 // - 中断生成 endmodule这种设计在800万像素摄像头模组中实现了稳定的60fps数据传输。

更多文章