Files
kernbench2/tests/test_routing.py
ywkang 687c98086d ADR housekeeping: category prefixes, lifecycle folders, retroactive 0034-0037
Filename + lifecycle:
- ADR rename to ADR-NNNN-<cat>-title.md with 8 3-letter category prefixes
  (dev / mem / lat / prog / algo / par / api / ver). Numbers stay immutable.
- ADR Lifecycle split into 3 folders, documented in CLAUDE.md Part 2:
  docs/adr/ (Accepted), docs/adr-proposed/ (Proposed/Stub/Draft),
  docs/adr-history/ (Superseded/Merged). Status field gains "Draft" for
  retroactive docs pending verification.

Merges (one ADR per topic, no change-history annotations):
- ADR-0017 absorbs ADR-0019 (Cube NOC + per-PE HBM connectivity, 10 D-items)
- ADR-0014 absorbs ADR-0021 (PE pipeline execution model, 8 D-items incl.
  TileToken self-routing and multi-op composite epilogue scope)
- ADR-0023 absorbs docs/ipcq-dma-codesign-hw.md as new "HW Realization
  Notes (Informative)" section (D16-D23 + Open HW Questions). codesign-hw.md
  deleted; ADR-0019/0021 moved to adr-history with one-line stub status

Retroactive documentation (G4 closures, code-verified):
- ADR-0037 forwarding component (TransitComponent: first-flit overhead,
  serial worker, path-based routing, single impl/multiple names)
- ADR-0036 IO_CPU component (target_start_ns global barrier stamping,
  per-cube fan-out, response aggregation)
- ADR-0035 M_CPU & M_CPU.DMA component (3 fan-out paths, DMA Resources,
  target_start_ns passthrough)
- ADR-0034 HBM controller internal design (per-PC state, address-based
  selection, flit-aware per-flit commit, async finalize, command-only
  fallback path)

Content updates:
- ADR-0010 expanded to full CLI surface (run/probe/web), retitled
  "Command Line Interface and Execution Semantics"
- ADR-0007 D2 rewritten to current state; ADR-0015 supersession notes pruned
- ADR-0005 wrapped in Decision header with D1-D5; ADR-0022 metadata
  block replaced with standard Status header
- ADR-0024 trimmed to rank=SIP launcher essentials (D1-D4);
  ADR-0027 cleaned of supersession history
- ADR-0033 D6 cleanup: address-based PC selection moved out of future-work
  (now documented in ADR-0034 D3); related D1/D3 wording realigned
- Cross-references back-filled in 5 ADRs (G3 gaps closed)

Onboarding docs split:
- docs/onboarding/ created
- moved: hw-architecture-overview.md, latency-model.md, di-presentation.md,
  ccl-author-guide{,.en}.md
- references updated in README, ADR-0023{,.en}, src/kernbench/ccl/__init__.py

Source / test / yaml: ADR-NNNN cross-references in docstrings and YAML
comments updated after the merges (ADR-0021->0014 D6, ADR-0019->0017 D8).
No behavior change.

Tooling:
- tools/verify_adr_lang_pairs.py + tests/test_verify_adr_lang_pairs.py
  (ADR EN/KO pair invariant checker)
- .claude/commands/report.md tracked (/report slash command)
- .gitignore: allow .claude/commands/*.md while keeping settings files ignored

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 01:15:55 -07:00

218 lines
8.0 KiB
Python

import pytest
from pathlib import Path
from kernbench.policy.address.phyaddr import PhysAddr, UnitType
from kernbench.policy.routing.router import AddressResolver, PathRouter, RoutingError
from kernbench.topology.builder import load_topology
TOPOLOGY_PATH = Path(__file__).parent.parent / "topology.yaml"
def _graph():
return load_topology(TOPOLOGY_PATH)
# ── AddressResolver ──────────────────────────────────────────────────
def test_resolve_hbm_addr():
"""HBM address -> sip{S}.cube{C}.hbm_ctrl.pe{X} (per-PE controller, ADR-0017 D9)."""
g = _graph()
resolver = AddressResolver(g)
# offset 0x1000 falls inside PE0's slice (slice_size = 6 GB)
pa = PhysAddr.hbm_addr(sip_id=0, die_id=3, hbm_offset=0x1000)
assert resolver.resolve(pa) == "sip0.cube3.hbm_ctrl.pe0"
def test_resolve_hbm_addr_high_offset():
"""HBM offset that lands in PE4's slice must resolve to hbm_ctrl.pe4."""
g = _graph()
resolver = AddressResolver(g)
# 0x600000000 / (6 GB) = 4
pa = PhysAddr.hbm_addr(sip_id=0, die_id=0, hbm_offset=0x600000000)
assert resolver.resolve(pa) == "sip0.cube0.hbm_ctrl.pe4"
def test_resolve_pe_tcm_addr():
"""PE TCM address -> sip{S}.cube{C}.pe{P}.pe_tcm"""
g = _graph()
resolver = AddressResolver(g)
pa = PhysAddr.pe_tcm_addr(sip_id=1, die_id=5, pe_id=7, tcm_offset=0x400)
assert resolver.resolve(pa) == "sip1.cube5.pe7.pe_tcm"
def test_resolve_sram_addr():
"""SRAM address -> sip{S}.cube{C}.sram"""
g = _graph()
resolver = AddressResolver(g)
pa = PhysAddr.cube_sram_addr(sip_id=0, die_id=10, sram_offset=0x800)
assert resolver.resolve(pa) == "sip0.cube10.sram"
def test_resolve_mcpu_addr():
"""MCPU pe_resource address -> sip{S}.cube{C}.m_cpu"""
g = _graph()
resolver = AddressResolver(g)
pa = PhysAddr.mcpu_resource_addr(
sip_id=0, die_id=2,
mcpu_sub_unit=0, sub_offset=0,
)
assert resolver.resolve(pa) == "sip0.cube2.m_cpu"
def test_resolve_nonexistent_node():
"""Address pointing to a node outside the compiled topology raises RoutingError."""
g = _graph()
resolver = AddressResolver(g)
# sip_id=15 doesn't exist in the 2-SIP topology
pa = PhysAddr.hbm_addr(sip_id=15, die_id=0, hbm_offset=0)
with pytest.raises(RoutingError):
resolver.resolve(pa)
# ── PathRouter: local HBM via router mesh ────────────────────────────
def test_path_local_hbm():
"""PE0 -> own slice: pe_dma -> r0c0 -> hbm_ctrl.pe0 (1 mesh hop)."""
g = _graph()
router = PathRouter(g)
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.pe0")
assert path[0] == "sip0.cube0.pe0.pe_dma"
assert path[-1] == "sip0.cube0.hbm_ctrl.pe0"
# Path must go through at least one router node
assert any(n.startswith("sip0.cube0.r") for n in path), \
"HBM path must traverse router mesh"
# No xbar or bridge nodes in the new topology
assert not any("xbar" in n or "bridge" in n for n in path)
# ── PathRouter: remote PE HBM (different corner, same cube) ──────────
def test_path_remote_pe_hbm():
"""PE4 (bottom half) -> its own slice: routes through router mesh."""
g = _graph()
router = PathRouter(g)
path = router.find_path("sip0.cube0.pe4", "sip0.cube0.hbm_ctrl.pe4")
assert path[0] == "sip0.cube0.pe4.pe_dma"
assert path[-1] == "sip0.cube0.hbm_ctrl.pe4"
assert any(n.startswith("sip0.cube0.r") for n in path)
assert not any("xbar" in n or "bridge" in n for n in path)
# ── PathRouter: cross-PE HBM distance reflects mesh hops (ADR-0017 D7) ─
def test_cross_pe_hbm_distance_increases_with_mesh_hops():
"""ADR-0017 D7: accessing another PE's HBM slice must take more
routing distance than accessing one's own slice, because each
per-PE hbm_ctrl is reachable only via its PE's router.
"""
g = _graph()
router = PathRouter(g)
_, dist_local = router.find_path_with_distance(
"sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.pe0")
_, dist_to_pe7 = router.find_path_with_distance(
"sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.pe7")
assert dist_to_pe7 > dist_local, (
f"pe0→pe7_slice should require more mesh distance than pe0→pe0_slice; "
f"got local={dist_local}, to_pe7={dist_to_pe7}"
)
def test_remote_pe_distance_not_less_than_local():
"""PE4 -> pe0_slice distance >= PE0 -> pe0_slice distance.
Both access pe0's slice (hbm_ctrl.pe0). PE0's path is shortest; PE4
must mesh-route up to r0c0 before entering the slice.
"""
g = _graph()
router = PathRouter(g)
_, dist_pe0 = router.find_path_with_distance(
"sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.pe0")
_, dist_pe4 = router.find_path_with_distance(
"sip0.cube0.pe4", "sip0.cube0.hbm_ctrl.pe0")
assert dist_pe4 >= dist_pe0
def test_path_remote_cube_hbm():
"""PE0 in cube0 can reach pe0's HBM in cube1 via UCIe (ADR-0004 D4)."""
g = _graph()
router = PathRouter(g)
path = router.find_path("sip0.cube0.pe0", "sip0.cube1.hbm_ctrl.pe0")
assert path[0] == "sip0.cube0.pe0.pe_dma"
assert path[-1] == "sip0.cube1.hbm_ctrl.pe0"
# inter-cube path must cross a UCIe link
assert any("ucie" in n.lower() for n in path), \
"remote cube path must traverse UCIe"
# must not be trivially short (needs router + ucie + remote router + hbm)
assert len(path) >= 5
# ── PathRouter: SRAM via router mesh ─────────────────────────────────
def test_path_sram_via_router_mesh():
"""PE -> SRAM must go through router mesh nodes."""
g = _graph()
router = PathRouter(g)
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.sram")
assert path[0] == "sip0.cube0.pe0.pe_dma"
assert path[-1] == "sip0.cube0.sram"
# Must traverse at least one router node
assert any(n.startswith("sip0.cube0.r") for n in path), \
"SRAM path must traverse router mesh"
# No xbar nodes
assert not any("xbar" in n for n in path)
# ── PathRouter: PE TCM (local) ──────────────────────────────────────
def test_path_local_tcm():
"""PE0 -> own TCM is PE-internal, not via router mesh."""
g = _graph()
router = PathRouter(g)
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.pe0.pe_tcm")
assert path[0] == "sip0.cube0.pe0.pe_dma"
assert path[-1] == "sip0.cube0.pe0.pe_tcm"
# PE-internal path, no fabric
assert not any("xbar" in n or n.startswith("sip0.cube0.r") for n in path)
# ── PathRouter: distance monotonic ──────────────────────────────────
def test_path_distance_positive():
"""Routed paths that traverse the mesh must have positive accumulated
distance (ADR-0002 D4). Use a cross-PE target so the path includes
inter-router mesh edges (which have non-zero distance_mm). The
single-hop pe0→pe0_slice path stays at 0 because PE_DMA↔router and
router↔hbm_ctrl are zero-length placements within the same corner."""
g = _graph()
router = PathRouter(g)
_, dist = router.find_path_with_distance(
"sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.pe7")
assert dist > 0
def test_path_deterministic():
"""Same (src, dst) must always produce the same path."""
g = _graph()
r1 = PathRouter(g)
r2 = PathRouter(g)
p1 = r1.find_path("sip0.cube0.pe3", "sip0.cube0.hbm_ctrl.pe0")
p2 = r2.find_path("sip0.cube0.pe3", "sip0.cube0.hbm_ctrl.pe0")
assert p1 == p2
def test_remote_cube_path_no_routing_error():
"""Routing to remote cube HBM must not raise RoutingError (ADR-0004 D4)."""
g = _graph()
router = PathRouter(g)
# cube0.PE0 -> cube1.hbm_ctrl (adjacent cube, E direction)
path = router.find_path("sip0.cube0.pe0", "sip0.cube1.hbm_ctrl.pe0")
assert len(path) >= 1 # succeeds without exception