]> ruderich.org/simon Gitweb - svdpoke/svdpoke.git/commitdiff
First working version
authorSimon Ruderich <simon@ruderich.org>
Fri, 3 Oct 2025 07:30:39 +0000 (09:30 +0200)
committerSimon Ruderich <simon@ruderich.org>
Fri, 3 Oct 2025 07:30:39 +0000 (09:30 +0200)
.gitmodules [new file with mode: 0644]
cmsis-svd [new submodule]
cmsis_svd [new symlink]
svdpoke.py [new file with mode: 0755]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..818426b
--- /dev/null
@@ -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 (submodule)
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 (symlink)
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 (executable)
index 0000000..ae2051d
--- /dev/null
@@ -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)')