Files
BSI-901-fpga-software/docs/csi040_ctrl_design.md
T
2026-09-11 16:18:36 +08:00

615 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | 默认写 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 中访问判定为:
```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,单位 byte0 会被转换为 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` 时序。