Source code for m2isar.backends.etiss.virtualstruct_utils

# SPDX-License-Identifier: Apache-2.0
#
# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R
#
# Copyright (C) 2025
# Chair of Electrical Design Automation
# Technical University of Munich

"""Helper functions for dealing with ETISS VirtualStruct and GDBCore."""

import re
import pathlib
from typing import List, Union
from collections import defaultdict
import xml.etree.ElementTree as ET
from xml.etree import ElementTree, ElementInclude
from ...metamodel.arch import MemoryAttribute

[docs] DEFAULT_ALIASES = {"zero": "x0", "ra": "x1", "sp": "x2", "gp": "x3", "tp": "x4", "t0": "x5", "t1": "x6", "t2": "x7", "s0": "x8", "fp": "x8", "s1": "x9", "a0": "x10", "a1": "x11", "a2": "x12", "a3": "x13", "a4": "x14", "a5": "x15", "a6": "x16", "a7": "x17", "s2": "x18", "s3": "x19", "s4": "x20", "s5": "x21", "s6": "x22", "s7": "x23", "s8": "x24", "s9": "x25", "s10": "x26", "s11": "x27", "t3": "x28", "t4": "x29", "t5": "x30", "t6": "x31"}
[docs] def list_to_ranges(nums: List[int]) -> List[Union[int, range]]: """ Convert a list of integers into a list of contiguous ranges and single values. - Contiguous sequences (len >= 2) -> range(start, stop) - Single isolated numbers -> int """ if not nums: return [] nums = list(set(nums)) # deduplicate if len(nums) == 1 and nums[0] is None: return nums # Expand everything into a flat sorted list of ints expanded = [] for item in nums: if isinstance(item, range): expanded.extend(item) else: expanded.append(item) nums = sorted(set(expanded)) # sort & deduplicate result = [] start = prev = nums[0] for n in nums[1:]: if n == prev + 1: prev = n continue # close current segment if prev > start: result.append(range(start, prev + 1)) else: result.append(start) start = prev = n # handle last segment if prev > start: result.append(range(start, prev + 1)) else: result.append(start) return result
[docs] def process_xml_descr(path): tree = ElementTree.parse(path) root = tree.getroot() parent = pathlib.Path(path).parent def custom_loader(href, parse, encoding=None): if not pathlib.Path(href).is_file(): href = parent / href if parse == "xml": with open(href, 'rb') as file: data = ElementTree.parse(file).getroot() else: if not encoding: encoding = 'UTF-8' with open(href, 'r', encoding=encoding) as file: data = file.read() return data ElementInclude.include(root, loader=custom_loader) mapping = {} num = -1 for node in tree.iter(): if node.tag == "reg": num = node.attrib.get("regnum", num + 1) if isinstance(num, str): num = int(num, 0) name = node.attrib["name"] sz = int(node.attrib["bitsize"]) mapping[num] = (name, sz) return mapping
[docs] def resolve_reg(name, mems, aliases): # print("resolve_reg", name, mems, aliases) split_name = lambda s: (m.group(1), int(m.group(2))) if (m:=re.fullmatch(r'([a-zA-Z]+)(\d+)', s)) else None idx = None ret = mems.get(name, mems.get(name.lower(), mems.get(name.upper()))) if ret is None: ret = aliases.get(name, aliases.get(name.lower(), aliases.get(name.upper(), aliases.get(f"{name.upper()}_CSR")))) if ret is None: splitted = split_name(name) if splitted is not None: name, idx = splitted ret = mems.get(name, mems.get(name.lower(), mems.get(name.upper()))) if ret is None: ret = aliases.get(name, aliases.get(name.lower(), aliases.get(name.upper(), aliases.get(f"{name.upper()}_CSR")))) if ret is None: new_name = DEFAULT_ALIASES.get(name, DEFAULT_ALIASES.get(name.lower(), DEFAULT_ALIASES.get(name.upper()))) if new_name is not None: return resolve_reg(new_name, mems, aliases) return ret, idx
[docs] def process_gdb_xml_descr_args(args: List[str], cores: list): descr_mapping = {} args = [part.strip() for arg in args for part in arg.split(",") if part.strip()] for descr in args: assert ":" in descr, f"Invalid mapping: {descr}" core, xml_path = descr.split(":", 1) assert core in cores, f"Unknown core: {core}" mapping = process_xml_descr(xml_path) descr_mapping[core] = mapping return descr_mapping
[docs] def get_gdb_mapping(mapping: dict, memories: dict, memory_aliases: dict): HARCODED_NAMES = {"PC": "instructionPointer"} gdb_mapping = None if mapping is not None: gdb_mapping = {} for regnum, data in mapping.items(): name, sz = data resolved = resolve_reg(name, memories, memory_aliases) assert resolved is not None, f"Register lookup failed: {name}" if resolved is not None: mem, idx = resolved assert mem is not None, f"Register lookup failed: {name}" if mem.parent is not None: # alias rng = mem.range assert rng.length == 1, "Aliased ranges are not allowed" assert idx is None idx = rng.lower mem = mem.parent # assert mem.size == sz, f"Expected size missmatch: {mem.size} vs. {sz}" # TODO: handle fcsr size assert mem.size >= sz or name in [f"v{i}" for i in range(32)], f"Expected size missmatch: {mem.size} vs. {sz} [{name}]" name2 = mem.name name2 = HARCODED_NAMES.get(name2, name2) if idx is not None: name2 += str(idx) gdb_mapping[regnum] = (name, name2) return gdb_mapping
[docs] def get_virtualstruct_regs(mapping: dict, memories: dict, memory_aliases: dict): main_reg = None float_reg = None vector_reg = None csr_reg = None pc_reg = None for mem in memories.values(): if mem.is_pc: pc_reg = mem elif MemoryAttribute.IS_MAIN_REG in mem.attributes or mem.name == "X": main_reg = mem elif MemoryAttribute.IS_FLOAT_REG in mem.attributes or mem.name == "F": float_reg = mem elif MemoryAttribute.IS_VECTOR_REG in mem.attributes or mem.name == "F": vector_reg = mem elif MemoryAttribute.IS_CSR_REG in mem.attributes or mem.name == "CSR": csr_reg = mem aliased_csrs = set() if csr_reg is not None: for mem in memory_aliases.values(): if mem.parent == csr_reg: if mem.range.length == 1: idx = mem.range.lower aliased_csrs.add(idx) assert main_reg is not None, "Unable to identify main_reg" assert pc_reg is not None, "Unable to identify pc_reg" VIRTUALSTRUCT_CLASSES = { main_reg.name: "RegField", **({float_reg.name: "FloatRegField"} if float_reg is not None else {}), **({vector_reg.name: "VectorRegField"} if vector_reg is not None else {}), **({csr_reg.name: "CSRField"} if csr_reg is not None else {}), **({pc_reg.name: "pcField"} if pc_reg is not None else {}), } aliased_csrs_only = True DEFAULT_VIRTUALSTRUCT_REGS = { "RegField": [range(0, main_reg.range.length)], **({"FloatRegField": [range(0, float_reg.range.length)]} if float_reg is not None else {}), # **({"VectorRegField": [range(0, vector_reg.range.length)]} if vector_reg is not None else {}), **({"VectorRegField": [range(0, 32)]} if vector_reg is not None else {}), **({"CSRField": list(sorted(aliased_csrs)) if aliased_csrs_only else [range(0, csr_reg.range.length)]} if csr_reg is not None else {}), "pcField": [None] } virtualstruct_regs = defaultdict(list) virtualstruct_regs.update(DEFAULT_VIRTUALSTRUCT_REGS) if mapping is not None: for regnum, data in mapping.items(): name, sz = data resolved = resolve_reg(name, memories, memory_aliases) assert resolved is not None, f"Register lookup failed: {name}" if resolved is not None: mem, idx = resolved assert mem is not None, f"Register lookup failed: {name}" if mem.parent is not None: # alias rng = mem.range assert rng.length == 1, "Aliased ranges are not allowed" assert idx is None idx = rng.lower mem = mem.parent # assert mem.size == sz, f"Expected size missmatch: {mem.size} vs. {sz}" # TODO: handle fcsr size assert mem.size >= sz or name in [f"v{i}" for i in range(32)], f"Expected size missmatch: {mem.size} vs. {sz} [{name}]" name = mem.name virtualstruct_class = VIRTUALSTRUCT_CLASSES.get(name) assert virtualstruct_class is not None, f"Unable to find VirtualStruct class for reg: {name}" virtualstruct_regs[virtualstruct_class].append(idx) if virtualstruct_regs is not None: virtualstruct_regs = {key: list_to_ranges(val) for key, val in virtualstruct_regs.items()} return virtualstruct_regs