Repository navigation
cpu: add the R12000, R14000 and R16000 as selectable models - #405
Draft
jamesbraid wants to merge 11 commits into
Draft
jamesbraid wants to merge 11 commits into
jamesbraid wants to merge 11 commits into
Conversation
EntryHi, the TLB match and the xuseg bounds check all stopped at VA bit 39, but the R10000 family implements 44 bits: VPN2 is 43:13 and the user segment is 16 TiB (R10000 UM pp. 219, 288). A 64-bit kernel can put an n64 user stack above 40 bits (0x0fff_ffff_aee4, for one). EntryHi dropped VA[43:40] and such an address took an Address Error. The width now comes from the part's VA_BITS in each place: the EntryHi write mask, the TLB's 64-bit compare (Tlb::set_va_bits, installed by the executor beside set_cache_attrs), the xuseg bounds check, the micro-TLB's cacheable segment test and the EntryHi value a TLB exception loads. The mask was spelled three times and is now one const fn, mips_tlb::vpn2_mask. All of it is a const of the CPU model or set once at construction; the TLB lookup is unchanged. This reverses a comment in the executor (b7d2a18) that said widening the EntryHi mask breaks the IP28 and Octane kernels. Those two machines and an O2 set to the R10000 now see 44 bits. The R4400 and R5000 (Indy, Indigo2, O2 with an R5000) stay at 40. One test maps a page at 0x123_4000_0000 on the R10000 and the R4400: the R10000 keeps EntryHi and reads the page, the R4400 masks it and takes Address Error.
A PROM's cache walk reads and writes the primary caches with Index Load Data and Index Store Data. Only the secondary cache had a data array, so an Index Load Data on a primary cache always returned zero and an Index Store Data was dropped. Give the primary caches data arrays and mask each store to its diagnostic field (R10000 UM pp. 262, 271-272): 36 data bits and 1 parity bit for the instruction cache, 32 and 4 for the data cache, 64 and 10 for the secondary. An Index Store Data to the secondary cache writes a quadword, so the other doubleword becomes zero and takes the same check bits (p. 91). A mapped primary Index Load Data or Index Store Data still takes TLB Refill and Invalid, so CACHE now translates the operand for those two; the other index operations still do not. The secondary data slot is now the address >> 3 where it was >> 4, so the walk gets one slot per doubleword instead of two doublewords sharing one. This affects the Octane, the IP28 and an O2 with an R10000. Tests cover a primary round trip and the quadword store. Index Store Data is a diagnostic walk's operation, so it runs out of line in a cold function, and CACHE tests the primary-cache pair against a bit mask. Written inline they grew cache_op and exec_cache in every CPU model and moved the interpreter's hot loops, which cost sys/llsc about 2% on a ship build.
Index Store Tag stored all 64 bits of a secondary-cache tag, so a walking-1s test over the tag read back bits the R10000 defines as reserved. The manual says they return zero (R10000 UM p. 270, figure 11-31). Keep MRU, the physical tag, state, virtual index and ECC, which assembled is 0x8000_000f_ffff_cdff, and drop the rest. The mask is CpuModel::SECONDARY_TAG_MASK, all ones except on the R10000 family. MRU stays because the PROM writes it as TagHi bit 31 and reads it back. The later parts share the tag layout (the R12000 manual agrees; the R14000 and R16000 are taken to match), and a PROM's tag test on the R14000 expects the reserved bits to read as zero. Until the R12000 and later models exist here, the family is just the R10000. Tests: an all-ones store on the R10000 and R10000 O2 reads back the mask, an R4400 control keeps TagLo bits 12 and 13, and the walking-1s round trip expects the reserved bits as zero.
The Octane shipped with an R12000 or R14000 and the O2 with an R12000,
but `cpu` took only r4400, r5000 and r10000. An Octane hinv could not
show the later parts, and CP0 Config reported 1 MB of secondary cache
for every R10000-family part, so a 2 MB model printed "1 Mbyte".
Add r12000, r14000 and r16000. They share the R10000's programming
model (44-bit virtual, 40-bit physical, the R10000 cache operations)
through model::r10k_family. Config SS is now log2(secondary size /
512 KB). The Octane's R14000 is the R12000's 2 MB type with its PRId
set at run time, as the Octane R10000 already does, so only three new
executors are compiled, not four.
Octane r10000, r12000, r14000 (195, 360, 600 MHz by default)
O2 r5000, r10000, r12000 (400 MHz; 64-byte secondary lines,
because with 128 the O2 PROM panics before its banner)
IP28 r10000 only
There are two R16000 parts, r16000 (revision 2.2, 800 MHz, PRId 0x0f42)
and r16000_700 (revision 2.1, 700 MHz, PRId 0x0f41). Both have a 4 MB
secondary cache and present their own Config reset word; the 700 MHz part
is the 800 MHz type with PRId and that word set at run time, as the Octane
R14000 sets its PRId, so it adds no executor. The 800 MHz, 8 MB part
would need a third executor, as the cache size is a type parameter, and is
left out. The R16000 has no machine yet. The Indy and Indigo2 profiles
build any part, and iris-bench uses that for r16000 and r16000-700
cells. The bench harness names an implementation 0x0f CPU an R16000 above
major revision 2, and the prebuilt guest image is refreshed for it.
On the R12000 and later, Index Writeback Invalidate (D) also clears the
line's primary data tag, as Index Load Tag then shows; the R10000 keeps it.
The new parts also keep the upper word on a partial LWR, as the R10000 does:
it is implementation-defined, and keeping it matches what the IRIX CPU
instruction test expects and the qemu-sgi-origami model, so for the R12000
and later it is a model choice.
R10000 hot path: +0.09% host instructions per guest instruction on
iris-bench, inside the ~1% noise (an R4400 control moved +0.86%).
FrameMask (CP0 register 21) is new with the R10000. Bits 15:0 clear EntryLo bits 33:18 as a TLB write passes them on, and the rest of the register reads as zero (R10000 UM v2.0 p. 260). iris had no register 21: mfc0 read zero and mtc0 was dropped, so a kernel that sets FrameMask to keep software flags out of the physical address got no effect. The core holds the register; the executor sets which bits are writable, all 16 on the R10000 and none on the R4400 and R5000. TLBWI and TLBWR pass the masked EntryLo values, and TLBR returns the masked entry. The register is saved with the rest of CP0 and loads as zero from an older save. CpuModel::R10K_FAMILY says a part is in the R10000 family; for now that is the R10000. Nothing changes while FrameMask is zero, its reset value, and the write path runs only on a TLB write. Tests write FrameMask on the R10000 and the R4400, map a page and check the physical address and the readback; a second writes with TLBWR as well as TLBWI and reads the entry back with TLBR.
In 64-bit mode a partial LWR (offset 0 to 2) sign-extended the merged word on every CPU. The manual calls the upper word of a partial LWR implementation specific (NEC U12754E pp. 125-126), and the reference results observed for the R10000 keep the old bits 63:32 at all three partial offsets. With sign extension a test that compares those results miscompares. The interpreter and the JIT now merge the old upper word on the R10000; a full word (offset 3) still sign-extends. The upper word is kept only when 64-bit operations are enabled for the current mode, matching QEMU; otherwise it sign-extends. The later parts are not covered, since nothing shows what they do. The choice is a JitConsts field set from CpuModel::PARTIAL_LWR_KEEPS_UPPER, so compile workers see it. Whether 64-bit operations are on is a byte in the core that the executor sets wherever it re-derives the privilege state, so a compiled LWR spends a byte load and a compare on it rather than decoding Status; the merge runs on the whole register and needs no more work than the old sign-extending sequence. The code-cache fingerprint gains lwr_upper=1 only when it is set, so the R4400 and R5000 machines keep their warm caches. Behaviour changes on the Octane, IP28 and an O2 with the R10000. The R4400 and R5000 are unchanged. Tests run LWR at every offset in the interpreter on the R4400, R5000 and R10000 in kernel, supervisor and user mode with the mode's own 64-bit bit set and clear, run the same offsets as compiled code on the R10000 and R5000 and compare with the interpreter, and check the fingerprint.
On the R10000 EntryLo holds a 28-bit PFN in bits 33:6, a 40-bit physical address, and the two-bit uncached attribute in bits 63:62; 61:34 are reserved (R10000 UM v2.0 pp. 239-240). iris kept the R4400's 30 bits, so the attribute and PFN bits 33:30 read back as zero and never reached the TLB. The core now has an EntryLo mask that the executor sets per part: the R4400 and R5000 keep 0x3FFF_FFFF, the R10000 gets 0xC000_0003_FFFF_FFFF. The TLB carries the PFN up to physical 39:12, but a TLB write clears the PFN bits below the page size, so TLBR does not return what was written to them (R10000 UM v2.0 pp. 299-300, TLBWI and TLBWR; NEC VR10000 instruction set manual U12754EJ1V0UM pp. 199-200). That is documented for the R10000; for the R12000, R14000 and R16000 no manual describes a TLB write, so they do the same as a model choice. The R4400 and R5000 keep the whole PFN. The uncached attribute is stored and read back only; it is not carried to the bus. Bits 33:32 are physical 39:38, and the Octane's guest keeps software flags there. FrameMask (previous commit) clears them on a TLB write, so the page lands at its real address and the Octane's 40-bit physical mask needs no narrowing. On the Octane, the IP28 and an O2 set to the R10000, mfc0 of EntryLo now returns bits 63:62 and PFN bits 33:30 if the guest wrote them; before they read as zero. The R4400 and R5000 are unchanged. A test checks the readback on the R10000 and the R4400, and that a page with PFN bits 33:32 set maps to physical 39:38 and is brought back by FrameMask. The Octane PROM's tlb diagnostic writes 0xa5a5a5a4 and 0x5a5a5a58 to EntryLo with 16 KB pages and expects 0xa5a5a524 and 0x5a5a5a18 back: EntryLo bits 7:6 are physical bits 13:12, inside the page. It reported 320 compare errors. A write now clears EntryLo bits PageMask >> 7; translation already ignored them, so only TLBR changes. A second test checks the diagnostic's patterns through TLBWI and TLBWR on the R10000 and the R4400, and that a 16 KB page translates the same on both.
A mapped page whose physical address has a bit set above bit 31 could reach the PCI window instead of RAM on the O2. The O2 aliases RAM at 0x8000_0000, and fold_phys tested that window on the full translated address before cutting it to the 32 bits the board decodes. IRIX keeps software bits in its page-table entries just above the PFN, so such an address missed the window, then lost the high bit in the mask and landed on the PCI range at 0x8000_0000. Cut to the decoded bits first, then apply the alias, then cut its target. The window lies within the decoded bits, so a page already inside it lands where it did. The extra AND runs only on an alias hit. Only the O2 has a physical alias; the Indy, Indigo2 and Octane are unchanged. A test maps a page at 0x1_8000_0000 with the O2's mask and window and expects RAM at 0. It reads 0x8000_0000 without the fix.
The R10000 picks the TLB refill vector from the address space the missing address lies in and that space's Status bit: user addresses follow UX, xsseg SX, and kseg3, sseg/ksseg and xkseg KX. The current mode only decides which space an address is in (R10000 UM v2.0 pp. 333-334, 342). The manual contrasts this with the R4400, which picks by the current mode's bit. iris did the R4400 thing on every model. So a 64-bit kernel (KX=1, UX=0) missing on a kuseg address went to the XTLB vector at 0x080 on the R10000, where the part uses 0x000. The Octane PROM's utlb diagnostic loads VA 0 in exactly that state and waits at the 32-bit vector. It got "Read TLB miss exception" from the XTLB vector instead and failed. On the R10000 family a refill miss now reads the region of the address, sign-extended from 32 bits in 32-bit mode, and tests UX, SX or KX. Only the miss path changes. The TLB compare width still follows the mode. The test misses in each space with each bit on the R10000, and checks the R4400 keeps choosing by mode.
The R10000 and R12000 do not shut down on a multiple match. A TLB write that would let one lookup match two entries first invalidates every entry it overlaps, the wired ones included, then writes the new entry and sets Status.TS (NEC U10278EJ4V0UM, Status register TS bit p.223 and "Avoiding TLB Conflict" p.300). iris stored the entry and left the duplicates matchable, so which one answered depended on how the lookup map happened to be rebuilt. On the two-processor Tezro the firmware flushes the TLB with 64 invalid entries on the kernel's page number, and the kernel then writes the valid global 16 MiB entry over them; a later write elsewhere left an invalid duplicate answering, and the fetch took TLB Invalid. The R14000 and R16000 get the same rule as a model choice, matching QEMU's r10k_invalidate_conflicts(); no manual for them was read. The R4400 and R5000 keep their behaviour. An overlap is two entries a lookup could match together: same region and page number under the wider of the two page masks, and equal ASIDs or either entry global. Invalid entries count. The removed entry reads back as an unwritten slot except for its PageMask, as in QEMU: the kernel TLBRs its wired sentinel entries and reuses PageMask for the next write, and a zeroed mask made it write a per-CPU mapping as 4 KiB. The cost is confined to TLB writes on these models. An entry that overlaps the new one covers a lookup-map slot the new one covers, so a write whose slots are all empty or its own skips the scan; any other write compares against the (at most 63) entries written since power-on. A PageMask that is not a run of ones, which software can write, defeats the map's ranges and turns the shortcut off. The hit path, the vmap fast path and the miss path are unchanged, and the rule is off on every other model. TLBWI and TLBWR are never compiled by jitv2, so both reach the same write and set TS in the interpreter. Tests cover the Tezro sequence, wired entries, PageMask kept on a read-back, entries that cannot match together, and TS from TLBWI and TLBWR. They fail with the rule off, and the R4400 and R5000 test fails if the rule is enabled for them.
A store that nothing answers raised a synchronous Data Bus Error with a
precise PC on every CPU. The R10000 cannot do that. It sends an uncached
store as a write request, and a write request gets no response; Bus Error
comes only from an error response to a read or upgrade request, or from
uncorrectable read data (R10000 UM pp. 104, 316). The processor never
learns that a store failed.
The R12000 manual documents the same no-response write path, and the later
parts are taken to match, so the test is the R10000 family and not the R10000
alone.
Add CpuModel::POSTS_UNCACHED_STORES, true for the R10000 family's caches. On such a
model a refused store logs and completes in write_data_impl and in the
masked-store path (SWL, SWR, SDL, SDR), which the interpreter and the
compiled code both go through. An uncached SC or SCD that is refused
completes and succeeds (rt = 1); that is a model choice, since the manual
says nothing about uncached store-conditional. Loads, instruction fetches,
cached accesses and the R4400 and R5000 keep the exception, and device
return values are untouched because BUS_ERR also tells non-CPU callers
that a request was declined.
The error goes no further. Nothing in iris raises an interrupt or sets an
error register for a store the Octane's bus refuses. The IP22/IP28 bus
already completes a write to an unanswered address, latching MC
CPU_ERROR/GIO_ERROR and raising no interrupt, so the stores that change
here are the ones a device refuses itself.
The log line stays visible ("Unsupported uncached write ... posted,
completes") but a probe loop must not flood stderr: it prints the first
four refused stores and then each power of two, with the running count.
The Octane's ide diagnostics store 0x591204 and then 0x4 to physical
0x1e130400, where nothing answers. Before, the first store took a Data Bus
Error; now both print one line and the run carries on.
Tests: the interpreter on the R10000 (full and shadow caches) and the
R5000 for SW, SWL and SC plus the load that still faults; the JIT against
the interpreter for SW and SWL on the R10000 and for the load and the
R5000 store that fault; the report schedule. cargo test --release
--features jitv2 --lib: 2422 passed, 0 failed.
jamesbraid
force-pushed
the
cpu-r1x000-models
branch
from
November 2, 2026 15:48
61577e2 to
8d1bc15
Compare
jamesbraid
marked this pull request as draft
November 2, 2026 16:29
jamesbraid
force-pushed
the
cpu-r1x000-models
branch
7 times, most recently
from
November 4, 2026 07:10
e358c29 to
eb69808
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
So you can boot the CPU your real machine had. Octane2s and O2s shipped with R12000s and R14000s, and the Fuel and Tezro need the R14000 and R16000. Last of three CPU PRs, on #404.
cpunow takesr12000,r14000,r16000(800 MHz) andr16000_700:Also:
iris-bench run --quickon the R10000 moved +0.09%, which is noise.Testing
hinvshowing the right part and clock on the Octane (R10000, R12000, R14000), O2 (R5000, R10000, R12000) and Indigo2 (R10000).cargo test --release --lib --features jitv2: 2907 passed.