Files
ps2000-my/tools/init_tbl_asm.py
T

281 lines
9.6 KiB
Python

#!/usr/bin/env python3
"""
Assemble PS2000 Init Script JSON to C include file.
Reconstructs the binary format expected by sub_280C0 parser.
The output is a C include file containing a byte array that can
be embedded in the library:
static const uint8_t _ps2000_init_script[] = {
#include "init_script_data.inc"
};
Usage:
python3 init_tbl_asm.py init_script.json > init_script_data.inc
python3 init_tbl_asm.py init_script.json --out init_script_data.inc
python3 init_tbl_asm.py init_script.json --var-name my_script
"""
import sys
import json
import struct
import argparse
# ==========================================================================
# IO COMMAND TABLE (reverse mapping: name -> opcode)
# ==========================================================================
IO_COMMANDS_REVERSE = {
"IO_COMMAND_READ_XDATA": 0x00,
"IO_COMMAND_WRITE_XDATA": 0x01,
"IO_COMMAND_READ_EXTENDED_REGISTER": 0x02,
"IO_COMMAND_WRITE_EXTENDED_REGISTER": 0x03,
"IO_COMMAND_READ_I2C": 0x04,
"IO_COMMAND_READ_I2C_WITH_STATUS": 0x05,
"IO_COMMAND_WRITE_I2C": 0x06,
"IO_COMMAND_WRITE_SPI": 0x07,
"IO_COMMAND_WRITE_ADC_SPI": 0x08,
"IO_COMMAND_READ_WRITE_SPI": 0x09,
"IO_COMMAND_WRITE_I2C_VIA_SPI": 0x0A,
"IO_COMMAND_READ_I2C_VIA_SPI": 0x0B,
"IO_COMMAND_READ_WRITE_ADC_SPI": 0x0C,
"IO_COMMAND_WAIT_FOR_EEPROM_WRITE": 0x0D,
"IO_COMMAND_WAIT_FOR_FX3_EEPROM_WRITE":0x0E,
"IO_COMMAND_READ_FPGA_REGISTER": 0x0F,
"IO_COMMAND_WRITE_FPGA_REGISTER": 0x10,
"IO_COMMAND_READ_WRITE_FPGA_REGISTER": 0x11,
"IO_COMMAND_READ_FIFO": 0x12,
"IO_COMMAND_ABORT_READ_FIFO": 0x13,
"IO_COMMAND_WRITE_FIFO": 0x14,
"IO_SET_AUTOIN_LEN": 0x15,
"IO_DELAY_MS": 0x16,
"IO_READ_EEPROM": 0x17,
"IO_WRITE_EEPROM": 0x18,
"IO_COMMAND_FX3_USE_GPIO": 0x19,
"IO_COMMAND_FX3_UNUSE_GPIO": 0x1A,
"IO_COMMAND_FX3_IS_GPIF_DONE": 0x1B,
"IO_COMMAND_FX3_GPIO_PWM": 0x1C,
"IO_COMMAND_USB_WRITE_GPIF_FREQ": 0x1D,
"IO_COMMAND_FX3_READ_REGISTER": 0x1E,
"IO_COMMAND_READ_FLASH_PAGE": 0x1F,
"USB_IO_WRITE_FLASH_PAGE": 0x20,
"USB_IO_ERASE_FLASH_PAGE": 0x21,
"USB_IO_SELECT_CSN_SPI": 0x22,
"IO_COMMAND_CHECK_EMPTY_EEPROM": 0x23,
"IO_COMMAND_CHECK_FLASH_AVAIL": 0x24,
"IO_COMMAND_PROTECT_FLASH": 0x25,
"IO_COMMAND_ERASE_FLASH_PAGE": 0x26,
"IO_COMMAND_PACKET": 0x27,
"FPGA_IO_COMMAND": 0x28,
"END": 0xFF,
}
# Commands with variable-length data (16-bit length prefix + data)
VARIABLE_COMMANDS = {
"IO_COMMAND_WRITE_FIFO",
"IO_WRITE_EEPROM",
"USB_IO_WRITE_FLASH_PAGE",
"IO_COMMAND_PACKET",
"FPGA_IO_COMMAND",
}
# Commands with no payload
NO_PAYLOAD_COMMANDS = {
"IO_COMMAND_WAIT_FOR_EEPROM_WRITE",
"IO_COMMAND_WAIT_FOR_FX3_EEPROM_WRITE",
"IO_COMMAND_ABORT_READ_FIFO",
"IO_COMMAND_FX3_IS_GPIF_DONE",
"IO_COMMAND_CHECK_EMPTY_EEPROM",
"IO_COMMAND_CHECK_FLASH_AVAIL",
"END",
}
# ==========================================================================
# ASSEMBLER
# ==========================================================================
def parse_hex_or_int(value, default=0):
"""Parse a value that may be hex string or integer."""
if value is None:
return default
if isinstance(value, int):
return value
if isinstance(value, str):
return int(value, 16) if value.startswith("0x") else int(value)
return default
def assemble(cmds: list) -> bytearray:
"""
Assemble command list into binary init script.
Args:
cmds: List of command dictionaries (from disassembler output)
Returns:
Binary script data
Raises:
ValueError: If an unknown opcode is encountered
"""
out = bytearray()
for i, cmd in enumerate(cmds):
# Skip entries with errors from disassembly
if "error" in cmd:
print(f"Warning: Skipping command {i} with error: {cmd['error']}",
file=sys.stderr)
continue
name = cmd.get("op", "")
if name not in IO_COMMANDS_REVERSE:
raise ValueError(f"Unknown opcode at index {i}: {name}")
op = IO_COMMANDS_REVERSE[name]
out.append(op)
# END marker terminates the script
if name == "END":
break
# Variable-length commands
if name in VARIABLE_COMMANDS:
data_hex = cmd.get("data_hex", "")
data_bytes = bytes.fromhex(data_hex) if data_hex else b""
# Check length fits in 16-bit prefix
if len(data_bytes) > 0xFFFF:
raise ValueError(
f"Command {i} ({name}): data too long ({len(data_bytes)} bytes)")
out.extend(struct.pack('<H', len(data_bytes)))
out.extend(data_bytes)
# No-payload commands
elif name in NO_PAYLOAD_COMMANDS:
pass
# Fixed-length payload commands
else:
addr = parse_hex_or_int(cmd.get("addr"), 0)
out.append(addr & 0xFF)
if "value" in cmd:
val = parse_hex_or_int(cmd["value"], 0)
out.extend(struct.pack('<H', val & 0xFFFF))
if "extra" in cmd:
extra = parse_hex_or_int(cmd["extra"], 0)
out.append(extra & 0xFF)
# Ensure script ends with END marker
if len(out) == 0 or out[-1] != 0xFF:
out.append(0xFF)
return out
# ==========================================================================
# C INCLUDE FORMATTER
# ==========================================================================
def format_c_include(data: bytearray,
var_name: str = "_ps2000_init_script",
bytes_per_line: int = 16) -> str:
"""
Format binary data as C include file.
Args:
data: Binary script data
var_name: C variable name for the array
bytes_per_line: Number of bytes per line
Returns:
C include file content
"""
lines = []
lines.append(f"/* Auto-generated PS2000 init script ({len(data)} bytes) */")
lines.append(f"/* Generated by init_tbl_asm.py */")
lines.append(f"/* Do not edit manually - regenerate from JSON source */")
lines.append("")
lines.append(f"static const uint8_t {var_name}[] = {{")
for i in range(0, len(data), bytes_per_line):
chunk = data[i:i+bytes_per_line]
hex_vals = ", ".join(f"0x{b:02X}" for b in chunk)
comma = "," if i + bytes_per_line < len(data) else ""
# Add offset comment for readability
offset_comment = f" /* 0x{i:04X} */"
lines.append(f" {hex_vals}{comma}{offset_comment}")
lines.append("};")
lines.append("")
lines.append(f"static const uint32_t {var_name}_len = {len(data)}U;")
lines.append("")
return "\n".join(lines)
# ==========================================================================
# MAIN
# ==========================================================================
def main():
parser = argparse.ArgumentParser(
description="Assemble PS2000 init script JSON to C include file",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s init_script.json
%(prog)s init_script.json --out init_script_data.inc
%(prog)s init_script.json --var-name my_custom_script
"""
)
parser.add_argument("input", help="Input JSON file")
parser.add_argument("--out", "-o", default="-",
help="Output C include file (default: stdout)")
parser.add_argument("--var-name", default="_ps2000_init_script",
help="C variable name for the script array")
parser.add_argument("--bytes-per-line", type=int, default=16,
help="Bytes per line in output (default: 16)")
args = parser.parse_args()
try:
with open(args.input) as f:
data = json.load(f)
except IOError as e:
print(f"Error reading file: {e}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
# Support both flat list and wrapped format from disassembler
if isinstance(data, dict) and "commands" in data:
cmds = data["commands"]
elif isinstance(data, list):
cmds = data
else:
print("Error: Invalid JSON format. Expected list or "
"object with 'commands' key.", file=sys.stderr)
sys.exit(1)
try:
binary = assemble(cmds)
except ValueError as e:
print(f"Assembly error: {e}", file=sys.stderr)
sys.exit(1)
output = format_c_include(binary, args.var_name, args.bytes_per_line)
if args.out == "-":
print(output)
else:
try:
with open(args.out, "w") as f:
f.write(output)
print(f"Written {len(binary)} bytes to {args.out}", file=sys.stderr)
except IOError as e:
print(f"Error writing file: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()