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-03 14:05:38 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-03 14:05:38 -0400
commit32b551d43781dc7b8ac4587ca2ba1554b0cbd5e5 (patch)
treed1cab6bcc35e461610cd5dfcf3f51ba679a576fc /debug/risc-v-gsp-rm-vm/sim/machine.py
parentfa71f1b53b524e5f6c6c1fbff1088d610b9f3236 (diff)
downloadopen-iov-32b551d43781dc7b8ac4587ca2ba1554b0cbd5e5.tar.gz
open-iov-32b551d43781dc7b8ac4587ca2ba1554b0cbd5e5.zip
vgsp: host CUDA bridge + sema release recovery; greeneye simulator wave
- kernel: gsp_cuda_bridge (QMD launch capture, debugfs job/out/done, param+output resolution cached from inline writes), sema drain fixes (never hold/starve a channel), GR ring autopin + counter autoreg, per-page push reads, counter-derive gate; module builds clean - tools/cuda-bridge: CUDA driver-API daemon executing mail/vadd kernels on the physical GPU, CUBB2/CBD2 v2 protocol, layout check + self-tests - tools/guest-diag + tools/cuda-ab: scanner/pin fixes, victory push-only mode - debug/risc-v-gsp-rm-vm: Phase S simulator wave (GSP device model, boot ladder, triage/re-demo harnesses, docs 30-41), GPGPU-sim scaffolding - STATE-CURRENT/docs updates
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/machine.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/machine.py185
1 files changed, 181 insertions, 4 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/machine.py b/debug/risc-v-gsp-rm-vm/sim/machine.py
index 8d77dd8..9908753 100644
--- a/debug/risc-v-gsp-rm-vm/sim/machine.py
+++ b/debug/risc-v-gsp-rm-vm/sim/machine.py
@@ -5,12 +5,20 @@ The execution loop is a monolithic dispatch over the cached decode tuples from
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.
+
+Alongside the instruction count (virtual time, ``insns``) the machine keeps a
+``cycles`` counter advanced by :mod:`sim.cycles`, a table-driven **modeled**
+instruction-class cost. It changes no execution behaviour: with the default
+model enabled, ``insns``, stops and register/memory state are exactly as
+before; ``cycle_model=False`` leaves ``cycles`` at 0. See
+``docs/32-GSP-CYCLE-MODEL.md``.
"""
from __future__ import annotations
import mmap
import struct
+from .cycles import DEFAULT_MODEL
from .decode import (
A_ADD,
A_AND,
@@ -22,7 +30,6 @@ from .decode import (
A_MULHU,
A_OR,
A_REM,
- A_REMU,
A_SLL,
A_SLT,
A_SLTU,
@@ -38,8 +45,6 @@ from .decode import (
I_SLLIW,
I_SLTI,
I_SLTIU,
- I_SRAI,
- I_SRAIW,
I_SRLI,
I_SRLIW,
I_XORI,
@@ -50,7 +55,6 @@ from .decode import (
K_EBREAK,
K_ECALL,
K_FENCE,
- K_ILLEGAL,
K_JAL,
K_JALR,
K_LOAD,
@@ -182,6 +186,12 @@ class Machine:
``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.
+
+ ``cycle_model`` selects the modeled cycle accounting (:mod:`sim.cycles`):
+ ``None`` (default) uses :data:`sim.cycles.DEFAULT_MODEL`, a
+ :class:`~sim.cycles.CycleModel` instance uses that table, and ``False``
+ disables accounting (``cycles`` stays 0). Accounting never changes
+ execution.
"""
def __init__(
@@ -192,14 +202,36 @@ class Machine:
unknown_mmio: str = "fault",
learn_mmio: bool = False,
bias: int = 0,
+ cycle_model=None,
+ devices=None,
+ reset_regs=None,
+ translator=None,
):
if unknown_mmio not in UNKNOWN_MMIO_MODES:
raise ValueError(f"unknown_mmio must be one of {UNKNOWN_MMIO_MODES}, got {unknown_mmio!r}")
+ if cycle_model is False:
+ cycle_model = None
+ elif cycle_model is None:
+ cycle_model = DEFAULT_MODEL
+ self.cycle_model = cycle_model
+ self.cycles = 0
+ if cycle_model is None:
+ self._cycle_base = None
+ self._cycle_op = None
+ self._cycle_mem = 0
+ self._cycle_io = 0
+ else:
+ self._cycle_base = cycle_model.base_costs
+ self._cycle_op = cycle_model.op_costs
+ self._cycle_mem = cycle_model.memory_latency
+ self._cycle_io = cycle_model.mmio_latency
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.devices = devices
+ self.translator = translator # aperture -> RAM seam (sim.devices.dma)
self.unknown_mmio = unknown_mmio
self.learn_mmio = bool(learn_mmio)
self.bias = bias # PC relocation for learned-map firmware addresses
@@ -219,6 +251,34 @@ class Machine:
self._u16p = self._u16.pack_into
self._u32p = self._u32.pack_into
self._u64p = self._u64.pack_into
+ if reset_regs:
+ self.apply_reset_regs(reset_regs)
+
+ # -- reset state (boot shim support) -----------------------------------
+
+ def apply_reset_regs(self, regs: dict) -> None:
+ """Set initial register values (boot shim: a0-a5 bootargs, etc.).
+
+ Accepts integer indices (0-31) or register names (``"a0"``..``"a7"``,
+ ``"ra"``, ``"sp"``, ``"tp"``, ``"s0"``.., ``"t0"``..). x0 is ignored.
+ """
+ names = {"zero": 0, "ra": 1, "sp": 2, "gp": 3, "tp": 4,
+ "t0": 5, "t1": 6, "t2": 7,
+ "s0": 8, "fp": 8, "s1": 9,
+ "a0": 10, "a1": 11, "a2": 12, "a3": 13, "a4": 14, "a5": 15,
+ "a6": 16, "a7": 17,
+ "s2": 18, "s3": 19, "s4": 20, "s5": 21, "s6": 22, "s7": 23,
+ "s8": 24, "s9": 25, "s10": 26, "s11": 27,
+ "t3": 28, "t4": 29, "t5": 30, "t6": 31}
+ for key, value in regs.items():
+ index = key if isinstance(key, int) else names.get(str(key))
+ if index is None:
+ raise ValueError(f"unknown register name {key!r}")
+ self.set_reg(int(index), int(value))
+
+ def insn_count(self) -> int:
+ """Virtual time (instruction count) for modeled clock devices."""
+ return self.insns
# -- image loading -----------------------------------------------------
@@ -262,6 +322,21 @@ class Machine:
out.append(self.uart.read(addr - UART_BASE))
addr += 1
size -= 1
+ elif self.translator is not None and self.translator.translate(addr) is not None:
+ sim = self.translator.translate(addr)
+ off = sim - self.ram_base
+ if off < 0 or off + size > self.ram_size:
+ raise Fault(5, addr)
+ out += self.ram[off : off + size]
+ addr += size
+ size = 0
+ elif self.devices is not None:
+ peeked = self.devices.peek(addr, size)
+ if peeked is None:
+ raise Fault(5, addr)
+ out += peeked
+ addr += size
+ size = 0
else:
raise Fault(5, addr)
return bytes(out)
@@ -280,6 +355,17 @@ class Machine:
elif UART_BASE <= here < UART_BASE + UART_SIZE:
self.uart.write(here - UART_BASE, data[offset])
offset += 1
+ elif self.translator is not None and self.translator.translate(here) is not None:
+ sim = self.translator.translate(here)
+ off = sim - self.ram_base
+ if off < 0 or off + remaining > self.ram_size:
+ raise Fault(7, here)
+ self.ram[off : off + remaining] = data[offset:]
+ offset = len(data)
+ elif self.devices is not None and self.devices.write(
+ here, remaining, int.from_bytes(data[offset:], "little")
+ ):
+ offset = len(data)
else:
raise Fault(7, here)
@@ -308,9 +394,57 @@ class Machine:
self.device_map = DeviceMap(meta={"source": "learn"})
self.device_map.record(addr, size, rw, value, pc=pc, bias=self.bias, source="trace")
+ def _aperture_load(self, addr: int, size: int) -> int | None:
+ """Serve a load from a translated memory aperture (FBGPA/SYSGPA)."""
+ if self.translator is None:
+ return None
+ sim = self.translator.translate(addr)
+ if sim is None:
+ return None
+ off = sim - self.ram_base
+ if off < 0 or off + size > self.ram_size:
+ return None
+ if size == 8:
+ return self._u64.unpack_from(self.ram, off)[0]
+ if size == 4:
+ return self._u32.unpack_from(self.ram, off)[0]
+ if size == 2:
+ return self._u16.unpack_from(self.ram, off)[0]
+ if size == 1:
+ return self.ram[off]
+ return None
+
+ def _aperture_store(self, addr: int, size: int, value: int) -> bool:
+ """Serve a store into a translated memory aperture (FBGPA/SYSGPA)."""
+ if self.translator is None:
+ return False
+ sim = self.translator.translate(addr)
+ if sim is None:
+ return False
+ off = sim - self.ram_base
+ if off < 0 or off + size > self.ram_size:
+ return False
+ if size == 8:
+ self._u64p(self.ram, off, value & 0xFFFFFFFFFFFFFFFF)
+ elif size == 4:
+ self._u32p(self.ram, off, value & 0xFFFFFFFF)
+ elif size == 2:
+ self._u16p(self.ram, off, value & 0xFFFF)
+ else:
+ self.ram[off] = value & 0xFF
+ self.invalidate()
+ return True
+
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)
+ value = self._aperture_load(addr, size)
+ if value is not None:
+ return value
+ if self.devices is not None:
+ handled, value = self.devices.read(addr, size, pc=pc)
+ if handled:
+ return value
handled, value = self._mapped_mmio(addr, size, False)
if handled:
return value
@@ -323,6 +457,10 @@ class Machine:
if UART_BASE <= addr < UART_BASE + UART_SIZE:
self.uart.write(addr - UART_BASE, value)
return
+ if self._aperture_store(addr, size, value):
+ return
+ if self.devices is not None and self.devices.write(addr, size, value, pc=pc):
+ return
handled, _ = self._mapped_mmio(addr, size, True)
if handled:
if self.learn_mmio and self.device_map is not None:
@@ -368,6 +506,13 @@ class Machine:
a = b = 0
traced = trace is not None
active = False
+ # Modeled cycle accounting (sim.cycles): pure table lookups, no timing.
+ cycle_base = self._cycle_base
+ cycle_op = self._cycle_op
+ cycle_mem = self._cycle_mem
+ cycle_io = self._cycle_io
+ counting = cycle_base is not None
+ cycle_sum = 0
while n < budget:
if has_bps and pc in breakpoints:
@@ -394,6 +539,9 @@ class Machine:
cache[pc] = d
kind, sub, rd, r1, r2, imm, nxt = d
n += 1
+ if counting:
+ kc = cycle_base[kind]
+ cycle_sum += cycle_op[sub] if kc < 0 else kc
if traced:
insn_pc = pc
active = trace.begin(insn_pc, self.insns + n)
@@ -434,6 +582,8 @@ class Machine:
else:
if rd:
x[rd] = i16(ram, o)[0]
+ if counting:
+ cycle_sum += cycle_mem
pc = nxt
else:
try:
@@ -447,6 +597,8 @@ class Machine:
x[rd] = v
if active:
trace.mem(insn_pc, ea, size, False, self.insns + n)
+ if counting:
+ cycle_sum += cycle_io
pc = nxt
continue
@@ -473,6 +625,15 @@ class Machine:
u16p(ram, o, value & 0xFFFF)
else:
ram[o] = value & 0xFF
+ # Self-modifying code: drop any cached decode overlapping
+ # the written range (firmware patches code at runtime).
+ p = ea & ~1
+ end = ea + size
+ while p < end:
+ cache.pop(p, None)
+ p += 2
+ if counting:
+ cycle_sum += cycle_mem
pc = nxt
else:
try:
@@ -484,6 +645,8 @@ class Machine:
break
if active:
trace.mem(insn_pc, ea, size, True, self.insns + n)
+ if counting:
+ cycle_sum += cycle_io
pc = nxt
continue
@@ -755,6 +918,8 @@ class Machine:
trace.mem(insn_pc, ea, width, True, self.insns + n)
if rd:
x[rd] = (_sext32(old) & MASK64) if width == 4 else old
+ if counting:
+ cycle_sum += cycle_mem if 0 <= ea - base <= limit - width else cycle_io
pc = nxt
except Fault as f:
reason = R_FAULT
@@ -796,10 +961,17 @@ class Machine:
self.pc = pc
self.insns += n
+ self.cycles += cycle_sum
if single_step and reason == R_BUDGET:
reason = R_STEP
return (reason, pc, a, b)
+ def device_state(self) -> dict | None:
+ """Serialisable state of the attached device registry (or None)."""
+ if self.devices is None or not hasattr(self.devices, "state"):
+ return None
+ return self.devices.state()
+
# -- guest memory helpers (AMO) ----------------------------------------
def _guest_load(self, addr: int, width: int, pc: int = 0) -> int:
@@ -817,6 +989,11 @@ class Machine:
self._u64p(self.ram, off, value)
else:
self._u32p(self.ram, off, value & 0xFFFFFFFF)
+ p = addr & ~1
+ end = addr + width
+ while p < end:
+ self._cache.pop(p, None)
+ p += 2
return
self._mmio_store(addr, width, value, pc)