Added JTAG interface with testbench
This commit is contained in:
167
scripts/jtag_write_user_impact.py
Executable file
167
scripts/jtag_write_user_impact.py
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write USER JTAG data via iMPACT using SVF + play flow."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
DEFAULT_IMPACT = "/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64/impact"
|
||||
DEFAULT_SETTINGS = "/opt/Xilinx/14.7/ISE_DS/settings64.sh"
|
||||
|
||||
|
||||
def _parse_int(value: str) -> int:
|
||||
return int(value, 0)
|
||||
|
||||
|
||||
def _resolve_impact(binary: str | None) -> str | None:
|
||||
if binary:
|
||||
return binary if os.path.isfile(binary) else shutil.which(binary)
|
||||
if os.path.isfile(DEFAULT_IMPACT):
|
||||
return DEFAULT_IMPACT
|
||||
return shutil.which("impact")
|
||||
|
||||
|
||||
def _fmt_hex_for_bits(value: int, bits: int) -> str:
|
||||
masked = value & ((1 << bits) - 1)
|
||||
nibbles = (bits + 3) // 4
|
||||
return f"{masked:0{nibbles}X}"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Write USER JTAG data to Spartan-6 via iMPACT play (SVF)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input",
|
||||
required=True,
|
||||
help="Input file to stream over USER JTAG (written byte-by-byte).",
|
||||
)
|
||||
parser.add_argument("--output", type=str, default=None)
|
||||
parser.add_argument("--dr-bits", type=int, default=8, help="DR width in bits (default: 8).")
|
||||
parser.add_argument("--ir-value", type=_parse_int, default=0x02, help="IR opcode (default USER1: 0x02).")
|
||||
parser.add_argument("--ir-bits", type=int, default=6, help="IR width in bits (default: 6).")
|
||||
parser.add_argument("--serial", default=None, help="Cable ESN/serial (example: D306180FABCD).")
|
||||
parser.add_argument("--impact", default=None, help=f"Path/name of impact binary (default: {DEFAULT_IMPACT}).")
|
||||
parser.add_argument(
|
||||
"--settings",
|
||||
default=DEFAULT_SETTINGS,
|
||||
help=f"Xilinx settings script sourced for this run only (default: {DEFAULT_SETTINGS}).",
|
||||
)
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print generated SVF/CMD and exit.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dr_bits <= 0 or args.ir_bits <= 0:
|
||||
print("error: --dr-bits and --ir-bits must be > 0", file=sys.stderr)
|
||||
return 2
|
||||
if args.dr_bits != 8:
|
||||
print("error: this file-stream mode currently supports only --dr-bits 8", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
impact = _resolve_impact(args.impact)
|
||||
if impact is None:
|
||||
print("error: impact binary not found", file=sys.stderr)
|
||||
return 127
|
||||
if not os.path.isfile(args.input):
|
||||
print(f'error: input file not found: "{args.input}"', file=sys.stderr)
|
||||
return 2
|
||||
|
||||
ir_hex = _fmt_hex_for_bits(args.ir_value, args.ir_bits)
|
||||
with open(args.input, "rb") as f:
|
||||
payload = f.read()
|
||||
if len(payload) == 0:
|
||||
print("error: input file is empty", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
svf_lines = [
|
||||
"! Auto-generated by jtag_write_user_impact.py",
|
||||
f"! Source file: {args.input} ({len(payload)} bytes)",
|
||||
"TRST ABSENT;",
|
||||
"ENDIR IDLE;",
|
||||
"ENDDR IDLE;",
|
||||
"STATE RESET;",
|
||||
"STATE IDLE;",
|
||||
f"SIR {args.ir_bits} TDI ({ir_hex});",
|
||||
]
|
||||
for byte in payload:
|
||||
svf_lines.append(f"SDR 8 TDI ({byte:02X});")
|
||||
svf_lines += [
|
||||
"RUNTEST 16 TCK;",
|
||||
"STATE IDLE;",
|
||||
"",
|
||||
]
|
||||
svf_text = "\n".join(svf_lines)
|
||||
|
||||
cable_cmd = "setCable -port auto"
|
||||
if args.serial:
|
||||
cable_cmd += f" -esn {args.serial}"
|
||||
|
||||
if args.dry_run:
|
||||
print("### SVF ###")
|
||||
preview_limit = 64
|
||||
if len(payload) <= preview_limit:
|
||||
print(svf_text, end="")
|
||||
else:
|
||||
preview = "\n".join(svf_lines[:8 + preview_limit])
|
||||
print(preview)
|
||||
print(f"... ({len(payload) - preview_limit} more SDR lines omitted)")
|
||||
print("### iMPACT CMD ###")
|
||||
print("setMode -bs")
|
||||
print(cable_cmd)
|
||||
print("addDevice -p 1 -file <generated.svf>")
|
||||
print("play")
|
||||
print("quit")
|
||||
return 0
|
||||
|
||||
svf_path = None
|
||||
cmd_path = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile("w", suffix=".svf", delete=False) as sf:
|
||||
sf.write(svf_text)
|
||||
svf_path = sf.name
|
||||
if args.output is not None:
|
||||
with open(args.output, "w") as sf:
|
||||
sf.write(svf_text)
|
||||
|
||||
cmd_text = "\n".join(
|
||||
[
|
||||
"setMode -bs",
|
||||
cable_cmd,
|
||||
f'addDevice -p 1 -file "{svf_path}"',
|
||||
"play",
|
||||
"quit",
|
||||
"",
|
||||
]
|
||||
)
|
||||
with tempfile.NamedTemporaryFile("w", suffix=".cmd", delete=False) as cf:
|
||||
cf.write(cmd_text)
|
||||
cmd_path = cf.name
|
||||
|
||||
shell_cmd = (
|
||||
f'source "{args.settings}" >/dev/null 2>&1 && '
|
||||
f'"{impact}" -batch "{cmd_path}"'
|
||||
)
|
||||
proc = subprocess.run(["bash", "-lc", shell_cmd], text=True, capture_output=True)
|
||||
output = (proc.stdout or "") + (proc.stderr or "")
|
||||
print(output, end="")
|
||||
return proc.returncode
|
||||
finally:
|
||||
if cmd_path is not None:
|
||||
try:
|
||||
os.unlink(cmd_path)
|
||||
except OSError:
|
||||
pass
|
||||
if svf_path is not None:
|
||||
try:
|
||||
os.unlink(svf_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user