APB_I2C验证平台3————SPI 时钟生成模块设计

一、模块概述

spi_clgen是 SPI 控制器的时钟生成模块,负责从系统时钟产生 SPI 时钟(SCLK),并提供边沿检测信号供移位寄存器使用。

该模块通过一个可配置的计数器实现分频,支持不同的 SPI 传输速率,并能在传输结束时精确控制最后一个时钟沿。

二、接口信号

信号名方向位宽说明
时钟与复位
clk_in输入1系统时钟(APB PCLK)
rst输入1复位(高有效)
控制信号
enable输入1SPI 传输使能(来自 shift 模块的 tip)
go输入1启动传输信号(来自控制寄存器)
last_clk输入1最后一位时钟标志
divider输入SPI_DIVIDER_LEN分频系数(0 = 最小分频)
输出信号
clk_out输出1SPI 时钟(SCLK)
pos_edge输出1上升沿脉冲(提前一个系统时钟周期)
neg_edge输出1下降沿脉冲(提前一个系统时钟周期)

三、核心原理

SPI 时钟通过计数器分频产生:计数器从divider递减到 0,每减到 0 一次,clk_out翻转一次。两个cnt=0产生一个完整的 SPI 时钟周期。

时钟周期计算

text

SPI 时钟周期 = 2 × (divider + 1) × 系统时钟周期 举例(系统时钟 50MHz): - divider = 0 → SCLK = 25MHz(最小分频) - divider = 1 → SCLK = 12.5MHz - divider = 255 → SCLK ≈ 97.7kHz(最大分频)

四、计数器逻辑

assign cnt_zero = cnt == {`SPI_DIVIDER_LEN{1'b0}}; assign cnt_one = cnt == {{`SPI_DIVIDER_LEN-1{1'b0}}, 1'b1}; // Counter counts half period always @(posedge clk_in or posedge rst) begin if(rst) cnt <= #Tp {`SPI_DIVIDER_LEN{1'b1}}; else begin if(!enable || cnt_zero) cnt <= #Tp divider; else cnt <= #Tp cnt - {{`SPI_DIVIDER_LEN-1{1'b0}}, 1'b1}; end end

计数器行为

条件操作说明
复位cnt = 全1最慢时钟,安全状态
!enablecnt = divider空闲时加载分频值
cnt_zerocnt = divider计数到 0,重新加载
其他cnt = cnt - 1

正常递减

五、时钟输出逻辑

// clk_out is asserted every other half period always @(posedge clk_in or posedge rst) begin if(rst) clk_out <= #Tp 1'b0; else clk_out <= #Tp (enable && cnt_zero && (!last_clk || clk_out)) ? ~clk_out : clk_out; end

翻转条件

翻转 = enable && cnt_zero && (!last_clk || clk_out)
条件含义
enable传输进行中
cnt_zero计数器到 0(半周期结束)
!last_clk || clk_out非最后一位,或当前为高电平

最后一位控制

场景clk_out 状态!last_clk || clk_out动作
正常传输,clk_out=01(!last_clk=1翻转为高
正常传输,clk_out=11(!last_clk=1翻转为低
最后一位,clk_out=00(last_clk=1, clk_out=0保持低
最后一位,clk_out=11(clk_out=1翻转为低

效果:最后一位只输出半个时钟的高电平,然后保持在低电平(空闲状态),避免传输结束后出现多余时钟沿。

六、边沿信号生成

边沿信号提前一个系统时钟周期产生,让移位寄存器有足够时间准备数据。

条件场景说明
enable && !clk_out && cnt_one正常模式时钟为低,计数器=1 → 即将产生上升沿
enable && clk_out && cnt_one正常模式时钟为高,计数器=1 → 即将产生下降沿
!(|divider) && clk_out最小分频分频器=0,时钟为高 → 产生上升沿
!(|divider) && !clk_out && enable最小分频分频器=0,时钟为低 → 产生下降沿
!(|divider) && go && !enable最小分频启动分频器=0,刚启动 → 产生第一个上升沿

分频器为 0 的特殊处理

!(|divider)表示divider全为 0(最小分频模式)。此时 SPI 时钟等于系统时钟/2,边沿信号需要特殊处理,确保第一时间产生。

七.完整spi_clgen.v代码

`include "spi_defines.v" `include "timescale.v" module spi_clgen (clk_in, rst, go, enable, last_clk, divider, clk_out, pos_edge, neg_edge); parameter Tp = 1; // -------------------------------------------------------------------- // 时钟生成模块:从系统时钟产生 SPI 时钟 (sclk) 及其边沿预告信号 // 输入: // clk_in : 系统时钟 (APB PCLK) // rst : 复位 (高有效) // enable : SPI 传输使能 (来自 spi_shift 的 tip) // go : 启动传输信号 (来自控制寄存器) // last_clk : 最后一位时钟标志 (来自 spi_shift 的 last) // divider : 分频系数 (0 表示最小分频) // 输出: // clk_out : SPI 时钟 (sclk) // pos_edge : 上升沿预告脉冲 (提前一个系统时钟周期) // neg_edge : 下降沿预告脉冲 // -------------------------------------------------------------------- input clk_in; input rst; input enable; input go; input last_clk; input [`SPI_DIVIDER_LEN-1:0] divider; output clk_out; output pos_edge; output neg_edge; reg clk_out; reg pos_edge; reg neg_edge; reg [`SPI_DIVIDER_LEN-1:0] cnt; // 半周期计数器 wire cnt_zero; // 计数器为 0 (半周期结束) wire cnt_one; // 计数器为 1 (下一个周期将到 0) // cnt_zero = 所有位为 0 assign cnt_zero = cnt == {`SPI_DIVIDER_LEN{1'b0}}; // cnt_one = 最低位为 1,其余位为 0 (即数值 1) assign cnt_one = cnt == {{`SPI_DIVIDER_LEN-1{1'b0}}, 1'b1}; // ==================================================================== // 半周期计数器 // 复位时:cnt = 全1 (最大值,SPI 时钟最慢) // 计数过程: // - 若 !enable 或 cnt_zero (空闲或半周期结束),则加载 divider // - 否则 cnt 递减 1 // ==================================================================== always @(posedge clk_in or posedge rst) begin if(rst) cnt <= #Tp {`SPI_DIVIDER_LEN{1'b1}}; else begin if(!enable || cnt_zero) cnt <= #Tp divider; else // 递减操作:原代码 {{...}} 构造了位宽匹配的 1,简化理解即 cnt - 1 cnt <= #Tp cnt - {{`SPI_DIVIDER_LEN-1{1'b0}}, 1'b1}; end end // ==================================================================== // SPI 时钟输出 (clk_out) // 翻转条件:enable && cnt_zero && (!last_clk || clk_out) // - enable 为高:传输进行中 // - cnt_zero 为高:半周期结束 // - (!last_clk || clk_out) 控制最后一位只输出半个高电平: // 最后一位时 (last_clk=1),若 clk_out=0 则条件不成立 → 不翻转,保持低; // 若 clk_out=1 则条件成立 → 翻转为低。从而实现最后半个周期高电平后停在高电平?实际是最后高后翻低再保持低。 // 正常传输时,last_clk=0,条件恒真,每 cnt_zero 翻转一次。 // ==================================================================== always @(posedge clk_in or posedge rst) begin if(rst) clk_out <= #Tp 1'b0; else clk_out <= #Tp (enable && cnt_zero && (!last_clk || clk_out)) ? ~clk_out : clk_out; end // ==================================================================== // 边沿预告信号 (pos_edge, neg_edge) // 这些脉冲在系统时钟域产生,比实际 SPI 时钟边沿提前一个周期, // 用于通知 spi_shift 模块准备采样或驱动数据。 // // pos_edge 产生条件: // 1. 正常模式:enable && !clk_out && cnt_one // (传输中、当前时钟为低、计数器为 1 → 即将产生上升沿) // 2. 最小分频模式 (divider=0): // a) !(|divider) && clk_out (时钟为高时产生上升沿) // b) !(|divider) && go && !enable (刚启动时产生第一个上升沿) // // neg_edge 产生条件: // 1. 正常模式:enable && clk_out && cnt_one // (传输中、当前时钟为高、计数器为 1 → 即将产生下降沿) // 2. 最小分频模式:!(|divider) && !clk_out && enable // (时钟为低且传输中产生下降沿) // ==================================================================== always @(posedge clk_in or posedge rst) begin if(rst) begin pos_edge <= #Tp 1'b0; neg_edge <= #Tp 1'b0; end else begin pos_edge <= #Tp (enable && !clk_out && cnt_one) || (!(|divider) && clk_out) || (!(|divider) && go && !enable); neg_edge <= #Tp (enable && clk_out && cnt_one) || (!(|divider) && !clk_out && enable); end end endmodule