19 KiB
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]清除。
推荐软件流程:
- 轮询
busy == 0或STATUS[5] == 0。 - 写命令寄存器。
- 等待
done_latched == 1或等待固定时间。 - 如有读操作,读取
SPI_RDATA。 - 写
STATUS[0] = 1清除 done 标志。
6.2 写控制寄存器
写 BRAM_ADDR_CONTROL:
control_shadow <= bram_din[7:0]- 启动
SEQ_WRITE_CONTROL - 产生 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:
ch_enable_shadow <= bram_din[15:0]- 启动
SEQ_CH_ENABLE - 分两步写 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 寄存器。
操作流程:
- 写
SPI_ADDR,设置 ASIC 地址。 - 若是写操作,写
SPI_WDATA。 - 写
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时序。