# ADR-0037: Forwarding 컴포넌트 (forwarding_v1) ## Status Accepted ## Context 시뮬레이션 그래프에는 순전히 패브릭 통과를 모델링하기 위해 존재하는 노드 위치들이 많다 — NOC 메시 라우터, 스위치, UCIe 프로토콜 엔드포인트, IO 칩렛 io_noc, transit 큐브. 이들은 공통 패턴을 공유한다: 메시지를 수신하고, 컴포넌트별 오버헤드(헤더 디코드 + 라우팅 결정 시간을 모델링)를 적용하며, 사전 계산된 경로를 따라 다음 홉으로 전달한다. 본 ADR은 이러한 transit 노드에 대한 계약을 정의한다: 웜홀 cut-through 의미로 flit 인지 포워딩을 처리하는 단일 컴포넌트 타입(`TransitComponent`)이며, 각 인스턴스가 수행하는 개념적 역할에 따라 여러 impl 이름 아래에 사용된다. ## Decision ### D1. 역할 Forwarding 컴포넌트(`TransitComponent` 클래스)는 시뮬레이션 그래프의 **상태 없는 transit 노드**이다. 메시지가 물리적으로 통과하지만 의미론적 처리는 일어나지 않는 모든 패브릭 위치를 모델링한다. 통과당 컴포넌트는: 1. `in_port`에서 들어오는 Transaction 또는 Flit을 읽는다. 2. 설정된 컴포넌트별 오버헤드(`overhead_ns`)를 적용한다. 멀티 flit 페이로드라도 **Transaction당 한 번** 적용된다(D2 참조). 3. Transaction의 사전 계산된 `path`를 따라 다음 홉을 조회한다. 4. 해당 `out_port`로 전달한다; 종단 노드(다음 홉 없음)에서는 `is_last` flit이 도착하면 `txn.done`을 시그널한다. 본 컴포넌트는 다음을 하지 **않는다**: - 라우팅 결정 — 경로는 라우터에 의해 사전 계산된다(ADR-0002 / ADR-0017 D2). Forwarding은 홉별 단계만 실행한다. - 와이어 전파나 대역폭 점유 모델링 — 컴포넌트 사이의 별도 와이어 프로세스가 처리한다(ADR-0015 D2). - 주소 해석 — AddressResolver가 담당한다(ADR-0017 D9). - 완료 집계 — 종단 엔드포인트(IO_CPU, M_CPU, HBM_CTRL)가 담당한다. ### D2. First-flit 오버헤드 모델 (헤더 디코드) Transaction별 `overhead_ns`는 첫 flit 도착 시 **정확히 한 번** 적용된다: - `_txn_decoded: set[int]`이 본 노드에서 이미 오버헤드를 지불한 Transaction들을 추적한다. - 어떤 Transaction의 첫 flit 도착 시: `yield self.run(env, msg.txn.nbytes)` — 오버헤드를 지불한다. - 동일 Transaction의 후속 flit들은 오버헤드를 건너뛰고 추가 지연 없이 파이프라인 통과한다. - `is_last` flit 시: Transaction을 `_txn_decoded`에서 제거한다. 이는 실제 HW의 동작 — 헤더 디코드와 라우팅 결정이 첫 flit에서 한 번 일어나고, 이후 페이로드 flit들은 같은 경로로 스트리밍되는(웜홀 cut-through) — 을 모델링한다. 멀티 홉 파이프라이닝은 자연스럽게 발현된다 — 각 홉이 자신의 first-flit 오버헤드를 추가하지만, 첫 flit 이후의 flit들은 이미 첫 flit이 통과한 어떤 홉에서도 오버헤드를 다시 지불하지 않는다. ### D3. 직렬 워커 포워딩 (순서 보존) 본 컴포넌트의 워커는 `_inbox`에서 flit을 소비하여 도착 순서대로 직렬 포워딩하는 단일 SimPy 프로세스이다. 컴포넌트는 flit마다 `env.process(...)`를 spawn하지 **않는다**. 근거: 첫 flit이 `overhead_ns`에서 yield하는 동안 후속 flit이 병렬 프로세스에서 실행되면, 후속 flit이 첫 flit을 추월할 수 있다. 이는 순서가 어긋난 전달을 낳고, `is_last` flit이 첫 flit보다 먼저 목적지에 도착하게 하여 — 트랜잭션의 완료 의미와 다운스트림의 flit 인덱스 기반 처리 모두를 손상시킨다. ### D4. 경로 기반 next-hop 라우팅 라우팅은 Forwarding 컴포넌트의 관심사가 **아니다**. Transaction은 라우터에 의해 사전 계산된 `path`(ADR-0002 / ADR-0017 D2)와 함께 도착한다. 컴포넌트는 단지 자신의 경로상 위치를 찾아 `path[index + 1]`로 전달한다: ```python def _next_hop_in_path(self, txn): my_id = self.node.id path = txn.path for i, n in enumerate(path): if n == my_id and i + 1 < len(path): return path[i + 1] return None ``` `next_hop`이 발견되고 `out_ports`에 존재하면 flit이 전달된다. 그렇지 않으면(종단 노드) `is_last` flit이 도착할 때 `txn.done.succeed()`가 호출된다. ### D5. Flit 인지 모드와 Non-Flit 폴백 `_FLIT_AWARE = True`는 본 컴포넌트가 베이스 클래스의 `_fan_in` 내 flit 재조립 로직에서 제외되도록 한다. Flit은 재조립 없이 `_inbox`에 직접 놓이며, 이는 워커 루프(D2, D3)에서의 per-flit 처리를 가능케 한다. Non-Flit 메시지 — 0바이트 제어 Transaction이나 그 외 청크화되지 않는 페이로드 — 는 `env.process`를 통해 베이스 클래스의 레거시 `_forward_txn` 경로로 빠진다. 이는 flit 수준 처리의 이득이 없는 제어 평면 트래픽에 대한 하위 호환성을 보존한다. ### D6. 베이스 클래스에서의 멀티 스트림 병합 라우터에서의 멀티 스트림 FIFO 병합은 Forwarding이 아닌 베이스 클래스의 책임이다. 베이스 클래스의 `_fan_in`은 `in_port`마다 하나의 프로세스를 spawn한다; 모두가 공유된 단일 `_inbox`에 push한다. 따라서 서로 다른 업스트림 스트림의 flit들은 `_inbox`의 FIFO 순서로 flit 단위에서 인터리브된다. Forwarding 워커는 단지 `_inbox`를 도착 순서대로 소비할 뿐이다 — 공유 inbox 위의 공정 FIFO로 라우터별 멀티 플로우 중재를 올바르게 모델링한다. ### D7. 여러 impl 이름 아래의 단일 구현 단일 `TransitComponent` 클래스가 `components.yaml`에서 네 가지 impl 이름으로 등록된다: - `builtin.forwarding` — 범용 forwarding (예: `io_noc`, `noc_router`, UCIe conn 브리지) - `builtin.switch` — 트레이 수준 스위치 - `builtin.noc` — 큐브 수준 NOC 패브릭(레거시 싱글톤; 현재 NOC 라우터는 `builtin.forwarding`을 사용) - `builtin.ucie` — UCIe 프로토콜 엔드포인트 네 별칭 모두 동일한 동작을 갖는 동일한 클래스를 인스턴스화한다. 인스턴스별 차별화는 `attrs.overhead_ns`에만 존재한다. 별도 impl 이름이 존재하는 것은 가독성을 위한 의도 태그이자, 하위 호환을 깨지 않고 향후 분기를 허용하기 위함이다. ### D8. 설정 가능한 `overhead_ns` 단일 속성이 인스턴스별 레이턴시를 결정한다: | 사용 사이트 | impl 이름 | overhead_ns | | --- | --- | --- | | 트레이 수준 스위치 | `builtin.switch` | 5.0 | | 큐브 NOC 라우터 | `builtin.forwarding` | 2.0 | | IO 칩렛 io_noc | `builtin.forwarding` | 0.0 | | UCIe 프로토콜 엔드포인트(`ucie-{N,S,E,W}`) | `builtin.ucie` | 8.0 | | UCIe conn 브리지(`ucie-{PORT}.conn{N}`) | `builtin.forwarding` | 0.0 | 기본값은 0.0이다. 속성은 매 `run()` 호출에서 읽히므로 동적 재설정이 가능하나 현재는 사용되지 않는다. ## Consequences ### Positive - 단일 클래스가 시뮬레이션 그래프의 모든 transit 노드 역할을 처리한다 — 개체 수가 많은 컴포넌트 타입에 대한 최소 코드 표면. - Flit 인지 처리 + 직렬 워커는 per-flit 프로세스 오버헤드 없이 멀티 홉 경로 전반에 걸쳐 웜홀 의미를 보존한다. - `overhead_ns`만이 유일한 인스턴스별 튜너블이다; 라우팅, 대역폭, 주소 해석은 자체 컴포넌트/모듈에서 깨끗이 분리되어 있다. - 멀티 스트림 병합이 베이스 클래스 구조에서 자연스럽게 발현된다; 라우터 전용 로직이 공정 FIFO 중재를 중복 구현하지 않는다. - Non-Flit 폴백 경로는 모든 메시지를 flit 프레임워크로 강제하지 않고도 제어 평면 트래픽이 계속 동작하도록 한다. ### Negative - 단일 클래스가 사용 사이트의 의도를 `attrs.overhead_ns` 설정 안에 숨긴다; 어떤 impl 이름이 어떤 동작 클래스로 매핑되는지 보려면 독자가 `topology.yaml` + `components.yaml`을 참조해야 한다. - per-flit 직렬 워커는 `overhead_ns`가 크고 같은 라우터에 다수의 동시 트랜잭션이 도착할 때 병목이 된다; 현재 값(0–8 ns)에서는 무시할 만한 수준이다. ## Links - ADR-0002 (라우팅 거리 — 경로 계산) - ADR-0015 D1 (컴포넌트 포트 모델) - ADR-0015 D2 (와이어 프로세스 — 본 컴포넌트와 별개의 BW + 전파) - ADR-0015 D6 (Transit 큐브 forwarding 패턴) - ADR-0016 D1 (IO 칩렛 io_noc — 본 컴포넌트 사용) - ADR-0017 D1 (큐브 NOC 라우터 — 본 컴포넌트 사용) - ADR-0017 D6 (UCIe 분해 — `ucie-{PORT}` 인스턴스가 본 컴포넌트 사용) - ADR-0033 D1 (Flit 인지 통과, first-flit 오버헤드, 멀티 스트림 병합 의미)