`timescale 1 ns / 1 ps //------------------------------------------------------------------------------ // 简单的 CSI040 SPI master。 // // 命令侧是一个 request/ready 接口: // cmd_valid 和 cmd_ready 同时为 1 时启动一帧 SPI。 // cmd_write 选择写 opcode 或读 opcode。 // cmd_addr 在 opcode 后发送,高字节先发。 // cmd_wdata 的 payload byte0 来自 cmd_wdata[7:0]。 // cmd_len 选择 payload 字节数,并被限制到 1..MAX_BURST_BYTES。 // // 当前 SPI 模式: // csn 空闲为高,低有效 // sck 空闲为低 // si 在下降沿后更新 // 读数据阶段在上升沿采样 so //------------------------------------------------------------------------------ module csi040_spi_master# ( parameter CLK_DIV = 50, // SCK 半周期对应的 clk 周期数 parameter REG_ADDR_WIDTH = 16, parameter ADDR_BYTES = 2, parameter MAX_BURST_BYTES = 4, parameter [7:0] OPCODE_WRITE = 8'h00, parameter [7:0] OPCODE_READ = 8'h80 ) ( input clk, input rst_n, // 来自 csi040_ctrl 的命令请求。 output cmd_ready, input cmd_valid, input cmd_write, input [REG_ADDR_WIDTH-1:0]cmd_addr, input [8*MAX_BURST_BYTES-1:0] cmd_wdata, input [7:0] cmd_len, output reg done, output reg [8*MAX_BURST_BYTES-1:0] cmd_rdata, output reg busy, // ASIC SPI 引脚。 output csn, output sck, output si, input so ); // 内部最大帧宽度包含 opcode + 地址 + 最大 payload。 // 实际只会移出 HEADER_BITS + 8*safe_cmd_len 个 bit。 localparam FRAME_MAX_BITS = 8 * (1 + ADDR_BYTES + MAX_BURST_BYTES); localparam HEADER_BITS = 8 * (1 + ADDR_BYTES); localparam [15:0] MAX_BURST_BYTES_W = MAX_BURST_BYTES; localparam [15:0] HEADER_BITS_W = HEADER_BITS; localparam IDLE_ST = 2'd0; localparam TRANS_ST = 2'd1; localparam DONE_ST = 2'd2; // 传输状态、移位寄存器和计数器。 reg [1:0] cstate; reg [FRAME_MAX_BITS-1:0] tx_shift; reg [8*MAX_BURST_BYTES-1:0] rx_shift; reg [15:0] half_cnt; reg [15:0] total_bits; reg [15:0] sampled_bits; reg csn_r; reg sck_r; reg si_r; reg cmd_write_r; reg [7:0] cmd_len_r; reg last_sampled; // 组合逻辑组帧寄存器。 reg [FRAME_MAX_BITS-1:0] frame; reg [REG_ADDR_WIDTH-1:0] addr_pad; integer n; wire half_tick; wire data_bit_phase; wire sample_data_bit; wire [15:0] safe_cmd_len; wire [15:0] cmd_total_bits; // 把 cmd_len 限制在合法范围。因为 CSI040 寄存器按 byte 访问, // 长度为 0 时按 1 字节处理。 assign safe_cmd_len = (cmd_len == 0) ? 16'd1 : (cmd_len > MAX_BURST_BYTES) ? MAX_BURST_BYTES_W : {8'd0, cmd_len}; // 总移位 bit 数 = opcode + 地址字节 + payload 字节。 assign cmd_total_bits = HEADER_BITS_W + (safe_cmd_len << 3); // 一个 SCK 半周期已经到达。 assign half_tick = (half_cnt == (CLK_DIV - 1)); // 只有发完 opcode/address 头部之后,读数据阶段才有效。 assign data_bit_phase = (sampled_bits >= HEADER_BITS); assign sample_data_bit = (!cmd_write_r) && data_bit_phase; assign cmd_ready = (cstate == IDLE_ST); assign csn = csn_r; assign sck = sck_r; assign si = si_r; // 构造最大长度 SPI 帧: // [opcode][addr MSB..LSB][payload byte0][payload byte1]... // payload byte0 来自 cmd_wdata[7:0]。 always @(*) begin frame = {FRAME_MAX_BITS{1'b0}}; addr_pad = cmd_addr; frame[FRAME_MAX_BITS-1 -: 8] = cmd_write ? OPCODE_WRITE : OPCODE_READ; for (n = 0; n < ADDR_BYTES; n = n + 1) begin frame[FRAME_MAX_BITS-1-8*(n+1) -: 8] = addr_pad[8*(ADDR_BYTES-n)-1 -: 8]; end for (n = 0; n < MAX_BURST_BYTES; n = n + 1) begin frame[FRAME_MAX_BITS-1-8*(1+ADDR_BYTES+n) -: 8] = cmd_wdata[8*n +: 8]; end end // SPI 主状态机。每 CLK_DIV 个 clk 周期翻转一次 SCK。 // SCK 上升沿采样数据,SCK 下降沿准备下一位 SI。 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cstate <= IDLE_ST; tx_shift <= 0; rx_shift <= 0; half_cnt <= 0; total_bits <= 0; sampled_bits <= 0; csn_r <= 1'b1; sck_r <= 1'b0; si_r <= 1'b0; cmd_write_r <= 1'b0; cmd_len_r <= 0; last_sampled <= 1'b0; done <= 1'b0; cmd_rdata <= 0; busy <= 1'b0; end else begin done <= 1'b0; case (cstate) IDLE_ST: begin csn_r <= 1'b1; sck_r <= 1'b0; si_r <= 1'b0; half_cnt <= 0; sampled_bits <= 0; last_sampled <= 1'b0; busy <= 1'b0; if (cmd_valid) begin // 帧开始时锁存命令,这样上层后续改变命令总线也不影响本帧。 tx_shift <= frame; rx_shift <= 0; total_bits <= cmd_total_bits; cmd_write_r <= cmd_write; cmd_len_r <= safe_cmd_len[7:0]; csn_r <= 1'b0; si_r <= frame[FRAME_MAX_BITS-1]; busy <= 1'b1; cstate <= TRANS_ST; end end TRANS_ST: begin busy <= 1'b1; if (half_tick) begin half_cnt <= 0; if (!sck_r) begin // 上升沿:ASIC 采样 SI;读 payload 阶段 master 采样 SO。 sck_r <= 1'b1; if (sample_data_bit) rx_shift <= {rx_shift[8*MAX_BURST_BYTES-2:0], so}; sampled_bits <= sampled_bits + 1'b1; if (sampled_bits == (total_bits - 1)) last_sampled <= 1'b1; end else begin // 下降沿:准备下一位输出;如果最后一位已经采完则结束。 sck_r <= 1'b0; if (last_sampled) begin cstate <= DONE_ST; cmd_rdata <= rx_shift; end else begin tx_shift <= {tx_shift[FRAME_MAX_BITS-2:0], 1'b0}; si_r <= tx_shift[FRAME_MAX_BITS-2]; end end end else begin half_cnt <= half_cnt + 1'b1; end end DONE_ST: begin // 拉高 CSN,并产生 1 个 clk 周期的 done 脉冲。 csn_r <= 1'b1; sck_r <= 1'b0; si_r <= 1'b0; busy <= 1'b0; done <= 1'b1; last_sampled <= 1'b0; cstate <= IDLE_ST; end default: begin cstate <= IDLE_ST; end endcase end end endmodule