summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py')
-rwxr-xr-xdebug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py278
1 files changed, 278 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py b/debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py
new file mode 100755
index 0000000..052695e
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+"""config_sanity.py -- stdlib-only sanity check for the TU104 scaffold config.
+
+Parses gpgpu-sim/configs/turing-tu104-2080super/gpgpusim.config and asserts
+that the key device knobs and the machine-checkable `# SPEC:` markers are
+present and internally consistent:
+
+ * SM count : n_clusters x n_cores_per_cluster == 48
+ * compute capability : major.minor == 7.5, PTX force max capability == 75
+ * memory type : GDDR6 (burst length 16, QDR freq ratio 4)
+ * memory capacity : SPEC vram_gib == 8 (documentation-only; GPGPU-Sim
+ 4.x has no VRAM capacity knob)
+ * L2 size : sets x bsize x assoc x n_mem x n_sub == 4 MiB
+ * bandwidth : buswidth x freq_ratio x DRAM clock x n_mem == 496 GB/s
+ * clocks : base 1650 MHz, DRAM 3875 MHz (15.5 Gbps / 4)
+
+This is a scaffold consistency test, not a calibration: passing says the file
+internally encodes the published TU104 numbers, NOT that the simulator models
+TU104 accurately. Exit code 0 = all checks pass; 1 = at least one mismatch;
+2 = usage/parse error.
+
+Usage:
+ python3 gpgpu-sim/scripts/config_sanity.py [--config PATH] [--quiet]
+"""
+
+from __future__ import annotations
+
+import argparse
+import re
+import sys
+from pathlib import Path
+
+EXPECTED_SPECS = {
+ "sm_count": "48",
+ "memory_type": "GDDR6",
+ "vram_gib": "8",
+ "bus_width_bits": "256",
+ "bandwidth_gbps": "496",
+ "l2_mib": "4",
+ "rops": "64",
+ "tmus": "192",
+ "base_mhz": "1650",
+ "boost_mhz": "1815",
+ "compute_capability": "7.5",
+}
+
+DEFAULT_CONFIG = (
+ Path(__file__).resolve().parents[1]
+ / "configs"
+ / "turing-tu104-2080super"
+ / "gpgpusim.config"
+)
+ICNT_NAME = "config_turing_islip.icnt"
+
+
+def parse_config(path: Path) -> tuple[dict[str, str], dict[str, str]]:
+ """Return (options, spec_markers) parsed from a gpgpusim.config file."""
+ opts: dict[str, str] = {}
+ specs: dict[str, str] = {}
+ for raw in path.read_text(encoding="utf-8").splitlines():
+ spec = re.match(r"^\s*#\s*SPEC:\s*([A-Za-z0-9_]+)\s*=\s*(\S+)\s*$", raw)
+ if spec:
+ specs[spec.group(1)] = spec.group(2)
+ line = raw.split("#", 1)[0].strip()
+ if not line or not line.startswith("-"):
+ continue
+ parts = line.split(None, 1)
+ opts[parts[0]] = parts[1].strip() if len(parts) > 1 else ""
+ return opts, specs
+
+
+class Checker:
+ def __init__(self, quiet: bool = False) -> None:
+ self.quiet = quiet
+ self.passed = 0
+ self.failures: list[str] = []
+
+ def check(self, name: str, condition: bool, detail: str) -> None:
+ if condition:
+ self.passed += 1
+ if not self.quiet:
+ print(f"PASS: {name}: {detail}")
+ else:
+ self.failures.append(f"{name}: {detail}")
+ print(f"FAIL: {name}: {detail}")
+
+ def note(self, text: str) -> None:
+ if not self.quiet:
+ print(f"NOTE: {text}")
+
+
+def parse_int(opts: dict[str, str], key: str) -> int | None:
+ try:
+ return int(opts[key], 0)
+ except (KeyError, ValueError):
+ return None
+
+
+def parse_l2(opts: dict[str, str]) -> tuple[int, int, int] | None:
+ """Parse '<nsets>:<bsize>:<assoc>' from -gpgpu_cache:dl2 S:... ."""
+ m = re.match(r"^S:(\d+):(\d+):(\d+)", opts.get("-gpgpu_cache:dl2", ""))
+ if not m:
+ return None
+ return int(m.group(1)), int(m.group(2)), int(m.group(3))
+
+
+def run_checks(config_path: Path, quiet: bool = False) -> int:
+ if not config_path.is_file():
+ print(f"config_sanity: ERROR: config not found: {config_path}", file=sys.stderr)
+ return 2
+
+ opts, specs = parse_config(config_path)
+ c = Checker(quiet=quiet)
+
+ # 1. spec markers ------------------------------------------------------
+ for key, expected in EXPECTED_SPECS.items():
+ actual = specs.get(key)
+ c.check(
+ f"SPEC {key}",
+ actual == expected,
+ f"expected {expected}, got {actual!r}",
+ )
+
+ # 2. SMs / compute capability -----------------------------------------
+ clusters = parse_int(opts, "-gpgpu_n_clusters")
+ cores = parse_int(opts, "-gpgpu_n_cores_per_cluster")
+ sm_count = None if clusters is None or cores is None else clusters * cores
+ c.check(
+ "SM count",
+ sm_count == int(EXPECTED_SPECS["sm_count"]),
+ f"n_clusters({clusters}) x n_cores_per_cluster({cores}) = {sm_count},"
+ f" expected {EXPECTED_SPECS['sm_count']}",
+ )
+ cc = f"{opts.get('-gpgpu_compute_capability_major', '?')}." \
+ f"{opts.get('-gpgpu_compute_capability_minor', '?')}"
+ c.check(
+ "compute capability",
+ cc == EXPECTED_SPECS["compute_capability"],
+ f"{cc}, expected {EXPECTED_SPECS['compute_capability']}",
+ )
+ c.check(
+ "PTX force max capability",
+ parse_int(opts, "-gpgpu_ptx_force_max_capability") == 75,
+ f"{opts.get('-gpgpu_ptx_force_max_capability', '(missing)')}, expected 75 (sm_75)",
+ )
+
+ # 3. clocks / GDDR6 / memory geometry ---------------------------------
+ clocks = opts.get("-gpgpu_clock_domains", "").split(":")
+ dram_mhz = None
+ if len(clocks) == 4:
+ try:
+ dram_mhz = float(clocks[3])
+ except ValueError:
+ dram_mhz = None
+ c.check(
+ "DRAM clock",
+ dram_mhz == 3875.0,
+ f"{clocks[3] if len(clocks) == 4 else clocks}, expected 3875.0 MHz (15.5 Gbps / 4)",
+ )
+ c.check(
+ "base core clock",
+ len(clocks) == 4 and clocks[0] == "1650.0",
+ f"{clocks[0] if clocks else '(missing)'}, expected 1650.0 MHz",
+ )
+ c.check(
+ "memory type GDDR6",
+ specs.get("memory_type") == "GDDR6"
+ and parse_int(opts, "-gpgpu_dram_burst_length") == 16
+ and parse_int(opts, "-dram_data_command_freq_ratio") == 4,
+ "burst_length="
+ f"{opts.get('-gpgpu_dram_burst_length', '(missing)')}, "
+ "freq_ratio="
+ f"{opts.get('-dram_data_command_freq_ratio', '(missing)')}"
+ " (GDDR6 expected: 16 / 4)",
+ )
+ c.check(
+ "memory chips per controller",
+ parse_int(opts, "-gpgpu_n_mem_per_ctrlr") == 1,
+ f"{opts.get('-gpgpu_n_mem_per_ctrlr', '(missing)')}, expected 1 (upstream Turing convention)",
+ )
+ c.check(
+ "VRAM capacity marker (documentation-only)",
+ specs.get("vram_gib") == "8",
+ "SPEC vram_gib="
+ f"{specs.get('vram_gib')!r}; GPGPU-Sim 4.x has no VRAM capacity knob,"
+ " so 8 GiB is recorded as a spec marker only",
+ )
+
+ # 4. L2 size consistency ----------------------------------------------
+ n_mem = parse_int(opts, "-gpgpu_n_mem")
+ n_sub = parse_int(opts, "-gpgpu_n_sub_partition_per_mchannel")
+ l2 = parse_l2(opts)
+ l2_mib = None
+ if l2 and n_mem and n_sub:
+ sets, bsize, assoc = l2
+ l2_mib = sets * bsize * assoc * n_mem * n_sub / (1024 * 1024)
+ spec_l2 = float(EXPECTED_SPECS["l2_mib"])
+ c.check(
+ "L2 size",
+ l2_mib == spec_l2,
+ f"dl2({l2}) x n_mem({n_mem}) x n_sub({n_sub}) = {l2_mib} MiB,"
+ f" expected {spec_l2} MiB",
+ )
+
+ # 5. bandwidth consistency --------------------------------------------
+ buswidth = parse_int(opts, "-gpgpu_dram_buswidth")
+ ratio = parse_int(opts, "-dram_data_command_freq_ratio")
+ bw_gbps = None
+ if None not in (buswidth, ratio, dram_mhz, n_mem):
+ bw_gbps = buswidth * ratio * dram_mhz * 1e6 * n_mem / 1e9
+ spec_bw = float(EXPECTED_SPECS["bandwidth_gbps"])
+ c.check(
+ "modeled bandwidth",
+ bw_gbps == spec_bw,
+ f"buswidth({buswidth} B) x ratio({ratio}) x DRAM({dram_mhz} MHz)"
+ f" x n_mem({n_mem}) = {bw_gbps} GB/s, expected {spec_bw} GB/s",
+ )
+ bw_bits_ok = False
+ if n_mem and buswidth:
+ bw_bits_ok = (
+ specs.get("bus_width_bits") == "256"
+ and int(EXPECTED_SPECS["bus_width_bits"]) // (8 * n_mem) == buswidth
+ )
+ c.check(
+ "bus width marker",
+ bw_bits_ok,
+ f"SPEC bus_width_bits={specs.get('bus_width_bits')!r}, modeled"
+ f" buswidth={buswidth} B x n_mem={n_mem} (expected {EXPECTED_SPECS['bus_width_bits']} bits"
+ f" = {buswidth} B x {n_mem} modules)",
+ )
+
+ # 6. informational markers not modeled by GPGPU-Sim 4.x ---------------
+ for key, label in (("rops", "ROPs"), ("tmus", "TMUs"), ("boost_mhz", "boost clock")):
+ c.check(
+ f"SPEC {key} present",
+ specs.get(key) == EXPECTED_SPECS[key],
+ f"{label}: {specs.get(key)!r} (expected {EXPECTED_SPECS[key]});"
+ " recorded for documentation, not a simulator knob",
+ )
+
+ # 7. sibling interconnect file (not used with -network_mode 2) -------
+ icnt = config_path.parent / ICNT_NAME
+ c.check(
+ "interconnect config present",
+ icnt.is_file(),
+ f"{icnt}",
+ )
+
+ total = c.passed + len(c.failures)
+ print(
+ f"config_sanity: {c.passed}/{total} checks passed"
+ + (f", {len(c.failures)} failed" if c.failures else "")
+ )
+ if c.failures:
+ for failure in c.failures:
+ print(f"config_sanity: FAILED -> {failure}", file=sys.stderr)
+ return 1
+ print("config_sanity: OK (scaffold consistency only; no accuracy claim)")
+ return 0
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = argparse.ArgumentParser(
+ description="Sanity-check the GreenEye-GPGPU-Sim TU104 scaffold config."
+ )
+ parser.add_argument(
+ "--config",
+ type=Path,
+ default=DEFAULT_CONFIG,
+ help=f"path to gpgpusim.config (default: {DEFAULT_CONFIG})",
+ )
+ parser.add_argument("--quiet", action="store_true", help="only print failures and summary")
+ args = parser.parse_args(argv)
+ return run_checks(args.config, quiet=args.quiet)
+
+
+if __name__ == "__main__":
+ sys.exit(main())