# ADR-0040: PE_TCM Component Model — 듀얼 채널 BW 직렬화 ## Status Accepted (2026-05-20). ADR-0014 (PE Pipeline Execution Model) 가 "PE_TCM은 BW-기반 직렬화 scratchpad memory" 라고 언급하나 (D1), TCM 컴포넌트 자체의 정확한 동작 모델을 별도로 명시한다. ## First action (제일 처음에 하는 일) `start()`가 호출되면 즉시 두 개의 `simpy.Resource(env, capacity=1)`을 만들고 `self._read_res` / `self._write_res`에 보관한다. 이 두 자원이 **읽기 채널**과 **쓰기 채널**을 각각 1-in-flight로 직렬화하는 단일 결정 포인트다. 런타임 첫 동작: `_worker`가 `_inbox`에서 메시지를 한 건 꺼내 타입 분기: - `TcmRequest` (`pe_fetch_store`에서 옴): `env.process(self._handle_tcm_request)`로 포크. 즉 **TCM의 첫 일은 "방향 (read/write)에 맞는 채널 락을 잡는 것"**. 락 획득 후 `bw > 0 and nbytes > 0` 이면 `delay_ns = nbytes / bw` 만큼 `env.timeout`, 그리고 `req.done.succeed()`. - 그 외 (Transaction): `env.process(self._forward_txn)`로 포크 (legacy fabric 통과 경로). 생성 시점에 `node.attrs["read_bw_gbs"]` / `node.attrs["write_bw_gbs"]` (default 각 `512.0 GB/s`) 를 읽어 보관해 둔다. ## Context PE 파이프라인 (ADR-0014 D1, D6) 에서 PE_TCM은 다음 두 종류의 트래픽을 받는다: 1. **PE_FETCH_STORE → PE_TCM의 `TcmRequest`** — TCM ↔ Register File 전송 시, PE_FETCH_STORE가 TCM의 BW로 직렬화된 access latency를 받아오기 위해 짧은 sideband 요청을 보낸다 (`direction = "read"` 또는 `"write"`, `nbytes`, `done` 이벤트). 2. **legacy Transaction forwarding** — 토폴로지 그래프 상에서 TCM이 통과 노드로 잡힐 가능성에 대비한 일반 forwarding 경로 (현재 critical path에서는 사용되지 않으나 보존됨). 문제: ADR-0014는 "PE_TCM은 BW-기반 직렬화"라고만 언급한다. 그러나 코드에는 명시적으로: - **읽기와 쓰기는 별도 채널이며 동시 진행 가능**, 다만 같은 방향끼리는 cap=1로 직렬화된다. - BW는 `read_bw_gbs` / `write_bw_gbs` 두 값으로 분리 설정 가능하다. - `delay_ns = nbytes / bw_gbs` 공식 (단위 환산: GB/s × ns ≈ B 라는 약식). - nbytes==0이면 BW 항을 건너뛰지만 채널 락은 잡는다. - `run()`은 `overhead_ns` (default 0.0) 만큼 yield 하나, 이는 legacy fabric 경로(Transaction forwarding)에서만 사용된다. 이 모든 사항을 별도 ADR로 못 박을 필요가 있다. 특히 "왜 read/write가 분리 채널인가" 와 "BW는 누가 결정하는가" 는 향후 누군가가 capacity=2 등으로 변경하려 할 때 명확한 근거가 필요한 항목이다. ## Decision ### D1. 듀얼 채널 — read와 write는 독립 자원 `_read_res = simpy.Resource(env, capacity=1)`, `_write_res = simpy.Resource(env, capacity=1)`. 같은 방향의 동시 요청은 자원 큐에서 직렬화되나, 다른 방향끼리는 동시에 진행 가능. 이는 실제 HW에서 TCM이 듀얼 포트 (read port + write port) 로 운용되는 모델과 정합되며, GEMM 파이프라인에서 fetch(read)와 store(write)가 시간상 겹치는 정상 케이스를 BW-직렬화 모델로 표현하기 위해 의도된 분리다. ### D2. 단일 채널의 BW 모델 — `nbytes / bw_gbs` 채널 락 획득 후, `nbytes > 0 and bw > 0`이면 `yield env.timeout(nbytes / bw_gbs)`. 단위 약식은 GB/s × ns ≈ B 로, 시뮬레이터 전체에서 사용하는 BW 공식과 동일 (ADR-0033 참고 — 시뮬레이터는 일관된 약식 단위를 사용한다). - `nbytes == 0`: BW 항은 0이지만 락은 잡혔다가 즉시 풀린다. 이 케이스가 의도된 이유: 빈 fetch/store를 보내는 plan generator가 PE_FETCH_STORE 측에서 `nbytes`만 0으로 채워 보내는 경우에도, TCM 측의 op_log / 채널 회계가 일관되게 한 번 소비된다. - `bw == 0` (config 실수): timeout 호출 자체를 skip하므로 0-time pass. 정상 세팅에서는 발생하지 않는다. ### D3. BW는 `node.attrs`의 `read_bw_gbs` / `write_bw_gbs`로 설정 기본값 `512.0 GB/s`. 토폴로지 빌더 (`topology/builder.py`) 가 `pe_template`에서 TCM을 인스턴스화할 때 해당 attrs를 전달한다. 기본값 변경은 ADR-0014 D1 또는 ADR-0033 latency model 측의 의사결정과 함께 가야 한다. ### D4. TcmRequest의 schema는 PE_TCM이 owner다 `@dataclass TcmRequest(direction: str, nbytes: int, done: simpy.Event, tag: str = "")` 는 `components/builtin/pe_tcm.py`에 정의된다. PE_FETCH_STORE는 이 dataclass를 import해서 생성·송신만 한다. 호출자 측이 schema를 정의하지 않는 이유: - BW 직렬화의 의미는 TCM 측 책임 — 어떤 필드가 직렬화 결정에 쓰이는가는 TCM이 결정한다. - `direction` 문자열을 `"read"` / `"write"` 둘로 좁히는 유효값 검증도 TCM 측에 서 담당 (`_handle_tcm_request`의 if/else 분기). ### D5. legacy Transaction forwarding 경로의 보존 `_worker`가 `TcmRequest`가 아닌 메시지를 받으면 `_forward_txn`으로 보낸다. 이때 `run()`의 `overhead_ns`가 적용된다. 현재 표준 PE 파이프라인에서는 TCM이 Transaction의 통과 노드로 잡히지 않으나, fabric 토폴로지가 향후 변경될 때를 위해 보존한다 (D1 의 사용 패턴과 직교). 이 경로는 op_log 측에서 일반 Transaction 회계로 잡히며, BW 채널 락은 잡지 않는다. ### D6. PE_TCM은 자체 데이터 저장소가 아니다 (timing only) TCM은 **시간만** 모델링한다. 실제 데이터 페이로드는 sim_engine의 별도 `memory_store` (있다면) 가 보관하고, TCM 컴포넌트는 그것을 갱신하지 않는다. PE_FETCH_STORE도 TcmRequest를 통해 BW 지연만 받아오고 실제 register 컨텐츠는 별도 경로로 다룬다 (ADR-0020 2-pass data execution 모델 — Phase 2에서 데이터 처리). ## Alternatives Considered ### A1. 단일 채널 (capacity=2 의 read+write 공유) 기각. fetch(read)와 store(write)가 시간상 겹치는 정상 케이스를 인공적으로 직렬화하게 되어 PE 파이프라인의 BW upper bound가 잘못 모델링된다. ### A2. 채널 capacity > 1 (예: 2-banked TCM) 기각. 현재 HW 모델은 단일 bank 가정. 멀티-bank로 확장하고 싶다면 별도 ADR이 필요하며, 그때 D1을 supersede한다. 지금 단계에서 capacity를 늘리면 BW upper bound는 그대로인데 명목상의 직렬화만 헐거워져 실제 모델 정확도 ↓. ### A3. BW 공식을 `nbytes / bw + overhead_ns`로 일반화 기각. `overhead_ns`는 D5의 legacy forwarding 경로에만 사용한다. fetch/store critical path에 추가 overhead가 필요해지면, 그것은 TCM이 아니라 PE_FETCH_STORE 측 `run()` 또는 register-file access 모델에 두는 것이 책임 경계 측면에서 더 적절하다. ## Consequences - TCM의 BW 회계가 ADR-level에서 굳어지므로, GEMM/Math sweep의 op_log 해석 시 "왜 fetch와 store가 동시에 진행되었나" / "왜 같은 방향만 직렬화되나" 같은 질문이 빠르게 D1으로 해결된다. - 미래의 멀티-bank TCM이나 read/write 비대칭 BW 모델 변경 시 영향 범위가 명확해진다 (D1·D2·D3 중 어디를 수정하는지). - TCM이 데이터 저장소가 아니라는 점(D6)이 명시되어, ADR-0020 2-pass execution 과의 책임 경계가 견고해진다.