# 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 오버헤드, 멀티 스트림 병합
  의미)
