diff --git a/sw-sysemu/devices/mram_bridge_er.h b/sw-sysemu/devices/mram_bridge_er.h new file mode 100644 index 000000000..272a72029 --- /dev/null +++ b/sw-sysemu/devices/mram_bridge_er.h @@ -0,0 +1,121 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +#ifndef BEMU_MRAM_BRIDGE_ER_H +#define BEMU_MRAM_BRIDGE_ER_H + +#include +#include "agent.h" +#include "system.h" +#include "memory/memory_error.h" +#include "memory/memory_region.h" +#include "emu_gio.h" + +namespace bemu { + +template +struct MramBridgeEr : public MemoryRegion { + using addr_type = typename MemoryRegion::addr_type; + using size_type = typename MemoryRegion::size_type; + using value_type = typename MemoryRegion::value_type; + using pointer = typename MemoryRegion::pointer; + using const_pointer = typename MemoryRegion::const_pointer; + + // Register offsets (64-bit aligned) + static constexpr size_type ARBITER_MODE = 0x00; + static constexpr size_type BRIDGE_STATUS = 0x08; + static constexpr size_type SLVERR_STATUS = 0x10; + static constexpr size_type CONTROL = 0x18; + static constexpr size_type ECC_1BIT_COUNT = 0x20; + static constexpr size_type ECC_2BIT_COUNT = 0x28; + static constexpr size_type ECC_3BIT_COUNT = 0x30; + + // bridge_status_reg bits + static constexpr uint32_t MRAM_READY_MASK = 0xF00; // bits [11:8] + + // slverr_status_reg bits + static constexpr uint32_t SLVERR_MRAM_NOT_READY = 1 << 2; + static constexpr uint32_t SLVERR_MRAM_UNPOWERED = 1 << 3; + + void read(const Agent& agent, size_type pos, size_type n, pointer result) override { + (void)n; + uint64_t val = 0; + + switch (pos) { + case ARBITER_MODE: + val = arbiter_mode; + break; + case BRIDGE_STATUS: + // mram_ready reflects current dsleep state + val = agent.chip->memory.is_mram_dsleep() ? 0 : MRAM_READY_MASK; + break; + case SLVERR_STATUS: + // Clear-on-read + val = slverr_status; + slverr_status = 0; + break; + case CONTROL: + val = control; + break; + case ECC_1BIT_COUNT: + case ECC_2BIT_COUNT: + case ECC_3BIT_COUNT: + val = 0; + break; + default: + throw memory_error(Base + pos); + } + + *reinterpret_cast(result) = val; + } + + void write(const Agent& agent, size_type pos, size_type n, const_pointer source) override { + (void)agent; (void)n; + uint64_t val = *reinterpret_cast(source); + + switch (pos) { + case ARBITER_MODE: + arbiter_mode = val & 0x3; + break; + case BRIDGE_STATUS: + // Read-only, writes ignored + break; + case SLVERR_STATUS: + // Read-only (clear-on-read), writes ignored + break; + case CONTROL: + control = val; + break; + case ECC_1BIT_COUNT: + case ECC_2BIT_COUNT: + case ECC_3BIT_COUNT: + // Counters are read-only from software, writes ignored + break; + default: + throw memory_error(Base + pos); + } + } + + void init(const Agent&, size_type, size_type, const_pointer) override { + throw std::runtime_error("bemu::MramBridgeEr::init()"); + } + + addr_type first() const override { return Base; } + addr_type last() const override { return Base + N - 1; } + + void dump_data(const Agent&, std::ostream&, size_type, size_type) const override { } + + // Called from PMA to set slverr sticky bits + void set_slverr(uint32_t bits) { slverr_status |= bits; } + +private: + uint32_t arbiter_mode = 0x2; // Reset: round-robin + uint32_t slverr_status = 0; + uint32_t control = 0; +}; + +} // namespace bemu + +#endif // BEMU_MRAM_BRIDGE_ER_H diff --git a/sw-sysemu/devices/sysregs_er.cpp b/sw-sysemu/devices/sysregs_er.cpp index 55492bd70..b5da8eb65 100644 --- a/sw-sysemu/devices/sysregs_er.cpp +++ b/sw-sysemu/devices/sysregs_er.cpp @@ -25,6 +25,7 @@ void SysregsEr::reset(ResetCause cause) sys_interrupt = 0; reset_cause = static_cast(cause); // Set reset cause power_domain_req = 0; + mram_powered = false; power_domain_ack = 0; spin_lock = 0; chip_mode = 0; @@ -161,6 +162,7 @@ void SysregsEr::write_register(const Agent& agent, uint64_t offset, uint32 case POWER_DOMAIN_REQ: power_domain_req = value; + mram_powered = !(value & POWER_DOMAIN_REQ_MRAM_DSLEEP_EN); break; case POWER_STATUS: diff --git a/sw-sysemu/devices/sysregs_er.h b/sw-sysemu/devices/sysregs_er.h index 48a5b5e2f..91fb5d3bb 100644 --- a/sw-sysemu/devices/sysregs_er.h +++ b/sw-sysemu/devices/sysregs_er.h @@ -56,6 +56,7 @@ struct SysregsEr : public MemoryRegion { void wdt_clock_tick(const Agent& agent, uint64_t cycle); bool is_uart_enabled() const { return system_config & SYSTEM_CONFIG_UART_ENABLE; } + bool is_mram_dsleep() const { return !mram_powered; } private: @@ -93,6 +94,8 @@ struct SysregsEr : public MemoryRegion { static constexpr uint32_t SPIN_LOCK_LOCK = 1 << 0; + static constexpr uint32_t POWER_DOMAIN_REQ_MRAM_DSLEEP_EN = 1 << 16; + static constexpr uint32_t SOFT_RESET_MRAM_RST_B = 1 << 2; // Register Values @@ -113,6 +116,9 @@ struct SysregsEr : public MemoryRegion { uint32_t system_divider; uint32_t periph_divider; + // MRAM power state: starts off, turns on when mram_dsleep_en is first written 0 + bool mram_powered = false; + // Watchdog device with 4-cycle divider (250MHz from 1GHz system clock) Watchdog<4> watchdog; diff --git a/sw-sysemu/memory/erbium/main_memory.cpp b/sw-sysemu/memory/erbium/main_memory.cpp index d2cf3d20d..c45891a1f 100644 --- a/sw-sysemu/memory/erbium/main_memory.cpp +++ b/sw-sysemu/memory/erbium/main_memory.cpp @@ -5,6 +5,7 @@ #include "memory/erbium/main_memory.h" #include "emu_defines.h" +#include "devices/mram_bridge_er.h" #include "devices/plic_er.h" #include "devices/shakti_uart.h" #include "devices/sysregs_er.h" @@ -17,7 +18,7 @@ namespace bemu { void MainMemory::reset() { regions[erbreg_idx].reset(new SysregsEr()); - regions[mram_bridge_idx].reset(new DenseRegion()); + regions[mram_bridge_idx].reset(new MramBridgeEr()); regions[uart_idx].reset(new ShaktiUart()); regions[bootrom_idx].reset(new DenseRegion()); regions[sram_idx].reset(new DenseRegion()); @@ -97,6 +98,16 @@ int MainMemory::uart_get_rx_fd() const { return ptr->rx_fd; } +bool MainMemory::is_mram_dsleep() const { + auto ptr = dynamic_cast*>(regions[erbreg_idx].get()); + return ptr->is_mram_dsleep(); +} + +void MainMemory::mram_bridge_set_slverr(uint32_t bits) { + auto ptr = dynamic_cast*>(regions[mram_bridge_idx].get()); + ptr->set_slverr(bits); +} + bool MainMemory::is_uart_enabled() const { auto ptr = dynamic_cast*>(regions[erbreg_idx].get()); return ptr->is_uart_enabled(); diff --git a/sw-sysemu/memory/erbium/main_memory.h b/sw-sysemu/memory/erbium/main_memory.h index 38b179e95..b9f2ac7b0 100644 --- a/sw-sysemu/memory/erbium/main_memory.h +++ b/sw-sysemu/memory/erbium/main_memory.h @@ -123,6 +123,10 @@ struct MainMemory { void wdt_clock_tick(const Agent& agent, uint64_t cycle); + // MRAM helpers + bool is_mram_dsleep() const; + void mram_bridge_set_slverr(uint32_t bits); + // UART helpers void uart_set_tx_fd(int fd); void uart_set_rx_fd(int fd); diff --git a/sw-sysemu/pma_er.cpp b/sw-sysemu/pma_er.cpp index 5b29cb424..b57586c90 100644 --- a/sw-sysemu/pma_er.cpp +++ b/sw-sysemu/pma_er.cpp @@ -174,6 +174,12 @@ uint64_t pma_check_data_access(const Hart& cpu, uint64_t vaddr, } if (paddr_is_mram(addr)) { + // MRAM in deep sleep: set slverr sticky bits and fault + if (cpu.chip->memory.is_mram_dsleep()) { + cpu.chip->memory.mram_bridge_set_slverr(0x04 | 0x08); // mram_not_ready | mram_unpowered + throw_access_fault(vaddr, macc); + } + uint16_t mprot = cpu.chip->neigh_esrs[neigh_index(cpu)].mprot; Privilege mode = effective_execution_mode(cpu, macc); @@ -330,6 +336,12 @@ uint64_t pma_check_fetch_access(const Hart& cpu, uint64_t vaddr, } if (paddr_is_mram(addr)) { + // MRAM in deep sleep: all accesses fault + if (cpu.chip->memory.is_mram_dsleep()) { + cpu.chip->memory.mram_bridge_set_slverr(0x04 | 0x08); // mram_not_ready | mram_unpowered + throw_access_fault(vaddr, Mem_Access_Fetch); + } + // MRAM subject to MPROT protection uint16_t mprot = cpu.chip->neigh_esrs[neigh_index(cpu)].mprot; diff --git a/sw-sysemu/tests/erbium/common/boot.S b/sw-sysemu/tests/erbium/common/boot.S index 96490a930..796cb5b3d 100644 --- a/sw-sysemu/tests/erbium/common/boot.S +++ b/sw-sysemu/tests/erbium/common/boot.S @@ -23,6 +23,10 @@ _boot: csrrw x0, mip, x0 csrwi tensor_mask, 0 + /* Wake MRAM from deep sleep (clear mram_dsleep_en in PowerDomainReq) */ + li t0, 0x02000038 + sw zero, 0(t0) + /* Set mtvec to default trap handler (at 0x02009000) */ la t0, default_trap_handler csrw mtvec, t0 diff --git a/sw-sysemu/tests/erbium/src/mram_dsleep_fault.c b/sw-sysemu/tests/erbium/src/mram_dsleep_fault.c new file mode 100644 index 000000000..c224a2226 --- /dev/null +++ b/sw-sysemu/tests/erbium/src/mram_dsleep_fault.c @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +/* +* Test: MRAM access while in deep sleep triggers load access fault +* +* Boot code wakes MRAM by default, so we put it back to sleep +* by writing mram_dsleep_en=1 to PowerDomainReq, then attempt +* a load from MRAM. +* +* Expected: Load access fault (cause=5), test PASSes via trap handler +*/ + +#include "test.h" +#include "trap.h" +#include + +#define POWER_DOMAIN_REQ ((volatile uint32_t *)0x02000038ull) +#define MRAM_DSLEEP_EN (1u << 16) + +int main() { + /* Put MRAM back to deep sleep */ + *POWER_DOMAIN_REQ = MRAM_DSLEEP_EN; + + expect_exception(CAUSE_LOAD_ACCESS_FAULT); + + volatile uint64_t *mram = (volatile uint64_t *)0x40000200ull; + uint64_t val = *mram; + (void)val; + + TEST_FAIL; + return 0; +} diff --git a/sw-sysemu/tests/erbium/src/mram_dsleep_wake.c b/sw-sysemu/tests/erbium/src/mram_dsleep_wake.c new file mode 100644 index 000000000..f2b751707 --- /dev/null +++ b/sw-sysemu/tests/erbium/src/mram_dsleep_wake.c @@ -0,0 +1,49 @@ +/*------------------------------------------------------------------------- +* Copyright (c) 2026 Ainekko, Co. +* SPDX-License-Identifier: Apache-2.0 +*-------------------------------------------------------------------------*/ + +/* +* Test: MRAM accessible after waking from deep sleep +* +* Boot code wakes MRAM. We put it back to sleep, then wake it again +* and verify that reads/writes work and bridge_status_reg shows +* mram_ready=0xF. +* +* Expected: PASS +*/ + +#include "test.h" +#include + +#define POWER_DOMAIN_REQ ((volatile uint32_t *)0x02000038ull) +#define BRIDGE_STATUS_REG ((volatile uint64_t *)0x02001008ull) +#define MRAM_DSLEEP_EN (1u << 16) +#define MRAM_READY_MASK 0xF00ull + +#define TEST_ADDR ((volatile uint64_t *)0x40000200ull) +#define TEST_PATTERN 0xCAFEBABE12345678ull + +int main() { + /* Put MRAM to deep sleep */ + *POWER_DOMAIN_REQ = MRAM_DSLEEP_EN; + + /* Verify mram_ready is 0 while asleep */ + if ((*BRIDGE_STATUS_REG & MRAM_READY_MASK) != 0) + TEST_FAIL; + + /* Wake MRAM */ + *POWER_DOMAIN_REQ = 0; + + /* Verify mram_ready shows all banks ready */ + if ((*BRIDGE_STATUS_REG & MRAM_READY_MASK) != MRAM_READY_MASK) + TEST_FAIL; + + /* Verify MRAM read/write works */ + *TEST_ADDR = TEST_PATTERN; + if (*TEST_ADDR != TEST_PATTERN) + TEST_FAIL; + + TEST_PASS; + return 0; +}