Files
ku5p_ccsds122_datamover_uart/ccsds122_storage.srcs/scripts/uart_terminal.py

360 lines
13 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
UART Interactive Terminal CCSDS122 register & DDR access
Commands:
w <addr> <data> Write 32-bit register (e.g. w 0x80000000 0x01)
r <addr> Read 32-bit register (e.g. r 0x80000010)
aw <addr> <len> <file> Write DDR from binary file
aw <addr> hex:<hexstr> Write DDR from hex string (e.g. aw 0 hex:deadbeef)
ar <addr> <len> Read DDR, print hex dump
ar <addr> <len> <file> Read DDR, save to file
help Show this help
quit Exit
Protocol:
Reg Write: 'W' [4B addr BE] [4B data BE]
Reg Read: 'R' [4B addr BE] 4B response
DDR Write: 'A' [4B addr BE] [4B len BE] [data bytes]
DDR Read: 'D' [4B addr BE] [4B len BE] len bytes response
"""
import serial
import struct
import sys
import os
# ---- UART Command Primitives ------------------------------------------------
def send_byte_raw(ser, b):
"""Send a single raw byte (for escape/test)."""
ser.write(bytes([b]))
def reg_write(ser, addr, data):
"""AXI-Lite register write."""
cmd = b'W' + struct.pack('>I', addr) + struct.pack('>I', data)
ser.write(cmd)
print(f" W 0x{addr:08X} = 0x{data:08X} ({data})")
def reg_read(ser, addr):
"""AXI-Lite register read, returns 32-bit value."""
cmd = b'R' + struct.pack('>I', addr)
ser.write(cmd)
raw = ser.read(4)
if len(raw) < 4:
raise IOError(f"Reg read @ 0x{addr:08X}: timeout, got {len(raw)} bytes")
return struct.unpack('>I', raw)[0]
def ddr_write(ser, addr, data_bytes):
"""Write data to DDR at specific address via 'A' command."""
cmd = b'A' + struct.pack('>I', addr) + struct.pack('>I', len(data_bytes))
ser.write(cmd + data_bytes)
def ddr_read(ser, addr, length, timeout=10):
"""Read data from DDR via 'D' command, returns bytes.
内部自动补齐到 16B 对齐, 避免末拍不满导致字节排序错乱."""
aligned = ((length + 15) // 16) * 16
cmd = b'D' + struct.pack('>I', addr) + struct.pack('>I', aligned)
ser.write(cmd)
ser.timeout = min(timeout, max(10, aligned * 10 / 2000000 * 3))
raw = ser.read(aligned)
if len(raw) < length:
print(f" WARNING: expected {length} bytes, got {len(raw)}")
return raw[:length]
# ---- Hex Dump ---------------------------------------------------------------
def hexdump(data, addr=0, show_all=False):
"""Print hex dump of data bytes."""
if not show_all and len(data) > 256:
for i in range(0, 256, 16):
chunk = data[i:i+16]
hexstr = ' '.join(f'{b:02X}' for b in chunk)
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
print(f" {addr+i:08X} {hexstr:<48s} |{ascii_str}|")
print(f" ... ({len(data) - 256} more bytes)")
else:
for i in range(0, len(data), 16):
chunk = data[i:i+16]
hexstr = ' '.join(f'{b:02X}' for b in chunk)
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
print(f" {addr+i:08X} {hexstr:<48s} |{ascii_str}|")
# ---- Register Name Lookup ---------------------------------------------------
REG_NAMES = {
0x80000000: "CTRL",
0x80000004: "IMG_ADDR",
0x80000008: "IMG_LEN",
0x8000000C: "CMPR_ADDR",
0x80000010: "STATUS",
0x80000014: "CMPR_LEN",
0x80000018: "VERSION",
0x8000001C: "STORAGE_MODE",
0x80000020: "MEM_BLOCK_SZ",
0x80000024: "PAN_BYTES",
0x80000028: "B1_BYTES",
0x8000002C: "B2_BYTES",
0x80000030: "B3_BYTES",
0x80000034: "B4_BYTES",
0x80000038: "TOTAL_BYTES",
0x8000003C: "IMG_PER_REGION",
}
def reg_name(addr):
for base, name in REG_NAMES.items():
if addr == base:
return name
return ""
# ---- Command Parser ---------------------------------------------------------
def parse_int(s):
"""Parse hex (0x...) or decimal integer."""
s = s.strip()
if s.lower().startswith('0x'):
return int(s, 16)
return int(s)
def cmd_w(ser, parts):
"""w <addr> <data>"""
if len(parts) < 3:
print(" Usage: w <addr> <data>")
return
addr = parse_int(parts[1])
data = parse_int(parts[2])
name = reg_name(addr)
extra = f" ({name})" if name else ""
print(f" Write 0x{addr:08X}{extra}")
reg_write(ser, addr, data)
def cmd_r(ser, parts):
"""r <addr>"""
if len(parts) < 2:
print(" Usage: r <addr>")
return
addr = parse_int(parts[1])
name = reg_name(addr)
extra = f" ({name})" if name else ""
print(f" Read 0x{addr:08X}{extra}")
val = reg_read(ser, addr)
print(f" → 0x{val:08X} ({val})")
# Decode STATUS register
if addr == 0x80000010:
print(f" busy={val & 1}, done={(val>>1) & 1}")
elif addr == 0x80000000:
print(f" enable={val & 1}, bpp={(val>>1) & 7}")
def cmd_aw(ser, parts):
"""aw <addr> <len> <file> Write file to DDR at addr
aw <addr> <len> hex:<hexstr> Write hex string to DDR at addr
aw <addr> <len> --loop <rows> <stride> <file> Strided write from file"""
if len(parts) < 3:
print(" Usage: aw <addr> <len> <file>")
print(" aw <addr> <len> hex:<hexstr>")
print(" aw <addr> <len> --loop <rows> <stride> <file>")
return
addr = parse_int(parts[1])
length = parse_int(parts[2])
# ---- Loop mode: --loop <rows> <stride> <file> --------------------------
loop_mode = "--loop" in parts
if loop_mode:
loop_idx = parts.index("--loop")
loop_rows = int(parts[loop_idx+1])
loop_stride = int(parts[loop_idx+2])
fname_idx = loop_idx + 3
if len(parts) <= fname_idx:
print(" ERROR: missing file path for --loop mode")
return
fpath = os.path.expandvars(os.path.expanduser(parts[fname_idx]))
if not os.path.exists(fpath):
print(f" ERROR: file not found: {fpath}")
return
with open(fpath, 'rb') as f:
file_data = f.read()
total_written = 0
print(f" Write DDR 0x{addr:08X}, {length}B/row, {loop_rows} rows, stride={loop_stride}")
for row in range(loop_rows):
raddr = addr + row * loop_stride
src_start = row * length
src_end = src_start + length
if src_start >= len(file_data):
print(f" WARNING: file exhausted at row {row}")
break
chunk = file_data[src_start:src_end]
if len(chunk) < length:
chunk = chunk + bytes(length - len(chunk))
ddr_write(ser, raddr, chunk)
total_written += len(chunk)
if row % 64 == 0 or row == loop_rows - 1:
print(f" row {row:4d}/{loop_rows} @0x{raddr:08X} {len(chunk)}B")
print(f" Loop done: {loop_rows} rows, {total_written} bytes written")
return
# ---- Normal mode ---------------------------------------------------------
src = parts[2]
if src.lower().startswith('hex:'):
hexstr = src[4:].replace(' ', '')
data = bytes.fromhex(hexstr)
else:
fpath = os.path.expandvars(os.path.expanduser(src))
if not os.path.exists(fpath):
print(f" ERROR: file not found: {fpath}")
return
with open(fpath, 'rb') as f:
data = f.read()
print(f" Write DDR 0x{addr:08X}, {len(data)} bytes")
ddr_write(ser, addr, data)
print(f" Done")
def cmd_ar(ser, parts):
"""ar <addr> <len> [file] or ar <addr> <len> --loop <rows> <stride> [file_prefix]"""
if len(parts) < 3:
print(" Usage: ar <addr> <len> [file]")
print(" ar <addr> <len> --loop <rows> <stride> [file_prefix]")
return
addr = parse_int(parts[1])
length = parse_int(parts[2])
# ---- Loop mode: --loop <rows> <stride> ----------------------------------
loop_mode = "--loop" in parts
if loop_mode:
loop_idx = parts.index("--loop")
loop_rows = int(parts[loop_idx+1])
loop_stride = int(parts[loop_idx+2])
fname_idx = loop_idx + 3
fpath = parts[fname_idx] if len(parts) > fname_idx else None
all_data = bytearray()
total_read = 0
for row in range(loop_rows):
raddr = addr + row * loop_stride
data = ddr_read(ser, raddr, length, timeout=max(10, length * 10 / 2000000 * 4))
all_data.extend(data)
total_read += len(data)
if row % 64 == 0 or row == loop_rows - 1:
print(f" row {row:4d}/{loop_rows} @0x{raddr:08X} {len(data)}B")
if fpath:
fpath = os.path.expandvars(os.path.expanduser(fpath))
with open(fpath, 'wb') as f:
f.write(all_data)
print(f" Saved {total_read} bytes to {fpath}")
else:
hexdump(all_data, addr, show_all=(len(all_data) <= 1024))
print(f" Loop done: {loop_rows} rows, {total_read} bytes total")
return
# ---- Normal mode ---------------------------------------------------------
fpath = parts[3] if len(parts) > 3 else None
print(f" Read DDR 0x{addr:08X}, {length} bytes")
data = ddr_read(ser, addr, length, timeout=max(10, length * 10 / 2000000 * 4))
if fpath:
fpath = os.path.expandvars(os.path.expanduser(fpath))
with open(fpath, 'wb') as f:
f.write(data)
print(f" Saved {len(data)} bytes to {fpath}")
else:
hexdump(data, addr, show_all=(length <= 1024))
def cmd_help():
print("""Commands:
w <addr> <data> Write 32-bit register
r <addr> Read 32-bit register
aw <addr> <len> <file> Write DDR from binary file
aw <addr> <len> hex:<hexstr> Write DDR from hex string
ar <addr> <len> Read DDR, print hex dump
aw <addr> <len> --loop <rows> <stride> <file> Strided write
ar <addr> <len> --loop <rows> <stride> [file] Strided read
ar <addr> <len> <file> Read DDR, save to file
dump Show all registers
help Show this help
quit / exit Exit
Addresses:
0x00000000 - DDR (raw image data)
0x40000000 - DDR (compressed output)
0x80000000+ - axil_slave registers
Examples:
r 0x80000018 Read VERSION
aw 0x03000000 512 --loop 256 3072 b1.bin Strided write B1 region
w 0x80000000 0x01 Write CTRL = enable
ar 0x00000000 512 --loop 256 12288 pan.bin Strided read PAN region
r 0x80000010 Read STATUS
aw 0x00000000 test.bin Write test.bin to DDR addr 0
ar 0x40000000 1024 Read 1024 bytes from DDR
ar 0x40000000 4096 out.bin Read DDR, save to file
""")
def cmd_dump(ser):
"""Read all known registers."""
print(f"\n {'Addr':<12} {'Name':<16} {'Value':<12} {'Dec'}")
print(f" {'-'*12} {'-'*16} {'-'*12} {'-'*10}")
for addr, name in sorted(REG_NAMES.items()):
val = reg_read(ser, addr)
print(f" 0x{addr:08X} {name:<16s} 0x{val:08X} ({val})")
if addr == 0x80000010:
print(f" {'':>12s} {'':>16s} busy={val & 1}, done={(val>>1) & 1}")
elif addr == 0x80000000:
print(f" {'':>12s} {'':>16s} enable={val & 1}, bpp={(val>>1) & 7}")
# =============================================================================
def main():
import argparse
parser = argparse.ArgumentParser(description="CCSDS122 UART interactive terminal")
parser.add_argument("--port", default="COM3", help="Serial port")
parser.add_argument("--baud", type=int, default=2000000, help="Baud rate")
args = parser.parse_args()
print(f"Opening {args.port} @ {args.baud}...")
ser = serial.Serial(args.port, args.baud, timeout=10, write_timeout=10)
# Let FPGA UART RX stabilize
import time as _time
ser.rts = 0; _time.sleep(0.002); ser.rts = 1; _time.sleep(0.005)
print("Connected. Type 'help' for commands.\n")
try:
while True:
try:
line = input("uart> ").strip()
except (EOFError, KeyboardInterrupt):
print("\nquit")
break
if not line:
continue
parts = line.split()
cmd = parts[0].lower()
if cmd in ('quit', 'exit', 'q'):
break
elif cmd == 'help':
cmd_help()
elif cmd == 'w':
cmd_w(ser, parts)
elif cmd == 'r':
cmd_r(ser, parts)
elif cmd == 'aw':
cmd_aw(ser, parts)
elif cmd == 'ar':
cmd_ar(ser, parts)
elif cmd == 'dump':
cmd_dump(ser)
else:
print(f" Unknown command: {cmd}. Type 'help'.")
finally:
ser.close()
print("Closed.")
if __name__ == "__main__":
main()