APB_I2C验证平台4----------数据移位传输详解

张开发
2026/4/11 20:50:41 15 分钟阅读

分享文章

APB_I2C验证平台4----------数据移位传输详解
一.模块概述spi_shift是 SPI 主控制器的核心数据通路模块负责将 CPU 写入的并行数据 (发送数据) 加载到移位寄存器在 SPI 时钟边沿将移位寄存器的数据逐位串行输出 (MOSI)在 SPI 时钟边沿从 MISO 逐位采样并存入移位寄存器传输完成后CPU 读取移位寄存器内容作为接收数据。模块内部使用一个统一的移位寄存器data既存放待发送数据也存放接收到的串行数据。通过控制信号latch和rx_clk/tx_clk分时复用。二.接口信号信号名方向位宽描述时钟与复位clk输入1系统时钟 (APB PCLK)rst输入1异步复位高有效控制与状态len输入SPI_CHAR_LEN_BITS传输位数减一 (0 表示 1 位)lsb输入1最低位优先 (1LSB first, 0MSB first)go输入1启动传输 (来自控制寄存器)pos_edge输入1SPI 时钟上升沿预告脉冲 (来自 spi_clgen)neg_edge输入1SPI 时钟下降沿预告脉冲rx_negedge输入11在下降沿采样 MISO0在上升沿采样tx_negedge输入11在下降沿变化 MOSI0在上升沿变化tip输出1传输进行中 (高有效)last输出1最后一位标志 (cnt 0)数据接口p_in输入32CPU 写数据 (并行输入)p_out输出SPI_MAX_CHARCPU 读数据 (并行输出)s_clk输入1SPI 时钟 (来自 spi_clgen)s_in输入1串行输入 (MISO)s_out输出1串行输出 (MOSI)latch输入4锁存信号对应 APB 写发送寄存器的地址 (spi_tx_sel[3:0])byte_sel输入4字节选择信号支持非对齐字节写入三. 内部结构3.1 移位寄存器data宽度为SPI_MAX_CHAR(可配置 8/16/24/32/64/128 位)由 CPU 写入 (发送数据) 和串行接收 (MISO) 共用输出p_out直接连接到data供 CPU 读取接收数据3.2 位计数器cnt宽度SPI_CHAR_LEN_BITS 1比len多 1 位用于处理len0边界情况空闲时 (tip0)加载len(若len0) 或2^SPI_CHAR_LEN_BITS(若len0)传输中 (tip1)每个pos_edge时钟减 1last !(|cnt)指示当前传输为最后一位3.3 位位置计算tx_bit_pos决定从data的哪一位取出串行输出MSB 优先 (lsb0)tx_bit_pos cnt - 1LSB 优先 (lsb1)tx_bit_pos {!(|len), len} - cnt(处理len0)rx_bit_pos决定串行输入s_in存入data的哪一位MSB 优先rx_bit_pos rx_negedge ? cnt : cnt - 1LSB 优先rx_bit_pos {!(|len), len} - (rx_negedge ? cnt1 : cnt)使用位时截取低SPI_CHAR_LEN_BITS位作为索引 (高位仅用于边界处理)3.4 时钟使能信号tx_clk (tx_negedge ? neg_edge : pos_edge) !last用于控制串行输出更新的时刻。最后一位之后不再产生发送时钟。rx_clk (rx_negedge ? neg_edge : pos_edge) (!last || s_clk)用于控制串行输入采样的时刻。最后一位时需要配合s_clk完成最后一次采样。3.5 传输状态机 (tip)复位时tip 0当go1且tip0时tip1启动传输当tip1 last1 pos_edge1时tip0结束传输(注意仅支持采样边沿为上升沿的模式即模式0或模式3)4. 关键逻辑详解4.1 数据加载 (CPU 写发送数据)在!tip(空闲) 且latch[i]1时将p_in按字节写入data的对应位置。支持 128/64/32/24/16/8 位宽度通过byte_sel实现字节粒度写入。4.2 串行发送条件(tx_clk || !tip)确保空闲时 (!tip) 预置第一位 (满足建立时间)传输中每个tx_clk脉冲输出下一位输出位从data[tx_bit_pos[低N位]]取得。4.3 串行接收在rx_clk有效时将s_in存入data[rx_bit_pos[低N位]]否则保持原值。五.源代码include spi_defines.v include timescale.v module spi_shift (clk, rst, latch, byte_sel, len, lsb, go, pos_edge, neg_edge, rx_negedge, tx_negedge, tip, last, p_in, p_out, s_clk, s_in, s_out); parameter Tp 1; // -------------------------------------------------------------------- // 输入输出信号说明 // clk : 系统时钟 (APB PCLK) // rst : 复位 (高有效) // latch[3:0] : 锁存信号对应 APB 写发送寄存器地址 (spi_tx_sel) // byte_sel[3:0]: 字节选择用于非对齐写入 // len : 传输位数减一 (0 表示 1 位) // lsb : 最低位优先 (1LSB first, 0MSB first) // go : 启动传输 (来自控制寄存器) // pos_edge : SPI 时钟上升沿预告脉冲 (来自 spi_clgen) // neg_edge : SPI 时钟下降沿预告脉冲 // rx_negedge : 1在下降沿采样 MISO0上升沿采样 // tx_negedge : 1在下降沿变化 MOSI0上升沿变化 // tip : 传输进行中 (高有效) // last : 最后一位标志 (cnt 0) // p_in[31:0] : CPU 写数据 (并行输入) // p_out : CPU 读数据 (并行输出) // s_clk : SPI 时钟 (来自 spi_clgen) // s_in : 串行输入 (MISO) // s_out : 串行输出 (MOSI) // -------------------------------------------------------------------- input clk; input rst; input [3:0] latch; input [3:0] byte_sel; input [SPI_CHAR_LEN_BITS-1:0] len; input lsb; input go; input pos_edge; input neg_edge; input rx_negedge; input tx_negedge; output tip; output last; input [31:0] p_in; output [SPI_MAX_CHAR-1:0] p_out; input s_clk; input s_in; output s_out; reg s_out; reg tip; reg [SPI_CHAR_LEN_BITS:0] cnt; // 位计数器比 len 多 1 位 (用于 len0 边界) reg [SPI_MAX_CHAR-1:0] data; // 移位寄存器存放发送数据也接收串行数据 wire [SPI_CHAR_LEN_BITS:0] tx_bit_pos; // 发送位位置指针 (可能含边界扩展位) wire [SPI_CHAR_LEN_BITS:0] rx_bit_pos; // 接收位位置指针 wire rx_clk; // 接收采样时钟使能 wire tx_clk; // 发送移位时钟使能 // 并行输出直接连接 data 寄存器 assign p_out data; // // 发送位位置计算 // 原复杂表达式简化为 1b1功能等价 // assign tx_bit_pos lsb ? {!(|len), len} - cnt : cnt - 1b1; // LSB 优先时需要 {!(|len),len} 将 len0 映射为一个正数 (2^SPI_CHAR_LEN_BITS) // 避免 len - cnt 出现负数 // MSB 优先时直接 cnt-1 // // 接收位位置计算 // 下降沿采样时 (rx_negedge1) 需要 cnt1因为采的是上一个下降沿发送的数据 // assign rx_bit_pos lsb ? {!(|len), len} - (rx_negedge ? cnt 1b1 : cnt) : (rx_negedge ? cnt : cnt - 1b1); // 最后一位标志cnt 所有位为 0 assign last !(|cnt); // // 接收时钟使能 // 根据 rx_negedge 选择 pos_edge 或 neg_edge // 最后一位时需要 s_clk 配合完成最后采样 (!last || s_clk) // assign rx_clk (rx_negedge ? neg_edge : pos_edge) (!last || s_clk); // // 发送时钟使能 // 根据 tx_negedge 选择 pos_edge 或 neg_edge // 最后一位之后不再发送 ( !last) // assign tx_clk (tx_negedge ? neg_edge : pos_edge) !last; // // 位计数器行为 // 空闲时 (tip0) 加载 len (或 len0 时的特殊值) // 传输中 (tip1) 每个 pos_edge 减 1 // always (posedge clk or posedge rst) begin if(rst) cnt #Tp 0; else begin if(tip) cnt #Tp pos_edge ? (cnt - 1b1) : cnt; else // 空闲时若 len0 则 cnt 2^SPI_CHAR_LEN_BITS否则 cnt len cnt #Tp !(|len) ? {1b1, {SPI_CHAR_LEN_BITS{1b0}}} : {1b0, len}; end end // // 传输进行中标志 (tip) 状态机 // go1 且空闲 → 启动 // 最后一位且 pos_edge 采样完成 → 结束 // 注意只支持采样边沿为上升沿 (pos_edge) 的模式 (模式0/3) // always (posedge clk or posedge rst) begin if(rst) tip #Tp 1b0; else if(go ~tip) tip #Tp 1b1; else if(tip last pos_edge) tip #Tp 1b0; end // // 串行发送 (MOSI) // 条件 (tx_clk || !tip) 保证 // - 空闲时 (!tip) 预置第一位满足建立时间 // - 传输中每个 tx_clk 脉冲输出下一位 // data[tx_bit_pos[低N位]]截取 tx_bit_pos 的低有效位作为 data 的索引 // always (posedge clk or posedge rst) begin if (rst) s_out #Tp 1b0; else s_out #Tp (tx_clk || !tip) ? data[tx_bit_pos[SPI_CHAR_LEN_BITS-1:0]] : s_out; end // // 接收与数据加载 (data 寄存器) // 复位清零 // 当 CPU 写发送寄存器 (latch[x]1) 且空闲 (!tip) 时将 p_in 按字节存入 data // 否则在 rx_clk 有效时将 s_in 存入 data[rx_bit_pos[低N位]] // always (posedge clk or posedge rst) begin if (rst) data #Tp 0; ifdef SPI_MAX_CHAR_128 // 128 位模式4 次 latch 分别写入 32 位段 else if (latch[0] !tip) begin if (byte_sel[3]) data[31:24] #Tp p_in[31:24]; if (byte_sel[2]) data[23:16] #Tp p_in[23:16]; if (byte_sel[1]) data[15:8] #Tp p_in[15:8]; if (byte_sel[0]) data[7:0] #Tp p_in[7:0]; end else if (latch[1] !tip) begin if (byte_sel[3]) data[63:56] #Tp p_in[31:24]; if (byte_sel[2]) data[55:48] #Tp p_in[23:16]; if (byte_sel[1]) data[47:40] #Tp p_in[15:8]; if (byte_sel[0]) data[39:32] #Tp p_in[7:0]; end else if (latch[2] !tip) begin if (byte_sel[3]) data[95:88] #Tp p_in[31:24]; if (byte_sel[2]) data[87:80] #Tp p_in[23:16]; if (byte_sel[1]) data[79:72] #Tp p_in[15:8]; if (byte_sel[0]) data[71:64] #Tp p_in[7:0]; end else if (latch[3] !tip) begin if (byte_sel[3]) data[127:120] #Tp p_in[31:24]; if (byte_sel[2]) data[119:112] #Tp p_in[23:16]; if (byte_sel[1]) data[111:104] #Tp p_in[15:8]; if (byte_sel[0]) data[103:96] #Tp p_in[7:0]; end else ifdef SPI_MAX_CHAR_64 // 64 位模式2 次 latch 写入 32 位段 else if (latch[0] !tip) begin if (byte_sel[3]) data[31:24] #Tp p_in[31:24]; if (byte_sel[2]) data[23:16] #Tp p_in[23:16]; if (byte_sel[1]) data[15:8] #Tp p_in[15:8]; if (byte_sel[0]) data[7:0] #Tp p_in[7:0]; end else if (latch[1] !tip) begin if (byte_sel[3]) data[63:56] #Tp p_in[31:24]; if (byte_sel[2]) data[55:48] #Tp p_in[23:16]; if (byte_sel[1]) data[47:40] #Tp p_in[15:8]; if (byte_sel[0]) data[39:32] #Tp p_in[7:0]; end else // 32 位及以下模式单次 latch 写入按字节选择 else if (latch[0] !tip) begin ifdef SPI_MAX_CHAR_8 if (byte_sel[0]) data[SPI_MAX_CHAR-1:0] #Tp p_in[SPI_MAX_CHAR-1:0]; endif ifdef SPI_MAX_CHAR_16 if (byte_sel[0]) data[7:0] #Tp p_in[7:0]; if (byte_sel[1]) data[SPI_MAX_CHAR-1:8] #Tp p_in[SPI_MAX_CHAR-1:8]; endif ifdef SPI_MAX_CHAR_24 if (byte_sel[0]) data[7:0] #Tp p_in[7:0]; if (byte_sel[1]) data[15:8] #Tp p_in[15:8]; if (byte_sel[2]) data[SPI_MAX_CHAR-1:16] #Tp p_in[SPI_MAX_CHAR-1:16]; endif ifdef SPI_MAX_CHAR_32 if (byte_sel[0]) data[7:0] #Tp p_in[7:0]; if (byte_sel[1]) data[15:8] #Tp p_in[15:8]; if (byte_sel[2]) data[23:16] #Tp p_in[23:16]; if (byte_sel[3]) data[SPI_MAX_CHAR-1:24] #Tp p_in[SPI_MAX_CHAR-1:24]; endif end endif endif else // 串行接收在 rx_clk 有效时将 s_in 写入 data 的对应位 // rx_bit_pos[低N位] 作为索引高位在 len0 时用于边界处理 data[rx_bit_pos[SPI_CHAR_LEN_BITS-1:0]] #Tp rx_clk ? s_in : data[rx_bit_pos[SPI_CHAR_LEN_BITS-1:0]]; end endmodule

更多文章