从零到上板:用FPGA实现SPI主从机完整数据回环(Vivado ILA抓波形实战)
从零到上板:用FPGA实现SPI主从机完整数据回环(Vivado ILA抓波形实战)
在嵌入式系统开发中,SPI协议因其高速、全双工的特性成为芯片间通信的首选方案之一。本文将带您完成一个完整的FPGA开发流程:从Verilog代码编写、功能仿真到实际上板调试,最终通过Vivado的ILA工具实时捕获SPI总线波形,验证主从机数据回环的正确性。不同于单纯的模块仿真,我们聚焦于系统级集成与硬件调试技巧,适合已经掌握SPI基础理论、需要实战经验的开发者。
1. SPI主从机系统架构设计
1.1 整体框架规划
一个完整的SPI回环系统需要包含以下核心模块:
- 主机发送模块:负责生成SCK时钟、MOSI数据流和CS片选信号
- 从机接收模块:同步捕获主机发送的数据
- 数据回环通路:将接收数据反馈给主机形成闭环验证
- 调试接口:集成ILA核用于实时信号捕获
关键信号定义如下表:
| 信号名称 | 方向 | 描述 |
|---|---|---|
| sys_clk | 输入 | 系统时钟(50MHz) |
| rst_n | 输入 | 低电平有效的全局复位 |
| spi_clk | 主机输出 | SPI串行时钟(可配置极性) |
| spi_mosi | 主机输出 | 主机输出从机输入数据线 |
| spi_miso | 从机输出 | 主机输入从机输出数据线 |
| spi_cs | 主机输出 | 从机片选信号(低电平有效) |
1.2 时钟域处理要点
由于SPI时钟(SCK)由主机动态生成,而从机需要同步捕获该时钟域的数据,必须特别注意跨时钟域处理:
// 三级寄存器同步链消除亚稳态 always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin spi_clk_sync <= 3'b0; spi_mosi_sync <= 3'b0; end else begin spi_clk_sync <= {spi_clk_sync[1:0], spi_clk}; spi_mosi_sync <= {spi_mosi_sync[1:0], spi_mosi}; end end提示:对于高速SPI通信(>10MHz),建议使用IDDR原语处理时钟边沿检测
2. 主机发送模块实现细节
2.1 可配置时钟生成
主机模块需要根据系统时钟分频产生SPI时钟,关键参数包括:
- CPOL:时钟空闲状态极性(0=低电平,1=高电平)
- CPHA:数据采样相位(0=第一个边沿,1=第二个边沿)
时钟分频计数器实现如下:
localparam CLK_DIV = SYS_CLK_FREQ / SPI_CLK_FREQ / 2; always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin clk_cnt <= 0; spi_clk_int <= CPOL; end else if(spi_en) begin if(clk_cnt == CLK_DIV-1) begin clk_cnt <= 0; spi_clk_int <= ~spi_clk_int; end else begin clk_cnt <= clk_cnt + 1; end end else begin spi_clk_int <= CPOL; end end2.2 数据移位控制
根据CPHA配置,数据需要在时钟的特定边沿更新:
always @(*) begin case({CPOL, CPHA}) 2'b00: data_update_edge = ~spi_clk_int & (clk_cnt == CLK_DIV-1); 2'b01: data_update_edge = spi_clk_int & (clk_cnt == CLK_DIV-1); // 其他模式组合... endcase end always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin shift_reg <= 8'h00; bit_cnt <= 0; end else if(data_update_edge) begin shift_reg <= {shift_reg[6:0], 1'b0}; bit_cnt <= bit_cnt + 1; end end3. 从机接收模块设计要点
3.1 边沿检测电路
从机需要精确检测SPI时钟的有效边沿进行数据采样:
// 上升沿检测 assign pos_edge = (spi_clk_sync[2:1] == 2'b01); // 下降沿检测 assign neg_edge = (spi_clk_sync[2:1] == 2'b10); always @(*) begin case({CPOL, CPHA}) 2'b00: sample_en = pos_edge; 2'b01: sample_en = neg_edge; // 其他模式组合... endcase end3.2 数据对齐与校验
接收到的串行数据需要转换为并行格式,并添加有效性校验:
always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin rx_data <= 8'h00; rx_valid <= 1'b0; end else if(spi_cs_sync[3]) begin rx_valid <= 1'b0; // CS无效时清除有效标志 end else if(sample_en) begin rx_data <= {rx_data[6:0], spi_mosi_sync[3]}; if(bit_cnt == 7) rx_valid <= 1'b1; end end4. 系统集成与ILA调试实战
4.1 回环测试顶层设计
将主从机模块集成到顶层,建立数据回环通路:
spi_master #( .CPOL(0), .CPHA(0) ) u_master ( .sys_clk(sys_clk), .rst_n(rst_n), .spi_clk(spi_clk), .spi_mosi(spi_mosi), .spi_miso(spi_miso), .spi_cs(spi_cs) ); spi_slave #( .CPOL(0), .CPHA(0) ) u_slave ( .sys_clk(sys_clk), .rst_n(rst_n), .spi_clk(spi_clk), .spi_mosi(spi_mosi), .spi_miso(spi_miso), .spi_cs(spi_cs), .rx_data(rx_data), .rx_valid(rx_valid) );4.2 ILA核配置技巧
在Vivado中添加ILA核时需注意:
- 设置足够深的采样存储(至少4096点)
- 添加关键信号:spi_clk、spi_mosi、spi_miso、spi_cs
- 配置触发条件:如rx_valid上升沿
TCL配置示例:
create_debug_core u_ila ila set_property C_DATA_DEPTH 8192 [get_debug_cores u_ila] set_property C_TRIGIN_EN false [get_debug_cores u_ila] add_probe spi_clk [get_debug_ports u_ila/clk] add_probe spi_mosi [get_debug_ports u_ila/probe0] add_probe rx_valid [get_debug_ports u_ila/probe1]4.3 上板调试常见问题
- 时钟偏移问题:如果采样数据不稳定,尝试调整ILA采样时钟相位
- 触发条件设置:复杂触发可使用触发状态机(TSM)
- 数据对齐异常:检查CPOL/CPHA配置是否与从设备一致
实际捕获的波形示例:
CS __----____________________________________ CLK _-_-_-_-_-_-_-_-_________________________ MOSI _X_X_X_X_X_X_X_X_________________________ MISO _X_X_X_X_X_X_X_X_________________________ | | | | | | | | 0 1 2 3 4 5 6 7 (bit位置)在Xilinx Artix-7开发板上实测时,发现当SPI时钟超过25MHz时,需要插入IO延迟约束才能保证稳定采样。通过ILA的波形对比功能,可以直观验证发送与接收数据的比特对齐情况。
