Show individual routers in cube_view SVG, fix row Y overlap

- cube_view now renders all 32 router nodes from cube_mesh.yaml
  instead of collapsed "router_mesh" placeholder
- Fix mesh_gen row Y position overlap (r1/r2 and r3/r4 had same Y)
  by adding hbm_gap spacing between PE rows and HBM zone
- Add noc_router to visualizer KIND_SIZE for proper sizing
- Update cube view tests for individual router nodes

339 passed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 18:22:38 -07:00
parent d2c92b8a18
commit 91085733ba
5 changed files with 395 additions and 183 deletions
+106 -76
View File
@@ -921,21 +921,26 @@ def _build_cube_view(spec: dict) -> ViewGraph:
label=name.upper().replace("_", " "),
)
# Router mesh representative node (collapsed for view)
router_spec = cube["components"]["noc_router"]
cx = cube_w / 2
cy = cube_h / 2
nodes["router_mesh"] = Node(
id="router_mesh", kind=router_spec["kind"], impl=router_spec["impl"],
attrs=router_spec["attrs"], pos_mm=(cx + 2.0, cy),
label="ROUTER MESH",
)
# Load mesh data early (needed for router nodes + PE distances)
mesh_data = spec.get("_mesh", {})
# PEs as opaque blocks (no per-PE xbar nodes)
# Router nodes from cube_mesh.yaml (explicit in view)
router_spec = cube["components"]["noc_router"]
routers = mesh_data.get("routers", {})
for rkey, rval in routers.items():
if rval is None:
continue
rx, ry = rval["pos_mm"]
nodes[rkey] = Node(
id=rkey, kind=router_spec["kind"], impl=router_spec["impl"],
attrs=router_spec["attrs"], pos_mm=(rx, ry),
label=rkey.upper(),
)
# PEs as opaque blocks
corners = cube["pe_layout"]["corners"]
pe_per_corner = cube["pe_layout"]["pe_per_corner"]
corner_pos = _corner_pe_positions(cube_w, cube_h)
mesh_data = spec.get("_mesh", {})
pe_noc_distances = _compute_pe_noc_distances(
mesh_data, corner_pos, corners, pe_per_corner,
) if mesh_data else {}
@@ -950,76 +955,101 @@ def _build_cube_view(spec: dict) -> ViewGraph:
attrs={"corner": corner}, pos_mm=(px, py),
label=f"PE{pe_idx}",
)
# PE ↔ router_mesh (view representation)
pe_to_router_bw = clinks.get("pe_to_router_bw_gbs", 256.0)
view_edges.append(Edge(
src=pid, dst="router_mesh",
distance_mm=pe_noc_distances.get(pe_idx, 0.0),
bw_gbs=pe_to_router_bw,
kind="pe_to_router",
))
view_edges.append(Edge(
src="router_mesh", dst=pid,
distance_mm=clinks.get("noc_to_pe_cpu_mm", 0.0),
kind="command",
))
pe_idx += 1
# router_mesh ↔ hbm_ctrl
# View edges based on cube_mesh.yaml attach (mirrors _instantiate_cube logic)
pe_to_router_bw = clinks.get("pe_to_router_bw_gbs", 256.0)
hbm_to_router_bw = clinks.get("hbm_to_router_bw_gbs", 256.0)
view_edges.append(Edge(
src="router_mesh", dst="hbm_ctrl",
distance_mm=0.0, bw_gbs=hbm_to_router_bw,
kind="router_to_hbm",
))
view_edges.append(Edge(
src="hbm_ctrl", dst="router_mesh",
distance_mm=0.0, bw_gbs=hbm_to_router_bw,
kind="hbm_to_router",
))
# router_mesh ↔ m_cpu
view_edges.append(Edge(
src="m_cpu", dst="router_mesh",
distance_mm=clinks.get("m_cpu_to_router_mm", 0.0),
kind="command",
))
view_edges.append(Edge(
src="router_mesh", dst="m_cpu",
distance_mm=clinks.get("m_cpu_to_router_mm", 0.0),
kind="command",
))
# router_mesh ↔ sram
sram_bw = clinks.get("sram_to_router_bw_gbs", 128.0)
view_edges.append(Edge(
src="router_mesh", dst="sram",
distance_mm=0.0, bw_gbs=sram_bw,
kind="router_to_sram",
))
ucie_conn_bw_v = ucie_cfg.get("per_connection_bw_gbs", 128.0)
for port in ucie_cfg["ports"]:
for ci in range(ucie_n_conn):
conn_id = f"ucie-{port}.conn{ci}"
view_edges.append(Edge(
src="router_mesh", dst=conn_id,
distance_mm=0.0, bw_gbs=ucie_conn_bw_v,
kind="router_to_ucie_conn",
))
view_edges.append(Edge(
src=conn_id, dst=f"ucie-{port}",
distance_mm=0.0, kind="ucie_internal",
))
view_edges.append(Edge(
src=f"ucie-{port}", dst=conn_id,
distance_mm=0.0, kind="ucie_internal",
))
view_edges.append(Edge(
src=conn_id, dst="router_mesh",
distance_mm=0.0, bw_gbs=ucie_conn_bw_v,
kind="ucie_conn_to_router",
))
n_rows = mesh_data.get("mesh", {}).get("rows", 6)
n_cols = mesh_data.get("mesh", {}).get("cols", 6)
# Router ↔ router mesh edges
for r in range(n_rows):
for c in range(n_cols):
rkey = f"r{r}c{c}"
if routers.get(rkey) is None:
continue
src_pos = routers[rkey]["pos_mm"]
# Horizontal neighbor
for nc in range(c + 1, n_cols):
nkey = f"r{r}c{nc}"
if routers.get(nkey) is None:
continue
dist = abs(routers[nkey]["pos_mm"][0] - src_pos[0])
view_edges.append(Edge(
src=rkey, dst=nkey, distance_mm=round(dist, 2),
kind="router_mesh",
))
break
# Vertical neighbor
for nr in range(r + 1, n_rows):
nkey = f"r{nr}c{c}"
if routers.get(nkey) is None:
continue
dist = abs(routers[nkey]["pos_mm"][1] - src_pos[1])
view_edges.append(Edge(
src=rkey, dst=nkey, distance_mm=round(dist, 2),
kind="router_mesh",
))
break
# Component ↔ router edges from attach lists
for rkey, rval in routers.items():
if rval is None:
continue
for item in rval.get("attach", []):
if item.endswith(".dma"):
pe_prefix = item.rsplit(".", 1)[0]
pid = pe_prefix.replace("pe", "pe") # "pe0" → "pe0"
if pid in nodes:
view_edges.append(Edge(
src=pid, dst=rkey, distance_mm=0.0,
bw_gbs=pe_to_router_bw, kind="pe_to_router",
))
view_edges.append(Edge(
src=rkey, dst=pid, distance_mm=0.0,
kind="command",
))
elif item.endswith(".hbm"):
view_edges.append(Edge(
src=rkey, dst="hbm_ctrl", distance_mm=0.0,
bw_gbs=hbm_to_router_bw, kind="router_to_hbm",
))
elif item == "m_cpu":
view_edges.append(Edge(
src="m_cpu", dst=rkey, distance_mm=0.0, kind="command",
))
view_edges.append(Edge(
src=rkey, dst="m_cpu", distance_mm=0.0, kind="command",
))
elif item == "sram":
view_edges.append(Edge(
src="sram", dst=rkey, distance_mm=0.0,
bw_gbs=sram_bw, kind="router_to_sram",
))
elif item.startswith("ucie_"):
parts = item.split(".")
direction = parts[0].replace("ucie_", "").upper()
conn_num = parts[1].replace("c", "")
conn_id = f"ucie-{direction}.conn{conn_num}"
view_edges.append(Edge(
src=rkey, dst=conn_id, distance_mm=0.0,
bw_gbs=ucie_conn_bw_v, kind="router_to_ucie_conn",
))
view_edges.append(Edge(
src=conn_id, dst=rkey, distance_mm=0.0,
bw_gbs=ucie_conn_bw_v, kind="ucie_conn_to_router",
))
view_edges.append(Edge(
src=conn_id, dst=f"ucie-{direction}",
distance_mm=0.0, kind="ucie_internal",
))
view_edges.append(Edge(
src=f"ucie-{direction}", dst=conn_id,
distance_mm=0.0, kind="ucie_internal",
))
return ViewGraph(
name="cube", nodes=nodes, edges=view_edges,
+8 -4
View File
@@ -111,6 +111,7 @@ def _compute_row_positions(
# Top half: evenly spaced from top PE y to just above HBM zone
top_pe_y = 1.5
hbm_gap = 1.5 # minimum gap between PE rows and HBM rows
hbm_top_y = cube_h / 2 - 1.5 # ~5.5 for h=14
hbm_bot_y = cube_h / 2 + 1.5 # ~8.5 for h=14
bot_pe_y = cube_h - 1.5
@@ -119,21 +120,24 @@ def _compute_row_positions(
if rows_per_half == 1:
top_rows = [top_pe_y]
else:
step = (hbm_top_y - top_pe_y) / (rows_per_half - 1) if rows_per_half > 1 else 0
# End before HBM zone with gap
top_end = hbm_top_y - hbm_gap
step = (top_end - top_pe_y) / (rows_per_half - 1) if rows_per_half > 1 else 0
for i in range(rows_per_half):
top_rows.append(round(top_pe_y + i * step, 1))
# HBM rows
hbm_rows = [round(hbm_top_y, 1), round(hbm_bot_y, 1)]
# Bottom half: mirror of top
# Bottom half: mirror of top, start after HBM zone with gap
bot_rows: list[float] = []
if rows_per_half == 1:
bot_rows = [bot_pe_y]
else:
step = (bot_pe_y - hbm_bot_y) / (rows_per_half - 1) if rows_per_half > 1 else 0
bot_start = hbm_bot_y + hbm_gap
step = (bot_pe_y - bot_start) / (rows_per_half - 1) if rows_per_half > 1 else 0
for i in range(rows_per_half):
bot_rows.append(round(hbm_bot_y + i * step, 1))
bot_rows.append(round(bot_start + i * step, 1))
return top_rows + hbm_rows + bot_rows, rows_per_half
+1
View File
@@ -62,6 +62,7 @@ _KIND_SIZE: dict[str, tuple[float, float]] = {
"cube": (6.0, 4.0),
"iochiplet": (4.0, 1.5),
"switch": (5.0, 1.5),
"noc_router": (1.2, 0.8),
}