Skip to content

cpu: keep valid R10000-family cache lines in each CPU's own view of memory - #448

Draft
jamesbraid wants to merge 5 commits into
techomancer:mainfrom
jamesbraid:r10k-cache-line-staging
Draft

jamesbraid wants to merge 5 commits into
techomancer:mainfrom
jamesbraid:r10k-cache-line-staging

Conversation

@jamesbraid

@jamesbraid jamesbraid commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

The R10000-family PROMs use the caches as working memory before DRAM is set up: they make lines valid with Index Store Tag and Index Store Data, then load, store and fetch through them while the uncached path sees memory. The O2 keeps POST's stack in page 0's lines while it tests page 0 uncached; the Fuel and Tezro keep their stack in primary data lines and run code from primary instruction lines, and the Tezro's processors hold the same addresses at once with different contents. Without this the Fuel and Tezro PROMs don't boot, and the O2 relies on CRIME's side page.

Each CPU reads memory through a window of its own, a copy of ppmem's window. A valid line makes its page private in that CPU's view; the uncached path, DMA and the other CPUs keep seeing memory. Nothing changes on a load, store or fetch, in the decode cache or in compiled code: those bodies are byte for byte main's. Only the CACHE instruction path changes.

Changes

  • ppmem: each CPU gets its own window; pages move in and out of it at CACHE instructions only; a remap keeps held pages. The unused bitmap-sink plumbing goes.
  • jitv2: a page any CPU holds is not compiled. ppmem puts one mark on the page's generation counter and moves it with the page on a remap; the check is at compile time only.
  • Primary data and secondary lines: a tag that makes a line valid takes its page and writes the data in; the page goes with its last line. On the R5000, which has no secondary cache and no Index Store Data, a primary data tag store holds the line and the page starts as a copy of memory; its POST keeps its stack this way.
  • Primary instruction lines (only the Fuel and Tezro use these): words go in as the instructions they predecode. Index Load Data (I) answers a refill from the held secondary line until Index Store Data (I) writes the set.
  • O2: CRIME's stack page and its tag-store hook go.

Approximations

  • Pages, not lines, are private (the host page size). No coherence with other CPUs or DMA.
  • Nothing is written back; every staged line on the O2, Octane, Fuel and Tezro ends by Index Store Tag.
  • The manuals fix only that TagHi is a secondary doubleword's high word and TagLo its low word. TagLo at the lower address is inferred from PROM 6.210 and the IP35 boot-flash PROM, whose staged code matches its image only that way.
  • The predecode table is inferred from PROM 6.210's words. Refill is assumed for a set a held secondary line maps to. An instruction outside the eleven classes comes back in a form only this model reads, which takes classes 0x78-0x7f.

Each is stated in code and in clanker/cpu/cache-lines-live-in-the-cpus-view.md.

Testing

  • Boots to login, halt -p clean, no lines held at login: O2 with the default R5000 (jitv2 83 s, interpreter 167 s), R10000 (90 s) and R12000 (87 s); Octane, 1 CPU (94 s); Indy, R4400 (134 s) and R5000 (141 s); IP28. On the Fuel and Tezro branches with this series swapped in: Fuel to login with ALL DIAGS PASSED, network and rsh; Tezro to the BASEIO monitor with 1 CPU and to login with 2.
  • cargo test --features jitv2 --lib release and debug (3030 passed), cargo test --workspace --no-run --features jitv2 at every commit.

Performance

Against main, iris-bench run --quick --deterministic, n=5 per side, interleaved: host instructions within ±0.14% on every build (default, jitv2, jitv2 dispatch off, lightning; R4400 and R10000) with matching guest instruction counts and 40/40 checksums. Cycles and wall time moved both ways by several percent on a host at load 150-220.

iris-bench ab, 20 rounds, concurrent, R10000, on a quieter host: lightning suite −1.0% [−2.1%, +0.2%], host instructions −0.1%. Release with dispatch off flagged +4.9% [+3.8%, +5.9%] against main at equal host instructions (+0.1%), and +3.6% [−1.7%, +4.3%] against the previous version of this PR. The same three release binaries pinned to one core, perf stat, 3 rounds each: cycles −2.3% against main and −1.2% against the previous version, with indirect mispredicts +7.7%. I read that as code layout and core placement rather than added work, but it is not settled.

Size: the series is +776/−276 model and +441/−127 test lines. The jitv2 and primary instruction commits are +134 and +234, down from +223 and +291.

@jamesbraid
jamesbraid force-pushed the r10k-cache-line-staging branch 2 times, most recently from 4eba0c8 to 2d6c223 Compare November 8, 2026 02:53
@techomancer

Copy link
Copy Markdown
Owner

o2 prom does it for stack. so we invented crazy bus device to fake two views of the same physical address so ram sizing doesnt destroy stuff in "cache". that allowed us to remove branch from cached reads and get rid of the shadow cache transition.

@techomancer

Copy link
Copy Markdown
Owner

is this necessary? we do the prom stack hack using bus devices? it works on o2 and octane?

@jamesbraid

Copy link
Copy Markdown
Collaborator Author

originally tezro and fuel needed it, but that was before I understood the iris approach more. I'm trying with the bus devices for tezro/fuel to see if this can be dropped

A CPU's cache, its decode cache and its compiled code reach RAM through
ppmem's window, and every CPU and Physical's direct paths share that one
window. The R10000-family PROMs use the caches as working memory before
memory is set up: lines made valid with Index Store Tag and Index Store
Data hold a stack or code while uncached accesses, which bypass the
caches (NEC VR10000 user's manual p. 75), see memory. The Tezro's
processors hold lines for the same addresses with different contents at
once. One shared window has no room for any of that.

PpMemSpace::add_cached_view gives a CPU a window of its own. Every
mapping laid in the shared window is laid in each view as well, and a
view publishes its bitmap to that CPU's inline fields, so the CPU reads
its view through the same base pointer and bitmap test it used before.
hold_line_page turns a page of a view into private memory, starting as
a copy of memory, and release_line_page lays memory back when the last
line goes, dropping what the page held. A remap saves and restores the
held pages around it. A page with no memory behind it is refused. One
in a region the window maps only in part takes the whole region in
that view, reading zeros where nothing is mapped, because the region
bitmap is all a CPU tests: the O2's POST holds lines for page 0 before
CRIME maps the rest of its region. Nothing holds a page yet. The views
are not coherent with each other or with DMA.

The generic machine, the Octane, the O2 and the IP27 machine give each
CPU a view now. Physical and the devices that read the window directly
keep the shared one. Nothing on a load, store or fetch changes. The CPU
no longer takes over the shared window's bitmap, so set_bitmap_sink and
add_bitmap_sink lose their last callers and go.
The Fuel's and the Tezro's PROMs run code from primary instruction lines
they make valid with Index Store Tag and Index Store Data, and on iris
those words differ from memory once memory is set up there (measured on
the model, not on hardware). Compiles read memory and compiled code is
shared by every CPU, so code compiled from memory would run in place of
the lines. Upstream iris has no machine that runs code from a held page
yet: the O2 keeps only a stack in lines and runs POST uncached, and the
Octane's lines name no memory. This is for the Fuel and the Tezro.

The first CPU to hold a page now puts GEN_HELD on the page's generation
counter, which retires what was compiled there, and the last release
takes it off with an ordinary bump. A compile that finds the mark
publishes no code for that generation and denies every offset, so
arrivals interpret the page from the CPU's own view without asking
again. The first compile after the release allows every offset once
more. The release lowers the counter, so publish treats any change of
generation as new bytes, not only a rise. ppmem owns the mark, so it is
one mark however many CPUs hold the page, and a remap moves it to the
counter that backs the page afterwards.

All of it is at compile time. Dispatch and compiled code are unchanged,
and every other page stays compiled. Without the denial, four Tezro
processors asking for the same held PROM page took 65-77 s to the
BASEIO monitor against 38-42 s with it (an earlier version of this
change, jitv2 loop-profile builds).
…view

The R10000-family PROMs keep stacks and data in cache lines they make
valid with Index Store Tag and Index Store Data. The O2's POST tests
page 0 uncached while its stack lives in secondary and primary data
lines for the same page, the Fuel's and Tezro's stacks sit in primary
data lines for 0x100000-0x107fff, and on the Tezro every processor holds
those lines at once with its own contents. The shadow cache kept the
tags and data arrays for diagnostics only, so a cached access read
memory.

A tag change that makes a primary data or secondary line valid now holds
its page in the CPU's cached view and writes the line's data array into
it, since Index Store Tag writes the tag array alone (NEC VR5000/VR10000
instruction manual pp. 24-25). Loads, stores, fetches, the decode cache
and compiled code read the view as before and change not at all.
Uncached accesses bypass the caches (NEC VR10000 user's manual p. 75)
and keep seeing memory, as do other masters, since the views are not
coherent. A tag that names another line, or none, ends the line, and the
page goes with its last line. Index Store Data into a held line writes
through. The tags stay the only line state, plus a held bit.

The O2's R5000 POST keeps its stack the same way, in primary data lines
for page 0 made valid by Index Store Tag (D) with TagLo 0xc0, and tests
page 0 uncached meanwhile. An R5000 primary data tag has the R10000's
PTag and PState fields (NEC VR5000 user's manual U11761EJ6V0UM p. 133),
and a store hit on a write-back line makes no bus request (p. 243), so
a valid one holds its page too. The R5000 has no Index Store Data, so
the page starts as memory. Its instruction lines hold nothing: with no
Index Store Data to fill one, it could hold only what memory holds.

Every line the shipped PROMs stage ends by Index Store Tag, none is
written back, and none is read back with Index Load Data while it holds
a page, so the writeback and invalidate operations stay as they were.
The R12000 zeroing a primary data tag at Index Writeback Invalidate ends
a line like any other tag change.

A secondary doubleword puts TagLo at the lower address. The manuals fix
only that TagHi is the doubleword's high word and TagLo its low word
(MIPS R10000 user's manual p. 201, NEC VR10000 user's manual p. 289);
which address each pairs with is inferred, not established, from shipped
PROMs. PROM 6.210 on the Fuel and the Tezro stages code with the word at
A in TagLo and the word at A + 4 in TagHi, 990 pairs in a Tezro boot,
and its staged code equals its image only this way. The IP35 boot-flash
PROM's staging pairs them the same way: simulated with the other order,
378 of its 562 staged fetches break, every trampoline fetch among them.
With the other order the Tezro's PROM falls back to running its cache
test uncached, which took twice as long to reach the BASEIO monitor.

Limits: a page, not a line, is private, so the rest of a held page shows
memory as it was when the page was taken, and nothing is coherent with
other CPUs or DMA. A snapshot taken while lines hold pages is refused at
restore. Lines that name no memory, like the Octane's tag test patterns,
hold nothing. `l2 probe 0` lists the pages held.
Upstream iris has no machine that needs this yet. The Fuel's and the
Tezro's PROM 6.210 copies itself to memory from code it stages in
primary instruction lines with Index Store Tag and Index Store Data (I)
and runs through cached xkphys, and once memory is configured, memory
at those addresses holds something else. Without the lines the PROM
hangs on iris at "Copying PROM code to memory ..." (measured on the
model).

A valid primary instruction line now holds its page in the CPU's view
like a data line, with each word written there as the instruction it
predecodes. A primary instruction word is 36 bits of predecoded
instruction (NEC VR10000 user's manual p. 288, established). Neither
manual gives the layout, so it is inferred from the words PROM 6.210
stores, and only its eleven sampled classes decode. A word of any other
class leaves memory's word in the page, where hardware would run it.
Index Store Data (I) into a held line writes through.

The PROM gets the predecoded form of other code from the hardware: it
runs a secondary line it staged, which refills a primary line, and
reads the words back with Index Load Data (I) to stage them itself
(measured on the model). The model does not refill on a fetch, so Index
Load Data (I) in a set a held secondary line maps to now answers with
that line's words predecoded, as if it had been refilled, until Index
Store Data (I) writes the set. A guest's own pattern in the set reads
back as written (MIPS R10000 user's manual pp. 201-202). An instruction
outside the eleven classes comes back in a form only this model reads,
bits 35:32 set over the instruction, which takes predecode classes
0x78-0x7f.

Nothing on a fetch, load or store, and nothing in the decode cache,
changes. A page held only for code also answers cached loads with the
code's bytes, where hardware would read memory.
The O2 kept POST's stack in bank 0 and, while a primary data tag for
page 0 stood, pointed the decode table at a page of its own for the
uncached path, switched by a hook on every Index Store Tag (D). The
CPU's cached view holds those lines now, with the bank, the decode
table and the rendering engine on memory meanwhile, so the side page,
its hook in the shadow cache, the slot-1 mapping it needed at reset
and MipsCache::set_pd_tag_store_hook go.

A snapshot taken during POST by an older iris carries the side page but
no cache lines to rebuild the stack from, and is refused.
@jamesbraid
jamesbraid force-pushed the r10k-cache-line-staging branch from 1945e4f to 1d20edb Compare November 8, 2026 23:27
@jamesbraid jamesbraid changed the title cpu: serve loads, stores and fetches from lines the guest stages with CACHE index operations cpu: keep valid R10000-family cache lines in each CPU's own view of memory Nov 8, 2026
@jamesbraid
jamesbraid force-pushed the r10k-cache-line-staging branch 3 times, most recently from c3a9033 to 517a9af Compare November 9, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants