Update FPGA and Vivado skills
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# FPGA 远程升级协议(TCP 1234)
|
||||
|
||||
## 首先:这条路和网页升级毫无关系
|
||||
|
||||
设备网页 `sys_set.html` 的 **Remote Update** 框是给 **imx6 固件包**用的,不是给 FPGA 用的。
|
||||
把 `.mcs` 传上去等于扔进垃圾桶:
|
||||
|
||||
```
|
||||
upload.cgi → 存到 /aw/upgrade/ → 通知 app(WEB_UPGRADE)
|
||||
app: start_upgrade() → set_upgrade_flg(1) → imx_up_thr → check_package.sh
|
||||
check_package.sh: find /aw/upgrade -name '*.tar.gz' ← 只认 tar.gz
|
||||
找不到 → ret=3 → rm -rf /aw/upgrade/* ← 你的 mcs 被删了
|
||||
```
|
||||
|
||||
(`start_upgrade()` 还会在 `ctrl==CTRL_LOCAL` 时直接 return 0 —— 本地模式下网页升级本就不生效。)
|
||||
|
||||
FPGA 走的是**另一条独立的路**:app 里的 `fpga_upgrade_task()` 在 **TCP 1234** 上起了个 server
|
||||
(`inc/config.h: UPGRADE_LISTEN_PORT 1234`),跑私有帧协议。原厂用 `tools/Xfpga_ru.exe` 驱动它
|
||||
(一个 16 KB 的 .NET GUI,没有命令行)。`scripts/fpga_ru.py` 就是它的替代品。
|
||||
|
||||
服务端权威实现:`my_exciter/imx6/src/atsc_app/task/tcp_task.c` :: `fpga_upgrade_process()`。
|
||||
|
||||
## 帧格式
|
||||
|
||||
```
|
||||
请求 AA | cmd | datalen:u16 小端 | payload | sum | BB
|
||||
应答 AA | ack | 01 | 00 | ack | sum | BB (固定 7 字节)
|
||||
sum 从帧头到 sum 之前所有字节的 8-bit 累加和(get_check_sum,见 bsp/general_api.c)
|
||||
```
|
||||
|
||||
服务端校验 `rxdata[0]==0xAA && rxdata[datalen+5]==0xBB`,再比对 `get_check_sum(rxdata, datalen+4)`。
|
||||
应答里 ack 同时出现在 `[1]` 和 `[4]`;校验和错时 `[1]` 被改写,所以**读 `[4]`**。
|
||||
|
||||
| cmd | 值 | ack | 值 |
|
||||
|---|---|---|---|
|
||||
| `UPGRADE_START` | 0x01 | `START_OK` | 0x01 |
|
||||
| `POLL_ERASER` | 0x02 | `ERASER_ING` / `ERASER_END` | 0x02 / 0x03 |
|
||||
| `W_DATA_BLOCK` | 0x03 | `READY` / `NOT_READY` | 0x04 / 0x05 |
|
||||
| `W_DATA_BLOCK_LAST` | 0x04 | `ABORT_SUCCESS` / `ABORT_ERROR` | 0x06 / 0x07 |
|
||||
|
||||
## 时序
|
||||
|
||||
1. `UPGRADE_START`(空负载)→ `START_OK`。
|
||||
服务端往 FPGA 寄存器 `0xE0` 写 `RESET_EN(bit0)=1` 再写 0,放开 `SpiFlashProgrammer`。
|
||||
2. 轮询 `POLL_ERASER` 直到 `ERASER_END`。擦 8 MB 要几十秒。
|
||||
3. 逐帧 `W_DATA_BLOCK`,负载是 **mcs 的原始 ASCII 文本行**。
|
||||
- `READY` → 发下一帧
|
||||
- `NOT_READY` → 等 1 ms **重发同一帧**
|
||||
4. 负载里出现 EOF 记录(`:00000001FF`)后,改发 `W_DATA_BLOCK_LAST`(空负载)轮询,
|
||||
直到 `ABORT_SUCCESS` 或 `ABORT_ERROR`。服务端最多让你轮 100 次。
|
||||
5. `ABORT_SUCCESS` 后 app 调 `reset_fpga_system()` 并置 `fpga_up_sys_reboot=1` —— **imx6 会重启,
|
||||
连接会断**。这是正常的,不是错误。
|
||||
|
||||
## 三个会把你坑死的硬限制
|
||||
|
||||
都在 `recv_upgrade_data_parse()` / `inc/tcp_task.h` 里,超了就 `ABORT_ERROR`:
|
||||
|
||||
1. **负载 ≤ 538 字节**。`rxdata[MAX_DATA_LEN=544]` 装的是**整帧**,帧头尾占 6 字节。
|
||||
2. **每行长度必须在 `[11, 50]` 字节之间**(`UPDATA_RAW_FRAME_MIN/MAX_LEN`),**含行尾 CRLF**。
|
||||
实际 mcs 只有三种行长:17(ELA)、45(数据)、13(EOF),都合法。
|
||||
3. **一帧里必须是整行**。服务端不看换行符,它用 `strchr(payload, ':')` 重新切分,
|
||||
最后一行按 `strlen` 量到负载末尾。**一行跨两帧 = 两行畸形行 = 直接中止。**
|
||||
|
||||
另外服务端一次 `lwip_recv()` 读一帧,所以客户端要一次 `send()` 发完整帧,并关掉 Nagle。
|
||||
|
||||
数据行 `:10 AAAA 00 <32 hex> CC` 里只有那 16 个数据字节被取走;地址和 ELA 记录全被忽略
|
||||
(详见 `references/quickboot.md`)。8 MB 镜像 = 524288 行 × 16 B,按 11 行/帧 → 约 47663 帧。
|
||||
|
||||
## 曾经的"第二次远程升级必失败" —— 已定位并修复
|
||||
|
||||
**症状**:JTAG 烧完 RU_golden 后第一次远程升级成功;冷启动后再升级第二次,卡在擦除阶段永不结束。
|
||||
|
||||
**根因**:`smod_top.v` 里 flash 的三根 SPI 线**根本没接出去**。
|
||||
|
||||
```verilog
|
||||
// 修复前 —— 只是内部 wire,MISO 还被钉死成常量
|
||||
wire outSpiCsB, outSpiMosi;
|
||||
wire inSpiMiso = 1'b1;
|
||||
|
||||
// RU_golden_top.v 一直是对的
|
||||
output outSpiCsB ;
|
||||
output outSpiMosi ;
|
||||
input inSpiMiso ;
|
||||
```
|
||||
|
||||
`smod_ioplanning.xdc` 和 `timing.xdc` 早就把 `outSpiCsB`(P18) / `outSpiMosi`(R14) / `inSpiMiso`(R15)
|
||||
约束好了,但顶层没有这几个端口,于是 Vivado 只是丢了三条 CRITICAL WARNING 就继续:
|
||||
|
||||
```
|
||||
CRITICAL WARNING: [Vivado 12-4739] set_output_delay:No valid object(s) found for '[get_ports outSpiCsB]'
|
||||
```
|
||||
|
||||
后果:my_exciter 里的 `SpiFlashProgrammer` 片选和 MOSI 悬空、MISO 恒为 1,读 flash ID 得到全 `0xFF`,
|
||||
**在擦除之前就报错退出**。golden 的引脚是接好的,所以由 golden 执行的那一次升级永远成功 ——
|
||||
两次升级执行擦写的是**不同 bitstream 里的 programmer**,这正是"第一次成、第二次败"的由来。
|
||||
|
||||
**修复**:把三个信号提升为 `smod_top` 的顶层端口(已改)。实测 `rstate` 从
|
||||
`0x7`(ERROR=1, DONE=1, EraseOK=0)变为 `0x0` → `0xc`(Ready=1, EraseOK=1),擦除正常结束。
|
||||
|
||||
## ⚠ imx6 缺陷:`POLL_ERASER` 不看 ERROR 位
|
||||
|
||||
上面那个 FPGA bug 之所以表现为"卡死"而不是"报错",是因为 `tcp_task.c` 的擦除轮询只判断 `EARSE_OK`:
|
||||
|
||||
```c
|
||||
rstate = fpga_byte_read(0x01, FPGA_UPGRADE_CTRL_ADDR);
|
||||
if (rstate & FPGA_UP_STATUS_EARSE_OK_MASK) ret_data = ERASER_END;
|
||||
else ret_data = ERASER_ING; // ERROR 位被无视
|
||||
```
|
||||
|
||||
于是"已经失败"和"还在擦"返回同一个 `ERASER_ING`,客户端只能一直等到超时。原厂那个
|
||||
`Xfpga_ru.exe` 就是这样毫无提示地转圈。
|
||||
|
||||
**这个 bug 还在。** 排查时不要信客户端的"erasing...",直接读设备日志:
|
||||
|
||||
```bash
|
||||
ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.1.143 \
|
||||
"grep 'ing|end' /var/module_log/imx_atsc_app.log | tail -3"
|
||||
```
|
||||
|
||||
`rstate=0x7` = programmer 已中止;`rstate=0x0` 才是真的在擦。
|
||||
|
||||
**根治**:在 `POLL_ERASER` 分支里加 `if (rstate & FPGA_UPGRADE_STATUS_ERROR_MASK) ret_data = ABORT_ERROR;`。
|
||||
改完要重新交叉编译并部署 app。
|
||||
|
||||
## 尚未证实的风险:DPD 与升级数据流共用 SPI1
|
||||
|
||||
这一条**不是**上面那个 bug 的原因,但结构上确实存在,写在这里以免以后踩:
|
||||
|
||||
- FPGA 侧升级期间 `inReset_EnableB==0`,`spi_slave32` 常开,SPI1 上任何 32-bit 字都会被当成镜像数据。
|
||||
`smod_top.v` 自己的注释:`// when doing dpd S&L, MISO is spi read pin`。
|
||||
- imx6 侧 DPD 的 `ispi_tx_big_block()`(`bsp/dpd_proc.c`)和升级的 `ispi_fpga_send_buf_4Byte()`
|
||||
**用同一个 `fpga_spi_fd`**(`bsp/isys_config.c`)。
|
||||
- 互斥量 `g_now_only_upgrade_fpga` 只在 `lcd_task.c` 一处被读,且在 `ERASER_END` 才置位。
|
||||
另一个标志 `get_upgrade_flg()` 只有网页升级会置位。
|
||||
|
||||
实测三次连续升级成功时 `adpc=0`(ADPC_OFF),所以 DPD 的 S&L 没有在跑 —— **这条路径从未被真正验证过**。
|
||||
稳妥起见,升级前把 ADPC 设为 OFF。
|
||||
@@ -0,0 +1,77 @@
|
||||
# Flash 布局与 QuickBoot 兜底
|
||||
|
||||
板上 flash:Winbond **w25q128bv**,128 Mbit = 16 MB,SPIx1,**24-bit 地址**,64 KB sector / 4 KB subsector。
|
||||
|
||||
## 三个地址,一个都不能记错
|
||||
|
||||
来自 `my_exciter.srcs/sources_1/quickboot/SpiFlashProgrammer.vhd`(**RU_golden 和 my_exciter 引用的是同一份文件**,
|
||||
见 `RU_golden.xpr` 里的 `$PPRDIR/../my_exciter/.../SpiFlashProgrammer.vhd`,所以两个工程的常数天然一致):
|
||||
|
||||
```vhdl
|
||||
cAddrUpdateStart = X"00400000" -- update 镜像起点
|
||||
cAddrUpdateEnd = X"00C00000" -- update 镜像终点+1 → 槽宽 8 MB
|
||||
cAddrSwitchWordN25Q = X"00000FFC" -- 切换字
|
||||
cAddrWidth = 24 -- 和 w25q128bv 匹配
|
||||
```
|
||||
|
||||
flash 实际布局(从 `golden_design_initial.mcs` 解码得到,不是推测):
|
||||
|
||||
```
|
||||
0x000000..0x000FFB FF FF ... 空
|
||||
0x000FFC 00 99 55 66 切换字(AA 被写成 00 = 无效)
|
||||
0x001000 20000000 NOOP
|
||||
30020001 00400000 写 WBSTAR = 0x400000
|
||||
30008001 0000000F 写 CMD = IPROG → 跳到 update 镜像
|
||||
0x001050 AA995566 golden bitstream 自己的同步字
|
||||
..0x3FFFFF golden bitstream(RU_golden_top.bit,3.4 MB)
|
||||
0x400000..0xBFFFFF update 镜像(8 MB,日常远程升级写这里)
|
||||
0xC00000..0xFFFFFF 未用
|
||||
```
|
||||
|
||||
**兜底机制**:上电时 FPGA 从地址 0 扫描同步字。
|
||||
- 切换字 = `AA995566` → 在 0xFFC 同步 → 执行 0x1000 处的 IPROG → 加载 update 镜像。
|
||||
- 切换字 ≠ `AA995566`(擦除后是 `FFFFFFFF`)→ 0xFFC 不同步 → 继续扫 → 在 0x1050 撞上 golden 的同步字 → **加载 golden**。
|
||||
|
||||
远程升级一开始就把切换字所在的 subsector 擦掉(`sProgrammerEraseSwitchWord`,SSE 命令,
|
||||
擦 `0x000000..0x000FFF`),写完校验通过后再把切换字写回去。所以**升级中途断电只会退回 golden,不会变砖**。
|
||||
|
||||
> subsector 是 4 KB,擦 0xFFC 只波及 `0x000000..0x000FFF`。IPROG 头在 `0x001000`,属于下一个
|
||||
> subsector,**不会被擦到**。golden 是安全的 —— 如果怀疑"升级把 golden 弄坏了",可以排除这条。
|
||||
|
||||
## 为什么 `-imagesize 64`
|
||||
|
||||
perl 脚本的模型是"golden 和 update 一样大、update 紧跟 golden":
|
||||
`imageSizeBytes = imagesize(Mbit) × 131072`,`updateStartAddr = imageSizeBytes`。
|
||||
|
||||
- **golden**(`MakeSpiFlashProgrammerOnlyInitMcs.pl -imagesize 32`)→ 4 MB → IPROG 里的
|
||||
WBSTAR = `0x400000`,和 `cAddrUpdateStart` 对上。日志见 `RU_golden/bitstream/gen_initial.log`。
|
||||
- **update**(`MakeSpiFlashProgrammerMcsFiles_backup.pl -imagesize 64`)→ 8 MB → 负载
|
||||
`pad(bitstream, 0x7FFFFC) + crc32` 正好 8 MB,和槽宽 `0xC00000-0x400000` 对上。
|
||||
|
||||
两个 imagesize 不一样是**故意的**(VHDL 注释里的 "Plan B: golden enlarged to 4MB"),别去"统一"它们。
|
||||
|
||||
`_update.mcs` 里的 Intel-HEX 地址写的是 `0x800000`,看起来和 `cAddrUpdateStart=0x400000` 矛盾 ——
|
||||
**它不矛盾,因为地址被忽略**。imx6 的 `recv_upgrade_data_parse()` 只从 `:10xxxx00<32 hex>` 记录里抠出
|
||||
16 个数据字节,扩展线性地址记录 `:02000004` 直接当"中间控制字段"丢掉。FPGA 从 `cAddrUpdateStart`
|
||||
顺序写。所以对远程升级而言,`_update.mcs` 只有**负载长度**和**CRC**是有意义的。
|
||||
|
||||
校验 `_update.mcs` 的起始地址是 `0x800000`,本质上是在校验"这个文件是用 `-imagesize 64` 生成的"。
|
||||
|
||||
## 两条烧写路径,别搞混
|
||||
|
||||
| | JTAG 直烧 | 网络远程升级 |
|
||||
|---|---|---|
|
||||
| 文件 | `RU_golden/bitstream/golden_design_initial.mcs` | `bitstream/<tag>_update.mcs` |
|
||||
| 工具 | `/fpga_flash` skill(flash 型号 `w25q128bv`) | `scripts/fpga_ru.py` |
|
||||
| 写到 | `0x000000..0x3FFFFF` | `0x400000..0xBFFFFF` |
|
||||
| 何时用 | 首次装机、板子变砖、换打底镜像 | 日常迭代 |
|
||||
|
||||
`_update.mcs` **不能**拿去 JTAG 烧写:它只是 8 MB 裸数据 + CRC,没有 bitstream 头。
|
||||
反过来 `_initial.mcs` 也不能拿去远程升级。
|
||||
|
||||
严守上电时序:断电 → 断 USB-Blast → 上电 → 连 USB-Blast → 烧写 → 断电 → 断 USB-Blast → 上电 → 连 USB-Blast。
|
||||
|
||||
## 调试期的第三条路
|
||||
|
||||
`program_device` 直接把 `.bit` 下到 FPGA,掉电即失,不碰 flash,配 `.ltx` 还能抓 ILA。
|
||||
迭代验证用它最快,功能确认后再走远程升级固化。固化用的 bit 要**去掉 ILA 重新实现**。
|
||||
Reference in New Issue
Block a user