# 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) 2022
# Chair of Electrical Design Automation
# Technical University of Munich
"""Functions for generating auxillary ETISS ArchImpl files."""
import logging
import pathlib
from itertools import chain
from typing import Union
from mako.template import Template
from .instruction_utils import actual_size, reg_type_info
from ... import M2TypeError
from ...metamodel import arch, behav, type_info, attribute_info
from . import BlockEndType
from .instruction_generator import (generate_fields,
generate_instruction_callback)
from .templates import template_dir
[docs]
logger = logging.getLogger("arch_writer")
[docs]
def write_child_reg_def(reg: Union[arch.Memory, arch.RegisterBank, arch.Register], regs: "list[str]"):
"""Recursively generate child declarations for Memories and Register(Banks)"""
logger.debug("processing register %s", reg)
if attribute_info.RegisterAttribute.IS_PC in reg.attributes or attribute_info.MemoryAttribute.IS_MAIN_MEM in reg.attributes:
logger.debug("this register is either the PC or main memory, skipping")
return
assert(isinstance(reg.ty, (type_info.ArrayType, type_info.PrimitiveType, type_info.PointerType)))
type_acc = reg_type_info(reg)
if isinstance(reg.ty, type_info.ArrayType):
array_txt = f"[{arch.get_const_or_val(reg.ty.length)}]"
assert(reg.ty.element_type.kind in (type_info.TypeKind.UINT, type_info.TypeKind.INT))
elif isinstance(reg.ty, type_info.PrimitiveType):
assert(reg.ty.kind in (type_info.TypeKind.UINT, type_info.TypeKind.INT))
array_txt = ""
elif isinstance(reg.ty, type_info.PointerType):
array_txt = f"[{arch.get_const_or_val(reg.data_range.length)}]" if arch.get_const_or_val(reg.data_range.length) > 1 else ""
else:
array_txt = ""
if hasattr(reg, "children"):
logger.debug("processing children")
for child in reg.children:
write_child_reg_def(child, regs)
# registers with children (aliases) are defined as two arrays:
# 1) array of pointers, used for actual access
# 2) array of actual data type, for every index which is not aliased
type_acc = reg_type_info(reg)
if isinstance(reg.ty, type_info.ArrayType):
assert(reg.ty.element_type.kind in (type_info.TypeKind.UINT, type_info.TypeKind.INT))
size = actual_size(reg.ty.element_type.size)
elif isinstance(reg.ty, type_info.PrimitiveType):
assert(reg.ty.kind in (type_info.TypeKind.UINT, type_info.TypeKind.INT))
size = actual_size(reg.ty.size)
else:
raise "Register Types needs to be of Array or PrimitiveType"
if (len(reg.children) > 0):
regs.append(f"{type_acc}{size} *{reg.name}{array_txt}")
regs.append(f"{type_acc}{size} ins_{reg.name}{array_txt}")
else:
regs.append(f"{type_acc}{actual_size(size)} {reg.name}{array_txt}")
else:
size = None
if isinstance(reg.ty, type_info.PointerType):
assert(isinstance(reg.ty.ty, type_info.PrimitiveType))
size = actual_size(reg.ty.ty.size)
else:
assert(isinstance(reg.ty, type_info.PrimitiveType))
size = actual_size(reg.ty.size)
regs.append(f"{type_acc}{size} {reg.name}{array_txt}")
[docs]
def write_arch_struct(core: arch.CoreDef, start_time: str, output_path: pathlib.Path):
arch_struct_template = Template(filename=str(template_dir/'etiss_arch_struct.mako'))
regs = []
mems = []
logger.info("writing architecture struct")
assert core.memories is not None and core.register_banks is not None
for _, reg_desc in chain(core.register_banks.items()):
write_child_reg_def(reg_desc, regs)
for _, mem_desc in chain(core.memories.items()):
write_child_reg_def(mem_desc, mems)
txt = arch_struct_template.render(
start_time=start_time,
core_name=core.name,
regs=regs,
mems=mems
)
with open(output_path / f"{core.name}.h", "w", encoding="utf-8") as f:
f.write(txt)
[docs]
def build_reg_hierarchy(reg: Union[arch.Memory, arch.Alias, arch.Register, arch.RegisterBank],
ptr_regs: "list[Union[arch.Memory, arch.RegisterBank]]", actual_regs: "list[Union[arch.Register, arch.RegisterBank]]",
alias_regs: "dict[arch.Alias]", initval_regs: "list[Union[arch.Memory, arch.Register, arch.RegisterBank]]"):
"""Populate the passed lists with memory objects of their category.
# CSR is actually an extern memory range but treatet as a reg
ptr_regs: Registers that need to be a pointer within ETISS
actual_regs: Registers that are not a pointer
alias_regs: Registers which are an alias to some other register
initval_regs: Registers which have initial value(s) defined in the model
"""
# pylint: disable=protected-access
if reg._initval:
initval_regs.append(reg)
if isinstance(reg, (arch.Memory, arch.Register, arch.RegisterBank)):
if len(reg.children) > 0:
for child in reg.children:
if hasattr(child, "is_main_mem"):
if child.is_main_mem:
logger.warning("main memory is a child memory of %s", reg)
continue
build_reg_hierarchy(child, ptr_regs, actual_regs, alias_regs, initval_regs)
assert isinstance(child, arch.Alias)
alias_regs[child] = reg
ptr_regs.append(reg)
else:
actual_regs.append(reg)
else:
assert isinstance(reg, arch.Alias)
return
[docs]
def write_arch_cpp(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, aliased_regnames: bool=True):
"""Generate {CoreName}Arch.cpp file. Contains mainly register initialization code."""
arch_cpp_template = Template(filename=str(template_dir/'etiss_arch_cpp.mako'))
ptr_regs : list[arch.Memory] = []
actual_regs : list[Union[arch.Register, arch.RegisterBank]] = []
alias_regs: dict[arch.Alias] = {}
initval_regs: list[arch.Memory] = []
logger.info("writing architecture class file")
# determine memory types
for _, mem_desc in chain(core.memories.items(), core.register_banks.items(), core.memory_aliases.items(), core.register_aliases.items()):
if hasattr(mem_desc, "is_main_mem"):
if mem_desc.is_main_mem:
continue
build_reg_hierarchy(mem_desc, ptr_regs, actual_regs, alias_regs, initval_regs)
# generate main register file names for ETISS's 'char* reg_name[]'
reg_names = [f"{core.main_reg_file.name}{n}" for n in range(core.main_reg_file.ty.length)]
if core.float_reg_file is not None:
reg_names += [f"{core.float_reg_file.name}{n}" for n in range(core.float_reg_file.ty.length)]
# TODO(annnnna42): add float reg names (F0-F31) here
# if main register file entries have aliases optionally use these for 'char* reg_name[]'
if aliased_regnames:
for child in core.main_reg_file.children:
reg_names[child.range.lower] = child.name
# TODO(annnnna42): add float reg aliases here
txt = arch_cpp_template.render(
start_time=start_time,
core_name=core.name,
instr_classes=sorted(core.instr_classes),
reg_init_code="",
reg_names=reg_names,
ptr_regs=ptr_regs,
actual_regs=actual_regs,
alias_regs=alias_regs,
initval_regs=initval_regs,
procno_memory=core.procno_memory,
type_info=type_info,
arch=arch,
)
with open(output_path / f"{core.name}Arch.cpp", "w", encoding="utf-8") as f:
f.write(txt)
[docs]
def write_arch_lib(core: arch.CoreDef, start_time: str, output_path: pathlib.Path):
arch_lib_template = Template(filename=str(template_dir/'etiss_arch_lib.mako'))
logger.info("writing architecture lib")
txt = arch_lib_template.render(
start_time=start_time,
core_name=core.name
)
with open(output_path / f"{core.name}ArchLib.cpp", "w", encoding="utf-8") as f:
f.write(txt)
[docs]
def write_arch_specific_cpp(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, virtualstruct_regs: dict, fill_mode: str):
fill_jit_extensions=None
fill_length_updater=None
fill_endianess_compensation=None
assert isinstance(fill_mode, str)
fill_mode = fill_mode.lower()
if fill_mode == "auto":
extra_headers = set()
extra_libs = set()
extra_header_paths = set()
extra_lib_paths = set()
has_softfloat = core.float_reg_file is not None
has_softvector = core.vector_reg_file is not None
extra_header_paths.add("etiss/jit")
extra_lib_paths.add("etiss/jit")
if has_softfloat:
extra_headers.add("etiss/jit/libsoftfloat.h")
extra_libs.add("softfloat")
if has_softvector:
extra_headers.add("etiss/jit/libsoftvector.h")
extra_headers.add("etiss/jit/softvector.h")
extra_libs.add("softvector")
extra_libs.add("etiss_softvector")
fill_jit_extensions = Template(filename=str(template_dir/'etiss_jit_extensions.mako')).render(
extra_headers=";".join(sorted(list(extra_headers))),
extra_libs=";".join(sorted(list(extra_libs))),
extra_header_paths=";".join(sorted(list(extra_header_paths))),
extra_lib_paths=";".join(sorted(list(extra_lib_paths))),
)
fill_length_updater = Template(filename=str(template_dir/'etiss_length_updater.mako')).render(core_name=core.name)
else:
assert fill_mode == "empty", f"Unsupported fill_mode: {fill_mode}"
arch_source_template = Template(filename=str(template_dir/'etiss_arch_specific_cpp.mako'))
error_fn = None
for fn in core.functions.values():
if attribute_info.FunctionAttribute.ETISS_TRAP_ENTRY_FN in fn.attributes:
error_fn = fn
break
for fn in core.functions.values():
if attribute_info.FunctionAttribute.ETISS_TRAP_TRANSLATE_FN in fn.attributes:
error_fn = fn
break
error_callbacks: "dict[int, str]" = {}
if error_fn is not None:
for bitsize in core.instr_classes:
error_bitfield = arch.BitField("error_code", arch.RangeSpec(31, 0), type_info.TypeKind.UINT)
error_instr = arch.Instruction(f"trap_entry {bitsize}", {attribute_info.InstrAttribute.NO_CONT: None}, [error_bitfield], "", "", None, None)
error_bitfield_descr = error_instr.fields.get("error_code")
error_op = behav.Operation([
behav.ProcedureCall(error_fn, [behav.NamedReference(error_bitfield_descr)])
])
error_instr.operation = error_op
error_instr.throws = True
error_instr._size = bitsize # pylint: disable=protected-access
error_fields = generate_fields(32, error_instr)
error_callbacks[bitsize] = generate_instruction_callback(core, error_instr, error_fields, True, BlockEndType.NONE, False)
logger.info("writing architecture specific file")
global_irq_en_mask = None
if core.global_irq_en_memory is not None:
attr = core.global_irq_en_memory.attributes[attribute_info.MemoryAttribute.ETISS_IS_GLOBAL_IRQ_EN][0]
if not isinstance(attr, behav.Literal):
raise M2TypeError(f"IRQ enable mask of {core.global_irq_en_memory.name} is not compile static")
global_irq_en_mask = attr.value
txt = arch_source_template.render(
start_time=start_time,
core_name=core.name,
main_reg=core.main_reg_file,
float_reg=core.float_reg_file,
irq_en_reg=core.irq_en_memory,
irq_pending_reg=core.irq_pending_memory,
global_irq_en_reg=core.global_irq_en_memory,
global_irq_en_mask=global_irq_en_mask,
error_callbacks=error_callbacks,
error_fn=error_fn,
virtualstruct_regs=virtualstruct_regs,
fill_jit_extensions=fill_jit_extensions,
fill_length_updater=fill_length_updater,
fill_endianess_compensation=fill_endianess_compensation,
)
with open(output_path / f"{core.name}ArchSpecificImp.cpp", "w", encoding="utf-8") as f:
f.write(txt)
[docs]
def write_arch_gdbcore(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, gdb_mapping: dict):
arch_gdbcore_template = Template(filename=str(template_dir/'etiss_arch_gdbcore.mako'))
logger.info("writing gdbcore")
txt = arch_gdbcore_template.render(
start_time=start_time,
core_name=core.name,
main_reg=core.main_reg_file,
float_reg=core.float_reg_file,
mapping=gdb_mapping,
)
with open(output_path / f"{core.name}GDBCore.h", "w", encoding="utf-8") as f:
f.write(txt)
[docs]
def write_arch_cmake(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, separate: bool):
arch_cmake_template = Template(filename=str(template_dir/'etiss_arch_cmake.mako'))
logger.info("writing CMakeLists")
arch_files = [f'{core.name}Instr.cpp']
# if generation of one instr.cpp per extension is desired, only generate extensions which actually
# contain instructions
if separate:
arch_files += [f'{core.name}_{ext_name}Instr.cpp' for ext_name in core.contributing_types if len(core.instructions_by_ext[ext_name]) > 0]
txt = arch_cmake_template.render(
start_time=start_time,
core_name=core.name,
arch_files=arch_files
)
with open(output_path / "CMakeLists.txt", "w", encoding="utf-8") as f:
f.write(txt)