900 lines
31 KiB
Python
900 lines
31 KiB
Python
#
|
|
# Copyright 2017-2019 Advanced Micro Devices, Inc.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
# copy of this software and associated documentation files (the "Software"),
|
|
# to deal in the Software without restriction, including without limitation
|
|
# on the rights to use, copy, modify, merge, publish, distribute, sub
|
|
# license, and/or sell copies of the Software, and to permit persons to whom
|
|
# the Software is furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice (including the next
|
|
# paragraph) shall be included in all copies or substantial portions of the
|
|
# Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
|
# THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
|
|
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
"""
|
|
Python package containing common tools for manipulating register JSON.
|
|
"""
|
|
|
|
import itertools
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
from collections import defaultdict
|
|
from contextlib import contextmanager
|
|
|
|
class UnionFind(object):
|
|
"""
|
|
Simplistic implementation of a union-find data structure that also keeps
|
|
track of the sets that have been unified.
|
|
|
|
- add: add an element to the implied global set of elements
|
|
- union: unify the sets containing the two given elements
|
|
- find: return the representative element of the set containing the
|
|
given element
|
|
- get_set: get the set containing the given element
|
|
- sets: iterate over all sets (the sets form a partition of the set of all
|
|
elements that have ever been added)
|
|
"""
|
|
def __init__(self):
|
|
self.d = {}
|
|
|
|
def add(self, k):
|
|
if k not in self.d:
|
|
self.d[k] = set([k])
|
|
|
|
def union(self, k1, k2):
|
|
k1 = self.find(k1)
|
|
k2 = self.find(k2)
|
|
if k1 == k2:
|
|
return
|
|
if len(k1) < len(k2):
|
|
k1, k2 = k2, k1
|
|
self.d[k1].update(self.d[k2])
|
|
self.d[k2] = (k1,)
|
|
|
|
def find(self, k):
|
|
e = self.d[k]
|
|
if isinstance(e, set):
|
|
return k
|
|
assert isinstance(e, tuple)
|
|
r = self.find(e[0])
|
|
self.d[k] = (r,)
|
|
return r
|
|
|
|
def get_set(self, k):
|
|
k = self.find(k)
|
|
assert isinstance(self.d[k], set)
|
|
return self.d[k]
|
|
|
|
def sets(self):
|
|
for v in self.d.values():
|
|
if isinstance(v, set):
|
|
yield v
|
|
|
|
|
|
class Object(object):
|
|
"""
|
|
Convenience helper class that essentially acts as a dictionary for convenient
|
|
conversion from and to JSON while allowing the use of .field notation
|
|
instead of subscript notation for member access.
|
|
"""
|
|
def __init__(self, **kwargs):
|
|
for k, v in kwargs.items():
|
|
setattr(self, k, v)
|
|
|
|
def update(self, **kwargs):
|
|
for key, value in kwargs.items():
|
|
setattr(self, key, value)
|
|
return self
|
|
|
|
def __str__(self):
|
|
return 'Object(' + ', '.join(
|
|
'{k}={v}'.format(**locals()) for k, v, in self.__dict__.items()
|
|
) + ')'
|
|
|
|
@staticmethod
|
|
def from_json(json, keys=None):
|
|
if isinstance(json, list):
|
|
return [Object.from_json(v) for v in json]
|
|
elif isinstance(json, dict):
|
|
obj = Object()
|
|
for k, v in json.items():
|
|
if keys is not None and k in keys:
|
|
v = keys[k](v)
|
|
else:
|
|
v = Object.from_json(v)
|
|
setattr(obj, k, v)
|
|
return obj
|
|
else:
|
|
return json
|
|
|
|
@staticmethod
|
|
def to_json(obj):
|
|
if isinstance(obj, Object):
|
|
return dict((k, Object.to_json(v)) for k, v in obj.__dict__.items())
|
|
elif isinstance(obj, dict):
|
|
return dict((k, Object.to_json(v)) for k, v in obj.items())
|
|
elif isinstance(obj, list):
|
|
return [Object.to_json(v) for v in obj]
|
|
else:
|
|
return obj
|
|
|
|
class MergeError(Exception):
|
|
def __init__(self, msg):
|
|
super(MergeError, self).__init__(msg)
|
|
|
|
class RegisterDatabaseError(Exception):
|
|
def __init__(self, msg):
|
|
super(RegisterDatabaseError, self).__init__(msg)
|
|
|
|
@contextmanager
|
|
def merge_scope(name):
|
|
"""
|
|
Wrap a merge handling function in a "scope" whose name will be added when
|
|
propagating MergeErrors.
|
|
"""
|
|
try:
|
|
yield
|
|
except Exception as e:
|
|
raise MergeError('{name}: {e}'.format(**locals()))
|
|
|
|
def merge_dicts(dicts, keys=None, values=None):
|
|
"""
|
|
Generic dictionary merging function.
|
|
|
|
dicts -- list of (origin, dictionary) pairs to merge
|
|
keys -- optional dictionary to provide a merge-strategy per key;
|
|
the merge strategy is a callable which will receive a list of
|
|
(origin, value) pairs
|
|
value -- optional function which provides a merge-strategy for values;
|
|
the merge strategy is a callable which will receive the name of
|
|
the key and a list of (origin, value) pairs
|
|
|
|
The default strategy is to allow merging keys if all origin dictionaries
|
|
that contain the key have the same value for it.
|
|
"""
|
|
ks = set()
|
|
for _, d in dicts:
|
|
ks.update(d.keys())
|
|
|
|
result = {}
|
|
for k in ks:
|
|
vs = [(o, d[k]) for o, d in dicts if k in d]
|
|
with merge_scope('Key {k}'.format(**locals())):
|
|
if keys is not None and k in keys:
|
|
result[k] = keys[k](vs)
|
|
elif values is not None:
|
|
result[k] = values(k, vs)
|
|
else:
|
|
base_origin, base = vs[0]
|
|
for other_origin, other in vs[1:]:
|
|
if base != other:
|
|
raise MergeError('{base} (from {base_origin}) != {other} (from {other_origin})'.format(**locals()))
|
|
result[k] = base
|
|
return result
|
|
|
|
def merge_objects(objects, keys=None):
|
|
"""
|
|
Like merge_dicts, but applied to instances of Object.
|
|
"""
|
|
return Object(**merge_dicts([(origin, obj.__dict__) for origin, obj in objects], keys=keys))
|
|
|
|
class RegisterDatabase(object):
|
|
"""
|
|
A register database containing:
|
|
|
|
- enums: these are lists of named values that can occur in a register field
|
|
- register types: description of a register type or template as a list of
|
|
fields
|
|
- register mappings: named and typed registers mapped at locations in an
|
|
address space
|
|
"""
|
|
def __init__(self):
|
|
self.__enums = {}
|
|
self.__register_types = {}
|
|
self.__register_mappings = []
|
|
self.__regmap_by_addr = None
|
|
self.__chips = None
|
|
|
|
def __post_init(self):
|
|
"""
|
|
Perform some basic canonicalization:
|
|
- enum entries are sorted by value
|
|
- register type fields are sorted by starting bit
|
|
- __register_mappings is sorted by offset
|
|
- the chips field of register mappings is sorted
|
|
|
|
Lazily computes the set of all chips mentioned by register mappings.
|
|
"""
|
|
if self.__regmap_by_addr is not None:
|
|
return
|
|
|
|
for enum in self.__enums.values():
|
|
enum.entries.sort(key=lambda entry: entry.value)
|
|
|
|
for regtype in self.__register_types.values():
|
|
regtype.fields.sort(key=lambda field: field.bits[0])
|
|
|
|
self.__regmap_by_addr = defaultdict(list)
|
|
self.__chips = set()
|
|
|
|
# Merge register mappings using sort order and garbage collect enums
|
|
# and register types.
|
|
old_register_mappings = self.__register_mappings
|
|
old_register_mappings.sort(key=lambda regmap: regmap.map.at)
|
|
|
|
self.__register_mappings = []
|
|
for regmap in old_register_mappings:
|
|
addr = (regmap.map.to, regmap.map.at)
|
|
chips = set(getattr(regmap, 'chips', ['undef']))
|
|
type_ref = getattr(regmap, 'type_ref', None)
|
|
|
|
self.__chips.update(chips)
|
|
|
|
merged = False
|
|
for other in reversed(self.__register_mappings):
|
|
if other.name != regmap.name:
|
|
break
|
|
|
|
other_addr = (other.map.to, other.map.at)
|
|
other_chips = getattr(other, 'chips', ['undef'])
|
|
other_type_ref = getattr(other, 'type_ref', None)
|
|
|
|
if addr == other_addr and\
|
|
(type_ref is None or other_type_ref is None or type_ref == other_type_ref):
|
|
other.chips = sorted(list(chips.union(other_chips)))
|
|
if type_ref is not None:
|
|
other.type_ref = type_ref
|
|
merged = True
|
|
break
|
|
|
|
if merged:
|
|
continue
|
|
|
|
addrmappings = self.__regmap_by_addr[addr]
|
|
|
|
for other in addrmappings:
|
|
other_type_ref = getattr(other, 'type_ref', None)
|
|
other_chips = getattr(other, 'chips', ['undef'])
|
|
if type_ref is not None and other_type_ref is not None and \
|
|
type_ref != other_type_ref and chips.intersection(other_chips):
|
|
raise RegisterDatabaseError(
|
|
'Registers {0} and {1} overlap and have conflicting types'.format(
|
|
other.name, regmap.name))
|
|
|
|
addrmappings.append(regmap)
|
|
self.__register_mappings.append(regmap)
|
|
|
|
def garbage_collect(self):
|
|
"""
|
|
Remove unreferenced enums and register types.
|
|
"""
|
|
old_enums = self.__enums
|
|
old_register_types = self.__register_types
|
|
|
|
self.__enums = {}
|
|
self.__register_types = {}
|
|
for regmap in self.__register_mappings:
|
|
if hasattr(regmap, 'type_ref') and regmap.type_ref not in self.__register_types:
|
|
regtype = old_register_types[regmap.type_ref]
|
|
self.__register_types[regmap.type_ref] = regtype
|
|
for field in regtype.fields:
|
|
if hasattr(field, 'enum_ref') and field.enum_ref not in self.__enums:
|
|
self.__enums[field.enum_ref] = old_enums[field.enum_ref]
|
|
|
|
def __validate_register_type(self, regtype):
|
|
for field in regtype.fields:
|
|
if hasattr(field, 'enum_ref') and field.enum_ref not in self.__enums:
|
|
raise RegisterDatabaseError(
|
|
'Register type field {0} has unknown enum_ref {1}'.format(
|
|
field.name, field.enum_ref))
|
|
|
|
def __validate_register_mapping(self, regmap):
|
|
if hasattr(regmap, 'type_ref') and regmap.type_ref not in self.__register_types:
|
|
raise RegisterDatabaseError(
|
|
'Register mapping {0} has unknown type_ref {1}'.format(
|
|
regmap.name, regmap.type_ref))
|
|
|
|
def __validate(self):
|
|
for regtype in self.__register_types.values():
|
|
self.__validate_register_type(regtype)
|
|
for regmap in self.__register_mappings:
|
|
self.__validate_register_mapping(regmap)
|
|
|
|
@staticmethod
|
|
def enum_key(enum):
|
|
"""
|
|
Return a key that uniquely describes the signature of the given
|
|
enum (assuming that it has been canonicalized). Two enums with the
|
|
same key can be merged.
|
|
"""
|
|
return ''.join(
|
|
':{0}:{1}'.format(entry.name, entry.value)
|
|
for entry in enum.entries
|
|
)
|
|
|
|
def add_enum(self, name, enum):
|
|
if name in self.__enums:
|
|
raise RegisterDatabaseError('Duplicate enum ' + name)
|
|
self.__enums[name] = enum
|
|
|
|
@staticmethod
|
|
def __merge_enums(enums, union=False):
|
|
def merge_entries(entries_lists):
|
|
values = defaultdict(list)
|
|
for origin, enum in entries_lists:
|
|
for entry in enum:
|
|
values[entry.value].append((origin, entry))
|
|
|
|
if not union:
|
|
if any(len(entries) != len(enums) for entries in values.values()):
|
|
raise RegisterDatabaseError(
|
|
'Attempting to merge enums with different values')
|
|
|
|
return [
|
|
merge_objects(entries)
|
|
for entries in values.values()
|
|
]
|
|
|
|
return merge_objects(
|
|
enums,
|
|
keys={
|
|
'entries': merge_entries,
|
|
}
|
|
)
|
|
|
|
def merge_enums(self, names, newname, union=False):
|
|
"""
|
|
Given a list of enum names, merge them all into one with a new name and
|
|
update all references.
|
|
"""
|
|
if newname not in names and newname in self.__enums:
|
|
raise RegisterDatabaseError('Enum {0} already exists'.format(newname))
|
|
|
|
newenum = self.__merge_enums(
|
|
[(name, self.__enums[name]) for name in names],
|
|
union=union
|
|
)
|
|
|
|
for name in names:
|
|
del self.__enums[name]
|
|
self.__enums[newname] = newenum
|
|
|
|
for regtype in self.__register_types.values():
|
|
for field in regtype.fields:
|
|
if getattr(field, 'enum_ref', None) in names:
|
|
field.enum_ref = newname
|
|
|
|
self.__regmap_by_addr = None
|
|
|
|
def add_register_type(self, name, regtype):
|
|
if regtype in self.__register_types:
|
|
raise RegisterDatabaseError('Duplicate register type ' + name)
|
|
self.__register_types[name] = regtype
|
|
self.__validate_register_type(regtype)
|
|
|
|
def register_type(self, name):
|
|
self.__post_init()
|
|
return self.__register_types[name]
|
|
|
|
@staticmethod
|
|
def __merge_register_types(regtypes, union=False, field_keys={}):
|
|
def merge_fields(fields_lists):
|
|
fields = defaultdict(list)
|
|
for origin, fields_list in fields_lists:
|
|
for field in fields_list:
|
|
fields[field.bits[0]].append((origin, field))
|
|
|
|
if not union:
|
|
if any(len(entries) != len(regtypes) for entries in fields.values()):
|
|
raise RegisterDatabaseError(
|
|
'Attempting to merge register types with different fields')
|
|
|
|
return [
|
|
merge_objects(field, keys=field_keys)
|
|
for field in fields.values()
|
|
]
|
|
|
|
with merge_scope('Register types {0}'.format(', '.join(name for name, _ in regtypes))):
|
|
return merge_objects(
|
|
regtypes,
|
|
keys={
|
|
'fields': merge_fields,
|
|
}
|
|
)
|
|
|
|
def merge_register_types(self, names, newname, union=False):
|
|
"""
|
|
Given a list of register type names, merge them all into one with a
|
|
new name and update all references.
|
|
"""
|
|
if newname not in names and newname in self.__register_types:
|
|
raise RegisterDatabaseError('Register type {0} already exists'.format(newname))
|
|
|
|
newregtype = self.__merge_register_types(
|
|
[(name, self.__register_types[name]) for name in names],
|
|
union=union
|
|
)
|
|
|
|
for name in names:
|
|
del self.__register_types[name]
|
|
self.__register_types[newname] = newregtype
|
|
|
|
for regmap in self.__register_mappings:
|
|
if getattr(regmap, 'type_ref', None) in names:
|
|
regmap.type_ref = newname
|
|
|
|
self.__regmap_by_addr = None
|
|
|
|
def add_register_mapping(self, regmap):
|
|
self.__regmap_by_addr = None
|
|
self.__register_mappings.append(regmap)
|
|
self.__validate_register_mapping(regmap)
|
|
|
|
def remove_register_mappings(self, regmaps_to_remove):
|
|
self.__post_init()
|
|
|
|
regmaps_to_remove = set(regmaps_to_remove)
|
|
|
|
regmaps = self.__register_mappings
|
|
self.__register_mappings = []
|
|
for regmap in regmaps:
|
|
if regmap not in regmaps_to_remove:
|
|
self.__register_mappings.append(regmap)
|
|
|
|
self.__regmap_by_addr = None
|
|
|
|
def enum(self, name):
|
|
"""
|
|
Return the enum of the given name, if any.
|
|
"""
|
|
self.__post_init()
|
|
return self.__enums.get(name, None)
|
|
|
|
def enums(self):
|
|
"""
|
|
Yields all (name, enum) pairs.
|
|
"""
|
|
self.__post_init()
|
|
for name, enum in self.__enums.items():
|
|
yield (name, enum)
|
|
|
|
def fields(self):
|
|
"""
|
|
Yields all (register_type, fields) pairs.
|
|
"""
|
|
self.__post_init()
|
|
for regtype in self.__register_types.values():
|
|
for field in regtype.fields:
|
|
yield (regtype, field)
|
|
|
|
def register_types(self):
|
|
"""
|
|
Yields all (name, register_type) pairs.
|
|
"""
|
|
self.__post_init()
|
|
for name, regtype in self.__register_types.items():
|
|
yield (name, regtype)
|
|
|
|
def register_mappings_by_name(self, name):
|
|
"""
|
|
Return a list of register mappings with the given name.
|
|
"""
|
|
self.__post_init()
|
|
|
|
begin = 0
|
|
end = len(self.__register_mappings)
|
|
while begin < end:
|
|
middle = (begin + end) // 2
|
|
if self.__register_mappings[middle].name < name:
|
|
begin = middle + 1
|
|
elif name < self.__register_mappings[middle].name:
|
|
end = middle
|
|
else:
|
|
break
|
|
|
|
if begin >= end:
|
|
return []
|
|
|
|
# We now have begin <= mid < end with begin.name <= name, mid.name == name, name < end.name
|
|
# Narrow down begin and end
|
|
hi = middle
|
|
while begin < hi:
|
|
mid = (begin + hi) // 2
|
|
if self.__register_mappings[mid].name < name:
|
|
begin = mid + 1
|
|
else:
|
|
hi = mid
|
|
|
|
lo = middle + 1
|
|
while lo < end:
|
|
mid = (lo + end) // 2
|
|
if self.__register_mappings[mid].name == name:
|
|
lo = mid + 1
|
|
else:
|
|
end = mid
|
|
|
|
return self.__register_mappings[begin:end]
|
|
|
|
def register_mappings(self):
|
|
"""
|
|
Yields all register mappings.
|
|
"""
|
|
self.__post_init()
|
|
for regmap in self.__register_mappings:
|
|
yield regmap
|
|
|
|
def chips(self):
|
|
"""
|
|
Yields all chips.
|
|
"""
|
|
self.__post_init()
|
|
return iter(self.__chips)
|
|
|
|
def merge_chips(self, chips, newchip):
|
|
"""
|
|
Merge register mappings of the given chips into a single chip of the
|
|
given name. Recursively merges register types and enums when appropriate.
|
|
"""
|
|
self.__post_init()
|
|
|
|
chips = set(chips)
|
|
|
|
regtypes_merge = UnionFind()
|
|
enums_merge = UnionFind()
|
|
|
|
# Walk register mappings to find register types that should be merged.
|
|
for idx, regmap in itertools.islice(enumerate(self.__register_mappings), 1, None):
|
|
if not hasattr(regmap, 'type_ref'):
|
|
continue
|
|
if chips.isdisjoint(regmap.chips):
|
|
continue
|
|
|
|
for other in self.__register_mappings[idx-1::-1]:
|
|
if regmap.name != other.name:
|
|
break
|
|
if chips.isdisjoint(other.chips):
|
|
continue
|
|
if regmap.map.to != other.map.to or regmap.map.at != other.map.at:
|
|
raise RegisterDatabaseError(
|
|
'Attempting to merge chips with incompatible addresses of {0}'.format(regmap.name))
|
|
if not hasattr(regmap, 'type_ref'):
|
|
continue
|
|
|
|
if regmap.type_ref != other.type_ref:
|
|
regtypes_merge.add(regmap.type_ref)
|
|
regtypes_merge.add(other.type_ref)
|
|
regtypes_merge.union(regmap.type_ref, other.type_ref)
|
|
|
|
# Walk over regtype sets that are to be merged and find enums that
|
|
# should be merged.
|
|
for type_refs in regtypes_merge.sets():
|
|
fields_merge = defaultdict(set)
|
|
for type_ref in type_refs:
|
|
regtype = self.__register_types[type_ref]
|
|
for field in regtype.fields:
|
|
if hasattr(field, 'enum_ref'):
|
|
fields_merge[field.name].add(field.enum_ref)
|
|
|
|
for enum_refs in fields_merge.values():
|
|
if len(enum_refs) > 1:
|
|
enum_refs = list(enum_refs)
|
|
enums_merge.add(enum_refs[0])
|
|
for enum_ref in enum_refs[1:]:
|
|
enums_merge.add(enum_ref)
|
|
enums_merge.union(enum_ref, enum_refs[0])
|
|
|
|
# Merge all mergeable enum sets
|
|
remap_enum_refs = {}
|
|
for enum_refs in enums_merge.sets():
|
|
enum_refs = sorted(enum_refs)
|
|
newname = enum_refs[0] + '_' + newchip
|
|
i = 0
|
|
while newname in self.__enums:
|
|
newname = enum_refs[0] + '_' + newchip + str(i)
|
|
i += 1
|
|
|
|
for enum_ref in enum_refs:
|
|
remap_enum_refs[enum_ref] = newname
|
|
|
|
# Don't use self.merge_enums, because we don't want to automatically
|
|
# update _all_ references to the merged enums (some may be from
|
|
# register types that aren't going to be merged).
|
|
self.add_enum(newname, self.__merge_enums(
|
|
[(enum_ref, self.__enums[enum_ref]) for enum_ref in enum_refs],
|
|
union=True
|
|
))
|
|
|
|
# Merge all mergeable type refs
|
|
remap_type_refs = {}
|
|
for type_refs in regtypes_merge.sets():
|
|
type_refs = sorted(type_refs)
|
|
newname = type_refs[0] + '_' + newchip
|
|
i = 0
|
|
while newname in self.__enums:
|
|
newname = type_refs[0] + '_' + newchip + str(i)
|
|
i += 1
|
|
|
|
updated_regtypes = []
|
|
for type_ref in type_refs:
|
|
remap_type_refs[type_ref] = newname
|
|
|
|
regtype = Object.from_json(Object.to_json(self.__register_types[type_ref]))
|
|
for field in regtype.fields:
|
|
if hasattr(field, 'enum_ref'):
|
|
field.enum_ref = remap_enum_refs.get(enum_ref, enum_ref)
|
|
|
|
updated_regtypes.append(regtype)
|
|
|
|
def merge_enum_refs(enum_refs):
|
|
enum_refs = set(
|
|
remap_enum_refs.get(enum_ref, enum_ref)
|
|
for origin, enum_ref in enum_refs
|
|
)
|
|
assert len(enum_refs) == 1 # should be ensured by how we determine the enums to be merged
|
|
return enum_refs.pop()
|
|
|
|
self.add_register_type(newname, self.__merge_register_types(
|
|
[(type_ref, self.__register_types[type_ref]) for type_ref in type_refs],
|
|
field_keys={
|
|
'enum_ref': merge_enum_refs,
|
|
},
|
|
union=True
|
|
))
|
|
|
|
# Merge register mappings
|
|
register_mappings = self.__register_mappings
|
|
self.__register_mappings = []
|
|
|
|
regmap_accum = None
|
|
for regmap in register_mappings:
|
|
if regmap_accum and regmap.name != regmap_accum.name:
|
|
regmap_accum.chips = [newchip]
|
|
self.__register_mappings.append(regmap_accum)
|
|
regmap_accum = None
|
|
|
|
joining_chips = chips.intersection(regmap.chips)
|
|
if not joining_chips:
|
|
self.__register_mappings.append(regmap)
|
|
continue
|
|
remaining_chips = set(regmap.chips).difference(chips)
|
|
|
|
type_ref = getattr(regmap, 'type_ref', None)
|
|
if type_ref is None:
|
|
regmap.chips = sorted(remaining_chips.union([newchip]))
|
|
self.__register_mappings.append(regmap)
|
|
continue
|
|
|
|
type_ref = remap_type_refs.get(type_ref, type_ref)
|
|
if remaining_chips:
|
|
regmap.chips = sorted(remaining_chips)
|
|
self.__register_mappings.append(regmap)
|
|
if not regmap_accum:
|
|
regmap = Object.from_json(Object.to_json(regmap))
|
|
if type_ref is not None:
|
|
regmap.type_ref = type_ref
|
|
|
|
if not regmap_accum:
|
|
regmap_accum = regmap
|
|
else:
|
|
if not hasattr(regmap_accum.type_ref, 'type_ref'):
|
|
if type_ref is not None:
|
|
regmap_accum.type_ref = type_ref
|
|
else:
|
|
assert type_ref is None or type_ref == regmap_accum.type_ref
|
|
if regmap_accum:
|
|
self.__register_mappings.append(regmap_accum)
|
|
|
|
def update(self, other):
|
|
"""
|
|
Add the contents of the other database to self.
|
|
|
|
Doesn't de-duplicate entries.
|
|
"""
|
|
self.__post_init()
|
|
other.__post_init()
|
|
|
|
enum_remap = {}
|
|
regtype_remap = {}
|
|
|
|
for regmap in other.__register_mappings:
|
|
regmap = Object.from_json(Object.to_json(regmap))
|
|
|
|
type_ref = getattr(regmap, 'type_ref', None)
|
|
if type_ref is not None and type_ref not in regtype_remap:
|
|
regtype = Object.from_json(Object.to_json(other.__register_types[type_ref]))
|
|
|
|
chips = getattr(regmap, 'chips', [])
|
|
suffix = '_' + chips[0] if chips else ''
|
|
|
|
for field in regtype.fields:
|
|
enum_ref = getattr(field, 'enum_ref', None)
|
|
if enum_ref is not None and enum_ref not in enum_remap:
|
|
enum = Object.from_json(Object.to_json(other.__enums[enum_ref]))
|
|
|
|
remapped = enum_ref + suffix if enum_ref in self.__enums else enum_ref
|
|
i = 0
|
|
while remapped in self.__enums:
|
|
remapped = enum_ref + suffix + str(i)
|
|
i += 1
|
|
self.add_enum(remapped, enum)
|
|
enum_remap[enum_ref] = remapped
|
|
|
|
if enum_ref is not None:
|
|
field.enum_ref = enum_remap[enum_ref]
|
|
|
|
remapped = type_ref + suffix if type_ref in self.__register_types else type_ref
|
|
i = 0
|
|
while remapped in self.__register_types:
|
|
remapped = type_ref + suffix + str(i)
|
|
i += 1
|
|
self.add_register_type(remapped, regtype)
|
|
regtype_remap[type_ref] = remapped
|
|
|
|
if type_ref is not None:
|
|
regmap.type_ref = regtype_remap[type_ref]
|
|
|
|
self.add_register_mapping(regmap)
|
|
|
|
def to_json(self):
|
|
self.__post_init()
|
|
return {
|
|
'enums': Object.to_json(self.__enums),
|
|
'register_types': Object.to_json(self.__register_types),
|
|
'register_mappings': Object.to_json(self.__register_mappings),
|
|
}
|
|
|
|
def encode_json_pretty(self):
|
|
"""
|
|
Use a custom JSON encoder which pretty prints, but keeps inner structures compact
|
|
"""
|
|
# Since the JSON module isn't very extensible, this ends up being
|
|
# really hacky.
|
|
obj = self.to_json()
|
|
|
|
replacements = []
|
|
def placeholder(s):
|
|
placeholder = "JSON-{key}-NOSJ".format(key=len(replacements))
|
|
replacements.append(json.dumps(s, sort_keys=True))
|
|
return placeholder
|
|
|
|
# Pre-create non-indented encodings for inner objects
|
|
for enum in obj['enums'].values():
|
|
enum['entries'] = [
|
|
placeholder(entry)
|
|
for entry in enum['entries']
|
|
]
|
|
|
|
for regtype in obj['register_types'].values():
|
|
regtype['fields'] = [
|
|
placeholder(field)
|
|
for field in regtype['fields']
|
|
]
|
|
|
|
for regmap in obj['register_mappings']:
|
|
regmap['map'] = placeholder(regmap['map'])
|
|
if 'chips' in regmap:
|
|
regmap['chips'] = placeholder(regmap['chips'])
|
|
|
|
# Now create the 'outer' encoding with indentation and search-and-replace
|
|
# placeholders
|
|
result = json.dumps(obj, indent=1, sort_keys=True)
|
|
|
|
result = re.sub(
|
|
'"JSON-([0-9]+)-NOSJ"',
|
|
lambda m: replacements[int(m.group(1))],
|
|
result
|
|
)
|
|
|
|
return result
|
|
|
|
@staticmethod
|
|
def from_json(json):
|
|
db = RegisterDatabase()
|
|
|
|
db.__enums = dict((k, Object.from_json(v)) for k, v in json['enums'].items())
|
|
if 'register_types' in json:
|
|
db.__register_types = dict(
|
|
(k, Object.from_json(v))
|
|
for k, v in json['register_types'].items()
|
|
)
|
|
if 'register_mappings' in json:
|
|
db.__register_mappings = Object.from_json(json['register_mappings'])
|
|
|
|
# Old format
|
|
if 'registers' in json:
|
|
for reg in json['registers']:
|
|
type_ref = None
|
|
if 'fields' in reg and reg['fields']:
|
|
type_ref = reg['names'][0]
|
|
db.add_register_type(type_ref, Object(
|
|
fields=Object.from_json(reg['fields'])
|
|
))
|
|
|
|
for name in reg['names']:
|
|
regmap = Object(
|
|
name=name,
|
|
map=Object.from_json(reg['map'])
|
|
)
|
|
if type_ref is not None:
|
|
regmap.type_ref = type_ref
|
|
db.add_register_mapping(regmap)
|
|
|
|
db.__post_init()
|
|
return db
|
|
|
|
def deduplicate_enums(regdb):
|
|
"""
|
|
Find enums that have the exact same entries and merge them.
|
|
"""
|
|
buckets = defaultdict(list)
|
|
for name, enum in regdb.enums():
|
|
buckets[RegisterDatabase.enum_key(enum)].append(name)
|
|
|
|
for bucket in buckets.values():
|
|
if len(bucket) > 1:
|
|
regdb.merge_enums(bucket, bucket[0])
|
|
|
|
def deduplicate_register_types(regdb):
|
|
"""
|
|
Find register types with the exact same fields (identified by name and
|
|
bit range) and merge them.
|
|
|
|
However, register types *aren't* merged if they have different enums for
|
|
the same field (as an exception, if one of them has an enum and the other
|
|
one doesn't, we assume that one is simply missing a bit of information and
|
|
merge the register types).
|
|
"""
|
|
buckets = defaultdict(list)
|
|
for name, regtype in regdb.register_types():
|
|
key = ''.join(
|
|
':{0}:{1}:{2}:'.format(
|
|
field.name, field.bits[0], field.bits[1],
|
|
)
|
|
for field in regtype.fields
|
|
)
|
|
buckets[key].append((name, regtype.fields))
|
|
|
|
for bucket in buckets.values():
|
|
# Register types in the same bucket have the same fields in the same
|
|
# places, but they may have different enum_refs. Allow merging when
|
|
# one has an enum_ref and another doesn't, but don't merge if they
|
|
# have enum_refs that differ.
|
|
bucket_enum_refs = [
|
|
[getattr(field, 'enum_ref', None) for field in fields]
|
|
for name, fields in bucket
|
|
]
|
|
while bucket:
|
|
regtypes = [bucket[0][0]]
|
|
enum_refs = bucket_enum_refs[0]
|
|
del bucket[0]
|
|
del bucket_enum_refs[0]
|
|
|
|
idx = 0
|
|
while idx < len(bucket):
|
|
if all([
|
|
not lhs or not rhs or lhs == rhs
|
|
for lhs, rhs in zip(enum_refs, bucket_enum_refs[idx])
|
|
]):
|
|
regtypes.append(bucket[idx][0])
|
|
enum_refs = [lhs or rhs for lhs, rhs in zip(enum_refs, bucket_enum_refs[idx])]
|
|
del bucket[idx]
|
|
del bucket_enum_refs[idx]
|
|
else:
|
|
idx += 1
|
|
|
|
if len(regtypes) > 1:
|
|
regdb.merge_register_types(regtypes, regtypes[0])
|
|
|
|
# kate: space-indent on; indent-width 4; replace-tabs on;
|