615 lines
19 KiB
Markdown
615 lines
19 KiB
Markdown
# 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. 总体结构
|
||
|
||
```text
|
||
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 中访问判定为:
|
||
|
||
```verilog
|
||
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 返回表达式为:
|
||
|
||
```verilog
|
||
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 机制
|
||
|
||
模块忙信号定义:
|
||
|
||
```verilog
|
||
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:
|
||
|
||
```text
|
||
ASIC addr = CSI_REG_CONTROL = 0x0002
|
||
data = control_shadow
|
||
length = 1 byte
|
||
```
|
||
|
||
testbench 中写:
|
||
|
||
```text
|
||
write_bram(13'h0020, 32'h00000010)
|
||
```
|
||
|
||
期望 SPI 帧:
|
||
|
||
```text
|
||
0x02_0002_10
|
||
```
|
||
|
||
### 6.3 写通道使能
|
||
|
||
写 `BRAM_ADDR_CH_ENABLE`:
|
||
|
||
1. `ch_enable_shadow <= bram_din[15:0]`
|
||
2. 启动 `SEQ_CH_ENABLE`
|
||
3. 分两步写 ASIC:
|
||
|
||
```text
|
||
seq_step 0: addr 0x0007, data ch_enable_shadow[7:0]
|
||
seq_step 1: addr 0x0008, data ch_enable_shadow[15:8]
|
||
```
|
||
|
||
testbench 中写 `0xA55A`,期望两帧:
|
||
|
||
```text
|
||
0x02_0007_5A
|
||
0x02_0008_A5
|
||
```
|
||
|
||
### 6.4 写触发命令
|
||
|
||
写 `BRAM_ADDR_TRIGGER` 启动 `SEQ_TRIGGER`,SPI 写:
|
||
|
||
```text
|
||
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` 计算:
|
||
|
||
```verilog
|
||
ch_addr = CSI_CH_BASE + (ch * CSI_CH_STRIDE) + offset;
|
||
```
|
||
|
||
默认:
|
||
|
||
```text
|
||
CSI_CH_BASE = 0x0010
|
||
CSI_CH_STRIDE = 0x0010
|
||
```
|
||
|
||
所以:
|
||
|
||
```text
|
||
ASIC channel register address = 0x0010 + ch_index * 0x0010 + ch_offset
|
||
```
|
||
|
||
示例:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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`,帧格式为:
|
||
|
||
```text
|
||
byte0 opcode
|
||
byte1 address high byte
|
||
byte2 address low byte
|
||
byte3+ payload bytes
|
||
```
|
||
|
||
1 字节写示例:
|
||
|
||
```text
|
||
opcode = 0x02
|
||
addr = 0x0002
|
||
data = 0x10
|
||
frame = 0x02_0002_10
|
||
```
|
||
|
||
地址字节按 MSB first 输出。payload 来自:
|
||
|
||
```verilog
|
||
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 时钟频率:
|
||
|
||
```text
|
||
f_sck = f_clk / (2 * CLK_DIV)
|
||
```
|
||
|
||
testbench 中:
|
||
|
||
```text
|
||
f_clk = 100 MHz
|
||
CLK_DIV = 2
|
||
f_sck = 25 MHz
|
||
```
|
||
|
||
该值用于加速仿真。实际硬件中应根据 CSI040 SPI 时序要求重新设置 `SPI_CLK_DIV`。
|
||
|
||
### 7.4 读数据采样
|
||
|
||
SPI master 在 header 发送完之后开始采样 `so`:
|
||
|
||
```text
|
||
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` 复位后:
|
||
|
||
```text
|
||
csn = 1
|
||
sck = 0
|
||
si = 0
|
||
done = 0
|
||
busy = 0
|
||
state = IDLE_ST
|
||
```
|
||
|
||
## 9. 当前 Testbench 覆盖
|
||
|
||
`csi040_ctrl_tb` 实例化 DUT 时重载:
|
||
|
||
```verilog
|
||
.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. 仿真与波形
|
||
|
||
已验证的命令:
|
||
|
||
```powershell
|
||
& '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
|
||
```
|
||
|
||
当前仿真结果:
|
||
|
||
```text
|
||
$finish called at time : 10940 ns
|
||
```
|
||
|
||
未触发 testbench 中的 `ERROR ... $stop`。
|
||
|
||
打开 GUI:
|
||
|
||
```powershell
|
||
& 'C:\Xilinx\Vivado\2023.1\bin\xsim.bat' csi040_ctrl_tb_sim_dbg -gui
|
||
```
|
||
|
||
GUI 中可用:
|
||
|
||
```tcl
|
||
log_wave -r /csi040_ctrl_tb/*
|
||
run all
|
||
```
|
||
|
||
建议观察信号:
|
||
|
||
```text
|
||
/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. 软件使用建议
|
||
|
||
典型写配置流程:
|
||
|
||
```text
|
||
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` 时序。
|