diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py')
| -rwxr-xr-x | debug/risc-v-gsp-rm-vm/gpgpu-sim/scripts/config_sanity.py | 278 |
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()) |
