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>
This commit is contained in:
@@ -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 "<install root>" # 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)
|
||||
|
||||
Reference in New Issue
Block a user