rockbox/utils/reggen-ng/reggen-ng.py
Aidan MacDonald 73cee8f177 Add RegGen-NG tool and definitions for Ingenic X1000
Change-Id: Ib9ec35c068e1cff8dcf120a13cfe3f5f58908a95
2021-03-02 00:36:01 +00:00

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())