You've already forked amd-register-interface-extractor
mirror of
https://github.com/Dasharo/amd-register-interface-extractor.git
synced 2026-03-06 14:55:16 -08:00
177 lines
8.4 KiB
Python
Executable File
177 lines
8.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Assumption: Input SVD has only absolute addresses--and those are in <addressOffset>
|
|
|
|
from lxml import etree
|
|
import sys
|
|
import settings
|
|
import logging
|
|
from logging import debug, info, warning, error, critical
|
|
|
|
def eval_int(element):
|
|
""" Given a SVD node, extracts the integer from its text. """
|
|
s = element.text
|
|
if s.startswith("0x"):
|
|
return int(s[len("0x"):], 16)
|
|
else:
|
|
return int(s)
|
|
|
|
"""
|
|
Let's say there's a hypothetical peripheral FOO with these registers:
|
|
|
|
A at offset 0
|
|
B at offset 4
|
|
|
|
and there are 3 instances of that peripheral.
|
|
|
|
Then, phase3 (and, really, AMD) arranges the registers like this:
|
|
|
|
A:
|
|
instance0 at offset 0 (derivedFrom not set)
|
|
instance1 at offset 8 (derivedFrom set to A.instance0)
|
|
instance2 at offset 16 (derivedFrom set to A.instance0)
|
|
B:
|
|
instance0 at offset 4 (derivedFrom not set)
|
|
instance1 at offset 12 (derivedFrom set to B.instance0)
|
|
instance2 at offset 20 (derivedFrom set to B.instance0)
|
|
|
|
What we ideally want is the first structure. Phase4 tries to collect clusters together in order to get the first structure.
|
|
|
|
To that end, if this node has no derivedFrom, it's good to know what the first item derived from this one is going to be.
|
|
For example, after A.instance0 there comes A.instance1, and that gives you the maximal cluster size for this peripheral cluster.
|
|
"""
|
|
|
|
def traverse(source_root, parent_name, peripheral_name):
|
|
""" Traverses source_root and modifies it in place to cluster things together that belong together. """
|
|
has_any_register = len([child for child in source_root if child.tag == "register"]) > 0
|
|
if has_any_register:
|
|
registers = sorted((eval_int(child.find("addressOffset")), eval_int(child.find("size")), child.find("name").text, child) for child in source_root if child.tag == "register")
|
|
addressOffset = -1
|
|
cluster = None
|
|
addressLimits = []
|
|
def add_to_cluster(x_child):
|
|
source_root.remove(x_child)
|
|
cluster.append(x_child)
|
|
def update_addressLimits(x_child):
|
|
nonlocal addressLimits
|
|
if x_child.attrib.get("derivedFrom") is not None:
|
|
return
|
|
#addressLimits = []
|
|
x_child_name = x_child.find("name").text
|
|
assert x_child_name
|
|
found = False
|
|
instances = [y_child for (_, _, _, y_child) in registers if y_child is x_child or y_child.attrib.get("derivedFrom") == x_child_name]
|
|
i = instances.index(x_child)
|
|
for j in range(i + 1, len(instances)):
|
|
next_register = instances[j]
|
|
assert next_register.tag == "register"
|
|
next_register_addressOffset = eval_int(next_register.find("addressOffset"))
|
|
if next_register_addressOffset > x_addressOffset:
|
|
assert next_register_addressOffset > x_addressOffset, (next_register.find("name").text, x_child_name)
|
|
while j >= len(addressLimits):
|
|
addressLimits.append(2**32)
|
|
if next_register_addressOffset < addressLimits[j]:
|
|
addressLimits[j] = next_register_addressOffset
|
|
def finish_cluster():
|
|
nonlocal cluster
|
|
nonlocal addressLimits
|
|
if cluster is not None:
|
|
members = [node for node in cluster if node.tag != "name"]
|
|
if len(members) > 0:
|
|
if len(members) == 1:
|
|
for node in cluster:
|
|
if node.tag == "name":
|
|
pass
|
|
elif node.tag == "register":
|
|
cluster.remove(node)
|
|
source_root.append(node)
|
|
cluster_name = cluster.find("name").text
|
|
if not cluster_name.endswith("_unsorted"):
|
|
info("Eliding cluster {!r} grouping because there's only one node in it".format(cluster_name))
|
|
else:
|
|
# We assume that if the user didn't configure groups (and thus the thing ends up in "_unsorted" cluster) then he doesn't care about the register and thus there's no need to spam him.
|
|
pass
|
|
break
|
|
else:
|
|
assert False, node.tag
|
|
else:
|
|
source_root.append(cluster)
|
|
cluster = etree.Element("cluster")
|
|
addressLimits = []
|
|
def calculate_cluster_name(name, fallback=True):
|
|
if name.startswith(parent_name):
|
|
name = name[len(parent_name):]
|
|
while name.startswith("_"):
|
|
name = name[1:]
|
|
else:
|
|
parent_basename = parent_name.split("_")[-1]
|
|
if name.replace("_", "").startswith(parent_basename):
|
|
name = name.replace("_", "")[len(parent_basename):]
|
|
while name.startswith("_"):
|
|
name = name[1:]
|
|
return settings.phase4_cluster_names.get(peripheral_name, {}).get(name, (name + "_unsorted") if fallback else None)
|
|
finish_cluster()
|
|
addressOffset, first_size, first_name, first_child = registers[0]
|
|
new_cluster_name_text = calculate_cluster_name(first_name, True)
|
|
cluster_name = etree.Element("name")
|
|
cluster_name.text = new_cluster_name_text
|
|
cluster.append(cluster_name)
|
|
previous_addressOffset = -1
|
|
for x_addressOffset, x_size, x_name, x_child in registers:
|
|
#if x_name.find("FabricIndirectConfigAccessDataLo_n0") != -1:
|
|
# import pdb
|
|
# pdb.set_trace()
|
|
dim = x_child.find("dim")
|
|
if dim is not None:
|
|
x_size = x_size * eval_int(dim)
|
|
update_addressLimits(x_child)
|
|
new_cluster_name_text = calculate_cluster_name(x_name, False)
|
|
if x_addressOffset == previous_addressOffset:
|
|
assert new_cluster_name_text is None
|
|
elif x_addressOffset < addressOffset or x_addressOffset > addressOffset + 8 or (addressLimits != [] and x_addressOffset >= addressLimits[0]) or new_cluster_name_text is not None: # next register instance is not where we expected it to be, or we are outside that instance now, or user requested new cluster.
|
|
new_cluster_name_text = calculate_cluster_name(x_name, True)
|
|
if addressLimits != [] and x_addressOffset >= addressLimits[0]:
|
|
#new_cluster_name_text = new_cluster_name_text + "_{}".format(addressLimits[0])
|
|
addressLimits = addressLimits[1:]
|
|
cluster_name = cluster.find("name") if cluster is not None else None
|
|
cluster_name_text = cluster_name.text if cluster_name is not None else None
|
|
if cluster_name_text != new_cluster_name_text:
|
|
finish_cluster()
|
|
cluster_name = etree.Element("name")
|
|
cluster_name.text = new_cluster_name_text
|
|
cluster.append(cluster_name)
|
|
else: # if x_addressOffset >= addressLimit:
|
|
addressLimits = []
|
|
update_addressLimits(x_child)
|
|
addressOffset = x_addressOffset
|
|
add_to_cluster(x_child)
|
|
previous_addressOffset = x_addressOffset
|
|
addressOffset = x_addressOffset + x_size//8
|
|
finish_cluster()
|
|
if len([x_child for x_child in source_root]) == 1 and source_root[0].tag == "cluster": # There's only one cluster
|
|
for x_child in source_root:
|
|
source_root.remove(x_child)
|
|
for y_child in x_child:
|
|
if y_child.tag in ["cluster", "register"]:
|
|
source_root.append(y_child)
|
|
break
|
|
else:
|
|
name = source_root.find("name")
|
|
if name is not None:
|
|
name = name.text
|
|
if source_root.tag == "peripheral":
|
|
peripheral_name = name
|
|
else:
|
|
name = parent_name
|
|
for child in source_root:
|
|
traverse(child, name, peripheral_name)
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
tree = etree.parse(sys.stdin if len(sys.argv) == 1 else open(sys.argv[-1]))
|
|
root = tree.getroot()
|
|
|
|
traverse(root, "", None)
|
|
|
|
tree.write(sys.stdout.buffer, pretty_print=True)
|
|
sys.stdout.flush()
|