github.com/aykevl/tinygo@v0.5.0/tools/gen-device-svd.py (about) 1 #!/usr/bin/env python3 2 3 import sys 4 import os 5 from xml.etree import ElementTree 6 from glob import glob 7 from collections import OrderedDict 8 import re 9 import argparse 10 11 class Device: 12 # dummy 13 pass 14 15 def getText(element): 16 if element is None: 17 return "None" 18 return ''.join(element.itertext()) 19 20 def formatText(text): 21 text = re.sub('[ \t\n]+', ' ', text) # Collapse whitespace (like in HTML) 22 text = text.replace('\\n ', '\n') 23 text = text.strip() 24 return text 25 26 def readSVD(path, sourceURL): 27 # Read ARM SVD files. 28 device = Device() 29 xml = ElementTree.parse(path) 30 root = xml.getroot() 31 deviceName = getText(root.find('name')) 32 deviceDescription = getText(root.find('description')).strip() 33 licenseTexts = root.findall('licenseText') 34 if len(licenseTexts) == 0: 35 licenseText = None 36 elif len(licenseTexts) == 1: 37 licenseText = formatText(getText(licenseTexts[0])) 38 else: 39 raise ValueError('multiple <licenseText> elements') 40 41 device.peripherals = [] 42 peripheralDict = {} 43 groups = {} 44 45 interrupts = OrderedDict() 46 47 for periphEl in root.findall('./peripherals/peripheral'): 48 name = getText(periphEl.find('name')) 49 descriptionTags = periphEl.findall('description') 50 description = '' 51 if descriptionTags: 52 description = formatText(getText(descriptionTags[0])) 53 baseAddress = int(getText(periphEl.find('baseAddress')), 0) 54 groupNameTags = periphEl.findall('groupName') 55 groupName = None 56 if groupNameTags: 57 groupName = getText(groupNameTags[0]) 58 59 interruptEls = periphEl.findall('interrupt') 60 for interrupt in interruptEls: 61 intrName = getText(interrupt.find('name')) 62 intrIndex = int(getText(interrupt.find('value'))) 63 addInterrupt(interrupts, intrName, intrIndex, description) 64 # As a convenience, also use the peripheral name as the interrupt 65 # name. Only do that for the nrf for now, as the stm32 .svd files 66 # don't always put interrupts in the correct peripheral... 67 if len(interruptEls) == 1 and deviceName.startswith('nrf'): 68 addInterrupt(interrupts, name, intrIndex, description) 69 70 if periphEl.get('derivedFrom') or groupName in groups: 71 if periphEl.get('derivedFrom'): 72 derivedFromName = periphEl.get('derivedFrom') 73 derivedFrom = peripheralDict[derivedFromName] 74 else: 75 derivedFrom = groups[groupName] 76 peripheral = { 77 'name': name, 78 'groupName': derivedFrom['groupName'], 79 'description': description or derivedFrom['description'], 80 'baseAddress': baseAddress, 81 } 82 device.peripherals.append(peripheral) 83 peripheralDict[name] = peripheral 84 if 'subtypes' in derivedFrom: 85 for subtype in derivedFrom['subtypes']: 86 subp = { 87 'name': name + "_"+subtype['clusterName'], 88 'groupName': subtype['groupName'], 89 'description': subtype['description'], 90 'baseAddress': baseAddress, 91 } 92 device.peripherals.append(subp) 93 continue 94 95 peripheral = { 96 'name': name, 97 'groupName': groupName or name, 98 'description': description, 99 'baseAddress': baseAddress, 100 'registers': [], 101 'subtypes': [], 102 } 103 device.peripherals.append(peripheral) 104 peripheralDict[name] = peripheral 105 106 if groupName and groupName not in groups: 107 groups[groupName] = peripheral 108 109 regsEls = periphEl.findall('registers') 110 if regsEls: 111 if len(regsEls) != 1: 112 raise ValueError('expected just one <registers> in a <peripheral>') 113 for register in regsEls[0].findall('register'): 114 peripheral['registers'].extend(parseRegister(groupName or name, register, baseAddress)) 115 for cluster in regsEls[0].findall('cluster'): 116 clusterName = getText(cluster.find('name')).replace('[%s]', '') 117 clusterDescription = getText(cluster.find('description')) 118 clusterPrefix = clusterName + '_' 119 clusterOffset = int(getText(cluster.find('addressOffset')), 0) 120 if cluster.find('dim') is None: 121 if clusterOffset is 0: 122 # make this a separate peripheral 123 cpRegisters = [] 124 for regEl in cluster.findall('register'): 125 cpRegisters.extend(parseRegister(groupName, regEl, baseAddress, clusterName+"_")) 126 cpRegisters.sort(key=lambda r: r['address']) 127 clusterPeripheral = { 128 'name': name+ "_" +clusterName, 129 'groupName': groupName+ "_" +clusterName, 130 'description': description+ " - " +clusterName, 131 'clusterName': clusterName, 132 'baseAddress': baseAddress, 133 'registers': cpRegisters, 134 } 135 device.peripherals.append(clusterPeripheral) 136 peripheral['subtypes'].append(clusterPeripheral) 137 continue 138 dim = None 139 dimIncrement = None 140 else: 141 dim = int(getText(cluster.find('dim'))) 142 dimIncrement = int(getText(cluster.find('dimIncrement')), 0) 143 clusterRegisters = [] 144 for regEl in cluster.findall('register'): 145 clusterRegisters.extend(parseRegister(groupName or name, regEl, baseAddress + clusterOffset, clusterPrefix)) 146 clusterRegisters.sort(key=lambda r: r['address']) 147 if dimIncrement is None: 148 lastReg = clusterRegisters[-1] 149 lastAddress = lastReg['address'] 150 if lastReg['array'] is not None: 151 lastAddress = lastReg['address'] + lastReg['array'] * lastReg['elementsize'] 152 firstAddress = clusterRegisters[0]['address'] 153 dimIncrement = lastAddress - firstAddress 154 peripheral['registers'].append({ 155 'name': clusterName, 156 'address': baseAddress + clusterOffset, 157 'description': clusterDescription, 158 'registers': clusterRegisters, 159 'array': dim, 160 'elementsize': dimIncrement, 161 }) 162 peripheral['registers'].sort(key=lambda r: r['address']) 163 164 device.interrupts = sorted(interrupts.values(), key=lambda v: v['index']) 165 licenseBlock = '' 166 if licenseText is not None: 167 licenseBlock = '// ' + licenseText.replace('\n', '\n// ') 168 licenseBlock = '\n'.join(map(str.rstrip, licenseBlock.split('\n'))) # strip trailing whitespace 169 device.metadata = { 170 'file': os.path.basename(path), 171 'descriptorSource': sourceURL, 172 'name': deviceName, 173 'nameLower': deviceName.lower(), 174 'description': deviceDescription, 175 'licenseBlock': licenseBlock, 176 } 177 178 return device 179 180 def addInterrupt(interrupts, intrName, intrIndex, description): 181 if intrName in interrupts: 182 if interrupts[intrName]['index'] != intrIndex: 183 raise ValueError('interrupt with the same name has different indexes: %s (%d vs %d)' 184 % (intrName, interrupts[intrName]['index'], intrIndex)) 185 if description not in interrupts[intrName]['description'].split(' // '): 186 interrupts[intrName]['description'] += ' // ' + description 187 else: 188 interrupts[intrName] = { 189 'name': intrName, 190 'index': intrIndex, 191 'description': description, 192 } 193 194 def parseBitfields(groupName, regName, fieldsEls, bitfieldPrefix=''): 195 fields = [] 196 if fieldsEls: 197 for fieldEl in fieldsEls[0].findall('field'): 198 fieldName = getText(fieldEl.find('name')) 199 descrEls = fieldEl.findall('description') 200 lsbTags = fieldEl.findall('lsb') 201 if len(lsbTags) == 1: 202 lsb = int(getText(lsbTags[0])) 203 else: 204 lsb = int(getText(fieldEl.find('bitOffset'))) 205 msbTags = fieldEl.findall('msb') 206 if len(msbTags) == 1: 207 msb = int(getText(msbTags[0])) 208 else: 209 msb = int(getText(fieldEl.find('bitWidth'))) + lsb - 1 210 fields.append({ 211 'name': '{}_{}{}_{}_Pos'.format(groupName, bitfieldPrefix, regName, fieldName), 212 'description': 'Position of %s field.' % fieldName, 213 'value': lsb, 214 }) 215 fields.append({ 216 'name': '{}_{}{}_{}_Msk'.format(groupName, bitfieldPrefix, regName, fieldName), 217 'description': 'Bit mask of %s field.' % fieldName, 218 'value': (0xffffffff >> (31 - (msb - lsb))) << lsb, 219 }) 220 if lsb == msb: # single bit 221 fields.append({ 222 'name': '{}_{}{}_{}'.format(groupName, bitfieldPrefix, regName, fieldName), 223 'description': 'Bit %s.' % fieldName, 224 'value': 1 << lsb, 225 }) 226 for enumEl in fieldEl.findall('enumeratedValues/enumeratedValue'): 227 enumName = getText(enumEl.find('name')) 228 enumDescription = getText(enumEl.find('description')) 229 enumValue = int(getText(enumEl.find('value')), 0) 230 fields.append({ 231 'name': '{}_{}{}_{}_{}'.format(groupName, bitfieldPrefix, regName, fieldName, enumName), 232 'description': enumDescription, 233 'value': enumValue, 234 }) 235 return fields 236 237 def parseRegister(groupName, regEl, baseAddress, bitfieldPrefix=''): 238 regName = getText(regEl.find('name')) 239 regDescription = getText(regEl.find('description')) 240 offsetEls = regEl.findall('offset') 241 if not offsetEls: 242 offsetEls = regEl.findall('addressOffset') 243 address = baseAddress + int(getText(offsetEls[0]), 0) 244 245 size = 4 246 elSizes = regEl.findall('size') 247 if elSizes: 248 size = int(getText(elSizes[0]), 0) // 8 249 250 dimEls = regEl.findall('dim') 251 fieldsEls = regEl.findall('fields') 252 253 array = None 254 if dimEls: 255 array = int(getText(dimEls[0]), 0) 256 dimIncrement = int(getText(regEl.find('dimIncrement')), 0) 257 if "[%s]" in regName: 258 # just a normal array of registers 259 regName = regName.replace('[%s]', '') 260 elif "%s" in regName: 261 # a "spaced array" of registers, special processing required 262 # we need to generate a separate register for each "element" 263 results = [] 264 for i in range(array): 265 regAddress = address + (i * dimIncrement) 266 results.append({ 267 'name': regName.replace('%s', str(i)), 268 'address': regAddress, 269 'description': regDescription.replace('\n', ' '), 270 'bitfields': [], 271 'array': None, 272 'elementsize': size, 273 }) 274 # set first result bitfield 275 shortName = regName.replace('_%s', '').replace('%s', '') 276 results[0]['bitfields'] = parseBitfields(groupName, shortName, fieldsEls, bitfieldPrefix) 277 return results 278 279 return [{ 280 'name': regName, 281 'address': address, 282 'description': regDescription.replace('\n', ' '), 283 'bitfields': parseBitfields(groupName, regName, fieldsEls, bitfieldPrefix), 284 'array': array, 285 'elementsize': size, 286 }] 287 288 def writeGo(outdir, device): 289 # The Go module for this device. 290 out = open(outdir + '/' + device.metadata['nameLower'] + '.go', 'w') 291 pkgName = os.path.basename(outdir.rstrip('/')) 292 out.write('''\ 293 // Automatically generated file. DO NOT EDIT. 294 // Generated by gen-device-svd.py from {file}, see {descriptorSource} 295 296 // +build {pkgName},{nameLower} 297 298 // {description} 299 // 300 {licenseBlock} 301 package {pkgName} 302 303 import "unsafe" 304 305 // Special types that cause loads/stores to be volatile (necessary for 306 // memory-mapped registers). 307 //go:volatile 308 type RegValue uint32 309 310 //go:volatile 311 type RegValue16 uint16 312 313 //go:volatile 314 type RegValue8 uint8 315 316 // Some information about this device. 317 const ( 318 DEVICE = "{name}" 319 ) 320 '''.format(pkgName=pkgName, **device.metadata)) 321 322 out.write('\n// Interrupt numbers\nconst (\n') 323 for intr in device.interrupts: 324 out.write('\tIRQ_{name} = {index} // {description}\n'.format(**intr)) 325 intrMax = max(map(lambda intr: intr['index'], device.interrupts)) 326 out.write('\tIRQ_max = {} // Highest interrupt number on this device.\n'.format(intrMax)) 327 out.write(')\n') 328 329 # Define actual peripheral pointers. 330 out.write('\n// Peripherals.\nvar (\n') 331 for peripheral in device.peripherals: 332 out.write('\t{name} = (*{groupName}_Type)(unsafe.Pointer(uintptr(0x{baseAddress:x}))) // {description}\n'.format(**peripheral)) 333 out.write(')\n') 334 335 # Define peripheral struct types. 336 for peripheral in device.peripherals: 337 if 'registers' not in peripheral: 338 # This peripheral was derived from another peripheral. No new type 339 # needs to be defined for it. 340 continue 341 out.write('\n// {description}\ntype {groupName}_Type struct {{\n'.format(**peripheral)) 342 address = peripheral['baseAddress'] 343 padNumber = 0 344 for register in peripheral['registers']: 345 if address > register['address'] and 'registers' not in register : 346 # In Nordic SVD files, these registers are deprecated or 347 # duplicates, so can be ignored. 348 #print('skip: %s.%s %s - %s %s' % (peripheral['name'], register['name'], address, register['address'], register['elementsize'])) 349 continue 350 eSize = register['elementsize'] 351 if eSize == 4: 352 regType = 'RegValue' 353 elif eSize == 2: 354 regType = 'RegValue16' 355 elif eSize == 1: 356 regType = 'RegValue8' 357 else: 358 eSize = 4 359 regType = 'RegValue' 360 361 # insert padding, if needed 362 if address < register['address']: 363 bytesNeeded = register['address'] - address 364 if bytesNeeded == 1: 365 out.write('\t_padding{padNumber} {regType}\n'.format(padNumber=padNumber, regType='RegValue8')) 366 elif bytesNeeded == 2: 367 out.write('\t_padding{padNumber} {regType}\n'.format(padNumber=padNumber, regType='RegValue16')) 368 else: 369 numSkip = (register['address'] - address) // eSize 370 if numSkip == 1: 371 out.write('\t_padding{padNumber} {regType}\n'.format(padNumber=padNumber, regType=regType)) 372 else: 373 out.write('\t_padding{padNumber} [{num}]{regType}\n'.format(padNumber=padNumber, num=numSkip, regType=regType)) 374 padNumber += 1 375 address = register['address'] 376 377 lastCluster = False 378 if 'registers' in register: 379 # This is a cluster, not a register. Create the cluster type. 380 regType = 'struct {\n' 381 subaddress = register['address'] 382 for subregister in register['registers']: 383 if subregister['elementsize'] == 4: 384 subregType = 'RegValue' 385 elif subregister['elementsize'] == 2: 386 subregType = 'RegValue16' 387 else: 388 subregType = 'RegValue8' 389 390 if subregister['array']: 391 subregType = '[{}]{}'.format(subregister['array'], subregType) 392 if subaddress != subregister['address']: 393 bytesNeeded = subregister['address'] - subaddress 394 if bytesNeeded == 1: 395 regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType='RegValue8') 396 elif bytesNeeded == 2: 397 regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType='RegValue16') 398 else: 399 numSkip = (subregister['address'] - subaddress) 400 if numSkip < 1: 401 continue 402 elif numSkip == 1: 403 regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType='RegValue8') 404 else: 405 regType += '\t\t_padding{padNumber} [{num}]{subregType}\n'.format(padNumber=padNumber, num=numSkip, subregType='RegValue8') 406 padNumber += 1 407 subaddress += bytesNeeded 408 if subregister['array'] is not None: 409 subaddress += subregister['elementsize'] * subregister['array'] 410 else: 411 subaddress += subregister['elementsize'] 412 regType += '\t\t{name} {subregType}\n'.format(name=subregister['name'], subregType=subregType) 413 if register['array'] is not None: 414 if subaddress != register['address'] + register['elementsize']: 415 numSkip = ((register['address'] + register['elementsize']) - subaddress) // 4 416 if numSkip <= 1: 417 regType += '\t\t_padding{padNumber} {subregType}\n'.format(padNumber=padNumber, subregType=subregType) 418 else: 419 regType += '\t\t_padding{padNumber} [{num}]{subregType}\n'.format(padNumber=padNumber, num=numSkip, subregType=subregType) 420 else: 421 lastCluster = True 422 regType += '\t}' 423 address = subaddress 424 if register['array'] is not None: 425 regType = '[{}]{}'.format(register['array'], regType) 426 out.write('\t{name} {regType}\n'.format(name=register['name'], regType=regType)) 427 428 # next address 429 if lastCluster is True: 430 lastCluster = False 431 elif register['array'] is not None: 432 address = register['address'] + register['elementsize'] * register['array'] 433 else: 434 address = register['address'] + register['elementsize'] 435 out.write('}\n') 436 437 # Define bitfields. 438 for peripheral in device.peripherals: 439 if 'registers' not in peripheral: 440 # This peripheral was derived from another peripheral. Bitfields are 441 # already defined. 442 continue 443 out.write('\n// Bitfields for {name}: {description}\nconst('.format(**peripheral)) 444 for register in peripheral['registers']: 445 if register.get('bitfields'): 446 writeGoRegisterBitfields(out, register, register['name']) 447 for subregister in register.get('registers', []): 448 writeGoRegisterBitfields(out, subregister, register['name'] + '.' + subregister['name']) 449 out.write(')\n') 450 451 def writeGoRegisterBitfields(out, register, name): 452 out.write('\n\t// {}'.format(name)) 453 if register['description']: 454 out.write(': {description}'.format(**register)) 455 out.write('\n') 456 for bitfield in register['bitfields']: 457 out.write('\t{name} = 0x{value:x}'.format(**bitfield)) 458 if bitfield['description']: 459 out.write(' // {description}'.format(**bitfield)) 460 out.write('\n') 461 462 463 def writeAsm(outdir, device): 464 # The interrupt vector, which is hard to write directly in Go. 465 out = open(outdir + '/' + device.metadata['nameLower'] + '.s', 'w') 466 out.write('''\ 467 // Automatically generated file. DO NOT EDIT. 468 // Generated by gen-device-svd.py from {file}, see {descriptorSource} 469 470 // {description} 471 // 472 {licenseBlock} 473 474 .syntax unified 475 476 // This is the default handler for interrupts, if triggered but not defined. 477 .section .text.Default_Handler 478 .global Default_Handler 479 .type Default_Handler, %function 480 Default_Handler: 481 wfe 482 b Default_Handler 483 484 // Avoid the need for repeated .weak and .set instructions. 485 .macro IRQ handler 486 .weak \\handler 487 .set \\handler, Default_Handler 488 .endm 489 490 // Must set the "a" flag on the section: 491 // https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049 492 // https://sourceware.org/binutils/docs/as/Section.html#ELF-Version 493 .section .isr_vector, "a", %progbits 494 .global __isr_vector 495 // Interrupt vector as defined by Cortex-M, starting with the stack top. 496 // On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading 497 // _stack_top and Reset_Handler. 498 .long _stack_top 499 .long Reset_Handler 500 .long NMI_Handler 501 .long HardFault_Handler 502 .long MemoryManagement_Handler 503 .long BusFault_Handler 504 .long UsageFault_Handler 505 .long 0 506 .long 0 507 .long 0 508 .long 0 509 .long SVC_Handler 510 .long DebugMon_Handler 511 .long 0 512 .long PendSV_Handler 513 .long SysTick_Handler 514 515 // Extra interrupts for peripherals defined by the hardware vendor. 516 '''.format(**device.metadata)) 517 num = 0 518 for intr in device.interrupts: 519 if intr['index'] == num - 1: 520 continue 521 if intr['index'] < num: 522 raise ValueError('interrupt numbers are not sorted') 523 while intr['index'] > num: 524 out.write(' .long 0\n') 525 num += 1 526 num += 1 527 out.write(' .long {name}_IRQHandler\n'.format(**intr)) 528 529 out.write(''' 530 // Define default implementations for interrupts, redirecting to 531 // Default_Handler when not implemented. 532 IRQ NMI_Handler 533 IRQ HardFault_Handler 534 IRQ MemoryManagement_Handler 535 IRQ BusFault_Handler 536 IRQ UsageFault_Handler 537 IRQ SVC_Handler 538 IRQ DebugMon_Handler 539 IRQ PendSV_Handler 540 IRQ SysTick_Handler 541 ''') 542 for intr in device.interrupts: 543 out.write(' IRQ {name}_IRQHandler\n'.format(**intr)) 544 545 def generate(indir, outdir, sourceURL): 546 if not os.path.isdir(indir): 547 print('cannot find input directory:', indir, file=sys.stderr) 548 sys.exit(1) 549 if not os.path.isdir(outdir): 550 os.mkdir(outdir) 551 infiles = glob(indir + '/*.svd') 552 if not infiles: 553 print('no .svd files found:', indir, file=sys.stderr) 554 sys.exit(1) 555 for filepath in sorted(infiles): 556 print(filepath) 557 device = readSVD(filepath, sourceURL) 558 writeGo(outdir, device) 559 writeAsm(outdir, device) 560 561 562 if __name__ == '__main__': 563 parser = argparse.ArgumentParser(description='Generate Go register descriptors and interrupt vectors from .svd files') 564 parser.add_argument('indir', metavar='indir', type=str, 565 help='input directory containing .svd files') 566 parser.add_argument('outdir', metavar='outdir', type=str, 567 help='output directory') 568 parser.add_argument('--source', metavar='source', type=str, 569 help='output directory', 570 default='<unknown>') 571 args = parser.parse_args() 572 generate(args.indir, args.outdir, args.source)