前言
上一篇文章介绍了Verilog功能模块——SPI主机,包括主机设计思路与使用方法。
本文则用纯Verilog设计了功能完整的4线SPI从机,与网上一些以高频率clk时钟模拟从机不同,本文中的SPI从机工作时钟来源于主机的sclk,符合SPI同步通信的原则。
本文详细说明了模块编码思路和使用注意事项,最后分享了源码。
一、模块功能
本Verilog功能模块——SPI从机实现了SPI协议要求的完整时序控制,具体功能如下:
-
同步通信,工作时钟来源于主机的sclk -
支持所有4种SPI模式,通过SPI_MODE参数配置; -
数据位宽通过DATA_WIDTH参数可配置(1-32位); -
采用异步复位设计。
二、模块框图
三、信号接口
3.1 参数列表
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
SPI_MODE | integer | 3 | SPI模式, 可选0, 1, 2, 3 (默认) |
DATA_WIDTH | integer | 16 | 单次通信发送或接收数据的位宽, 最小为2, 常见8/16 |
3.2 接口信号列表
信号分组 | 信号名 | 方向 | 说明 |
---|---|---|---|
外部控制SPI信号 | spi_slave_tx_is_busy | output | SPI繁忙指示, 高电平表示SPI正在工作 |
spi_slave_tx_data[DATA_WIDTH-1:0] | input | SPI发送数据, 数据总是高位先发 | |
spi_slave_rx_data[DATA_WIDTH-1:0] | output | SPI接收数据, 最先读出的数据在最高位 | |
spi_slave_rx_data_valid | output | SPI接收数据有效指示,高电平有效 | |
SPI硬线链接 | spi_cs_n | input | 片选, 低电平有效 |
spi_sclk | input | SPI时钟, 从机提供 | |
spi_mosi | input | 从机输出从机输入 | |
spi_miso | output | 从机输入从机输出 | |
复位 | arstn | input | 异步复位, 低电平有效 |
四、编码思路
-
通过参数控制SPI模式、数据位宽、时钟频率与三种时序参数
parameter integer SPI_MODE = 3, // SPI模式, 可选0, 1, 2, 3 (默认)
parameter integer DATA_WIDTH = 16 // 单次通信发送或接收数据的位宽, 最小为1, 常见8/16 -
对参数进行有效性检查,限制参数赋值
//++ 参数有效性检查 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
initial begin
if (SPI_MODE != 0 && SPI_MODE != 1 && SPI_MODE != 2 && SPI_MODE != 3)
$error("SPI_MODE must be 0, 1, 2, 3");
if (DATA_WIDTH <= 0)
$error("DATA_WIDTH must be >= 1");
end
//-- 参数有效性检查 ------------------------------------------------------------ -
暂存cs_n信号的上一状态,作为加载发送数据的控制信号
//++ 片选状态跟踪 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
reg spi_cs_n_old; // 片选跳变前一瞬间的状态, 跳变结束后会变为新值
always @(posedge spi_cs_n or negedge spi_cs_n or negedge arstn) begin
if (~arstn)
spi_cs_n_old <= 1'b1;
else
spi_cs_n_old <= spi_cs_n;
end
//-- 片选状态跟踪 ------------------------------------------------------------
//++ 发送数据 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/*
原则是先采样再移位
*/
reg [$clog2(DATA_WIDTH+1)-1:0] sample_cnt; // 采样计数
reg [DATA_WIDTH-1:0] tx_data_lsfr;// 移位寄存器
generate
if (SPI_MODE == 0 || SPI_MODE == 3) begin // 下降沿移位, 第一个下降沿移位
always @(negedge spi_sclk or negedge spi_cs_n) begin
if (spi_cs_n_old)
tx_data_lsfr <= spi_slave_tx_data; // 片选下降沿加载发送数据
else if (~spi_cs_n && sample_cnt != 'd0)
tx_data_lsfr <= tx_data_lsfr << 1;
else
tx_data_lsfr <= tx_data_lsfr;
end
end else begin // 上升沿移位, 第一个上升沿不移位
always @(posedge spi_sclk or negedge spi_cs_n) begin
if (spi_cs_n_old)
tx_data_lsfr <= spi_slave_tx_data; // 片选下降沿加载发送数据
else if (~spi_cs_n && sample_cnt != 'd0)
tx_data_lsfr <= tx_data_lsfr << 1;
else
tx_data_lsfr <= tx_data_lsfr;
end
end
endgenerate
assign spi_miso = spi_slave_tx_is_busy ? tx_data_lsfr[DATA_WIDTH-1] : 1'bz; // 三态输出控制
//-- 发送数据 ------------------------------------------------------------注意这里的spi_cs_n_old,它是SPI同步从机最关键的代码,如果用
~spi_cs_n
,那么在整个传输中,spi_cs_n都是低电平,后续代码都无法执行了,所以只能用spi_cs_n_old,它只在spi_cs_n下降沿执行一次。
五、使用说明
// SPI从机外部控制信号
output wire spi_slave_tx_is_busy, // 指示SPI从机正在发送, 高电平有效
input wire [DATA_WIDTH-1:0] spi_slave_tx_data, // 发送数据
output reg [DATA_WIDTH-1:0] spi_slave_rx_data, // 接收数据
output reg spi_slave_rx_data_valid, // 接收数据有效
外部模块只需控制spi_slave_tx_data即可,此信号会在每个spi_cs_n下降沿锁存。
六、仿真验证
见本系列文章——Verilog功能模块–SPI从机和从机(04)–SPI从机从机回环仿真与实测。
七、源码分享
源码在Gitee与Github开源,两平台同步。与上篇文章是:
如果本文对你有所帮助,欢迎点赞、转发、收藏、评论让更多人看到,赞赏支持就更好了。
如果对文章内容有疑问,请务必清楚描述问题,留言评论或私信告知我,我看到会回复。

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