From 8c426b406d372c8cd038b467f9a34ed0dff520a1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 5 May 2018 19:02:43 +0200 Subject: [PATCH] Automatically convert .svd files to Go source files --- .gitignore | 1 + Makefile | 7 +- gen-device.py | 238 +++++++++++++++++++++++++++++++++ src/device/nrf/README.markdown | 12 ++ 4 files changed, 257 insertions(+), 1 deletion(-) create mode 100755 gen-device.py create mode 100644 src/device/nrf/README.markdown diff --git a/.gitignore b/.gitignore index 378eac25..13daec9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +src/device/nrf/*.go diff --git a/Makefile b/Makefile index 332e924f..1c6d8c8f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: tgo tgo: build/tgo -.PHONY: all tgo run-hello run-blinky flash-blinky clean +.PHONY: all tgo run-hello run-blinky flash-blinky clean gen-device gen-device-nrf # Custom LLVM toolchain. LLVM = @@ -54,6 +54,11 @@ flash-blinky: build/blinky.hex clean: @rm -rf build +gen-device: gen-device-nrf + +gen-device-nrf: + ./gen-device.py lib/nrfx/mdk/ src/device/nrf/ + go fmt ./src/device/nrf # Build the Go compiler. diff --git a/gen-device.py b/gen-device.py new file mode 100755 index 00000000..1f2e449a --- /dev/null +++ b/gen-device.py @@ -0,0 +1,238 @@ +#!/usr/bin/python3 + +import sys +import os +from xml.dom import minidom +from glob import glob +from collections import OrderedDict +import re + +ARM_ARCHS = { + 'CM0': 'armv6m', + 'CM4': 'armv7em', +} + +class Device: + # dummy + pass + +def getText(element): + strings = [] + for node in element.childNodes: + if node.nodeType == node.TEXT_NODE: + strings.append(node.data) + return ''.join(strings) + +def formatText(text): + text = re.sub('[ \t\n]+', ' ', text) # Collapse whitespace (like in HTML) + text = text.replace('\\n ', '\n') + text = text.strip() + return text + +def readSVD(path): + # Read ARM SVD files. + device = Device() + xml = minidom.parse(path) + root = xml.getElementsByTagName('device')[0] + deviceName = getText(root.getElementsByTagName('name')[0]) + deviceDescription = getText(root.getElementsByTagName('description')[0]) + licenseText = formatText(getText(root.getElementsByTagName('licenseText')[0])) + cpu = root.getElementsByTagName('cpu')[0] + cpuName = getText(cpu.getElementsByTagName('name')[0]) + + device.peripherals = [] + + interrupts = OrderedDict() + + for periphEl in root.getElementsByTagName('peripherals')[0].getElementsByTagName('peripheral'): + name = getText(periphEl.getElementsByTagName('name')[0]) + description = getText(periphEl.getElementsByTagName('description')[0]) + baseAddress = int(getText(periphEl.getElementsByTagName('baseAddress')[0]), 0) + + peripheral = { + 'name': name, + 'description': description, + 'registers': [], + } + device.peripherals.append(peripheral) + + for interrupt in periphEl.getElementsByTagName('interrupt'): + intrName = getText(interrupt.getElementsByTagName('name')[0]) + intrIndex = int(getText(interrupt.getElementsByTagName('value')[0])) + if intrName in interrupts: + if interrupts[intrName]['index'] != intrIndex: + raise ValueError('interrupt with the same name has different indexes: ' + intrName) + interrupts[intrName]['description'] += ' // ' + description + else: + interrupts[intrName] = { + 'name': intrName, + 'index': intrIndex, + 'description': description, + } + + regsEls = periphEl.getElementsByTagName('registers') + if regsEls: + for el in regsEls[0].childNodes: + if el.nodeName == 'register': + peripheral['registers'].append(parseSVDRegister(name, el, baseAddress)) + elif el.nodeName == 'cluster': + if el.getElementsByTagName('dim'): + continue # TODO + clusterPrefix = getText(el.getElementsByTagName('name')[0]) + '_' + clusterOffset = int(getText(el.getElementsByTagName('addressOffset')[0]), 0) + for regEl in el.childNodes: + if regEl.nodeName == 'register': + peripheral['registers'].append(parseSVDRegister(name, regEl, baseAddress + clusterOffset, clusterPrefix)) + else: + continue + + device.interrupts = interrupts.values() # TODO: sort by index + device.metadata = { + 'file': os.path.basename(path), + 'descriptorSource': 'https://github.com/NordicSemiconductor/nrfx/tree/master/mdk', + 'name': deviceName, + 'nameLower': deviceName.lower(), + 'description': deviceDescription, + 'licenseBlock': '\n// ' + licenseText.replace('\n', '\n// '), + 'regType': 'uint32', + 'arch': ARM_ARCHS[cpuName], + 'family': getText(root.getElementsByTagName('series')[0]), + } + + return device + +def parseSVDRegister(peripheralName, regEl, baseAddress, namePrefix=''): + regName = getText(regEl.getElementsByTagName('name')[0]) + regDescription = getText(regEl.getElementsByTagName('description')[0]) + offsetEls = regEl.getElementsByTagName('offset') + if not offsetEls: + offsetEls = regEl.getElementsByTagName('addressOffset') + address = baseAddress + int(getText(offsetEls[0]), 0) + + dimEls = regEl.getElementsByTagName('dim') + array = None + if dimEls: + array = int(getText(dimEls[0]), 0) + regName = regName.replace('[%s]', '') + + fields = [] + fieldsEls = regEl.getElementsByTagName('fields') + if fieldsEls: + for fieldEl in fieldsEls[0].childNodes: + if fieldEl.nodeName != 'field': + continue + fieldName = getText(fieldEl.getElementsByTagName('name')[0]) + descrEls = fieldEl.getElementsByTagName('description') + for enumEl in fieldEl.getElementsByTagName('enumeratedValue'): + enumName = getText(enumEl.getElementsByTagName('name')[0]) + enumDescription = getText(enumEl.getElementsByTagName('description')[0]) + enumValue = int(getText(enumEl.getElementsByTagName('value')[0]), 0) + fields.append({ + 'name': '{}_{}{}_{}_{}'.format(peripheralName, namePrefix, regName, fieldName, enumName), + 'description': enumDescription, + 'value': enumValue, + }) + + return { + 'variants': [{ + 'name': namePrefix + regName, + 'address': address, + }], + 'description': regDescription.replace('\n', ' '), + 'bitfields': fields, + 'array': array, + } + +def writeGo(outdir, device): + # The Go module for this device. + out = open(outdir + '/' + device.metadata['nameLower'] + '.go', 'w') + pkgName = os.path.basename(outdir.rstrip('/')) + out.write('''\ +// Automatically generated file. DO NOT EDIT. +// Generated by gen-device.py from {file}, see {descriptorSource} + +// +build {pkgName},{nameLower} + +// {description} +// {licenseBlock} +package {pkgName} + +// Magic type name for the compiler. +type __reg {regType} + +// Export this magic type name. +type RegValue = __reg + +// Some information about this device. +const ( + DEVICE = "{name}" + ARCH = "{arch}" + FAMILY = "{family}" +) +'''.format(pkgName=pkgName, **device.metadata)) + + out.write('\n// Interrupts\nconst (\n') + for intr in device.interrupts: + out.write('\tIRQ_{name} = {index} // {description}\n'.format(**intr)) + intrMax = max(map(lambda intr: intr['index'], device.interrupts)) + out.write('\tIRQ_max = {} // Highest interrupt number on this device.\n'.format(intrMax)) + out.write(')\n') + + out.write('\n// Peripherals\nvar (') + for peripheral in device.peripherals: + out.write('\n\t// {description}\n\t{name} = struct {{\n'.format(**peripheral)) + for register in peripheral['registers']: + for variant in register['variants']: + regType = '__reg' + if register['array'] is not None: + regType = '[{}]__reg'.format(register['array']) + out.write('\t\t{name} {regType}\n'.format(**variant, regType=regType)) + out.write('\t}{\n') + for register in peripheral['registers']: + for variant in register['variants']: + out.write('\t\t{name}: '.format(**variant)) + if register['array'] is not None: + out.write('[{num}]__reg{{'.format(num=register['array'])) + if register['description']: + out.write(' // {description}'.format(**register)) + out.write('\n') + for i in range(register['array']): + out.write('\t\t\t0x{:x},\n'.format(variant['address'] + i * 4)) # TODO: pointer width + out.write('\t\t},') + else: + out.write('0x{address:x},'.format(**variant)) + if register['description']: + out.write(' // {description}'.format(**register)) + out.write('\n') + out.write('\t}\n') + out.write(')\n') + + for peripheral in device.peripherals: + if not sum(map(lambda r: len(r['bitfields']), peripheral['registers'])): continue + out.write('\n// Bitfields for {name}: {description}\nconst('.format(**peripheral)) + for register in peripheral['registers']: + if not register['bitfields']: continue + for variant in register['variants']: + out.write('\n\t// {name}'.format(**variant)) + if register['description']: + out.write(': {description}'.format(**register)) + out.write('\n') + for bitfield in register['bitfields']: + out.write('\t{name} = 0x{value:x}'.format(**bitfield)) + if bitfield['description']: + out.write('// {description}'.format(**bitfield)) + out.write('\n') + out.write(')\n') + + +def generate(indir, outdir): + for filepath in glob(indir + '/*.svd'): + print(filepath) + device = readSVD(filepath) + writeGo(outdir, device) + + +if __name__ == '__main__': + indir = sys.argv[1] # directory with register descriptor files (*.svd) + outdir = sys.argv[2] # output directory + generate(indir, outdir) diff --git a/src/device/nrf/README.markdown b/src/device/nrf/README.markdown new file mode 100644 index 00000000..4363613b --- /dev/null +++ b/src/device/nrf/README.markdown @@ -0,0 +1,12 @@ +# Generated Go files for Nordic Semiconductors devices + +In this directory, Go register description files are stored that are generated +by `gen-device.py` from .svd files provided by Nordic. See the SVD files [over +here](https://github.com/NordicSemiconductor/nrfx/tree/master/mdk). + +The original files are provided under the 3-clause BSD license, see [this +post](https://devzone.nordicsemi.com/b/blog/posts/introducing-nordics-new-software-licensing-schemes) +for details. As the generated files transform most of the original file, I think +they should be licensed under the same license as the original files. Generated +files will contain the license statement that is included in the original SVD +files.