From 20611157c314dc83dff230bb460189f730495ed9 Mon Sep 17 00:00:00 2001 From: FallenSigh Date: Fri, 26 Jun 2026 17:40:20 +0800 Subject: [PATCH] =?UTF-8?q?tools:=20add=20extract=5Fcbor=5Fuart.py=20?= =?UTF-8?q?=E2=80=94=20extract=20UART=20output=20from=20CBOR=20trace=20fil?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parses CBOR hardware trace format, extracts UART serial output bytes, and optionally parses MATMUL timing markers + pipeline diagnostics. Usage: python3 tools/extract_cbor_uart.py # full UART output python3 tools/extract_cbor_uart.py -s # timing summary python3 tools/extract_cbor_uart.py -o out # save to file --- tools/extract_cbor_uart.py | 157 +++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100755 tools/extract_cbor_uart.py diff --git a/tools/extract_cbor_uart.py b/tools/extract_cbor_uart.py new file mode 100755 index 0000000..d4a3969 --- /dev/null +++ b/tools/extract_cbor_uart.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +""" +extract_cbor_uart.py - Extract UART serial output from CBOR hardware trace files. + +Usage: + python3 tools/extract_cbor_uart.py # print to stdout + python3 tools/extract_cbor_uart.py -o out.txt # save to file + python3 tools/extract_cbor_uart.py -s # summary only +""" + +import cbor2 +import sys +import argparse +import re +from pathlib import Path + + +def extract_uart_output(data, verbose=False): + """Extract UART text from CBOR trace events. + + The CBOR trace format contains events where UART output appears as + byte arrays within list records. We scan all byte payloads that + contain printable ASCII and concatenate them. + """ + output_parts = [] + total_bytes = 0 + uart_bytes = 0 + event_count = len(data) if isinstance(data, list) else 0 + + for i, item in enumerate(data): + if not isinstance(item, list): + continue + + for elem in item: + if not isinstance(elem, bytes): + continue + total_bytes += len(elem) + + # Filter: UART output is printable ASCII with CR/LF + # (as opposed to binary memory dumps) + try: + text = elem.decode('ascii') + # UART text has a high proportion of printable chars + printable = sum(1 for c in text if c.isprintable() or c in '\r\n\t') + ratio = printable / max(len(text), 1) + if ratio > 0.5 and len(text) <= 1024: + uart_bytes += len(elem) + if verbose: + print(f" Event[{i}] +{len(elem)} bytes: {text[:60].rstrip()}", + file=sys.stderr) + output_parts.append(text) + except (UnicodeDecodeError, ValueError): + pass # binary data, skip + + return ''.join(output_parts), uart_bytes, total_bytes, event_count + + +def parse_timing(text): + """Extract timing statistics from UART output.""" + patterns = { + 'crc32': r'MATMUL_CRC32=([0-9a-fA-F]+)', + 'prologue': r'TIME_PROLOGUE=(\d+)', + 'loop': r'TIME_LOOP=(\d+)', + 'loop_per': r'TIME_LOOP_PER=(\d+)', + 'epilogue': r'TIME_EPILOGUE=(\d+)', + 'total': r'TIME_TOTAL=(\d+)', + # Pipeline diagnostics (v2.1) + 'wait_ip': r'PIPE_WAIT_IP=(\d+)', + 'wait_ip_avg': r'PIPE_WAIT_IP_AVG=(\d+)', + 'wait_dma0': r'PIPE_WAIT_DMA0=(\d+)', + 'wait_dma0_avg': r'PIPE_WAIT_DMA0_AVG=(\d+)', + 'wait_dma1': r'PIPE_WAIT_DMA1=(\d+)', + 'wait_dma1_avg': r'PIPE_WAIT_DMA1_AVG=(\d+)', + 'fire_dma': r'PIPE_FIRE_DMA=(\d+)', + 'fire_ip': r'PIPE_FIRE_IP=(\d+)', + 'stall_ip': r'PIPE_STALL_IP=(\d+)', + 'stall_dma0': r'PIPE_STALL_DMA0=(\d+)', + 'stall_dma1': r'PIPE_STALL_DMA1=(\d+)', + 'sum_waits': r'PIPE_SUM_WAITS=(\d+)', + } + results = {} + for key, pattern in patterns.items(): + m = re.search(pattern, text) + if m: + val = m.group(1) + try: + results[key] = int(val, 16) if 'crc' in key else int(val) + except ValueError: + results[key] = val + return results + + +def main(): + parser = argparse.ArgumentParser( + description='Extract UART serial output from CBOR hardware trace files.') + parser.add_argument('file', type=Path, help='CBOR trace file (.cbor)') + parser.add_argument('-o', '--output', type=Path, + help='Write output to file (default: stdout)') + parser.add_argument('-s', '--summary', action='store_true', + help='Print timing summary only, not full UART text') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose event-by-event output to stderr') + args = parser.parse_args() + + if not args.file.exists(): + print(f"Error: file not found: {args.file}", file=sys.stderr) + sys.exit(1) + + # Load CBOR + with open(args.file, 'rb') as f: + data = cbor2.load(f) + + # Extract UART + uart_text, uart_bytes, total_bytes, event_count = extract_uart_output( + data, verbose=args.verbose) + + # Summary to stderr + print(f"File: {args.file.name}", file=sys.stderr) + print(f" Events: {event_count}, Total bytes: {total_bytes}, " + f"UART bytes: {uart_bytes}", file=sys.stderr) + + # Parse timing + timing = parse_timing(uart_text) + if timing: + print(f" MATMUL_CRC32: {timing.get('crc32', 'N/A')}", file=sys.stderr) + print(f" TIME_TOTAL: {timing.get('total', 'N/A')} ns " + f"({timing.get('total', 0)/1e6:.2f} ms)", file=sys.stderr) + if 'loop_per' in timing: + print(f" LOOP_PER: {timing.get('loop_per', 'N/A')} ns/iter", + file=sys.stderr) + + # Pipeline profile + pipe_keys = ['wait_ip', 'wait_dma0', 'wait_dma1', 'fire_dma', 'fire_ip', + 'stall_ip', 'stall_dma0', 'stall_dma1'] + pipe_data = {k: timing.get(k) for k in pipe_keys if k in timing} + if pipe_data: + print(f" --- Pipeline ---", file=sys.stderr) + for k in pipe_keys: + if k in timing: + label = k.replace('_', ' ').upper() + val = timing[k] + print(f" {label:20s} = {val}", file=sys.stderr) + + # Output + if args.summary: + print(f"\nSummary saved. Use without -s to see full UART output.", + file=sys.stderr) + elif args.output: + args.output.write_text(uart_text) + print(f"UART output saved to: {args.output} ({len(uart_text)} bytes)", + file=sys.stderr) + else: + sys.stdout.write(uart_text) + + +if __name__ == '__main__': + main()