From 4ea8776f849ca5b9cc561802146b3b625f632f64 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Fri, 3 Oct 2025 09:30:39 +0200 Subject: [PATCH] First working version --- .gitmodules | 3 + cmsis-svd | 1 + cmsis_svd | 1 + svdpoke.py | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 .gitmodules create mode 160000 cmsis-svd create mode 120000 cmsis_svd create mode 100755 svdpoke.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..818426b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cmsis-svd"] + path = cmsis-svd + url = https://github.com/cmsis-svd/cmsis-svd.git diff --git a/cmsis-svd b/cmsis-svd new file mode 160000 index 0000000..e1f3120 --- /dev/null +++ b/cmsis-svd @@ -0,0 +1 @@ +Subproject commit e1f3120b99ed9cdfe927d15b9732cc9dfbac3099 diff --git a/cmsis_svd b/cmsis_svd new file mode 120000 index 0000000..9aa80f6 --- /dev/null +++ b/cmsis_svd @@ -0,0 +1 @@ +cmsis-svd/python/cmsis_svd \ No newline at end of file diff --git a/svdpoke.py b/svdpoke.py new file mode 100755 index 0000000..ae2051d --- /dev/null +++ b/svdpoke.py @@ -0,0 +1,192 @@ +#!/usr/bin/python3 + +# svdpoke uses SVD files to display MCU register values and their meanings, +# including the current value if given. It can also be used in combination +# with openocd to connect to a MCU and read the register values directly from +# the connected device. + +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2025 Simon Ruderich + +import argparse +import functools +import os +import re +import socket +import sys + +import cmsis_svd + + +def parse_svd(path): + device = cmsis_svd.SVDParser.for_xml_file(path).get_device() + peripherals = [] + register_map = dict() + register_peripheral_map = dict() + for p in device.peripherals: + peripherals.append(p) + xs = [] + for r in p.get_registers(): + register_map[f'{p.name}_{r.name}'] = r + xs.append(r) + register_peripheral_map[p.name] = xs + return peripherals, register_map, register_peripheral_map + +def clean_description(desc): + # Remove newlines and multiple spaces in descriptions which somehow are + # present in some SVD files + return re.sub(r'\s+', ' ', desc) + +def print_peripherals(peripherals): + width = 0 + for p in peripherals: + if width < len(p.name): + width = len(p.name) + for p in peripherals: + desc = clean_description(p.description) + print(f'{p.name:{width}} @ 0x{p.base_address:08X}: {desc}') + +def print_register(register, register_value): + parent = register.parent + base = parent.base_address + offset = register.address_offset + addr = base + offset + print(f'{parent.name}_{register.name} @ 0x{addr:08X} (0x{base:08X} + 0x{offset:X}):') + + data = [] + for f in register.get_fields(): + if f.bit_width == 1: + bits = f'{f.bit_offset}' + name = f'{f.name}' + else: + bits = f'{f.bit_offset+f.bit_width-1}:{f.bit_offset}' + name = f'{f.name}[{f.bit_width-1}:0]' + if register_value is None: + value_bin = None + value_hex = None + value_dec = None + else: + mask = (1 << f.bit_width) - 1 + x = (register_value >> f.bit_offset) & mask + value_bin = f'0b{x:0{f.bit_width}b}' + value_hex = f'0x{x:X}' + value_dec = f'{x:d}' + if f.access is cmsis_svd.model.SVDAccessType.READ_ONLY: + access = 'r' + elif f.access is cmsis_svd.model.SVDAccessType.WRITE_ONLY: + access = 'w' + elif f.access is cmsis_svd.model.SVDAccessType.READ_WRITE: + access = 'rw' + elif f.access is cmsis_svd.model.SVDAccessType.WRITE_ONCE: + access = 'wo' + elif f.access is cmsis_svd.model.READ_WRITE_ONCE: + access = 'rwo' + else: + access = '?' + desc = clean_description(f.description) + data.append([bits, name, value_bin, value_hex, value_dec, access, desc]) + + # Get maximum width per column + widths = [0 for x in data[0]] + for xs in data: + for i, x in enumerate(xs): + if x is None: + continue + if widths[i] < len(x): + widths[i] = len(x) + w_bits, w_name, w_value_bin, w_value_hex, w_value_dec, w_access, _ = widths + + for xs in data: + bits, name, value_bin, value_hex, value_dec, access, desc = xs + if value_bin is None: + value = '' + else: + value = f' = {value_bin:>{w_value_bin}} {value_hex:>{w_value_hex}} {value_dec:>{w_value_dec}}' + print(f' {bits:>{w_bits}} {name:{w_name}}{value} {access:{w_access}} {desc}') + +def openocd_connect(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(('localhost', 4444)) + s.settimeout(10.0) # in seconds + # Wait for first prompt + data = b'' + while True: + data += s.recv(4096) + if data.endswith(b'\n\r> '): + break + return s + +def openocd_read_memory(socket, addr): + socket.send(f'mdw 0x{addr:x}\n'.encode('ascii')) + + data = b'' + while True: + data += socket.recv(4096) + if data.endswith(b'\n\r> '): # wait for prompt + match = re.search(b'\x000x[0-9a-f]+: ([0-9a-f]+) \r\n', data) + if match is None: + sys.exit(f'Unexpected output from openocd: {data} ' + + '(see openocd log for details)') + return int(match.group(1), 16) + +def openocd_read_register(socket, register): + addr = register.parent.base_address + register.address_offset + return openocd_read_memory(socket, addr) + + +def integer(x): + return int(x, base=0) + +svd_env = 'SVDPOKE_SVD' +parser = argparse.ArgumentParser() +parser.add_argument('-s', '--svd', help='path to SVD file ' + + f'(defaults to {svd_env} environment variable)') +parser.add_argument('-v', '--value', help='set register value and show bits', + type=integer) +parser.add_argument('-o', '--openocd', help='read register value via openocd', + action='store_true') +parser.add_argument('register', nargs='*', help='register or peripheral name') +args = parser.parse_args() + +if args.svd is None: + if svd_env not in os.environ: + sys.exit(f'Either --svd or {svd_env} must be specified, see --help') + args.svd = os.environ[svd_env] + +if args.value is not None and args.openocd: + sys.exit(f'--value conflicts with --openocd') + + +peripherals, register_map, register_peripheral_map = parse_svd(args.svd) + +# No arguments, print all peripherals +if len(args.register) == 0: + print_peripherals(peripherals) + +else: + if args.openocd: + s = openocd_connect() + + for name in args.register: + # Full register name (i.e. RCC_ICSCR): show this register (with value) + if name in register_map: + register = register_map[name] + if args.openocd: + value = openocd_read_register(s, register) + else: + value = args.value + print_register(register, value) + print() + # Peripheral name only (i.e. RCC): show all registers (ignore value) + elif name in register_peripheral_map: + for register in register_peripheral_map[name]: + if args.openocd: + value = openocd_read_register(s, register) + else: + value = None + print_register(register, value) + print() + else: + sys.exit(f'Register {name} not found; ' + 'must be either a full name (i.e. RCC_ICSCR) ' + 'or a peripheral (i.e. RCC)') -- 2.49.1