# 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 값만 바꿨을 때) 가 정상 동작임이 분명.
