diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-03 14:05:38 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-03 14:05:38 -0400 |
| commit | 32b551d43781dc7b8ac4587ca2ba1554b0cbd5e5 (patch) | |
| tree | d1cab6bcc35e461610cd5dfcf3f51ba679a576fc /debug/risc-v-gsp-rm-vm/sim/machine.py | |
| parent | fa71f1b53b524e5f6c6c1fbff1088d610b9f3236 (diff) | |
| download | open-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.py | 185 |
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) |
