#!/usr/bin/env python3 """ UART Interactive Terminal — CCSDS122 register & DDR access Commands: w Write 32-bit register (e.g. w 0x80000000 0x01) r Read 32-bit register (e.g. r 0x80000010) aw Write DDR from binary file aw hex: Write DDR from hex string (e.g. aw 0 hex:deadbeef) ar Read DDR, print hex dump ar 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 """ if len(parts) < 3: print(" Usage: w ") 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 """ if len(parts) < 2: print(" Usage: r ") 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 Write file to DDR at addr aw hex: Write hex string to DDR at addr aw --loop Strided write from file""" if len(parts) < 3: print(" Usage: aw ") print(" aw hex:") print(" aw --loop ") return addr = parse_int(parts[1]) length = parse_int(parts[2]) # ---- Loop mode: --loop -------------------------- 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 [file] or ar --loop [file_prefix]""" if len(parts) < 3: print(" Usage: ar [file]") print(" ar --loop [file_prefix]") return addr = parse_int(parts[1]) length = parse_int(parts[2]) # ---- Loop mode: --loop ---------------------------------- 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 Write 32-bit register r Read 32-bit register aw Write DDR from binary file aw hex: Write DDR from hex string ar Read DDR, print hex dump aw --loop Strided write ar --loop [file] Strided read ar 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()