Files

19 KiB
Raw Permalink Blame History

CSI040 控制模块详细设计文档

1. 文档范围

本文档描述 CSI040 ASIC 控制模块的 RTL 设计,覆盖以下文件:

  • c64_pro.srcs/sources_1/rtl/csi040/csi040_ctrl.v
  • c64_pro.srcs/sources_1/rtl/csi040/csi040_spi_master.v
  • c64_pro.srcs/sources_1/rtl/csi040/csi040_ctrl_tb.v

该模块在 FPGA/PL 侧提供一组 BRAM 风格的控制寄存器。上位逻辑或 PS 通过写这些寄存器来触发 CSI040 ASIC 的 SPI 读写、通道参数配置、模式解锁、触发控制等操作。

2. 总体结构

PS / 软件
   |
   | BRAM 风格寄存器访问
   v
+------------------+
| csi040_ctrl      |
|                  |
| 软件寄存器映射   |
| shadow 寄存器    |
| seq 序列控制器   |
| 状态/错误锁存    |
+--------+---------+
         |
         | cmd_valid / cmd_ready / cmd_addr / cmd_wdata / cmd_len
         v
+---------------------+
| csi040_spi_master   |
|                     |
| SPI 组帧            |
| SCK/CSN/SI/SO 时序  |
+----------+----------+
           |
           | asic_csn / asic_sck / asic_si / asic_so
           v
       CSI040 ASIC

csi040_ctrl 是上层可见的控制模块,负责接收 BRAM 写操作、保存软件配置、发起高层命令序列,并把命令转换为 SPI master 的抽象命令接口。

csi040_spi_master 是底层 SPI 引擎,负责把 opcode、ASIC 地址和数据字节按 SPI 时序移出,并在读操作时采样 asic_so

3. 模块参数

3.1 csi040_ctrl

参数 默认值 说明
VERSION 32'h04040001 模块版本号,读 BRAM 地址 0x0000 返回。
SPI_CLK_DIV 50 SPI SCK 半周期分频系数,f_sck = f_clk / (2 * SPI_CLK_DIV)
REG_ADDR_WIDTH 16 ASIC 内部寄存器地址宽度。
SPI_ADDR_BYTES 2 SPI 帧中的地址字节数。
MAX_BURST_BYTES 4 单次 SPI 最大数据字节数。
SPI_OPCODE_WRITE 8'h00 默认写 opcodetestbench 中重载为 8'h02
SPI_OPCODE_READ 8'h80 默认读 opcodetestbench 中重载为 8'h03
CSI_REG_* 多个地址 CSI040 ASIC 内部寄存器地址参数。

3.2 csi040_spi_master

参数 默认值 说明
CLK_DIV 50 SCK 半周期对应的 clk 周期数。
REG_ADDR_WIDTH 16 cmd_addr 地址宽度。
ADDR_BYTES 2 SPI 地址字段字节数。
MAX_BURST_BYTES 4 最大 payload 字节数。
OPCODE_WRITE 8'h00 SPI 写命令 opcode。
OPCODE_READ 8'h80 SPI 读命令 opcode。

4. 顶层接口

4.1 时钟复位

信号 方向 说明
clk input 主时钟,当前所有寄存器逻辑都使用该时钟。
rst_n input 异步低有效复位。
bram_clk input 接口保留信号,当前 RTL 未使用。
bram_rst input 接口保留信号,当前 RTL 未使用。

注意:虽然接口里有 bram_clk,但当前实现没有使用它。因此 BRAM 控制访问应与 clk 同步。如果后续接真实 AXI BRAM Controller 且时钟不同,需要补 CDC 或改成 bram_clk 域逻辑。

4.2 BRAM 风格控制口

信号 方向 位宽 说明
bram_addr input 13 BRAM 字节地址,当前使用 4 字节对齐地址。
bram_din input 32 写数据。
bram_dout output reg 32 读数据,在读访问时更新。
bram_en input 1 BRAM 访问使能。
bram_we input 4 写使能。当前仅识别 4'hF 为写、4'h0 为读。

RTL 中访问判定为:

assign bram_wr_valid = bram_en && (bram_we == 4'hF);
assign bram_rd_valid = bram_en && (bram_we == 4'h0);

因此当前模块不支持 byte enable 局部写。如果软件侧产生 bram_we = 4'h1/4'h3 之类的写操作,该写不会被模块识别。

4.3 状态输出

信号 方向 说明
busy output 模块忙标志。只要高层序列、SPI master 或待发 SPI 命令任一处于忙状态,该信号为高。
spi_done output SPI master 完成脉冲,持续 1 个 clk 周期。
error_busy output reg 忙时启动新命令的粘滞错误标志,需要软件写 STATUS[1] 清除。

4.4 ASIC 侧引脚

信号 方向 说明
asic_rst_n output reg ASIC 复位控制,软件通过 PIN_CTRL[0] 控制,复位默认值为 1。
asic_trig output reg ASIC 外部触发引脚,软件通过 PIN_CTRL[1] 控制,复位默认值为 0。
asic_csn output SPI 片选,低有效。
asic_sck output SPI 时钟,空闲为低。
asic_si output SPI master 输出到 ASIC 的串行数据。
asic_so input ASIC 输出到 SPI master 的串行数据。

5. BRAM 寄存器映射

所有地址均为字节地址。

地址 名称 访问 说明
13'h0000 VERSION R 读取模块版本号 VERSION
13'h0004 STATUS R/W1C 读取模块状态;写 bit0 清 done_latched,写 bit1 清 error_busy
13'h0008 SPI_ADDR R/W Direct SPI 访问的 ASIC 地址。
13'h000C SPI_WDATA R/W Direct SPI 写数据。payload 第 1 字节来自 bram_din[7:0]
13'h0010 SPI_CTRL W 启动 Direct SPI 访问。bit0=writebits[15:8]=length
13'h0014 SPI_RDATA R 最近一次 SPI 读回数据。
13'h0018 PIN_CTRL R/W bit0=asic_rst_nbit1=asic_trig
13'h001C LAST_ADDR R 最近一次完成 SPI 操作的 ASIC 地址。
13'h0020 CONTROL R/W 更新 control_shadow,并写 ASIC 控制寄存器 0x0002
13'h0024 CH_ENABLE R/W 更新 ch_enable_shadow,并写 ASIC 通道使能寄存器 0x0007/0x0008
13'h0028 TRIGGER W 写 ASIC 触发寄存器 0x0110 = 8'hAA
13'h002C SEQ_CMD W 启动特殊序列:DCS unlock、Test unlock、mode exit、控制寄存器快捷写。
13'h0040 CH_INDEX R/W 选择后续通道参数写入的 channel index。
13'h0044 CH_MISC W 写当前 channel 的 offset 0x0
13'h0048 CH_CUR_A W 写当前 channel 的 offset 0x1
13'h004C CH_CUR_B W 写当前 channel 的 offset 0x2
13'h0050 CH_CUR_C W 写当前 channel 的 offset 0x3
13'h0054 CH_CUR_D W 写当前 channel 的 offset 0x4
13'h0058 CH_STIM_PW W 写当前 channel 的 offset 0x5
13'h005C CH_DZ0 W 写当前 channel 的 offset 0x6
13'h0060 CH_DZ1 W 写当前 channel 的 offset 0x7
13'h0064 CH_SKIP_AB W 写当前 channel 的 offset 0x8
13'h0068 CH_SKIP_CD W 写当前 channel 的 offset 0x9
13'h006C CH_STIM_NUM W 写当前 channel 的 offset 0xA
13'h0070 CH_DZ2 W 写当前 channel 的 offset 0xB
13'h0074 CH_THERAPY_DELAY W 写当前 channel 的 offset 0xC
13'h0078 CH_THERAPY_NUM W 写当前 channel 的 offset 0xD
13'h007C CH_DZ3 W 写当前 channel 的 offset 0xE
13'h0080 CH_MASTER_NUM W 写当前 channel 的 offset 0xF

5.1 STATUS 字段

RTL 返回表达式为:

bram_dout <= {16'd0, seq_id, seq_step, error_busy, done_latched, spi_cmd_ready, ctrl_busy};

该拼接宽度为 26 bit,赋值给 32 bit 时左侧补 0。因此软件侧实际可按低位理解:

名称 说明
[5] ctrl_busy 当前模块忙。
[6] spi_cmd_ready SPI master 是否空闲可接收新命令。
[7] done_latched 最近至少完成过一次 SPI 操作,写 STATUS[0]=1 清除。
[8] error_busy 忙时写命令产生的错误,写 STATUS[1]=1 清除。
[11:9] seq_step 当前高层序列步骤。
[15:12] seq_id 当前高层序列编号。
其他 0 保留。

5.2 状态清除

BRAM_ADDR_STATUS 时:

写入位 行为
bram_din[0] = 1 清除 done_latched
bram_din[1] = 1 清除 error_busy

6. 高层命令序列设计

模块内部用 seq_id + seq_step + seq_active 控制多拍或多 SPI 帧操作。

seq_id 名称 触发来源
4'h0 SEQ_IDLE 空闲。
4'h1 SEQ_WRITE_CONTROL CONTROL,或 SEQ_CMD[3]/[4]
4'h2 SEQ_CH_ENABLE CH_ENABLE
4'h3 SEQ_TRIGGER TRIGGER
4'h4 SEQ_DCS_UNLOCK SEQ_CMD[0]
4'h5 SEQ_TEST_UNLOCK SEQ_CMD[1]
4'h6 SEQ_MODE_EXIT SEQ_CMD[2]
4'h7 SEQ_CH_WRITE 写任意通道参数寄存器。
4'h8 SEQ_DIRECT SPI_CTRL

6.1 Busy 机制

模块忙信号定义:

ctrl_busy = seq_active || spi_busy || spi_cmd_valid;
busy      = ctrl_busy;

对所有会启动 SPI 的 BRAM 写操作,RTL 都先检查 !ctrl_busy。若忙时写入命令寄存器:

  • 不启动新命令;
  • error_busy <= 1'b1
  • 该错误保持到软件写 STATUS[1] 清除。

推荐软件流程:

  1. 轮询 busy == 0STATUS[5] == 0
  2. 写命令寄存器。
  3. 等待 done_latched == 1 或等待固定时间。
  4. 如有读操作,读取 SPI_RDATA
  5. STATUS[0] = 1 清除 done 标志。

6.2 写控制寄存器

BRAM_ADDR_CONTROL

  1. control_shadow <= bram_din[7:0]
  2. 启动 SEQ_WRITE_CONTROL
  3. 产生 1 个 SPI write
ASIC addr = CSI_REG_CONTROL = 0x0002
data      = control_shadow
length    = 1 byte

testbench 中写:

write_bram(13'h0020, 32'h00000010)

期望 SPI 帧:

0x02_0002_10

6.3 写通道使能

BRAM_ADDR_CH_ENABLE

  1. ch_enable_shadow <= bram_din[15:0]
  2. 启动 SEQ_CH_ENABLE
  3. 分两步写 ASIC
seq_step 0: addr 0x0007, data ch_enable_shadow[7:0]
seq_step 1: addr 0x0008, data ch_enable_shadow[15:8]

testbench 中写 0xA55A,期望两帧:

0x02_0007_5A
0x02_0008_A5

6.4 写触发命令

BRAM_ADDR_TRIGGER 启动 SEQ_TRIGGERSPI 写:

ASIC addr = CSI_REG_STIM_TRIGGER = 0x0110
data      = 8'hAA

注意:该 SPI trigger 与 asic_trig 引脚是两个不同路径。asic_trig 只受 PIN_CTRL[1] 控制。

6.5 特殊序列 SEQ_CMD

BRAM_ADDR_SEQ_CMD 时,RTL 按 bit 优先级解析:

bit 序列 实际 SPI 操作
0 DCS unlock 连续向 0x000955h, B3h, 0Ah
1 Test unlock 连续向 0x0009AAh, 4Ch, F5h
2 Mode exit 0x0009F9h
3 控制快捷写 control_shadow <= 8'h10,随后写 CONTROL
4 控制快捷写 control_shadow <= 8'h08,随后写 CONTROL

若多个 bit 同时为 1,优先级从 bit0 到 bit4。

6.6 通道参数写

通道地址由函数 ch_addr 计算:

ch_addr = CSI_CH_BASE + (ch * CSI_CH_STRIDE) + offset;

默认:

CSI_CH_BASE   = 0x0010
CSI_CH_STRIDE = 0x0010

所以:

ASIC channel register address = 0x0010 + ch_index * 0x0010 + ch_offset

示例:

CH_INDEX = 3
写 CH_CUR_A = 0x7F
offset = 0x1
ASIC addr = 0x0010 + 3 * 0x0010 + 0x1 = 0x0041
SPI frame = 0x02_0041_7F

当前所有通道参数写都只发送 bram_din[7:0]

6.7 Direct SPI 访问

Direct SPI 用于软件直接访问任意 ASIC 寄存器。

操作流程:

  1. SPI_ADDR,设置 ASIC 地址。
  2. 若是写操作,写 SPI_WDATA
  3. SPI_CTRL 启动 SPI
SPI_CTRL[0]    = direct_write, 1 为写,0 为读
SPI_CTRL[15:8] = direct_len,单位 byte0 会被转换为 1

direct_len 在 SPI master 内部被限制到 1..MAX_BURST_BYTES

读操作完成后:

  • last_rdata <= spi_cmd_rdata[31:0]
  • 软件读 SPI_RDATA 获取数据
  • 软件读 LAST_ADDR 获取最近访问地址

testbench 中直接读 Part ID

SPI_ADDR = 0x011D
SPI_CTRL = 0x00000100  // write=0, len=1
期望帧 = 0x03_011D_00
读回 = 0x28

7. SPI Master 设计

csi040_spi_master 接收抽象命令,并转换成 SPI bit 流。

7.1 命令接口

信号 方向 说明
cmd_ready output SPI master 空闲时为 1。
cmd_valid input 命令有效。空闲时采样。
cmd_write input 1 为写,0 为读。
cmd_addr input ASIC 寄存器地址。
cmd_wdata input 写 payload。低字节先作为 payload byte0。
cmd_len input payload 字节数。0 转 1,超过最大值则截断。
done output reg SPI 帧完成脉冲,持续 1 个 clk
cmd_rdata output reg 读回 payload。
busy output reg SPI master 忙。

7.2 SPI 帧格式

默认 ADDR_BYTES = 2,帧格式为:

byte0   opcode
byte1   address high byte
byte2   address low byte
byte3+  payload bytes

1 字节写示例:

opcode = 0x02
addr   = 0x0002
data   = 0x10
frame  = 0x02_0002_10

地址字节按 MSB first 输出。payload 来自:

cmd_wdata[8*n +: 8]

cmd_wdata[7:0] 是第一个 payload byte。

7.3 SPI 状态机

状态 行为
IDLE_ST cmd_ready=1csn=1sck=0。若 cmd_valid=1,锁存 frame 并进入传输。
TRANS_ST CLK_DIVclk 翻转一次 sck;上升沿采样读数据;下降沿准备下一位输出。
DONE_ST csn 拉高,done 脉冲 1 拍,busy 拉低,返回 idle。

SPI 时钟频率:

f_sck = f_clk / (2 * CLK_DIV)

testbench 中:

f_clk = 100 MHz
CLK_DIV = 2
f_sck = 25 MHz

该值用于加速仿真。实际硬件中应根据 CSI040 SPI 时序要求重新设置 SPI_CLK_DIV

7.4 读数据采样

SPI master 在 header 发送完之后开始采样 so

HEADER_BITS = 8 * (1 + ADDR_BYTES)

默认地址 2 字节时,header 为 24 bit。读数据从第 25 个 bit 开始采样,采样结果移入 rx_shift,帧结束时赋给 cmd_rdata

8. 复位行为

rst_n = 0 时,csi040_ctrl 主要寄存器复位如下:

寄存器 复位值
direct_addr 0
direct_wdata 0
direct_len 1
direct_write 1
control_shadow 0
ch_enable_shadow 0
ch_index 0
ch_data 0
ch_offset 0
seq_id SEQ_IDLE
seq_step 0
seq_active 0
error_busy 0
done_latched 0
last_rdata 0
last_addr 0
asic_rst_n 1
asic_trig 0

csi040_spi_master 复位后:

csn  = 1
sck  = 0
si   = 0
done = 0
busy = 0
state = IDLE_ST

9. 当前 Testbench 覆盖

csi040_ctrl_tb 实例化 DUT 时重载:

.SPI_CLK_DIV      ( 2 ),
.SPI_OPCODE_WRITE ( 8'h02 ),
.SPI_OPCODE_READ  ( 8'h03 )

testbench 使用 100 MHz clock,复位 8 个周期后开始测试。

覆盖场景:

测试项 BRAM 操作 期望 SPI 帧
控制寄存器写 write_bram(0x0020, 0x10) 0x02000210
通道使能低字节 write_bram(0x0024, 0xA55A) 0x0200075A
通道使能高字节 同上 0x020008A5
通道 3 Current A CH_INDEX=3CH_CUR_A=0x7F 0x0200417F
DCS unlock step0 SEQ_CMD=1 0x02000955
DCS unlock step1 同上 0x020009B3
DCS unlock step2 同上 0x0200090A
Direct read Part ID SPI_ADDR=0x011DSPI_CTRL=0x0100 0x03011D00
读回检查 ASIC 模型返回 0x28 SPI_RDATA[7:0] == 0x28

testbench 中 SPI monitor 的行为:

  • negedge asic_csn 清空 bit 计数和 shift 寄存器;
  • posedge asic_sck 采样 asic_si
  • posedge asic_csn 锁存完整帧;
  • 在读数据阶段从 asic_so 返回 read_response = 8'h28

10. 仿真与波形

已验证的命令:

& 'C:\Xilinx\Vivado\2023.1\bin\xvlog.bat' `
  c64_pro.srcs/sources_1/rtl/csi040/csi040_spi_master.v `
  c64_pro.srcs/sources_1/rtl/csi040/csi040_ctrl.v `
  c64_pro.srcs/sources_1/rtl/csi040/csi040_ctrl_tb.v

& 'C:\Xilinx\Vivado\2023.1\bin\xelab.bat' csi040_ctrl_tb -debug typical -snapshot csi040_ctrl_tb_sim_dbg

& 'C:\Xilinx\Vivado\2023.1\bin\xsim.bat' csi040_ctrl_tb_sim_dbg -runall

当前仿真结果:

$finish called at time : 10940 ns

未触发 testbench 中的 ERROR ... $stop

打开 GUI

& 'C:\Xilinx\Vivado\2023.1\bin\xsim.bat' csi040_ctrl_tb_sim_dbg -gui

GUI 中可用:

log_wave -r /csi040_ctrl_tb/*
run all

建议观察信号:

/csi040_ctrl_tb/bram_addr
/csi040_ctrl_tb/bram_din
/csi040_ctrl_tb/bram_dout
/csi040_ctrl_tb/bram_en
/csi040_ctrl_tb/bram_we
/csi040_ctrl_tb/busy
/csi040_ctrl_tb/spi_done
/csi040_ctrl_tb/error_busy
/csi040_ctrl_tb/asic_csn
/csi040_ctrl_tb/asic_sck
/csi040_ctrl_tb/asic_si
/csi040_ctrl_tb/asic_so
/csi040_ctrl_tb/frame_in
/csi040_ctrl_tb/frame_bits
/csi040_ctrl_tb/dut/seq_id
/csi040_ctrl_tb/dut/seq_step
/csi040_ctrl_tb/dut/seq_active
/csi040_ctrl_tb/dut/spi_cmd_valid
/csi040_ctrl_tb/dut/spi_cmd_ready
/csi040_ctrl_tb/dut/spi_cmd_addr
/csi040_ctrl_tb/dut/spi_cmd_wdata
/csi040_ctrl_tb/dut/spi_cmd_len
/csi040_ctrl_tb/dut/u_csi040_spi_master/cstate
/csi040_ctrl_tb/dut/u_csi040_spi_master/tx_shift
/csi040_ctrl_tb/dut/u_csi040_spi_master/rx_shift
/csi040_ctrl_tb/dut/u_csi040_spi_master/sampled_bits

11. 软件使用建议

典型写配置流程:

1. 轮询 STATUS[5] == 0
2. 写目标 BRAM 命令寄存器
3. 轮询 STATUS[7] == 1
4. 如是读命令,读取 SPI_RDATA
5. 写 STATUS[0] = 1 清除 done_latched
6. 如 STATUS[8] = 1,记录错误并写 STATUS[1] = 1 清除 error_busy

注意事项:

  • 忙时不要写命令寄存器,否则命令会被丢弃,并置位 error_busy
  • PIN_CTRL 是直接引脚控制,不会自动生成 SPI。
  • TRIGGER 是 SPI 写 ASIC trigger register,不等同于 asic_trig 引脚。
  • SPI_CTRL[15:8] 为直接 SPI payload 长度,最大有效值受 MAX_BURST_BYTES 限制。
  • 当前 BRAM 接口只支持全字写。

12. 后续可改进点

  • 明确 bram_clk/bram_rst 的设计意图:要么真正使用它们,要么在接口说明中标记为 unused。
  • 增加 byte enable 写支持,方便接 AXI BRAM Controller。
  • STATUS 位定义软件头文件常量,避免软件侧硬编码 bit 位置。
  • 扩展 testbench
    • 忙时写命令,检查 error_busy
    • PIN_CTRL 读写;
    • TRIGGER SPI 序列;
    • Test unlock 和 mode exit
    • Direct SPI 多字节 burst
    • cmd_len=0、超过 MAX_BURST_BYTES 的边界行为;
    • SPI read 多字节返回数据。
  • 增加 SystemVerilog assertion,用来约束命令重入、SPI 帧长度和 csn/sck 时序。