summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/machine.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
commit002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch)
tree6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/sim/machine.py
parent2f249817df327f5dcccf0464ad517fa60d6071d3 (diff)
downloadopen-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz
open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip
debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/machine.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/machine.py825
1 files changed, 825 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/machine.py b/debug/risc-v-gsp-rm-vm/sim/machine.py
new file mode 100644
index 0000000..8d77dd8
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/sim/machine.py
@@ -0,0 +1,825 @@
+"""Deterministic RV64IMAC machine: RAM, NS16550A UART, debugger-visible state.
+
+The execution loop is a monolithic dispatch over the cached decode tuples from
+``sim.decode``. Everything guest-visible is a pure function of the loaded
+image and the instruction budget handed to ``run``: no host clock, no threads,
+no entropy. RAM is an anonymous ``mmap`` (lazily paged, so a 256 MiB region is
+cheap); all other addresses resolve to the UART or fault.
+"""
+from __future__ import annotations
+
+import mmap
+import struct
+
+from .decode import (
+ A_ADD,
+ A_AND,
+ A_DIV,
+ A_DIVU,
+ A_MUL,
+ A_MULH,
+ A_MULHSU,
+ A_MULHU,
+ A_OR,
+ A_REM,
+ A_REMU,
+ A_SLL,
+ A_SLT,
+ A_SLTU,
+ A_SRA,
+ A_SRL,
+ A_SUB,
+ A_XOR,
+ I_ADDI,
+ I_ADDIW,
+ I_ANDI,
+ I_ORI,
+ I_SLLI,
+ I_SLLIW,
+ I_SLTI,
+ I_SLTIU,
+ I_SRAI,
+ I_SRAIW,
+ I_SRLI,
+ I_SRLIW,
+ I_XORI,
+ K_AMO,
+ K_AUIPC,
+ K_BRANCH,
+ K_CSR,
+ K_EBREAK,
+ K_ECALL,
+ K_FENCE,
+ K_ILLEGAL,
+ K_JAL,
+ K_JALR,
+ K_LOAD,
+ K_LUI,
+ K_MRET,
+ K_OP,
+ K_OP32,
+ K_OPI,
+ K_OPI32,
+ K_STORE,
+ LOAD_SIZE,
+ MASK64,
+ STORE_SIZE,
+ decode16,
+ decode32,
+)
+
+# Stop reasons returned by Machine.run().
+R_BUDGET = 0
+R_BP = 1
+R_WATCH = 2
+R_EBREAK = 3
+R_FAULT = 4
+R_STEP = 5
+R_ECALL = 6
+
+#: Accepted ``unknown_mmio`` policies for accesses outside RAM/UART/the map.
+UNKNOWN_MMIO_MODES = ("fault", "lenient")
+
+UART_BASE = 0x10000000
+UART_SIZE = 0x100
+
+# RV64I M A C advertised in misa (MXL=2, I=bit8, M=bit12, A=bit0, C=bit2).
+MISA_RV64IMAC = 0x8000000000001105
+
+
+class Fault(Exception):
+ """Guest-visible trap: unmapped access or illegal instruction."""
+
+ def __init__(self, cause: int, tval: int = 0):
+ super().__init__(f"cause={cause:#x} tval={tval:#x}")
+ self.cause = cause
+ self.tval = tval
+
+
+class Uart16550:
+ """Behavioural NS16550A: TX is captured, LSR always reports THR empty."""
+
+ def __init__(self):
+ self.tx = bytearray()
+ self.ier = 0
+ self.lcr = 0
+ self.mcr = 0
+ self.scr = 0
+ self.dll = 0
+ self.dlm = 0
+
+ def read(self, off: int, size: int = 1) -> int:
+ if off == 0:
+ return self.dll # RBR (no RX modelled) / DLL when DLAB=1
+ if off == 1:
+ return self.ier
+ if off == 2:
+ return 0x01 # IIR: no pending interrupt
+ if off == 3:
+ return self.lcr
+ if off == 4:
+ return self.mcr
+ if off == 5:
+ return 0x60 # LSR: THRE | TEMT
+ if off == 6:
+ return 0xB0 # MSR: CTS|DSR|DCD
+ if off == 7:
+ return self.scr
+ return 0
+
+ def write(self, off: int, value: int, size: int = 1) -> None:
+ value &= 0xFF
+ if off == 0:
+ if self.lcr & 0x80:
+ self.dll = value
+ else:
+ self.tx.append(value)
+ elif off == 1:
+ self.ier = value
+ elif off == 3:
+ self.lcr = value
+ elif off == 4:
+ self.mcr = value
+ elif off == 7:
+ self.scr = value
+ # FCR (2) and reserved offsets are write-only/no-ops.
+
+ def text(self) -> str:
+ return self.tx.decode("utf-8", "replace")
+
+
+def _signed64(value: int) -> int:
+ return value - 0x10000000000000000 if value & 0x8000000000000000 else value
+
+
+def _signed32(value: int) -> int:
+ value &= 0xFFFFFFFF
+ return value - 0x100000000 if value & 0x80000000 else value
+
+
+def _sext32(value: int) -> int:
+ value &= 0xFFFFFFFF
+ return (value ^ 0x80000000) - 0x80000000
+
+
+def _watch_match(watchers, ea: int, size: int, rw_bit: int):
+ end = ea + size
+ for wa, we, wk, mask in watchers:
+ if (mask & rw_bit) and ea < we and wa < end:
+ return wk
+ return None
+
+
+class Machine:
+ """One deterministic RV64IMAC hart plus RAM and UART.
+
+ ``device_map`` is a :class:`greeneye.device_map.DeviceMap` (or any object
+ with the same ``lookup``/``record`` surface) describing modelled MMIO.
+ ``unknown_mmio`` selects what happens to an access that is neither RAM,
+ UART nor in the map: ``"fault"`` (default, M1 parity) stops as before;
+ ``"lenient"`` returns the mapped behaviour or zero, records the access in
+ :attr:`mmio_log`, counts it in :attr:`mmio_unknown`, and, with
+ ``learn_mmio=True``, writes the observation back into the map. Nothing is
+ logged or counted when lenient mode is off, and debugger ``read_mem``/
+ ``write_mem`` stay strict.
+ """
+
+ def __init__(
+ self,
+ memory_mb: int = 256,
+ ram_base: int = 0x80000000,
+ device_map=None,
+ unknown_mmio: str = "fault",
+ learn_mmio: bool = False,
+ bias: int = 0,
+ ):
+ if unknown_mmio not in UNKNOWN_MMIO_MODES:
+ raise ValueError(f"unknown_mmio must be one of {UNKNOWN_MMIO_MODES}, got {unknown_mmio!r}")
+ self.ram_base = ram_base
+ self.ram_size = memory_mb * 1024 * 1024
+ self.ram = mmap.mmap(-1, self.ram_size)
+ self.uart = Uart16550()
+ self.device_map = device_map
+ self.unknown_mmio = unknown_mmio
+ self.learn_mmio = bool(learn_mmio)
+ self.bias = bias # PC relocation for learned-map firmware addresses
+ self.mmio_log: list[dict] = []
+ self.mmio_unknown = 0
+ self.x = [0] * 32
+ self.pc = ram_base
+ self.insns = 0
+ self.csrs: dict[int, int] = {0x301: MISA_RV64IMAC}
+ self._cache: dict[int, tuple] = {}
+ self._u16 = struct.Struct("<H")
+ self._u32 = struct.Struct("<I")
+ self._i8 = struct.Struct("<b")
+ self._i16 = struct.Struct("<h")
+ self._i32 = struct.Struct("<i")
+ self._u64 = struct.Struct("<Q")
+ self._u16p = self._u16.pack_into
+ self._u32p = self._u32.pack_into
+ self._u64p = self._u64.pack_into
+
+ # -- image loading -----------------------------------------------------
+
+ def load_segments(self, plan, entry: int) -> None:
+ """Place ``(addr, bytes)`` PT_LOAD results and set the PC."""
+ for addr, data in plan:
+ offset = addr - self.ram_base
+ if offset < 0 or offset + len(data) > self.ram_size:
+ raise Fault(1, addr)
+ self.ram[offset : offset + len(data)] = data
+ self.pc = entry & MASK64
+ self.invalidate()
+
+ def invalidate(self) -> None:
+ """Drop the decode cache after debugger-visible code changes."""
+ self._cache.clear()
+
+ # -- register access (debugger surface) --------------------------------
+
+ def reg(self, index: int) -> int:
+ if index == 0:
+ return 0
+ return self.x[index]
+
+ def set_reg(self, index: int, value: int) -> None:
+ if index != 0:
+ self.x[index] = value & MASK64
+
+ # -- raw memory access (debugger surface) ------------------------------
+
+ def read_mem(self, addr: int, size: int) -> bytes:
+ out = bytearray()
+ while size > 0:
+ if self.ram_base <= addr < self.ram_base + self.ram_size:
+ take = min(size, self.ram_base + self.ram_size - addr)
+ off = addr - self.ram_base
+ out += self.ram[off : off + take]
+ addr += take
+ size -= take
+ elif UART_BASE <= addr < UART_BASE + UART_SIZE:
+ out.append(self.uart.read(addr - UART_BASE))
+ addr += 1
+ size -= 1
+ else:
+ raise Fault(5, addr)
+ return bytes(out)
+
+ def write_mem(self, addr: int, data: bytes) -> None:
+ self.invalidate()
+ offset = 0
+ while offset < len(data):
+ here = addr + offset
+ remaining = len(data) - offset
+ if self.ram_base <= here < self.ram_base + self.ram_size:
+ take = min(remaining, self.ram_base + self.ram_size - here)
+ ro = here - self.ram_base
+ self.ram[ro : ro + take] = data[offset : offset + take]
+ offset += take
+ elif UART_BASE <= here < UART_BASE + UART_SIZE:
+ self.uart.write(here - UART_BASE, data[offset])
+ offset += 1
+ else:
+ raise Fault(7, here)
+
+ # -- MMIO (guest side) -------------------------------------------------
+
+ def _mapped_mmio(self, addr: int, size: int, write: bool) -> tuple[bool, int]:
+ """Consult the device map; ``(handled, value)`` for a modelled access."""
+ dm = self.device_map
+ if dm is None:
+ return False, 0
+ spec = dm.lookup(addr, size, write)
+ if spec == "unimplemented":
+ return False, 0
+ if spec.startswith("value:"):
+ return True, int(spec[6:], 16)
+ return True, 0 # "zero": mapped but no read value observed
+
+ def _log_unknown(self, pc: int, addr: int, size: int, rw: str, value: int = 0) -> None:
+ """Lenient fallback: log, count, and optionally learn one access."""
+ self.mmio_unknown += 1
+ self.mmio_log.append({"pc": pc, "addr": addr, "size": size, "rw": rw})
+ if self.learn_mmio:
+ if self.device_map is None:
+ from greeneye.device_map import DeviceMap # lazy: sim stays usable alone
+
+ self.device_map = DeviceMap(meta={"source": "learn"})
+ self.device_map.record(addr, size, rw, value, pc=pc, bias=self.bias, source="trace")
+
+ def _mmio_load(self, addr: int, size: int, pc: int = 0) -> int:
+ if UART_BASE <= addr < UART_BASE + UART_SIZE:
+ return self.uart.read(addr - UART_BASE)
+ handled, value = self._mapped_mmio(addr, size, False)
+ if handled:
+ return value
+ if self.unknown_mmio == "lenient":
+ self._log_unknown(pc, addr, size, "r")
+ return 0
+ raise Fault(5, addr)
+
+ def _mmio_store(self, addr: int, size: int, value: int, pc: int = 0) -> None:
+ if UART_BASE <= addr < UART_BASE + UART_SIZE:
+ self.uart.write(addr - UART_BASE, value)
+ return
+ handled, _ = self._mapped_mmio(addr, size, True)
+ if handled:
+ if self.learn_mmio and self.device_map is not None:
+ self.device_map.record(addr, size, "w", value, pc=pc, bias=self.bias, source="trace")
+ return
+ if self.unknown_mmio == "lenient":
+ self._log_unknown(pc, addr, size, "w", value)
+ return
+ raise Fault(7, addr)
+
+ # -- execution ---------------------------------------------------------
+
+ def step(self, watchers=(), trace=None):
+ return self.run(1, breakpoints=(), watchers=watchers, single_step=True, trace=trace)
+
+ def run(self, budget: int, breakpoints=(), watchers=(), single_step: bool = False,
+ trace=None):
+ """Execute up to ``budget`` instructions.
+
+ Returns ``(reason, pc, a, b)``; ``a``/``b`` carry the watched address
+ and kind (R_WATCH) or the trap cause and tval (R_FAULT). When
+ ``trace`` is a :class:`sim.trace.Trace`, every executed instruction
+ and every completed memory access in its address range is recorded.
+ """
+ x = self.x
+ ram = self.ram
+ base = self.ram_base
+ limit = self.ram_size
+ cache = self._cache
+ i8 = self._i8.unpack_from
+ i16 = self._i16.unpack_from
+ i32 = self._i32.unpack_from
+ u16 = self._u16.unpack_from
+ u32 = self._u32.unpack_from
+ u64 = self._u64.unpack_from
+ u16p = self._u16p
+ u32p = self._u32p
+ u64p = self._u64p
+ has_bps = bool(breakpoints)
+ pc = self.pc
+ n = 0
+ reason = R_BUDGET
+ a = b = 0
+ traced = trace is not None
+ active = False
+
+ while n < budget:
+ if has_bps and pc in breakpoints:
+ reason = R_BP
+ a = pc
+ break
+
+ off = pc - base
+ if off < 0 or off + 4 > limit:
+ reason = R_FAULT
+ a = 1
+ b = pc
+ break
+ hw = ram[off] | (ram[off + 1] << 8)
+ if hw & 3 == 3:
+ d = cache.get(pc)
+ if d is None:
+ d = decode32(pc, hw | (ram[off + 2] << 16) | (ram[off + 3] << 24))
+ cache[pc] = d
+ else:
+ d = cache.get(pc)
+ if d is None:
+ d = decode16(pc, hw)
+ cache[pc] = d
+ kind, sub, rd, r1, r2, imm, nxt = d
+ n += 1
+ if traced:
+ insn_pc = pc
+ active = trace.begin(insn_pc, self.insns + n)
+
+ # -- loads / stores (most frequent in firmware hot loops) ------
+ if kind == K_LOAD:
+ ea = (x[r1] + imm) & MASK64
+ size = LOAD_SIZE[sub]
+ if watchers:
+ hit = _watch_match(watchers, ea, size, 2)
+ if hit is not None:
+ reason = R_WATCH
+ a = ea
+ b = hit
+ break
+ o = ea - base
+ if 0 <= o <= limit - size:
+ if active:
+ trace.mem(insn_pc, ea, size, False, self.insns + n)
+ if sub == 3:
+ if rd:
+ x[rd] = u64(ram, o)[0]
+ elif sub == 2:
+ if rd:
+ x[rd] = i32(ram, o)[0]
+ elif sub == 4:
+ if rd:
+ x[rd] = ram[o]
+ elif sub == 0:
+ if rd:
+ x[rd] = i8(ram, o)[0]
+ elif sub == 5:
+ if rd:
+ x[rd] = u16(ram, o)[0]
+ elif sub == 6:
+ if rd:
+ x[rd] = u32(ram, o)[0]
+ else:
+ if rd:
+ x[rd] = i16(ram, o)[0]
+ pc = nxt
+ else:
+ try:
+ v = self._mmio_load(ea, size, pc)
+ except Fault as f:
+ reason = R_FAULT
+ a = f.cause
+ b = f.tval
+ break
+ if rd:
+ x[rd] = v
+ if active:
+ trace.mem(insn_pc, ea, size, False, self.insns + n)
+ pc = nxt
+ continue
+
+ if kind == K_STORE:
+ ea = (x[r1] + imm) & MASK64
+ size = STORE_SIZE[sub]
+ if watchers:
+ hit = _watch_match(watchers, ea, size, 1)
+ if hit is not None:
+ reason = R_WATCH
+ a = ea
+ b = hit
+ break
+ o = ea - base
+ value = x[r2]
+ if 0 <= o <= limit - size:
+ if active:
+ trace.mem(insn_pc, ea, size, True, self.insns + n)
+ if sub == 3:
+ u64p(ram, o, value)
+ elif sub == 2:
+ u32p(ram, o, value & 0xFFFFFFFF)
+ elif sub == 1:
+ u16p(ram, o, value & 0xFFFF)
+ else:
+ ram[o] = value & 0xFF
+ pc = nxt
+ else:
+ try:
+ self._mmio_store(ea, size, value, pc)
+ except Fault as f:
+ reason = R_FAULT
+ a = f.cause
+ b = f.tval
+ break
+ if active:
+ trace.mem(insn_pc, ea, size, True, self.insns + n)
+ pc = nxt
+ continue
+
+ # -- control flow ----------------------------------------------
+ if kind == K_BRANCH:
+ va = x[r1]
+ vb = x[r2]
+ if sub == 0:
+ take = va == vb
+ elif sub == 1:
+ take = va != vb
+ elif sub == 4:
+ take = _signed64(va) < _signed64(vb)
+ elif sub == 5:
+ take = _signed64(va) >= _signed64(vb)
+ elif sub == 6:
+ take = va < vb
+ else:
+ take = va >= vb
+ pc = imm if take else nxt
+ elif kind == K_JAL:
+ if rd:
+ x[rd] = nxt
+ pc = imm
+ elif kind == K_JALR:
+ target = (x[r1] + imm) & MASK64
+ target &= ~1
+ if rd:
+ x[rd] = nxt
+ pc = target
+ elif kind == K_AUIPC:
+ if rd:
+ x[rd] = (pc + imm) & MASK64
+ pc = nxt
+ elif kind == K_LUI:
+ if rd:
+ x[rd] = imm
+ pc = nxt
+
+ # -- integer ALU -----------------------------------------------
+ elif kind == K_OPI:
+ va = x[r1] if r1 else 0
+ if sub == I_ADDI:
+ rv = (va + imm) & MASK64
+ elif sub == I_SLLI:
+ rv = (va << (imm & 63)) & MASK64
+ elif sub == I_SLTI:
+ rv = 1 if _signed64(va) < imm else 0
+ elif sub == I_SLTIU:
+ rv = 1 if va < (imm & MASK64) else 0
+ elif sub == I_XORI:
+ rv = va ^ (imm & MASK64)
+ elif sub == I_ORI:
+ rv = va | (imm & MASK64)
+ elif sub == I_ANDI:
+ rv = va & (imm & MASK64)
+ elif sub == I_SRLI:
+ rv = va >> (imm & 63)
+ else: # I_SRAI
+ rv = (_signed64(va) >> (imm & 63)) & MASK64
+ if rd:
+ x[rd] = rv
+ pc = nxt
+
+ elif kind == K_OP:
+ va = x[r1]
+ vb = x[r2]
+ if sub == A_ADD:
+ rv = (va + vb) & MASK64
+ elif sub == A_SLL:
+ rv = (va << (vb & 63)) & MASK64
+ elif sub == A_SLT:
+ rv = 1 if _signed64(va) < _signed64(vb) else 0
+ elif sub == A_SLTU:
+ rv = 1 if va < vb else 0
+ elif sub == A_XOR:
+ rv = va ^ vb
+ elif sub == A_SRL:
+ rv = va >> (vb & 63)
+ elif sub == A_OR:
+ rv = va | vb
+ elif sub == A_AND:
+ rv = va & vb
+ elif sub == A_SUB:
+ rv = (va - vb) & MASK64
+ elif sub == A_SRA:
+ rv = (_signed64(va) >> (vb & 63)) & MASK64
+ elif sub == A_MUL:
+ rv = (va * vb) & MASK64
+ elif sub == A_MULH:
+ rv = ((_signed64(va) * _signed64(vb)) >> 64) & MASK64
+ elif sub == A_MULHSU:
+ rv = ((_signed64(va) * vb) >> 64) & MASK64
+ elif sub == A_MULHU:
+ rv = ((va * vb) >> 64) & MASK64
+ elif sub == A_DIV:
+ sa, sb = _signed64(va), _signed64(vb)
+ if sb == 0:
+ rv = MASK64
+ elif sa == -(1 << 63) and sb == -1:
+ rv = 1 << 63
+ else:
+ rv = (abs(sa) // abs(sb)) & MASK64
+ if (sa < 0) != (sb < 0):
+ rv = (-rv) & MASK64
+ elif sub == A_DIVU:
+ rv = MASK64 if vb == 0 else va // vb
+ elif sub == A_REM:
+ sa, sb = _signed64(va), _signed64(vb)
+ if sb == 0 or (sa == -(1 << 63) and sb == -1):
+ rv = 0
+ else:
+ rv = (abs(sa) % abs(sb)) & MASK64
+ if sa < 0:
+ rv = (-rv) & MASK64
+ else: # A_REMU
+ rv = va if vb == 0 else va % vb
+ if rd:
+ x[rd] = rv
+ pc = nxt
+
+ elif kind == K_OPI32:
+ va = x[r1] if r1 else 0
+ if sub == I_ADDIW:
+ rv = (va + imm) & 0xFFFFFFFF
+ elif sub == I_SLLIW:
+ rv = (va << (imm & 31)) & 0xFFFFFFFF
+ elif sub == I_SRLIW:
+ rv = (va & 0xFFFFFFFF) >> (imm & 31)
+ else: # I_SRAIW
+ rv = (_signed32(va) >> (imm & 31)) & 0xFFFFFFFF
+ if rd:
+ x[rd] = _sext32(rv) & MASK64
+ pc = nxt
+
+ elif kind == K_OP32:
+ va = x[r1]
+ vb = x[r2]
+ if sub == A_ADD:
+ rv = (va + vb) & 0xFFFFFFFF
+ elif sub == A_SUB:
+ rv = (va - vb) & 0xFFFFFFFF
+ elif sub == A_SLL:
+ rv = (va << (vb & 31)) & 0xFFFFFFFF
+ elif sub == A_SRL:
+ rv = (va & 0xFFFFFFFF) >> (vb & 31)
+ elif sub == A_SRA:
+ rv = (_signed32(va) >> (vb & 31)) & 0xFFFFFFFF
+ elif sub == A_MUL:
+ rv = (va * vb) & 0xFFFFFFFF
+ elif sub == A_DIV:
+ sa, sb = _signed32(va), _signed32(vb)
+ if sb == 0:
+ rv = 0xFFFFFFFF
+ elif sa == -(1 << 31) and sb == -1:
+ rv = 1 << 31
+ else:
+ rv = (abs(sa) // abs(sb)) & 0xFFFFFFFF
+ if (sa < 0) != (sb < 0):
+ rv = (-rv) & 0xFFFFFFFF
+ elif sub == A_DIVU:
+ b32 = vb & 0xFFFFFFFF
+ rv = 0xFFFFFFFF if b32 == 0 else (va & 0xFFFFFFFF) // b32
+ elif sub == A_REM:
+ sa, sb = _signed32(va), _signed32(vb)
+ if sb == 0 or (sa == -(1 << 31) and sb == -1):
+ rv = 0
+ else:
+ rv = (abs(sa) % abs(sb)) & 0xFFFFFFFF
+ if sa < 0:
+ rv = (-rv) & 0xFFFFFFFF
+ else: # A_REMU
+ b32 = vb & 0xFFFFFFFF
+ rv = (va & 0xFFFFFFFF) if b32 == 0 else (va & 0xFFFFFFFF) % b32
+ if rd:
+ x[rd] = _sext32(rv) & MASK64
+ pc = nxt
+
+ # -- system / atomics / traps ----------------------------------
+ elif kind == K_FENCE:
+ pc = nxt
+
+ elif kind == K_CSR:
+ csr = imm
+ old = self.csrs.get(csr, 0)
+ if sub in (5, 6, 7):
+ src = r1 # zimm
+ if sub == 5:
+ if rd:
+ x[rd] = old
+ self.csrs[csr] = src
+ else:
+ if rd:
+ x[rd] = old
+ if src:
+ self.csrs[csr] = (old | src) if sub == 6 else (old & ~src)
+ else:
+ src = x[r1]
+ if sub == 1:
+ if rd:
+ x[rd] = old
+ self.csrs[csr] = src & MASK64
+ elif sub == 2:
+ if rd:
+ x[rd] = old
+ if r1:
+ self.csrs[csr] = (old | src) & MASK64
+ elif sub == 3:
+ if rd:
+ x[rd] = old
+ if r1:
+ self.csrs[csr] = (old & ~src) & MASK64
+ else:
+ reason = R_FAULT
+ a = 2
+ b = csr
+ break
+ pc = nxt
+
+ elif kind == K_AMO:
+ width = 8 if sub & 0x40 else 4
+ op5 = sub & 0x3F
+ ea = x[r1] & MASK64
+ if watchers:
+ hit = _watch_match(watchers, ea, width, 1)
+ if hit is not None:
+ reason = R_WATCH
+ a = ea
+ b = hit
+ break
+ try:
+ old = self._guest_load(ea, width, pc)
+ if active:
+ trace.mem(insn_pc, ea, width, False, self.insns + n)
+ if op5 == 0x02: # lr
+ if rd:
+ x[rd] = old
+ elif op5 == 0x03: # sc
+ self._guest_store(ea, width, x[r2], pc)
+ if active:
+ trace.mem(insn_pc, ea, width, True, self.insns + n)
+ if rd:
+ x[rd] = 0
+ else:
+ rhs = x[r2]
+ mask = 0xFFFFFFFF if width == 4 else MASK64
+ ov = old & mask
+ rv = rhs & mask
+ if op5 == 0x00:
+ new = (ov + rv) & mask
+ elif op5 == 0x01:
+ new = rv
+ elif op5 == 0x04:
+ new = ov ^ rv
+ elif op5 == 0x08:
+ new = ov | rv
+ elif op5 == 0x0C:
+ new = ov & rv
+ elif op5 == 0x10:
+ new = ov if _signed64(ov) < _signed64(rv) else rv
+ elif op5 == 0x14:
+ new = ov if _signed64(ov) > _signed64(rv) else rv
+ elif op5 == 0x18:
+ new = ov if ov < rv else rv
+ else: # 0x1C
+ new = ov if ov > rv else rv
+ self._guest_store(ea, width, new, pc)
+ if active:
+ trace.mem(insn_pc, ea, width, True, self.insns + n)
+ if rd:
+ x[rd] = (_sext32(old) & MASK64) if width == 4 else old
+ pc = nxt
+ except Fault as f:
+ reason = R_FAULT
+ a = f.cause
+ b = f.tval
+ break
+
+ elif kind == K_EBREAK:
+ if single_step:
+ pc = nxt
+ else:
+ reason = R_EBREAK
+ a = pc
+ break
+
+ elif kind == K_ECALL:
+ if single_step:
+ pc = nxt
+ else:
+ reason = R_ECALL
+ a = pc
+ break
+
+ elif kind == K_MRET:
+ target = self.csrs.get(0x341, 0) & MASK64
+ if target:
+ pc = target
+ else:
+ reason = R_FAULT
+ a = 2
+ b = 0
+ break
+
+ else: # K_ILLEGAL
+ reason = R_FAULT
+ a = 2
+ b = 0
+ break
+
+ self.pc = pc
+ self.insns += n
+ if single_step and reason == R_BUDGET:
+ reason = R_STEP
+ return (reason, pc, a, b)
+
+ # -- guest memory helpers (AMO) ----------------------------------------
+
+ def _guest_load(self, addr: int, width: int, pc: int = 0) -> int:
+ off = addr - self.ram_base
+ if 0 <= off <= self.ram_size - width:
+ if width == 8:
+ return self._u64.unpack_from(self.ram, off)[0]
+ return self._u32.unpack_from(self.ram, off)[0]
+ return self._mmio_load(addr, width, pc)
+
+ def _guest_store(self, addr: int, width: int, value: int, pc: int = 0) -> None:
+ off = addr - self.ram_base
+ if 0 <= off <= self.ram_size - width:
+ if width == 8:
+ self._u64p(self.ram, off, value)
+ else:
+ self._u32p(self.ram, off, value & 0xFFFFFFFF)
+ return
+ self._mmio_store(addr, width, value, pc)
+
+
+#: Task-facing alias: the native simulator machine is :class:`Machine`.
+SimMachine = Machine