# ADR-0038: PCIE_EP Component Model

## Status

Accepted (2026-05-20).

ADR-0035 (M_CPU), ADR-0036 (IO_CPU), ADR-0037 (Forwarding)
와 같은 결의 컴포넌트-레벨 ADR.

## First action (제일 처음에 하는 일)

`_inbox`에서 Transaction을 한 건 꺼내 `_forward_txn`을 통해 `run()`을 호출하고,
그 안에서 `node.attrs["overhead_ns"]` 만큼 `env.timeout()`으로 PCIe 프로토콜
처리 지연을 적용한다. 그 이후 시점부터는 일반 `ComponentBase` 워커가 정의한
forwarding 규약을 따른다 (다음 hop이 있으면 `out_ports[next_hop].put(...)`,
아니면 `drain_ns`를 소비하고 `txn.done.succeed()`).

즉, **PCIE_EP의 첫 번째 일은 "PCIe 프로토콜 오버헤드를 시간으로 표현하는 것"**
하나뿐이고, 라우팅·페이로드 변환·MMIO 디코딩 같은 부가 의사결정은 하지 않는다.

## Context

PCIE_EP는 토폴로지 그래프에서 **호스트와 디바이스 사이의 단방향 경계 포인트**
역할을 한다. 빌더 (`topology/builder.py`)는 SIP마다 IO chiplet 인스턴스를
생성하고 그 안에 `pcie_ep`, `io_cpu`, `io_noc`을 둔 뒤, 외부 호스트 측의 cross-SIP
switch와 `pcie_ep` 사이에 양방향 엣지를 깐다:

- `switch → pcie_ep`: host → device 트래픽 (MemoryWrite, MemoryRead, KernelLaunch).
- `pcie_ep → switch`: device-side outbound (예: cross-SIP IPCQ 토큰).

IOChiplet 내부적으로는 `pcie_ep ↔ io_noc` 양방향 엣지가 깔리고, 그 다음 hop이
`io_cpu`나 cube 측 hbm_ctrl 경로로 분기된다 (ADR-0036 IO_CPU 모델 참고).
라우터·리졸버는 SPEC R7이 요구하는 "PCIE_EP는 메모리 오퍼레이션을 위한
엔드포인트"라는 계약을 이미 인지하고 있어, `find_pcie_ep(sip)`,
`find_memory_path(pcie_ep, dst_node)` 같은 helper가 PCIE_EP를 시작점으로 한다.

문제는 이 모든 의존 관계가 builder/router/resolver 쪽에는 있으나, **PCIE_EP
자신의 내부 모델을 명시하는 ADR이 없다**는 것이다. 결과적으로:

- "PCIE_EP는 어떤 latency를 모델링하나?"가 코드를 읽어야만 답이 나온다.
- 다른 컴포넌트(IO_CPU=ADR-0036, M_CPU=ADR-0035)와의 비대칭이 발생한다.
- 향후 PCIe link-layer 모델(예: TLP credit, retry)을 더 정교하게 만들지에 대한
  의사결정 근거가 흩어진다.

이 ADR은 현재의 **얇은 (thin) PCIE_EP 모델**을 명시적으로 못 박고, 그것이
의도된 단순화임을 기록한다 (ADR-0033 latency model 단순화 정책과 정렬).

## Decision

### D1. PCIE_EP는 ComponentBase의 일반 forwarding 워커를 그대로 사용한다

`PcieEpComponent`는 `ComponentBase`를 상속하며 `_worker`/`_forward_txn`을
오버라이드하지 않는다. 따라서 모든 Transaction은 다음 순서로 처리된다:

1. `_fan_in`이 들어오는 메시지(또는 Flit reassembly된 Transaction)를 `_inbox`에
   적재한다.
2. `_worker`가 `_inbox`에서 하나 꺼내 `env.process(self._forward_txn(env, txn))`로
   포크한다 (per-message 파이프라이닝).
3. `_forward_txn`이 op_log 시작 hook → `run()` 지연 → op_log 종료 hook 순서로
   호출한다.
4. `run()`은 단 한 줄: `yield env.timeout(overhead_ns)`.
5. 다음 hop이 있으면 `out_ports[next_hop].put(txn.advance())`, 없으면 (terminal로
   도착한 경우) `drain_ns`를 소비 후 `txn.done.succeed()`.

### D2. PCIE_EP의 유일한 시간 모델은 `overhead_ns`다

`node.attrs["overhead_ns"]`만 latency 파라미터로 인정한다. 코드 기본값은
`0.0`이며, `topology.yaml` 의 IOChiplet `components.pcie_ep.attrs` 가 실제 값을
지정한다 (현재 토폴로지: `overhead_ns: 5.0` ns).

별도의 BW 직렬화 자원(simpy.Resource), 큐 깊이, retry 모델은 두지 않는다.
링크-레벨 BW 직렬화는 wire-side에서 처리된다 — IOChiplet 내부는
`pcie_ep_to_noc_bw_gbs = 256.0 GB/s` 링크, 외부는 system의 `io_ep_to_switch`
링크 BW가 적용된다 (ADR-0015 port/wire 모델). PCIE_EP 컴포넌트 자체는 이
BW 회계에 관여하지 않는다.

### D3. PCIE_EP는 양방향 사용을 인지하지만, 방향에 따라 동작을 바꾸지 않는다

토폴로지 빌더가 `switch ↔ pcie_ep` 와 `pcie_ep ↔ io_noc` 양방향 엣지를 깐다.
따라서 PCIE_EP는:

- inbound (host→device): switch에서 도착한 Transaction을 io_noc 쪽으로 다음 hop
  계산을 통해 forward.
- outbound (device→host): io_noc/io_cpu에서 도착한 Transaction을 switch 쪽으로
  forward.

두 경우 모두 D1의 일반 forwarding 워커가 처리하며, 컴포넌트 코드 자체는 방향을
구분하지 않는다 (`txn.next_hop`만 따른다).

### D4. PCIE_EP는 Flit-aware가 아니다 (legacy reassembly 경로)

`_FLIT_AWARE`를 `True`로 두지 않는다. 따라서 `_fan_in`이 상류에서 chunkify된
Flit들을 부모 Transaction으로 재조립하여 `_inbox`에 넣는다 (ADR-0033 Phase 2c
점진적 rollout 정책과 정렬).

PCIE_EP가 PCIe TLP-level credit 모델을 갖도록 확장될 미래에 D4를 재평가한다.

### D5. PCIE_EP는 라우팅 helper의 **명명된 노드**다

`policy/routing/router.py`의 `find_pcie_ep(sip, io_id="io0")`,
`find_all_pcie_eps()`, `find_memory_path(pcie_ep, dst_node)`는 PCIE_EP를 메모리
경로의 시작점(또는 종점)으로 간주한다. 컴포넌트 본체는 이 helper에 어떤 정보도
제공하지 않으며, 명명 규칙(`sip{S}.{io_id}.pcie_ep`)은 토폴로지 빌더가 보장한다.

## Alternatives Considered

### A1. PCIe TLP-level 모델 (credit, retry, MPS 분할)

기각. ADR-0033이 명시한 "현재 latency 모델은 abstract overhead + BW 직렬화로
표현"이라는 단순화 원칙에 어긋난다. 호스트↔디바이스 protocol 정합성은 SPEC §5
"Non-Goals"에 의해 의도적으로 out-of-scope이다.

### A2. PCIE_EP에 자체 simpy.Resource로 inflight 제한 두기

기각. 현재 워크로드에서 호스트 트래픽은 컨텐션 병목이 아니다. 필요해지는 시점에
별도 ADR로 도입한다 (호환성 측면에서 D1은 그대로 두고 D2를 확장하는 형태).

### A3. PCIE_EP를 IO_CPU와 합치기

기각. PCIE_EP는 host-side에서 처음 만나는 protocol boundary 노드이고, IO_CPU는
디바이스-쪽 control-plane 처리 노드다 (ADR-0036). 트래픽 fan-out·command 디코딩
같은 의사결정 비용은 IO_CPU에 모이며, PCIE_EP는 link-edge overhead만 표현하는
것이 의미가 있다. 합치면 두 책임이 섞여 ADR-0007 (runtime API/sim_engine 경계)
정신에 어긋난다.

## Consequences

- PCIE_EP는 코드 라인이 거의 0인 채로 명시적인 모델 ADR을 갖게 된다 — 일관성
  ↑, 유지보수 비용 ↓.
- 향후 PCIe-level 정밀화가 필요해지면 D2/D4를 확장하는 새 ADR을 만들어
  supersede한다.
- `find_memory_path` 등 router helper가 PCIE_EP를 명명된 노드로 의존한다는
  사실이 D5에서 명시되므로, 컴포넌트 ID 명명 규칙 변경 시 영향 범위가 명확해진다.
