This repository has been archived on 2026-08-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ciciec2026/tools/extract_cbor_uart.py
FallenSigh 20611157c3 tools: add extract_cbor_uart.py — extract UART output from CBOR trace files
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 <file.cbor>          # full UART output
  python3 tools/extract_cbor_uart.py <file.cbor> -s       # timing summary
  python3 tools/extract_cbor_uart.py <file.cbor> -o out   # save to file
2026-06-26 17:40:20 +08:00

158 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
extract_cbor_uart.py - Extract UART serial output from CBOR hardware trace files.
Usage:
python3 tools/extract_cbor_uart.py <file.cbor> # print to stdout
python3 tools/extract_cbor_uart.py <file.cbor> -o out.txt # save to file
python3 tools/extract_cbor_uart.py <file.cbor> -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()