diff options
author | Ayke van Laethem <[email protected]> | 2019-12-14 00:45:04 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-12-14 22:27:45 +0100 |
commit | cf32607306fd8cd4a75a7fb4ed9b47413b415fe5 (patch) | |
tree | 8d81d06ea874d6e7d312bc9482a835d1ecd03f5b | |
parent | ad022ef23d73bbcb8901d7d023a2c8ac907ffb6b (diff) | |
download | tinygo-cf32607306fd8cd4a75a7fb4ed9b47413b415fe5.tar.gz tinygo-cf32607306fd8cd4a75a7fb4ed9b47413b415fe5.zip |
tools: rewrite gen-device-svd in Go
This should make it more maintainable. Another big advantage that
generation time (including gofmt) is now 3 times faster. No real attempt
at refactoring has been made, that will need to be done at a later time.
-rw-r--r-- | .circleci/config.yml | 3 | ||||
-rw-r--r-- | Dockerfile | 17 | ||||
-rw-r--r-- | Makefile | 22 | ||||
-rw-r--r-- | src/device/stm32/stm32f103xx-bitfields.go | 2 | ||||
-rwxr-xr-x | tools/gen-device-svd.py | 645 | ||||
-rwxr-xr-x | tools/gen-device-svd/gen-device-svd.go | 971 |
6 files changed, 988 insertions, 672 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 4e148c933..92e6a424d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,6 @@ commands: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - sudo apt-get update sudo apt-get install \ - python3 \ llvm<<parameters.llvm>>-dev \ clang<<parameters.llvm>> \ libclang<<parameters.llvm>>-dev \ @@ -83,7 +82,6 @@ commands: name: "Install apt dependencies" command: | sudo apt-get install \ - python3 \ gcc-arm-linux-gnueabihf \ binutils-arm-none-eabi \ libc6-dev-armel-cross \ @@ -148,7 +146,6 @@ commands: name: "Install apt dependencies" command: | sudo apt-get install \ - python3 \ gcc-arm-linux-gnueabihf \ binutils-arm-none-eabi \ libc6-dev-armel-cross \ diff --git a/Dockerfile b/Dockerfile index cfa75aff8..7b6649793 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,9 +41,8 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib RUN cd /tinygo/ && \ apt-get update && \ - apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \ + apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \ make gen-device-avr && \ - apt-get remove -y python3 && \ apt-get autoremove -y && \ apt-get clean @@ -59,11 +58,8 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib RUN cd /tinygo/ && \ apt-get update && \ - apt-get install -y apt-utils python3 make clang-9 && \ - make gen-device-nrf && make gen-device-stm32 && \ - apt-get remove -y python3 && \ - apt-get autoremove -y && \ - apt-get clean + apt-get install -y apt-utils make clang-9 && \ + make gen-device-nrf && make gen-device-stm32 # tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms. FROM tinygo-wasm AS tinygo-all @@ -74,10 +70,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib RUN cd /tinygo/ && \ apt-get update && \ - apt-get install -y apt-utils python3 make clang-9 binutils-avr gcc-avr avr-libc && \ - make gen-device && \ - apt-get remove -y python3 && \ - apt-get autoremove -y && \ - apt-get clean + apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \ + make gen-device CMD ["tinygo"] @@ -14,9 +14,6 @@ export GOROOT = $(shell $(GO) env GOROOT) # md5sum binary MD5SUM = md5sum -# Python binary -PYTHON ?= python - # tinygo binary for tests TINYGO ?= tinygo @@ -102,20 +99,23 @@ gen-device-avr: ./build/gen-device-avr lib/avr/packs/tiny src/device/avr/ @GO111MODULE=off $(GO) fmt ./src/device/avr -gen-device-nrf: - $(PYTHON) ./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk +build/gen-device-svd: ./tools/gen-device-svd/*.go + $(GO) build -o $@ ./tools/gen-device-svd/ + +gen-device-nrf: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/ GO111MODULE=off $(GO) fmt ./src/device/nrf -gen-device-sam: - $(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel +gen-device-sam: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/ GO111MODULE=off $(GO) fmt ./src/device/sam -gen-device-sifive: - $(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community +gen-device-sifive: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ GO111MODULE=off $(GO) fmt ./src/device/sifive -gen-device-stm32: - $(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro +gen-device-stm32: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/ GO111MODULE=off $(GO) fmt ./src/device/stm32 diff --git a/src/device/stm32/stm32f103xx-bitfields.go b/src/device/stm32/stm32f103xx-bitfields.go index e6d6414b6..5e68bc84c 100644 --- a/src/device/stm32/stm32f103xx-bitfields.go +++ b/src/device/stm32/stm32f103xx-bitfields.go @@ -1,5 +1,5 @@ // Hand created file. DO NOT DELETE. -// STM32F103XX bitfield definitions that are not auto-generated by gen-device-svd.py +// STM32F103XX bitfield definitions that are not auto-generated by gen-device-svd.go // +build stm32,stm32f103xx diff --git a/tools/gen-device-svd.py b/tools/gen-device-svd.py deleted file mode 100755 index ebd64a5c9..000000000 --- a/tools/gen-device-svd.py +++ /dev/null @@ -1,645 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import sys -import os -from xml.etree import ElementTree -from glob import glob -from collections import OrderedDict -import re -import argparse - -validName = re.compile('^[a-zA-Z0-9_]+$') - - -class Device: - # dummy - pass - -def getText(element): - if element is None: - return "None" - return ''.join(element.itertext()) - -def formatText(text): - text = re.sub('[ \t\n]+', ' ', text) # Collapse whitespace (like in HTML) - text = text.replace('\\n ', '\n') - text = text.strip() - return text - -# Replace characters that are not allowed in a symbol name with a '_'. This is -# useful to be able to process SVD files with errors. -def cleanName(text): - if not validName.match(text): - return ''.join(list(map(lambda c: c if validName.match(c) else '_', text))) - return text - -def readSVD(path, sourceURL): - # Read ARM SVD files. - device = Device() - xml = ElementTree.parse(path) - root = xml.getroot() - deviceName = getText(root.find('name')) - deviceDescription = getText(root.find('description')).strip() - licenseTexts = root.findall('licenseText') - if len(licenseTexts) == 0: - licenseText = None - elif len(licenseTexts) == 1: - licenseText = formatText(getText(licenseTexts[0])) - else: - raise ValueError('multiple <licenseText> elements') - - device.peripherals = [] - peripheralDict = {} - groups = {} - - interrupts = OrderedDict() - - for periphEl in root.findall('./peripherals/peripheral'): - name = getText(periphEl.find('name')) - descriptionTags = periphEl.findall('description') - description = '' - if descriptionTags: - description = formatText(getText(descriptionTags[0])) - baseAddress = int(getText(periphEl.find('baseAddress')), 0) - groupNameTags = periphEl.findall('groupName') - groupName = None - if groupNameTags: - # Some group names (for example the STM32H7A3x) have an invalid - # group name. Replace invalid characters with '_'. - groupName = cleanName(getText(groupNameTags[0])) - - interruptEls = periphEl.findall('interrupt') - for interrupt in interruptEls: - intrName = getText(interrupt.find('name')) - intrIndex = int(getText(interrupt.find('value'))) - addInterrupt(interrupts, intrName, intrIndex, description) - # As a convenience, also use the peripheral name as the interrupt - # name. Only do that for the nrf for now, as the stm32 .svd files - # don't always put interrupts in the correct peripheral... - if len(interruptEls) == 1 and deviceName.startswith('nrf'): - addInterrupt(interrupts, name, intrIndex, description) - - if periphEl.get('derivedFrom') or groupName in groups: - if periphEl.get('derivedFrom'): - derivedFromName = periphEl.get('derivedFrom') - derivedFrom = peripheralDict[derivedFromName] - else: - derivedFrom = groups[groupName] - peripheral = { - 'name': name, - 'groupName': derivedFrom['groupName'], - 'description': description or derivedFrom['description'], - 'baseAddress': baseAddress, - } - device.peripherals.append(peripheral) - peripheralDict[name] = peripheral - if 'subtypes' in derivedFrom: - for subtype in derivedFrom['subtypes']: - subp = { - 'name': name + "_"+subtype['clusterName'], - 'groupName': subtype['groupName'], - 'description': subtype['description'], - 'baseAddress': baseAddress, - } - device.peripherals.append(subp) - continue - - peripheral = { - 'name': name, - 'groupName': groupName or name, - 'description': description, - 'baseAddress': baseAddress, - 'registers': [], - 'subtypes': [], - } - device.peripherals.append(peripheral) - peripheralDict[name] = peripheral - - if groupName and groupName not in groups: - groups[groupName] = peripheral - - regsEls = periphEl.findall('registers') - if regsEls: - if len(regsEls) != 1: - raise ValueError('expected just one <registers> in a <peripheral>') - for register in regsEls[0].findall('register'): - peripheral['registers'].extend(parseRegister(groupName or name, register, baseAddress)) - for cluster in regsEls[0].findall('cluster'): - clusterName = getText(cluster.find('name')).replace('[%s]', '') - if cluster.find('dimIndex') is not None: - clusterName = clusterName.replace('%s', '') - clusterDescription = getText(cluster.find('description')) - clusterPrefix = clusterName + '_' - clusterOffset = int(getText(cluster.find('addressOffset')), 0) - if cluster.find('dim') is None: - if clusterOffset == 0: - # make this a separate peripheral - cpRegisters = [] - for regEl in cluster.findall('register'): - cpRegisters.extend(parseRegister(groupName, regEl, baseAddress, clusterName+"_")) - # handle sub-clusters of registers - for subClusterEl in cluster.findall('cluster'): - subclusterName = getText(subClusterEl.find('name')).replace('[%s]', '') - subclusterDescription = getText(subClusterEl.find('description')) - subclusterPrefix = subclusterName + '_' - subclusterOffset = int(getText(subClusterEl.find('addressOffset')), 0) - subdim = int(getText(subClusterEl.find('dim'))) - subdimIncrement = int(getText(subClusterEl.find('dimIncrement')), 16) - - if subdim > 1: - subcpRegisters = [] - subregSize = 0 - for regEl in subClusterEl.findall('register'): - subregSize += int(getText(regEl.find('size'))) - subcpRegisters.extend(parseRegister(groupName, regEl, baseAddress + subclusterOffset, subclusterPrefix)) - cpRegisters.append({ - 'name': subclusterName, - 'address': baseAddress + subclusterOffset, - 'description': subclusterDescription, - 'registers': subcpRegisters, - 'array': subdim, - 'elementsize': subdimIncrement, - }) - else: - for regEl in subClusterEl.findall('register'): - cpRegisters.extend(parseRegister(getText(regEl.find('name')), regEl, baseAddress + subclusterOffset, subclusterPrefix)) - - cpRegisters.sort(key=lambda r: r['address']) - clusterPeripheral = { - 'name': name+ "_" +clusterName, - 'groupName': groupName+ "_" +clusterName, - 'description': description+ " - " +clusterName, - 'clusterName': clusterName, - 'baseAddress': baseAddress, - 'registers': cpRegisters, - } - device.peripherals.append(clusterPeripheral) - peripheral['subtypes'].append(clusterPeripheral) - continue - dim = None - dimIncrement = None - else: - dim = int(getText(cluster.find('dim'))) - if dim == 1: - dimIncrement = None - else: - dimIncrement = int(getText(cluster.find('dimIncrement')), 0) - clusterRegisters = [] - for regEl in cluster.findall('register'): - clusterRegisters.extend(parseRegister(groupName or name, regEl, baseAddress + clusterOffset, clusterPrefix)) - clusterRegisters.sort(key=lambda r: r['address']) - if dimIncrement is None: - lastReg = clusterRegisters[-1] - lastAddress = lastReg['address'] - if lastReg['array'] is not None: - lastAddress = lastReg['address'] + lastReg['array'] * lastReg['elementsize'] - firstAddress = clusterRegisters[0]['address'] - dimIncrement = lastAddress - firstAddress - peripheral['registers'].append({ - 'name': clusterName, - 'address': baseAddress + clusterOffset, - 'description': clusterDescription, - 'registers': clusterRegisters, - 'array': dim, - 'elementsize': dimIncrement, - }) - peripheral['registers'].sort(key=lambda r: r['address']) - - device.interrupts = sorted(interrupts.values(), key=lambda v: v['index']) - licenseBlock = '' - if licenseText is not None: - licenseBlock = '// ' + licenseText.replace('\n', '\n// ') - licenseBlock = '\n'.join(map(str.rstrip, licenseBlock.split('\n'))) # strip trailing whitespace - device.metadata = { - 'file': os.path.basename(path), - 'descriptorSource': sourceURL, - 'name': deviceName, - 'nameLower': deviceName.lower(), - 'description': deviceDescription, - 'licenseBlock': licenseBlock, - } - - return device - -def addInterrupt(interrupts, intrName, intrIndex, description): - if intrName in interrupts: - if interrupts[intrName]['index'] != intrIndex: - # Note: some SVD files like the one for STM32H7x7 contain mistakes. - # Instead of throwing an error, simply log it. - print ('interrupt with the same name has different indexes: %s (%d vs %d)' - % (intrName, interrupts[intrName]['index'], intrIndex)) - if description not in interrupts[intrName]['description'].split(' // '): - interrupts[intrName]['description'] += ' // ' + description - else: - interrupts[intrName] = { - 'name': intrName, - 'index': intrIndex, - 'description': description, - } - -def parseBitfields(groupName, regName, fieldsEls, bitfieldPrefix=''): - fields = [] - if fieldsEls: - for fieldEl in fieldsEls[0].findall('field'): - # Some bitfields (like the STM32H7x7) contain invalid bitfield - # names like 'CNT[31]'. Replace invalid characters with '_' when - # needed. - fieldName = cleanName(getText(fieldEl.find('name'))) - if not fieldName[0].isupper() and not fieldName[0].isdigit(): - fieldName = fieldName.upper() - if len(fieldEl.findall('lsb')) == 1 and len(fieldEl.findall('msb')) == 1: - # try to use lsb/msb tags - lsb = int(getText(fieldEl.findall('lsb')[0])) - msb = int(getText(fieldEl.findall('msb')[0])) - elif len(fieldEl.findall('bitOffset')) > 0 and len(fieldEl.findall('bitWidth')) > 0: - # try to use bitOffset/bitWidth tags - lsb = int(getText(fieldEl.find('bitOffset'))) - msb = int(getText(fieldEl.find('bitWidth'))) + lsb - 1 - elif len(fieldEl.findall('bitRange')) > 0: - # try use bitRange - bitRangeTags = fieldEl.findall('bitRange') - lsb = int(getText(bitRangeTags[0]).split(":")[1][:-1]) - msb = int(getText(bitRangeTags[0]).split(":")[0][1:]) - else: - # this is an error. what to do? - print("unable to find lsb/msb in field:", fieldName) - - fields.append({ - 'name': '{}_{}{}_{}_Pos'.format(groupName, bitfieldPrefix, regName, fieldName), - 'description': 'Position of %s field.' % fieldName, - 'value': lsb, - }) - fields.append({ - 'name': '{}_{}{}_{}_Msk'.format(groupName, bitfieldPrefix, regName, fieldName), - 'description': 'Bit mask of %s field.' % fieldName, - 'value': (0xffffffff >> (31 - (msb - lsb))) << lsb, - }) - if lsb == msb: # single bit - fields.append({ - 'name': '{}_{}{}_{}'.format(groupName, bitfieldPrefix, regName, fieldName), - 'description': 'Bit %s.' % fieldName, - 'value': 1 << lsb, - }) - for enumEl in fieldEl.findall('enumeratedValues/enumeratedValue'): - enumName = getText(enumEl.find('name')) - if not enumName[0].isupper() and not enumName[0].isdigit(): - enumName = enumName.upper() - enumDescription = getText(enumEl.find('description')).replace('\n', ' ') - enumValue = int(getText(enumEl.find('value')), 0) - fields.append({ - 'name': '{}_{}{}_{}_{}'.format(groupName, bitfieldPrefix, regName, fieldName, enumName), - 'description': enumDescription, - 'value': enumValue, - }) - return fields - -class Register: - def __init__(self, element, baseAddress): - self.element = element - self.baseAddress = baseAddress - - def name(self): - return getText(self.element.find('name')).replace('[%s]', '') - - def description(self): - return getText(self.element.find('description')).replace('\n', ' ') - - def address(self): - offsetEls = self.element.findall('offset') - if not offsetEls: - offsetEls = self.element.findall('addressOffset') - return self.baseAddress + int(getText(offsetEls[0]), 0) - - def dim(self): - dimEls = self.element.findall('dim') - if len(dimEls) == 0: - return None - elif len(dimEls) == 1: - return int(getText(dimEls[0]), 0) - else: - raise ValueError('expected at most one <dim> element in %s register' % self.name()) - - def size(self): - size = 4 - elSizes = self.element.findall('size') - if elSizes: - size = int(getText(elSizes[0]), 0) // 8 - return size - - -def parseRegister(groupName, regEl, baseAddress, bitfieldPrefix=''): - reg = Register(regEl, baseAddress) - - fieldsEls = regEl.findall('fields') - - if reg.dim() is not None: - dimIncrement = int(getText(regEl.find('dimIncrement')), 0) - if "%s" in reg.name(): - # a "spaced array" of registers, special processing required - # we need to generate a separate register for each "element" - results = [] - for i in range(reg.dim()): - regAddress = reg.address() + (i * dimIncrement) - results.append({ - 'name': reg.name().replace('%s', str(i)), - 'address': regAddress, - 'description': reg.description(), - 'bitfields': [], - 'array': None, - 'elementsize': reg.size(), - }) - # set first result bitfield - shortName = reg.name().replace('_%s', '').replace('%s', '').upper() - results[0]['bitfields'] = parseBitfields(groupName, shortName, fieldsEls, bitfieldPrefix) - return results - regName = reg.name() - if not regName[0].isupper() and not regName[0].isdigit(): - regName = regName.upper() - return [{ - 'name': regName, - 'address': reg.address(), - 'description': reg.description(), - 'bitfields': parseBitfields(groupName, regName, fieldsEls, bitfieldPrefix), - 'array': reg.dim(), - 'elementsize': reg.size(), - }] - -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-svd.py from {file}, see {descriptorSource} - -// +build {pkgName},{nameLower} - -// {description} -// -{licenseBlock} -package {pkgName} - -import ( - "runtime/volatile" - "unsafe" -) - -// Some information about this device. -const ( - DEVICE = "{name}" -) -'''.format(pkgName=pkgName, **device.metadata)) - - out.write('\n// Interrupt numbers\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') - - # Define actual peripheral pointers. - out.write('\n// Peripherals.\nvar (\n') - for peripheral in device.peripherals: - out.write('\t{name} = (*{groupName}_Type)(unsafe.Pointer(uintptr(0x{baseAddress:x}))) // {description}\n'.format(**peripheral)) - out.write(')\n') - - # Define peripheral struct types. - for peripheral in device.peripherals: - if 'registers' not in peripheral: - # This peripheral was derived from another peripheral. No new type - # needs to be defined for it. - continue - out.write('\n// {description}\ntype {groupName}_Type struct {{\n'.format(**peripheral)) - address = peripheral['baseAddress'] - for register in peripheral['registers']: - if address > register['address'] and 'registers' not in register : - # In Nordic SVD files, these registers are deprecated or - # duplicates, so can be ignored. - #print('skip: %s.%s %s - %s %s' % (peripheral['name'], register['name'], address, register['address'], register['elementsize'])) - continue - eSize = register['elementsize'] - if eSize == 4: - regType = 'volatile.Register32' - elif eSize == 2: - regType = 'volatile.Register16' - elif eSize == 1: - regType = 'volatile.Register8' - else: - eSize = 4 - regType = 'volatile.Register32' - - # insert padding, if needed - if address < register['address']: - bytesNeeded = register['address'] - address - if bytesNeeded == 1: - out.write('\t_ {regType}\n'.format(regType='volatile.Register8')) - elif bytesNeeded == 2: - out.write('\t_ {regType}\n'.format(regType='volatile.Register16')) - elif bytesNeeded == 3: - out.write('\t_ [3]{regType}\n'.format(regType='volatile.Register8')) - else: - numSkip = (register['address'] - address) // eSize - if numSkip == 1: - out.write('\t_ {regType}\n'.format(regType=regType)) - else: - out.write('\t_ [{num}]{regType}\n'.format(num=numSkip, regType=regType)) - address = register['address'] - - lastCluster = False - if 'registers' in register: - # This is a cluster, not a register. Create the cluster type. - regType = 'struct {\n' - subaddress = register['address'] - for subregister in register['registers']: - if subregister['elementsize'] == 4: - subregType = 'volatile.Register32' - elif subregister['elementsize'] == 2: - subregType = 'volatile.Register16' - elif subregister['elementsize'] == 1: - subregType = 'volatile.Register8' - - if subregister['array']: - subregType = '[{}]{}'.format(subregister['array'], subregType) - if subaddress != subregister['address']: - bytesNeeded = subregister['address'] - subaddress - if bytesNeeded == 1: - regType += '\t\t_ {subregType}\n'.format(subregType='volatile.Register8') - elif bytesNeeded == 2: - regType += '\t\t_ {subregType}\n'.format(subregType='volatile.Register16') - else: - numSkip = (subregister['address'] - subaddress) - if numSkip < 1: - continue - elif numSkip == 1: - regType += '\t\t_ {subregType}\n'.format(subregType='volatile.Register8') - else: - regType += '\t\t_ [{num}]{subregType}\n'.format(num=numSkip, subregType='volatile.Register8') - subaddress += bytesNeeded - if subregister['array'] is not None: - subregSize = subregister['array'] * subregister['elementsize'] - else: - subregSize = subregister['elementsize'] - subaddress += subregSize - regType += '\t\t{name} {subregType}\n'.format(name=subregister['name'], subregType=subregType) - if register['array'] is not None: - if subaddress != register['address'] + register['elementsize']: - numSkip = ((register['address'] + register['elementsize']) - subaddress) // subregSize - if numSkip <= 1: - regType += '\t\t_ {subregType}\n'.format(subregType=subregType) - else: - regType += '\t\t_ [{num}]{subregType}\n'.format(num=numSkip, subregType=subregType) - else: - lastCluster = True - regType += '\t}' - address = subaddress - if register['array'] is not None: - regType = '[{}]{}'.format(register['array'], regType) - out.write('\t{name} {regType}\n'.format(name=register['name'], regType=regType)) - - # next address - if lastCluster is True: - lastCluster = False - elif register['array'] is not None: - address = register['address'] + register['elementsize'] * register['array'] - else: - address = register['address'] + register['elementsize'] - out.write('}\n') - - # Define bitfields. - for peripheral in device.peripherals: - if 'registers' not in peripheral: - # This peripheral was derived from another peripheral. Bitfields are - # already defined. - continue - out.write('\n// Bitfields for {name}: {description}\nconst('.format(**peripheral)) - for register in peripheral['registers']: - if register.get('bitfields'): - writeGoRegisterBitfields(out, register, register['name']) - for subregister in register.get('registers', []): - writeGoRegisterBitfields(out, subregister, register['name'] + '.' + subregister['name']) - out.write(')\n') - -def writeGoRegisterBitfields(out, register, name): - out.write('\n\t// {}'.format(name)) - 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') - - -def writeAsm(outdir, device): - # The interrupt vector, which is hard to write directly in Go. - out = open(outdir + '/' + device.metadata['nameLower'] + '.s', 'w') - out.write('''\ -// Automatically generated file. DO NOT EDIT. -// Generated by gen-device-svd.py from {file}, see {descriptorSource} - -// {description} -// -{licenseBlock} - -.syntax unified - -// This is the default handler for interrupts, if triggered but not defined. -.section .text.Default_Handler -.global Default_Handler -.type Default_Handler, %function -Default_Handler: - wfe - b Default_Handler - -// Avoid the need for repeated .weak and .set instructions. -.macro IRQ handler - .weak \\handler - .set \\handler, Default_Handler -.endm - -// Must set the "a" flag on the section: -// https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049 -// https://sourceware.org/binutils/docs/as/Section.html#ELF-Version -.section .isr_vector, "a", %progbits -.global __isr_vector - // Interrupt vector as defined by Cortex-M, starting with the stack top. - // On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading - // _stack_top and Reset_Handler. - .long _stack_top - .long Reset_Handler - .long NMI_Handler - .long HardFault_Handler - .long MemoryManagement_Handler - .long BusFault_Handler - .long UsageFault_Handler - .long 0 - .long 0 - .long 0 - .long 0 - .long SVC_Handler - .long DebugMon_Handler - .long 0 - .long PendSV_Handler - .long SysTick_Handler - - // Extra interrupts for peripherals defined by the hardware vendor. -'''.format(**device.metadata)) - num = 0 - for intr in device.interrupts: - if intr['index'] == num - 1: - continue - if intr['index'] < num: - raise ValueError('interrupt numbers are not sorted') - while intr['index'] > num: - out.write(' .long 0\n') - num += 1 - num += 1 - out.write(' .long {name}_IRQHandler\n'.format(**intr)) - - out.write(''' - // Define default implementations for interrupts, redirecting to - // Default_Handler when not implemented. - IRQ NMI_Handler - IRQ HardFault_Handler - IRQ MemoryManagement_Handler - IRQ BusFault_Handler - IRQ UsageFault_Handler - IRQ SVC_Handler - IRQ DebugMon_Handler - IRQ PendSV_Handler - IRQ SysTick_Handler -''') - for intr in device.interrupts: - out.write(' IRQ {name}_IRQHandler\n'.format(**intr)) - -def generate(indir, outdir, sourceURL): - if not os.path.isdir(indir): - print('cannot find input directory:', indir, file=sys.stderr) - sys.exit(1) - if not os.path.isdir(outdir): - os.mkdir(outdir) - infiles = glob(indir + '/*.svd') - if not infiles: - print('no .svd files found:', indir, file=sys.stderr) - sys.exit(1) - for filepath in sorted(infiles): - print(filepath) - device = readSVD(filepath, sourceURL) - writeGo(outdir, device) - writeAsm(outdir, device) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Generate Go register descriptors and interrupt vectors from .svd files') - parser.add_argument('indir', metavar='indir', type=str, - help='input directory containing .svd files') - parser.add_argument('outdir', metavar='outdir', type=str, - help='output directory') - parser.add_argument('--source', metavar='source', type=str, - help='output directory', - default='<unknown>') - args = parser.parse_args() - generate(args.indir, args.outdir, args.source) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go new file mode 100755 index 000000000..2a3d03910 --- /dev/null +++ b/tools/gen-device-svd/gen-device-svd.go @@ -0,0 +1,971 @@ +package main + +import ( + "bufio" + "encoding/xml" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "text/template" + "unicode" +) + +var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") + +type SVDFile struct { + XMLName xml.Name `xml:"device"` + Name string `xml:"name"` + Description string `xml:"description"` + LicenseText string `xml:"licenseText"` + Peripherals []struct { + Name string `xml:"name"` + Description string `xml:"description"` + BaseAddress string `xml:"baseAddress"` + GroupName string `xml:"groupName"` + DerivedFrom string `xml:"derivedFrom,attr"` + Interrupts []struct { + Name string `xml:"name"` + Index int `xml:"value"` + } `xml:"interrupt"` + Registers []*SVDRegister `xml:"registers>register"` + Clusters []*SVDCluster `xml:"registers>cluster"` + } `xml:"peripherals>peripheral"` +} + +type SVDRegister struct { + Name string `xml:"name"` + Description string `xml:"description"` + Dim *string `xml:"dim"` + DimIncrement string `xml:"dimIncrement"` + Size *string `xml:"size"` + Fields []*SVDField `xml:"fields>field"` + Offset *string `xml:"offset"` + AddressOffset *string `xml:"addressOffset"` +} + +type SVDField struct { + Name string `xml:"name"` + Description string `xml:"description"` + Lsb *uint32 `xml:"lsb"` + Msb *uint32 `xml:"msb"` + BitOffset *uint32 `xml:"bitOffset"` + BitWidth *uint32 `xml:"bitWidth"` + BitRange *string `xml:"bitRange"` + EnumeratedValues []struct { + Name string `xml:"name"` + Description string `xml:"description"` + Value string `xml:"value"` + } `xml:"enumeratedValues>enumeratedValue"` +} + +type SVDCluster struct { + Dim *int `xml:"dim"` + DimIncrement string `xml:"dimIncrement"` + DimIndex *string `xml:"dimIndex"` + Name string `xml:"name"` + Description string `xml:"description"` + Registers []*SVDRegister `xml:"register"` + Clusters []*SVDCluster `xml:"cluster"` + AddressOffset string `xml:"addressOffset"` +} + +type Device struct { + metadata map[string]string + interrupts []*interrupt + peripherals []*peripheral +} + +type interrupt struct { + Name string + peripheralIndex int + Value int // interrupt number + Description string +} + +type peripheral struct { + Name string + GroupName string + BaseAddress uint64 + Description string + ClusterName string + registers []*PeripheralField + subtypes []*peripheral +} + +// A PeripheralField is a single field in a peripheral type. It may be a full +// peripheral or a cluster within a peripheral. +type PeripheralField struct { + name string + address uint64 + description string + registers []*PeripheralField // contains fields if this is a cluster + array int + elementSize int + bitfields []Bitfield +} + +type Bitfield struct { + name string + description string + value uint32 +} + +func formatText(text string) string { + text = regexp.MustCompile(`[ \t\n]+`).ReplaceAllString(text, " ") // Collapse whitespace (like in HTML) + text = strings.Replace(text, "\\n ", "\n", -1) + text = strings.TrimSpace(text) + return text +} + +// Replace characters that are not allowed in a symbol name with a '_'. This is +// useful to be able to process SVD files with errors. +func cleanName(text string) string { + if !validName.MatchString(text) { + result := make([]rune, 0, len(text)) + for _, c := range text { + if validName.MatchString(string(c)) { + result = append(result, c) + } else { + result = append(result, '_') + } + } + text = string(result) + } + return text +} + +// Read ARM SVD files. +func readSVD(path, sourceURL string) (*Device, error) { + // Open the XML file. + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + decoder := xml.NewDecoder(f) + device := &SVDFile{} + err = decoder.Decode(device) + if err != nil { + return nil, err + } + + peripheralDict := map[string]*peripheral{} + groups := map[string]*peripheral{} + + interrupts := make(map[string]*interrupt) + var peripheralsList []*peripheral + + for _, periphEl := range device.Peripherals { + description := formatText(periphEl.Description) + baseAddress, err := strconv.ParseUint(periphEl.BaseAddress, 0, 32) + if err != nil { + return nil, fmt.Errorf("invalid base address: %w", err) + } + // Some group names (for example the STM32H7A3x) have an invalid + // group name. Replace invalid characters with "_". + groupName := cleanName(periphEl.GroupName) + + for _, interrupt := range periphEl.Interrupts { + addInterrupt(interrupts, interrupt.Name, interrupt.Index, description) + // As a convenience, also use the peripheral name as the interrupt + // name. Only do that for the nrf for now, as the stm32 .svd files + // don't always put interrupts in the correct peripheral... + if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") { + addInterrupt(interrupts, periphEl.Name, interrupt.Index, description) + } + } + + if _, ok := groups[groupName]; ok || periphEl.DerivedFrom != "" { + var derivedFrom *peripheral + if periphEl.DerivedFrom != "" { + derivedFrom = peripheralDict[periphEl.DerivedFrom] + } else { + derivedFrom = groups[groupName] + } + p := &peripheral{ + Name: periphEl.Name, + GroupName: derivedFrom.GroupName, + Description: description, + BaseAddress: baseAddress, + } + if p.Description == "" { + p.Description = derivedFrom.Description + } + peripheralsList = append(peripheralsList, p) + peripheralDict[p.Name] = p + for _, subtype := range derivedFrom.subtypes { + peripheralsList = append(peripheralsList, &peripheral{ + Name: periphEl.Name + "_" + subtype.ClusterName, + GroupName: subtype.GroupName, + Description: subtype.Description, + BaseAddress: baseAddress, + }) + } + continue + } + + p := &peripheral{ + Name: periphEl.Name, + GroupName: groupName, + Description: description, + BaseAddress: baseAddress, + registers: []*PeripheralField{}, + } + if p.GroupName == "" { + p.GroupName = periphEl.Name + } + peripheralsList = append(peripheralsList, p) + peripheralDict[periphEl.Name] = p + + if _, ok := groups[groupName]; !ok && groupName != "" { + groups[groupName] = p + } + + for _, register := range periphEl.Registers { + regName := groupName // preferably use the group name + if regName == "" { + regName = periphEl.Name // fall back to peripheral name + } + p.registers = append(p.registers, parseRegister(regName, register, baseAddress, "")...) + } + for _, cluster := range periphEl.Clusters { + clusterName := strings.Replace(cluster.Name, "[%s]", "", -1) + if cluster.DimIndex != nil { + clusterName = strings.Replace(clusterName, "%s", "", -1) + } + clusterPrefix := clusterName + "_" + clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32) + if err != nil { + panic(err) + } + var dim, dimIncrement int + if cluster.Dim == nil { + if clusterOffset == 0 { + // make this a separate peripheral + cpRegisters := []*PeripheralField{} + for _, regEl := range cluster.Registers { + cpRegisters = append(cpRegisters, parseRegister(groupName, regEl, baseAddress, clusterName+"_")...) + } + // handle sub-clusters of registers + for _, subClusterEl := range cluster.Clusters { + subclusterName := strings.Replace(subClusterEl.Name, "[%s]", "", -1) + subclusterPrefix := subclusterName + "_" + subclusterOffset, err := strconv.ParseUint(subClusterEl.AddressOffset, 0, 32) + if err != nil { + panic(err) + } + subdim := *subClusterEl.Dim + subdimIncrement, err := strconv.ParseInt(subClusterEl.DimIncrement, 0, 32) + if err != nil { + panic(err) + } + + if subdim > 1 { + subcpRegisters := []*PeripheralField{} + subregSize := 0 + for _, regEl := range subClusterEl.Registers { + size, err := strconv.ParseInt(*regEl.Size, 0, 32) + if err != nil { + panic(err) + } + subregSize += int(size) + subcpRegisters = append(subcpRegisters, parseRegister(groupName, regEl, baseAddress+subclusterOffset, subclusterPrefix)...) + } + cpRegisters = append(cpRegisters, &PeripheralField{ + name: subclusterName, + address: baseAddress + subclusterOffset, + description: subClusterEl.Description, + registers: subcpRegisters, + array: subdim, + elementSize: int(subdimIncrement), + }) + } else { + for _, regEl := range subClusterEl.Registers { + cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, baseAddress+subclusterOffset, subclusterPrefix)...) + } + } + } + + sort.SliceStable(cpRegisters, func(i, j int) bool { + return cpRegisters[i].address < cpRegisters[j].address + }) + clusterPeripheral := &peripheral{ + Name: periphEl.Name + "_" + clusterName, + GroupName: groupName + "_" + clusterName, + Description: description + " - " + clusterName, + ClusterName: clusterName, + BaseAddress: baseAddress, + registers: cpRegisters, + } + peripheralsList = append(peripheralsList, clusterPeripheral) + peripheralDict[clusterPeripheral.Name] = clusterPeripheral + p.subtypes = append(p.subtypes, clusterPeripheral) + continue + } + dim = -1 + dimIncrement = -1 + } else { + dim = *cluster.Dim + if dim == 1 { + dimIncrement = -1 + } else { + inc, err := strconv.ParseUint(cluster.DimIncrement, 0, 32) + if err != nil { + panic(err) + } + dimIncrement = int(inc) + } + } + clusterRegisters := []*PeripheralField{} + for _, regEl := range cluster.Registers { + regName := groupName + if regName == "" { + regName = periphEl.Name + } + clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, baseAddress+clusterOffset, clusterPrefix)...) + } + sort.SliceStable(clusterRegisters, func(i, j int) bool { + return clusterRegisters[i].address < clusterRegisters[j].address + }) + if dimIncrement == -1 { + lastReg := clusterRegisters[len(clusterRegisters)-1] + lastAddress := lastReg.address + if lastReg.array != -1 { + lastAddress = lastReg.address + uint64(lastReg.array*lastReg.elementSize) + } + firstAddress := clusterRegisters[0].address + dimIncrement = int(lastAddress - firstAddress) + } + p.registers = append(p.registers, &PeripheralField{ + name: clusterName, + address: baseAddress + clusterOffset, + description: cluster.Description, + registers: clusterRegisters, + array: dim, + elementSize: dimIncrement, + }) + } + sort.SliceStable(p.registers, func(i, j int) bool { + return p.registers[i].address < p.registers[j].address + }) + } + + // Make a sorted list of interrupts. + interruptList := make([]*interrupt, 0, len(interrupts)) + for _, intr := range interrupts { + interruptList = append(interruptList, intr) + } + sort.SliceStable(interruptList, func(i, j int) bool { + if interruptList[i].Value != interruptList[j].Value { + return interruptList[i].Value < interruptList[j].Value + } + return interruptList[i].peripheralIndex < interruptList[j].peripheralIndex + }) + + // Properly format the license block, with comments. + licenseBlock := "" + if text := formatText(device.LicenseText); text != "" { + licenseBlock = "// " + strings.Replace(text, "\n", "\n// ", -1) + licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n") + } + + return &Device{ + metadata: map[string]string{ + "file": filepath.Base(path), + "descriptorSource": sourceURL, + "name": device.Name, + "nameLower": strings.ToLower(device.Name), + "description": strings.TrimSpace(device.Description), + "licenseBlock": licenseBlock, + }, + interrupts: interruptList, + peripherals: peripheralsList, + }, nil +} + +func addInterrupt(interrupts map[string]*interrupt, name string, index int, description string) { + if _, ok := interrupts[name]; ok { + if interrupts[name].Value != index { + // Note: some SVD files like the one for STM32H7x7 contain mistakes. + // Instead of throwing an error, simply log it. + fmt.Fprintf(os.Stderr, "interrupt with the same name has different indexes: %s (%d vs %d)", + name, interrupts[name].Value, index) + } + parts := strings.Split(interrupts[name].Description, " // ") + hasDescription := false + for _, part := range parts { + if part == description { + hasDescription = true + } + } + if !hasDescription { + interrupts[name].Description += " // " + description + } + } else { + interrupts[name] = &interrupt{ + Name: name, + peripheralIndex: len(interrupts), + Value: index, + Description: description, + } + } +} + +func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) []Bitfield { + var fields []Bitfield + for _, fieldEl := range fieldEls { + // Some bitfields (like the STM32H7x7) contain invalid bitfield + // names like "CNT[31]". Replace invalid characters with "_" when + // needed. + fieldName := cleanName(fieldEl.Name) + if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) { + fieldName = strings.ToUpper(fieldName) + } + + // Find the lsb/msb that is encoded in various ways. + // Standards are great, that's why there are so many to choose from! + var lsb, msb uint32 + if fieldEl.Lsb != nil && fieldEl.Msb != nil { + // try to use lsb/msb tags + lsb = *fieldEl.Lsb + msb = *fieldEl.Msb + } else if fieldEl.BitOffset != nil && fieldEl.BitWidth != nil { + // try to use bitOffset/bitWidth tags + lsb = *fieldEl.BitOffset + msb = *fieldEl.BitWidth + lsb - 1 + } else if fieldEl.BitRange != nil { + // try use bitRange + // example string: "[20:16]" + parts := strings.Split(strings.Trim(*fieldEl.BitRange, "[]"), ":") + l, err := strconv.ParseUint(parts[1], 0, 32) + if err != nil { + panic(err) + } + lsb = uint32(l) + m, err := strconv.ParseUint(parts[0], 0, 32) + if err != nil { + panic(err) + } + msb = uint32(m) + } else { + // this is an error. what to do? + fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName) + continue + } + + fields = append(fields, Bitfield{ + name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), + description: fmt.Sprintf("Position of %s field.", fieldName), + value: lsb, + }) + fields = append(fields, Bitfield{ + name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), + description: fmt.Sprintf("Bit mask of %s field.", fieldName), + value: (0xffffffff >> (31 - (msb - lsb))) << lsb, + }) + if lsb == msb { // single bit + fields = append(fields, Bitfield{ + name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), + description: fmt.Sprintf("Bit %s.", fieldName), + value: 1 << lsb, + }) + } + for _, enumEl := range fieldEl.EnumeratedValues { + enumName := enumEl.Name + if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { + enumName = strings.ToUpper(enumName) + } + enumDescription := strings.Replace(enumEl.Description, "\n", " ", -1) + enumValue, err := strconv.ParseUint(enumEl.Value, 0, 32) + if err != nil { + panic(err) + } + fields = append(fields, Bitfield{ + name: fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName), + description: enumDescription, + value: uint32(enumValue), + }) + } + } + return fields +} + +type Register struct { + element *SVDRegister + baseAddress uint64 +} + +func NewRegister(element *SVDRegister, baseAddress uint64) *Register { + return &Register{ + element: element, + baseAddress: baseAddress, + } +} + +func (r *Register) name() string { + return strings.Replace(r.element.Name, "[%s]", "", -1) +} + +func (r *Register) description() string { + return strings.Replace(r.element.Description, "\n", " ", -1) +} + +func (r *Register) address() uint64 { + offsetString := r.element.Offset + if offsetString == nil { + offsetString = r.element.AddressOffset + } + addr, err := strconv.ParseUint(*offsetString, 0, 32) + if err != nil { + panic(err) + } + return r.baseAddress + addr +} + +func (r *Register) dim() int { + if r.element.Dim == nil { + return -1 // no dim elements + } + dim, err := strconv.ParseInt(*r.element.Dim, 0, 32) + if err != nil { + panic(err) + } + return int(dim) +} + +func (r *Register) size() int { + if r.element.Size != nil { + size, err := strconv.ParseInt(*r.element.Size, 0, 32) + if err != nil { + panic(err) + } + return int(size) / 8 + } + return 4 +} + +func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField { + reg := NewRegister(regEl, baseAddress) + + if reg.dim() != -1 { + dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32) + if err != nil { + panic(err) + } + if strings.Contains(reg.name(), "%s") { + // a "spaced array" of registers, special processing required + // we need to generate a separate register for each "element" + var results []*PeripheralField + for i := uint64(0); i < uint64(reg.dim()); i++ { + regAddress := reg.address() + (i * dimIncrement) + results = append(results, &PeripheralField{ + name: strings.Replace(reg.name(), "%s", strconv.FormatUint(i, 10), -1), + address: regAddress, + description: reg.description(), + array: -1, + elementSize: reg.size(), + }) + } + // set first result bitfield + shortName := strings.ToUpper(strings.Replace(strings.Replace(reg.name(), "_%s", "", -1), "%s", "", -1)) + results[0].bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) + return results + } + } + regName := reg.name() + if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) { + regName = strings.ToUpper(regName) + } + + return []*PeripheralField{&PeripheralField{ + name: regName, + address: reg.address(), + description: reg.description(), + bitfields: parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix), + array: reg.dim(), + elementSize: reg.size(), + }} +} + +// The Go module for this device. +func writeGo(outdir string, device *Device) error { + outf, err := os.Create(filepath.Join(outdir, device.metadata["nameLower"]+".go")) + if err != nil { + return err + } + defer outf.Close() + w := bufio.NewWriter(outf) + + maxInterruptValue := 0 + for _, intr := range device.interrupts { + if intr.Value > maxInterruptValue { + maxInterruptValue = intr.Value + } + } + + t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT. +// Generated by gen-device-svd.go from {{.metadata.file}}, see {{.metadata.descriptorSource}} + +// +build {{.pkgName}},{{.metadata.nameLower}} + +// {{.metadata.description}} +// +{{.metadata.licenseBlock}} +package {{.pkgName}} + +import ( + "runtime/volatile" + "unsafe" +) + +// Some information about this device. +const ( + DEVICE = "{{.metadata.name}}" +) + +// Interrupt numbers +const ({{range .interrupts}} + IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}} + IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. +) + +// Peripherals. +var ( +{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}} +{{end}}) +`)) + err = t.Execute(w, map[string]interface{}{ + "metadata": device.metadata, + "interrupts": device.interrupts, + "peripherals": device.peripherals, + "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), + "interruptMax": maxInterruptValue, + }) + if err != nil { + return err + } + + // Define peripheral struct types. + for _, peripheral := range device.peripherals { + if peripheral.registers == nil { + // This peripheral was derived from another peripheral. No new type + // needs to be defined for it. + continue + } + fmt.Fprintf(w, "\n// %s\ntype %s_Type struct {\n", peripheral.Description, peripheral.GroupName) + address := peripheral.BaseAddress + for _, register := range peripheral.registers { + if register.registers == nil && address > register.address { + // In Nordic SVD files, these registers are deprecated or + // duplicates, so can be ignored. + //fmt.Fprintf(os.Stderr, "skip: %s.%s 0x%x - 0x%x %d\n", peripheral.Name, register.name, address, register.address, register.elementSize) + continue + } + + var regType string + eSize := register.elementSize + switch eSize { + case 4: + regType = "volatile.Register32" + case 2: + regType = "volatile.Register16" + case 1: + regType = "volatile.Register8" + default: + eSize = 4 + regType = "volatile.Register32" + } + + // insert padding, if needed + if address < register.address { + bytesNeeded := register.address - address + switch bytesNeeded { + case 1: + w.WriteString("\t_ volatile.Register8\n") + case 2: + w.WriteString("\t_ volatile.Register16\n") + case 3: + w.WriteString("\t_ [3]volatile.Register8\n") + default: + numSkip := (register.address - address) / uint64(eSize) + if numSkip == 1 { + fmt.Fprintf(w, "\t_ %s\n", regType) + } else { + fmt.Fprintf(w, "\t_ [%d]%s\n", numSkip, regType) + } + } + address = register.address + } + + lastCluster := false + if register.registers != nil { + // This is a cluster, not a register. Create the cluster type. + regType = "struct {\n" + subaddress := register.address + var subregSize uint64 + var subregType string + for _, subregister := range register.registers { + switch subregister.elementSize { + case 4: + subregType = "volatile.Register32" + case 2: + subregType = "volatile.Register16" + case 1: + subregType = "volatile.Register8" + } + if subregType == "" { + panic("unknown element size") + } + + if subregister.array != -1 { + subregType = fmt.Sprintf("[%d]%s", subregister.array, subregType) + } + if subaddress != subregister.address { + bytesNeeded := subregister.address - subaddress + if bytesNeeded == 1 { + regType += "\t\t_ volatile.Register8\n" + } else if bytesNeeded == 2 { + regType += "\t\t_ volatile.Register16\n" + } else { + numSkip := (subregister.address - subaddress) + if numSkip < 1 { + continue + } else if numSkip == 1 { + regType += "\t\t_ volatile.Register8\n" + } else { + regType += fmt.Sprintf("\t\t_ [%d]volatile.Register8\n", numSkip) + } + } + subaddress += bytesNeeded + } + if subregister.array != -1 { + subregSize = uint64(subregister.array * subregister.elementSize) + } else { + subregSize = uint64(subregister.elementSize) + } + subaddress += subregSize + regType += fmt.Sprintf("\t\t%s %s\n", subregister.name, subregType) + } + if register.array != -1 { + if subaddress != register.address+uint64(register.elementSize) { + numSkip := ((register.address + uint64(register.elementSize)) - subaddress) / subregSize + if numSkip <= 1 { + regType += fmt.Sprintf("\t\t_ %s\n", subregType) + } else { + regType += fmt.Sprintf("\t\t_ [%d]%s\n", numSkip, subregType) + } + } + } else { + lastCluster = true + } + regType += "\t}" + address = subaddress + } + + if register.array != -1 { + regType = fmt.Sprintf("[%d]%s", register.array, regType) + } + fmt.Fprintf(w, "\t%s %s\n", register.name, regType) + + // next address + if lastCluster { + lastCluster = false + } else if register.array != -1 { + address = register.address + uint64(register.elementSize*register.array) + } else { + address = register.address + uint64(register.elementSize) + } + } + w.WriteString("}\n") + } + + // Define bitfields. + for _, peripheral := range device.peripherals { + if peripheral.registers == nil { + // This peripheral was derived from another peripheral. Bitfields are + // already defined. + continue + } + fmt.Fprintf(w, "\n// Bitfields for %s: %s\nconst(", peripheral.Name, peripheral.Description) + for _, register := range peripheral.registers { + if len(register.bitfields) != 0 { + writeGoRegisterBitfields(w, register, register.name) + } + if register.registers == nil { + continue + } + for _, subregister := range register.registers { + writeGoRegisterBitfields(w, subregister, register.name+"."+subregister.name) + } + } + w.WriteString(")\n") + } + + return w.Flush() +} + +func writeGoRegisterBitfields(w *bufio.Writer, register *PeripheralField, name string) { + w.WriteString("\n\t// " + name) + if register.description != "" { + w.WriteString(": " + register.description) + } + w.WriteByte('\n') + for _, bitfield := range register.bitfields { + fmt.Fprintf(w, "\t%s = 0x%x", bitfield.name, bitfield.value) + if bitfield.description != "" { + w.WriteString(" // " + bitfield.description) + } + w.WriteByte('\n') + } +} + +// The interrupt vector, which is hard to write directly in Go. +func writeAsm(outdir string, device *Device) error { + outf, err := os.Create(filepath.Join(outdir, device.metadata["nameLower"]+".s")) + if err != nil { + return err + } + defer outf.Close() + w := bufio.NewWriter(outf) + + t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT. +// Generated by gen-device-svd.go from {{.file}}, see {{.descriptorSource}} + +// {{.description}} +// +{{.licenseBlock}} + +.syntax unified + +// This is the default handler for interrupts, if triggered but not defined. +.section .text.Default_Handler +.global Default_Handler +.type Default_Handler, %function +Default_Handler: + wfe + b Default_Handler + +// Avoid the need for repeated .weak and .set instructions. +.macro IRQ handler + .weak \handler + .set \handler, Default_Handler +.endm + +// Must set the "a" flag on the section: +// https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049 +// https://sourceware.org/binutils/docs/as/Section.html#ELF-Version +.section .isr_vector, "a", %progbits +.global __isr_vector + // Interrupt vector as defined by Cortex-M, starting with the stack top. + // On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading + // _stack_top and Reset_Handler. + .long _stack_top + .long Reset_Handler + .long NMI_Handler + .long HardFault_Handler + .long MemoryManagement_Handler + .long BusFault_Handler + .long UsageFault_Handler + .long 0 + .long 0 + .long 0 + .long 0 + .long SVC_Handler + .long DebugMon_Handler + .long 0 + .long PendSV_Handler + .long SysTick_Handler + + // Extra interrupts for peripherals defined by the hardware vendor. +`)) + err = t.Execute(w, device.metadata) + if err != nil { + return err + } + num := 0 + for _, intr := range device.interrupts { + if intr.Value == num-1 { + continue + } + if intr.Value < num { + panic("interrupt numbers are not sorted") + } + for intr.Value > num { + w.WriteString(" .long 0\n") + num++ + } + num++ + fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name) + } + + w.WriteString(` + // Define default implementations for interrupts, redirecting to + // Default_Handler when not implemented. + IRQ NMI_Handler + IRQ HardFault_Handler + IRQ MemoryManagement_Handler + IRQ BusFault_Handler + IRQ UsageFault_Handler + IRQ SVC_Handler + IRQ DebugMon_Handler + IRQ PendSV_Handler + IRQ SysTick_Handler +`) + for _, intr := range device.interrupts { + fmt.Fprintf(w, " IRQ %s_IRQHandler\n", intr.Name) + } + return w.Flush() +} + +func generate(indir, outdir, sourceURL string) error { + if _, err := os.Stat(indir); os.IsNotExist(err) { + fmt.Fprintln(os.Stderr, "cannot find input directory:", indir) + os.Exit(1) + } + os.MkdirAll(outdir, 0777) + + infiles, err := filepath.Glob(filepath.Join(indir, "*.svd")) + if err != nil { + fmt.Fprintln(os.Stderr, "could not read .svd files:", err) + os.Exit(1) + } + sort.Strings(infiles) + for _, infile := range infiles { + fmt.Println(infile) + device, err := readSVD(infile, sourceURL) + if err != nil { + return fmt.Errorf("failed to read: %w", err) + } + err = writeGo(outdir, device) + if err != nil { + return fmt.Errorf("failed to write Go file: %w", err) + } + err = writeAsm(outdir, device) + if err != nil { + return fmt.Errorf("failed to write assembly file: %w", err) + } + } + return nil +} + +func main() { + sourceURL := flag.String("source", "<unknown>", "source SVD file") + flag.Parse() + if flag.NArg() != 2 { + fmt.Fprintln(os.Stderr, "provide exactly two arguments: input directory (with .svd files) and output directory for generated files") + flag.PrintDefaults() + return + } + indir := flag.Arg(0) + outdir := flag.Arg(1) + err := generate(indir, outdir, *sourceURL) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} |