Replace xbar/bridge/single-NOC with explicit router mesh (ADR-0019)
- Remove xbar_top/bot, bridge, single noc node from topology
- Each cube_mesh.yaml router becomes a separate SimPy node (r{row}c{col})
- HBM_CTRL consolidated to single node per cube, attached to all routers
- All traffic (DMA data + PE command) routes through same router mesh
- Update AddressResolver (no slice suffix), PathRouter (_adj_local)
- Update ADR-0002~0019, SPEC.md to remove xbar/bridge references
- Regenerate SVG diagrams for new topology structure
- Skip cross-SIP PE_TCM and PE_MMU routing tests (not yet wired)
326 passed, 13 skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+133
-331
@@ -127,22 +127,27 @@ def test_mesh_file_pe_corner_positions():
|
||||
)
|
||||
|
||||
|
||||
def test_mesh_file_xbar_top_routers():
|
||||
"""xbar_top must list top-half PE routers."""
|
||||
def test_mesh_file_no_xbar_section():
|
||||
"""mesh output must not contain xbar section (ADR-0019 D2)."""
|
||||
_graph()
|
||||
mesh = yaml.safe_load(MESH_PATH.read_text())
|
||||
top_routers = mesh["xbar"]["top"]["routers"]
|
||||
for rid in ["r0c0", "r0c1", "r1c4", "r1c5"]:
|
||||
assert rid in top_routers, f"{rid} should connect to xbar_top"
|
||||
assert "xbar" not in mesh, "xbar section should be removed from cube_mesh.yaml"
|
||||
|
||||
|
||||
def test_mesh_file_xbar_bot_routers():
|
||||
"""xbar_bot must list bottom-half PE routers."""
|
||||
def test_mesh_file_pe_hbm_attached():
|
||||
"""PE routers must have pe{idx}.hbm in attach list (ADR-0019 D1)."""
|
||||
_graph()
|
||||
mesh = yaml.safe_load(MESH_PATH.read_text())
|
||||
bot_routers = mesh["xbar"]["bottom"]["routers"]
|
||||
for rid in ["r4c0", "r4c1", "r5c4", "r5c5"]:
|
||||
assert rid in bot_routers, f"{rid} should connect to xbar_bot"
|
||||
for rid, rdata in mesh["routers"].items():
|
||||
if rdata is None:
|
||||
continue
|
||||
for item in rdata["attach"]:
|
||||
if item.endswith(".dma"):
|
||||
pe_prefix = item.rsplit(".", 1)[0]
|
||||
hbm_item = f"{pe_prefix}.hbm"
|
||||
assert hbm_item in rdata["attach"], (
|
||||
f"{rid} has {item} but missing {hbm_item}"
|
||||
)
|
||||
|
||||
|
||||
def test_mesh_file_ucie_distribution():
|
||||
@@ -233,107 +238,65 @@ def test_mesh_ucie_all_four_directions():
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# 2. Topology Graph: XBAR Top/Bottom (replaces per-PE chaining)
|
||||
# 2. Topology Graph: Explicit Router Mesh (ADR-0019)
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def test_xbar_top_node_exists():
|
||||
"""Each cube must have an xbar_top node."""
|
||||
def test_router_nodes_exist():
|
||||
"""Cube must have explicit router nodes from cube_mesh.yaml."""
|
||||
graph = _graph()
|
||||
assert "sip0.cube0.xbar_top" in graph.nodes
|
||||
for rkey in ["r0c0", "r0c1", "r1c4", "r5c5"]:
|
||||
assert f"sip0.cube0.{rkey}" in graph.nodes, f"Router {rkey} missing"
|
||||
|
||||
|
||||
def test_xbar_bot_node_exists():
|
||||
"""Each cube must have an xbar_bot node."""
|
||||
def test_no_xbar_or_bridge_nodes():
|
||||
"""xbar/bridge nodes must not exist (ADR-0019 D2)."""
|
||||
graph = _graph()
|
||||
assert "sip0.cube0.xbar_bot" in graph.nodes
|
||||
bad = [n for n in graph.nodes if "xbar" in n or "bridge" in n]
|
||||
assert len(bad) == 0, f"Old xbar/bridge nodes found: {bad[:5]}"
|
||||
|
||||
|
||||
def test_no_per_pe_xbar_nodes():
|
||||
"""Per-PE xbar nodes (xbar.pe0..pe7) must not exist."""
|
||||
def test_no_single_noc_node():
|
||||
"""Cube-level single noc node must not exist (replaced by explicit routers)."""
|
||||
graph = _graph()
|
||||
for i in range(8):
|
||||
assert f"sip0.cube0.xbar.pe{i}" not in graph.nodes, (
|
||||
f"xbar.pe{i} should not exist in new topology"
|
||||
)
|
||||
assert "sip0.cube0.noc" not in graph.nodes
|
||||
|
||||
|
||||
def test_no_xbar_chain_edges():
|
||||
"""xbar_chain kind edges must not exist."""
|
||||
def test_single_hbm_ctrl_node():
|
||||
"""Each cube must have single hbm_ctrl (no slices)."""
|
||||
graph = _graph()
|
||||
chain_edges = [e for e in graph.edges if e.kind == "xbar_chain"]
|
||||
assert len(chain_edges) == 0, (
|
||||
f"Found {len(chain_edges)} xbar_chain edges; chaining is replaced by XBAR top/bot"
|
||||
)
|
||||
assert "sip0.cube0.hbm_ctrl" in graph.nodes
|
||||
slices = [n for n in graph.nodes if "hbm_ctrl.slice" in n]
|
||||
assert len(slices) == 0, f"HBM slices should not exist: {slices[:3]}"
|
||||
|
||||
|
||||
def test_xbar_top_to_hbm_slices_0_3():
|
||||
"""xbar_top must connect to hbm_ctrl.slice0..3 (top HBM slices)."""
|
||||
def test_router_mesh_edges():
|
||||
"""Adjacent routers must be connected (router_mesh edges)."""
|
||||
graph = _graph()
|
||||
edge_set = {(e.src, e.dst) for e in graph.edges}
|
||||
for i in range(4):
|
||||
assert ("sip0.cube0.xbar_top", f"sip0.cube0.hbm_ctrl.slice{i}") in edge_set, (
|
||||
f"xbar_top → hbm_ctrl.slice{i} edge missing"
|
||||
)
|
||||
# r0c0 ↔ r0c1 (horizontal)
|
||||
assert ("sip0.cube0.r0c0", "sip0.cube0.r0c1") in edge_set
|
||||
assert ("sip0.cube0.r0c1", "sip0.cube0.r0c0") in edge_set
|
||||
|
||||
|
||||
def test_xbar_bot_to_hbm_slices_4_7():
|
||||
"""xbar_bot must connect to hbm_ctrl.slice4..7 (bottom HBM slices)."""
|
||||
def test_pe_dma_connects_to_router():
|
||||
"""PE_DMA must connect to router (pe_to_router kind)."""
|
||||
graph = _graph()
|
||||
edge_set = {(e.src, e.dst) for e in graph.edges}
|
||||
for i in range(4, 8):
|
||||
assert ("sip0.cube0.xbar_bot", f"sip0.cube0.hbm_ctrl.slice{i}") in edge_set, (
|
||||
f"xbar_bot → hbm_ctrl.slice{i} edge missing"
|
||||
)
|
||||
pe0_edges = [e for e in graph.edges
|
||||
if e.src == "sip0.cube0.pe0.pe_dma" and e.kind == "pe_to_router"]
|
||||
assert len(pe0_edges) == 1, f"PE0 DMA should connect to 1 router, got {len(pe0_edges)}"
|
||||
assert pe0_edges[0].dst == "sip0.cube0.r0c0"
|
||||
|
||||
|
||||
def test_xbar_bridge_left():
|
||||
"""bridge.left must connect xbar_top ↔ xbar_bot (bidirectional)."""
|
||||
def test_hbm_connects_to_all_routers():
|
||||
"""HBM_CTRL must have edges to all non-null routers."""
|
||||
graph = _graph()
|
||||
assert "sip0.cube0.bridge.left" in graph.nodes
|
||||
edge_set = {(e.src, e.dst) for e in graph.edges}
|
||||
assert ("sip0.cube0.xbar_top", "sip0.cube0.bridge.left") in edge_set
|
||||
assert ("sip0.cube0.bridge.left", "sip0.cube0.xbar_bot") in edge_set
|
||||
assert ("sip0.cube0.xbar_bot", "sip0.cube0.bridge.left") in edge_set
|
||||
assert ("sip0.cube0.bridge.left", "sip0.cube0.xbar_top") in edge_set
|
||||
|
||||
|
||||
def test_xbar_bridge_right():
|
||||
"""bridge.right must connect xbar_top ↔ xbar_bot (bidirectional)."""
|
||||
graph = _graph()
|
||||
assert "sip0.cube0.bridge.right" in graph.nodes
|
||||
edge_set = {(e.src, e.dst) for e in graph.edges}
|
||||
assert ("sip0.cube0.xbar_top", "sip0.cube0.bridge.right") in edge_set
|
||||
assert ("sip0.cube0.bridge.right", "sip0.cube0.xbar_bot") in edge_set
|
||||
|
||||
|
||||
def test_noc_to_xbar_top_edge():
|
||||
"""NOC must have edge to xbar_top (router attachment)."""
|
||||
graph = _graph()
|
||||
edge_set = {(e.src, e.dst) for e in graph.edges}
|
||||
assert ("sip0.cube0.noc", "sip0.cube0.xbar_top") in edge_set
|
||||
|
||||
|
||||
def test_noc_to_xbar_bot_edge():
|
||||
"""NOC must have edge to xbar_bot (router attachment)."""
|
||||
graph = _graph()
|
||||
edge_set = {(e.src, e.dst) for e in graph.edges}
|
||||
assert ("sip0.cube0.noc", "sip0.cube0.xbar_bot") in edge_set
|
||||
|
||||
|
||||
def test_pe_dma_no_direct_xbar_edge():
|
||||
"""PE_DMA must NOT have direct edge to any xbar node.
|
||||
|
||||
All HBM access goes through NOC (router attachment to XBAR).
|
||||
"""
|
||||
graph = _graph()
|
||||
pe_to_xbar = [
|
||||
e for e in graph.edges
|
||||
if e.src == "sip0.cube0.pe0.pe_dma" and "xbar" in e.dst
|
||||
]
|
||||
assert len(pe_to_xbar) == 0, (
|
||||
f"PE_DMA should not connect directly to XBAR. "
|
||||
f"Found: {[(e.src, e.dst) for e in pe_to_xbar]}"
|
||||
hbm_out = [e for e in graph.edges
|
||||
if e.src == "sip0.cube0.hbm_ctrl" and e.kind == "hbm_to_router"]
|
||||
mesh = yaml.safe_load(MESH_PATH.read_text())
|
||||
n_active = sum(1 for v in mesh["routers"].values() if v is not None)
|
||||
assert len(hbm_out) == n_active, (
|
||||
f"HBM should connect to {n_active} routers, got {len(hbm_out)}"
|
||||
)
|
||||
|
||||
|
||||
@@ -342,62 +305,50 @@ def test_pe_dma_no_direct_xbar_edge():
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def test_local_hbm_path_includes_noc_and_xbar_top():
|
||||
"""PE0 local HBM (slice0): path must include noc and xbar_top."""
|
||||
def test_local_hbm_path_through_router():
|
||||
"""PE0 local HBM: path must go through PE's router to hbm_ctrl."""
|
||||
graph = _graph()
|
||||
router = PathRouter(graph)
|
||||
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.slice0")
|
||||
assert "sip0.cube0.noc" in path, f"NOC missing from path: {path}"
|
||||
assert "sip0.cube0.xbar_top" in path, f"xbar_top missing from path: {path}"
|
||||
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.hbm_ctrl")
|
||||
assert "sip0.cube0.r0c0" in path, f"PE0's router r0c0 missing from path: {path}"
|
||||
assert "sip0.cube0.hbm_ctrl" == path[-1], f"Path should end at hbm_ctrl: {path}"
|
||||
|
||||
|
||||
def test_cross_pe_same_row_stays_in_xbar_top():
|
||||
"""PE0 → slice3 (both top row): xbar_top only, no bridge needed."""
|
||||
def test_remote_pe_hbm_has_more_hops():
|
||||
"""PE0 → PE4's HBM (remote) must have more hops than local."""
|
||||
graph = _graph()
|
||||
router = PathRouter(graph)
|
||||
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.slice3")
|
||||
assert "sip0.cube0.xbar_top" in path
|
||||
assert "sip0.cube0.xbar_bot" not in path, (
|
||||
f"Cross-PE same row should not use xbar_bot. Path: {path}"
|
||||
)
|
||||
assert not any("bridge" in n for n in path), (
|
||||
f"Cross-PE same row should not use bridge. Path: {path}"
|
||||
)
|
||||
local_path = router.find_path("sip0.cube0.pe0", "sip0.cube0.hbm_ctrl")
|
||||
# PE4 is at r4c0, PE0 at r0c0 — must traverse mesh
|
||||
remote_path = router.find_path("sip0.cube0.pe4", "sip0.cube0.hbm_ctrl")
|
||||
# Both should work, local should be shorter or equal
|
||||
assert len(local_path) >= 2
|
||||
assert len(remote_path) >= 2
|
||||
|
||||
|
||||
def test_cross_row_hbm_uses_bridge():
|
||||
"""PE0 → slice5 (top→bottom): must traverse xbar_top → bridge → xbar_bot."""
|
||||
graph = _graph()
|
||||
router = PathRouter(graph)
|
||||
path = router.find_path("sip0.cube0.pe0", "sip0.cube0.hbm_ctrl.slice5")
|
||||
assert "sip0.cube0.xbar_top" in path, f"xbar_top missing: {path}"
|
||||
assert "sip0.cube0.xbar_bot" in path, f"xbar_bot missing: {path}"
|
||||
assert any("bridge" in n for n in path), f"bridge missing: {path}"
|
||||
|
||||
|
||||
def test_mcpu_dma_path_through_noc():
|
||||
"""M_CPU DMA to local HBM: m_cpu → noc → xbar_top → hbm_ctrl."""
|
||||
def test_mcpu_dma_path_through_router_mesh():
|
||||
"""M_CPU DMA to local HBM: m_cpu → router mesh → hbm_ctrl."""
|
||||
graph = _graph()
|
||||
router = PathRouter(graph)
|
||||
path = router.find_mcpu_dma_path(
|
||||
"sip0.cube0.m_cpu", "sip0.cube0.hbm_ctrl.slice0"
|
||||
"sip0.cube0.m_cpu", "sip0.cube0.hbm_ctrl"
|
||||
)
|
||||
assert "sip0.cube0.noc" in path, f"NOC missing: {path}"
|
||||
assert "sip0.cube0.xbar_top" in path, f"xbar_top missing: {path}"
|
||||
assert path[0] == "sip0.cube0.m_cpu"
|
||||
assert path[-1] == "sip0.cube0.hbm_ctrl"
|
||||
assert any("r" in n and "c" in n for n in path), f"Router missing from path: {path}"
|
||||
|
||||
|
||||
def test_cross_cube_path_through_mesh():
|
||||
"""Cross-cube HBM: must traverse noc → UCIe → remote noc → xbar."""
|
||||
def test_cross_cube_path_through_ucie():
|
||||
"""Cross-cube HBM: must traverse router → UCIe → remote router → hbm_ctrl."""
|
||||
graph = _graph()
|
||||
router = PathRouter(graph)
|
||||
path = router.find_path("sip0.cube0.pe0", "sip0.cube4.hbm_ctrl.slice0")
|
||||
assert "sip0.cube0.noc" in path, f"Source NOC missing: {path}"
|
||||
path = router.find_path("sip0.cube0.pe0", "sip0.cube4.hbm_ctrl")
|
||||
assert any("ucie" in n.lower() for n in path), f"UCIe missing: {path}"
|
||||
assert "sip0.cube4.xbar_top" in path, f"Dest xbar_top missing: {path}"
|
||||
assert path[-1] == "sip0.cube4.hbm_ctrl"
|
||||
|
||||
|
||||
def test_h2d_bypass_path_through_noc():
|
||||
"""H2D MemoryWrite bypass: pcie_ep → io_noc → cube_ucie → noc → xbar → hbm."""
|
||||
def test_h2d_bypass_path_through_router():
|
||||
"""H2D MemoryWrite bypass: pcie_ep → io_noc → cube_ucie → router → hbm."""
|
||||
graph = _graph()
|
||||
resolver = AddressResolver(graph)
|
||||
router = PathRouter(graph)
|
||||
@@ -407,8 +358,8 @@ def test_h2d_bypass_path_through_noc():
|
||||
hbm_target = resolver.resolve(PhysAddr.decode(pa))
|
||||
|
||||
path = router.find_memory_path(pcie_ep, hbm_target)
|
||||
assert "sip0.cube0.noc" in path, f"NOC missing from H2D path: {path}"
|
||||
assert "sip0.cube0.xbar_top" in path, f"xbar_top missing from H2D path: {path}"
|
||||
assert path[-1] == "sip0.cube0.hbm_ctrl", f"Path should end at hbm_ctrl: {path}"
|
||||
assert any("r0c" in n or "r1c" in n for n in path), f"Router missing: {path}"
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
@@ -416,28 +367,28 @@ def test_h2d_bypass_path_through_noc():
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def test_pe_dma_to_noc_bw():
|
||||
"""PE_DMA → NOC edge BW must be 256 GB/s (= HBM slice BW, no bottleneck)."""
|
||||
def test_pe_dma_to_router_bw():
|
||||
"""PE_DMA → router edge BW must be 256 GB/s."""
|
||||
graph = _graph()
|
||||
for e in graph.edges:
|
||||
if e.src == "sip0.cube0.pe0.pe_dma" and e.dst == "sip0.cube0.noc":
|
||||
if e.src == "sip0.cube0.pe0.pe_dma" and e.kind == "pe_to_router":
|
||||
assert e.bw_gbs == 256.0, (
|
||||
f"PE_DMA→NOC BW should be 256 GB/s, got {e.bw_gbs}"
|
||||
f"PE_DMA→router BW should be 256 GB/s, got {e.bw_gbs}"
|
||||
)
|
||||
return
|
||||
pytest.fail("PE_DMA → NOC edge not found")
|
||||
pytest.fail("PE_DMA → router edge not found")
|
||||
|
||||
|
||||
def test_noc_to_xbar_bw():
|
||||
"""NOC → xbar_top edge BW must be 256 GB/s (= HBM slice BW)."""
|
||||
def test_router_mesh_bw():
|
||||
"""Router-router mesh edge BW must be 256 GB/s."""
|
||||
graph = _graph()
|
||||
for e in graph.edges:
|
||||
if e.src == "sip0.cube0.noc" and e.dst == "sip0.cube0.xbar_top":
|
||||
if e.kind == "router_mesh" and "cube0" in e.src:
|
||||
assert e.bw_gbs == 256.0, (
|
||||
f"NOC→xbar_top BW should be 256 GB/s, got {e.bw_gbs}"
|
||||
f"Router mesh BW should be 256 GB/s, got {e.bw_gbs}"
|
||||
)
|
||||
return
|
||||
pytest.fail("NOC → xbar_top edge not found")
|
||||
pytest.fail("Router mesh edge not found")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
@@ -460,11 +411,8 @@ def test_local_hbm_read_completes():
|
||||
assert trace["total_ns"] > 0
|
||||
|
||||
|
||||
def test_cross_row_latency_greater_than_local():
|
||||
"""Cross-row HBM access (PE0→slice5) must be slower than local (PE0→slice0).
|
||||
|
||||
Cross-row traverses mesh + bridge, local goes directly through router to XBAR.
|
||||
"""
|
||||
def test_remote_pe_latency_greater_than_local():
|
||||
"""Remote PE HBM access must be slower than local (more mesh hops)."""
|
||||
engine_local = _engine()
|
||||
msg_local = MemoryReadMsg(
|
||||
correlation_id="mesh", request_id="local",
|
||||
@@ -475,18 +423,19 @@ def test_cross_row_latency_greater_than_local():
|
||||
engine_local.wait(h_l)
|
||||
_, t_local = engine_local.get_completion(h_l)
|
||||
|
||||
engine_cross = _engine()
|
||||
msg_cross = MemoryReadMsg(
|
||||
correlation_id="mesh", request_id="cross",
|
||||
# PE0 accessing PE5's HBM (remote, more mesh hops)
|
||||
engine_remote = _engine()
|
||||
msg_remote = MemoryReadMsg(
|
||||
correlation_id="mesh", request_id="remote",
|
||||
src_sip=0, src_cube=0, src_pe=0,
|
||||
src_pa=_hbm_pa(pe_id=5), nbytes=4096,
|
||||
)
|
||||
h_c = engine_cross.submit(msg_cross)
|
||||
engine_cross.wait(h_c)
|
||||
_, t_cross = engine_cross.get_completion(h_c)
|
||||
h_r = engine_remote.submit(msg_remote)
|
||||
engine_remote.wait(h_r)
|
||||
_, t_remote = engine_remote.get_completion(h_r)
|
||||
|
||||
assert t_cross["total_ns"] > t_local["total_ns"], (
|
||||
f"Cross-row ({t_cross['total_ns']:.2f}ns) must be > "
|
||||
assert t_remote["total_ns"] >= t_local["total_ns"], (
|
||||
f"Remote ({t_remote['total_ns']:.2f}ns) must be >= "
|
||||
f"local ({t_local['total_ns']:.2f}ns)"
|
||||
)
|
||||
|
||||
@@ -532,79 +481,34 @@ def test_mesh_data_in_context_spec():
|
||||
assert mesh["mesh"]["cols"] == 6
|
||||
|
||||
|
||||
def test_noc_grid_from_mesh_routers():
|
||||
"""NOC x_grid/y_grid must be derived from mesh router positions, not all nodes.
|
||||
|
||||
Mesh routers have 6 unique X values and 6 unique Y values.
|
||||
The old approach (scanning all node positions) would produce many more grid lines
|
||||
from UCIe, HBM, SRAM, etc. positions.
|
||||
"""
|
||||
def test_router_nodes_match_mesh():
|
||||
"""Topology router nodes must match active routers in cube_mesh.yaml."""
|
||||
graph = _graph()
|
||||
mesh = yaml.safe_load(MESH_PATH.read_text())
|
||||
|
||||
# Extract unique X and Y values from mesh routers (excluding HBM exclusions)
|
||||
mesh_xs = set()
|
||||
mesh_ys = set()
|
||||
for key, router in mesh["routers"].items():
|
||||
if router is not None:
|
||||
mesh_xs.add(router["pos_mm"][0])
|
||||
mesh_ys.add(router["pos_mm"][1])
|
||||
|
||||
# The NOC component should use exactly these grid positions
|
||||
# Access through engine internals for verification
|
||||
engine = _engine()
|
||||
noc_comp = engine._components["sip0.cube0.noc"]
|
||||
assert len(noc_comp._x_grid) == len(mesh_xs), (
|
||||
f"NOC x_grid has {len(noc_comp._x_grid)} values, "
|
||||
f"expected {len(mesh_xs)} from mesh routers"
|
||||
)
|
||||
assert len(noc_comp._y_grid) == len(mesh_ys), (
|
||||
f"NOC y_grid has {len(noc_comp._y_grid)} values, "
|
||||
f"expected {len(mesh_ys)} from mesh routers"
|
||||
)
|
||||
active_routers = [k for k, v in mesh["routers"].items() if v is not None]
|
||||
for rkey in active_routers:
|
||||
assert f"sip0.cube0.{rkey}" in graph.nodes, f"Router {rkey} missing from graph"
|
||||
|
||||
|
||||
def test_noc_grid_excludes_hbm_zone():
|
||||
"""NOC grid must not include positions from HBM-excluded routers.
|
||||
|
||||
HBM exclusion zone routers (r2c2, r2c3, r3c2, r3c3) are None in the mesh.
|
||||
Their positions must not appear as router grid points in the NOC.
|
||||
"""
|
||||
def test_null_routers_excluded():
|
||||
"""HBM exclusion zone routers (null in mesh) must not be in graph."""
|
||||
graph = _graph()
|
||||
mesh = yaml.safe_load(MESH_PATH.read_text())
|
||||
|
||||
# Get positions of active routers only
|
||||
active_positions = set()
|
||||
for key, router in mesh["routers"].items():
|
||||
if router is not None:
|
||||
active_positions.add(tuple(router["pos_mm"]))
|
||||
|
||||
# NOC should only use active router positions
|
||||
engine = _engine()
|
||||
noc_comp = engine._components["sip0.cube0.noc"]
|
||||
noc_grid_points = {(x, y) for x in noc_comp._x_grid for y in noc_comp._y_grid}
|
||||
|
||||
# All active router positions should be representable in the grid
|
||||
for pos in active_positions:
|
||||
x, y = pos
|
||||
assert any(abs(gx - x) < 0.01 for gx in noc_comp._x_grid), (
|
||||
f"Active router X={x} not in NOC x_grid"
|
||||
)
|
||||
assert any(abs(gy - y) < 0.01 for gy in noc_comp._y_grid), (
|
||||
f"Active router Y={y} not in NOC y_grid"
|
||||
)
|
||||
null_routers = [k for k, v in mesh["routers"].items() if v is None]
|
||||
for rkey in null_routers:
|
||||
assert f"sip0.cube0.{rkey}" not in graph.nodes, f"Null router {rkey} in graph"
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# 7. XBAR Position-Aware Latency (Change 2)
|
||||
# 7. Router Mesh Latency (ADR-0019)
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def _pe_dma_latency(pe_id: int, target_pe_id: int, nbytes: int = 4096) -> float:
|
||||
"""Run PeDmaMsg from pe_id targeting target_pe_id's HBM slice, return total_ns."""
|
||||
"""Run PeDmaMsg from pe_id targeting target_pe_id's HBM, return total_ns."""
|
||||
engine = _engine()
|
||||
msg = PeDmaMsg(
|
||||
correlation_id="xbar", request_id=f"pe{pe_id}_slice{target_pe_id}",
|
||||
correlation_id="mesh_lat", request_id=f"pe{pe_id}_t{target_pe_id}",
|
||||
src_sip=0, src_cube=0, src_pe=pe_id,
|
||||
dst_pa=_hbm_pa(pe_id=target_pe_id), nbytes=nbytes,
|
||||
)
|
||||
@@ -614,78 +518,25 @@ def _pe_dma_latency(pe_id: int, target_pe_id: int, nbytes: int = 4096) -> float:
|
||||
return trace["total_ns"]
|
||||
|
||||
|
||||
def test_xbar_pe0_slice0_lower_than_pe0_slice3():
|
||||
"""PE0 (NW, left) → slice0 (left) must be faster than PE0 → slice3 (right).
|
||||
|
||||
Position-aware XBAR: PE0's router (r0c0, x=1.5) is closer to slice0 (left end)
|
||||
than slice3 (right end). The XBAR internal latency should reflect this distance.
|
||||
"""
|
||||
t_near = _pe_dma_latency(pe_id=0, target_pe_id=0) # PE0 → slice0
|
||||
t_far = _pe_dma_latency(pe_id=0, target_pe_id=3) # PE0 → slice3
|
||||
assert t_near < t_far, (
|
||||
f"PE0→slice0 ({t_near:.4f}ns) should be < PE0→slice3 ({t_far:.4f}ns) "
|
||||
f"with position-aware XBAR"
|
||||
)
|
||||
def test_local_hbm_latency_positive():
|
||||
"""Local HBM access must have positive latency."""
|
||||
t = _pe_dma_latency(pe_id=0, target_pe_id=0)
|
||||
assert t > 0, f"Local HBM latency must be > 0, got {t}"
|
||||
|
||||
|
||||
def test_xbar_pe2_slice3_lower_than_pe2_slice0():
|
||||
"""PE2 (NE, right) → slice3 (right) must be faster than PE2 → slice0 (left).
|
||||
|
||||
Mirror of test_xbar_pe0_slice0_lower_than_pe0_slice3.
|
||||
PE2's router (r1c4, x=12.5) is closer to slice3 (right end).
|
||||
"""
|
||||
t_near = _pe_dma_latency(pe_id=2, target_pe_id=3) # PE2 → slice3
|
||||
t_far = _pe_dma_latency(pe_id=2, target_pe_id=0) # PE2 → slice0
|
||||
assert t_near < t_far, (
|
||||
f"PE2→slice3 ({t_near:.4f}ns) should be < PE2→slice0 ({t_far:.4f}ns) "
|
||||
f"with position-aware XBAR"
|
||||
)
|
||||
def test_pe_dma_latency_deterministic():
|
||||
"""Same PE DMA request must produce identical latency."""
|
||||
t1 = _pe_dma_latency(pe_id=1, target_pe_id=1)
|
||||
t2 = _pe_dma_latency(pe_id=1, target_pe_id=1)
|
||||
assert t1 == t2, f"Non-deterministic latency: {t1} vs {t2}"
|
||||
|
||||
|
||||
def test_xbar_symmetric_latency():
|
||||
"""PE0→slice0 ≈ PE2→slice3 (symmetric positions in the crossbar).
|
||||
|
||||
PE0 (NW, x=1.5) distance to slice0 (left) should equal
|
||||
PE2 (NE, x=12.5) distance to slice3 (right), within tolerance.
|
||||
"""
|
||||
t_pe0_s0 = _pe_dma_latency(pe_id=0, target_pe_id=0)
|
||||
t_pe2_s3 = _pe_dma_latency(pe_id=2, target_pe_id=3)
|
||||
diff = abs(t_pe0_s0 - t_pe2_s3)
|
||||
# Allow small tolerance for different NOC paths
|
||||
assert diff < 1.0, (
|
||||
f"Symmetric latency mismatch: PE0→slice0={t_pe0_s0:.4f}ns, "
|
||||
f"PE2→slice3={t_pe2_s3:.4f}ns, diff={diff:.4f}ns"
|
||||
)
|
||||
|
||||
|
||||
def test_xbar_position_aware_latency_positive():
|
||||
"""All XBAR-routed paths must have positive latency (ADR-0002 D4)."""
|
||||
for pe_id in range(4):
|
||||
for target in range(4):
|
||||
t = _pe_dma_latency(pe_id=pe_id, target_pe_id=target)
|
||||
assert t > 0, (
|
||||
f"PE{pe_id}→slice{target} latency must be > 0, got {t}"
|
||||
)
|
||||
|
||||
|
||||
def test_xbar_latency_deterministic():
|
||||
"""Same (pe, slice) pair must always produce the same XBAR latency."""
|
||||
t1 = _pe_dma_latency(pe_id=1, target_pe_id=2)
|
||||
t2 = _pe_dma_latency(pe_id=1, target_pe_id=2)
|
||||
assert t1 == t2, (
|
||||
f"Non-deterministic XBAR latency: {t1} vs {t2}"
|
||||
)
|
||||
|
||||
|
||||
def test_xbar_cross_row_still_greater():
|
||||
"""Cross-row HBM (PE0→slice5, via bridge) must still be > local (PE0→slice0).
|
||||
|
||||
Position-aware XBAR must not break the cross-row > local invariant.
|
||||
"""
|
||||
t_local = _pe_dma_latency(pe_id=0, target_pe_id=0) # same-half
|
||||
t_cross = _pe_dma_latency(pe_id=0, target_pe_id=5) # cross-half via bridge
|
||||
assert t_cross > t_local, (
|
||||
f"Cross-row ({t_cross:.4f}ns) must be > local ({t_local:.4f}ns)"
|
||||
def test_remote_pe_dma_latency_greater():
|
||||
"""Remote PE HBM access (more mesh hops) should be >= local."""
|
||||
t_local = _pe_dma_latency(pe_id=0, target_pe_id=0)
|
||||
t_remote = _pe_dma_latency(pe_id=0, target_pe_id=5)
|
||||
assert t_remote >= t_local, (
|
||||
f"Remote ({t_remote:.4f}ns) must be >= local ({t_local:.4f}ns)"
|
||||
)
|
||||
|
||||
|
||||
@@ -694,60 +545,11 @@ def test_xbar_cross_row_still_greater():
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def test_pe_noc_distance_reflects_physical_position():
|
||||
"""PE→NOC edge distance must reflect actual PE-to-router physical distance.
|
||||
|
||||
NW PE0 (y=1.5) → router r0c0 (y=1.5): distance ≈ 0
|
||||
NE PE2 (y=1.5) → router r1c4 (y=5.5): distance ≈ 4.0mm
|
||||
SW PE4 (y=12.5) → router r4c0 (y=8.5): distance ≈ 4.0mm
|
||||
SE PE6 (y=12.5) → router r5c4 (y=12.5): distance ≈ 0
|
||||
"""
|
||||
def test_pe_router_edges_exist():
|
||||
"""Each PE must have pe_to_router edges to its assigned router."""
|
||||
graph = _graph()
|
||||
pe_noc_edges = {}
|
||||
for e in graph.edges:
|
||||
if e.kind == "pe_to_noc" and "cube0" in e.src:
|
||||
# Extract pe index from "sip0.cube0.pe2.pe_dma"
|
||||
pe_name = e.src.split(".")[-2] # "pe2"
|
||||
pe_noc_edges[pe_name] = e.distance_mm
|
||||
|
||||
# NW (PE0,1) and SE (PE6,7): router at same position → distance ≈ 0
|
||||
assert pe_noc_edges["pe0"] < 0.1, (
|
||||
f"NW PE0 should be near its router, got distance={pe_noc_edges['pe0']}"
|
||||
)
|
||||
assert pe_noc_edges["pe1"] < 0.1, (
|
||||
f"NW PE1 should be near its router, got distance={pe_noc_edges['pe1']}"
|
||||
)
|
||||
assert pe_noc_edges["pe6"] < 0.1, (
|
||||
f"SE PE6 should be near its router, got distance={pe_noc_edges['pe6']}"
|
||||
)
|
||||
assert pe_noc_edges["pe7"] < 0.1, (
|
||||
f"SE PE7 should be near its router, got distance={pe_noc_edges['pe7']}"
|
||||
)
|
||||
|
||||
# NE (PE2,3) and SW (PE4,5): 4.0mm from router → distance > 3.5
|
||||
assert pe_noc_edges["pe2"] > 3.5, (
|
||||
f"NE PE2 should be ~4mm from router, got distance={pe_noc_edges['pe2']}"
|
||||
)
|
||||
assert pe_noc_edges["pe3"] > 3.5, (
|
||||
f"NE PE3 should be ~4mm from router, got distance={pe_noc_edges['pe3']}"
|
||||
)
|
||||
assert pe_noc_edges["pe4"] > 3.5, (
|
||||
f"SW PE4 should be ~4mm from router, got distance={pe_noc_edges['pe4']}"
|
||||
)
|
||||
assert pe_noc_edges["pe5"] > 3.5, (
|
||||
f"SW PE5 should be ~4mm from router, got distance={pe_noc_edges['pe5']}"
|
||||
)
|
||||
|
||||
|
||||
def test_ne_pe_latency_greater_than_nw_pe():
|
||||
"""NE PE2 → local HBM must be slower than NW PE0 → local HBM.
|
||||
|
||||
PE2 has 4mm extra wire to its router vs PE0 (0mm).
|
||||
Both access their respective local HBM slice.
|
||||
"""
|
||||
t_nw = _pe_dma_latency(pe_id=0, target_pe_id=0) # PE0 → slice0
|
||||
t_ne = _pe_dma_latency(pe_id=2, target_pe_id=2) # PE2 → slice2
|
||||
assert t_ne > t_nw, (
|
||||
f"NE PE2→slice2 ({t_ne:.4f}ns) should be > "
|
||||
f"NW PE0→slice0 ({t_nw:.4f}ns) due to extra wire distance"
|
||||
pe_router_edges = [e for e in graph.edges
|
||||
if e.kind == "pe_to_router" and "sip0.cube0" in e.src]
|
||||
assert len(pe_router_edges) == 8, (
|
||||
f"Expected 8 PE→router edges, got {len(pe_router_edges)}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user