优化版 HTML:已添加侧边目录、章节锚点、响应式表格、打印样式和离线 CSS。
#CSI040 控制模块详细设计文档
#1. 文档范围
本文档描述 CSI040 ASIC 控制模块的 RTL 设计,覆盖以下文件:
c64_pro.srcs/sources_1/rtl/csi040/csi040_ctrl.vc64_pro.srcs/sources_1/rtl/csi040/csi040_spi_master.vc64_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 | 默认写 opcode;testbench 中重载为 8'h02。 |
SPI_OPCODE_READ | 8'h80 | 默认读 opcode;testbench 中重载为 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=write,bits[15:8]=length。 |
13'h0014 | SPI_RDATA | R | 最近一次 SPI 读回数据。 |
13'h0018 | PIN_CTRL | R/W | bit0=asic_rst_n,bit1=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 == 0 或 STATUS[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_TRIGGER,SPI 写:
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 | 连续向 0x0009 写 55h, B3h, 0Ah。 |
1 | Test unlock | 连续向 0x0009 写 AAh, 4Ch, F5h。 |
2 | Mode exit | 向 0x0009 写 F9h。 |
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,单位 byte;0 会被转换为 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=1,csn=1,sck=0。若 cmd_valid=1,锁存 frame 并进入传输。 |
TRANS_ST | 每 CLK_DIV 个 clk 翻转一次 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=3,CH_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=0x011D,SPI_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读写;TRIGGERSPI 序列;- Test unlock 和 mode exit;
- Direct SPI 多字节 burst;
cmd_len=0、超过MAX_BURST_BYTES的边界行为;- SPI read 多字节返回数据。
- 增加 SystemVerilog assertion,用来约束命令重入、SPI 帧长度和
csn/sck时序。