diff --git a/fpga_flash/SKILL.md b/fpga_flash/SKILL.md index 2eab4fa..ad66db1 100644 --- a/fpga_flash/SKILL.md +++ b/fpga_flash/SKILL.md @@ -151,6 +151,7 @@ ILA core 的 `Dropping logic core` WARNING 是正常现象(载入 loader/golde |------|----------|----------| | `check_flash_csv.py` 退出码 1 | `VIVADO_ROOT` 未配置或指错 | 修正 `~/skills_env.md` 的 `VIVADO_ROOT`;临时可传 `--vivado-dir` 覆盖 | | Vivado 中找不到 w25q128bv | CSV 已修改但 Vivado 未重启 | 提示用户重启 Vivado 后重新执行 | +| `bad lexical cast: source type value could not be interpreted as target`,报在 `get_cfgmem_parts` **或** `refresh_hw_device` 上 | CSV 里的 w25q128bv 行畸形(2026-07-30 之前的版本写的是 9 字段、从 `NAME` 开头,缺了首列数字 `CFGMEM_ID`),Vivado 拿名字当整数解析 | 重跑 `check_flash_csv.py`,它会识别并替换掉畸形行(退出码 2),然后**重启 Vivado**。注意报在 `refresh_hw_device` 上极具误导性 —— 那一步内部也读这张表,看起来像硬件/probe 问题,其实与 `.ltx`、debug core 无关 | | `No hardware target` 错误 | JTAG 未连接或板卡未上电 | 检查连接后重新执行第四步 | | DONE 灯不亮 | 启动模式跳线错误 | 检查 MODE 引脚电平,确认为 SPI 启动模式 | | 烧写后板卡跑旧程序 | Flash 未完全擦除 | 确认 `PROGRAM.ERASE 1` 已设置后重新执行 | diff --git a/fpga_flash/scripts/check_flash_csv.py b/fpga_flash/scripts/check_flash_csv.py index 6c3f44c..b3bda84 100644 --- a/fpga_flash/scripts/check_flash_csv.py +++ b/fpga_flash/scripts/check_flash_csv.py @@ -1,29 +1,70 @@ #!/usr/bin/env python3 """ -Step 1: Check and update Vivado Flash device CSV to include w25q128bv. +Step 1: Check and update Vivado's Flash device table so it knows w25q128bv. -Checks whether 'w25q128bv-spi-x1_x2_x4' is present in Vivado's -xicom_cfgmem_part_table.csv. If not, appends the correct row. +Vivado ships no entry for the Winbond W25Q128BV in SPI x1_x2_x4 mode, which is what +the SMOD/EMV7.1 Artix-7 boards use. This adds one. + +The row is not hard-coded. It is built by cloning the row Vivado already ships for +mt25ql128-spi-x1_x2_x4 -- same interface (spi, x1_x2_x4), same COMPATIBLE_DEVICES list +(which includes xc7a200t) -- and then swapping only the Winbond-specific fields. Columns +are located *by header name*, so a different Vivado version with a different schema still +gets a well-formed row instead of a silently corrupt one. + +Why cloning matters: the table's first column is a numeric CFGMEM_ID. A row that starts +with the device name instead makes Vivado try to lexical_cast a string into an int and +die with + + bad lexical cast: source type value could not be interpreted as target + +which takes down BOTH get_cfgmem_parts *and* refresh_hw_device -- the second one is what +makes this so confusing to diagnose, because the failure surfaces during hardware refresh, +nowhere near the CSV. Versions of this script before 2026-07-30 wrote exactly such a row; +if one is found it is removed and replaced. + +MEM_DEVICE_ALIAS points at n25q128-3.3v-spi-x1_x2_x4, the memory definition Vivado +actually ships (mt25ql128 aliases to the same one). No Winbond-specific data file is +needed: the flash loader bitstream is selected by FPGA part, not by flash part +(data/xicom/cfgmem/bitfile.zip holds bitfile/spi_xc7a200t_pullnone.bit and friends). +Verified on Vivado 2018.1 against a real W25Q128BV -- programming reads back +`Mfg ID : ef Memory Type : 40 Memory Capacity : 18`, i.e. the true Winbond JEDEC ID. Usage: python check_flash_csv.py # Vivado path from ~/skills_env.md python check_flash_csv.py --vivado-dir "" # or override it Exit codes: - 0 = device already present (no changes made) - 2 = device was successfully added (Vivado restart required) - 1 = error (CSV not found or write failure) + 0 = device already present and well-formed (no changes made) + 2 = device was added or a malformed row was repaired (Vivado restart required) + 1 = error (CSV not found, model row missing, or write failure) """ import argparse import os +import shutil import sys +import time from vivado_env import resolve_vivado_dir DEVICE_NAME = "w25q128bv-spi-x1_x2_x4" -# Fields: NAME, MEM_DEVICE, MEM_TYPE, MEM_DENSITY, DATA_WIDTH, -# MEM_MANUFACTURER, NUM_CFG_FILES, MEM_DEVICE_ALIAS, MEM_FAMILY -NEW_ROW = "w25q128bv-spi-x1_x2_x4,w25q128bv,spi,128,x1_x2_x4,Winbond,1,w25q128b,w25q" +MODEL_NAME = "mt25ql128-spi-x1_x2_x4" + +# Only these differ from the model row; everything else (COMPATIBLE_DEVICES, MEM_TYPE, +# MEM_DENSITY, DATA_WIDTH, NUM_CFG_FILES, ...) is inherited so it stays consistent with +# whatever Vivado version is installed. +OVERRIDES = { + "HIDDEN": "0", + "NAME": DEVICE_NAME, + "MEM_DEVICE": "w25q128bv", + "MEM_MANUFACTURER": "Winbond", + "MEM_DEVICE_ALIAS": "n25q128-3.3v-spi-x1_x2_x4", + "MEM_FAMILY": "w25q", +} + + +def detect_newline(raw_bytes): + """Keep the file's existing line ending; do not rewrite 500 lines of it.""" + return "\r\n" if b"\r\n" in raw_bytes else "\n" def main(): @@ -51,21 +92,71 @@ def main(): ) sys.exit(1) - with open(csv_path, "r", encoding="utf-8") as f: - content = f.read() + with open(csv_path, "rb") as f: + raw = f.read() + newline = detect_newline(raw) + lines = raw.decode("utf-8", "replace").splitlines() - if DEVICE_NAME in content: - print(f"[check_flash_csv] '{DEVICE_NAME}' already present. No changes needed.") + if not lines: + print(f"ERROR: {csv_path} is empty.", file=sys.stderr) + sys.exit(1) + + header = lines[0].split(",") + ncols = len(header) + try: + col = {name: i for i, name in enumerate(header)} + id_i, name_i = col["CFGMEM_ID"], col["NAME"] + except KeyError as exc: + print(f"ERROR: unexpected CSV schema, missing column {exc}.", file=sys.stderr) + print(f" header: {lines[0][:200]}", file=sys.stderr) + sys.exit(1) + + model = None + bad_rows = [] # line indices mentioning DEVICE_NAME but not well-formed + good_row = False + max_id = 0 + + for i, line in enumerate(lines[1:], start=1): + parts = line.split(",") + well_formed = len(parts) == ncols + if well_formed and parts[id_i].strip().isdigit(): + max_id = max(max_id, int(parts[id_i].strip())) + if well_formed and parts[name_i] == MODEL_NAME and model is None: + model = parts + if DEVICE_NAME in line: + if well_formed and parts[name_i] == DEVICE_NAME and parts[id_i].strip().isdigit(): + good_row = True + else: + bad_rows.append(i) + + if good_row and not bad_rows: + print(f"[check_flash_csv] '{DEVICE_NAME}' already present and well-formed. " + f"No changes needed.") sys.exit(0) - # Append the new row (ensure trailing newline before adding) - if not content.endswith("\n"): - content += "\n" - content += NEW_ROW + "\n" + if model is None: + print(f"ERROR: could not find the model row '{MODEL_NAME}' in {csv_path}.", + file=sys.stderr) + print(" Cannot synthesise a w25q128bv row without it; add one by hand.", + file=sys.stderr) + sys.exit(1) + row = list(model) + row[id_i] = str(max_id + 1) + for key, value in OVERRIDES.items(): + if key in col: + row[col[key]] = value + new_line = ",".join(row) + + kept = [ln for i, ln in enumerate(lines) if i not in bad_rows] + if not good_row: + kept.append(new_line) + + backup = f"{csv_path}.bak-{time.strftime('%Y%m%d-%H%M%S')}" try: - with open(csv_path, "w", encoding="utf-8") as f: - f.write(content) + shutil.copy2(csv_path, backup) + with open(csv_path, "w", encoding="utf-8", newline="") as f: + f.write(newline.join(kept) + newline) except PermissionError: print( f"ERROR: Cannot write to {csv_path}. Try running as Administrator.", @@ -73,8 +164,14 @@ def main(): ) sys.exit(1) - print(f"[check_flash_csv] Added '{DEVICE_NAME}' to:") + if bad_rows: + print(f"[check_flash_csv] Removed {len(bad_rows)} malformed '{DEVICE_NAME}' row(s) " + f"(line(s) {', '.join(str(i + 1) for i in bad_rows)}) written by an older " + f"version of this script.") + print(f"[check_flash_csv] Added '{DEVICE_NAME}' (CFGMEM_ID={row[id_i]}, " + f"cloned from {MODEL_NAME}) to:") print(f" {csv_path}") + print(f" backup: {backup}") print() print("NOTE: You must restart Vivado for the new Flash device to be recognized.") sys.exit(2)