Files
Myfro9andClaude Opus 5 7a11bac29d fix(fpga_flash): 修复写入 Vivado cfgmem 器件表的 w25q128bv 行格式错误
check_flash_csv.py 里 NEW_ROW 是硬编码的 9 字段、从 NAME 开头,而 Vivado
2018.1 的 xicom_cfgmem_part_table.csv 是 14 列、首列为数字 CFGMEM_ID。
Vivado 于是拿 "w25q128bv-spi-x1_x2_x4" 去转 int,抛

    bad lexical cast: source type value could not be interpreted as target

这条错误同时打挂 get_cfgmem_parts 和 refresh_hw_device。后者最具误导性:
它报在硬件刷新阶段,看起来像 probe/.ltx/debug core 的问题,与 CSV 毫无关联,
实际上 refresh_hw_device 内部也读这张表。SMOD 板实测因此完全无法烧写 flash。

改为动态生成:克隆 Vivado 自带的 mt25ql128-spi-x1_x2_x4 行(同为 spi /
x1_x2_x4,COMPATIBLE_DEVICES 含 xc7a200t),只替换 Winbond 相关字段,
字段位置按表头列名定位而非写死下标,换 Vivado 版本 schema 变了也不会写出
畸形行。CFGMEM_ID 取现有最大值 +1。

MEM_DEVICE_ALIAS 指向 n25q128-3.3v-spi-x1_x2_x4 —— Vivado 真正带数据的那个
定义(mt25ql128 也别名到它)。不需要 Winbond 专属数据文件:flash loader
比特流按 FPGA 型号索引(bitfile.zip 里是 spi_xc7a200t_pullnone.bit),
与 flash 型号无关。

其它改动:
- 识别并替换旧版本留下的畸形行(退出码 2),而不是因为 "名字已出现在文件里"
  就误判为已安装直接返回 0
- 改写前自动备份 CSV
- 保留原文件的行尾风格,不整体改写 CRLF/LF

实测(Vivado 2018.1 + SMOD Artix-7 xc7a200t + 真实 W25Q128BV):
- 三条路径均正确:已存在且格式正确→exit 0 且不改文件;全新→exit 2;
  注入 9 字段畸形行→识别并修复→exit 2
- get_cfgmem_parts 可正常返回,属性 MEM_TYPE=spi / MEM_DENSITY=128
- 烧写读回 Mfg ID : ef  Memory Type : 40  Memory Capacity : 18(真实
  Winbond JEDEC ID),Erase / Program-Verify / completed successfully 全过

SKILL.md 排错表补上该症状,并注明报在 refresh_hw_device 上的误导性。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 22:19:56 +08:00

182 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""
Step 1: Check and update Vivado's Flash device table so it knows w25q128bv.
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 "<install root>" # or override it
Exit codes:
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"
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():
parser = argparse.ArgumentParser(
description="Check/update Vivado Flash CSV for w25q128bv"
)
parser.add_argument(
"--vivado-dir",
default=None,
help="Vivado install root; defaults to VIVADO_ROOT from ~/skills_env.md",
)
args = parser.parse_args()
csv_path = os.path.join(
resolve_vivado_dir(args.vivado_dir),
"data", "xicom", "xicom_cfgmem_part_table.csv"
)
csv_path = csv_path.replace("\\", "/")
if not os.path.isfile(csv_path):
print(f"ERROR: CSV not found at: {csv_path}", file=sys.stderr)
print(
" Please verify --vivado-dir points to a valid Vivado installation.",
file=sys.stderr,
)
sys.exit(1)
with open(csv_path, "rb") as f:
raw = f.read()
newline = detect_newline(raw)
lines = raw.decode("utf-8", "replace").splitlines()
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)
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:
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.",
file=sys.stderr,
)
sys.exit(1)
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)
if __name__ == "__main__":
main()