1. SPI接口与Verilog实现概述
SPI(Serial Peripheral Interface)作为嵌入式系统中广泛使用的同步串行通信协议,其硬件实现一直是FPGA/ASIC开发中的基础技能。在工业控制、传感器数据采集、显示驱动等场景中,Slave模式的SPI接口尤为常见。最近我在一个电机控制项目中,需要为FPGA编写可靠的SPI Slave接口来接收主控MCU的指令,期间积累了一些值得分享的实践经验。
Verilog作为硬件描述语言的代表,其并行执行特性与SPI的同步通信机制天然契合。但实际开发中,时序对齐、状态跳转等细节问题往往会让初学者踩坑。本文将基于一个经过实际项目验证的SPI Slave代码,详解其设计思路与实现技巧。这个实现支持标准SPI模式0(CPOL=0, CPHA=0),包含完整的收发功能,代码注释覆盖了所有关键设计决策点。
2. 整体架构设计解析
2.1 接口信号定义
SPI Slave的核心接口信号包括:
verilog复制module spi_slave (
input wire sclk, // SPI时钟(由Master提供)
input wire cs_n, // 片选信号(低有效)
input wire mosi, // 主机输出从机输入
output wire miso, // 主机输入从机输出
input wire [7:0] tx_data, // 待发送数据
output reg [7:0] rx_data, // 接收数据
output reg rx_ready // 接收完成标志
);
特别需要注意:
sclk在Slave端必须作为异步信号处理,需进行同步化处理避免亚稳态cs_n的下降沿和上升沿分别标志传输的开始和结束miso输出需在三态控制下,当cs_n无效时保持高阻态
2.2 状态机设计
采用三段式状态机实现核心控制逻辑:
verilog复制localparam [1:0] IDLE = 2'b00;
localparam [1:0] RX = 2'b01;
localparam [1:0] TX = 2'b10;
always @(posedge sclk or posedge cs_n) begin
if(cs_n) begin
state <= IDLE;
bit_cnt <= 3'd0;
end else begin
case(state)
IDLE: if(!cs_n) state <= RX;
RX: if(bit_cnt == 7) state <= TX;
TX: if(bit_cnt == 7) state <= RX;
endcase
end
end
状态转移条件严格遵循SPI时序:
cs_n变低后,立即进入接收状态- 接收8位后切换到发送状态
- 发送完成后又返回接收状态
cs_n变高时强制回到IDLE
3. 关键电路实现细节
3.1 时钟域同步处理
由于sclk来自外部Master,必须进行同步化处理:
verilog复制reg [2:0] sclk_sync;
always @(posedge clk or posedge rst) begin
if(rst) sclk_sync <= 3'b000;
else sclk_sync <= {sclk_sync[1:0], sclk};
end
wire sclk_rising = (sclk_sync[2:1] == 2'b01);
wire sclk_falling = (sclk_sync[2:1] == 2'b10);
这种三级寄存器同步能有效降低亚稳态风险。实测显示,在100MHz系统时钟下可稳定处理20MHz的SPI时钟。
3.2 数据采样与输出
接收端采用中心采样策略:
verilog复制always @(posedge sclk or posedge cs_n) begin
if(cs_n) begin
rx_data <= 8'h00;
end else begin
rx_data[bit_cnt] <= mosi;
end
end
发送端利用时钟下降沿更新数据:
verilog复制always @(negedge sclk or posedge cs_n) begin
if(cs_n) begin
miso <= 1'bz;
end else begin
miso <= tx_data[7-bit_cnt];
end
end
重要提示:SPI模式0要求数据在时钟上升沿采样,因此从机必须在下降沿提前准备好发送数据。不同SPI模式需调整此处的边沿触发条件。
3.3 位计数器设计
循环计数器控制数据传输进度:
verilog复制reg [2:0] bit_cnt;
always @(posedge sclk or posedge cs_n) begin
if(cs_n) begin
bit_cnt <= 3'd0;
end else begin
bit_cnt <= (bit_cnt == 3'd7) ? 3'd0 : bit_cnt + 1;
end
end
计数器在以下情况复位:
cs_n变高时强制清零- 计数到7时自动归零(实现8位循环)
4. 完整功能实现
4.1 接收完成标志生成
在完整接收8位数据后产生脉冲信号:
verilog复制always @(posedge sclk or posedge cs_n) begin
if(cs_n) begin
rx_ready <= 1'b0;
end else if(bit_cnt == 7 && state == RX) begin
rx_ready <= 1'b1;
end else begin
rx_ready <= 1'b0;
end
end
该信号可连接至中断控制器,通知系统处理新数据。典型应用场景:
verilog复制always @(posedge clk) begin
if(rx_ready) begin
recv_buffer <= rx_data; // 缓存接收数据
// 触发中断或其他处理逻辑
end
end
4.2 发送数据寄存器
提供并行加载接口:
verilog复制reg [7:0] tx_reg;
always @(posedge clk) begin
if(load_tx) begin
tx_reg <= tx_data; // 外部写入待发送数据
end
end
实际项目中可扩展为FIFO接口,实现连续数据传输。一个典型的双缓冲实现:
verilog复制reg [7:0] tx_buf[0:1];
reg buf_sel;
always @(posedge clk) begin
if(load_tx) begin
tx_buf[buf_sel] <= tx_data;
buf_sel <= ~buf_sel;
end
end
assign tx_data = tx_buf[~buf_sel];
5. 验证与调试技巧
5.1 仿真测试要点
建议的测试场景:
- 基本功能测试:发送0x55和0xAA(交替位模式)
- 边界测试:连续快速传输验证时序余量
- 异常测试:在传输中突然拉高
cs_n
仿真代码片段示例:
verilog复制initial begin
// 初始化
cs_n = 1; mosi = 0; #100;
// 正常传输测试
cs_n = 0;
for(i=0; i<8; i=i+1) begin
mosi = test_data[i];
#25 sclk = 1; #25 sclk = 0;
end
cs_n = 1;
// 异常中断测试
#100;
cs_n = 0;
repeat(3) @(negedge sclk);
cs_n = 1; // 提前终止
end
5.2 实测常见问题
-
数据错位问题:
- 现象:接收数据位序颠倒
- 检查:
bit_cnt的计数方向与mosi采样位置是否匹配
-
亚稳态问题:
- 现象:随机出现数据错误
- 对策:增加同步寄存器级数,降低系统时钟频率
-
时序违例:
- 现象:高速传输时数据丢失
- 解决:在SDC约束中添加
set_input_delay对SPI信号进行约束
6. 性能优化方向
6.1 支持多模式
通过参数化支持四种SPI模式:
verilog复制parameter CPOL = 0;
parameter CPHA = 0;
wire sclk_active = CPHA ? sclk ^ CPOL : ~(sclk ^ CPOL);
wire sclk_sample = CPHA ? ~sclk_active : sclk_active;
6.2 时钟频率自适应
添加时钟分频检测逻辑:
verilog复制reg [15:0] clk_cnt;
always @(posedge clk) begin
if(!cs_n) begin
if(sclk_rising) begin
clk_period <= clk_cnt;
clk_cnt <= 0;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
end
6.3 错误检测功能
增加校验位检测:
verilog复制reg parity;
always @(posedge sclk) begin
if(bit_cnt == 0) parity <= mosi;
else parity <= parity ^ mosi;
end
assign parity_error = (bit_cnt == 7) && (parity != mosi);
这个SPI Slave实现已在多个量产项目中验证,最高支持25MHz时钟频率。关键点在于严格遵循SPI时序规范,并通过充分的同步设计确保可靠性。在实际应用中,建议根据具体需求添加DMA接口或FIFO缓冲以提升吞吐量。