`timescale 1 ns / 1 ps //------------------------------------------------------------------------------ // CSI040 ASIC 控制模块。 // // 这个模块对软件暴露一组 BRAM 风格寄存器。软件写寄存器时, // 要么更新本地 shadow 寄存器,要么启动一个高层命令序列; // 高层命令序列最终会被转换成一次或多次 CSI040 SPI 传输。 // // 软件侧: // bram_* - 简单 32 bit 寄存器访问口 // busy - 命令引擎忙,暂时不能接收新命令 // error_busy - 忙时写命令产生的粘滞错误标志 // // ASIC 侧: // asic_csn/sck/si/so - 由 csi040_spi_master 驱动的 SPI 总线 // asic_rst_n/trig - 由 BRAM PIN_CTRL 直接控制的引脚 //------------------------------------------------------------------------------ module csi040_ctrl# ( // 通用 SPI/ASIC 参数配置。 parameter VERSION = 32'h04040001, parameter SPI_CLK_DIV = 50, parameter REG_ADDR_WIDTH = 16, parameter SPI_ADDR_BYTES = 2, parameter MAX_BURST_BYTES = 4, parameter [7:0] SPI_OPCODE_WRITE = 8'h00, parameter [7:0] SPI_OPCODE_READ = 8'h80, // 命令引擎会用到的 CSI040 ASIC 内部寄存器地址。 parameter [REG_ADDR_WIDTH-1:0] CSI_REG_STATUS1 = 16'h0000, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_STATUS2 = 16'h0001, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_CONTROL = 16'h0002, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_INTERRUPT = 16'h0003, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_Z_MEAS_SAMP = 16'h0004, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_Z_CURRENT_CAL = 16'h0005, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_Z_DELAY_RANGE = 16'h0006, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_CH_ENABLE1 = 16'h0007, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_CH_ENABLE2 = 16'h0008, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_TEST_DCS_KEY = 16'h0009, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_TEST_MODE = 16'h000A, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_CUR_REF_TRIM = 16'h000B, parameter [REG_ADDR_WIDTH-1:0] CSI_CH_BASE = 16'h0010, parameter [REG_ADDR_WIDTH-1:0] CSI_CH_STRIDE = 16'h0010, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_STIM_TRIGGER = 16'h0110, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_PART_ID = 16'h011D, parameter [REG_ADDR_WIDTH-1:0] CSI_REG_REVISION = 16'h011E ) ( input clk, input rst_n, // BRAM 风格的软件寄存器接口。bram_clk/bram_rst 当前只是保留接口, // 实际访问逻辑用 clk 采样。 input [12:0] bram_addr, input bram_clk, input [31:0] bram_din, output reg [31:0] bram_dout, input bram_en, input bram_rst, input [3:0] bram_we, output busy, output spi_done, output reg error_busy, // ASIC 直接控制引脚和 SPI 引脚。 output reg asic_rst_n, output reg asic_trig, output asic_csn, output asic_sck, output asic_si, input asic_so ); //------------------------------------------------------------------------- // 软件可见的 BRAM 寄存器地址映射。 //------------------------------------------------------------------------- localparam BRAM_ADDR_VERSION = 13'h0000; localparam BRAM_ADDR_STATUS = 13'h0004; localparam BRAM_ADDR_SPI_ADDR = 13'h0008; localparam BRAM_ADDR_SPI_WDATA = 13'h000C; localparam BRAM_ADDR_SPI_CTRL = 13'h0010; localparam BRAM_ADDR_SPI_RDATA = 13'h0014; localparam BRAM_ADDR_PIN_CTRL = 13'h0018; localparam BRAM_ADDR_LAST_ADDR = 13'h001C; localparam BRAM_ADDR_CONTROL = 13'h0020; localparam BRAM_ADDR_CH_ENABLE = 13'h0024; localparam BRAM_ADDR_TRIGGER = 13'h0028; localparam BRAM_ADDR_SEQ_CMD = 13'h002C; localparam BRAM_ADDR_CH_INDEX = 13'h0040; localparam BRAM_ADDR_CH_MISC = 13'h0044; localparam BRAM_ADDR_CH_CUR_A = 13'h0048; localparam BRAM_ADDR_CH_CUR_B = 13'h004C; localparam BRAM_ADDR_CH_CUR_C = 13'h0050; localparam BRAM_ADDR_CH_CUR_D = 13'h0054; localparam BRAM_ADDR_CH_STIM_PW = 13'h0058; localparam BRAM_ADDR_CH_DZ0 = 13'h005C; localparam BRAM_ADDR_CH_DZ1 = 13'h0060; localparam BRAM_ADDR_CH_SKIP_AB = 13'h0064; localparam BRAM_ADDR_CH_SKIP_CD = 13'h0068; localparam BRAM_ADDR_CH_STIM_NUM = 13'h006C; localparam BRAM_ADDR_CH_DZ2 = 13'h0070; localparam BRAM_ADDR_CH_THERAPY_DELAY = 13'h0074; localparam BRAM_ADDR_CH_THERAPY_NUM = 13'h0078; localparam BRAM_ADDR_CH_DZ3 = 13'h007C; localparam BRAM_ADDR_CH_MASTER_NUM = 13'h0080; // CSI040 每个通道寄存器窗口内的偏移。 // ASIC 地址 = CSI_CH_BASE + ch_index * CSI_CH_STRIDE + CH_OFF_*。 localparam CH_OFF_MISC = 4'h0; localparam CH_OFF_CUR_A = 4'h1; localparam CH_OFF_CUR_B = 4'h2; localparam CH_OFF_CUR_C = 4'h3; localparam CH_OFF_CUR_D = 4'h4; localparam CH_OFF_STIM_PW = 4'h5; localparam CH_OFF_DZ0 = 4'h6; localparam CH_OFF_DZ1 = 4'h7; localparam CH_OFF_SKIP_AB = 4'h8; localparam CH_OFF_SKIP_CD = 4'h9; localparam CH_OFF_STIM_NUM = 4'hA; localparam CH_OFF_DZ2 = 4'hB; localparam CH_OFF_THERAPY_DELAY = 4'hC; localparam CH_OFF_THERAPY_NUM = 4'hD; localparam CH_OFF_DZ3 = 4'hE; localparam CH_OFF_MASTER_NUM = 4'hF; // 高层命令序列编号。一个序列可能只发一帧 SPI,也可能发多帧, // 例如通道使能、DCS 解锁等。 localparam SEQ_IDLE = 4'h0; localparam SEQ_WRITE_CONTROL = 4'h1; localparam SEQ_CH_ENABLE = 4'h2; localparam SEQ_TRIGGER = 4'h3; localparam SEQ_DCS_UNLOCK = 4'h4; localparam SEQ_TEST_UNLOCK = 4'h5; localparam SEQ_MODE_EXIT = 4'h6; localparam SEQ_CH_WRITE = 4'h7; localparam SEQ_DIRECT = 4'h8; // 发给 SPI master 的抽象命令接口。 reg spi_cmd_valid; reg spi_cmd_write; reg [REG_ADDR_WIDTH-1:0] spi_cmd_addr; reg [8*MAX_BURST_BYTES-1:0] spi_cmd_wdata; reg [7:0] spi_cmd_len; wire spi_cmd_ready; wire [8*MAX_BURST_BYTES-1:0] spi_cmd_rdata; wire spi_busy; wire spi_master_done; // Direct SPI 访问寄存器。软件想直接访问 ASIC 任意地址时, // 通过 SPI_ADDR、SPI_WDATA、SPI_CTRL 配置这些寄存器。 reg [REG_ADDR_WIDTH-1:0] direct_addr; reg [31:0] direct_wdata; reg [7:0] direct_len; reg direct_write; reg [31:0] last_rdata; reg [REG_ADDR_WIDTH-1:0] last_addr; // shadow 配置寄存器和序列状态。 reg [7:0] control_shadow; reg [15:0] ch_enable_shadow; reg [3:0] ch_index; reg [7:0] ch_data; reg [3:0] ch_offset; reg [3:0] seq_id; reg [2:0] seq_step; reg seq_active; reg done_latched; wire bram_wr_valid; wire bram_rd_valid; wire ctrl_busy; // 当前只接受整字写。部分 byte enable 写会被忽略。 assign bram_wr_valid = bram_en && (bram_we == 4'hF); assign bram_rd_valid = bram_en && (bram_we == 4'h0); // 从序列启动开始,到 SPI master 和待发送命令脉冲都回到空闲前, // busy 一直为 1。 assign ctrl_busy = seq_active || spi_busy || spi_cmd_valid; assign busy = ctrl_busy; assign spi_done = spi_master_done; // 把选中的通道号和通道内偏移转换成 CSI040 ASIC 寄存器地址。 function [REG_ADDR_WIDTH-1:0] ch_addr; input [3:0] ch; input [3:0] offset; begin ch_addr = CSI_CH_BASE + (ch * CSI_CH_STRIDE) + offset; end endfunction // 返回 CSI040 特殊模式序列需要写入的 key 字节。 // DCS 解锁: 55, B3, 0A // Test 解锁: AA, 4C, F5 // 退出模式: F9 function [7:0] seq_key_data; input [3:0] id; input [2:0] step; begin case (id) SEQ_DCS_UNLOCK: begin case (step) 3'd0: seq_key_data = 8'h55; 3'd1: seq_key_data = 8'hB3; default: seq_key_data = 8'h0A; endcase end SEQ_TEST_UNLOCK: begin case (step) 3'd0: seq_key_data = 8'hAA; 3'd1: seq_key_data = 8'h4C; default: seq_key_data = 8'hF5; endcase end default: seq_key_data = 8'hF9; endcase end endfunction // 底层 SPI 移位器。csi040_ctrl 只决定发什么命令; // 具体串行时序都由该子模块完成。 csi040_spi_master# ( .CLK_DIV ( SPI_CLK_DIV ), .REG_ADDR_WIDTH ( REG_ADDR_WIDTH ), .ADDR_BYTES ( SPI_ADDR_BYTES ), .MAX_BURST_BYTES ( MAX_BURST_BYTES ), .OPCODE_WRITE ( SPI_OPCODE_WRITE ), .OPCODE_READ ( SPI_OPCODE_READ ) ) u_csi040_spi_master ( .clk ( clk ), .rst_n ( rst_n ), .cmd_ready ( spi_cmd_ready ), .cmd_valid ( spi_cmd_valid ), .cmd_write ( spi_cmd_write ), .cmd_addr ( spi_cmd_addr ), .cmd_wdata ( spi_cmd_wdata ), .cmd_len ( spi_cmd_len ), .done ( spi_master_done ), .cmd_rdata ( spi_cmd_rdata ), .busy ( spi_busy ), .csn ( asic_csn ), .sck ( asic_sck ), .si ( asic_si ), .so ( asic_so ) ); //------------------------------------------------------------------------- // 软件写解码、shadow 寄存器更新、序列状态维护。 // // 这个 always 块做三件事: // 1. 复位和维护本地 shadow 寄存器。 // 2. SPI 完成时,推进或结束当前高层序列。 // 3. 解码 BRAM 写操作,并在空闲时启动新命令序列。 //------------------------------------------------------------------------- always @(posedge clk or negedge rst_n) begin if (!rst_n) begin direct_addr <= 0; direct_wdata <= 0; direct_len <= 8'd1; direct_write <= 1'b1; control_shadow <= 8'h00; ch_enable_shadow <= 16'h0000; ch_index <= 4'h0; ch_data <= 8'h00; ch_offset <= 4'h0; seq_id <= SEQ_IDLE; seq_step <= 0; seq_active <= 1'b0; error_busy <= 1'b0; done_latched <= 1'b0; last_rdata <= 0; last_addr <= 0; asic_rst_n <= 1'b1; asic_trig <= 1'b0; end else begin // done_latched 是粘滞标志,直到软件写 STATUS[0] 才清除。 done_latched <= done_latched; // 每完成一帧 SPI,要么结束单帧序列,要么推进多帧序列的 seq_step。 if (spi_master_done) begin done_latched <= 1'b1; last_rdata <= spi_cmd_rdata[31:0]; last_addr <= spi_cmd_addr; case (seq_id) SEQ_CH_ENABLE: begin if (seq_step == 3'd1) begin seq_active <= 1'b0; seq_id <= SEQ_IDLE; end else begin seq_step <= seq_step + 1'b1; end end SEQ_DCS_UNLOCK, SEQ_TEST_UNLOCK: begin if (seq_step == 3'd2) begin seq_active <= 1'b0; seq_id <= SEQ_IDLE; end else begin seq_step <= seq_step + 1'b1; end end default: begin seq_active <= 1'b0; seq_id <= SEQ_IDLE; end endcase end // STATUS 的 done/error 位采用写 1 清除。 if (bram_wr_valid && (bram_addr == BRAM_ADDR_STATUS)) begin done_latched <= done_latched & (~bram_din[0]); error_busy <= error_busy & (~bram_din[1]); end if (bram_wr_valid) begin case (bram_addr) // Direct SPI 配置寄存器。写这些寄存器不会立刻发 SPI, // 只有写 BRAM_ADDR_SPI_CTRL 才会启动传输。 BRAM_ADDR_SPI_ADDR: direct_addr <= bram_din[REG_ADDR_WIDTH-1:0]; BRAM_ADDR_SPI_WDATA: direct_wdata <= bram_din; // 直接引脚控制,和 SPI trigger 命令是两条独立路径。 BRAM_ADDR_PIN_CTRL: begin asic_rst_n <= bram_din[0]; asic_trig <= bram_din[1]; end // 高层 CONTROL 写:先保存低 8 bit 到本地 shadow, // 再发 SPI 写入 ASIC 的 CSI_REG_CONTROL。 BRAM_ADDR_CONTROL: begin control_shadow <= bram_din[7:0]; if (!ctrl_busy) begin seq_id <= SEQ_WRITE_CONTROL; seq_step <= 0; seq_active <= 1'b1; end else begin error_busy <= 1'b1; end end // 通道使能被拆成 ASIC 寄存器 0x0007 和 0x0008, // 所以这里启动一个两步序列。 BRAM_ADDR_CH_ENABLE: begin ch_enable_shadow <= bram_din[15:0]; if (!ctrl_busy) begin seq_id <= SEQ_CH_ENABLE; seq_step <= 0; seq_active <= 1'b1; end else begin error_busy <= 1'b1; end end // SPI trigger 命令:向 CSI_REG_STIM_TRIGGER 写 0xAA。 BRAM_ADDR_TRIGGER: begin if (!ctrl_busy) begin seq_id <= SEQ_TRIGGER; seq_step <= 0; seq_active <= 1'b1; end else begin error_busy <= 1'b1; end end // 特殊脚本由低 bit 选择: // bit0 DCS 解锁,bit1 test 解锁,bit2 退出模式, // bit3/bit4 快捷写控制寄存器。 BRAM_ADDR_SEQ_CMD: begin if (!ctrl_busy) begin seq_step <= 0; seq_active <= 1'b1; if (bram_din[0]) seq_id <= SEQ_DCS_UNLOCK; else if (bram_din[1]) seq_id <= SEQ_TEST_UNLOCK; else if (bram_din[2]) seq_id <= SEQ_MODE_EXIT; else if (bram_din[3]) begin seq_id <= SEQ_WRITE_CONTROL; control_shadow <= 8'h10; end else if (bram_din[4]) begin seq_id <= SEQ_WRITE_CONTROL; control_shadow <= 8'h08; end else begin seq_active <= 1'b0; end end else begin error_busy <= 1'b1; end end // 选择后续通道参数写使用的通道号。这里只更新本地寄存器。 BRAM_ADDR_CH_INDEX: ch_index <= bram_din[3:0]; // 任意通道参数写都会捕获低 8 bit,并把 BRAM 地址映射成 // CSI040 通道窗口内的 offset。 BRAM_ADDR_CH_MISC, BRAM_ADDR_CH_CUR_A, BRAM_ADDR_CH_CUR_B, BRAM_ADDR_CH_CUR_C, BRAM_ADDR_CH_CUR_D, BRAM_ADDR_CH_STIM_PW, BRAM_ADDR_CH_DZ0, BRAM_ADDR_CH_DZ1, BRAM_ADDR_CH_SKIP_AB, BRAM_ADDR_CH_SKIP_CD, BRAM_ADDR_CH_STIM_NUM, BRAM_ADDR_CH_DZ2, BRAM_ADDR_CH_THERAPY_DELAY, BRAM_ADDR_CH_THERAPY_NUM, BRAM_ADDR_CH_DZ3, BRAM_ADDR_CH_MASTER_NUM: begin ch_data <= bram_din[7:0]; case (bram_addr) BRAM_ADDR_CH_MISC: ch_offset <= CH_OFF_MISC; BRAM_ADDR_CH_CUR_A: ch_offset <= CH_OFF_CUR_A; BRAM_ADDR_CH_CUR_B: ch_offset <= CH_OFF_CUR_B; BRAM_ADDR_CH_CUR_C: ch_offset <= CH_OFF_CUR_C; BRAM_ADDR_CH_CUR_D: ch_offset <= CH_OFF_CUR_D; BRAM_ADDR_CH_STIM_PW: ch_offset <= CH_OFF_STIM_PW; BRAM_ADDR_CH_DZ0: ch_offset <= CH_OFF_DZ0; BRAM_ADDR_CH_DZ1: ch_offset <= CH_OFF_DZ1; BRAM_ADDR_CH_SKIP_AB: ch_offset <= CH_OFF_SKIP_AB; BRAM_ADDR_CH_SKIP_CD: ch_offset <= CH_OFF_SKIP_CD; BRAM_ADDR_CH_STIM_NUM: ch_offset <= CH_OFF_STIM_NUM; BRAM_ADDR_CH_DZ2: ch_offset <= CH_OFF_DZ2; BRAM_ADDR_CH_THERAPY_DELAY: ch_offset <= CH_OFF_THERAPY_DELAY; BRAM_ADDR_CH_THERAPY_NUM: ch_offset <= CH_OFF_THERAPY_NUM; BRAM_ADDR_CH_DZ3: ch_offset <= CH_OFF_DZ3; default: ch_offset <= CH_OFF_MASTER_NUM; endcase if (!ctrl_busy) begin seq_id <= SEQ_CH_WRITE; seq_step <= 0; seq_active <= 1'b1; end else begin error_busy <= 1'b1; end end // Direct SPI 命令启动寄存器。bit0 选择写/读, // bits[15:8] 选择字节长度;0 表示 1 字节。 BRAM_ADDR_SPI_CTRL: begin if (!ctrl_busy) begin direct_write <= bram_din[0]; direct_len <= (bram_din[15:8] == 0) ? 8'd1 : bram_din[15:8]; seq_id <= SEQ_DIRECT; seq_step <= 0; seq_active <= 1'b1; end else begin error_busy <= 1'b1; end end endcase end end end //------------------------------------------------------------------------- // SPI 命令生成器。 // // 当高层序列有效且 SPI master 空闲时,根据当前 seq_id/seq_step // 生成一拍 cmd_valid,并给出本次 SPI 需要的地址、数据和长度。 //------------------------------------------------------------------------- always @(posedge clk or negedge rst_n) begin if (!rst_n) begin spi_cmd_valid <= 1'b0; spi_cmd_write <= 1'b0; spi_cmd_addr <= 0; spi_cmd_wdata <= 0; spi_cmd_len <= 8'd1; end else begin spi_cmd_valid <= 1'b0; if (seq_active && !spi_master_done && spi_cmd_ready && !spi_cmd_valid) begin case (seq_id) // 向 ASIC 控制寄存器写 1 字节。 SEQ_WRITE_CONTROL: begin spi_cmd_valid <= 1'b1; spi_cmd_write <= 1'b1; spi_cmd_addr <= CSI_REG_CONTROL; spi_cmd_wdata <= {{(8*MAX_BURST_BYTES-8){1'b0}}, control_shadow}; spi_cmd_len <= 8'd1; end // 两个 1 字节写:先写通道 0-7,再写通道 8-15。 SEQ_CH_ENABLE: begin spi_cmd_valid <= 1'b1; spi_cmd_write <= 1'b1; spi_cmd_addr <= (seq_step == 0) ? CSI_REG_CH_ENABLE1 : CSI_REG_CH_ENABLE2; spi_cmd_wdata <= (seq_step == 0) ? {{(8*MAX_BURST_BYTES-8){1'b0}}, ch_enable_shadow[7:0]} : {{(8*MAX_BURST_BYTES-8){1'b0}}, ch_enable_shadow[15:8]}; spi_cmd_len <= 8'd1; end // 通过 ASIC trigger register 启动刺激。 SEQ_TRIGGER: begin spi_cmd_valid <= 1'b1; spi_cmd_write <= 1'b1; spi_cmd_addr <= CSI_REG_STIM_TRIGGER; spi_cmd_wdata <= {{(8*MAX_BURST_BYTES-8){1'b0}}, 8'hAA}; spi_cmd_len <= 8'd1; end // 特殊 key 序列都写同一个 ASIC key 寄存器; // seq_key_data 根据当前 step 选择写入字节。 SEQ_DCS_UNLOCK, SEQ_TEST_UNLOCK, SEQ_MODE_EXIT: begin spi_cmd_valid <= 1'b1; spi_cmd_write <= 1'b1; spi_cmd_addr <= CSI_REG_TEST_DCS_KEY; spi_cmd_wdata <= {{(8*MAX_BURST_BYTES-8){1'b0}}, seq_key_data(seq_id, seq_step)}; spi_cmd_len <= 8'd1; end // 写一个选中通道的参数字节。 SEQ_CH_WRITE: begin spi_cmd_valid <= 1'b1; spi_cmd_write <= 1'b1; spi_cmd_addr <= ch_addr(ch_index, ch_offset); spi_cmd_wdata <= {{(8*MAX_BURST_BYTES-8){1'b0}}, ch_data}; spi_cmd_len <= 8'd1; end // 软件直接控制的原始 SPI 访问。 SEQ_DIRECT: begin spi_cmd_valid <= 1'b1; spi_cmd_write <= direct_write; spi_cmd_addr <= direct_addr; spi_cmd_wdata <= direct_wdata; spi_cmd_len <= direct_len; end endcase end end end //------------------------------------------------------------------------- // BRAM 读数据选择。只有有效读周期才更新 bram_dout。 //------------------------------------------------------------------------- always @(posedge clk or negedge rst_n) begin if (!rst_n) begin bram_dout <= 0; end else if (bram_rd_valid) begin case (bram_addr) BRAM_ADDR_VERSION: bram_dout <= VERSION; BRAM_ADDR_STATUS: bram_dout <= {16'd0, seq_id, seq_step, error_busy, done_latched, spi_cmd_ready, ctrl_busy}; BRAM_ADDR_SPI_ADDR: bram_dout <= {{(32-REG_ADDR_WIDTH){1'b0}}, direct_addr}; BRAM_ADDR_SPI_WDATA: bram_dout <= direct_wdata; BRAM_ADDR_SPI_RDATA: bram_dout <= last_rdata; BRAM_ADDR_PIN_CTRL: bram_dout <= {30'd0, asic_trig, asic_rst_n}; BRAM_ADDR_LAST_ADDR: bram_dout <= {{(32-REG_ADDR_WIDTH){1'b0}}, last_addr}; BRAM_ADDR_CONTROL: bram_dout <= {24'd0, control_shadow}; BRAM_ADDR_CH_ENABLE: bram_dout <= {16'd0, ch_enable_shadow}; BRAM_ADDR_CH_INDEX: bram_dout <= {28'd0, ch_index}; default: bram_dout <= 0; endcase end end endmodule