Fill component-model coverage gaps surfaced by /report's G4 analysis. Each ADR documents the component's First action, latency model, and honest notes on dormant code or implementation asymmetries discovered during re-evaluation against current code. - 0038 pcie_ep: thin protocol-overhead model; ComponentBase forwarding worker as-is; named-node contract for router helpers - 0039 pe_mmu: component + utility dual role; sub-page region stopgap; D2.1 flags pipeline path missing mmu.overhead_ns timeout (asymmetric with non-pipeline; not visible at default tlb_overhead_ns=0) - 0040 pe_tcm: dual-channel BW serialization (read/write Resource cap=1); TcmRequest schema owned by TCM; timing-only (no data store) - 0041 sram: terminal scratchpad model + ResponseMsg on reverse path; D1.1 flags _worker override as currently dormant (no Transaction actually targets the SRAM node today) - 0042 tiling: pure plan-generator module, not a component; corrects the G4 misclassification; pins GEMM/Math stage sequences and epilogue scope contract Also: /report skill G3 refinement — only flag older->newer asymmetric cross-references; newer->older (e.g., 0034-0037 citing infrastructure ADRs) are expected one-way and no longer reported. Bilingual pair verifier (tools/verify_adr_lang_pairs.py) passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.3 KiB
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은 다음 두 종류의 트래픽을 받는다:
- PE_FETCH_STORE → PE_TCM의
TcmRequest— TCM ↔ Register File 전송 시, PE_FETCH_STORE가 TCM의 BW로 직렬화된 access latency를 받아오기 위해 짧은 sideband 요청을 보낸다 (direction = "read"또는"write",nbytes,done이벤트). - 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 과의 책임 경계가 견고해진다.