3184 字
16 分钟
【AI】从有包到每包:DCA8000 采集卡 HSPI 调试记录

这几天一直在折腾一块自制 DCA8000 风格采集卡。目标很简单,甚至简单到看起来不该这么麻烦:

FPGA / 雷达 LVDS -> HSPI16 -> CH569W -> USB3.0 -> host capture

但硬件调试就是这样。你以为是在打通一条线,实际上是在确认每一层到底有没有说谎。

这篇记录一下这轮 HSPI bring-up 的过程。重点不是“已经 1Gbps 了”,还没有。重点是从 有包每包都对,中间其实过了很多坑。

DCA8000 HSPI bring-up debug map

相关仓库:mmWaveLab/mmwave-dca8000-capture-card

硬件约束#

先把最容易误导人的东西写前面。

我的板子 HSPI 数据线只有 16 根,也就是:

HD[15:0]

所以所有 packet words 都是 16-bit word。不是 32-bit 总线,不要从别的参考工程里直接搬 32-bit 配置。这个坑不写清楚,后面一定会有人,包括我自己,越调越偏。

还有一个现实问题:CH569W 进 BOOT/ISP 要手动按。不要搞什么“自动免按键 boot 脚本”。之前试过类似方向,最后结论很简单:假的,删掉。能用 USB runtime 命令和 Gowin SRAM 配置解决的,就不要反复刷 MCU。

第一阶段:有活动,但没收包#

最开始并不是完全没反应。

  • CH569W USB3 app 能枚举。
  • FPGA 能用 Gowin Programmer 下 SRAM。
  • HSPI 控制线能看到活动。
  • 但是 hspi_rx_packets 还是不涨。

这时候很容易开始怀疑一切:CRC、UDF 长度、HTACK 极性、HRVLD 边沿、HRACT 宽度、CH569W 配置位,甚至怀疑硬件连线。

也确实试了很多。

方向结果
runtime HSPI cfg sweep能排除一些配置,但没有形成稳定闭环
UDF length / EOP / CRC 组合证明不是单纯 CRC 猜错
wire-test one-hot pattern说明 CH 侧能看到部分线,但不是 HSPI 外设收包证明
HRVLD delay / prefix / reset pulse有变化,但没有直接闭环
slot IAP / app 内更新太绕,而且 BOOT 依赖更烦,后面删了

这里的结论是:GPIO 活动不等于 HSPI 收包。 这句话很普通,但当时很值钱。

第二阶段:终于有包了#

后面 hspi_dma_bridge 固件修过 re-arm 路径后,状态终于变好看了。

一次 10 包 run 里,CH569W 报:

firmware=0x00010e73 profile=hspi_dma_bridge
hspi_rx_packets=750
hspi_rx_errors=0
hspi_rx_bytes=47250

这个时候很容易激动。毕竟从 0750,看起来就是活了。

但见证奇迹:host 端还是坏的。

同一轮 capture 里,FPGA parser 报:

frames=700 gaps=1
fpga_packets=450
fpga_gaps=449
fpga_bad_packets=300
fpga_bad_words=1602

也就是说,CH569W 外设确实收到了东西,但 host 看到的 FPGA 帧并不对。

所以后面我把判据改掉了。不能再用 hspi_rx_packets > 0 当成功。

真正的 gate 变成:

host_capture_valid_fpga_frame=1
fpga_gaps=0
fpga_bad_packets=0
fpga_bad_words=0
hspi_rx_errors=0

这个 gate 比较残酷,但它至少不骗人。

第三阶段:每包少 1 字节#

继续看 D8FR USB 帧,发现一个很小的细节:

payload_length=63

但我配置的 HSPI payload 应该是:

64 bytes

少 1 字节。很小。也很烦。

这会造成 byte phase drift。第一包少一个字节,后面每包都可能错位。然后你看到的就是一堆看起来像 CRC、像时序、像数据线的问题。

最后定位到 CH569W 的 R16_HSPI_RX_LENx。它不是直接返回真实长度,而是 length-minus-one。

修复也很朴素:

if (size == 0) {
size = DCA8000_HSPI_HW_MAX_PAYLOAD_SIZE;
} else {
size++;
}

固件版本从 0x00010e7x 抬到 0x00010e8x。刷进去后,板子报:

firmware=0x00010e83
profile=hspi_dma_bridge
usb_link_speed=5000000000

到这里,问题从“有没有包”变成了“每包是不是完整”。

From packet seen to every packet valid

第四阶段:10 包、100 包过了#

RX_LEN 修复后,重新跑 10 包和 100 包。

这次终于不再是“看起来有包”。

结果是:

host_capture_valid_fpga_frame=1
payload_length=64
hspi_rx_errors=0
fpga_gaps=0
fpga_bad_packets=0
fpga_bad_words=0

10 包过,100 包也过。

这一步很关键。它说明前面那个 63/64 byte 的问题已经闭合。也就是说,USB D8FR 长度、CH569W re-arm、host parser、FPGA pattern,在低速条件下可以形成一个完整闭环。

这个时候可以说:低速小包链路活了。

但还不能说:1Gbps 活了。

第五阶段:开始推频,然后又坏了#

接下来开始往 1MHz HRCLK 推。

这里又出现了一个很有意思的现象:CH569W 仍然经常报 hspi_rx_errors=0,USB frame 也是 64 bytes,但 FPGA payload 开始坏。

也就是说,问题又换了一个形态:

USB 长度没错
CH569W 没报 HSPI error
但是 FPGA pattern word 错

这比 63/64 byte 麻烦。因为它不像一个简单 firmware off-by-one,更像采样窗口、IO 时序或者板级信号完整性。

目前比较可靠的边界是:

HRCLK状态
低速 10/100 包通过
约 588 kHz可以干净通过
595 kHz / 610 kHz / 625 kHz开始出现 FPGA pattern word corruption
1 MHz仍然坏

这时候再去说 62.5MHz、1Gbps,就有点自欺欺人。先不急。

第六阶段:1MHz 做了很多排除实验#

为了确认 1MHz 到底是不是某个简单参数问题,又做了一堆实验。

RCK_MOD#

试了 CH569W runtime variant 11,也就是动 RB_HSPI_RCK_MOD

结果更差,甚至没有 host frames。

所以先不管它。

单包 burst#

怀疑是不是 CH569W 来不及服务 burst,于是让 FPGA 每次只发一个包。

结果还是 payload corruption。

所以这不是单纯 CH burst-service pressure。

非连续 HRCLK#

怀疑 continuous HRCLK 有问题,试了非连续 HRCLK。

结果更差,CH569W 收不到有效 HSPI packet。

所以当前主线继续保留 continuous HRCLK。

降低 FPGA 输出驱动#

把 HSPI 输出从 DRIVE=8 降到 DRIVE=4

没改善。

不管。

PLL 125MHz TX/read clock#

加了 GW5A PLLA,把 50MHz 做到 125MHz,想让 HSPI TX/read clock 更细。

构建能过,时序也能过。但 1MHz 还是坏。

所以 PLL 路径保留为实验开关,默认关。

HRCLK output delay#

只延迟外部 HRCLK,不动内部 word update。

8 cycles 不行,20 cycles 更差。

HD/HRVLD/HRACT output delay#

反过来,延迟数据和控制,不延迟 HRCLK。

8 cycles 不行,20 cycles 更差,还引入了 HSPI error。

ODDR IOLOGIC#

把 HD[15:0]、HRCLK、HRVLD、HRACT 走 Gowin IOLOGIC ODDR。

这个实验有价值,因为综合结果里真的出现了:

ODDR=19

低速 10 包也能过。说明路径不是假的。

但是 1MHz 还是坏。

direct-pattern bypass#

怀疑上游 FIFO/test-source crossing,于是加了 direct-pattern,让 FPGA 在 HSPI TX clock domain 里直接生成 pattern。

低速过。

1MHz 还是坏。

这个排除很重要。说明主要问题不在上游 FIFO。

training pattern#

原来的 pattern 是 index ^ sequence,有些错误不够显眼。于是加了更高翻转率的 training pattern。

低速还是过。

1MHz 还是坏,而且坏得更像 word/window slip,不像随机 bit-lane noise。

tri-state HD[15:0]#

后来又根据手册和 WCH EVT 思路,把 FPGA 顶层 ch_hspi_d 改成 inout,在 HRVLD 不有效时释放总线。

这个改动是应该做的,因为 CH569W 手册里的 HTOE 时序本来就暗示不能一直硬推总线。

低速能过。

1MHz 没救。

但这不是白做。至少电气契约更像手册了,后面做 ACK 模式不至于先打架。

离线分析:它不像随机坏#

后面加了一个离线分析工具:

Terminal window
PYTHONPATH=src .venv/bin/python tools/analyze_hspi_pattern_capture.py \
test-results/.../capture.bin

这个工具会把 USB D8FR 里的 FPGA pattern words 拆出来,看坏字的位置、XOR、bit error、magic 间距、邻近 word 替换。

结果很有意思:很多坏 word 不是随机翻几个 bit,而是像拿到了附近 packet 的 header、next-packet role、neighbor word。

这就更像:

packet-window / sampling-window slip

不是:

USB framing 错
RX_LEN 错
CRC 猜错
FIFO crossing 错
随机某根数据线坏

当然,硬件调试里“更像”不等于“已经证明”。但至少下一步不该继续盲扫参数了。

第七阶段:大包高速,又换了一种坏法#

后面目标切回 1Gbps 主线。这里不再盯 1MHz 小包,而是直接做更接近最终目标的大包:

HRCLK=62.5MHz
payload=4064 bytes
HSPI=16-bit
runtime variant=8
USB3 SuperSpeed=5Gbps

这一步很容易误判。

CH569W 侧的 good-latch 能看到一个很漂亮的 FPGA frame:

5a00 cafe dca8 0001 07e9 ...

host 端也真的抓到过至少一个有效 FPGA frame:

host_capture_valid_fpga_frame=1

这说明一件事:USB3 不是完全不能承载正确 frame,FPGA 也不是完全发不出正确 frame。

但同一轮后面继续看 hspi-usb-submit-after,又出现了坏窗口:

8000 00c0 ...

这就把问题从“FPGA 一定发坏了”推进到一个更窄的位置:

CH569W HSPI DMA buffer
-> pending stage
-> frame fill
-> USB3 EP1 submit

坏大概率就在这条边界上。

CH569W USB3 queue diagnostic path

这也是这轮最大的收获:不要看到高速坏了就继续扫 CRC、扫边沿、扫 PLL。证据已经说得很清楚,至少先要把 CH569W 侧 buffer ownership 查干净。

第八阶段:整理工程,减少自己骗自己#

调到这里,仓库也开始变得危险。旧 wrapper、旧固件、旧测试入口太多了。

于是先做了一次整理:

User/
-> app/
-> board/
-> common/
-> hspi/
-> protocol/
-> usb/

这不是为了好看。主要是为了让下一轮不会又打开一个旧脚本,把已经排除过的问题重跑一遍。

旧的一次性 flash wrapper 删掉,只保留当前主线入口。test-results/ 继续不进 git,关键证据写进 ledger。这个项目已经不能靠“文件很多所以看起来很努力”推进了,必须靠更短的入口和更硬的证据推进。

第九阶段:0x1104,先给 USB3 一个 pending 位#

0x1103 的 USB-submit 诊断证明了:CH 侧能看到好 frame,但最后提交到 USB3 的窗口可能变坏。

所以 0x1104 没有继续改 FPGA,而是改 CH569W:

firmware=0x0001104
change=EP1 忙时不立刻 drop,保留一个 pending USB3 HSPI frame
usb3_hspi_stream_buffers=2
dma_rx_buffer_size=4096B
RAMX=90.10%

这个不是最终架构。最终要上 1Gbps,肯定不能靠 1-deep pending slot 过日子。

但它是一个很好的诊断:如果 busy_drops 明显下降、hspi-usb-submit-after 重新变成 5a00 cafe dca8 ...,说明前面的判断是对的,下一步就该做真正的 ring / zero-copy。

如果还是坏,那就继续往 HSPI DMA completed-buffer、copy window 或 frame fill 输入查。

当前固定刷写入口已经换成:

Terminal window
CH56X_MANUAL_BOOT_CONFIRMED=1 tools/flash_hspi_dma_bridge_usb_queue_0x1104.sh

bin 也固定在:

dist/reference-firmware/dca8000_reference-hspi_dma_bridge-usb-queue-0x1104/

这里还是要手动 BOOT。之前 app-mode IAP 试过,后来删了。它看起来方便,但实际上会把问题变得更不可信。现在宁愿麻烦一点,也不要假的自动化。

现在的状态#

目前我会这样描述这个项目:

USB3 app mode:通过
FPGA SRAM program:通过
HSPI re-arm:通过
RX_LEN 63/64 byte 问题:已闭合
低速 10 包 host-valid:通过
低速 100 包 host-valid:通过
1MHz 小包:失败,payload word corruption
62.5MHz / 4064B 大包:出现过有效 FPGA frame,但连续大包仍失败
0x1103 USB-submit 诊断:证明坏点靠近 CH569W DMA/pending/USB3 submit 边界
0x1104 USB queue 诊断:已构建并入库,待手动 BOOT 后刷写测试
USB2 dual CDC:实现了,还不是主采集路径

这已经比“有包了”强很多。现在不是“有没有包”的问题,也不只是“1MHz 小包坏”的问题,而是:

低速闭环已经成立;
高速大包能偶尔拿到对的 frame;
连续传输时,CH569W 侧 buffer/USB3 提交边界还不稳。

这比一开始清楚多了。

收获#

第一,有包不是成功。每包都对才是成功。

hspi_rx_packets=750 很漂亮,但 fpga_bad_words=1602 也很漂亮。两个一起出现的时候,前者不能安慰人。

第二,差 1 字节的问题最会装成时序问题。

63/64 bytes 这个 bug,如果不看 USB frame,只扫 CRC 和边沿,可以扫到天荒地老。

第三,能删的旧脚本就删。

v41/v43/v44、runtime sweep、raw snapshot、wire-test audit 这些都曾经有用。但现在继续摆在入口处,只会让人下一轮又点进去盲扫。

第四,保留 USB2,但别让它抢主线。

USB2 dual CDC 适合做雷达 CLI/logger/control。主采集还是 USB3 bulk。这个分工清楚以后,架构就不拧巴了。

第五,AI 适合做状态压缩,不适合替代示波器。

这次 AI 帮忙最大的地方,是把很多试验结果压成“下一步应该验证什么”。但真正决定方向的,还是那些很笨的证据:payload_length=64fpga_bad_words=0hspi_rx_errors=0、波形、capture。

第六,工程结构本身也是调试工具。

把 WCH 固件从 User/ 拆出来,把旧 wrapper 删掉,把当前 bin 固定到 0x1104,这些看起来不像“硬件进展”,但它会直接减少下一次误操作。硬件调试里,少走一次旧路就是进展。

下一步#

当前最短路线:

Terminal window
tools/check_ch56x_flash_target.sh \
dist/reference-firmware/dca8000_reference-hspi_dma_bridge-usb-queue-0x1104/mmwave-dca8000-hspi_dma_bridge-usb-queue-0x1104.bin

确认目标 bin 是 0x0001104,然后我手动进 BOOT/ISP,再刷:

Terminal window
CH56X_MANUAL_BOOT_CONFIRMED=1 tools/flash_hspi_dma_bridge_usb_queue_0x1104.sh

刷完之后复跑 0x1103 那一套大包对照:

62.5MHz HRCLK
4064B payload
hspi-usb-submit-before
hspi-usb-submit-after
good-latch
host capture.bin

如果 hspi-usb-submit-after8000 00c0 ... 变回 5a00 cafe dca8 ...,就继续做 USB3/HSPI ring。

如果还是坏,就继续查 CH569W completed-buffer 选择、copy window 和 frame fill 输入。

这就是现在的状态。不是结束,但已经从“有没有包”推进到了“低速闭环、高速大包有正确样本、连续流卡在 CH569W buffer/USB3 边界”。

【AI】从有包到每包:DCA8000 采集卡 HSPI 调试记录
https://fuwari.vercel.app/posts/dca8000-ch569w-hspi-bringup/
作者
KJ
发布于
2026-06-16
许可协议
CC BY-NC-SA 4.0