diff options
author | Ayke van Laethem <[email protected]> | 2019-11-28 16:11:54 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-12-07 16:04:47 +0100 |
commit | 24259cbb5f7b59090bb51d469f18a5171299db82 (patch) | |
tree | 1d973bd549f6f0ff5634ca210b3c45d4e2e9d8d4 /tools | |
parent | 2f932a9eee13248a8cb2b31837c33d4d956f1362 (diff) | |
download | tinygo-24259cbb5f7b59090bb51d469f18a5171299db82.tar.gz tinygo-24259cbb5f7b59090bb51d469f18a5171299db82.zip |
tools: rewrite gen-device-avr in Go
This brings a big speedup. Not counting gofmt time,
`make gen-device-avr` became about 3x faster. In the future, it might be
an idea to generate the AST in-memory and write it out already
formatted.
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/gen-device-avr.py | 291 | ||||
-rwxr-xr-x | tools/gen-device-avr/gen-device-avr.go | 464 |
2 files changed, 464 insertions, 291 deletions
diff --git a/tools/gen-device-avr.py b/tools/gen-device-avr.py deleted file mode 100755 index becd4cd8f..000000000 --- a/tools/gen-device-avr.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import sys -import os -from xml.dom import minidom -from glob import glob -from collections import OrderedDict -import re - -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 readATDF(path): - # Read Atmel device descriptor files. - # See: http://packs.download.atmel.com - - device = Device() - - xml = minidom.parse(path) - device = xml.getElementsByTagName('device')[0] - deviceName = device.getAttribute('name') - arch = device.getAttribute('architecture') - family = device.getAttribute('family') - - memorySizes = {} - for el in device.getElementsByTagName('address-space'): - addressSpace = { - 'size': int(el.getAttribute('size'), 0), - 'segments': {}, - } - memorySizes[el.getAttribute('name')] = addressSpace - for segmentEl in el.getElementsByTagName('memory-segment'): - addressSpace['segments'][segmentEl.getAttribute('name')] = int(segmentEl.getAttribute('size'), 0) - - device.interrupts = [] - for el in device.getElementsByTagName('interrupts')[0].getElementsByTagName('interrupt'): - device.interrupts.append({ - 'index': int(el.getAttribute('index')), - 'name': el.getAttribute('name'), - 'description': el.getAttribute('caption'), - }) - - allRegisters = {} - commonRegisters = {} - - device.peripherals = [] - for el in xml.getElementsByTagName('modules')[0].getElementsByTagName('module'): - peripheral = { - 'name': el.getAttribute('name'), - 'description': el.getAttribute('caption'), - 'registers': [], - } - device.peripherals.append(peripheral) - for regElGroup in el.getElementsByTagName('register-group'): - for regEl in regElGroup.getElementsByTagName('register'): - size = int(regEl.getAttribute('size')) - regName = regEl.getAttribute('name') - regOffset = int(regEl.getAttribute('offset'), 0) - reg = { - 'description': regEl.getAttribute('caption'), - 'bitfields': [], - 'array': None, - } - if size == 1: - reg['variants'] = [{ - 'name': regName, - 'address': regOffset, - }] - elif size == 2: - reg['variants'] = [{ - 'name': regName + 'L', - 'address': regOffset, - }, { - 'name': regName + 'H', - 'address': regOffset + 1, - }] - else: - # TODO - continue - - for bitfieldEl in regEl.getElementsByTagName('bitfield'): - mask = bitfieldEl.getAttribute('mask') - if len(mask) == 2: - # Two devices (ATtiny102 and ATtiny104) appear to have - # an error in the bitfields, leaving out the '0x' - # prefix. - mask = '0x' + mask - reg['bitfields'].append({ - 'name': regName + '_' + bitfieldEl.getAttribute('name'), - 'description': bitfieldEl.getAttribute('caption'), - 'value': int(mask, 0), - }) - - if regName in allRegisters: - firstReg = allRegisters[regName] - if firstReg['register'] in firstReg['peripheral']['registers']: - firstReg['peripheral']['registers'].remove(firstReg['register']) - if firstReg['address'] != regOffset: - continue # TODO - commonRegisters = allRegisters[regName]['register'] - continue - else: - allRegisters[regName] = {'address': regOffset, 'register': reg, 'peripheral': peripheral} - - peripheral['registers'].append(reg) - - ramSize = 0 # for devices with no RAM - for ramSegmentName in ['IRAM', 'INTERNAL_SRAM', 'SRAM']: - if ramSegmentName in memorySizes['data']['segments']: - ramSize = memorySizes['data']['segments'][ramSegmentName] - - device.metadata = { - 'file': os.path.basename(path), - 'descriptorSource': 'http://packs.download.atmel.com/', - 'name': deviceName, - 'nameLower': deviceName.lower(), - 'description': 'Device information for the {}.'.format(deviceName), - 'arch': arch, - 'family': family, - 'flashSize': memorySizes['prog']['size'], - 'ramSize': ramSize, - 'numInterrupts': len(device.interrupts), - } - - return device - -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-avr.py from {file}, see {descriptorSource} - -// +build {pkgName},{nameLower} - -// {description} -package {pkgName} - -import ( - "runtime/volatile" - "unsafe" -) - -// 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 (') - first = True - for peripheral in device.peripherals: - out.write('\n\t// {description}\n'.format(**peripheral)) - for register in peripheral['registers']: - for variant in register['variants']: - out.write('\t{name} = (*volatile.Register8)(unsafe.Pointer(uintptr(0x{address:x})))\n'.format(**variant)) - 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']: - name = bitfield['name'] - value = bitfield['value'] - if '{:08b}'.format(value).count('1') == 1: - out.write('\t{name} = 0x{value:x}'.format(**bitfield)) - if bitfield['description']: - out.write(' // {description}'.format(**bitfield)) - out.write('\n') - else: - n = 0 - for i in range(8): - if (value >> i) & 1 == 0: continue - out.write('\t{}{} = 0x{:x}'.format(name, n, 1 << i)) - if bitfield['description']: - out.write(' // {description}'.format(**bitfield)) - n += 1 - 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-avr.py from {file}, see {descriptorSource} - -; This is the default handler for interrupts, if triggered but not defined. -; Sleep inside so that an accidentally triggered interrupt won't drain the -; battery of a battery-powered device. -.section .text.__vector_default -.global __vector_default -__vector_default: - sleep - rjmp __vector_default - -; Avoid the need for repeated .weak and .set instructions. -.macro IRQ handler - .weak \\handler - .set \\handler, __vector_default -.endm - -; The interrupt vector of this device. Must be placed at address 0 by the linker. -.section .vectors -.global __vectors -'''.format(**device.metadata)) - num = 0 - for intr in device.interrupts: - jmp = 'jmp' - if device.metadata['flashSize'] <= 8 * 1024: - # When a device has 8kB or less flash, rjmp (2 bytes) must be used - # instead of jmp (4 bytes). - # https://www.avrfreaks.net/forum/rjmp-versus-jmp - jmp = 'rjmp' - if intr['index'] < num: - # Some devices have duplicate interrupts, probably for historical - # reasons. - continue - while intr['index'] > num: - out.write(' {jmp} __vector_default\n'.format(jmp=jmp)) - num += 1 - num += 1 - out.write(' {jmp} __vector_{name}\n'.format(jmp=jmp, **intr)) - - out.write(''' - ; Define default implementations for interrupts, redirecting to - ; __vector_default when not implemented. -''') - for intr in device.interrupts: - out.write(' IRQ __vector_{name}\n'.format(**intr)) - -def writeLD(outdir, device): - # Variables for the linker script. - out = open(outdir + '/' + device.metadata['nameLower'] + '.ld', 'w') - out.write('''\ -/* Automatically generated file. DO NOT EDIT. */ -/* Generated by gen-device-avr.py from {file}, see {descriptorSource} */ - -__flash_size = 0x{flashSize:x}; -__ram_size = 0x{ramSize:x}; -__num_isrs = {numInterrupts}; -'''.format(**device.metadata)) - out.close() - - -def generate(indir, outdir): - for filepath in sorted(glob(indir + '/*.atdf')): - print(filepath) - device = readATDF(filepath) - writeGo(outdir, device) - writeAsm(outdir, device) - writeLD(outdir, device) - - -if __name__ == '__main__': - indir = sys.argv[1] # directory with register descriptor files (*.atdf) - outdir = sys.argv[2] # output directory - generate(indir, outdir) diff --git a/tools/gen-device-avr/gen-device-avr.go b/tools/gen-device-avr/gen-device-avr.go new file mode 100755 index 000000000..d49162c9d --- /dev/null +++ b/tools/gen-device-avr/gen-device-avr.go @@ -0,0 +1,464 @@ +package main + +import ( + "bufio" + "encoding/xml" + "fmt" + "html/template" + "math/bits" + "os" + "path/filepath" + "strconv" + "strings" +) + +type AVRToolsDeviceFile struct { + XMLName xml.Name `xml:"avr-tools-device-file"` + Devices []struct { + Name string `xml:"name,attr"` + Architecture string `xml:"architecture,attr"` + Family string `xml:"family,attr"` + AddressSpaces []struct { + Name string `xml:"name,attr"` + Size string `xml:"size,attr"` + MemorySegments []struct { + Name string `xml:"name,attr"` + Size string `xml:"size,attr"` + } `xml:"memory-segment"` + } `xml:"address-spaces>address-space"` + Interrupts []Interrupt `xml:"interrupts>interrupt"` + } `xml:"devices>device"` + Modules []struct { + Name string `xml:"name,attr"` + Caption string `xml:"caption,attr"` + RegisterGroup struct { + Name string `xml:"name,attr"` + Caption string `xml:"caption,attr"` + Registers []struct { + Name string `xml:"name,attr"` + Caption string `xml:"caption,attr"` + Offset string `xml:"offset,attr"` + Size int `xml:"size,attr"` + Bitfields []struct { + Name string `xml:"name,attr"` + Caption string `xml:"caption,attr"` + Mask string `xml:"mask,attr"` + } `xml:"bitfield"` + } `xml:"register"` + } `xml:"register-group"` + } `xml:"modules>module"` +} + +type Device struct { + metadata map[string]interface{} + interrupts []Interrupt + peripherals []*Peripheral +} + +type AddressSpace struct { + Size string + Segments map[string]int +} + +type Interrupt struct { + Index int `xml:"index,attr"` + Name string `xml:"name,attr"` + Caption string `xml:"caption,attr"` +} + +type Peripheral struct { + Name string + Caption string + Registers []*Register +} + +type Register struct { + Caption string + Variants []RegisterVariant + Bitfields []Bitfield + peripheral *Peripheral +} + +type RegisterVariant struct { + Name string + Address int64 +} + +type Bitfield struct { + Name string + Caption string + Mask uint +} + +func readATDF(path string) (*Device, error) { + // Read Atmel device descriptor files. + // See: http://packs.download.atmel.com + + // Open the XML file. + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + decoder := xml.NewDecoder(f) + xml := &AVRToolsDeviceFile{} + err = decoder.Decode(xml) + if err != nil { + return nil, err + } + + device := xml.Devices[0] + + memorySizes := make(map[string]*AddressSpace, len(device.AddressSpaces)) + for _, el := range device.AddressSpaces { + memorySizes[el.Name] = &AddressSpace{ + Size: el.Size, + Segments: make(map[string]int), + } + for _, segmentEl := range el.MemorySegments { + size, err := strconv.ParseInt(segmentEl.Size, 0, 32) + if err != nil { + return nil, err + } + memorySizes[el.Name].Segments[segmentEl.Name] = int(size) + } + } + + allRegisters := map[string]*Register{} + + var peripherals []*Peripheral + for _, el := range xml.Modules { + peripheral := &Peripheral{ + Name: el.Name, + Caption: el.Caption, + } + peripherals = append(peripherals, peripheral) + + regElGroup := el.RegisterGroup + for _, regEl := range regElGroup.Registers { + regOffset, err := strconv.ParseInt(regEl.Offset, 0, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse offset %#v of register %s: %v", regEl.Offset, regEl.Name, err) + } + reg := &Register{ + Caption: regEl.Caption, + peripheral: peripheral, + } + switch regEl.Size { + case 1: + reg.Variants = []RegisterVariant{ + { + Name: regEl.Name, + Address: regOffset, + }, + } + case 2: + reg.Variants = []RegisterVariant{ + { + Name: regEl.Name + "L", + Address: regOffset, + }, + { + Name: regEl.Name + "H", + Address: regOffset + 1, + }, + } + default: + // TODO + continue + } + + for _, bitfieldEl := range regEl.Bitfields { + mask := bitfieldEl.Mask + if len(mask) == 2 { + // Two devices (ATtiny102 and ATtiny104) appear to have an + // error in the bitfields, leaving out the '0x' prefix. + mask = "0x" + mask + } + maskInt, err := strconv.ParseUint(mask, 0, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse mask %#v of bitfield %s: %v", mask, bitfieldEl.Name, err) + } + reg.Bitfields = append(reg.Bitfields, Bitfield{ + Name: regEl.Name + "_" + bitfieldEl.Name, + Caption: bitfieldEl.Caption, + Mask: uint(maskInt), + }) + } + + if _, ok := allRegisters[regEl.Name]; ok { + firstReg := allRegisters[regEl.Name] + for i := 0; i < len(firstReg.peripheral.Registers); i++ { + if firstReg.peripheral.Registers[i] == firstReg { + firstReg.peripheral.Registers = append(firstReg.peripheral.Registers[:i], firstReg.peripheral.Registers[i+1:]...) + break + } + } + continue + } else { + allRegisters[regEl.Name] = reg + } + + peripheral.Registers = append(peripheral.Registers, reg) + } + } + + ramSize := 0 // for devices with no RAM + for _, ramSegmentName := range []string{"IRAM", "INTERNAL_SRAM", "SRAM"} { + if segment, ok := memorySizes["data"].Segments[ramSegmentName]; ok { + ramSize = segment + } + } + + flashSize, err := strconv.ParseInt(memorySizes["prog"].Size, 0, 32) + if err != nil { + return nil, err + } + + return &Device{ + metadata: map[string]interface{}{ + "file": filepath.Base(path), + "descriptorSource": "http://packs.download.atmel.com/", + "name": device.Name, + "nameLower": strings.ToLower(device.Name), + "description": fmt.Sprintf("Device information for the %s.", device.Name), + "arch": device.Architecture, + "family": device.Family, + "flashSize": int(flashSize), + "ramSize": ramSize, + "numInterrupts": len(device.Interrupts), + }, + interrupts: device.Interrupts, + peripherals: peripherals, + }, nil +} + +func writeGo(outdir string, device *Device) error { + // The Go module for this device. + outf, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".go") + if err != nil { + return err + } + defer outf.Close() + w := bufio.NewWriter(outf) + + maxInterruptNum := 0 + for _, intr := range device.interrupts { + if intr.Index > maxInterruptNum { + maxInterruptNum = intr.Index + } + } + + t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT. +// Generated by gen-device-avr.go from {{.metadata.file}}, see {{.metadata.descriptorSource}} + +// +build {{.pkgName}},{{.metadata.nameLower}} + +// {{.metadata.description}} +package {{.pkgName}} + +import ( + "runtime/volatile" + "unsafe" +) + +// Some information about this device. +const ( + DEVICE = "{{.metadata.name}}" + ARCH = "{{.metadata.arch}}" + FAMILY = "{{.metadata.family}}" +) + +// Interrupts +const ({{range .interrupts}} + IRQ_{{.Name}} = {{.Index}} // {{.Caption}}{{end}} + IRQ_max = {{.interruptMax}} // Highest interrupt number on this device. +) + +// Peripherals. +var ({{range .peripherals}} + // {{.Caption}} +{{range .Registers}}{{range .Variants}} {{.Name}} = (*volatile.Register8)(unsafe.Pointer(uintptr(0x{{printf "%x" .Address}}))) +{{end}}{{end}}{{end}}) +`)) + err = t.Execute(w, map[string]interface{}{ + "metadata": device.metadata, + "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), + "interrupts": device.interrupts, + "interruptMax": maxInterruptNum, + "peripherals": device.peripherals, + }) + if err != nil { + return err + } + + // Write bitfields. + for _, peripheral := range device.peripherals { + // Only write bitfields when there are any. + numFields := 0 + for _, r := range peripheral.Registers { + numFields += len(r.Bitfields) + } + if numFields == 0 { + continue + } + + fmt.Fprintf(w, "\n// Bitfields for %s: %s\nconst(", peripheral.Name, peripheral.Caption) + for _, register := range peripheral.Registers { + if len(register.Bitfields) == 0 { + continue + } + for _, variant := range register.Variants { + fmt.Fprintf(w, "\n\t// %s", variant.Name) + if register.Caption != "" { + fmt.Fprintf(w, ": %s", register.Caption) + } + fmt.Fprintf(w, "\n") + } + for _, bitfield := range register.Bitfields { + if bits.OnesCount(bitfield.Mask) == 1 { + fmt.Fprintf(w, "\t%s = 0x%x", bitfield.Name, bitfield.Mask) + if len(bitfield.Caption) != 0 { + fmt.Fprintf(w, " // %s", bitfield.Caption) + } + fmt.Fprintf(w, "\n") + } else { + n := 0 + for i := uint(0); i < 8; i++ { + if (bitfield.Mask>>i)&1 == 0 { + continue + } + fmt.Fprintf(w, "\t%s%d = 0x%x", bitfield.Name, n, 1<<i) + if len(bitfield.Caption) != 0 { + fmt.Fprintf(w, " // %s", bitfield.Caption) + } + n++ + fmt.Fprintf(w, "\n") + } + } + } + } + fmt.Fprintf(w, ")\n") + } + return w.Flush() +} + +func writeAsm(outdir string, device *Device) error { + // The interrupt vector, which is hard to write directly in Go. + out, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".s") + if err != nil { + return err + } + defer out.Close() + t := template.Must(template.New("asm").Parse( + `; Automatically generated file. DO NOT EDIT. +; Generated by gen-device-avr.go from {{.file}}, see {{.descriptorSource}} + +; This is the default handler for interrupts, if triggered but not defined. +; Sleep inside so that an accidentally triggered interrupt won't drain the +; battery of a battery-powered device. +.section .text.__vector_default +.global __vector_default +__vector_default: + sleep + rjmp __vector_default + +; Avoid the need for repeated .weak and .set instructions. +.macro IRQ handler + .weak \handler + .set \handler, __vector_default +.endm + +; The interrupt vector of this device. Must be placed at address 0 by the linker. +.section .vectors +.global __vectors +`)) + err = t.Execute(out, device.metadata) + if err != nil { + return err + } + num := 0 + for _, intr := range device.interrupts { + jmp := "jmp" + if device.metadata["flashSize"].(int) <= 8*1024 { + // When a device has 8kB or less flash, rjmp (2 bytes) must be used + // instead of jmp (4 bytes). + // https://www.avrfreaks.net/forum/rjmp-versus-jmp + jmp = "rjmp" + } + if intr.Index < num { + // Some devices have duplicate interrupts, probably for historical + // reasons. + continue + } + for intr.Index > num { + fmt.Fprintf(out, " %s __vector_default\n", jmp) + num++ + } + num++ + fmt.Fprintf(out, " %s __vector_%s\n", jmp, intr.Name) + } + + fmt.Fprint(out, ` + ; Define default implementations for interrupts, redirecting to + ; __vector_default when not implemented. +`) + for _, intr := range device.interrupts { + fmt.Fprintf(out, " IRQ __vector_%s\n", intr.Name) + } + return nil +} + +func writeLD(outdir string, device *Device) error { + // Variables for the linker script. + out, err := os.Create(outdir + "/" + device.metadata["nameLower"].(string) + ".ld") + if err != nil { + return err + } + defer out.Close() + t := template.Must(template.New("ld").Parse(`/* Automatically generated file. DO NOT EDIT. */ +/* Generated by gen-device-avr.go from {{.file}}, see {{.descriptorSource}} */ + +__flash_size = 0x{{printf "%x" .flashSize}}; +__ram_size = 0x{{printf "%x" .ramSize}}; +__num_isrs = {{.numInterrupts}}; +`)) + return t.Execute(out, device.metadata) +} + +func generate(indir, outdir string) error { + matches, err := filepath.Glob(indir + "/*.atdf") + if err != nil { + return err + } + for _, filepath := range matches { + fmt.Println(filepath) + device, err := readATDF(filepath) + if err != nil { + return err + } + err = writeGo(outdir, device) + if err != nil { + return err + } + err = writeAsm(outdir, device) + if err != nil { + return err + } + err = writeLD(outdir, device) + if err != nil { + return err + } + } + return nil +} + +func main() { + indir := os.Args[1] // directory with register descriptor files (*.atdf) + outdir := os.Args[2] // output directory + err := generate(indir, outdir) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} |