tinygo/tools/gen-device-svd.py
Ayke van Laethem 8d170d3bd2
all: change special type __volatile to pragma //go:volatile
This is one step towards removing unnecessary special casts in most
cases. It is also part of removing as much magic as possible from the
compiler (the pragma is explicit, the special name is not).
2018-09-28 13:17:03 +02:00

406 строки
15 КиБ
Python
Исполняемый файл

#!/usr/bin/python3
import sys
import os
from xml.dom import minidom
from glob import glob
from collections import OrderedDict
import re
import argparse
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, sourceURL):
# 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]).strip()
licenseTexts = root.getElementsByTagName('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.getElementsByTagName('peripherals')[0].getElementsByTagName('peripheral'):
name = getText(periphEl.getElementsByTagName('name')[0])
descriptionTags = periphEl.getElementsByTagName('description')
description = ''
if descriptionTags:
description = formatText(getText(descriptionTags[0]))
baseAddress = int(getText(periphEl.getElementsByTagName('baseAddress')[0]), 0)
groupNameTags = periphEl.getElementsByTagName('groupName')
groupName = None
if groupNameTags:
groupName = getText(groupNameTags[0])
if periphEl.hasAttribute('derivedFrom') or groupName in groups:
if periphEl.hasAttribute('derivedFrom'):
derivedFromName = periphEl.getAttribute('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
continue
peripheral = {
'name': name,
'groupName': groupName or name,
'description': description,
'baseAddress': baseAddress,
'registers': [],
}
device.peripherals.append(peripheral)
peripheralDict[name] = peripheral
if groupName and groupName not in groups:
groups[groupName] = 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(groupName or 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(groupName or name, regEl, baseAddress + clusterOffset, clusterPrefix))
else:
continue
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 parseSVDRegister(groupName, 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')
lsbTags = fieldEl.getElementsByTagName('lsb')
if len(lsbTags) == 1:
lsb = int(getText(lsbTags[0]))
else:
lsb = int(getText(fieldEl.getElementsByTagName('bitOffset')[0]))
msbTags = fieldEl.getElementsByTagName('msb')
if len(msbTags) == 1:
msb = int(getText(msbTags[0]))
else:
msb = int(getText(fieldEl.getElementsByTagName('bitWidth')[0])) + lsb - 1
fields.append({
'name': '{}_{}{}_{}_Pos'.format(groupName, namePrefix, regName, fieldName),
'description': 'Position of %s field.' % fieldName,
'value': lsb,
})
fields.append({
'name': '{}_{}{}_{}_Msk'.format(groupName, namePrefix, 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, namePrefix, regName, fieldName),
'description': 'Bit %s.' % fieldName,
'value': 1 << lsb,
})
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(groupName, namePrefix, regName, fieldName, enumName),
'description': enumDescription,
'value': enumValue,
})
return {
'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-svd.py from {file}, see {descriptorSource}
// +build {pkgName},{nameLower}
// {description}
//
{licenseBlock}
package {pkgName}
import "unsafe"
// Special type that causes loads/stores to be volatile (necessary for
// memory-mapped registers).
//go:volatile
type RegValue uint32
// 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 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']
padNumber = 0
for register in peripheral['registers']:
if address > register['address']:
# In Nordic SVD files, these registers are deprecated or
# duplicates, so can be ignored.
#print('skip: %s.%s' % (peripheral['name'], register['name']))
continue
# insert padding, if needed
if address < register['address']:
numSkip = (register['address'] - address) // 4
if numSkip == 1:
out.write('\t_padding{padNumber} RegValue\n'.format(padNumber=padNumber))
else:
out.write('\t_padding{padNumber} [{num}]RegValue\n'.format(padNumber=padNumber, num=numSkip))
padNumber += 1
regType = 'RegValue'
if register['array'] is not None:
regType = '[{}]RegValue'.format(register['array'])
out.write('\t{name} {regType}\n'.format(**register, regType=regType))
# next address
if register['array'] is not None and 1:
address = register['address'] + 4 * register['array']
else:
address = register['address'] + 4
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 bitfields.
for peripheral in device.peripherals:
if 'registers' not in peripheral:
# This peripheral was derived from another peripheral. Bitfields are
# already defined.
continue
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
out.write('\n\t// {name}'.format(**register))
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 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
.section .isr_vector
.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:
raise ValueError('interrupt numbers are not sorted or contain a duplicate')
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)