Last active
July 11, 2026 01:55
-
-
Save z4yx/fa3c5320341c461b6c1daee2bb152323 to your computer and use it in GitHub Desktop.
SVF (Serial Vector Format) Generator for ARM CPU Memory Download
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| SVF (Serial Vector Format) Generator for ARM Cortex-R52 Memory Access | |
| ========================================================================= | |
| Based on ARM IHI0031H — ARM Debug Interface Architecture Specification (ADIv6) | |
| This tool generates SVF-format JTAG operation sequences for two use cases: | |
| 1. **Binary download** — download a .bin binary file into ARM Cortex-R52 | |
| processor memory via the CoreSight Debug Access Port (DAP). | |
| 2. **Command file** — execute arbitrary memory read/write commands defined | |
| in a text file, with optional per-read TDO verification. | |
| Architecture Overview | |
| --------------------- | |
| Host (SVF Player) ──JTAG──> JTAG-DP ──> DAP ──> MEM-AP ──> Memory Bus | |
| (AHB/AXI) | |
| JTAG-DP IR (4-bit): | |
| 0xE = IDCODE — Read device identification | |
| 0xA = DPACC — Debug Port register access (35-bit DR) | |
| 0xB = APACC — Access Port register access (35-bit DR) | |
| 0x8 = ABORT — Abort operation | |
| DPACC / APACC DR Scan Chain (35 bits): | |
| Bits [34:3] = DATA[31:0] — 32-bit read/write data | |
| Bits [2:1] = A[3:2] — Register address within bank | |
| Bit [0] = RnW — 1=Read, 0=Write | |
| Pipelined Reads: | |
| AP/DP read data is returned in the *next* DPACC/APACC transaction. | |
| Use a dummy DP-RDBUFF read to retrieve the last read result. | |
| Memory Write Sequence (binary mode) | |
| ----------------------------------- | |
| 1. JTAG Reset → Run-Test/Idle | |
| 2. Read IDCODE (verification) | |
| 3. Power-up debug domain (DP.CTRL/STAT) | |
| 4. Select MEM-AP (DP.SELECT) | |
| 5. Configure MEM-AP (AP.CSW: 32-bit, auto-increment) | |
| 6. For each word: write AP.TAR, then write AP.DRW | |
| 7. (Optional) Read-back verification | |
| 8. JTAG Reset | |
| Memory Command Sequence (command-file mode) | |
| -------------------------------------------- | |
| 1–4. Same setup as binary mode, but CSW uses ADDRINC_OFF. | |
| 5. For each command: | |
| - Write: set AP.TAR, write AP.DRW | |
| - Read: set AP.TAR, read AP.DRW, read DP.RDBUFF | |
| (with optional TDO verification if Y flag is set) | |
| 6. JTAG Reset | |
| Command File Format | |
| ------------------- | |
| <addr_hex> <data_hex> <W|R> [Y] | |
| # This is a comment line | |
| 0x80000000 DEADBEEF W | |
| 0x80000004 12345678 R Y | |
| 0x80001000 AABBCCDD R | |
| Usage | |
| ----- | |
| # Binary download | |
| python svf_gen.py firmware.bin --addr 0x80000000 -o download.svf | |
| # Command file | |
| python svf_gen.py --cmds ops.txt -o ops.svf | |
| python svf_gen.py --cmds ops.txt --width 16 -o ops.svf | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import struct | |
| import sys | |
| import textwrap | |
| from dataclasses import dataclass, field | |
| from typing import List, Optional | |
| # ============================================================================= | |
| # Memory Command (for text-based command files) | |
| # ============================================================================= | |
| @dataclass | |
| class MemCmd: | |
| """A single memory read/write command parsed from a text command file. | |
| Fields: | |
| addr: Target memory address (32-bit). | |
| data: Data value to write, or expected data on read. | |
| is_read: True for read (R), False for write (W). | |
| verify: For reads: whether to compare TDO against *data* (Y/N). | |
| Only meaningful when *is_read* is True. | |
| """ | |
| addr: int | |
| data: int | |
| is_read: bool | |
| verify: bool = False | |
| def parse_cmd_file(path: str, data_width: int = 32) -> List[MemCmd]: | |
| """Parse a text file of memory access commands. | |
| Each non-empty, non-comment line must contain at least three | |
| whitespace-separated fields:: | |
| <addr_hex> <data_hex> <W|R> [Y] | |
| - *addr_hex* — 32-bit address in hexadecimal (e.g. ``0x80000000``). | |
| - *data_hex* — data word in hex; width is clipped to *data_width* bits. | |
| - *W* or *R* — write or read operation. | |
| - *Y* — (optional, read-only) verify that TDO matches *data_hex*. | |
| Lines whose first non-whitespace character is ``#`` are treated as | |
| comments and skipped. Blank lines are also ignored. | |
| """ | |
| mask = (1 << data_width) - 1 | |
| cmds: List[MemCmd] = [] | |
| with open(path, "r") as fh: | |
| for lineno, raw in enumerate(fh, 1): | |
| line = raw.strip() | |
| if not line or line.startswith("#"): | |
| continue | |
| parts = line.split() | |
| if len(parts) < 3: | |
| raise ValueError( | |
| f"{path}:{lineno}: expected at least 3 fields " | |
| f"(addr data R|W), got {len(parts)}: {line!r}") | |
| addr = int(parts[0], 16) | |
| data = int(parts[1], 16) & mask | |
| rw = parts[2].upper() | |
| if rw not in ("R", "W"): | |
| raise ValueError( | |
| f"{path}:{lineno}: third field must be R or W, " | |
| f"got {rw!r}: {line!r}") | |
| is_read = (rw == "R") | |
| verify = False | |
| if is_read and len(parts) >= 4: | |
| v = parts[3].upper() | |
| if v == "Y": | |
| verify = True | |
| elif v != "N": | |
| raise ValueError( | |
| f"{path}:{lineno}: fourth field must be Y or N " | |
| f"(or omitted), got {v!r}: {line!r}") | |
| cmds.append(MemCmd(addr=addr, data=data, | |
| is_read=is_read, verify=verify)) | |
| return cmds | |
| # ============================================================================= | |
| # JTAG Daisy-Chain Configuration | |
| # ============================================================================= | |
| @dataclass | |
| class JtagChainConfig: | |
| """Describes a JTAG daisy-chain topology for multi-TAP systems. | |
| In a daisy chain, TAPs are connected in series: | |
| TDI → [TAP_0] → [TAP_1] → ... → [TAP_n-1] → TDO | |
| Only the *target* TAP receives real IR/DR data; all others are placed | |
| in BYPASS mode (IR = all-1s, 1-bit DR = 0). | |
| Configuration file format (JSON):: | |
| { | |
| "taps": [ | |
| {"name": "ICE", "irlen": 4}, | |
| {"name": "DAP", "irlen": 4}, | |
| {"name": "FPGA", "irlen": 6} | |
| ], | |
| "target": "DAP" | |
| } | |
| *target* may be a name (string) or *target_index* may be a zero-based | |
| integer. *target* takes precedence when both are present. | |
| """ | |
| taps: List[dict] = field(default_factory=list) | |
| target_index: int = 0 | |
| # ------------------------------------------------------------------ | |
| # Factory | |
| # ------------------------------------------------------------------ | |
| @classmethod | |
| def from_json_file(cls, path: str) -> "JtagChainConfig": | |
| """Load chain configuration from a JSON file.""" | |
| with open(path, "r") as fh: | |
| cfg = json.load(fh) | |
| taps = cfg["taps"] | |
| if not taps: | |
| raise ValueError("Chain config: 'taps' list must not be empty") | |
| # Resolve target | |
| target_index = cls._resolve_target(cfg, taps) | |
| if target_index < 0 or target_index >= len(taps): | |
| raise ValueError( | |
| f"Chain config: target_index {target_index} out of range " | |
| f"[0, {len(taps) - 1}]") | |
| return cls(taps=taps, target_index=target_index) | |
| @classmethod | |
| def single_tap(cls) -> "JtagChainConfig": | |
| """Convenience: a chain consisting of a single DAP TAP (no daisy chain).""" | |
| return cls(taps=[{"name": "DAP", "irlen": 4}], target_index=0) | |
| @staticmethod | |
| def _resolve_target(cfg: dict, taps: list) -> int: | |
| """Determine target TAP index from 'target' (name) or 'target_index'.""" | |
| if "target" in cfg: | |
| name = cfg["target"] | |
| for i, tap in enumerate(taps): | |
| if tap.get("name") == name: | |
| return i | |
| raise ValueError( | |
| f"Chain config: target '{name}' not found in taps list") | |
| if "target_index" in cfg: | |
| return int(cfg["target_index"]) | |
| # Default: first tap | |
| return 0 | |
| # ------------------------------------------------------------------ | |
| # Chain-aware SIR / SDR composition | |
| # ------------------------------------------------------------------ | |
| def compose_sir(self, target_ir: int, target_ir_bits: int) -> tuple: | |
| """Given the target TAP's IR value and bit-width, return the | |
| full daisy-chain SIR as ``(total_bits, tdi_value)``. | |
| Non-target TAPs receive BYPASS IR (all 1s) of their respective | |
| IR length. | |
| """ | |
| n = len(self.taps) | |
| if n <= 1: | |
| return (target_ir_bits, target_ir) | |
| t = self.target_index | |
| # IR lengths before / after the target | |
| irlen_before = sum(tap["irlen"] for tap in self.taps[:t]) | |
| irlen_after = sum(tap["irlen"] for tap in self.taps[t + 1:]) | |
| total_bits = irlen_before + target_ir_bits + irlen_after | |
| # BYPASS IR = all-1s for the given length | |
| bypass_before = (1 << irlen_before) - 1 if irlen_before else 0 | |
| bypass_after = (1 << irlen_after) - 1 if irlen_after else 0 | |
| # TDI layout (LSB shifted first in JTAG): | |
| # [bypass_after] [target_ir] [bypass_before] | |
| tdi = ((bypass_after << (irlen_before + target_ir_bits)) | |
| | (target_ir << irlen_before) | |
| | bypass_before) | |
| return (total_bits, tdi) | |
| def compose_sdr(self, target_dr: int, target_dr_bits: int) -> tuple: | |
| """Given the target TAP's DR value and bit-width, return the | |
| full daisy-chain SDR as ``(total_bits, tdi_value)``. | |
| Non-target TAPs in BYPASS contribute 1 bit each (value = 0). | |
| """ | |
| n = len(self.taps) | |
| if n <= 1: | |
| return (target_dr_bits, target_dr) | |
| t = self.target_index | |
| # Each non-target TAP in BYPASS = 1 DR bit | |
| dr_pre_bits = t # TAPs before target | |
| dr_post_bits = n - t - 1 # TAPs after target | |
| total_bits = dr_pre_bits + target_dr_bits + dr_post_bits | |
| # BYPASS DR = 0 | |
| tdi = target_dr << dr_pre_bits | |
| return (total_bits, tdi) | |
| def compose_tdo(self, target_tdo: int, target_bits: int, | |
| target_mask: int) -> tuple: | |
| """Given the expected target TAP TDO value and bit-width, return the | |
| full daisy-chain TDO as ``(full_tdo, full_mask)``. | |
| Only the target TAP's data bits are verified. Non-target TAPs in | |
| BYPASS may produce arbitrary TDO values, so their bits are excluded | |
| from the mask (mask = 0 at those positions). | |
| """ | |
| n = len(self.taps) | |
| if n <= 1: | |
| return (target_tdo, target_mask) | |
| t = self.target_index | |
| dr_pre_bits = t | |
| # Position target TDO after preceding BYPASS bits | |
| full_tdo = target_tdo << dr_pre_bits | |
| # Only check the target TAP's bits — ignore bypass TAPs | |
| full_mask = target_mask << dr_pre_bits | |
| return (full_tdo, full_mask) | |
| @property | |
| def is_daisy_chain(self) -> bool: | |
| """True when multiple TAPs are configured.""" | |
| return len(self.taps) > 1 | |
| @property | |
| def target_name(self) -> str: | |
| """Human-readable name of the target TAP.""" | |
| return self.taps[self.target_index].get("name", f"TAP#{self.target_index}") | |
| @property | |
| def summary(self) -> str: | |
| """One-line chain description for SVF comments.""" | |
| if not self.is_daisy_chain: | |
| return "Single TAP (no daisy chain)" | |
| names = [tap.get("name", "?") for tap in self.taps] | |
| markers = [ | |
| f"[{n}]" if i == self.target_index else f" {n} " | |
| for i, n in enumerate(names) | |
| ] | |
| return f"Daisy chain: {' → '.join(markers)} (target marked [...])" | |
| # ============================================================================= | |
| # SVF Command Generator | |
| # ============================================================================= | |
| class SvfGenerator: | |
| """Generates well-formatted SVF (Serial Vector Format) commands. | |
| SVF is an industry-standard ASCII format for describing JTAG operations, | |
| defined in the IEEE 1149.1 boundary-scan standard. This class emits | |
| syntactically correct SVF that can be played back by OpenOCD, UrJTAG, | |
| Xilinx tools, and other JTAG utilities. | |
| """ | |
| # ---- JTAG Instruction Register codes (4-bit for JTAG-DP) --------------- | |
| IR_IDCODE = 0xE # IDCODE register access | |
| IR_DPACC = 0xA # Debug Port access | |
| IR_APACC = 0xB # Access Port access | |
| IR_ABORT = 0x8 # Abort register | |
| # ---- DP register addresses (A[3:2] encoding within DPACC DR) ---------- | |
| DP_IDCODE_ABORT = 0x0 # Read: IDCODE; Write: ABORT | |
| DP_CTRL_STAT = 0x1 # Control / Status | |
| DP_SELECT = 0x2 # AP Select | |
| DP_RDBUFF = 0x3 # Read Buffer (pipeline flush) | |
| # ---- AP register addresses (A[3:2] encoding, bank 0) ------------------ | |
| AP_CSW = 0x0 # Control / Status Word | |
| AP_TAR = 0x1 # Transfer Address Register | |
| AP_DRW = 0x3 # Data Read / Write | |
| # ---- DP.CTRL/STAT bit definitions ------------------------------------ | |
| CSYSPWRUPREQ = 1 << 30 # System power-up request | |
| CDBGPWRUPREQ = 1 << 28 # Debug power-up request | |
| CSYSPWRUPACK = 1 << 31 # System power-up acknowledge (read-only) | |
| CDBGPWRUPACK = 1 << 29 # Debug power-up acknowledge (read-only) | |
| STICKYERR = 1 << 5 # Sticky error flag | |
| TRNCNT_MASK = 0xFFF # Turnaround counter (bits 11:0) | |
| TRNCNT_VAL = 0x200 # Recommended: 512 TCK cycles | |
| # ---- AP.CSW bit definitions ------------------------------------------- | |
| CSW_SIZE_8BIT = 0x0 | |
| CSW_SIZE_16BIT = 0x1 | |
| CSW_SIZE_32BIT = 0x2 | |
| CSW_SIZE_64BIT = 0x3 | |
| CSW_ADDRINC_OFF = 0 << 4 # No auto-increment | |
| CSW_ADDRINC_SINGLE = 1 << 4 # Single increment after access | |
| CSW_ADDRINC_PACKED = 2 << 4 # Packed transfers | |
| CSW_DEVICEEN = 1 << 6 # Device enabled | |
| CSW_DBGSWENABLE = 1 << 31 # Debug software enable (ADIv6) | |
| CSW_HPROT_DATA = 3 << 24 # AHB HPROT: data access, non-cacheable | |
| def __init__(self, | |
| output_file: Optional[str] = None, | |
| chain: Optional[JtagChainConfig] = None): | |
| """Open output stream (file or stdout). | |
| Args: | |
| output_file: Path to SVF output file (None = stdout). | |
| chain: Optional JTAG daisy-chain configuration. When provided, | |
| all SIR/SDR operations are automatically extended to | |
| account for non-target TAPs in BYPASS. | |
| """ | |
| self._f = open(output_file, "w") if output_file else sys.stdout | |
| self._indent = 0 | |
| self._chain = chain or JtagChainConfig.single_tap() | |
| self._emit_header() | |
| # ------------------------------------------------------------------ | |
| # Public API | |
| # ------------------------------------------------------------------ | |
| def comment(self, text: str): | |
| """Emit a human-readable comment line.""" | |
| for line in text.splitlines(): | |
| self._emit(f"// {line}") | |
| def blank(self): | |
| """Emit a blank line for readability.""" | |
| self._emit("") | |
| def runtest(self, tck_cycles: int): | |
| """Run-Test/Idle for *tck_cycles* TCK clock periods.""" | |
| if tck_cycles > 0: | |
| self._emit(f"RUNTEST {tck_cycles} TCK;") | |
| def jtag_idcode(self): | |
| """Set IR = IDCODE, then scan 32-bit DR to read device ID.""" | |
| self._sir(4, self.IR_IDCODE) | |
| self._sdr(32, 0x00000000, comment="Read IDCODE — TDO receives device ID") | |
| def jtag_dpacc(self): | |
| """Set IR = DPACC (Debug Port access).""" | |
| self._sir(4, self.IR_DPACC) | |
| def jtag_apacc(self): | |
| """Set IR = APACC (Access Port access).""" | |
| self._sir(4, self.IR_APACC) | |
| def dp_write(self, addr: int, data: int, comment: str = ""): | |
| """Write *data* to DP register at *addr* (A[3:2] = 0-3).""" | |
| dr = self._make_dp_ap_dr(addr, rnw=0, data=data) | |
| self.jtag_dpacc() | |
| self._sdr(35, dr, comment=comment) | |
| def dp_read(self, addr: int, comment: str = "", | |
| tdo_expected: Optional[int] = None, | |
| tdo_mask: Optional[int] = None): | |
| """Initiate read from DP register at *addr*. | |
| The actual read data appears on TDO during the **next** DPACC/APACC | |
| transaction (pipelined). Follow with a DP-RDBUFF read to retrieve it. | |
| Args: | |
| addr: DP register address (A[3:2] = 0-3). | |
| comment: Human-readable annotation. | |
| tdo_expected: Expected 35-bit TDO value for verification (data portion). | |
| tdo_mask: Bit mask for TDO comparison (1 = check, 0 = ignore). | |
| """ | |
| dr = self._make_dp_ap_dr(addr, rnw=1, data=0) | |
| self.jtag_dpacc() | |
| self._sdr(35, dr, comment=comment, | |
| tdo=tdo_expected, tdo_mask=tdo_mask) | |
| def ap_write(self, addr: int, data: int, comment: str = ""): | |
| """Write *data* to AP register at *addr* (A[3:2] = 0-3).""" | |
| dr = self._make_dp_ap_dr(addr, rnw=0, data=data) | |
| self.jtag_apacc() | |
| self._sdr(35, dr, comment=comment) | |
| def ap_read(self, addr: int, comment: str = ""): | |
| """Initiate read from AP register at *addr*. | |
| As with DP reads, the data appears in the next DPACC/APACC scan. | |
| """ | |
| dr = self._make_dp_ap_dr(addr, rnw=1, data=0) | |
| self.jtag_apacc() | |
| self._sdr(35, dr, comment=comment) | |
| def close(self): | |
| """Flush and close the output stream.""" | |
| if self._f is not sys.stdout: | |
| self._f.close() | |
| # ------------------------------------------------------------------ | |
| # Internal helpers | |
| # ------------------------------------------------------------------ | |
| @staticmethod | |
| def _make_dp_ap_dr(addr: int, rnw: int, data: int) -> int: | |
| """Build the 35-bit DPACC/APACC DR scan value. | |
| Layout (LSB shifted first on JTAG TDI): | |
| Bit [0] = RnW (1=Read, 0=Write) | |
| Bits [2:1] = A[3:2] (register select, 0-3) | |
| Bits [34:3] = DATA[31:0] (read/write payload) | |
| """ | |
| return ((data & 0xFFFFFFFF) << 3) | ((addr & 0x3) << 1) | (rnw & 0x1) | |
| @staticmethod | |
| def _hex(value: int, bits: int) -> str: | |
| """Format *value* as zero-padded hex, wide enough for *bits*. | |
| Examples: | |
| _hex(0xA, 4) -> "A" | |
| _hex(0xA, 8) -> "0A" | |
| _hex(0x91A2B3C0, 35) -> "0091A2B3C0" (9 nibbles for 35 bits) | |
| """ | |
| nibbles = (bits + 3) // 4 # round up to full nibbles | |
| mask = (1 << bits) - 1 | |
| return format(value & mask, f"0{nibbles}X") | |
| @staticmethod | |
| def _mask(bits: int) -> str: | |
| """All-ones hex mask for *bits* bits.""" | |
| return SvfGenerator._hex((1 << bits) - 1, bits) | |
| def _emit(self, line: str): | |
| """Write a line with optional indentation.""" | |
| prefix = " " * self._indent | |
| self._f.write(f"{prefix}{line}\n") | |
| def _emit_header(self): | |
| """Emit the SVF preamble that every compliant SVF file requires.""" | |
| chain_desc = self._chain.summary | |
| self._f.write(textwrap.dedent(f"""\ | |
| // ============================================================================ | |
| // SVF — Serial Vector Format | |
| // Target: ARM Cortex-R52 (ARM IHI0031H / ADIv6) | |
| // Chain: {chain_desc} | |
| // Generated by: svf_gen.py | |
| // ============================================================================ | |
| TRST OFF; | |
| ENDIR IDLE; | |
| ENDDR IDLE; | |
| STATE RESET IDLE; | |
| """).lstrip()) | |
| def _sir(self, target_bits: int, target_tdi: int): | |
| """Scan Instruction Register — set target TAP IR to *target_tdi*. | |
| When a daisy chain is configured, non-target TAPs receive BYPASS IR | |
| (all-1s) and the SIR length is extended accordingly. | |
| """ | |
| bits, tdi = self._chain.compose_sir(target_tdi, target_bits) | |
| self._emit(f"SIR {bits} TDI ({self._hex(tdi, bits)}) " | |
| f"SMASK ({self._mask(bits)});") | |
| def _sdr(self, target_bits: int, target_tdi: int, comment: str = "", | |
| tdo: Optional[int] = None, tdo_mask: Optional[int] = None): | |
| """Scan Data Register — shift *target_tdi* into the target TAP DR. | |
| When a daisy chain is configured, non-target TAPs in BYPASS add | |
| 1 bit each (value 0) and the SDR length is extended accordingly. | |
| Args: | |
| target_bits: Target TAP DR width. | |
| target_tdi: TDI value for the target TAP. | |
| comment: Human-readable annotation. | |
| tdo: Expected TDO value for verification (full chain). | |
| tdo_mask: TDO comparison mask (1=check, 0=ignore). | |
| """ | |
| bits, tdi = self._chain.compose_sdr(target_tdi, target_bits) | |
| parts = (f"SDR {bits} TDI ({self._hex(tdi, bits)}) " | |
| f"SMASK ({self._mask(bits)})") | |
| if tdo is not None and tdo_mask is not None: | |
| full_tdo, full_mask = self._chain.compose_tdo( | |
| tdo, target_bits, tdo_mask) | |
| parts += (f" TDO ({self._hex(full_tdo, bits)}) " | |
| f"MASK ({self._hex(full_mask, bits)})") | |
| tail = f" // {comment}" if comment else "" | |
| self._emit(f"{parts};{tail}") | |
| # ============================================================================= | |
| # High-Level Sequence Generator | |
| # ============================================================================= | |
| class CortexR52SvfBuilder: | |
| """Orchestrates the full SVF sequence for Cortex-R52 memory download. | |
| Supports both ADIv5 (e.g. Cortex-A9, Cortex-M4) and ADIv6 (Cortex-R52) | |
| DAP protocols. The APACC field definitions (DR format, CSW, TAR, DRW) | |
| and ACK OK encoding (0b010) are identical between versions. The only | |
| difference is CSW bit 31 (DBGSWENABLE), which exists only in ADIv6. | |
| """ | |
| # ACK encoding: OK = 0b010 in both ADIv5 and ADIv6. | |
| ACK_OK = 0x2 # OK response | |
| ACK_MASK = 0x7 # verify all 3 ACK bits | |
| # Known DP IDCODE values for common implementations | |
| # (Actual value depends on silicon revision; override with --idcode) | |
| CORTEX_R52_BASE_CID = 0x6BA02477 # Cortex-R52 r1p0 | |
| def __init__(self, | |
| bin_path: Optional[str] = None, | |
| base_addr: Optional[int] = None, | |
| output_path: Optional[str] = None, | |
| ap_sel: int = 0, | |
| data_width: int = 32, | |
| dp_idcode: Optional[int] = None, | |
| verify: bool = False, | |
| verbose: bool = False, | |
| chain_config_path: Optional[str] = None, | |
| adi_version: int = 6, | |
| cmd_list: Optional[List[MemCmd]] = None): | |
| self.bin_path = bin_path | |
| self.base_addr = base_addr | |
| self.output_path = output_path | |
| self.ap_sel = ap_sel | |
| self.data_width = data_width | |
| self.dp_idcode = dp_idcode | |
| self.verify = verify | |
| self.verbose = verbose | |
| self.chain_config_path = chain_config_path | |
| self.adi_version = adi_version | |
| self.cmd_list = cmd_list | |
| if data_width not in (8, 16, 32): | |
| raise ValueError("data_width must be 8, 16, or 32") | |
| if adi_version not in (5, 6): | |
| raise ValueError("adi_version must be 5 or 6") | |
| # Exactly one source must be provided | |
| has_bin = self.bin_path is not None | |
| has_cmd = self.cmd_list is not None | |
| if has_bin and has_cmd: | |
| raise ValueError( | |
| "Provide either a .bin file or --cmds, not both.") | |
| if not has_bin and not has_cmd: | |
| raise ValueError( | |
| "Provide either a .bin file or --cmds.") | |
| if has_bin and self.base_addr is None: | |
| raise ValueError("--addr is required for .bin file mode.") | |
| # ------------------------------------------------------------------ | |
| # CSW configuration | |
| # ------------------------------------------------------------------ | |
| def _csw_size_field(self) -> int: | |
| """Map data width to CSW Size[2:0] field.""" | |
| return {8: 0, 16: 1, 32: 2}[self.data_width] | |
| def _csw_value(self, auto_increment: bool = True) -> int: | |
| """Build the MEM-AP CSW register value. | |
| ADIv6 adds DBGSWENABLE (bit 31); ADIv5 reserves this bit so we | |
| omit it for v5 targets. | |
| Args: | |
| auto_increment: If True, use ADDRINC_SINGLE (suitable for | |
| sequential block writes). If False, use ADDRINC_OFF | |
| (suitable for random-access command files where TAR is | |
| set explicitly before each access). | |
| """ | |
| addrinc = (SvfGenerator.CSW_ADDRINC_SINGLE if auto_increment | |
| else SvfGenerator.CSW_ADDRINC_OFF) | |
| csw = (SvfGenerator.CSW_DEVICEEN | | |
| addrinc | | |
| SvfGenerator.CSW_HPROT_DATA | | |
| (self._csw_size_field())) | |
| if self.adi_version >= 6: | |
| csw |= SvfGenerator.CSW_DBGSWENABLE | |
| return csw | |
| # ------------------------------------------------------------------ | |
| # Shared setup (JTAG reset, power-up, AP selection, CSW) | |
| # ------------------------------------------------------------------ | |
| def _emit_setup_phases(self, g: SvfGenerator, csw: int, csw_desc: str): | |
| """Emit SVF phases 1–4 (reset, power-up, AP select, CSW config).""" | |
| # Phase 1 — Reset & IDCODE | |
| g.comment("=" * 70) | |
| g.comment("PHASE 1: JTAG Reset & Device Identification") | |
| g.comment("=" * 70) | |
| g.blank() | |
| g.runtest(10) | |
| g.jtag_idcode() | |
| g.runtest(10) | |
| # Phase 2 — Power-Up | |
| g.comment("=" * 70) | |
| g.comment("PHASE 2: Power-Up the Debug & System Domains") | |
| g.comment("=" * 70) | |
| g.blank() | |
| ctrl_stat_write = (SvfGenerator.CDBGPWRUPREQ | | |
| SvfGenerator.CSYSPWRUPREQ | | |
| SvfGenerator.TRNCNT_VAL) | |
| g.dp_write(SvfGenerator.DP_CTRL_STAT, ctrl_stat_write, | |
| "Request CDBGPWRUP + CSYSPWRUP, TRNCNT=512") | |
| g.runtest(50) | |
| g.dp_read(SvfGenerator.DP_CTRL_STAT, | |
| "Read CTRL/STAT (result in next transaction)") | |
| g.dp_read(SvfGenerator.DP_RDBUFF, | |
| "Read RDBUFF — TDO = CTRL/STAT value; check ACK bits") | |
| g.runtest(10) | |
| # Phase 3 — Select MEM-AP | |
| g.comment("=" * 70) | |
| g.comment("PHASE 3: Select MEM-AP via DP.SELECT") | |
| g.comment("=" * 70) | |
| g.blank() | |
| select_val = (self.ap_sel & 0xFF) << 24 | |
| g.dp_write(SvfGenerator.DP_SELECT, select_val, | |
| f"SELECT APSEL={self.ap_sel}, APBANKSEL=0") | |
| g.runtest(10) | |
| # Phase 4 — Configure CSW | |
| g.comment("=" * 70) | |
| g.comment("PHASE 4: Configure MEM-AP (CSW)") | |
| g.comment("=" * 70) | |
| g.blank() | |
| g.ap_write(SvfGenerator.AP_CSW, csw, csw_desc) | |
| g.runtest(10) | |
| # ------------------------------------------------------------------ | |
| # Main generation | |
| # ------------------------------------------------------------------ | |
| def generate(self): | |
| """Read the binary file (or command list) and write the SVF sequence.""" | |
| if self.cmd_list is not None: | |
| return self._generate_from_cmds() | |
| return self._generate_from_bin() | |
| def _generate_from_bin(self): | |
| """Original binary-download generation path.""" | |
| # --- Read & pad binary ------------------------------------------------- | |
| with open(self.bin_path, "rb") as fh: | |
| blob = fh.read() | |
| word_bytes = self.data_width // 8 | |
| remainder = len(blob) % word_bytes | |
| if remainder: | |
| pad = word_bytes - remainder | |
| blob += b'\x00' * pad | |
| if self.verbose: | |
| print(f"[INFO] Padded binary with {pad} zero-bytes to " | |
| f"{word_bytes}-byte alignment", file=sys.stderr) | |
| # Unpack into words | |
| if self.data_width == 32: | |
| fmt = f"<{len(blob) // 4}I" | |
| elif self.data_width == 16: | |
| fmt = f"<{len(blob) // 2}H" | |
| else: | |
| fmt = f"<{len(blob)}B" | |
| words = list(struct.unpack(fmt, blob)) | |
| # --- Build chain configuration ----------------------------------------- | |
| chain = None | |
| if self.chain_config_path: | |
| chain = JtagChainConfig.from_json_file(self.chain_config_path) | |
| if self.verbose: | |
| print(f"[INFO] JTAG chain config: {self.chain_config_path}", | |
| file=sys.stderr) | |
| print(f"[INFO] {chain.summary}", file=sys.stderr) | |
| gen = SvfGenerator(self.output_path, chain=chain) | |
| g = gen # shorthand | |
| # --- Shared setup phases 1–4 ------------------------------------------- | |
| csw = self._csw_value(auto_increment=True) | |
| size_name = {8: "8-bit", 16: "16-bit", 32: "32-bit"}[self.data_width] | |
| self._emit_setup_phases(g, csw, | |
| f"CSW: {size_name}, auto-increment, DeviceEn, DbgSwEnable") | |
| # ================================================================== | |
| # PHASE 5 — Write Binary Data to Memory | |
| # ================================================================== | |
| g.comment("=" * 70) | |
| g.comment(f"PHASE 5: Write {len(blob)} bytes to 0x{self.base_addr:08X}") | |
| g.comment(f" Data width: {self.data_width}-bit, " | |
| f"Words: {len(words)}") | |
| g.comment(" Mode: auto-increment (TAR set once)") | |
| g.comment("=" * 70) | |
| g.blank() | |
| word_bytes = self.data_width // 8 | |
| # TAR auto-increments after each DRW access — set it once | |
| g.ap_write(SvfGenerator.AP_TAR, self.base_addr, | |
| f"TAR ← 0x{self.base_addr:08X} (auto-increment mode)") | |
| for idx, word in enumerate(words): | |
| g.ap_write(SvfGenerator.AP_DRW, word, | |
| f"DRW ← 0x{word:0{self.data_width // 4}X} " | |
| f"→ 0x{self.base_addr + idx * word_bytes:08X} " | |
| f"[{idx}/{len(words) - 1}]") | |
| # Periodic progress annotations | |
| chunk = max(len(words) // 20, 1) | |
| if idx > 0 and idx % chunk == 0: | |
| pct = idx * 100 // len(words) | |
| g.comment(f" ... {pct}% complete ({idx}/{len(words)} words)") | |
| g.comment(f" ✓ Write complete — {len(words)} words, " | |
| f"{len(blob)} bytes total") | |
| g.blank() | |
| # ================================================================== | |
| # PHASE 6 — Verification (optional) | |
| # ================================================================== | |
| if self.verify: | |
| TDO_MASK = (0xFFFFFFFF << 3) | self.ACK_MASK | |
| g.comment("=" * 70) | |
| g.comment("PHASE 6: Read-Back Verification (with TDO check)") | |
| g.comment(f" Protocol: ADIv{self.adi_version} " | |
| f"(ACK OK = 0b{self.ACK_OK:03b}, mask = 0b{self.ACK_MASK:03b})") | |
| g.comment(" Each SDR includes TDO(expected) MASK — the SVF player") | |
| g.comment(" compares actual TDO against expected. Both DATA and ACK") | |
| g.comment(" bits are verified. Mismatches are reported immediately.") | |
| g.comment("=" * 70) | |
| g.blank() | |
| # TAR auto-increments after each DRW read — set it once | |
| g.ap_write(SvfGenerator.AP_TAR, self.base_addr, | |
| f"TAR ← 0x{self.base_addr:08X} (auto-increment mode, verify)") | |
| for idx, expected_word in enumerate(words): | |
| addr = self.base_addr + idx * word_bytes | |
| # AP read DRW — request; TDO here is the *previous* pipelined | |
| # result (stale), so we do NOT verify TDO on this transaction. | |
| g.ap_read(SvfGenerator.AP_DRW, | |
| f"Read DRW → 0x{addr:08X} (request, TDO=stale)") | |
| # DP read RDBUFF — TDO contains the DRW data from the AP read | |
| # above. Verify both data and ACK bits. | |
| tdo_expected = (expected_word << 3) | self.ACK_OK | |
| g.dp_read(SvfGenerator.DP_RDBUFF, | |
| f"RDBUFF TDO ?= 0x{expected_word:08X} (+ACK=OK) " | |
| f"[{idx}/{len(words) - 1}]", | |
| tdo_expected=tdo_expected, | |
| tdo_mask=TDO_MASK) | |
| # Periodic progress annotations | |
| vchunk = max(len(words) // 20, 1) | |
| if idx > 0 and idx % vchunk == 0: | |
| pct = idx * 100 // len(words) | |
| g.comment(f" ... {pct}% verified ({idx}/{len(words)} words)") | |
| g.comment(" ✓ Verification sequence complete") | |
| # ================================================================== | |
| # PHASE 7 — Clean Shutdown | |
| # ================================================================== | |
| self._emit_shutdown(g) | |
| g.close() | |
| return len(blob) | |
| # ------------------------------------------------------------------ | |
| # Command-file generation | |
| # ------------------------------------------------------------------ | |
| def _generate_from_cmds(self): | |
| """Generate SVF from a parsed list of MemCmd entries.""" | |
| if self.verbose: | |
| reads = sum(1 for c in self.cmd_list if c.is_read) | |
| writes = sum(1 for c in self.cmd_list if not c.is_read) | |
| print(f"[INFO] Command file: {writes} writes, {reads} reads " | |
| f"({len(self.cmd_list)} total)", file=sys.stderr) | |
| # --- Build chain configuration ----------------------------------------- | |
| chain = None | |
| if self.chain_config_path: | |
| chain = JtagChainConfig.from_json_file(self.chain_config_path) | |
| if self.verbose: | |
| print(f"[INFO] JTAG chain config: {self.chain_config_path}", | |
| file=sys.stderr) | |
| print(f"[INFO] {chain.summary}", file=sys.stderr) | |
| gen = SvfGenerator(self.output_path, chain=chain) | |
| g = gen # shorthand | |
| # --- Shared setup phases 1–4 ------------------------------------------- | |
| size_name = {8: "8-bit", 16: "16-bit", 32: "32-bit"}[self.data_width] | |
| csw = self._csw_value(auto_increment=False) | |
| self._emit_setup_phases(g, csw, | |
| f"CSW: {size_name}, ADDRINC_OFF, DeviceEn, DbgSwEnable") | |
| # ================================================================== | |
| # PHASE 5 — Execute Memory Commands | |
| # ================================================================== | |
| g.comment("=" * 70) | |
| g.comment(f"PHASE 5: Execute {len(self.cmd_list)} memory commands") | |
| g.comment(f" Data width: {self.data_width}-bit") | |
| g.comment(" Mode: explicit TAR per access (ADDRINC_OFF)") | |
| g.comment("=" * 70) | |
| g.blank() | |
| TDO_MASK = (0xFFFFFFFF << 3) | self.ACK_MASK | |
| # Adjust TDO_MASK for non-32-bit widths | |
| if self.data_width == 16: | |
| TDO_MASK = (0xFFFF << 3) | self.ACK_MASK | |
| elif self.data_width == 8: | |
| TDO_MASK = (0xFF << 3) | self.ACK_MASK | |
| for idx, cmd in enumerate(self.cmd_list): | |
| label = f"[{idx + 1}/{len(self.cmd_list)}]" | |
| if cmd.is_read: | |
| # --- Read operation --- | |
| g.ap_write(SvfGenerator.AP_TAR, cmd.addr, | |
| f"{label} TAR ← 0x{cmd.addr:08X}") | |
| # Initiate AP DRW read | |
| g.ap_read(SvfGenerator.AP_DRW, | |
| f"{label} Read DRW from 0x{cmd.addr:08X} " | |
| f"(TDO=stale)") | |
| if cmd.verify: | |
| # Verify via pipelined RDBUFF read | |
| tdo_expected = (cmd.data << 3) | self.ACK_OK | |
| g.dp_read(SvfGenerator.DP_RDBUFF, | |
| f"{label} RDBUFF TDO ?= 0x{cmd.data:0{self.data_width // 4}X} " | |
| f"(+ACK=OK)", | |
| tdo_expected=tdo_expected, | |
| tdo_mask=TDO_MASK) | |
| else: | |
| # Read without verification | |
| g.dp_read(SvfGenerator.DP_RDBUFF, | |
| f"{label} RDBUFF (TDO not checked)") | |
| else: | |
| # --- Write operation --- | |
| g.ap_write(SvfGenerator.AP_TAR, cmd.addr, | |
| f"{label} TAR ← 0x{cmd.addr:08X}") | |
| g.ap_write(SvfGenerator.AP_DRW, cmd.data, | |
| f"{label} DRW ← 0x{cmd.data:0{self.data_width // 4}X} " | |
| f"→ 0x{cmd.addr:08X}") | |
| # Periodic progress | |
| chunk = max(len(self.cmd_list) // 20, 1) | |
| if (idx + 1) > 0 and (idx + 1) % chunk == 0: | |
| pct = (idx + 1) * 100 // len(self.cmd_list) | |
| g.comment(f" ... {pct}% complete " | |
| f"({idx + 1}/{len(self.cmd_list)} commands)") | |
| g.comment(f" ✓ All {len(self.cmd_list)} commands executed") | |
| g.blank() | |
| # ================================================================== | |
| # PHASE 6 — Clean Shutdown | |
| # ================================================================== | |
| self._emit_shutdown(g) | |
| g.close() | |
| return len(self.cmd_list) | |
| # ------------------------------------------------------------------ | |
| # Shared shutdown | |
| # ------------------------------------------------------------------ | |
| @staticmethod | |
| def _emit_shutdown(g: SvfGenerator): | |
| """Emit the final JTAG reset sequence.""" | |
| g.comment("=" * 70) | |
| g.comment("PHASE 7: JTAG Reset (clean shutdown)") | |
| g.comment("=" * 70) | |
| g.blank() | |
| g.runtest(10) | |
| g._emit("STATE RESET IDLE;") | |
| g.blank() | |
| g.comment("End of SVF — Total TCK cycles include all operations above.") | |
| # ============================================================================= | |
| # CLI Entry Point | |
| # ============================================================================= | |
| def _parse_address(s: str) -> int: | |
| """Parse a hex address string like '0x80000000' or '80000000'.""" | |
| try: | |
| return int(s, 0) | |
| except ValueError: | |
| raise argparse.ArgumentTypeError( | |
| f"Invalid address '{s}'. Use hex (e.g., 0x80000000).") | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description=( | |
| "Generate SVF JTAG sequences for ARM Cortex-R52 memory access " | |
| "via CoreSight DAP. Supports .bin file download or text-based " | |
| "command files with per-access read/write control." | |
| ), | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=textwrap.dedent("""\ | |
| Examples (binary download) | |
| -------------------------- | |
| %(prog)s firmware.bin --addr 0x80000000 -o download.svf | |
| %(prog)s app.bin -a 0x20000000 --ap 1 -o flash.svf | |
| %(prog)s data.bin -a 0x08000000 --width 16 --verify | |
| %(prog)s fw.bin -a 0x80000000 --chain chain.json -o dl.svf | |
| %(prog)s fw.bin -a 0x00100000 -A 5 --verify # ADIv5 (Cortex-A9) | |
| Examples (command file) | |
| ----------------------- | |
| %(prog)s --cmds ops.txt -o ops.svf | |
| %(prog)s --cmds ops.txt -w 16 -o ops.svf | |
| %(prog)s --cmds ops.txt --chain chain.json -o ops.svf | |
| Command File Format | |
| ------------------- | |
| Each non-empty, non-comment line has 3 or 4 whitespace-separated | |
| fields: | |
| <addr_hex> <data_hex> <W|R> [Y] | |
| - addr_hex: memory address in hex (e.g. 0x80000000) | |
| - data_hex: data to write, or expected data on read | |
| - W | R: write or read operation | |
| - Y: (optional, read-only) verify TDO against data_hex | |
| Lines starting with # are comments. | |
| Chain Config Format (JSON) | |
| -------------------------- | |
| { | |
| "taps": [ | |
| {"name": "ICE", "irlen": 4}, | |
| {"name": "DAP", "irlen": 4}, | |
| {"name": "FPGA", "irlen": 6} | |
| ], | |
| "target": "DAP" | |
| } | |
| Reference | |
| --------- | |
| ARM IHI0031H — ARM Debug Interface Architecture Specification | |
| (ADIv6). Cortex-R52 Technical Reference Manual. | |
| """), | |
| ) | |
| # ---- Source selection (mutually exclusive) -------------------------------- | |
| parser.add_argument( | |
| "binfile", | |
| nargs="?", | |
| default=None, | |
| help="Path to the .bin (raw binary) file to download.", | |
| ) | |
| parser.add_argument( | |
| "--cmds", "-C", | |
| default=None, | |
| metavar="CMDS.txt", | |
| help=( | |
| "Path to a text file listing memory read/write commands. " | |
| "Each line: <addr_hex> <data_hex> <W|R> [Y]. " | |
| "Mutually exclusive with the positional .bin file argument." | |
| ), | |
| ) | |
| # ---- Address (required only for .bin mode) -------------------------------- | |
| parser.add_argument( | |
| "--addr", "-a", | |
| default=None, | |
| type=_parse_address, | |
| help="Target memory base address in hex (e.g., 0x80000000). " | |
| "Required for .bin file mode, ignored for --cmds mode.", | |
| ) | |
| # ---- Optional I/O --------------------------------------------------------- | |
| parser.add_argument( | |
| "--output", "-o", | |
| default=None, | |
| help="Output SVF file path. Default: stdout.", | |
| ) | |
| parser.add_argument( | |
| "--verbose", "-V", | |
| action="store_true", | |
| help="Print progress information to stderr.", | |
| ) | |
| # ---- DAP configuration ---------------------------------------------------- | |
| parser.add_argument( | |
| "--ap", "-p", | |
| type=int, | |
| default=0, | |
| help="MEM-AP selection index (APSEL, default: 0).", | |
| ) | |
| parser.add_argument( | |
| "--idcode", | |
| type=_parse_address, | |
| default=None, | |
| help="Expected DP IDCODE for verification (e.g., 0x6BA02477).", | |
| ) | |
| parser.add_argument( | |
| "--width", "-w", | |
| type=int, | |
| choices=[8, 16, 32], | |
| default=32, | |
| help="Memory access width in bits (8, 16, or 32). Default: 32.", | |
| ) | |
| parser.add_argument( | |
| "--adi-version", "-A", | |
| type=int, | |
| choices=[5, 6], | |
| default=6, | |
| help=( | |
| "ARM Debug Interface version: 5 or 6. Controls CSW bit 31 " | |
| "(DBGSWENABLE, ADIv6 only). Default: 6 (Cortex-R52). " | |
| "Use 5 for Cortex-A9, Cortex-M4, etc." | |
| ), | |
| ) | |
| # ---- JTAG chain ----------------------------------------------------------- | |
| parser.add_argument( | |
| "--chain", "-c", | |
| default=None, | |
| metavar="CONFIG.json", | |
| help=( | |
| "Path to a JSON file describing the JTAG daisy-chain topology. " | |
| "When provided, all SIR/SDR operations are extended to place " | |
| "non-target TAPs in BYPASS. See README for the config format." | |
| ), | |
| ) | |
| # ---- Features ------------------------------------------------------------- | |
| parser.add_argument( | |
| "--verify", "-v", | |
| action="store_true", | |
| help="Include read-back verification sequence in the SVF output " | |
| "(.bin mode only; for --cmds mode use the per-command Y flag).", | |
| ) | |
| args = parser.parse_args() | |
| # ---- Determine source mode ------------------------------------------------ | |
| has_bin = args.binfile is not None | |
| has_cmd = args.cmds is not None | |
| if has_bin and has_cmd: | |
| print("Error: provide either a .bin file or --cmds, not both.", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| if not has_bin and not has_cmd: | |
| print("Error: provide either a .bin file or --cmds.", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| # ---- Validate .bin mode --------------------------------------------------- | |
| file_size = 0 | |
| cmd_list = None | |
| if has_bin: | |
| if not os.path.isfile(args.binfile): | |
| print(f"Error: binary file not found — {args.binfile}", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| file_size = os.path.getsize(args.binfile) | |
| if file_size == 0: | |
| print("Error: binary file is empty.", file=sys.stderr) | |
| sys.exit(1) | |
| if args.addr is None: | |
| print("Error: --addr is required for .bin file mode.", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| # ---- Validate --cmds mode ------------------------------------------------- | |
| if has_cmd: | |
| if not os.path.isfile(args.cmds): | |
| print(f"Error: command file not found — {args.cmds}", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| try: | |
| cmd_list = parse_cmd_file(args.cmds, data_width=args.width) | |
| except ValueError as exc: | |
| print(f"Error parsing command file: {exc}", file=sys.stderr) | |
| sys.exit(1) | |
| if not cmd_list: | |
| print("Error: command file contains no valid commands.", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| if args.verify: | |
| print("[WARNING] --verify is ignored in --cmds mode; " | |
| "use the per-command Y flag for read verification.", | |
| file=sys.stderr) | |
| # ---- Validate chain config ------------------------------------------------ | |
| if args.chain and not os.path.isfile(args.chain): | |
| print(f"Error: chain config file not found — {args.chain}", | |
| file=sys.stderr) | |
| sys.exit(1) | |
| if args.verbose: | |
| if has_bin: | |
| print("[INFO] Mode : .bin download", | |
| file=sys.stderr) | |
| print(f"[INFO] Input file : {args.binfile} ({file_size} bytes)", | |
| file=sys.stderr) | |
| print(f"[INFO] Target addr : 0x{args.addr:08X}", file=sys.stderr) | |
| else: | |
| print("[INFO] Mode : command file", | |
| file=sys.stderr) | |
| print(f"[INFO] Command file : {args.cmds} " | |
| f"({len(cmd_list)} commands)", file=sys.stderr) | |
| print(f"[INFO] ADI version : v{args.adi_version}", file=sys.stderr) | |
| print(f"[INFO] Data width : {args.width}-bit", file=sys.stderr) | |
| print(f"[INFO] AP selection : APSEL={args.ap}", file=sys.stderr) | |
| print(f"[INFO] JTAG chain : {args.chain or '(none — single TAP)'}", | |
| file=sys.stderr) | |
| print(f"[INFO] Verify : {'Yes' if args.verify else 'No'}", | |
| file=sys.stderr) | |
| print(f"[INFO] Output : {args.output or '(stdout)'}", | |
| file=sys.stderr) | |
| # ---- Generate ------------------------------------------------------------- | |
| builder = CortexR52SvfBuilder( | |
| bin_path=args.binfile, | |
| base_addr=args.addr, | |
| output_path=args.output, | |
| ap_sel=args.ap, | |
| data_width=args.width, | |
| dp_idcode=args.idcode, | |
| verify=args.verify, | |
| verbose=args.verbose, | |
| chain_config_path=args.chain, | |
| adi_version=args.adi_version, | |
| cmd_list=cmd_list, | |
| ) | |
| result = builder.generate() | |
| if args.verbose: | |
| if has_bin: | |
| print(f"[INFO] SVF generation complete — {result} bytes payload.", | |
| file=sys.stderr) | |
| else: | |
| print(f"[INFO] SVF generation complete — {result} commands.", | |
| file=sys.stderr) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment