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)