# 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 명명 규칙 변경 시 영향 범위가 명확해진다.