Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions sw-sysemu/devices/mram_bridge_er.h
Original file line number Diff line number Diff line change
@@ -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 <cstdint>
#include "agent.h"
#include "system.h"
#include "memory/memory_error.h"
#include "memory/memory_region.h"
#include "emu_gio.h"

namespace bemu {

template <unsigned long long Base, size_t N>
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<uint64_t*>(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<const uint64_t*>(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
2 changes: 2 additions & 0 deletions sw-sysemu/devices/sysregs_er.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ void SysregsEr<Base>::reset(ResetCause cause)
sys_interrupt = 0;
reset_cause = static_cast<uint32_t>(cause); // Set reset cause
power_domain_req = 0;
mram_powered = false;
power_domain_ack = 0;
spin_lock = 0;
chip_mode = 0;
Expand Down Expand Up @@ -161,6 +162,7 @@ void SysregsEr<Base>::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:
Expand Down
6 changes: 6 additions & 0 deletions sw-sysemu/devices/sysregs_er.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand All @@ -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;

Expand Down
13 changes: 12 additions & 1 deletion sw-sysemu/memory/erbium/main_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -17,7 +18,7 @@ namespace bemu {
void MainMemory::reset()
{
regions[erbreg_idx].reset(new SysregsEr<region_bases[erbreg_idx]>());
regions[mram_bridge_idx].reset(new DenseRegion<region_bases[mram_bridge_idx], region_sizes[mram_bridge_idx]>());
regions[mram_bridge_idx].reset(new MramBridgeEr<region_bases[mram_bridge_idx], region_sizes[mram_bridge_idx]>());
regions[uart_idx].reset(new ShaktiUart<region_bases[uart_idx], region_sizes[uart_idx]>());
regions[bootrom_idx].reset(new DenseRegion<region_bases[bootrom_idx], region_sizes[bootrom_idx], false>());
regions[sram_idx].reset(new DenseRegion<region_bases[sram_idx], region_sizes[sram_idx]>());
Expand Down Expand Up @@ -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<SysregsEr<region_bases[erbreg_idx]>*>(regions[erbreg_idx].get());
return ptr->is_mram_dsleep();
}

void MainMemory::mram_bridge_set_slverr(uint32_t bits) {
auto ptr = dynamic_cast<MramBridgeEr<region_bases[mram_bridge_idx], region_sizes[mram_bridge_idx]>*>(regions[mram_bridge_idx].get());
ptr->set_slverr(bits);
}

bool MainMemory::is_uart_enabled() const {
auto ptr = dynamic_cast<SysregsEr<region_bases[erbreg_idx]>*>(regions[erbreg_idx].get());
return ptr->is_uart_enabled();
Expand Down
4 changes: 4 additions & 0 deletions sw-sysemu/memory/erbium/main_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions sw-sysemu/pma_er.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions sw-sysemu/tests/erbium/common/boot.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions sw-sysemu/tests/erbium/src/mram_dsleep_fault.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#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;
}
49 changes: 49 additions & 0 deletions sw-sysemu/tests/erbium/src/mram_dsleep_wake.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#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;
}