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>
11 KiB
ADR-0039: PE_MMU Component Model — 컴포넌트 + 유틸리티 이중 역할
Status
Accepted (2026-05-20).
ADR-0011 (PA/VA/LA address model) 의 VA 모델에서 "PE_MMU가 VA→PA 변환"이라고만 선언되어 있는데, PE_MMU 컴포넌트 자신의 동작 모델을 별도로 못 박는 ADR.
First action (제일 처음에 하는 일)
생성 시점에 node.attrs["page_size"] (default 2 MiB) 와
node.attrs["tlb_overhead_ns"] (default 0.0) 를 읽어 내부 PeMMU 객체
(policy.address.pe_mmu.PeMMU) 를 단 한 번 인스턴스화한다. 이 객체가 페이지
테이블·서브페이지 region 리스트·TLB 오버헤드의 단일 보유자(single owner)이다.
런타임에서의 첫 동작은 두 갈래로 갈린다:
- 컴포넌트 경로 (inbox 소비):
_worker가_inbox에서 Transaction을 한 건 꺼내, 그request가MmuMapMsg이면 각 엔트리에 대해self._mmu.map(va, pa, size)를 호출하고txn.done.succeed().MmuUnmapMsg이면unmap(va, size), 그 외 타입이면 표준_forward_txn으로 떨군다. 즉 MMU의 첫 일은 "map/unmap 명령을 페이지 테이블에 반영하는 것". - 유틸리티 경로 (직접 호출): PE_DMA / PE_GEMM 같은 동일 PE 내부 엔진이
pe_mmu.mmu.translate(va)를 직접 호출한다. 이 경로에서는 SimPy 이벤트가 발생하지 않으며, 호출자가 (overhead_ns > 0인 경우) 본인 process에서yield env.timeout(mmu.overhead_ns)를 처리한다.
Context
ADR-0011은 PA/VA/LA 세 가지 주소 모델을 정의하고 "VA 모델 = PE_MMU를 통한 변환"
이라고만 합의했다. 그러나 코드 상의 PeMmuComponent는 두 가지 상호 보완적인
역할을 동시에 수행한다:
- 토폴로지 그래프 상의 컴포넌트: cube NoC에서
MmuMapMsg/MmuUnmapMsgsideband 메시지를 수신하여 페이지 테이블을 갱신한다. - PE-로컬 유틸리티 객체: 동일 PE의 PE_DMA / PE_GEMM이 latency 0으로 (혹은
호출자 측에서
overhead_ns만 부담하면서) 직접translate(va)를 호출한다.
이 두 역할을 모두 다루는 ADR이 없어 다음 모호함이 발생한다:
- "왜 MMU 변환에 SimPy 이벤트가 안 잡히나?" (실제로는 호출자 측에서 잡고 있음)
- 서브페이지 region 모델은 무엇이고, 왜 그 모델인가? (코드 docstring에는 있으나
ADR이 없음 —
project_mmu_subpage_stopgap라는 memory note 참조만 존재) - map/unmap 메시지가 누구로부터 와서 언제까지 갱신되어야 하는가 (ordering 계약)?
또한 PeMMU.map() 은 "later append, last-write-wins (역방향 탐색)" 의미를 갖는데,
이것은 단순한 단일-PA 페이지 테이블 모델로는 표현 불가능한 DPPolicy의 서브페이지
샤딩 (예: 128B 페이로드 × 4KB 페이지) 시나리오를 위해 의도적으로 추가된
stopgap이다. 진짜 HW MMU와는 다른 단순화임을 ADR로 못 박을 필요가 있다.
Decision
D1. 이중 역할의 명시 — 컴포넌트와 유틸리티
PeMmuComponent는 단일 클래스 안에서 다음 두 인터페이스를 노출한다:
- 컴포넌트 인터페이스:
_inbox소비,_worker루프 (MMU sideband 메시지 처리). - 유틸리티 인터페이스:
pe_mmu.mmu속성으로 underlyingPeMMU객체를 노출 — PE_DMA / PE_GEMM이 이 객체를 직접 들고translate()를 호출.
후자는 layer skip이 아니다: PE 내부는 ADR-0007이 정의한 "components" 레이어 하나 안의 sibling 관계이고, 같은 PE prefix에서 가져온 PE_MMU 객체에 대한 직접 호출은 cross-layer가 아니다. cross-layer 위반은 runtime API / sim_engine / components 경계를 넘는 경우에만 적용된다.
D2. Latency 모델: translate()는 순수 함수, overhead는 호출자 책임
PeMMU.translate()는 순수 함수이며 SimPy yield를 하지 않는다. 호출자(PE 엔진)
가 변환 후 if self._mmu.overhead_ns > 0: yield env.timeout(self._mmu.overhead_ns)
를 자기 process에서 발생시킨다.
이유: PE 엔진의 SimPy process는 이미 자체 record_start / record_end (op_log) hook을 들고 있어 timing을 일관되게 잡을 수 있다. MMU가 별도의 process를 만들면 PE 엔진의 처리 흐름을 두 갈래로 쪼개 op_log/pipeline overlap 의미가 흐려진다.
D2.1. 현재 구현의 비대칭 — pipeline vs non-pipeline (Known asymmetry)
본 ADR 작성 시점의 pe_dma.py 구현은 두 호출 경로에서 overhead 처리가 다르다:
- non-pipeline (
handle_command):translate()직후if self._mmu.overhead_ns > 0: yield env.timeout(self._mmu.overhead_ns)를 발생시킨다. - pipeline (
_do_pipeline_dma):translate()만 호출하고 overhead timeout을 생략한다 — 함수 주석에 "same logic as non-pipeline path"라고 적혀 있으나 실제로는 일치하지 않는다.
기본 토폴로지에서 tlb_overhead_ns = 0.0 이라 이 차이는 timing에 직접 드러나지
않으나, tlb_overhead_ns > 0 으로 설정한 시뮬레이션에서는 pipeline 경로의
GEMM/Math 가 non-pipeline 동일 워크로드 대비 MMU overhead 만큼 빠르게 측정된다.
D2의 계약은 "모든 호출자가 overhead를 책임진다" 이며, pipeline 경로의 누락은 의도된 설계가 아니라 구현 비일관성이다. ADR-0014 D6 (pipeline self-routing) 이 이 overhead를 면제한다고 명시한 부분은 없다.
조치 선택지(별도 Phase 1/2 제안 필요):
- (a)
_do_pipeline_dma에서도if mmu.overhead_ns > 0: yield env.timeout(...)를 추가하여 D2 계약과 일치시킨다 — 권장. - (b) D2 계약을 "non-pipeline 경로에만 적용" 으로 좁히고, pipeline 경로의 면제를 ADR-0014 D6 갱신과 함께 정당화한다 — overhead 의미가 약해지므로 비권장.
본 ADR은 (a) 를 권장하며, accept 전 또는 직후의 별도 작은 변경으로 이를 교정하는 것을 가정한다.
D3. 페이지 테이블 구조 — 서브페이지 region 리스트 (stopgap)
self._table: dict[vpn, list[(start_in_page, end_in_page, pa_at_offset_zero)]]
구조로 한 페이지 안에 여러 disjoint region을 보유할 수 있다.
map(va, pa, size): 페이지를 가로지르면 region들을 append한다.translate(va): VPN으로 region 리스트를 가져온 후, 역방향으로 순회하며 처음 매칭되는 region을 채택 (last-write-wins).unmap(va, size): extent가 unmap 범위에 완전히 포함된 region만 제거한다. 경계가 어긋난 부분 overlap은 그대로 남기며, 매핑 호출자는 mapping과 동일한 경계로 unmap할 책임을 진다.
이는 진짜 HW MMU와는 다른 시뮬레이터 stopgap임을 ADR-0011 VA 모델 보강 요소로 명시한다. DPPolicy 서브페이지 샤딩 시 last-write-wins overwrite로 인한 조용한 미스라우팅을 방지하기 위함이다 (메모리 노트: project_mmu_subpage_stopgap).
D4. PageFault는 PA fallback 신호다
매핑이 없는 VA로 translate()가 호출되면 PageFault가 발생한다. PE_DMA는 이
예외를 잡아 원본 주소를 PA로 그대로 사용한다 (ADR-0011의 PA fallback 호환
경로). 따라서 PageFault는 에러가 아닌 "VA 매핑 부재 시 PA로 해석한다"는 신호다.
이 호환 경로는 ADR-0011이 합의한 PA-only 모드와의 후방 호환을 유지하기 위한 의도된 동작이다.
D5. MMU sideband 메시지의 수신 계약
MmuMapMsg / MmuUnmapMsg는 fabric을 통해 PE_MMU 컴포넌트의 _inbox로
도달한다 (R10이 명시하는 "MMU map 설치는 fabric latency를 따른다"). 메시지
schema는 runtime API (runtime_api/kernel.py) 가 정의하며, 현재 형식:
MmuMapMsg.entries: tuple[dict, ...]— 각 dict는{"va": int, "pa": int, "size": int}키를 갖는다.MmuUnmapMsg.entries: tuple[dict, ...]— 각 dict는{"va": int, "size": int}키를 갖는다.
PE_MMU 측 수신 처리:
_worker가_inbox.get()에서 메시지 한 건을 꺼낸다.hasattr(msg, "request")로 Transaction wrapper 인지 확인.isinstance(msg.request, MmuMapMsg)이면 각 entry 에 대해self._mmu.map(va=e["va"], pa=e["pa"], size=e["size"]).isinstance(msg.request, MmuUnmapMsg)이면 각 entry 에 대해self._mmu.unmap(va=e["va"], size=e["size"]).- 둘 다
msg.done.succeed()로 완료 통지.
외부 호출자(runtime API 측)가 done을 await하면 "매핑이 디바이스에 설치된
시점"이 SimPy 시간으로 보장된다 — 이 wait이 ADR-0011이 요구하는 "MMU map
installation incurs measured fabric latency" 의 실현이다.
이 ADR은 sideband 메시지의 sender 와 fan-out 정책을 정의하지 않는다 — 그것은 runtime API 책임이다. 본 ADR은 PE_MMU 측 수신 계약만 명시한다.
D6. 비-MMU Transaction은 일반 forwarding으로 위임
_worker가 inbox에서 꺼낸 메시지의 request가 MmuMapMsg / MmuUnmapMsg가
아닌 경우 (또는 request 속성이 없는 경우) _forward_txn으로 떨군다. 이는
미래에 PE_MMU가 cube-internal NOC 상의 통과 노드로 사용될 가능성을 차단하지
않기 위함이다 (현재는 그런 통과 트래픽이 없으나, 토폴로지 변경에 대해 안전).
Alternatives Considered
A1. translate()를 SimPy generator로 만들기
기각. D2에서 설명한 대로, PE 엔진의 op_log/pipeline overlap 의미가 흐려진다. 호출자 측에서 timeout을 일으키는 현재 패턴이 op_log 회계와 일치한다.
A2. 서브페이지 region 리스트 대신 페이지 크기 자체를 작게 하기 (예: 128B)
기각. 페이지 테이블 메모리 폭발과 cube-wide map message 크기 폭발을 초래한다. DPPolicy 샤딩이 128B를 요구한다 해도 그 외 대다수 매핑은 2MiB 단위이므로, 페이지 크기를 작게 잡는 것은 평균 비용이 비대해진다.
A3. PE_MMU를 컴포넌트가 아닌 PE_CPU의 내장 헬퍼로만 두기
기각. ADR-0011이 요구하는 "fabric을 통해 측정된 latency로 MMU map 설치" (MmuMapMsg 경로)를 표현하려면 토폴로지 그래프 상의 노드여야 한다. 또한 cube NoC visualizer에서 PE_MMU가 노드로 보여야 디버깅·진단이 일관된다.
Consequences
- PE_MMU의 이중 역할(컴포넌트 + 유틸리티)이 ADR-level에서 정당화되어, 미래의 refactor 압박 (둘 중 하나로 통일하라)에 대한 논거가 생긴다.
- 서브페이지 region 모델이 시뮬레이터 stopgap임을 ADR이 명시 — 이후 LA 모델 (ADR-0011) 도입 시 이 stopgap 제거 가능성을 평가하는 기준이 된다.
translate()가 yield하지 않는다는 계약이 ADR로 굳어지므로, 향후 누군가 "MMU에 자체 timeout을 넣자"는 제안을 할 때 D2를 근거로 거절할 수 있다.- PA fallback (D4) 이 정상 흐름임이 명시되어, PageFault를 에러로 오인하여 방어 로직을 추가하는 일을 막는다.