Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ hashes are given where a change is easiest to understand by reading the commit.

## November 2026

### R10000 family fixes, R12000 and R14000

- The R10000 family has 44-bit virtual and 40-bit physical addresses, FrameMask and a 28-bit EntryLo PFN, picks the TLB refill vector by address space, invalidates overlapped TLB writes (Status.TS), and completes uncached stores nothing answers. Partial LWR keeps the upper word, and the cache diagnostics (Index Store Data, secondary tag mask, Config size) work.
- `cpu` takes the parts each machine shipped with, such as `r12000_360` on the Octane, or a family name for the machine's own. The Indy and Indigo2 R4400 is now rev 5.0 at 150 MHz, the O2 R10000 rev 3.4 at 250 MHz, and an Indy no longer takes an R10000 (the R10000 Indigo2 is the IP28). The R12000 and R14000 are new.

### Boot test before publishing

- `build.yml` can boot IRIX 5.3 to a login prompt on Linux, macOS and Windows
Expand Down
6 changes: 3 additions & 3 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ the first `[section]`, because TOML tables continue until the next header.
| Key | Default | Values and behavior |
|---|---|---|
| `machine.profile` | `indy_ip24` | indy_ip24, indigo2_ip22, indigo2_ip28, o2_ip32 (configured by `[o2]`, see [the O2 guide](docs/o2-ip32.md)), octane_ip30 (configured by `[octane]`, see [the Octane guide](docs/octane-ip30.md)). |
| `machine.cpu` | `r4400` | r4400, r5000, r10000; CPU/ISA choice is independent of build features. An O2 takes r5000 or r10000 (an r4400 is refused when the machine is built); the Octane is r10000 only. |
| `machine.cpu` | the machine's first | A family (`r4400`, `r5000`, `r10000`, `r12000`, `r14000`) or a part (`r14000_600`); independent of build features. Indy and `indigo2_ip22`: r4400, r5000; `indigo2_ip28`: r10000; O2: r5000, r10000, r12000; Octane: r10000, r12000, r14000. The first listed is the default. |
| `machine.cpus` | `1` | Processors fitted. 2 only on the Octane; see [its guide](docs/Octane/multiprocessor.md). |

### [graphics]
Expand Down Expand Up @@ -297,7 +297,7 @@ Omit `[nfs]` to disable the share; the default forward list is empty.

| Key | Default | Values and behavior |
|---|---|---|
| `clock.fixed_mhz` | `33 on IP22/IP24; 97.5 on IP28; 90 on the O2 when unset` | CP0 Count MHz; IRIX's CPU inventory and the O2's PROM report twice this rate. |
| `clock.fixed_mhz` | `half the part's clock (75 on an R4400 Indy)` | CP0 Count MHz; IRIX's CPU inventory and the O2's PROM report twice this rate. |

### [rtc_offset]

Expand Down Expand Up @@ -435,7 +435,7 @@ iris-gui does not parse this standalone option set; use its named-machine UI.
| `--validate` | Check the configuration (parse, settings, files, directories, ports) without starting the machine; exit 0 or 1. See [HELP.md](HELP.md#checking-a-configuration). |
| `--list-net-interfaces` | Print the host network interfaces libpcap can bridge onto, then exit. Requires a build with --features pcap. |
| `--no-scsi-deferred-int` | Disable deferred SCSI status interrupts (default: enabled for OpenBSD/NetBSD compatibility). |
| `--cpu` | Emulated CPU: `r4400` (default), `r5000`, or `r10000`. Overrides `[machine] cpu`. |
| `--cpu` | Emulated CPU: a family (`r4400`, `r5000`, `r10000`, `r12000`, `r14000`) or a part (`r14000_600`) the machine takes. Overrides `[machine] cpu`. |
| `--graphics` | Graphics board: `newport` (default), `xz`, `extreme`, `solidimpact`, `highimpact`, `maximpact` (or `impact:solid`, `impact:high`, `impact:max`), `v6`, `v8`, `v10`, `v12` (`vpro:v12` and so on, Octane only). |
| `--raster` | VPro: `cpu` (default) or `wgpu`, the raster unit on the host GPU (`[graphics] raster`; needs a build with the `wgpu` feature). |
| `--renderer` | What draws the window: `gl` (default) or `wgpu` (`renderer`; needs a build with the `wgpu` feature). |
Expand Down
9 changes: 5 additions & 4 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Other documents:

IRIS emulates five SGI workstations: the Indy (IP24), the Indigo2 (IP22), the
Indigo2 IMPACT (IP28), the O2 (IP32) and the Octane (IP30). The CPU is an
R4400, R5000 or R10000, chosen at runtime (the Octane takes the R10000 only).
R4400, R5000, R10000, R12000 or R14000, chosen at runtime from the parts the
machine shipped with (`src/cpu/parts.rs`).
Graphics is Newport (REX3), GR2 (XZ/Extreme) or IMPACT on the Indy and
Indigo2, GBE on the O2 and Solid IMPACT on the Octane. It boots the real PROMs
and IRIX 5.3 and 6.5 to a desktop with networking.
Expand Down Expand Up @@ -271,8 +272,8 @@ in [docs/interrupt_map.md](docs/interrupt_map.md).

### Time

CP0 Count is derived from host wall-clock time at a fixed rate (33 MHz on
IP22/IP24, 97.5 MHz on IP28; `[clock] fixed_mhz` overrides). The Compare
CP0 Count is derived from host wall-clock time at a fixed rate (half the part's
clock, `Part::count_hz`: 75 MHz on the Indy R4400, 97.5 MHz on IP28; `[clock] fixed_mhz` overrides). The Compare
interrupt and other periodic device events come from host timers
(`src/hptimer.rs`), not from counting instructions. A guest that runs faster
or slower than real hardware sees the same time passing. `--features
Expand Down Expand Up @@ -371,7 +372,7 @@ consumer thread never takes `rec`.
handler sets the PC itself, including delay slots.
- The CPU model is the `CpuModel` trait (`MIPS4`, `PRID`, `FIR`,
`TLB_ENTRIES`, ...), implemented by each cache model. The executor is
monomorphised over it, so all three CPUs are in every binary with no
monomorphised over it, so every CPU is in every binary with no
per-model branch on the hot path. `Machine::new` picks one from `[machine]
cpu`.
- With `opcodefusion` (implied by `lightning`), the interpreter executes
Expand Down
12 changes: 6 additions & 6 deletions HELP.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ host. `lightning` builds and the JIT stack (`--features jitv2,gpujit`) give
higher MIPS; the hinv MHz string stays the same.

The guest's MHz comes from the CP0 Count rate, which is fixed: Count ticks at
33 MHz on IP22/IP24 (66 MHz CPU inventory) or 97.5 MHz on IP28
(195 MHz CPU inventory), anchored to host wall-clock time. There is
half the configured part's clock (75 MHz for the 150 MHz R4400 of an Indy or
Indigo2, 97.5 MHz for the 195 MHz R10000 of an IP28), anchored to host wall-clock time. There is
no calibration or inference. Override it with `[clock] fixed_mhz` or
`--clock-fixed-mhz` if a guest needs something else.

Expand Down Expand Up @@ -630,7 +630,7 @@ scsi_deferred_int = true # needed by OpenBSD/NetBSD; see --no-scsi-deferr

[machine]
profile = "indy_ip24" # "indigo2_ip22" (--ip22), "indigo2_ip28", or "o2_ip32" (an [o2] section: docs/o2-ip32.md); --machine NAME picks any
cpu = "r4400" # "r5000" or "r10000" (--cpu); use R10000 on IP28
cpu = "r4400" # "r5000" (--cpu); IP28: "r10000", Octane: r10000/r12000/r14000, O2: r5000/r10000/r12000; or a part, "r14000_600"
# cpus = 2 # Octane only: a second processor (docs/Octane/multiprocessor.md)

[graphics]
Expand All @@ -643,7 +643,7 @@ resolution = "guest" # or "1024x768", "1280x960", "1280x1024"
# gpu_backend = "auto" # with raster = "wgpu": "vulkan", "metal", "dx12" or "gl"

[clock]
# fixed_mhz = 33 # Count MHz: default 33 on IP22/IP24, 97.5 on IP28
# fixed_mhz = 33 # Count MHz: default half the part's clock: 75 on the Indy and Indigo2 R4400, 97.5 on IP28

# [rtc_offset] # guest clock start, relative to host time (all signed)
# years = -18 # years/months step the calendar (Mar 31 -1 month = Feb 28/29)
Expand Down Expand Up @@ -801,9 +801,9 @@ Configuration
--machine <NAME> Machine to emulate (a [machine] profile name); `help` lists them
--ip22 Same as --machine indigo2_ip22
--graphics <BOARD> newport, xz, extreme, solidimpact, highimpact, maximpact, v6, v8, v10, v12
--cpu <MODEL> r4400 (default), r5000, or r10000
--cpu <MODEL> r4400 (default), r5000, r10000, r12000 or r14000, as the machine allows
--bank0..--bank3 <MB> RAM bank sizes (0/8/16/32/64/128; IP28: also 256/512)
--clock-fixed-mhz <MHZ> CP0 Count frequency (IP22/IP24: 33; IP28: 97.5)
--clock-fixed-mhz <MHZ> CP0 Count frequency (default: half the part's clock, 75 for an Indy R4400)
Storage
--scsi1/2/3/7 <FILE> Hard disk image at that SCSI ID
Expand Down
4 changes: 4 additions & 0 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ and the normal mode for speed. The result is labelled
| `r5000-jitv2` | `jitv2` | `r5000` |
| `r4400-lightning` | `lightning` | `r4400` |
| `r4400-jitv2-lightning` | `jitv2,lightning` | `r4400` |
| `r10000-interp`, `r12000-interp`, `r14000-interp` | (default) | `r10000_195`, `r12000_360`, `r14000_600` |
The R10000 family always runs the shadow caches. The suite's Indy has the test
device on, which takes any part; a product Indy takes only the R4400 and R5000.
Each build is copied to `bench/build/iris-<features>` before the next one starts —
the next `cargo build` overwrites `target/release/iris`, and a matrix that races
Expand Down
1 change: 1 addition & 0 deletions clanker/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ CPU architecture: CP0, TLB and its fast paths, caches, FPU, LL/SC, CPU models. B
| [r5000-taglo-is-the-r4400-pstate.md](cpu/r5000-taglo-is-the-r4400-pstate.md) | rule | R5000 L1 TagLo uses the R4400 PState encoding |
| [r5k-l1i-cache-bugs.md](cpu/r5k-l1i-cache-bugs.md) | open | R5000 secondary cache: no working configuration currently exists |
| [sc-succeeds-on-llbit-alone.md](cpu/sc-succeeds-on-llbit-alone.md) | rule | `SC` succeeds on the LLbit alone — `LLAddr` is not a gate |
| [staged-secondary-cache-lines.md](cpu/staged-secondary-cache-lines.md) | rule | Staged secondary-cache lines hit until an explicit op ends them; the page fast paths must be told |
| [tcache-discards-dirty-data-semantics.md](cpu/tcache-discards-dirty-data-semantics.md) | rule | tcache makes L1-D write-through, so Hit_Invalidate cannot discard |
| [tcache-l2-instr-coherency.md](cpu/tcache-l2-instr-coherency.md) | rule | tcache: L2 decoded-instruction coherency, and how it was broken |
| [tlb-c-field-uses-the-models-table.md](cpu/tlb-c-field-uses-the-models-table.md) | rule | The TLB decodes EntryLo C with the CPU model's table; C=4 read as uncached sent every Octane user access to the bus |
Expand Down
129 changes: 129 additions & 0 deletions clanker/cpu/staged-secondary-cache-lines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Staged cache lines: a hit until an explicit operation ends them

The R10000-family shadow cache (`src/cpu/mips_cache_shadow.rs`) serves a cached
load, store or fetch from a secondary line the guest staged with Index Store Tag
(S) and Index Store Data (S): a valid state in TagLo[11:10] and a tag equal to
PA[39:18] of the access. The rules are those of the R10000 cache index operations
datasheet. A guest that never stages a line pays nothing: no access is tested
for one.

## The limit: no replacement

An ordinary cached miss never fills the shadow, so it never evicts. A staged
line stays until Index Writeback Invalidate, Hit Invalidate, Hit Writeback
Invalidate, an Index Store Tag that overwrites it, or power-on. A guest that
reuses the memory without invalidating reads the stale line and its stores stay
in the line, where hardware would have evicted it, writing a dirty one back to
the tag's address.

Staged lines left at the login prompt, `l2 probe 0` in the monitor, CPU 0:
Octane (IP30 PROM, IRIX 6.5.30) 0, O2 R10000 (IRIX 6.5.30) 0. The shipped PROMs invalidate what they stage, so the machine is
back on the window paths and the JIT before IRIX runs.

## Primary instruction-cache lines

Index Store Tag (I) and Index Store Data (I) stage a primary I line the same
way: tag TagLo[31:8] = PA[35:12], TagLo[6] valid, TagHi[3:0] = PA[39:36]; index
VA[13:6], way VA[0], one word per Index Store Data at VA[13:2], 16 per block. A
cached fetch checks both ways at its VA[13:6] against its PA, before the
secondary cache. Index Invalidate (I), Hit Invalidate (I), an Index Store Tag
with the valid bit clear, and power-on end the line. Loads are not served by it.
Model choices: the compare on a fetch is inferred and a hit does not touch LRU.

The stored word is the 36-bit predecoded instruction plus even parity, not an
instruction in TagLo. A fetch hit decodes it with the layout the R10000 cache index operations datasheet's
`r10000-cache-index-ops.md` infers from a shipped PROM's table, for sampled
opcodes only (`l1i_decode`, `L1I_CLASSES`). `addiu $4,$0,2` is `0x3_4420_0004`.
- A word of a class not in the table is refused: reported once per class on
stderr, and the fetch is treated as a miss and reads the staged secondary line
if there is one, else memory. An approximation, not
hardware behavior (hardware runs the word). Chosen over a Cache Error because
memory holds the instruction a word was built from in all but a few stale
entries, so a miss executes the right instruction for nearly every staged
word, where an error stops a guest the hardware runs. The limit: a staged
entry that is stale against memory runs the memory word, not the staged one,
which is why it is reported.
- The class table is the 11 classes of the datasheet (0x00 nop, 0x08 dsll/dsll32,
0x18 or, 0x1a addi/addiu/ori/lui, 0x1e daddiu, 0x35 cache, 0x36 ld, 0x3f mtc0,
0x40 jal, 0x41 jr/j, 0x71 REGIMM), all inferred. Class, p[28:26] and, for the
SPECIAL classes, the function field identify the instruction; a known class
with anything else is refused too.
- A cached fetch that misses the primary I cache and hits a staged secondary
line refills a 64-byte primary block from it (the only refill; ordinary
fetches are not tracked). The victim is an Invalid way before a Valid one, and
way 0 when both are valid: a model choice, the sheet states no victim rule. The
words are stored in the raw encoding with even parity, the architectural word
carried in place of hardware's predecoded form. The block is tagged with the
fetch's PA, valid, so Index Load Tag/Data (I) return it and a readback stored
verbatim into another line executes.
- Index Writeback Invalidate (S), Hit Invalidate (S) and Hit Writeback
Invalidate (S) purge the primary I and D lines inside the secondary block,
found through the stored tag's physical block and its virtual index
(TagLo[8:7]): a line of the block at another VA[13:12] survives. A line the
model refilled itself is purged whatever its index, because its index is the
fetch's and the model leaves the guest's staged tag alone. Approximation: the
hardware would have recorded the fetch's index in the tag.
- Parity is checked on a fetch hit: even over the 36 bits and ECC[0]. A bad word
posts a Cache Error (CacheErr.ED) unless Status.DE or ERL is set.
- Index Load Data (I) returns the stored word and parity as stored, so a readback
stored back verbatim round-trips and executes. A line the guest never staged
and the model did not refill reads back as the shadow's contents, not as the
predecoded memory word. A word with p[35:32] = 0xf and the model's raw flag
(set by a refill, kept by a verbatim Index Store Data of a word Index Load
Data handed out) is this model's own encoding of a raw instruction in p[31:0]
(`L1I_RAW`), so a word outside the sampled set can round-trip. Model choice;
the hardware contents are unknown.

The primary data cache is not served: loads never hit a line staged in it. Its
tags and words are the diagnostic shadow, and only the inclusion purge acts on
them: a secondary invalidate clears the state and modifier of its lines, and
the writeback forms first copy a line with modifier 010 into the secondary
line, which a DirtyExclusive block then sends to memory.

## How staged lines reach the data path

No access is checked against them. While the cache holds any, it takes RAM
off its own fast paths instead (`serve_window`):

- `read`, `write` and the compare-and-swap paths stop using ppmem's window
(`window_regions`), so every cached access takes the bus path, which goes
through a `StagedBus`: staged lines answer for their addresses, memory for
the rest;
- the decode cache gets no window (`fetch_window`), so it reads every word
through a `StagedBus` too. That one also finds staged primary instruction
lines, which are indexed by virtual address: every fetch is at the PC, which
the executor hands the cache with Status (`set_cpu_registers`);
- the executor runs no compiled code (`staged_lines_changed`). The JIT's inline
loads and stores reach RAM through the micro-TLB and ppmem's window without
calling the cache, and it compiles from memory. The executor learns of the
change from `take_staged_changed`, asked after each CACHE instruction.

The last line ending undoes all three. While lines are held the whole machine
runs in the interpreter on the bus path, which is slow; the shipped PROMs end
what they stage before the kernel runs.

Dropping the executor's part fails `a_compiled_load_hits_a_staged_line_not_the_window`,
`a_compiled_store_to_a_staged_line_stays_in_the_line` or
`a_staged_line_is_fetched_as_code_instead_of_the_compiled_page`; those run under
`jitv2` and under `lightning,jitv2`.

## Inferred or absent

- The TagLo word of a staged doubleword lands at the lower address
(`STAGED_TAGLO_AT_LOWER_ADDRESS`). Inferred from software, not measured.
- A store to a staged line makes it DirtyExclusive. NEC U10278EJ4V0UM p.246
counts such requests; the datasheet does not state the transition.
- Every CACHE operand is translated first (exceptions: TLB Refill/Invalid, privilege Address Error, never TLB Modified). The secondary set, way and quadword come from the operand's physical address, which the executor passes in place of the virtual one, the primaries' from its virtual address. An operand on an uncached page is undefined on the R10000 and goes ahead as if cached.
- The datasheet clears the R10000. The R12000 and R14000 use the same
tag layout here, which is an inference.
- Not modelled: the predecode of unsampled classes, primary data cache staged
hits, ECC and check bits, the purge a cached access makes when its VA[13:12]
differs from the tag's recorded index, the BadVAddr/Context/EntryHi values after a CACHE-raised TLB exception, Status.CH, the
tag rewrite on invalidate (only the state clears), coherence with other CPUs
and DMA.
- Staged lines are saved in snapshots (`save_cache_state`). The rest of the
shadow arrays, which only diagnostics read, are not.
- The raw words Index Load Data handed out (`l1i_issued`) are bounded at 64 Ki
and not saved. A raw word read before a snapshot and stored after it is stored
without the model's flag, so it is refused and reported like a guest-built
0xf word; it never runs as raw bits.
1 change: 1 addition & 0 deletions docs/indigo2-ip28.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

IP28 pairs the Indigo2 fullhouse devices with an R10000 CPU and IMPACT graphics.
The same binary supports IP24, IP22, and IP28; no `ip28` Cargo feature is needed.
The profile takes only the R10000, and the Indy and IP22 profiles only the R4400 or R5000.
IRIX 6.5 desktop boot and a two-bank 1 GB configuration have been recorded in
the repository's validation notes. Device and graphics coverage is still evolving.

Expand Down
Loading
Loading