1f36baa898
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>
195 lines
11 KiB
Markdown
195 lines
11 KiB
Markdown
# 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`는 두 가지 상호 보완적인
|
|
역할을 동시에 수행한다:
|
|
|
|
1. **토폴로지 그래프 상의 컴포넌트**: cube NoC에서 `MmuMapMsg` / `MmuUnmapMsg`
|
|
sideband 메시지를 수신하여 페이지 테이블을 갱신한다.
|
|
2. **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` 속성으로 underlying `PeMMU` 객체를 노출 —
|
|
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 측 수신 처리:
|
|
|
|
1. `_worker` 가 `_inbox.get()` 에서 메시지 한 건을 꺼낸다.
|
|
2. `hasattr(msg, "request")` 로 Transaction wrapper 인지 확인.
|
|
3. `isinstance(msg.request, MmuMapMsg)` 이면 각 entry 에 대해
|
|
`self._mmu.map(va=e["va"], pa=e["pa"], size=e["size"])`.
|
|
4. `isinstance(msg.request, MmuUnmapMsg)` 이면 각 entry 에 대해
|
|
`self._mmu.unmap(va=e["va"], size=e["size"])`.
|
|
5. 둘 다 `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를 에러로 오인하여
|
|
방어 로직을 추가하는 일을 막는다.
|