--SPI%E4%B8%BB%E6%9C%BA%E4%BB%8E%E6%9C%BA%E5%9B%9E%E7%8E%AF%E4%BB%BF%E7%9C%9F%E4%B8%8E%E5%AE%9E%E6%B5%8B-1.png)
前言
前文已经介绍了SPI的基本原理,主机和从机的设计方法。
本文设定了多个仿真条件,对SPI主机和从机进行回环仿真测试,验证了主机和从机模块都能正常工作。
一、回环测试实验设计
实验条件:
-
设计与仿真工具:Vivado 2024.2 -
模块工作时钟频率100MHz/120MHz -
其它参数见以下表格
实验表格1:
实验条件fCLK = 100MHz | fSCLK = 10MHz | fSCLK = 50MHz |
---|---|---|
SPI MODE = 0 数据位宽 = 8 CS_EDGE_TO_SCLK_EDGE_CLK_NUM = 1 SCLK_EDGE_TO_CS_EDGE_CLK_NUM = 3 CS_KEEP_HIGH_CLK_NUM = 2 |
条件1 | 条件2 |
SPI MODE = 1 数据位宽 = 10 CS_EDGE_TO_SCLK_EDGE_CLK_NUM = 2 SCLK_EDGE_TO_CS_EDGE_CLK_NUM = 4 CS_KEEP_HIGH_CLK_NUM = 3 |
条件3 | 条件4 |
实验表格2:
实验条件fCLK = 120MHz | fSCLK = 20MHz | fSCLK = 30MHz |
---|---|---|
SPI MODE = 2 数据位宽 = 12 CS_EDGE_TO_SCLK_EDGE_CLK_NUM = 3 SCLK_EDGE_TO_CS_EDGE_CLK_NUM = 5 CS_KEEP_HIGH_CLK_NUM = 4 |
条件5 | 条件6 |
SPI MODE = 3 数据位宽 = 16 CS_EDGE_TO_SCLK_EDGE_CLK_NUM = 4 SCLK_EDGE_TO_CS_EDGE_CLK_NUM = 6 CS_KEEP_HIGH_CLK_NUM = 5 |
条件7 | 条件8 |
说明:
-
SPI MODE有四种可以选择
-
数据位宽理论上可以取2~∞,这里选择8、10、12、16进行实验,既有8的整数倍,也有非整数倍,覆盖更多情形
-
XXX_CLK_NUM理论上可以取1~∞,但如果满足1、2、3这三种极限情况,其它取值也不会存在问题,所以选取这几种取值进行实验
二、如何判断实验是否成功
实验以仿真信号波形图为判断依据,同一实验条件波形图会有多张,如条件1对应图1.1,1.2,1.3等。从波形图中应能看到如下的实验细节:
-
SPI模式是否与设定相同
-
SCLK频率是否与设定相同
-
单次SPI通信发送/接收的数据位宽是否与设定相同
-
三个时序参数是否与设定相同
-
从机接收的数据是否始终等于主机发送的数据
-
主机接收的数据是否始终等于从机发送的数据
如果一个实验同时满足以上五点,则认为实验成功;否则,实验失败。
注意:SPI模式判断,参照表:
模式 | SCLK空闲值 | 数据采样边沿 | 数据更新边沿 |
---|---|---|---|
0 | 0 | 上升沿 | 下降沿 |
1 | 0 | 下降沿 | 上升沿 |
2 | 1 | 上升沿 | 下降沿 |
3 | 1 | 下降沿 | 上升沿 |
三、SPI主从回环——功能仿真
仿真testbench部分代码:
module mySPI_4Wire_LoopBack_tb();
//++ 仿真时间尺度 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
timeunit 1ns;
timeprecision 1ps;
//-- 仿真时间尺度 ------------------------------------------------------------
//++ 被测模块实例化 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam integer SPI_MODE = 0; // SPI模式, 可选0, 1, 2, 3 (默认)
localparam integer DATA_WIDTH = 8; // 单次通信发送或接收数据的位宽, 最小为2, 常见8/16
localparam integer SCLK_PERIOD_CLK_NUM = 10; // fSCLK, SCLK周期对应CLK数, 必须为偶数, 最小为2
localparam integer CS_EDGE_TO_SCLK_EDGE_CLK_NUM = 1; // TCC, CS_N下降沿到SCLK的第一个边沿对应CLK数, 最小为1
localparam integer SCLK_EDGE_TO_CS_EDGE_CLK_NUM = 2; // TCCH, 最后一个SCLK边沿到CS_N上升沿对应CLK数, 最小为3
localparam integer CS_KEEP_HIGH_CLK_NUM = 3; // TCWH, CS_N低电平后保持高电平的时间对应CLK数, 最小为2
localparam integer CLK_FREQ_MHZ = 100; // 模块工作时钟, 常用100/120
logic clk;
logic rstn;
mySPI_4Wire_LoopBack # (
.SPI_MODE (SPI_MODE ),
.DATA_WIDTH (DATA_WIDTH ),
.SCLK_PERIOD_CLK_NUM (SCLK_PERIOD_CLK_NUM ),
.CS_EDGE_TO_SCLK_EDGE_CLK_NUM (CS_EDGE_TO_SCLK_EDGE_CLK_NUM),
.SCLK_EDGE_TO_CS_EDGE_CLK_NUM (SCLK_EDGE_TO_CS_EDGE_CLK_NUM),
.CS_KEEP_HIGH_CLK_NUM (CS_KEEP_HIGH_CLK_NUM ),
.CLK_FREQ_MHZ (CLK_FREQ_MHZ )
) mySPI_4Wire_LoopBack_inst (.*);
//-- 被测模块实例化 ------------------------------------------------------------
//++ 生成时钟 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam CLKT = 1000 / CLK_FREQ_MHZ;
initial begin
clk = 0;
forever #(CLKT / 2) clk = ~clk;
end
//-- 生成时钟 ------------------------------------------------------------
//++ 测试输入 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
initial begin
rstn = 0;
#(CLKT * 1);
rstn = 1;
#(CLKT * (SCLK_PERIOD_CLK_NUM * DATA_WIDTH * 10));
$stop;
end
//-- 测试输入 ------------------------------------------------------------
endmodule
回环测试模块部分代码如下:
//++ 回环测试控制 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
reg rstn_r1;
always @(posedge clk) begin
rstn_r1 <= rstn;
end
assign spi_begin = rstn_r1;
always @(posedge clk) begin
if (~rstn)
spi_master_tx_data <= 'hAB;
else if (spi_master_rx_data_valid)
spi_master_tx_data <= spi_master_rx_data + 1'b1;
else
spi_master_tx_data <= spi_master_tx_data;
end
reg spi_slave_rx_data_valid_r1;
reg spi_slave_rx_data_valid_r2;
always @(posedge clk) begin
spi_slave_rx_data_valid_r1 <= spi_slave_rx_data_valid;
spi_slave_rx_data_valid_r2 <= spi_slave_rx_data_valid_r1;
end
wire spi_slave_rx_data_valid_pedge = spi_slave_rx_data_valid_r1 && ~spi_slave_rx_data_valid_r2;
always @(posedge clk) begin
if (~rstn)
spi_slave_tx_data <= 'hAB;
else if (spi_slave_rx_data_valid_pedge)
spi_slave_tx_data <= spi_slave_rx_data + 1'b1;
else
spi_slave_tx_data <= spi_slave_tx_data;
end
//-- 回环测试控制 ------------------------------------------------------------
第一次通信:主机发送AB,从机也发送AB,所以,主机接收到AB,从机也接收到AB;
第一次通信:主机发送AB+1,从机也发送AB+1,所以,主机接收到AB+1,从机也接收到AB+1;
以此类推。
所有实验发送数据不变,位宽变化只是使得 8’hAB 变为 10’h0AB,12’h0AB 和 16’h00AB。
3.1 fCLK = 100MHz,SPI_MODE = 0,数据位宽 = 8,时序参数 = 1/3/2,fSCLK = 10MHz
图1.1-单周期整体图:
图1.2-前两个SCLK周期波形图:
图1.3-后两个SCLK周期波形:
图1.4-两周期整体图:
从波形图中可以确定以下检查项:
检查项 | 设定值/发送值 | 仿真值/发送值 | 是否一致 |
---|---|---|---|
SCLK空闲值 | 0 | 0 | 是 |
数据采样边沿 | 上升沿 | 上升沿 | 是 |
数据更新边沿 | 下降沿 | 下降沿 | 是 |
SCLK频率 | 100/10 | 100/10 | 是 |
数据位宽 | 8 | 8 | 是 |
CS_EDGE_TO_SCLK_EDGE_CLK_NUM | 1 | 1 | 是 |
SCLK_EDGE_TO_CS_EDGE_CLK_NUM | 3 | 3 | 是 |
CS_KEEP_HIGH_CLK_NUM | 2 | 2 | 是 |
第一个周期,主机发送数据==从机接收数据 | 8’AB(发送) | 8’AB(接收) | 是 |
第一个周期,主机接收数据=从机发送数据 | 8’AB(发送) | 8’AB(接收) | 是 |
第二个周期,主机发送数据==从机接收数据 | 8’AC(发送) | 8’AC(接收) | 是 |
第二个周期,主机接收数据=从机发送数据 | 8’AC(发送) | 8’AC(接收) | 是 |
结论:
-
设定值与仿真值基本相同,其中SCLK_EDGE_TO_CS_EDGE_CLK_NUM与CS_KEEP_HIGH_CLK_NUM由于主机使用时序逻辑的限制,仿真值=设定值+2,此特性不影响实际功能。 -
主从机回环发送与接收均正常。
3.2 fCLK = 100MHz,SPI_MODE = 0,数据位宽 = 8,时序参数 = 1/3/2,fSCLK = 50MHz
图2.1–单周期整体图:
图2.2–两周期整体图:
从波形图中可以确定以下检查项:
检查项 | 设定值/发送值 | 仿真值/发送值 | 是否一致 |
---|---|---|---|
SCLK空闲值 | 0 | 0 | 是 |
数据采样边沿 | 上升沿 | 上升沿 | 是 |
数据更新边沿 | 下降沿 | 下降沿 | 是 |
SCLK频率 | 100/2 | 100/2 | 是 |
数据位宽 | 8 | 8 | 是 |
CS_EDGE_TO_SCLK_EDGE_CLK_NUM | 1 | 1 | 是 |
SCLK_EDGE_TO_CS_EDGE_CLK_NUM | 3 | 3 | 是 |
CS_KEEP_HIGH_CLK_NUM | 2 | 2 | 是 |
略 | 略 | 略 | 是 |
结论:回环测试正常。
3.3 fCLK = 100MHz,SPI_MODE = 1,数据位宽 = 10,时序参数 = 2/4/3,fSCLK = 10MHz
图3.1–单周期整体图:
图3.2–前两个SCLK周期:
图3.3–后两个SCLK周期:
图3.4–两周期整体图:
从波形图中可以确定以下检查项:
检查项 | 设定值/发送值 | 仿真值/发送值 | 是否一致 |
---|---|---|---|
SCLK空闲值 | 0 | 0 | 是 |
数据采样边沿 | 下降沿 | 下降沿 | 是 |
数据更新边沿 | 上升沿 | 上升沿 | 是 |
SCLK频率 | 100/10 | 100/10 | 是 |
数据位宽 | 10 | 10 | 是 |
CS_EDGE_TO_SCLK_EDGE_CLK_NUM | 2 | 2 | 是 |
SCLK_EDGE_TO_CS_EDGE_CLK_NUM | 4 | 4 | 是 |
CS_KEEP_HIGH_CLK_NUM | 3 | 3 | 是 |
略 | 略 | 略 | 是 |
结论:回环测试正常。
3.4 fCLK = 100MHz,SPI_MODE = 1,数据位宽 = 10,时序参数 = 2/4/3,fSCLK = 50MHz
4.1–单周期整体图:
4.2–两周期整体图:
其余略。
3.5 fCLK = 120MHz,SPI_MODE = 2,数据位宽 = 12,时序参数 = 3/5/4,fSCLK = 20MHz
5.1–单周期整体图:
5.2–前两个SCLK波形图:
5.3–后两个SCLK波形图:
5.4–两周期整体图:
从波形图中可以确定以下检查项:
检查项 | 设定值/发送值 | 仿真值/发送值 | 是否一致 |
---|---|---|---|
SCLK空闲值 | 1 | 1 | 是 |
数据采样边沿 | 下降沿 | 下降沿 | 是 |
数据更新边沿 | 上升沿 | 上升沿 | 是 |
SCLK频率 | 120/6 | 120/6 | 是 |
数据位宽 | 12 | 12 | 是 |
CS_EDGE_TO_SCLK_EDGE_CLK_NUM | 3 | 3 | 是 |
SCLK_EDGE_TO_CS_EDGE_CLK_NUM | 5 | 5 | 是 |
CS_KEEP_HIGH_CLK_NUM | 4 | 4 | 是 |
略 | 略 | 略 | 是 |
结论:回环测试正常。
3.6 fCLK = 120MHz,SPI_MODE = 2,数据位宽 = 12,时序参数 = 3/5/4,fSCLK = 30MHz
6.1–后两个SCLK波形图:
9.2–两周期整体图:
其余略。
3.7 fCLK = 120MHz,SPI_MODE = 3,数据位宽 = 16,时序参数 = 4/5/6,fSCLK = 20MHz
7.1–单周期整体图:
7.2–前两个SCLK波形图:
7.3–后两个SCLK波形图:
7.4–两周期整体图:
从波形图中可以确定以下检查项:
检查项 | 设定值/发送值 | 仿真值/发送值 | 是否一致 |
---|---|---|---|
SCLK空闲值 | 1 | 1 | 是 |
数据采样边沿 | 上升沿 | 上升沿 | 是 |
数据更新边沿 | 下降沿 | 下降沿 | 是 |
SCLK频率 | 120/6 | 120/6 | 是 |
数据位宽 | 16 | 16 | 是 |
CS_EDGE_TO_SCLK_EDGE_CLK_NUM | 4 | 4 | 是 |
SCLK_EDGE_TO_CS_EDGE_CLK_NUM | 5 | 5 | 是 |
CS_KEEP_HIGH_CLK_NUM | 6 | 6 | 是 |
略 | 略 | 略 | 是 |
结论:回环测试正常。
3.8 fCLK = 120MHz,SPI_MODE = 3,数据位宽 = 16,时序参数 = 4/5/6,fSCLK = 30MHz
8.1–后两个SCLK周期:
8.2-两周期整体图:
其余略。
四、结论
此SPI主从模块功能正常,且具有广泛的适用性:
-
可适配4种SPI模式 -
可选择任何SCLK频率(CLK频率的偶次分频) -
可灵活配置多个时序参数 -
可灵活配置数据位宽
五、分享
源码在Gitee与Github开源,两平台同步。与上篇文章是:
仿真的Vivado工程通过网盘分享。
欢迎大家关注我的微信公众号:徐晓康的博客,回复以下6位数字获取网盘链接。
451078
建议复制过去不会码错字!
如果本文对你有所帮助,欢迎点赞、转发、收藏、评论让更多人看到,赞赏支持就更好了。
如果对文章内容有疑问,请务必清楚描述问题,留言评论或私信告知我,我看到会回复。

徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。