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>
182 lines
6.6 KiB
Python
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()
|