diff --git a/m2isar/backends/etiss/architecture_writer.py b/m2isar/backends/etiss/architecture_writer.py index 6bb671d..1ecbdb0 100644 --- a/m2isar/backends/etiss/architecture_writer.py +++ b/m2isar/backends/etiss/architecture_writer.py @@ -105,7 +105,7 @@ def build_reg_hierarchy(reg: arch.Memory, ptr_regs: "list[arch.Memory]", actual_ 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_header_template = Template(filename=str(template_dir/'etiss_arch_cpp.mako')) + arch_cpp_template = Template(filename=str(template_dir/'etiss_arch_cpp.mako')) ptr_regs = [] actual_regs = [] @@ -122,13 +122,17 @@ def write_arch_cpp(core: arch.CoreDef, start_time: str, output_path: pathlib.Pat # 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.data_range.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.data_range.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_header_template.render( + txt = arch_cpp_template.render( start_time=start_time, core_name=core.name, instr_classes=sorted(core.instr_classes), @@ -145,11 +149,11 @@ def write_arch_cpp(core: arch.CoreDef, start_time: str, output_path: pathlib.Pat f.write(txt) def write_arch_lib(core: arch.CoreDef, start_time: str, output_path: pathlib.Path): - arch_header_template = Template(filename=str(template_dir/'etiss_arch_lib.mako')) + arch_lib_template = Template(filename=str(template_dir/'etiss_arch_lib.mako')) logger.info("writing architecture lib") - txt = arch_header_template.render( + txt = arch_lib_template.render( start_time=start_time, core_name=core.name ) @@ -158,21 +162,55 @@ def write_arch_lib(core: arch.CoreDef, start_time: str, output_path: pathlib.Pat f.write(txt) def write_arch_specific_header(core: arch.CoreDef, start_time: str, output_path: pathlib.Path): - arch_header_template = Template(filename=str(template_dir/'etiss_arch_specific_h.mako')) + arch_specific_header_template = Template(filename=str(template_dir/'etiss_arch_specific_h.mako')) logger.info("writing architecture specific header") - txt = arch_header_template.render( + txt = arch_specific_header_template.render( start_time=start_time, core_name=core.name, - main_reg=core.main_reg_file + main_reg=core.main_reg_file, + float_reg=core.float_reg_file, + vector_reg=core.vector_reg_file, + csr_reg=core.csr_reg_file ) with open(output_path / f"{core.name}ArchSpecificImp.h", "w", encoding="utf-8") as f: f.write(txt) -def write_arch_specific_cpp(core: arch.CoreDef, start_time: str, output_path: pathlib.Path): - arch_header_template = Template(filename=str(template_dir/'etiss_arch_specific_cpp.mako')) +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 @@ -212,37 +250,44 @@ def write_arch_specific_cpp(core: arch.CoreDef, start_time: str, output_path: pa 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_header_template.render( + 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 + 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) -def write_arch_gdbcore(core: arch.CoreDef, start_time: str, output_path: pathlib.Path): - arch_header_template = Template(filename=str(template_dir/'etiss_arch_gdbcore.mako')) +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_header_template.render( + txt = arch_gdbcore_template.render( start_time=start_time, core_name=core.name, - main_reg=core.main_reg_file + 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) def write_arch_cmake(core: arch.CoreDef, start_time: str, output_path: pathlib.Path, separate: bool): - arch_header_template = Template(filename=str(template_dir/'etiss_arch_cmake.mako')) + arch_cmake_template = Template(filename=str(template_dir/'etiss_arch_cmake.mako')) logger.info("writing CMakeLists") @@ -253,7 +298,7 @@ def write_arch_cmake(core: arch.CoreDef, start_time: str, output_path: pathlib.P 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_header_template.render( + txt = arch_cmake_template.render( start_time=start_time, core_name=core.name, arch_files=arch_files diff --git a/m2isar/backends/etiss/templates/etiss_arch_cmake.mako b/m2isar/backends/etiss/templates/etiss_arch_cmake.mako index 338bb75..f4acc47 100644 --- a/m2isar/backends/etiss/templates/etiss_arch_cmake.mako +++ b/m2isar/backends/etiss/templates/etiss_arch_cmake.mako @@ -33,4 +33,17 @@ add_custom_command( ) INSTALL(FILES "$${}{CMAKE_CURRENT_LIST_DIR}/$${}{PROJECT_NAME}Funcs.h" DESTINATION "include/jit/Arch/$${}{PROJECT_NAME}") -ETISSPluginArch($${}{PROJECT_NAME}) \ No newline at end of file +# handle gdbserver xml files +if(EXISTS "$${}{CMAKE_CURRENT_SOURCE_DIR}/xml") + add_custom_target(copy_$${}{PROJECT_NAME}_xml ALL + COMMAND $${}{CMAKE_COMMAND} -E make_directory + "$${}{CMAKE_CURRENT_LIST_DIR}/xml" + COMMAND $${}{CMAKE_COMMAND} -E copy_directory + "$${}{CMAKE_CURRENT_LIST_DIR}/xml" + "$${}{ETISS_BINARY_DIR}/xml/$${}{PROJECT_NAME}" + ) + add_dependencies($${}{PROJECT_NAME} copy_$${}{PROJECT_NAME}_xml) + install(DIRECTORY $${}{CMAKE_CURRENT_SOURCE_DIR}/xml/ DESTINATION xml/$${}{PROJECT_NAME}) +endif() + +ETISSPluginArch($${}{PROJECT_NAME}) diff --git a/m2isar/backends/etiss/templates/etiss_arch_gdbcore.mako b/m2isar/backends/etiss/templates/etiss_arch_gdbcore.mako index 8cdb5f6..69582d4 100644 --- a/m2isar/backends/etiss/templates/etiss_arch_gdbcore.mako +++ b/m2isar/backends/etiss/templates/etiss_arch_gdbcore.mako @@ -43,22 +43,45 @@ class ${core_name}GDBCore : public etiss::plugin::gdb::GDBCore public: std::string mapRegister(unsigned index) { + % if mapping is None: if (index < ${main_reg.range.length}) { std::stringstream ss; ss << "${main_reg.name}" << index; return ss.str(); } + % if float_reg is not None: + if ((${main_reg.range.length} < index) && (index < ${main_reg.range.length + float_reg.range.length + 1})) + { + std::stringstream ss; + ss << "${float_reg.name}" << (index - ${main_reg.range.length + 1}); + return ss.str(); + } + if ((${main_reg.range.length + float_reg.range.length} < index) && (index < ${main_reg.range.length + float_reg.range.length + 5})) + { // FCSR + std::stringstream ss; + ss << "CSR" << (index - ${main_reg.range.length + float_reg.range.length + 1}); + return ss.str(); + } + % endif + % endif switch (index) { + % if mapping is not None: + % for regnum, (name, name2) in mapping.items(): + case ${regnum}: + return "${name2}"; // ${name} + % endfor + % else: case ${main_reg.range.length}: return "instructionPointer"; + % endif /************************************************************************** - * Further register should be added here to send data over gdbserver * + * Further register should be added here to send data over gdbserver * **************************************************************************/ } return ""; - } + } unsigned mapRegister(std::string name) { return INVALIDMAPPING; } diff --git a/m2isar/backends/etiss/templates/etiss_arch_h.mako b/m2isar/backends/etiss/templates/etiss_arch_h.mako index 8d422b8..0bd40cf 100644 --- a/m2isar/backends/etiss/templates/etiss_arch_h.mako +++ b/m2isar/backends/etiss/templates/etiss_arch_h.mako @@ -75,6 +75,20 @@ class ${core_name}Arch : public etiss::CPUArch */ virtual etiss::int32 handleException(etiss::int32 code, ETISS_CPU *cpu); + /** + @brief This function will return the base installation directory of the ArchLib + + @see ${core_name}ArchSpecificImp.h + */ + virtual std::string installDir() const; + + /** + @brief This function will return the include prefix relative to the base installation directory of the ArchLib + + @see ${core_name}ArchSpecificImp.h + */ + virtual std::string jitFiles() const; + /** @brief This function is called during CPUArch initialization diff --git a/m2isar/backends/etiss/templates/etiss_arch_specific_cpp.mako b/m2isar/backends/etiss/templates/etiss_arch_specific_cpp.mako index 647dd38..f2a5e02 100644 --- a/m2isar/backends/etiss/templates/etiss_arch_specific_cpp.mako +++ b/m2isar/backends/etiss/templates/etiss_arch_specific_cpp.mako @@ -21,6 +21,7 @@ #include "${core_name}Arch.h" #include "${core_name}ArchSpecificImp.h" #include "${core_name}Funcs.h" +#include "etiss/Memory.h" /** @brief This function will be called automatically in order to handling exceptions such as interrupt, system call, @@ -48,6 +49,58 @@ etiss::int32 ${core_name}Arch::handleException(etiss::int32 cause, ETISS_CPU *cp return 0; } +/** + @brief See etiss/src/Misc.cpp +*/ +static etiss::ModuleHandle GetCurrentModule() +{ + static etiss::ModuleHandle hModule = 0; + + if (!hModule) + { + hModule = etiss::GetModuleByAddress((uintptr_t)GetCurrentModule); + } + + return hModule; +} + +/** + @brief See etiss/src/Misc.cpp +*/ +static std::string GetCurrentModulePath() +{ + static std::string modulePath; + + if (modulePath == "") + { + modulePath = etiss::GetModulePath(GetCurrentModule()); + } + + return modulePath; +} + +/** + @brief Resolves the ETISS install dir for both in-tree as well as out-of-tree builds +*/ +std::string ${core_name}Arch::installDir() const +{ + auto archLib = GetCurrentModulePath(); + auto libPathLoc = archLib.find_last_of("/\\"); + auto libPath = archLib.substr(0, libPathLoc); + auto pluginsPathLoc = libPath.find_last_of("/\\"); + auto pluginsPath = libPath.substr(0, pluginsPathLoc); + auto archPathLoc = pluginsPath.find_last_of("/\\"); + return libPath.substr(0, archPathLoc); +} + +/** + @brief This function is called during InstrSet initialization returns path to jit includes +*/ +std::string ${core_name}Arch::jitFiles() const +{ + return installDir() + "/include/jit"; +} + /** @brief This function is called during CPUArch initialization @@ -74,6 +127,13 @@ etiss::int32 ${core_name}Arch::handleException(etiss::int32 cause, ETISS_CPU *cp */ void ${core_name}Arch::initInstrSet(etiss::instr::ModedInstructionSet &mis) const { +% if fill_jit_extensions is not None: +${fill_jit_extensions} +% else: + /************************************************************************** + * JIT extensions should be defined here * + **************************************************************************/ +% endif if (false) { // Pre-compilation of instruction set to view instruction tree. Enable by setting 'true' above. @@ -104,9 +164,13 @@ ${callback}, %endfor +% if fill_length_updater is not None: +${fill_length_updater} +% else: /************************************************************************** * vis->length_updater_ should be replaced here * **************************************************************************/ +% endif } /** @@ -134,21 +198,36 @@ ${callback}, */ void ${core_name}Arch::compensateEndianess(ETISS_CPU *cpu, etiss::instr::BitArray &ba) const { +% if fill_endianess_compensation is not None: +${fill_endianess_compensation} +% else: /************************************************************************** * Endianess compensation * **************************************************************************/ +% endif } std::shared_ptr ${core_name}Arch::getVirtualStruct(ETISS_CPU *cpu) { auto ret = etiss::VirtualStruct::allocate(cpu, [](etiss::VirtualStruct::Field *f) { delete f; }); - for (uint32_t i = 0; i < ${main_reg.range.length}; ++i) + % if virtualstruct_regs is not None: + % for virtualstruct_class, idxs in virtualstruct_regs.items(): + % for idx in idxs: + % if isinstance(idx, range): + for (uint32_t i = ${idx.start}; i < ${idx.stop}; i += ${idx.step}) { - ret->addField(new RegField_${core_name}(*ret, i)); + ret->addField(new ${virtualstruct_class}_${core_name}(*ret, i)); } + % elif idx is None: + ret->addField(new ${virtualstruct_class}_${core_name}(*ret)); + % else: + ret->addField(new ${virtualstruct_class}_${core_name}(*ret, ${idx})); + % endif + % endfor + % endfor + % endif - ret->addField(new pcField_${core_name}(*ret)); return ret; } diff --git a/m2isar/backends/etiss/templates/etiss_arch_specific_h.mako b/m2isar/backends/etiss/templates/etiss_arch_specific_h.mako index 2449af4..5de2720 100644 --- a/m2isar/backends/etiss/templates/etiss_arch_specific_h.mako +++ b/m2isar/backends/etiss/templates/etiss_arch_specific_h.mako @@ -24,6 +24,7 @@ #include "etiss/VirtualStruct.h" #include "etiss/jit/CPU.h" #include "${core_name}.h" +#include "${core_name}Funcs.h" /** @brief VirtualStruct for ${core_name} architecture to faciliate register acess @@ -69,9 +70,10 @@ class RegField_${core_name} : public etiss::VirtualStruct::Field virtual ~RegField_${core_name}() {} protected: - virtual uint64_t _read() const + virtual uint64_t _read(size_t offset) const { // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); % if len(main_reg.children) > 0: return (uint64_t) *((${core_name}*)parent_.structure_)->${main_reg.name}[gprid_]; % else: @@ -80,9 +82,10 @@ class RegField_${core_name} : public etiss::VirtualStruct::Field // clang-format on } - virtual void _write(uint64_t val) + virtual void _write(uint64_t val, size_t offset) { // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); etiss::log(etiss::VERBOSE, "write to ETISS cpu state", name_, val); % if len(main_reg.children) > 0: *((${core_name}*)parent_.structure_)->${main_reg.name}[gprid_] = (etiss_uint${main_reg.size}) val; @@ -93,6 +96,180 @@ class RegField_${core_name} : public etiss::VirtualStruct::Field } }; +% if float_reg: +class FloatRegField_${core_name} : public etiss::VirtualStruct::Field +{ + private: + const unsigned gprid_; + + public: + FloatRegField_${core_name}(etiss::VirtualStruct &parent, unsigned gprid) + // clang-format off + : Field(parent, + std::string("${float_reg.name}")+etiss::toString(gprid), + std::string("${float_reg.name}")+etiss::toString(gprid), + R|W, + ${int(float_reg.size / 8)} + ), + gprid_(gprid) + // clang-format on + { + } + + FloatRegField_${core_name}(etiss::VirtualStruct &parent, std::string name, unsigned gprid) + // clang-format off + : Field(parent, + name, + name, + R|W, + ${int(float_reg.size / 8)} + ), + gprid_(gprid) + // clang-format on + { + } + + virtual ~FloatRegField_${core_name}() {} + + protected: + virtual uint64_t _read(size_t offset) const + { + // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); + % if len(main_reg.children) > 0: + return (uint64_t) *((${core_name}*)parent_.structure_)->${float_reg.name}[gprid_]; + % else: + return (uint64_t) ((${core_name}*)parent_.structure_)->${float_reg.name}[gprid_]; + % endif + // clang-format on + } + + virtual void _write(uint64_t val, size_t offset) + { + // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); + etiss::log(etiss::VERBOSE, "write to ETISS cpu state", name_, val); + % if len(main_reg.children) > 0: + *((${core_name}*)parent_.structure_)->${float_reg.name}[gprid_] = (etiss_uint${float_reg.size}) val; + % else: + ((${core_name}*)parent_.structure_)->${float_reg.name}[gprid_] = (etiss_uint${float_reg.size}) val; + % endif + // clang-format on + } +}; +% endif + +% if vector_reg: +class VectorRegField_${core_name} : public etiss::VirtualStruct::Field +{ + private: + const unsigned gprid_; + + public: + VectorRegField_${core_name}(etiss::VirtualStruct &parent,unsigned gprid) + // clang-format off + : Field(parent, + std::string("${vector_reg.name}")+etiss::toString(gprid), + std::string("${vector_reg.name}")+etiss::toString(gprid), + R|W, + ${int((vector_reg.range.length // 32) // (vector_reg.size // 8))} + ), + gprid_(gprid) + // clang-format on + { + } + + VectorRegField_${core_name}(etiss::VirtualStruct &parent, std::string name, unsigned gprid) + // clang-format off + : Field(parent, + name, + name, + R|W, + ${int((vector_reg.range.length // 32) // (vector_reg.size // 8))} + ), + gprid_(gprid) + // clang-format on + { + } + + virtual ~VectorRegField_${core_name}() {} + + protected: + virtual uint64_t _read(size_t offset) const + { + // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); + return (uint64_t) *((uint64_t*)&((${core_name}*)parent_.structure_)->${vector_reg.name}[gprid_ * width_ + sizeof(uint64_t) * offset]); + // clang-format on + } + + virtual void _write(uint64_t val, size_t offset) + { + // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); + etiss::log(etiss::VERBOSE, "write to ETISS cpu state", name_, val); + *((uint64_t*)&((${core_name}*)parent_.structure_)->${vector_reg.name}[gprid_ * width_ + sizeof(uint64_t)]) = (etiss_uint64) val; // TODO: write V[gprid_] instead + // clang-format on + } +}; +% endif + +% if csr_reg: +class CSRField_${core_name} : public etiss::VirtualStruct::Field +{ + private: + const unsigned gprid_; + + public: + CSRField_${core_name}(etiss::VirtualStruct &parent, unsigned gprid) + // clang-format off + : Field(parent, + std::string("${csr_reg.name}")+etiss::toString(gprid), + std::string("${csr_reg.name}")+etiss::toString(gprid), + R|W, + ${int(csr_reg.size / 8)} + ), + gprid_(gprid) + // clang-format on + { + } + + CSRField_${core_name}(etiss::VirtualStruct &parent, std::string name, unsigned gprid) + // clang-format off + : Field(parent, + name, + name, + R|W, + ${int(csr_reg.size / 8)} + ), + gprid_(gprid) + // clang-format on + { + } + + virtual ~CSRField_${core_name}() {} + + protected: + virtual uint64_t _read(size_t offset) const + { + // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); + return (uint64_t) ${core_name}_csr_read((ETISS_CPU*)parent_.structure_, nullptr, nullptr, (etiss_uint${csr_reg.size}) gprid_); + // clang-format on + } + + virtual void _write(uint64_t val, size_t offset) + { + // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); + etiss::log(etiss::VERBOSE, "write to ETISS cpu state", name_, val); + ${core_name}_csr_write((ETISS_CPU*)parent_.structure_, nullptr, nullptr, gprid_, (etiss_uint${csr_reg.size}) val); + // clang-format on + } +}; +% endif + + class pcField_${core_name} : public etiss::VirtualStruct::Field { public: @@ -112,16 +289,18 @@ class pcField_${core_name} : public etiss::VirtualStruct::Field virtual ~pcField_${core_name}() {} protected: - virtual uint64_t _read() const + virtual uint64_t _read(size_t offset) const { // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); return (uint64_t) ((ETISS_CPU *)parent_.structure_)->instructionPointer; // clang-format on } - virtual void _write(uint64_t val) + virtual void _write(uint64_t val, size_t offset) { // clang-format off + assert((offset == 0 || (offset < (bitwidth_ / sizeof(uint64_t)))) && "Virtualstruct field offset out of range"); etiss::log(etiss::VERBOSE, "write to ETISS cpu state", name_, val); ((ETISS_CPU *)parent_.structure_)->instructionPointer = (etiss_uint${main_reg.size}) val; // clang-format on diff --git a/m2isar/backends/etiss/templates/etiss_jit_extensions.mako b/m2isar/backends/etiss/templates/etiss_jit_extensions.mako new file mode 100644 index 0000000..577b2f3 --- /dev/null +++ b/m2isar/backends/etiss/templates/etiss_jit_extensions.mako @@ -0,0 +1,16 @@ + { + std::string requiredJitFilesPath = jitFiles(); + /* Set default JIT Extensions. Read Parameters set from ETISS configuration and append with architecturally needed */ + std::string cfgPar = ""; + cfgPar = etiss::cfg().get("jit.external_headers", ";"); + etiss::cfg().set("jit.external_headers", cfgPar + "${extra_headers}"); + + cfgPar = etiss::cfg().get("jit.external_libs", ";"); + etiss::cfg().set("jit.external_libs", cfgPar + "${extra_libs}"); + + cfgPar = etiss::cfg().get("jit.external_header_paths", ";"); + etiss::cfg().set("jit.external_header_paths", cfgPar + "${extra_header_paths}" + requiredJitFilesPath); + + cfgPar = etiss::cfg().get("jit.external_lib_paths", ";"); + etiss::cfg().set("jit.external_lib_paths", cfgPar + "${extra_lib_paths}"); + } diff --git a/m2isar/backends/etiss/templates/etiss_length_updater.mako b/m2isar/backends/etiss/templates/etiss_length_updater.mako new file mode 100644 index 0000000..8c9123a --- /dev/null +++ b/m2isar/backends/etiss/templates/etiss_length_updater.mako @@ -0,0 +1,88 @@ + vis->length_updater_ = [](VariableInstructionSet &, InstructionContext &ic, BitArray &ba) { + std::function update${core_name}InstrLength = + [](InstructionContext &ic, etiss_uint32 opRd) { + ic.instr_width_fully_evaluated_ = true; + ic.is_not_default_width_ = true; + if (opRd == 0x3f) + ic.instr_width_ = 64; + else if ((opRd & 0x3f) == 0x1f) + ic.instr_width_ = 48; + else if (((opRd & 0x1f) >= 0x3) && ((opRd & 0x1f) < 0x1f)) + ic.instr_width_ = 32; + else if(opRd == 0x7f) /* P-Extension instructions */ + ic.instr_width_ = 32; + else if ((opRd & 0x3) != 0x3) + ic.instr_width_ = 16; + else + // This might happen when code is followed by data. + ic.is_not_default_width_ = false; + }; + + BitArrayRange op(6, 0); + etiss_uint32 opRd = op.read(ba); + + /*BitArrayRange fullOp(ba.byteCount()*8-1,0); + etiss_uint32 fullOpRd = fullOp.read(ba); + + std::stringstream ss; + ss << "Byte count: " << ba.byteCount()<< std::endl; + ss << "opcode: 0x" <= 0x3) || ((opRd & 0x1f) < 0x1f)) || (opRd == 0)) + { + ic.is_not_default_width_ = false; + break; + } + else if(opRd == 0x7f) /* P-Extension instructions */ + { + update${core_name}InstrLength(ic, opRd); + break; + } + else + { + update${core_name}InstrLength(ic, opRd); + break; + } + case 6: + if (((opRd & 0x3f) == 0x1f) || (opRd == 0)) + { + ic.is_not_default_width_ = false; + break; + } + else + { + update${core_name}InstrLength(ic, opRd); + break; + } + case 8: + if ((opRd == 0x3f) || (opRd == 0)) + { + ic.is_not_default_width_ = false; + break; + } + else + { + update${core_name}InstrLength(ic, opRd); + break; + } + default: + // This might happen when code is followed by data. + ic.is_not_default_width_ = false; + } + }; diff --git a/m2isar/backends/etiss/virtualstruct_utils.py b/m2isar/backends/etiss/virtualstruct_utils.py new file mode 100644 index 0000000..82a4492 --- /dev/null +++ b/m2isar/backends/etiss/virtualstruct_utils.py @@ -0,0 +1,225 @@ +# 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 + +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"} + +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 + + +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 + + +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 + + +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 + +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 + + +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 diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index 05074c8..8624e0a 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -26,6 +26,7 @@ write_arch_specific_header, write_arch_struct) from .instruction_writer import write_functions, write_instructions +from .virtualstruct_utils import process_gdb_xml_descr_args, get_virtualstruct_regs, get_gdb_mapping # TODO: not required anymore for Python >= v3.9 @@ -86,6 +87,9 @@ def setup(): help="Force end translation blocks on no instructions, uncoditional jumps or all jumps.") parser.add_argument("--coverage", action=BooleanOptionalAction, default=False, help="Generate coverage tracking code into model.") parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) + parser.add_argument("--gdb-xml-descr", nargs="+", default=[]) + parser.add_argument("--fill-mode", choices=["auto", "empty"], default="empty") + # TODO: add modes for rvv,... args = parser.parse_args() # configure logging @@ -126,11 +130,13 @@ def setup(): return (model_obj.cores, logger, output_base_path, spec_name, start_time, args) + def main(): """etiss_writer main entrypoint function.""" # setup etiss writer cores, logger, output_base_path, spec_name, start_time, args = setup() + descr_mapping = process_gdb_xml_descr_args(args.gdb_xml_descr, cores) # preprocess all models for core_name, core in cores.items(): @@ -154,6 +160,9 @@ def main(): # generate each core in the model for core_name, core in cores.items(): logger.info("processing model %s", core_name) + mapping = descr_mapping.get(core_name) + virtualstruct_regs = get_virtualstruct_regs(mapping, core.memories, core.memory_aliases) + gdb_mapping = get_gdb_mapping(mapping, core.memories, core.memory_aliases) # create output files path output_path = output_base_path / spec_name / core_name @@ -168,10 +177,10 @@ def main(): write_arch_header(core, start_time, output_path) write_arch_cpp(core, start_time, output_path, False) write_arch_specific_header(core, start_time, output_path) - write_arch_specific_cpp(core, start_time, output_path) + write_arch_specific_cpp(core, start_time, output_path, virtualstruct_regs, args.fill_mode) write_arch_lib(core, start_time, output_path) write_arch_cmake(core, start_time, output_path, args.separate) - write_arch_gdbcore(core, start_time, output_path) + write_arch_gdbcore(core, start_time, output_path, gdb_mapping) write_functions(core, start_time, output_path, args.static_scalars, args.coverage) write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage) diff --git a/m2isar/backends/viewer/viewer.py b/m2isar/backends/viewer/viewer.py index fb897fe..57cbecb 100644 --- a/m2isar/backends/viewer/viewer.py +++ b/m2isar/backends/viewer/viewer.py @@ -125,6 +125,7 @@ def main(): # add auxillary attributes tree.insert(core_id, tk.END, text="Main Memory Object", values=(core_def.main_memory,)) tree.insert(core_id, tk.END, text="Main Register File Object", values=(core_def.main_reg_file,)) + # TODO: float_reg_file? tree.insert(core_id, tk.END, text="PC Memory Object", values=(core_def.pc_memory,)) # add functions to tree diff --git a/m2isar/frontends/coredsl2/architecture_model_builder.py b/m2isar/frontends/coredsl2/architecture_model_builder.py index 3739fd0..79ded9a 100644 --- a/m2isar/frontends/coredsl2/architecture_model_builder.py +++ b/m2isar/frontends/coredsl2/architecture_model_builder.py @@ -35,6 +35,9 @@ class ArchitectureModelBuilder(CoreDSL2Visitor): _overwritten_instrs: "list[tuple[arch.Instruction, arch.Instruction]]" _instr_classes: "set[int]" _main_reg_file: Union[arch.Memory, None] + _float_reg_file: Union[arch.Memory, None] + _vector_reg_file: Union[arch.Memory, None] + _csr_reg_file: Union[arch.Memory, None] def __init__(self): super().__init__() @@ -51,6 +54,9 @@ def __init__(self): self._overwritten_instrs = [] self._instr_classes = set() self._main_reg_file = None + self._float_reg_file = None + self._vector_reg_file = None + self._csr_reg_file = None def visitBit_field(self, ctx: CoreDSL2Parser.Bit_fieldContext): """Generate a bit field (instruction parameter in encoding).""" @@ -388,6 +394,12 @@ def visitDeclaration(self, ctx: CoreDSL2Parser.DeclarationContext): if arch.MemoryAttribute.IS_MAIN_REG in attributes: self._main_reg_file = m + if arch.MemoryAttribute.IS_FLOAT_REG in attributes: + self._float_reg_file = m + if arch.MemoryAttribute.IS_VECTOR_REG in attributes: + self._vector_reg_file = m + if arch.MemoryAttribute.IS_CSR_REG in attributes or name.upper() == "CSR": + self._csr_reg_file = m self._memories[name] = m ret_decls.append(m) diff --git a/m2isar/metamodel/arch.py b/m2isar/metamodel/arch.py index 6761b16..36da83a 100644 --- a/m2isar/metamodel/arch.py +++ b/m2isar/metamodel/arch.py @@ -205,6 +205,9 @@ class MemoryAttribute(Enum): IS_PC = auto() IS_MAIN_MEM = auto() IS_MAIN_REG = auto() + IS_FLOAT_REG = auto() + IS_VECTOR_REG = auto() + IS_CSR_REG = auto() DELETE = auto() ETISS_CAN_FAIL = auto() ETISS_IS_GLOBAL_IRQ_EN = auto() @@ -586,6 +589,9 @@ def __init__(self, name, contributing_types: "list[str]", template: str, constan self.instructions = instructions self.instr_classes = instr_classes self.main_reg_file = None + self.float_reg_file = None + self.vector_reg_file = None + self.csr_reg_file = None self.main_memory = None self.pc_memory = None self.global_irq_en_memory = None @@ -605,6 +611,12 @@ def __init__(self, name, contributing_types: "list[str]", template: str, constan for mem in itertools.chain(self.memories.values(), self.memory_aliases.values()): if MemoryAttribute.IS_MAIN_REG in mem.attributes: self.main_reg_file = mem + if MemoryAttribute.IS_FLOAT_REG in mem.attributes: + self.float_reg_file = mem + if MemoryAttribute.IS_VECTOR_REG in mem.attributes: + self.vector_reg_file = mem + if MemoryAttribute.IS_CSR_REG in mem.attributes or mem.name.upper() == "CSR": + self.csr_reg_file = mem elif MemoryAttribute.IS_PC in mem.attributes: self.pc_memory = mem elif MemoryAttribute.IS_MAIN_MEM in mem.attributes: