73cee8f177
Change-Id: Ib9ec35c068e1cff8dcf120a13cfe3f5f58908a95
465 lines
13 KiB
Python
Executable file
465 lines
13 KiB
Python
Executable file
#!/usr/bin/python3
|
|
#
|
|
# Tool to generate XML files for Rockbox regtools. The syntax is pretty simple
|
|
# and much less verbose than XML; see the '.reggen' files next to this script.
|
|
#
|
|
# Currently this tool expects a 'reggen' file on standard input and writes the
|
|
# equivalent XML to standard output.
|
|
#
|
|
# TODO: document this better and improve the command line usage
|
|
|
|
from lxml import etree as ET
|
|
from lxml.builder import E
|
|
import functools
|
|
|
|
class Soc:
|
|
def __init__(self):
|
|
self.name = None
|
|
self.title = None
|
|
self.desc = None
|
|
self.isa = None
|
|
self.version = None
|
|
self.authors = []
|
|
self.nodes = []
|
|
|
|
def gen(self):
|
|
node = E("soc", version="2")
|
|
if self.name is not None:
|
|
node.append(E("name", self.name))
|
|
if self.desc is not None:
|
|
node.append(E("desc", self.desc))
|
|
if self.isa is not None:
|
|
node.append(E("isa", self.isa))
|
|
if self.isa is not None:
|
|
node.append(E("version", self.version))
|
|
for a in self.authors:
|
|
node.append(E("author", a))
|
|
for n in self.nodes:
|
|
node.append(n.gen())
|
|
return node
|
|
|
|
class Node:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.title = None
|
|
self.desc = None
|
|
self.instances = []
|
|
self.children = []
|
|
self.register_width = None
|
|
self.fields = []
|
|
|
|
def gen(self):
|
|
node = E("node", E("name", self.name))
|
|
|
|
if self.title is not None:
|
|
node.append(E("title", self.title))
|
|
|
|
if self.desc is not None:
|
|
node.append(E("desc", self.desc))
|
|
|
|
for i in self.instances:
|
|
node.append(i.gen())
|
|
|
|
for c in self.children:
|
|
node.append(c.gen())
|
|
|
|
if self.register_width is not None:
|
|
reg = E("register",
|
|
E("width", self.register_width))
|
|
for f in self.fields:
|
|
reg.append(f.gen())
|
|
node.append(reg)
|
|
|
|
return node
|
|
|
|
class Instance:
|
|
def __init__(self, name, addr, stride=None, count=None):
|
|
self.name = name
|
|
self.address = addr
|
|
self.stride = stride
|
|
self.count = count
|
|
|
|
def gen(self):
|
|
node = E("instance", E("name", self.name))
|
|
|
|
if self.stride is not None:
|
|
node.append(E("range",
|
|
E("first", "0"),
|
|
E("count", self.count),
|
|
E("base", self.address),
|
|
E("stride", self.stride)))
|
|
else:
|
|
node.append(E("address", self.address))
|
|
|
|
return node
|
|
|
|
class Field:
|
|
def __init__(self, name, msb, lsb):
|
|
self.name = name
|
|
self.desc = None
|
|
self.msb = int(msb)
|
|
self.lsb = int(lsb)
|
|
self.enums = []
|
|
|
|
def gen(self):
|
|
node = E("field",
|
|
E("name", self.name),
|
|
E("position", "%d" % self.lsb))
|
|
|
|
if self.desc is not None:
|
|
node.append(E("desc", self.desc))
|
|
|
|
if self.msb != self.lsb:
|
|
node.append(E("width", str(self.msb - self.lsb + 1)))
|
|
|
|
for n, v in self.enums:
|
|
node.append(E("enum", E("name", n), E("value", v)))
|
|
|
|
return node
|
|
|
|
class Variant:
|
|
def __init__(self, ty, offset):
|
|
self.type = ty
|
|
self.offset = offset
|
|
|
|
def gen(self):
|
|
return E("variant",
|
|
E("type", self.type),
|
|
E("offset", self.offset))
|
|
|
|
class Parser:
|
|
def __init__(self, f):
|
|
self.input = f
|
|
self.line = 1
|
|
self.eolstop = False
|
|
self.next()
|
|
|
|
def parse(self):
|
|
self.skipws()
|
|
results = self.parse_block_inner({
|
|
'name': (self.parse_string, 1),
|
|
'title': (self.parse_string, 1),
|
|
'desc': (self.parse_string, 1),
|
|
'isa': (self.parse_string, 1),
|
|
'version': (self.parse_string, 1),
|
|
'author': self.parse_string,
|
|
'node': self.parse_node,
|
|
'reg': self.parse_register,
|
|
})
|
|
|
|
ret = Soc()
|
|
|
|
if 'name' in results:
|
|
ret.name = results['name'][0]
|
|
else:
|
|
self.err('missing "name" statement at toplevel')
|
|
|
|
if 'title' in results:
|
|
ret.title = results['title'][0]
|
|
if 'desc' in results:
|
|
ret.desc = results['desc'][0]
|
|
if 'isa' in results:
|
|
ret.isa = results['isa'][0]
|
|
if 'version' in results:
|
|
ret.version = results['version'][0]
|
|
if 'author' in results:
|
|
ret.authors += results['author']
|
|
if 'node' in results:
|
|
ret.nodes += results['node']
|
|
if 'reg' in results:
|
|
ret.nodes += results['reg']
|
|
|
|
return ret
|
|
|
|
def parse_node(self):
|
|
name = self.parse_ident()
|
|
ret, results = self.parse_node_block(name, {
|
|
'title': (self.parse_string, 1),
|
|
'node': self.parse_node,
|
|
'reg': self.parse_register,
|
|
})
|
|
|
|
if 'title' in results:
|
|
ret.title = results['title'][0]
|
|
if 'node' in results:
|
|
ret.children += results['node']
|
|
if 'reg' in results:
|
|
ret.children += results['reg']
|
|
|
|
return ret
|
|
|
|
def parse_register(self):
|
|
words, has_block = self.parse_wordline(True)
|
|
name = words[0]
|
|
width = "32"
|
|
addr = None
|
|
|
|
if len(words) == 1:
|
|
# reg NAME
|
|
pass
|
|
elif len(words) == 2:
|
|
# reg NAME ADDR
|
|
addr = words[1]
|
|
elif len(words) == 3:
|
|
# reg WIDTH NAME ADDR
|
|
name = words[1]
|
|
width = words[0]
|
|
addr = words[2]
|
|
else:
|
|
self.err('malformed register statement')
|
|
|
|
if has_block:
|
|
ret, results = self.parse_node_block(name, {
|
|
'field': self.parse_field,
|
|
'fld': self.parse_field,
|
|
'bit': self.parse_field,
|
|
'variant': self.parse_variant,
|
|
})
|
|
|
|
if 'field' in results:
|
|
ret.fields += results['field']
|
|
if 'fld' in results:
|
|
ret.fields += results['fld']
|
|
if 'bit' in results:
|
|
ret.fields += results['bit']
|
|
if 'variant' in results:
|
|
ret.fields += results['variant']
|
|
else:
|
|
ret = Node(name)
|
|
|
|
if len(ret.instances) == 0:
|
|
if addr is None:
|
|
self.err("no address specified for register")
|
|
ret.instances.append(Instance(ret.name, addr))
|
|
elif addr:
|
|
self.err("duplicate address specification for register")
|
|
|
|
ret.register_width = width
|
|
return ret
|
|
|
|
def parse_node_block(self, name, extra_parsers):
|
|
parsers = {
|
|
'desc': (self.parse_string, 1),
|
|
'addr': (self.parse_printable, 1),
|
|
'instance': functools.partial(self.parse_instance, name),
|
|
}
|
|
|
|
parsers.update(extra_parsers)
|
|
results = self.parse_block(parsers)
|
|
ret = Node(name)
|
|
|
|
if 'desc' in results:
|
|
ret.desc = results['desc'][0]
|
|
if 'addr' in results:
|
|
ret.instances.append(Instance(ret.name, results['addr'][0]))
|
|
if 'instance' in results:
|
|
if 'addr' in results:
|
|
self.err('instance statement not allowed when addr is given')
|
|
|
|
ret.instances += results['instance']
|
|
|
|
return ret, results
|
|
|
|
def parse_instance(self, default_name):
|
|
words = self.parse_wordline(False)[0]
|
|
|
|
if len(words) == 1:
|
|
# instance ADDR
|
|
return Instance(default_name, words[0])
|
|
elif len(words) == 2:
|
|
# instance NAME ADDR
|
|
return Instance(words[0], words[1])
|
|
elif len(words) == 3:
|
|
# instance ADDR STRIDE COUNT
|
|
return Instance(default_name, words[0], words[1], words[2])
|
|
elif len(words) == 4:
|
|
# instance NAME ADDR STRIDE COUNT
|
|
return Instance(words[0], words[1], words[2], words[3])
|
|
else:
|
|
self.err('malformed instance statement')
|
|
|
|
def parse_field(self):
|
|
words, has_block = self.parse_wordline(True)
|
|
name = None
|
|
msb = None
|
|
lsb = None
|
|
|
|
if len(words) == 2:
|
|
# field BIT NAME
|
|
lsb = msb = words[0]
|
|
name = words[1]
|
|
elif len(words) == 3:
|
|
# field MSB LSB NAME
|
|
msb = words[0]
|
|
lsb = words[1]
|
|
name = words[2]
|
|
else:
|
|
self.err('malformed field statement')
|
|
|
|
try:
|
|
int(msb)
|
|
int(lsb)
|
|
except:
|
|
self.err('field MSB/LSB must be integers')
|
|
|
|
ret = Field(name, msb, lsb)
|
|
if has_block:
|
|
results = self.parse_block({
|
|
'desc': self.parse_string,
|
|
'enum': self.parse_enum,
|
|
})
|
|
|
|
if 'desc' in results:
|
|
if len(results['desc']) > 1:
|
|
self.err("only one description allowed")
|
|
ret.desc = results['desc'][0]
|
|
|
|
if 'enum' in results:
|
|
ret.enums += results['enum']
|
|
|
|
return ret
|
|
|
|
def parse_wordline(self, allow_block=False):
|
|
words = []
|
|
self.eolstop = True
|
|
while(self.chr != '{' and self.chr != '}' and
|
|
self.chr != '\n' and self.chr != ';'):
|
|
words.append(self.parse_printable())
|
|
self.eolstop = False
|
|
|
|
if len(words) == 0:
|
|
self.err('this type of statement cannot be empty')
|
|
|
|
if self.chr == '{':
|
|
if not allow_block:
|
|
self.err('this type of statement does not accept blocks')
|
|
has_block = True
|
|
else:
|
|
has_block = False
|
|
self.skipws()
|
|
|
|
return words, has_block
|
|
|
|
def parse_variant(self):
|
|
ty = self.parse_printable()
|
|
off = self.parse_printable()
|
|
return Variant(ty, off)
|
|
|
|
def parse_enum(self):
|
|
name = self.parse_printable()
|
|
value = self.parse_printable()
|
|
return (name, value)
|
|
|
|
def parse_block(self, parsers):
|
|
if self.chr != '{':
|
|
self.err("expected '{'")
|
|
self.next()
|
|
self.skipws()
|
|
|
|
ret = self.parse_block_inner(parsers)
|
|
|
|
assert self.chr == '}'
|
|
self.next()
|
|
self.skipws()
|
|
return ret
|
|
|
|
def parse_block_inner(self, parsers):
|
|
ret = {}
|
|
cnt = {}
|
|
while self.chr != '}' and self.chr != '\0':
|
|
kwd = self.parse_ident()
|
|
if kwd not in parsers:
|
|
self.err("invalid keyword for block")
|
|
|
|
if kwd not in ret:
|
|
ret[kwd] = []
|
|
cnt[kwd] = 0
|
|
|
|
pentry = parsers[kwd]
|
|
if type(pentry) is tuple and len(pentry) == 2:
|
|
pfunc = pentry[0]
|
|
max_cnt = pentry[1]
|
|
else:
|
|
pfunc = pentry
|
|
max_cnt = 0
|
|
|
|
if max_cnt > 0 and cnt[kwd] == max_cnt:
|
|
if max_cnt == 1:
|
|
self.err("at most one '%s' statement allowed in this block" % kwd)
|
|
else:
|
|
self.err("at most %d '%s' statements allowed in this block" % (max_cnt, kwd))
|
|
|
|
ret[kwd].append(pfunc())
|
|
cnt[kwd] += 1
|
|
|
|
return ret
|
|
|
|
def parse_ident(self):
|
|
ident = ''
|
|
while self.chr.isalnum() or self.chr == '_':
|
|
ident += self.chr
|
|
self.next()
|
|
|
|
if len(ident) == 0:
|
|
self.err("expected identifier")
|
|
|
|
self.skipws()
|
|
return ident
|
|
|
|
def parse_string(self):
|
|
if self.chr != '"':
|
|
self.err("expected string")
|
|
self.next()
|
|
|
|
s = ''
|
|
while self.chr != '"':
|
|
s += self.chr
|
|
self.next()
|
|
self.next()
|
|
|
|
self.skipws()
|
|
return s
|
|
|
|
def parse_printable(self):
|
|
s = ''
|
|
while not self.chr.isspace() and self.chr != ';':
|
|
s += self.chr
|
|
self.next()
|
|
|
|
self.skipws()
|
|
return s
|
|
|
|
def skipws(self):
|
|
while True:
|
|
if self.chr == '#':
|
|
while self.chr != '\n':
|
|
self.next()
|
|
if self.eolstop:
|
|
break
|
|
self.next()
|
|
elif self.chr == '\n' or self.chr == ';':
|
|
if self.eolstop:
|
|
break
|
|
self.next()
|
|
elif self.chr.isspace():
|
|
self.next()
|
|
else:
|
|
break
|
|
|
|
def next(self):
|
|
self.chr = self.input.read(1)
|
|
if len(self.chr) == 0:
|
|
self.chr = '\0'
|
|
if self.chr == '\n':
|
|
self.line += 1
|
|
return self.chr
|
|
|
|
def err(self, msg):
|
|
raise ValueError("on line %d: %s" % (self.line, msg))
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
p = Parser(sys.stdin)
|
|
r = p.parse()
|
|
print('<?xml version="1.0"?>')
|
|
ET.dump(r.gen())
|