在FPGA开发中,串行通信协议如SPI和I2C是连接外设的常见选择。但每次项目都重写通信代码既低效又容易出错。本文将带你用Verilog设计一个高度可配置的通信IP核,支持SPI四种模式切换,并预留I2C扩展接口。这个IP核可以直接集成到你的Vivado工程中,像调用官方IP一样简单。
传统SPI实现通常固定为单一模式(如模式0),而实际项目中可能需要切换不同模式与各类传感器通信。我们的设计目标是通过参数化实现以下特性:
核心参数定义如下:
verilog复制module configurable_spi #(
parameter DATA_WIDTH = 8, // 数据位宽
parameter CLK_DIV = 4, // 时钟分频系数
parameter CPOL = 0, // 时钟极性
parameter CPHA = 0 // 时钟相位
)(
input clk,
input rst_n,
input [DATA_WIDTH-1:0] tx_data,
output [DATA_WIDTH-1:0] rx_data,
output sck,
output cs_n,
inout mosi_miso
);
SPI的四种模式本质是时钟极性和采样边沿的组合。通过灵活的状态机设计,我们可以用同一套代码支持所有模式:
verilog复制// 根据CPOL设置初始时钟状态
assign sck = (CPOL) ? 1'b1 : 1'b0;
// 时钟分频与边沿检测
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_counter <= 0;
sck_internal <= CPOL;
end else begin
if(clk_counter == CLK_DIV-1) begin
clk_counter <= 0;
sck_internal <= ~sck_internal; // 翻转时钟
end else begin
clk_counter <= clk_counter + 1;
end
end
end
不同模式下采样和切换数据的时机:
| 模式 | CPOL | CPHA | 采样边沿 | 数据切换边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 上升沿 | 下降沿 |
| 1 | 0 | 1 | 下降沿 | 上升沿 |
| 2 | 1 | 0 | 下降沿 | 上升沿 |
| 3 | 1 | 1 | 上升沿 | 下降沿 |
实现代码片段:
verilog复制always @(*) begin
case({CPOL,CPHA})
2'b00: begin // 模式0
sample_edge = posedge sck;
shift_edge = negedge sck;
end
2'b01: begin // 模式1
sample_edge = negedge sck;
shift_edge = posedge sck;
end
// 其他模式类似...
endcase
end
完成Verilog设计后,我们可以将其打包为Vivado IP,方便在不同项目中调用:
对于高级应用,可以添加AXI接口实现处理器控制:
tcl复制# 在IP封装脚本中添加AXI接口
ipx::add_bus_interface AXI_LITE [ipx::current_core]
set_property abstraction_type_vlnv xilinx.com:interface:aximm_rtl:1.0 [ipx::get_bus_interfaces AXI_LITE -of_objects [ipx::current_core]]
set_property bus_type_vlnv xilinx.com:interface:aximm:1.0 [ipx::get_bus_interfaces AXI_LITE -of_objects [ipx::current_core]]
在component.xml中定义可配置参数:
xml复制<user_parameters>
<parameter name="DATA_WIDTH" display_name="Data Width" type="integer" value="8" minimum="4" maximum="32">
<description>Number of bits per transfer</description>
</parameter>
<parameter name="CPOL" display_name="Clock Polarity" type="bool" value="0">
<description>Initial clock state (0=low, 1=high)</description>
</parameter>
</user_parameters>
虽然SPI和I2C协议差异较大,但我们可以通过模块化设计预留扩展接口:
verilog复制module multi_protocol_controller #(
parameter PROTOCOL = "SPI" // 或"I2C"
)(
// 共用接口
input clk,
input rst_n,
inout sda_sdi,
output scl_sck
);
generate
if(PROTOCOL == "SPI") begin
configurable_spi spi_inst(
.sck(scl_sck),
.mosi_miso(sda_sdi)
// 其他连接...
);
end else begin
i2c_master i2c_inst(
.scl(scl_sck),
.sda(sda_sdi)
// 其他连接...
);
end
endgenerate
verilog复制// I2C端口示例
inout wire sda;
reg sda_out;
reg sda_oe; // 输出使能
assign sda = sda_oe ? sda_out : 1'bz;
可靠的IP核需要完善的验证环境:
verilog复制initial begin
// 测试不同SPI模式
test_spi_mode(0, 0); // 模式0
test_spi_mode(0, 1); // 模式1
// ...
// 测试不同数据位宽
test_data_width(8);
test_data_width(16);
end
task test_spi_mode;
input cpol, cpha;
begin
uut.CPOL = cpol;
uut.CPHA = cpha;
// 发送测试数据并验证
end
endtask
调试技巧:在Vivado ILA中添加协议解码器,可以直接看到解析后的SPI数据帧。
对于高速应用场景,可以考虑以下优化:
verilog复制// 多从机选择示例
output reg [3:0] cs_n; // 4个从设备
always @(*) begin
case(device_select)
2'd0: cs_n = 4'b1110;
2'd1: cs_n = 4'b1101;
// ...
endcase
end
在最近的一个传感器集项目中,这种可配置IP核将开发时间缩短了约40%。特别是在需要同时与SPI Flash(模式0)和惯性传感器(模式3)通信的场景下,参数化设计避免了重复开发。