Quick Start
- 在Vivado 2024.2中新建工程,器件选择XC7A35T-2CSG324C(Arty A7-35T)。
- 创建顶层文件
spi_master.sv,定义端口:clk、rst_n、start、data_in[7:0]、sclk、mosi、miso、cs_n、busy、data_out[7:0]、done。 - 编写状态机代码(Moore型,5个状态:IDLE、LOAD、SHIFT、CAPTURE、DONE),实现SPI模式0(CPOL=0,CPHA=0)。
- 编写testbench,驱动
start脉冲,提供miso数据(如8'hA5),运行仿真500us。 - 观察波形:
cs_n拉低后sclk产生8个周期,mosi逐位输出data_in,miso在sclk下降沿被采样,done在传输结束后拉高一个周期。 - 综合实现后检查资源:LUT ≤ 80,FF ≤ 60,Fmax ≥ 200MHz(典型值)。
- 上板验证:连接SPI从设备(如ADC或DAC),通过ILA抓取
sclk、mosi、cs_n,确认时序符合SPI模式0。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T | 通用低成本FPGA,资源充足 | XC7A100T、Cyclone IV E |
| EDA版本 | Vivado 2024.2 | 支持SystemVerilog-2017 | Vivado 2023.x、Quartus Prime 23.x |
| 仿真器 | Vivado Simulator (xsim) | 内置于Vivado,无需额外安装 | ModelSim SE-64 2023.4、Verilator 5.x |
| 时钟/复位 | 100MHz 外部时钟,异步低电平复位 | 板载晶振提供,复位按键 | 50MHz、125MHz;同步复位(需修改状态机) |
| 接口依赖 | PMOD或Arduino Shield连接器 | 用于连接SPI从设备 | 自定义FMC子卡 |
| 约束文件 | XDC文件:时钟周期10ns,输入/输出延迟2ns | 保证时序收敛 | SDC格式(Quartus) |
目标与验收标准
- 功能点:支持SPI模式0(CPOL=0,CPHA=0),8位数据全双工传输,可连续发送。
- 性能指标:SCLK频率可配置(默认12.5MHz,即100MHz/8分频);传输8位耗时≤0.8µs(含状态切换开销)。
- 资源占用:LUT ≤ 80,FF ≤ 60(XC7A35T上实现)。
- 时序收敛:综合后WNS ≥ 0.2ns,Fmax ≥ 200MHz。
- 验收波形:仿真波形中
cs_n在传输期间保持低电平;sclk在cs_n拉低半个周期后开始翻转;mosi在sclk上升沿变化;miso在sclk下降沿被捕获;done在最后一位捕获后拉高一个时钟周期。
实施步骤
工程结构与顶层模块
创建以下文件结构:
spi_master/
├── rtl/
│ ├── spi_master.sv // 顶层状态机
│ └── clk_divider.sv // 时钟分频器(可选)
├── sim/
│ └── tb_spi_master.sv // 测试平台
├── constr/
│ └── top.xdc // 时序约束
└── vivado/ // Vivado工程目录逐行说明
- 第1行:顶层目录
spi_master。 - 第2行:RTL源码文件夹。
- 第3行:主状态机模块,实现SPI协议。
- 第4行:时钟分频模块,将100MHz分频为SCLK(12.5MHz)。
- 第5行:仿真文件夹。
- 第6行:测试平台文件。
- 第7行:约束文件夹。
- 第8行:时序约束文件。
- 第9行:Vivado工程目录。
关键模块:状态机实现(spi_master.sv)
module spi_master #(
parameter CLK_DIV = 8 // 分频系数:SCLK = clk / CLK_DIV
)(
input logic clk,
input logic rst_n,
input logic start,
input logic [7:0] data_in,
output logic sclk,
output logic mosi,
input logic miso,
output logic cs_n,
output logic busy,
output logic [7:0] data_out,
output logic done
);逐行说明
- 第1行:模块定义,带参数
CLK_DIV,默认8分频。 - 第2行:参数定义结束。
- 第3行:系统时钟输入。
- 第4行:异步低电平复位。
- 第5行:启动信号,高电平有效,至少保持一个时钟周期。
- 第6行:待发送的8位并行数据。
- 第7行:SPI串行时钟输出。
- 第8行:主出从入数据线。
- 第9行:主入从出数据线。
- 第10行:片选信号,低电平有效。
- 第11行:忙标志,传输期间为高。
- 第12行:接收到的8位并行数据输出。
- 第13行:传输完成标志,高电平持续一个时钟周期。
// 状态定义
typedef enum logic [2:0] {
IDLE = 3'b000,
LOAD = 3'b001,
SHIFT = 3'b010,
CAPTURE= 3'b011,
DONE = 3'b100
} state_t;
state_t state, next_state;逐行说明
- 第1行:注释,状态定义。
- 第2行:枚举类型定义,3位宽。
- 第3行:IDLE状态,等待
start。 - 第4行:LOAD状态,加载数据到移位寄存器。
- 第5行:SHIFT状态,在SCLK上升沿输出
mosi。 - 第6行:CAPTURE状态,在SCLK下降沿采样
miso。 - 第7行:DONE状态,置位完成信号。
- 第8行:枚举定义结束。
- 第9行:声明当前状态和下一状态变量。
// 内部寄存器
logic [7:0] shift_reg; // 移位寄存器(发送+接收)
logic [3:0] bit_cnt; // 位计数器(0~7)
logic [7:0] clk_cnt; // 分频计数器
logic sclk_en; // SCLK使能信号逐行说明
- 第1行:注释,内部寄存器声明。
- 第2行:8位移位寄存器,用于串行发送和接收。
- 第3行:4位计数器,记录已传输位数(0~7)。
- 第4行:8位分频计数器,用于生成SCLK。
- 第5行:SCLK使能信号,为1时允许SCLK翻转。
// 状态转移(时序逻辑)
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end逐行说明
- 第1行:注释,状态转移时序逻辑。
- 第2行:时钟上升沿或复位下降沿触发。
- 第3行:复位有效时进入IDLE状态。
- 第4行:否则更新为下一状态。
// 下一状态组合逻辑
always_comb begin
next_state = state; // 默认保持
case (state)
IDLE: begin
if (start)
next_state = LOAD;
end
LOAD: begin
next_state = SHIFT;
end
SHIFT: begin
if (bit_cnt == 4'd7 && clk_cnt == CLK_DIV-1)
next_state = DONE;
else if (clk_cnt == CLK_DIV-1)
next_state = CAPTURE;
end
CAPTURE: begin
if (clk_cnt == CLK_DIV-1)
next_state = SHIFT;
end
DONE: begin
next_state = IDLE;
end
endcase
end逐行说明
- 第1行:注释,下一状态组合逻辑块。
- 第2行:默认保持当前状态。
- 第3行:状态选择。
- 第4行:IDLE状态:检测
start信号。 - 第5行:若
start为高,跳转到LOAD。 - 第7行:LOAD状态:无条件跳转到SHIFT。
- 第10行:SHIFT状态:若已传输8位且分频计数器满,跳转到DONE。
- 第11行:否则若分频计数器满,跳转到CAPTURE。
- 第14行:CAPTURE状态:若分频计数器满,跳转到SHIFT。
- 第17行:DONE状态:无条件回到IDLE。
// 分频计数器与SCLK生成
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
clk_cnt <= 0;
sclk <= 0;
end else if (state == IDLE || state == DONE) begin
clk_cnt <= 0;
sclk <= 0; // IDLE时SCLK为低
end else if (sclk_en) begin
if (clk_cnt == CLK_DIV-1) begin
clk_cnt <= 0;
sclk <= ~sclk;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
end逐行说明
- 第1行:注释,分频计数器与SCLK生成。
- 第2行:时序逻辑,时钟上升沿或复位下降沿触发。
- 第3行:复位时清空计数器,SCLK置0。
- 第5行:IDLE或DONE状态时,计数器清零,SCLK保持低电平。
- 第7行:SCLK使能时,进行分频。
- 第8行:计数器满(CLK_DIV-1)时,清零并翻转SCLK。
- 第11行:否则计数器递增。
// SCLK使能逻辑(状态机控制)
always_comb begin
sclk_en = (state == SHIFT) || (state == CAPTURE);
end逐行说明
- 第1行:注释,SCLK使能逻辑。
- 第2行:组合逻辑,SHIFT或CAPTURE状态时使能SCLK。
// 移位寄存器与位计数
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
shift_reg <= 0;
bit_cnt <= 0;
data_out <= 0;
end else begin
case (state)
LOAD: begin
shift_reg <= data_in; // 加载发送数据
bit_cnt <= 0;
end
SHIFT: begin
if (clk_cnt == 0) begin // SCLK上升沿前
mosi <= shift_reg[7]; // 输出最高位
shift_reg <= {shift_reg[6:0], miso}; // 左移并捕获miso
end
end
CAPTURE: begin
if (clk_cnt == CLK_DIV/2 - 1) begin // SCLK下降沿后采样
bit_cnt <= bit_cnt + 1;
end
end
DONE: begin
data_out <= shift_reg; // 输出接收数据
end
endcase
end
end逐行说明
- 第1行:注释,移位寄存器与位计数。
- 第2行:时序逻辑。
- 第3行:复位时清零所有寄存器。
- 第7行:LOAD状态:加载
data_in到移位寄存器,位计数清零。 - 第11行:SHIFT状态:在
clk_cnt==0时(SCLK上升沿前)输出mosi并移位。 - 第12行:
mosi输出移位寄存器最高位。 - 第13行:左移一位,并将
miso移入最低位。 - 第16行:CAPTURE状态:在
clk_cnt == CLK_DIV/2 - 1时(SCLK下降沿后)递增位计数。 - 第20行:DONE状态:将移位寄存器内容输出到
data_out。
// 片选与忙标志
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cs_n <= 1;
busy <= 0;
done <= 0;
end else begin
case (state)
IDLE: begin
cs_n <= 1;
busy <= 0;
done <= 0;
end
LOAD: begin
cs_n <= 0; // 拉低片选
busy <= 1;
end
DONE: begin
cs_n <= 1; // 拉高片选
busy <= 0;
done <= 1; // 完成脉冲
end
endcase
end
end逐行说明
- 第1行:注释,片选与忙标志。
- 第2行:时序逻辑。
- 第3行:复位时片选拉高,忙标志清零,完成信号清零。
- 第8行:IDLE状态:片选拉高,忙标志清零。
- 第13行:LOAD状态:片选拉低(开始传输),忙标志置位。
- 第17行:DONE状态:片选拉高(结束传输),忙标志清零,完成信号置位一个周期。
endmodule


