diff --git a/sw-sysemu/CMakeLists.txt b/sw-sysemu/CMakeLists.txt index 911538b92..b82be9f61 100644 --- a/sw-sysemu/CMakeLists.txt +++ b/sw-sysemu/CMakeLists.txt @@ -169,6 +169,7 @@ set(CORE_SYSEMU_SOURCES insns/zifencei.cpp memory/main_memory.cpp pma.cpp + boot-protocol.cpp mmu.cpp msgport.cpp processor.cpp diff --git a/sw-sysemu/boot-protocol.cpp b/sw-sysemu/boot-protocol.cpp new file mode 100644 index 000000000..a1375f621 --- /dev/null +++ b/sw-sysemu/boot-protocol.cpp @@ -0,0 +1,10 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +#include "emu_defines.h" + +#if EMU_ERBIUM +#include "boot-protocol_er.cpp" +#endif diff --git a/sw-sysemu/boot-protocol_er.cpp b/sw-sysemu/boot-protocol_er.cpp new file mode 100644 index 000000000..c961dd439 --- /dev/null +++ b/sw-sysemu/boot-protocol_er.cpp @@ -0,0 +1,44 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +#include + +#include "system.h" +#include "processor.h" +#include "emu_gio.h" + +namespace bemu { + +void System::apply_boot_protocol(uint64_t payload_pc, uint64_t payload_sp) +{ + constexpr uint64_t MRAM_BASE = 0x40000000ULL; + constexpr uint64_t BOOT_PROT_SIZE = 0x100; + constexpr uint64_t PC_OFFSET = 0x28; + constexpr uint64_t SP_OFFSET = 0x20; + + // Write PC and SP to MRAM boot vector addresses + memory.init(noagent, MRAM_BASE + PC_OFFSET, sizeof(payload_pc), &payload_pc); + memory.init(noagent, MRAM_BASE + SP_OFFSET, sizeof(payload_sp), &payload_sp); + + uint64_t pc = payload_pc; + uint64_t sp = payload_sp; + + // Sanity checks + if (pc >= MRAM_BASE && pc < MRAM_BASE + BOOT_PROT_SIZE) + LOG_AGENT(WARN, noagent, "Payload PC (0x%" PRIx64 ") points into boot protocol region", pc); + if (sp >= MRAM_BASE && sp < MRAM_BASE + BOOT_PROT_SIZE) + LOG_AGENT(WARN, noagent, "Payload SP (0x%" PRIx64 ") points into boot protocol region", sp); + + // Set enabled harts + for (unsigned h = 0; h < EMU_NUM_THREADS; ++h) { + if (cpu[h].is_nonexistent() || cpu[h].is_unavailable()) + continue; + cpu[h].pc = pc; + cpu[h].npc = pc; + cpu[h].xregs[2] = sp; + } +} + +} // namespace bemu diff --git a/sw-sysemu/examples/common/erbium.ld b/sw-sysemu/examples/common/erbium.ld index a930b9a81..1fc22dbd0 100644 --- a/sw-sysemu/examples/common/erbium.ld +++ b/sw-sysemu/examples/common/erbium.ld @@ -5,14 +5,17 @@ BOOTROM_BASE = 0x0002008000; BOOTROM_SIZE = 8K; SRAM_BASE = 0x000200C000; SRAM_SIZE = 4K; -MRAM_BASE = 0x0040000000; -MRAM_SIZE = 16M; +MSYS_BASE = 0x0040000000; +MSYS_SIZE = 256; +MRAM_BASE = 0x0040000100; +MRAM_SIZE = 0xFFFF00; STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 1K; MAX_THREADS = 16; MEMORY { BOOTROM (rx) : org = BOOTROM_BASE, len = BOOTROM_SIZE SRAM (rwx) : org = SRAM_BASE, len = SRAM_SIZE + MSYS (r) : org = MSYS_BASE, len = MSYS_SIZE MRAM (rwx) : org = MRAM_BASE, len = MRAM_SIZE } diff --git a/sw-sysemu/pma_er.cpp b/sw-sysemu/pma_er.cpp index 5b29cb424..e6be016b8 100644 --- a/sw-sysemu/pma_er.cpp +++ b/sw-sysemu/pma_er.cpp @@ -10,6 +10,7 @@ #include "esrs.h" #include "system.h" #include "utility.h" +#include "emu_gio.h" namespace bemu { @@ -174,6 +175,13 @@ uint64_t pma_check_data_access(const Hart& cpu, uint64_t vaddr, } if (paddr_is_mram(addr)) { + // Boot protocol: warn if software writes to the reserved region. + if (addr < (MRAM_BASE + 0x100) + && data_access_is_write(macc) + && macc != Mem_Access_CacheOp) { + WARN_HART(memory, cpu, "Store to boot protocol region (0x%" PRIx64 ")", addr); + } + uint16_t mprot = cpu.chip->neigh_esrs[neigh_index(cpu)].mprot; Privilege mode = effective_execution_mode(cpu, macc); diff --git a/sw-sysemu/sys_emu/sys_emu.cpp b/sw-sysemu/sys_emu/sys_emu.cpp index 9503aeb8a..7432d6307 100644 --- a/sw-sysemu/sys_emu/sys_emu.cpp +++ b/sw-sysemu/sys_emu/sys_emu.cpp @@ -257,6 +257,9 @@ sys_emu::sys_emu(const sys_emu_cmd_options &cmd_options, api_communicate *api_co single_step.reset(); if (cmd_options.elf_files.empty() && cmd_options.file_load_files.empty() && +#if EMU_ERBIUM + cmd_options.boot_elf.empty() && +#endif cmd_options.mem_desc_file.empty() && cmd_options.api_comm_path.empty() && g_preload->empty()) { LOG_AGENT(FTL, agent, "%s", "Need an ELF file, a file load, a mem_desc file or runtime API!"); } @@ -286,6 +289,20 @@ sys_emu::sys_emu(const sys_emu_cmd_options &cmd_options, api_communicate *api_co } } +#if EMU_ERBIUM + // Load boot ELF (--boot_elf): loads ELF and captures entry point for boot protocol + uint64_t elf_entry = 0; + if (!cmd_options.boot_elf.empty()) { + LOG_AGENT(INFO, agent, "Loading boot ELF: \"%s\"", cmd_options.boot_elf.c_str()); + try { + elf_entry = chip.load_elf(cmd_options.boot_elf.c_str()); + } + catch (...) { + LOG_AGENT(FTL, agent, "Error loading boot ELF \"%s\"", cmd_options.boot_elf.c_str()); + } + } +#endif + // Parses the ELF files and memory description for (const auto &elf: cmd_options.elf_files) { LOG_AGENT(INFO, agent, "Loading ELF: \"%s\"", elf.c_str()); @@ -493,6 +510,14 @@ sys_emu::sys_emu(const sys_emu_cmd_options &cmd_options, api_communicate *api_co chip.end_warm_reset(shire); } +#if EMU_ERBIUM + // Apply boot protocol if enabled (--boot_elf, --payload_pc, or --payload_sp) + if (!cmd_options.boot_elf.empty() || cmd_options.payload_pc || cmd_options.payload_sp) { + uint64_t pc = cmd_options.payload_pc ? cmd_options.payload_pc : elf_entry; + chip.apply_boot_protocol(pc, cmd_options.payload_sp); + } +#endif + // Initialize xregs passed to command line for (auto &info: cmd_options.set_xreg) { chip.cpu[info.thread].xregs[info.xreg] = info.value; diff --git a/sw-sysemu/sys_emu/sys_emu.h b/sw-sysemu/sys_emu/sys_emu.h index d4e7c56d1..7be9a150a 100644 --- a/sw-sysemu/sys_emu/sys_emu.h +++ b/sw-sysemu/sys_emu/sys_emu.h @@ -100,6 +100,12 @@ struct sys_emu_cmd_options { std::vector set_xreg; +#if EMU_ERBIUM + std::string boot_elf; + uint64_t payload_pc = 0; + uint64_t payload_sp = 0; +#endif + bool coherency_check = false; uint64_t max_cycles = 10000000; bool mins_dis = false; diff --git a/sw-sysemu/sys_emu/sys_emu_parse_args.cpp b/sw-sysemu/sys_emu/sys_emu_parse_args.cpp index 0c6d0797a..d8650c230 100644 --- a/sw-sysemu/sys_emu/sys_emu_parse_args.cpp +++ b/sw-sysemu/sys_emu/sys_emu_parse_args.cpp @@ -48,6 +48,11 @@ static const char * help_msg = #if EMU_HAS_SVCPROC " -sp_reset_pc Sets Service Processor boot program counter (default: 0x40000000)\n" #endif // EMU_HAS_SVCPROC +#if EMU_ERBIUM +" -boot_elf Load ELF and use its entry point as payload PC for the boot protocol\n" +" -payload_pc Override payload PC written to MRAM boot vector (hex)\n" +" -payload_sp Override payload SP written to MRAM boot vector (hex)\n" +#endif // EMU_ERBIUM " -set_xreg ,, Sets the xregister (integer) of thread to value . can be 'sp' for the Service Processor\n" #endif // SDK_RELEASE " -max_cycles Stops execution after provided number of cycles (default: 10M)\n" @@ -185,6 +190,11 @@ sys_emu::parse_command_line_arguments(int argc, char* argv[]) {"reset_pc", required_argument, nullptr, 0}, #if EMU_HAS_SVCPROC {"sp_reset_pc", required_argument, nullptr, 0}, +#endif +#if EMU_ERBIUM + {"boot_elf", required_argument, nullptr, 0}, + {"payload_pc", required_argument, nullptr, 0}, + {"payload_sp", required_argument, nullptr, 0}, #endif {"set_xreg", required_argument, nullptr, 0}, #endif @@ -390,6 +400,20 @@ sys_emu::parse_command_line_arguments(int argc, char* argv[]) { sscanf(optarg, "%" PRIx64, &cmd_options.sp_reset_pc); } +#endif +#if EMU_ERBIUM + else if (!strcmp(name, "boot_elf")) + { + cmd_options.boot_elf = std::string(optarg); + } + else if (!strcmp(name, "payload_pc")) + { + sscanf(optarg, "%" PRIx64, &cmd_options.payload_pc); + } + else if (!strcmp(name, "payload_sp")) + { + sscanf(optarg, "%" PRIx64, &cmd_options.payload_sp); + } #endif else if (!strcmp(name, "set_xreg")) { diff --git a/sw-sysemu/system.cpp b/sw-sysemu/system.cpp index fbdb7283b..5d51a69b2 100644 --- a/sw-sysemu/system.cpp +++ b/sw-sysemu/system.cpp @@ -566,7 +566,7 @@ void System::config_simulated_harts(unsigned shire, uint32_t minionmask, } -void System::load_elf(std::istream& stream) +uint64_t System::load_elf(std::istream& stream) { ELFIO::elfio elf; elf.load(stream); @@ -606,16 +606,17 @@ void System::load_elf(std::istream& stream) memory.init(noagent, lma, sec->get_size(), sec->get_data()); } } + return elf.get_entry(); } -void System::load_elf(const char* filename) +uint64_t System::load_elf(const char* filename) { std::ifstream file; file.exceptions(std::ifstream::failbit | std::ifstream::badbit); file.open(filename, std::ios::in | std::ios::binary); - load_elf(file); + return load_elf(file); } diff --git a/sw-sysemu/system.h b/sw-sysemu/system.h index 643de7eef..83273ac36 100644 --- a/sw-sysemu/system.h +++ b/sw-sysemu/system.h @@ -115,8 +115,13 @@ class System { void init(Stepping); // Preload memory - void load_elf(std::istream&); - void load_elf(const char* filename); + uint64_t load_elf(std::istream&); + uint64_t load_elf(const char* filename); + +#if EMU_ERBIUM + // Boot protocol + void apply_boot_protocol(uint64_t payload_pc, uint64_t payload_sp); +#endif void load_raw(const char* filename, unsigned long long addr); // Reset state diff --git a/sw-sysemu/tests/erbium/Makefile b/sw-sysemu/tests/erbium/Makefile index 4445a2011..f814cd0d7 100644 --- a/sw-sysemu/tests/erbium/Makefile +++ b/sw-sysemu/tests/erbium/Makefile @@ -98,6 +98,33 @@ run/%: $(BUILD_DIR)/%.elf echo " UNKNOWN"; exit 1; \ fi +# Boot protocol tests: re-run all tests with --boot_elf +# The ELF entry point is used as the payload PC automatically. +run-boot-protocol: $(tests_elf) + @npass=0; nfail=0; \ + for test in $(tests); do \ + echo "+ running $$test (boot protocol)"; \ + cat "$(INPUT_DIR)/$$test.in" 2>/dev/null | \ + $(EMU) -l $(EMU_ARGS) --boot_elf $(BUILD_DIR)/$$test.elf > $(BUILD_DIR)/$$test.bp.out 2>&1; \ + if grep -q "Signal end test with FAIL" $(BUILD_DIR)/$$test.bp.out; then \ + echo " FAIL"; \ + nfail=$$((nfail + 1)); \ + elif grep -q "Signal end test with PASS" $(BUILD_DIR)/$$test.bp.out; then \ + echo " PASS"; \ + npass=$$((npass + 1)); \ + else \ + echo " UNKNOWN (no pass/fail signal)"; \ + nfail=$$((nfail + 1)); \ + fi; \ + done; \ + echo ""; \ + if [ $$nfail -eq 0 ]; then \ + echo "SUCCESS: $(num_tests) boot protocol | $$npass passed | $$nfail failed"; \ + else \ + echo "FAILURE: $(num_tests) boot protocol | $$npass passed | $$nfail failed"; \ + exit 1; \ + fi + # GDB tests (explicit list) gdb_tests := thread_disable_consistency num_gdb_tests := $(words $(gdb_tests)) diff --git a/sw-sysemu/tests/erbium/common/erbium.ld b/sw-sysemu/tests/erbium/common/erbium.ld index b1b94ac59..0873ab744 100644 --- a/sw-sysemu/tests/erbium/common/erbium.ld +++ b/sw-sysemu/tests/erbium/common/erbium.ld @@ -11,8 +11,10 @@ BOOTROM_BASE = 0x0002008000; BOOTROM_SIZE = 8K; SRAM_BASE = 0x000200C000; SRAM_SIZE = 4K; -MRAM_BASE = 0x0040000000; -MRAM_SIZE = 16M; +MSYS_BASE = 0x0040000000; +MSYS_SIZE = 256; +MRAM_BASE = 0x0040000100; +MRAM_SIZE = 0xFFFF00; /* Stack configuration */ MAX_THREADS = 16; @@ -22,6 +24,7 @@ STACK_SIZE_LOG2 = 6; MEMORY { BOOTROM (rx) : org = BOOTROM_BASE, len = BOOTROM_SIZE SRAM (rwx) : org = SRAM_BASE, len = SRAM_SIZE + MSYS (r) : org = MSYS_BASE, len = MSYS_SIZE MRAM (rwx) : org = MRAM_BASE, len = MRAM_SIZE } diff --git a/sw-sysemu/tests/erbium/src/all_harts.c b/sw-sysemu/tests/erbium/src/all_harts.c index 5ddd83aa0..f3244a97a 100644 --- a/sw-sysemu/tests/erbium/src/all_harts.c +++ b/sw-sysemu/tests/erbium/src/all_harts.c @@ -16,7 +16,7 @@ #define ESR_THREAD0_DISABLE 0x80F40240ULL #define ESR_THREAD1_DISABLE 0x80F40010ULL -#define MRAM_BASE 0x40000000ULL +#define MRAM_BASE 0x40000100ULL /* past 256-byte boot protocol region */ #define NUM_HARTS 16 #define CSR_FLB 0x820 diff --git a/sw-sysemu/tests/erbium/src/boot_protocol_sp.S b/sw-sysemu/tests/erbium/src/boot_protocol_sp.S new file mode 100644 index 000000000..e9b8a4baa --- /dev/null +++ b/sw-sysemu/tests/erbium/src/boot_protocol_sp.S @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +/* +* Test: --payload_sp sets the initial stack pointer +* +* This is a standalone assembly test (no boot.S) that checks SP at the +* very first instruction. Must be run with: +* --boot_elf --payload_sp 200c100 +* +* The boot protocol sets SP before the hart starts executing, so we +* check it immediately at entry before anything can overwrite it. +*/ + + .section .text.init, "ax", @progbits + .globl _boot +_boot: + li t0, 0x200c100 + bne sp, t0, fail + + /* PASS */ + li a7, 0x1feed000 + csrw 0x8d0, a7 /* validation0 */ + j done + +fail: + /* FAIL */ + li a7, 0x50bad000 + csrw 0x8d0, a7 /* validation0 */ + +done: + wfi + j done diff --git a/sw-sysemu/tests/erbium/src/pma_mram_boot_region.c b/sw-sysemu/tests/erbium/src/pma_mram_boot_region.c new file mode 100644 index 000000000..e56f3a9bf --- /dev/null +++ b/sw-sysemu/tests/erbium/src/pma_mram_boot_region.c @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +/* +* Test: Store to MRAM boot protocol region succeeds with warning +* +* The first 256 bytes of MRAM (0x40000000-0x400000FF) are reserved for +* the boot protocol. Stores are allowed (no fault) but the emulator +* logs a warning. Verify the store actually lands in memory. +*/ + +#include "test.h" +#include + +#define MRAM_BOOT_REGION 0x40000020ull /* Payload SP offset */ +#define TEST_PATTERN 0xDEADBEEFCAFEFEEDull + +int main() { + volatile uint64_t *boot_region = (volatile uint64_t *)MRAM_BOOT_REGION; + + *boot_region = TEST_PATTERN; + + if (*boot_region == TEST_PATTERN) { + TEST_PASS; + } + + TEST_FAIL; + return 0; +} diff --git a/sw-sysemu/tests/erbium/src/pma_mram_rw.c b/sw-sysemu/tests/erbium/src/pma_mram_rw.c index 974e3d92d..ebb1e431a 100644 --- a/sw-sysemu/tests/erbium/src/pma_mram_rw.c +++ b/sw-sysemu/tests/erbium/src/pma_mram_rw.c @@ -10,7 +10,7 @@ #include "test.h" #include -#define MRAM_BASE 0x40000000ull +#define MRAM_BASE 0x40000100ull /* past 256-byte boot protocol region */ #define TEST_PATTERN 0xDEADBEEFCAFEFEEDull int main() { diff --git a/sw-sysemu/tests/erbium/src/thread_disable_consistency.c b/sw-sysemu/tests/erbium/src/thread_disable_consistency.c index 582313f18..86a0a5857 100644 --- a/sw-sysemu/tests/erbium/src/thread_disable_consistency.c +++ b/sw-sysemu/tests/erbium/src/thread_disable_consistency.c @@ -20,8 +20,8 @@ #define ESR_THREAD0_DISABLE 0x80F40240ULL #define ESR_THREAD1_DISABLE 0x80F40010ULL -#define MRAM_BASE 0x40000000ULL -#define MARKERS (MRAM_BASE + 0x100) +#define MRAM_BASE 0x40000100ULL /* past 256-byte boot protocol region */ +#define MARKERS (MRAM_BASE) int main() { volatile uint64_t *thread0_disable = (volatile uint64_t *)ESR_THREAD0_DISABLE;