#!/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 "" # 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()