#
# This file is part of LUNA.
#
# Copyright (c) 2023-2025 Great Scott Gadgets <info@greatscottgadgets.com>
# SPDX-License-Identifier: BSD-3-Clause
"""Generate a SVD file for SoC designs."""
import logging
import sys
from collections import defaultdict
from xml.dom import minidom
from xml.etree import ElementTree
from xml.etree.ElementTree import Element, SubElement, Comment, tostring
from amaranth_soc import csr
from amaranth_soc.memory import MemoryMap, ResourceInfo
from ..gateware.cpu.ic import InterruptMap
from . import introspect
[docs]
class SVD:
def __init__(self, memory_map: MemoryMap, interrupts: InterruptMap):
self.interrupts = interrupts
self.csr_base = introspect.csr_base(memory_map)
self.csr_peripherals = introspect.csr_peripherals(memory_map)
self.wb_peripherals = introspect.wb_peripherals(memory_map)
[docs]
def generate(self, file=None, vendor="luna-soc", name="soc", description=None):
device = self._device(vendor, name, description)
# <peripherals />
peripherals = SubElement(device, "peripherals")
csr_base = self.csr_base
logging.debug(f"\ncsr_base: 0x{csr_base:08x}")
name : MemoryMap.Name
resource_info: ResourceInfo
for name, resource_infos in self.csr_peripherals.items():
# so, in theory, these are always sorted so:
pstart = resource_infos[0].start
pend = resource_infos[-1].end
name = "_".join([str(s) for s in name]) if isinstance(name, tuple) else name[0]
logging.debug(f" {name} 0x{pstart:04x} => 0x{pend:04x} width: {pend - pstart} bytes")
peripheral = self._peripheral(peripherals, name, pstart + csr_base, pend + csr_base)
# <registers />
registers = SubElement(peripheral, "registers")
for resource_info in resource_infos:
name = "_".join([str(s[0]) for s in resource_info.path[1:]])
rstart = resource_info.start - pstart
rend = resource_info.end - pstart
access = resource_info.resource._access
access = "read-only" if type(access) is csr.FieldPort.Access.R else \
"write-only" if type(access) is csr.FieldPort.Access.W else \
"read-write"
# TODO patch amaranth_soc/csr/reg.py:458 if not using vendored version
description = resource_info.resource.__class__.__doc__
logging.debug(f" {name}\t0x{rstart:02x} => 0x{rend:02x} width: {rend - rstart} bytes")
register = self._register(
registers, # root
name, # name
rstart, # register start
rend, # register end
access=access, # access
description=description # description
)
# <fields />
fields = SubElement(register, "fields")
offset = 0
for path, action in resource_info.resource:
name = "_".join([str(s) for s in path])
width = action.port.shape.width
access = "read-only" if type(action) is csr.action.R else \
"write-only" if type(action) is csr.action.W else \
"read-write"
description = "" # TODO
bitRange = "[{:d}:{:d}]".format(offset + width - 1, offset)
logging.debug(f" {name}\toffset:0x{offset} width: {width} bits range: {bitRange}")
field = self._field(
fields, # root
name, # name
offset, # field bitOffset
width, # field bitWidth
access=access, # access
description=description # description
)
offset += width
logging.debug("\nwishbone peripherals:")
for name, t in self.wb_peripherals.items():
logging.debug(f"\t{name} => {t}")
logging.debug("\n---------------\n")
# generate output
output = ElementTree.tostring(device, 'utf-8')
output = minidom.parseString(output)
output = output.toprettyxml(indent=" ", encoding="utf-8")
# write to file
if file is None:
sys.stdout.write(str(output.decode("utf-8")))
else:
file.write(str(output.decode("utf-8")))
file.close()
def _device(self, vendor, name, description):
device = Element("device")
device.set("schemaVersion", "1.1")
device.set("xmlns:xs", "http://www.w3.org/2001/XMLSchema-instance")
device.set("xs:noNamespaceSchemaLocation", "CMSIS-SVD.xsd")
el = SubElement(device, "vendor")
el.text = vendor
el = SubElement(device, "name")
el.text = name.upper()
el = SubElement(device, "description")
el.text = description or "TODO device.description"
el = SubElement(device, "addressUnitBits")
el.text = "8" # TODO
el = SubElement(device, "width")
el.text = "32" # TODO
el = SubElement(device, "size")
el.text = "32" # TODO
el = SubElement(device, "access")
el.text = "read-write"
el = SubElement(device, "resetValue")
el.text = "0x00000000" # TODO
el = SubElement(device, "resetMask")
el.text = "0xFFFFFFFF" # TODO
return device
def _peripheral(self, root, name, start=0, end=0, groupName=None):
peripheral = SubElement(root, "peripheral")
el = SubElement(peripheral, "name")
el.text = name
el = SubElement(peripheral, "groupName")
el.text = groupName or ""
el = SubElement(peripheral, "baseAddress")
el.text = "0x{:08x}".format(start)
addressBlock = SubElement(peripheral, "addressBlock")
el = SubElement(addressBlock, "offset")
el.text = "0" # TODO
el = SubElement(addressBlock, "size") # TODO
el.text = "0x{:02x}".format(end - start) # TODO
el = SubElement(addressBlock, "usage")
el.text = "registers"
# interrupts
# TODO search by start, end rather than name
for v, (n, p) in self.interrupts.items():
if name == n:
interrupt = SubElement(peripheral, "interrupt")
el = SubElement(interrupt, "name")
el.text = n
el = SubElement(interrupt, "value")
el.text = str(v)
break
return peripheral
def _register(self, root, name, start, end, access=None, description=None):
register = SubElement(root, "register")
el = SubElement(register, "name")
el.text = name
el = SubElement(register, "description")
el.text = description or f"{name} register"
el = SubElement(register, "addressOffset")
el.text = "0x{:04x}".format(start)
el = SubElement(register, "size")
el.text = "{:d}".format((end - start) * 8) # TODO
el = SubElement(register, "resetValue")
el.text = "0x00" # TODO - calculate from fields ?
if access is not None:
el = SubElement(register, "access")
el.text = access
return register
def _field(self, root, name, bitOffset, bitWidth, access=None, description=None):
field = SubElement(root, "field")
el = SubElement(field, "name")
el.text = name
el = SubElement(field, "description")
el.text = description or f"{name} field"
el = SubElement(field, "bitOffset")
el.text = "{:d}".format(bitOffset)
el = SubElement(field, "bitWidth")
el.text = "{:d}".format(bitWidth)
el = SubElement(field, "bitRange")
el.text = "[{:d}:{:d}]".format(bitOffset + bitWidth - 1, bitOffset)
if access is not None:
el = SubElement(field, "access")
el.text = access
return field
def _vendorExtensions(self):
pass