summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/cycles.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/cycles.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/cycles.py247
1 files changed, 247 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/cycles.py b/debug/risc-v-gsp-rm-vm/sim/cycles.py
new file mode 100644
index 0000000..b841c6c
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/sim/cycles.py
@@ -0,0 +1,247 @@
+"""Modeled RV64IMAC instruction-class cycle accounting for the native core.
+
+**Every number in this module is a modeled default - NOT a measurement.**
+The tables encode deliberately simple, public assumptions about an in-order
+single-issue RISC-V core: one cycle for plain ALU work, a multi-cycle cost for
+the M extension, a fixed modeled latency added when a memory access completes,
+and a larger one for MMIO. They are *not* derived from the GSP core, not from
+silicon, and not from vendor data.
+
+The simulator's fidelity statement is unchanged (docs/06-ACCURACY.md):
+instruction semantics (L0) and memory behaviour (L1) are modelled; L2
+(time/concurrency) is **not**. A cycle count from this module is a
+deterministic accounting artifact under an explicit model - never a timing,
+latency, throughput or silicon claim.
+
+The model is deterministic by construction: cost depends only on the decoded
+instruction class (``sim.decode`` kind/sub) and whether a completed access was
+RAM or MMIO. No host clock, no entropy, no caches, no pipeline hazards.
+"""
+from __future__ import annotations
+
+from .decode import (
+ A_DIV,
+ A_DIVU,
+ A_MUL,
+ A_MULH,
+ A_MULHSU,
+ A_MULHU,
+ A_REM,
+ A_REMU,
+ 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,
+)
+
+#: Model identifier written into session metadata and reports.
+CYCLE_MODEL_NAME = "rv64imac-modeled/1"
+
+#: Fidelity label that must travel with every cycle number from this module.
+CYCLE_FIDELITY = "L2-not-modelled; cycles are modeled defaults, not measured"
+
+#: One-line provenance/label, repeated wherever the model is described.
+NOTE = (
+ "modeled defaults sourced from public in-order RISC-V pipeline assumptions; "
+ "not measured, not a silicon timing claim"
+)
+
+# Instruction classes used for reporting and per-class reasoning.
+CLASS_ALU = "alu"
+CLASS_BRANCH = "branch"
+CLASS_JUMP = "jump"
+CLASS_MUL = "mul"
+CLASS_DIV = "div"
+CLASS_LOAD = "load"
+CLASS_STORE = "store"
+CLASS_AMO = "amo"
+CLASS_CSR = "csr"
+CLASS_TRAP = "trap"
+CLASS_FENCE = "fence"
+CLASS_ILLEGAL = "illegal"
+
+_CLASS_BY_KIND = {
+ K_LUI: CLASS_ALU,
+ K_AUIPC: CLASS_ALU,
+ K_JAL: CLASS_JUMP,
+ K_JALR: CLASS_JUMP,
+ K_BRANCH: CLASS_BRANCH,
+ K_LOAD: CLASS_LOAD,
+ K_STORE: CLASS_STORE,
+ K_OPI: CLASS_ALU,
+ K_OPI32: CLASS_ALU,
+ K_FENCE: CLASS_FENCE,
+ K_CSR: CLASS_CSR,
+ K_ECALL: CLASS_TRAP,
+ K_EBREAK: CLASS_TRAP,
+ K_MRET: CLASS_TRAP,
+ K_AMO: CLASS_AMO,
+ K_ILLEGAL: CLASS_ILLEGAL,
+}
+
+#: Sentinel in ``base_costs``: the cost lives in ``op_costs`` (M extension).
+_SENTINEL = -1
+
+_M_EXT_KINDS = (K_OP, K_OP32)
+
+
+class CycleModel:
+ """Table-driven, deterministic instruction-class cycle costs.
+
+ All keyword values are **modeled defaults** (see the module docstring);
+ they are parameters so a future calibration can replace them without
+ touching the execution loop. ``memory_latency``/``mmio_latency`` are the
+ extra cycles added when a load/store/AMO access completes (RAM vs
+ modelled-MMIO respectively); they are *not* cache models.
+ """
+
+ name = CYCLE_MODEL_NAME
+
+ def __init__(
+ self,
+ *,
+ alu: int = 1, # plain integer ALU / shift / lui / auipc / op-imm
+ branch: int = 1, # conditional branch (taken penalty deliberately not modelled)
+ jump: int = 1, # jal / jalr
+ mul: int = 3, # mul / mulh / mulhsu / mulhu (conservative)
+ div: int = 20, # div / divu / rem / remu (conservative, data-independent)
+ load: int = 1, # load base; + memory_latency/mmio_latency on completion
+ store: int = 1, # store base; + memory_latency/mmio_latency on completion
+ amo: int = 2, # amoswap/lr/sc base; + one memory latency on completion
+ csr: int = 2, # CSR read/modify/write
+ trap: int = 3, # ecall / ebreak / mret
+ fence: int = 1, # fence / fence.i (no ordering model; nominal)
+ illegal: int = 1, # decode failure (execution stops immediately)
+ memory_latency: int = 3, # extra cycles for a completed RAM access
+ mmio_latency: int = 12, # extra cycles for a completed unmodelled-MMIO access
+ ):
+ for label, value in (
+ ("alu", alu),
+ ("branch", branch),
+ ("jump", jump),
+ ("mul", mul),
+ ("div", div),
+ ("load", load),
+ ("store", store),
+ ("amo", amo),
+ ("csr", csr),
+ ("trap", trap),
+ ("fence", fence),
+ ("illegal", illegal),
+ ("memory_latency", memory_latency),
+ ("mmio_latency", mmio_latency),
+ ):
+ if not isinstance(value, int) or isinstance(value, bool) or value < 0:
+ raise ValueError(f"cycle model: {label} must be a non-negative int, got {value!r}")
+ if alu < 1 or branch < 1 or jump < 1:
+ raise ValueError("cycle model: alu/branch/jump must be >= 1 for a clean per-instruction model")
+ self.alu = alu
+ self.branch = branch
+ self.jump = jump
+ self.mul = mul
+ self.div = div
+ self.load = load
+ self.store = store
+ self.amo = amo
+ self.csr = csr
+ self.trap = trap
+ self.fence = fence
+ self.illegal = illegal
+ self.memory_latency = memory_latency
+ self.mmio_latency = mmio_latency
+
+ base = [0] * 18
+ base[K_LUI] = alu
+ base[K_AUIPC] = alu
+ base[K_JAL] = jump
+ base[K_JALR] = jump
+ base[K_BRANCH] = branch
+ base[K_LOAD] = load
+ base[K_STORE] = store
+ base[K_OPI] = alu
+ base[K_OP] = _SENTINEL
+ base[K_OPI32] = alu
+ base[K_OP32] = _SENTINEL
+ base[K_FENCE] = fence
+ base[K_CSR] = csr
+ base[K_ECALL] = trap
+ base[K_EBREAK] = trap
+ base[K_MRET] = trap
+ base[K_AMO] = amo
+ base[K_ILLEGAL] = illegal
+
+ op = [alu] * 18
+ for sub in (A_MUL, A_MULH, A_MULHSU, A_MULHU):
+ op[sub] = mul
+ for sub in (A_DIV, A_DIVU, A_REM, A_REMU):
+ op[sub] = div
+ # Immutable fast-path tables consumed by Machine.run's hot loop.
+ self.base_costs = tuple(base)
+ self.op_costs = tuple(op)
+
+ # -- queries -----------------------------------------------------------
+
+ def instruction_cost(self, kind: int, sub: int = 0) -> int:
+ """Modeled cycles for one decoded instruction (memory latency excluded)."""
+ cost = self.base_costs[kind]
+ if cost == _SENTINEL:
+ return self.op_costs[sub]
+ return cost
+
+ def memory_cost(self, is_mmio: bool = False) -> int:
+ """Modeled extra cycles when a load/store/AMO access completes."""
+ return self.mmio_latency if is_mmio else self.memory_latency
+
+ @staticmethod
+ def classify(kind: int, sub: int = 0) -> str:
+ """Human-readable class for a decoded instruction (reporting only)."""
+ if kind in _M_EXT_KINDS:
+ if sub >= A_DIV:
+ return CLASS_DIV
+ if sub >= A_MUL:
+ return CLASS_MUL
+ return CLASS_ALU
+ return _CLASS_BY_KIND.get(kind, "other")
+
+ def describe(self) -> dict:
+ """The full table, for session metadata and reports."""
+ return {
+ "name": self.name,
+ "note": NOTE,
+ "fidelity": CYCLE_FIDELITY,
+ "classes": {
+ CLASS_ALU: self.alu,
+ CLASS_BRANCH: self.branch,
+ CLASS_JUMP: self.jump,
+ CLASS_MUL: self.mul,
+ CLASS_DIV: self.div,
+ CLASS_LOAD: self.load,
+ CLASS_STORE: self.store,
+ CLASS_AMO: self.amo,
+ CLASS_CSR: self.csr,
+ CLASS_TRAP: self.trap,
+ CLASS_FENCE: self.fence,
+ CLASS_ILLEGAL: self.illegal,
+ },
+ "memory_latency": self.memory_latency,
+ "mmio_latency": self.mmio_latency,
+ }
+
+
+#: Shared default model. Machine() uses it unless told otherwise; it is
+#: immutable in practice, so one instance is safe for every target.
+DEFAULT_MODEL = CycleModel()