A streaming memcpy that trashes the L1 hurts everything that runs after it. The NTL hints let software declare, one instruction ahead, that the next instruction’s memory accesses have no reusable temporal locality — and let the cache hierarchy act accordingly, or ignore it entirely (they’re HINTs, encoded as ADDs to x0).
| Encoding | “No temporal locality within…” | |
|---|---|---|
| NTL.P1 | ADD x0, x0, x2 | …the innermost private cache. A uarch might skip L1 allocation, or allocate straight to LRU state. |
| NTL.PALL | ADD x0, x0, x3 | …any private cache. Also the recommended prefix for contended synchronization variables. |
| NTL.S1 | ADD x0, x0, x4 | …the innermost shared cache. |
| NTL.ALL | ADD x0, x0, x5 | …any cache at all — pure streaming; typically allocate nowhere. |
Portable software picks by working-set size (bigger set → outer variant); implementation-tuned software picks by the specific level it wants to protect. Table 9’s matrix distills to a rule: each variant denies locality at one boundary of your hierarchy — e.g. with private L1/L2 and shared L3: P1→L1, PALL→L2, S1/ALL→L3; and to explicitly avoid allocating in L1/L2/L3, tuned code uses P1/PALL/ALL respectively.
Interactions
- Scope: every memory-access instruction — base loads/stores, A, F/D/Q, C, V, even HLV/HSV — except the Zicbom cache ops. Composing with CBO.ZERO is meaningful (NTL.PALL + CBO.ZERO ≈ “zero this line, allocated only at L3”).
- Prefetches invert: NTL + Zicbop prefetch = prefetch into a cache outer than the named level (no prefix = innermost; NTL.ALL + prefetch may stage in a memory-controller buffer).
- Traps: if the target instruction traps, don’t let the hint leak onto the handler’s first instruction. An interrupt landing between hint and target simply loses the hint — semantics unchanged; cores that fuse NTL with the following access may prefer taking the interrupt before the NTL.
- LR/SC: being ADDs, NTLs don’t void the forward-progress guarantee — useful exactly for prefixing the LR or SC itself.
Hardware Designer Notes
Cheapest useful implementation: one 2-bit “NTL pending” register set by the four encodings at decode, consumed (and cleared) by the next memory op, feeding the cache’s allocate/replace policy port. Clear it on traps. Everything beyond that is cache-policy tuning, invisible to the architecture.
Minimal Linux-boot hart MUST
- Nothing — full conformance is decoding the four (eight with C) encodings as the ADD no-ops they already are
MAY simplify / trap-and-emulate
- Fuse NTL with the following memory op into one internal op carrying an allocation-policy field — the intended high-performance implementation
- Honor the hint selectively (e.g. respect it on AMOs and plain loads/stores, ignore it on LR/SC lines held exclusive in L1)
- Map variants to replacement-policy tweaks (insert-at-LRU) rather than allocation bypass — both are conformant
Check yourself — NTL hints
1.Why are the NTL hints encoded as ADD x0, x0, x2..x5 rather than new opcodes?
2.On a system with private L1/L2 and a shared L3, where does Table 9 recommend NTL.PALL steer allocation, and what should implementation-tuned software use to avoid the L3?
3.NTL.P1 is followed by PREFETCH.R (Zicbop). What does the combination mean?