# ADR-0053: Topology Builder + Visualizer Algorithms ## Status Accepted (2026-05-22). `topology/builder.py`, `topology/mesh_gen.py`, `topology/visualizer.py` 가 함께 수행하는 토폴로지 컴파일·시각화 파이프라인의 핵심 알고리즘 선택 (placement-driven router attachment, mesh auto-layout, source_hash 캐시, view projection, SVG rendering) 을 명시한다. ADR-0006 가 topology compilation 의 high-level intent (compiled topology, distance extraction, automatic diagram generation) 를 정의하나, **builder 가 실제로 어떤 알고리즘을 사용하는지** 는 코드 grep 으로만 확인 가능했다. ## First action (제일 처음에 하는 일) `resolve_topology(path_str)` 가 호출되면 다음 4 단계가 순서대로 일어난다: 1. **경로 검증** (`builder.py::resolve_topology`): `Path(path_str).expanduser().resolve()`, 존재 확인, file 여부 확인. 실패 시 `FileNotFoundError` 또는 `ValueError`. 2. **YAML 파싱** (`_read_spec`): `yaml.safe_load`. parse error 면 line/ column 정보 포함한 `ValueError`. dict 가 아니면 reject. 3. **mesh 자동 생성** (`mesh_gen.ensure_mesh_file`): topology yaml 과 같은 디렉터리에 `cube_mesh.yaml` 을 만들거나 (캐시 invalid 시) 재사용 (캐시 hit 시). 이 단계가 cube NoC 의 라우터 grid 와 부착 정보를 결정. 4. **graph 컴파일** (`_compile_graph`): system → IO chiplets → cubes → inter-cube edges → IO↔cube edges → system↔IO edges 순으로 nodes/edges 를 누적, 그 다음 4 개의 view projection (system, sip, cube, pe) 을 생성하여 `TopologyGraph` 로 묶음. 즉, **topology compile 의 첫 일은 "topology.yaml 을 dict 로 읽고, 동일 디렉터리에 cube_mesh.yaml 을 생성/검증한 뒤, system→sip→cube→pe 순으로 flat graph + 4-view projection 을 만드는 것"** 이다. ## Context `topology/` 패키지의 책임: - **builder.py** (1207 줄): topology.yaml 을 받아 `TopologyGraph` (nodes + edges + 4 view projections) 를 컴파일. - **mesh_gen.py** (305 줄): cube NoC 의 라우터 grid 와 PE/UCIe/M_CPU/SRAM 부착 위치를 자동 결정하여 `cube_mesh.yaml` 로 캐시. - **visualizer.py** (887 줄): `TopologyGraph` 로부터 SVG 다이어그램 4종 (system / sip / cube / pe) 을 생성. ADR-0006 가 "topology compilation 의 결과는 distance metadata 와 diagram generation 의 single source" 라는 high-level 결정을 정의하나, 구체 알고리즘 (예: placement-driven nearest-router attachment, HBM 제외 zone 산출, source_hash 의 어떤 필드가 invalidation 을 트리거하는가) 은 ADR 에 없다. 특히 다음 결정들이 ADR-level 에 부재: - 왜 mesh_gen 이 별도 파일 (`cube_mesh.yaml`) 로 캐시되는가? - source_hash 가 어떤 필드를 포함하며, 어떤 변경이 재생성을 강제하는가? - placement coordinate 가 cube 좌표가 아닌 mm 단위인 이유? - HBM zone 제외와 UCIe N/S/E/W 분배가 mesh 안에서 어떻게 결정되는가? - view projection 4 개 (system/sip/cube/pe) 의 추상화 레벨 차이? 이 ADR 이 이 결정들을 한 곳에 정리한다. ## Decision ### D1. compile 파이프라인 — 6 단계 `_compile_graph(spec)`: 1. **시스템 노드 생성** (`_instantiate_system`): `fabric.switch0`, host CPU 등 system-level 노드 추가. 2. **per-SIP loop** (`for sip_id in range(system.sips.count)`): - **IO chiplets** (`_instantiate_io_chiplets`): pcie_ep / io_cpu / io_noc / io_ucie PHY / conn 노드 + 내부 양방향 edge 생성. - **cube instantiation** (`_instantiate_cube`): cube_mesh.yaml 의 router grid 를 토대로 cube-별 라우터, PE sub-components (pe_cpu, pe_dma, pe_fetch_store, pe_gemm, pe_math, pe_mmu, pe_tcm, pe_scheduler, pe_ipcq), m_cpu, sram, hbm_ctrl 인스턴스화 + 내부 edge 깔기. - **inter-cube edges** (`_add_inter_cube_edges`): UCIe N/S/E/W mesh edge. - **IO ↔ cube edges** (`_add_io_to_cube_edges`): io_noc 와 cube 의 edge UCIe phy 사이 연결. 3. **switch ↔ IO edges** (`_add_system_to_io_edges`): `fabric.switch0` 와 각 SIP 의 `pcie_ep` 사이 양방향 edge (ADR-0038 D3 + ADR-0010 의 cross-SIP IPCQ 경로). 4. **view projections** 4 종 build: - `_build_system_view(spec)` — Tray 레벨, SIP 들과 system switch. - `_build_sip_view(spec)` — SIP 안의 cube mesh + IO chiplet. - `_build_cube_view(spec)` — 단일 cube 안의 router grid + PE/M_CPU/SRAM/ HBM_CTRL 부착. - `_build_pe_view(spec)` — 단일 PE 안의 9 sub-components + 내부 edge. 5. **TopologyGraph 리턴**: `TopologyGraph(spec, nodes, edges, system_view, sip_view, cube_view, pe_view)`. 이 6 단계는 **순서가 의미를 가진다**: cubes 가 만들어진 후에야 inter-cube edges 가 valid 한 src/dst 를 갖고, IO chiplet 이 먼저 만들어져야 IO ↔ cube edge 가 그를 참조할 수 있다. 새 노드 종류를 끼울 때는 의존 관계를 보고 적절한 위치에 삽입해야 한다. ### D2. `cube_mesh.yaml` — 별도 파일 + source_hash 캐시 `mesh_gen.ensure_mesh_file(cube_spec, mesh_path)`: 1. `source_hash = _compute_source_hash(cube_spec)` 산출. 입력 필드: - `geometry` (cube_mm.w/h 등). - `pe_layout` (corners, pe_per_corner). - `ucie.n_connections`. - `memory_map.hbm_mapping_mode`. - `placement` (m_cpu/sram pos_mm). 2. `mesh_path` (= `topology.yaml` 와 같은 디렉터리의 `cube_mesh.yaml`) 이 존재하고 `existing.source_hash == source_hash` 면 재사용 (캐시 hit). 3. 아니면 `_generate_mesh(cube_spec, source_hash)` 로 새 mesh 생성 후 yaml 로 저장. 별도 파일로 캐시하는 이유: - mesh 생성은 PE/UCIe/router 부착 계산이 들어가 매번 다시 하기 무거움. - 같은 cube spec 으로 여러 번 실행 시 동일 mesh 가 보장되어야 함. - 사람이 직접 mesh 를 inspect / debug 할 수 있는 artifact 가 됨. `source_hash` 가 list 한 5 개 필드가 mesh 형상을 결정하는 핵심이며, 그 외 (예: bandwidth, overhead_ns) 변경은 mesh 재생성을 트리거하지 않는다. ### D3. cube NoC mesh auto-layout 알고리즘 `_generate_mesh(cube_spec)`: #### D3.1. 행/열 결정 - `pe_positions = _corner_pe_positions(cube_w, cube_h)`: 4 corner (NW/NE/ SW/SE) 마다 PE center 좌표 (mm). hardcoded `(1.5, 1.5)` / `(cube_w-1.5, cube_h-1.5)` 패턴 + `pe_per_corner=2` 면 각 corner 에 2 PE 위치. - `col_xs = _compute_col_positions(...)`: PE 들의 x 좌표 union + `max_spacing = 3.0 mm` 보다 큰 gap 에 relay 컬럼 삽입. - `row_ys, rows_per_half = _compute_row_positions(cube_h, n_connections, pe_positions)`: - `n_conn = max(n_connections, 2)` (hot path minimum). - `rows_per_half = ceil(n_conn / 2)`. - top 절반 + HBM 두 row + bottom 절반. HBM 은 `(cube_h/2 - 1.5, cube_h/2 + 1.5)` 에 위치. PE rows 와 HBM rows 사이 `hbm_gap = 1.5 mm`. #### D3.2. HBM 제외 zone `hbm_row_start = rows_per_half`, `hbm_row_end = rows_per_half + 1`. `hbm_col_start = n_cols // 2 - 1`, `hbm_col_end = n_cols // 2`. 이 (row, col) 사각형 안의 router 슬롯은 `None` 으로 마킹 (라우터 없음). 실제 HBM 컨트롤러는 별도 `hbm_ctrl.pe{X}` 노드로 ADR-0017 D9 의 per-PE 파티션 패턴을 따라 부착. #### D3.3. PE 부착 각 corner 의 PE 들은 다음 row 에 매핑: - Top half: NW → row 0, NE → row 1 (top_corners 안의 index). - Bottom half: SW → row `hbm_row_end + 1`, SE → row `hbm_row_end + 2`. 각 PE 의 x 좌표가 가장 가까운 col 의 router 에 부착 (`min(range(n_cols), key=lambda c: abs(col_xs[c] - pe_x))`). 부착 항목은 `pe{pe_idx}.dma`, `pe{pe_idx}.cpu`, `pe{pe_idx}.hbm` 세 가지 (router 별 attach list 에 push). #### D3.4. M_CPU / SRAM 부착 — nearest router by Euclidean distance `placement.m_cpu.pos_mm` (default `[1.5, 5.5]`) 와 `placement.sram.pos_mm` (default `[1.5, 8.5]`) 의 좌표에서 가장 가까운 router 를 Euclidean distance 로 찾아 attach list 에 `"m_cpu"` / `"sram"` 추가. #### D3.5. UCIe N/S/E/W 분배 `ucie_pe_rows = top_pe_rows + bot_pe_rows` (총 `2 * rows_per_half` 개). - UCIe-E: 매 PE row 마다 rightmost col 의 router 에 `ucie_e.c{i}`. - UCIe-W: leftmost col 의 router 에 `ucie_w.c{i}` (E 의 mirror). - UCIe-N/S: PE column 들 중 절반을 좌측, 절반을 우측으로 나눠 top row / bottom row 의 해당 col 에 부착. 각 UCIe connection 은 `c{i}` index 가 붙어 ucie_n_connections 만큼의 PHY 가 분산된다 (ADR-0017 D5+). ### D4. node 명명 규칙 — 단일 소유자 builder.py 는 다음 명명 규칙으로 노드를 만든다 (ADR-0051 D5 의 단일 소유자 원칙): - `fabric.switch0` — system-level switch. - `sip{S}.{io_id}.{pcie_ep|io_cpu|io_noc|io_ucie.{dir}|conn.{id}}` — IO chiplet. - `sip{S}.cube{C}.{m_cpu|sram|hbm_ctrl.pe{X}|noc.r{R}c{C}|...}` — cube 내부. - `sip{S}.cube{C}.pe{P}.{pe_cpu|pe_dma|pe_fetch_store|pe_gemm|pe_math|pe_mmu|pe_tcm|pe_scheduler|pe_ipcq}` — PE sub-components. 이 명명 규칙을 변경하려면 builder.py 와 router.py (ADR-0051) 의 helper 양쪽이 함께 갱신되어야 한다. 컴포넌트는 명명 규칙을 직접 알지 못하고 helper 만 호출한다. ### D5. edge `kind` 분류 각 edge 가 부여받는 `kind` 가 라우팅 정책 (ADR-0051 D2) 의 입력. 주요 kind 값: - `"pe_internal"` — PE 내부 sub-component 간. - `"pe_to_router"` — PE_DMA ↔ cube NoC router. - `"router_mesh"` — cube NoC router 간. - `"router_to_hbm"`, `"router_to_mcpu"`, `"router_to_sram"`, `"sram_to_router"` 등 — cube-attached component 간. - `"ucie_internal"`, `"ucie_conn_to_router"`, `"router_to_ucie_conn"`, `"ucie_conn_to_noc"`, `"noc_to_ucie_conn"`, `"ucie_mesh"` — UCIe 관련. - `"io_internal"` — IO chiplet 내부. - `"io_to_cube"`, `"cube_to_io"` — IO ↔ cube 경계. - `"pcie"` — switch ↔ pcie_ep. - `"command"` — control-plane only edges (M_CPU ↔ NOC 등; PE DMA path 에서 제외). 새 edge kind 를 추가하면 router.py 의 4 adjacency graph (ADR-0051 D2) 의 어느 카테고리에 속할지 결정해야 한다 — 그렇지 않으면 default 로 `_adj_all` 에만 포함되어 의도와 다른 routing 발생 가능. ### D6. view projection — 4 추상화 레벨 `TopologyGraph` 는 flat (nodes + edges) 외에 4 개의 view projection 을 보유: - **system_view** (`_build_system_view`): Tray 레벨. SIP 박스들 + `fabric. switch0`. PCIE 링크 표시. 외부 발표용 high-level overview. - **sip_view** (`_build_sip_view`): 한 SIP 안. cube mesh + IO chiplet (pcie_ep + io_cpu + io_noc). UCIe N/S/E/W 가 cube 간 연결로 보임. - **cube_view** (`_build_cube_view`): 한 cube 안. router grid + PE/M_CPU/ SRAM/HBM_CTRL 부착 + UCIe PHY edge 부분. cube 내부 라우팅 / placement 진단용. - **pe_view** (`_build_pe_view`): 한 PE 안. 9 sub-components + 내부 edge (pe_internal kind). 자세한 PE 내부 dataflow 검토용. view 는 spec 에서 `visualization.emit_views: [system, sip, cube]` 같이 선택적으로 출력 (ADR-0006). pe view 는 기본 출력에서 빠져 있으나 코드는 유지 (자세한 디버그용). ### D7. visualizer.py — SVG 다이어그램 출력 `emit_diagrams(graph, out_dir)` 가 모든 view 를 SVG 로 렌더. 핵심 함수: - `_render_view_svg(view)` — 일반적인 view 렌더 (router grid 가 없는 경우). - `_render_cube_view_svg(view, spec)` — cube view 전용 (HBM block 그리기, router grid layout, PE/M_CPU/SRAM/HBM positioning). - `_draw_node`, `_draw_edge` — 노드 / edge 의 시각적 표현. - `_pick_scale`, `_compute_node_sizes` — 자동 스케일링. visualizer 는 **derived artifact** (ADR-0006) 로 분류되며, 코드 변경 시 production check 대상이 아니다. CLAUDE.md 의 "Derived Artifacts" 항목과 정합. ### D8. spec 변경의 영향 범위 | spec 필드 | 영향 | mesh 재생성 | |---------------------------------------|-------------------|-------------| | `system.sips.count` | SIP 갯수, node 수 | No | | `sip.cube_mesh.w/h` | cube mesh 형상 | No | | `cube.geometry.cube_mm.w/h` | cube 크기 (mm) | **Yes** | | `cube.pe_layout.corners/pe_per_corner`| PE 부착 위치 | **Yes** | | `cube.ucie.n_connections` | UCIe PHY 분배 | **Yes** | | `cube.memory_map.hbm_mapping_mode` | HBM 분배 모드 | **Yes** | | `cube.placement` | M_CPU/SRAM 위치 | **Yes** | | `cube.memory_map.*` (위 제외) | HBM 용량 / BW | No | | `*.links.*.bw_gbs` | edge bandwidth | No | | `*.attrs.overhead_ns` | 컴포넌트 latency | No | 위 표가 D2 의 `_compute_source_hash` 입력과 일치. mesh 재생성이 필요한 변경은 `cube_mesh.yaml` 의 source_hash 가 자동 invalidate. ## Alternatives Considered ### A1. mesh 를 별도 캐시 파일 없이 매 compile 시 재생성 기각. 같은 spec 으로 여러 번 호출되는 케이스 (CLI run, probe, test) 마다 mesh 생성 비용을 다시 지불. 또한 사람이 mesh 를 inspect 할 수 있는 artifact 가 사라짐. ### A2. mesh 생성을 builder.py 에 합치기 기각 (현재). 305 줄 짜리 자체 알고리즘이며, mesh layout 의 결정 (placement- driven router attachment, HBM exclusion zone) 이 builder 의 일반적인 node/edge 생성 책임과 다르다. 분리 유지가 단일 책임 원칙에 더 부합. ### A3. placement coordinate 를 cube 좌표 (col/row) 로 표현 기각. mm 단위 좌표가 시각화 측 (visualizer) 과 mesh layout 측 (nearest- router 산출) 양쪽에서 일관되게 쓰인다. cube 좌표는 router grid 가 결정 되기 전까지는 정의되지 않으므로 placement 입력에 부적절. ### A4. view projection 을 lazy 하게 생성 기각 (현재). 4 개 view 의 생성 비용이 작고 (보통 < 100 ms), eager 생성이 `TopologyGraph` 를 통한 single source of truth 를 보장. ### A5. visualizer 출력 형식을 SVG 외 (PNG/PDF) 도 기각. SVG 가 vector + 텍스트 검색 가능 + 브라우저 직접 렌더가 가능한 가장 유연한 형식. PNG 변환이 필요하면 별도 도구 (rsvg-convert 등) 로 후처리. ## Consequences - ADR-0006 의 high-level intent 가 D1–D7 로 구체화되어, topology 변경 영향을 D8 표로 빠르게 가늠 가능. - D3 의 mesh auto-layout 알고리즘이 ADR-level 에서 굳어져, 추후 새 PE 부착 패턴 (예: HBM 의 6-zone 분할) 도입 시 어느 단계가 영향받는지 명확. - D5 의 edge kind 목록과 D7 의 view 구조가 명시되어, 새 component 종류 추가 시 (builder + router + visualizer) 어디까지 손대야 하는지 PR reviewer 가 한눈에 파악 가능. - D2 의 source_hash invalidation 규칙이 명시되어, cube_mesh.yaml 이 stale 하게 남는 경우 (예: bw 값만 바꿨을 때) 가 정상 동작임이 분명.