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
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,65 @@ hashes are given where a change is easiest to understand by reading the commit.
the cost follows what the guest wrote. Reflinking is unchanged. On Windows the
copy is marked sparse first.

### R10000 TLB widths

- **Full 44-bit user addresses.** On the R10000 (Octane, IP28, and an O2 set to
`r10000`) EntryHi, the TLB match and the user-segment check keep VA bits 43:40;
they stopped at bit 39 before, so an address above 1 TiB took an Address Error.
The R4400 and R5000 are unchanged.
- **FrameMask (CP0 register 21)** on the R10000 clears the EntryLo bits it
names, 33:18, on every TLB write, so a kernel that keeps software flags there
gets the physical address it meant. The register read as zero and ignored
writes before. The R4400 and R5000 have none.
- **EntryLo** on the R10000 keeps its 28-bit PFN (bits 33:6, a 40-bit physical
address) and the uncached attribute in 63:62; it kept 30 bits before. The
attribute is stored and read back, not sent to the bus.
- **TLB writes on the R10000** store zeros in the PFN bits below the page size,
so TLBR reads them back as zero, as the part does; on 16 KB pages that is
EntryLo bits 7:6. Translation is unchanged. The PROM's `ide` `tlb` test
failed 320 compares on this. The manual documents it for the R10000; the
R12000, R14000 and R16000 do the same as a model choice. The R4400 and
R5000 still keep the whole PFN.
- **TLB refill vector on the R10000** follows the address space that missed:
a user address takes the XTLB vector only with UX set, xsseg with SX, and
kernel addresses with KX. A 64-bit kernel touching a 32-bit user address now
refills through 0x000, as the part does, not 0x080. The R4400 and R5000 still
choose by the current mode.

### Refused uncached stores on the R10000 family

- On the R10000 family (Octane, IP28, O2), an uncached store that nothing answers now
completes instead of raising a Data Bus Error, as the processor does. iris prints
"Unsupported uncached write ... (posted, completes)" for the first four such
stores and then at each power of two. Loads, instruction fetches, the R4400 and
the R5000 are unchanged.

### Partial LWR on the R10000

- On the R10000 (Octane, IP28, and an O2 with the R10000), an `LWR` that loads fewer
than four bytes now keeps bits 63:32 of the destination instead of sign-extending,
as the reference results for the part show. R4400 and R5000 machines are unchanged.
The JIT matches the interpreter, and the code-cache key changes only for the
machines that have the behaviour.

### R10000 cache operations

- **Index Load Data and Index Store Data** on the R10000's primary caches keep their
data (they read back zero before). The secondary cache's data slot is the address
shifted right by 3 instead of 4: one slot per doubleword.
- **Secondary-cache tags** on the R10000 family read the reserved bits (TagLo 9, 12, 13 and
TagHi 30:4) as zero; the MRU bit and the other tag fields are kept.

### R12000, R14000 and R16000

- `[machine] cpu` and `--cpu` take `r12000`, `r14000`, `r16000` (revision 2.2,
the 800 MHz part) and `r16000_700` (revision 2.1, the 700 MHz part). The Octane
takes the R10000, R12000 and R14000 (defaulting to 195, 360 and 600 MHz), the
O2 the R5000, R10000 and R12000 (400 MHz for the R12000), the IP28 the R10000.
The R16000 has no machine yet; `iris-bench` is its only user. CP0 Config now
reports the part's own secondary-cache size, where every part read 1 MB, and
each R16000 presents its own reset word.

### `IRIS_GUI_GL` works in iris-gui

- The GUI's GL capture path (`IRIS_GUI_GL=1`, or `[debug] gui_gl_capture`) never
Expand Down Expand Up @@ -513,6 +572,9 @@ hashes are given where a change is easiest to understand by reading the commit.

### SGI O2 (IP32)

- **The O2's memory alias holds for pages with high PTE bits set.** A mapped page
whose address had a bit set above bit 31 missed the alias of RAM at
0x8000_0000 and reached the PCI window instead.
- **The O2 runs on the shadow cache from reset.** The full cache model, and
the switch to and from it during POST and reboots, are gone
(`src/ip32_handover.rs`), and with them a branch on every cached load, store
Expand Down
5 changes: 3 additions & 2 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, R14000 or R16000 (two parts), chosen at runtime (the IP28 takes the R10000;
the Octane the R10000, R12000 or R14000; the O2 the R5000, R10000 or R12000).
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 @@ -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
4 changes: 2 additions & 2 deletions HELP.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,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", "r10000", "r12000", "r14000", "r16000" or "r16000_700" (--cpu); IP28: r10000, Octane: r10000/r12000/r14000, O2: r5000/r10000/r12000
# cpus = 2 # Octane only: a second processor (docs/Octane/multiprocessor.md)

[graphics]
Expand Down Expand Up @@ -787,7 +787,7 @@ 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, r14000, r16000 or r16000_700
--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)

Expand Down
7 changes: 6 additions & 1 deletion bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ for each cell, because a cell's features live in its binary.
| `r5000-jitv2` | `jitv2` | `r5000` |
| `r4400-lightning` | `lightning` | `r4400` |
| `r4400-jitv2-lightning` | `jitv2,lightning` | `r4400` |
| `r10000-interp`, `r12000-interp`, `r14000-interp`, `r16000-interp`, `r16000-700-interp` | (default) | `r10000`, `r12000`, `r14000`, `r16000`, `r16000_700` |

The R10000 family always runs the shadow caches, so these share the default
build. No shipped machine takes the R16000 yet; the Indy and Indigo2
(non-IP28) profiles build any part, and the cell relies on that.

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 Expand Up @@ -397,7 +402,7 @@ device on real hardware, so results come back over serial with CP0 Count as the
time base; the header says so.

**On which SGI machines?** The suite identifies any MIPS CPU by name from PRId
— R4000, R4400, R4600, R4700, R5000, R8000, R10000, R12000, R14000, RM5200,
— R4000, R4400, R4600, R4700, R5000, R8000, R10000, R12000, R14000, R16000, RM5200,
RM7000 — and an implementation it has no name for prints as `MIPS-imp-0xNN`
rather than refusing. That is safe to do because **nothing here is
CPU-specific**: the kernels are ordinary compiled MIPS III, and `golden.h` is
Expand Down
51 changes: 33 additions & 18 deletions bench/harness/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,34 +220,50 @@ static void run_one(const struct bench *b)

/*
* The CPU by name, from PRId alone — so this is right on any MIPS machine,
* not only on the two the emulator models. The revision split on imp 0x04 is
* the standard one: an R4000 and an R4400 report the same implementation and
* are distinguished by major revision.
* not only on the two the emulator models. Three implementation numbers are
* shared by two parts that differ in major revision: an R4000 and an R4400
* (imp 0x04, the R4400 from major 4), and an R14000 and an R16000 (imp 0x0f,
* the R16000 from major 3, the same split as Linux's cpu-probe.c). The table
* is searched in order, so the more specific row comes first.
*
* An imp we do not have a name for still runs; it just prints as "MIPS imp
* 0xNN", which is more use to whoever is holding that machine than "unknown".
*/
static const struct {
u32 imp;
u32 min_major; /* the row applies from this major revision up */
const char *name;
} cpu_names[] = {
{ IMP_R4400, 4, "R4400" },
{ IMP_R4000, 0, "R4000" },
{ IMP_R10000, 0, "R10000" },
{ IMP_R4300, 0, "R4300" },
{ IMP_R12000, 0, "R12000" },
{ IMP_R14000, 3, "R16000" },
{ IMP_R14000, 0, "R14000" },
{ IMP_R8000, 0, "R8000" },
{ IMP_R4600, 0, "R4600" },
{ IMP_R4700, 0, "R4700" },
{ IMP_R4650, 0, "R4650" },
{ IMP_R5000, 0, "R5000" },
{ IMP_RM7000, 0, "RM7000" },
{ IMP_RM5200, 0, "RM5200" },
};

static const char *cpu_name(void)
{
#if defined(BENCH_HOST)
return "host";
#else
static char other[20];
unsigned row;

switch (PRID_IMP(cpu_prid)) {
case IMP_R4000: return PRID_REV_MAJOR(cpu_prid) >= 4 ? "R4400" : "R4000";
case IMP_R10000: return "R10000";
case IMP_R4300: return "R4300";
case IMP_R12000: return "R12000";
case IMP_R14000: return "R14000";
case IMP_R8000: return "R8000";
case IMP_R4600: return "R4600";
case IMP_R4700: return "R4700";
case IMP_R4650: return "R4650";
case IMP_R5000: return "R5000";
case IMP_RM7000: return "RM7000";
case IMP_RM5200: return "RM5200";
default: {
for (row = 0; row < sizeof cpu_names / sizeof cpu_names[0]; row++) {
if (cpu_names[row].imp == PRID_IMP(cpu_prid) &&
PRID_REV_MAJOR(cpu_prid) >= cpu_names[row].min_major)
return cpu_names[row].name;
}
{
/* No snprintf here — freestanding. Build "MIPS-imp-0xNN" by hand.
*
* No spaces, deliberately: this string is emitted as the value of
Expand All @@ -265,7 +281,6 @@ static const char *cpu_name(void)
other[i] = 0;
return other;
}
}
#endif
}

Expand Down
4 changes: 2 additions & 2 deletions bench/prebuilt/PROVENANCE
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
# CI compares it and fails if the image was not refreshed alongside a
# source change. The ELF's own hash is informational only — two correct
# builds of identical source differ, because -g records the build path.
sources sha256 b2aa6db09ad6f42b68754e4199c66e0802e8cbc9f3c886e6a2c92b1f3502bf67
image sha256 21dc4eade5b3326afcf77199b405767a56775168c0682e65c9ba5134253b857a 308876 bytes
sources sha256 0e8ab96acd3b21308342b5b6dce317c0dc70f57fcaf7f9c5333b18a98cbd0ecc
image sha256 14e47c0c85aa4e153eb7352f6d0017d12c96e6b80df26edeb8663e9db7b541cb 300876 bytes
inputs 32 files
Binary file modified bench/prebuilt/irisbench.elf
Binary file not shown.
7 changes: 6 additions & 1 deletion cpu-tests/harness/iris.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,16 @@
* IP22 Indigo2 R4400, R4600, R8000, R10000
* IP32 O2 R5000, RM5200, RM7000, R10000, R12000
* IP30 Octane R10000, R12000, R14000
* IP27/IP35 Origin R10000, R12000, R14000
* IP27 Origin R10000, R12000, R14000
* IP35 Origin, Fuel R14000, R16000
*
* R4000 and R4400 share imp 0x04 and are told apart by revision — major >= 4
* is an R4400, which is the same rule IRIX and Linux use, and is why IRIS
* reports PRId 0x0440.
*
* The R16000 has no imp of its own: it reports the R14000's 0x0f and is told
* apart by revision, major >= 3 (Linux's cpu-probe.c tests major > 2 under
* PRID_IMP_R14000).
*/
#define IMP_R4000 0x04 /* R4400 too — see PRID_REV_MAJOR */
#define IMP_R4400 0x04
Expand Down
14 changes: 9 additions & 5 deletions docs/o2-ip32.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
The O2 is SGI's unified-memory workstation: CRIME (the memory controller and
rendering engine), MACE (the I/O chip: PCI, Ethernet, serial ports, PS/2,
audio, the clocks) and GBE (the display engine, which scans the frame buffer
out of main memory). IRIS emulates it with an R10000 or an R5000, chosen at run
time like the other machines.
out of main memory). IRIS emulates it with an R5000, R10000 or R12000, chosen at
run time like the other machines.

It runs IRIX 6.5 (tested with 6.5.22) to the desktop, with networking, audio,
SCSI disks and CD-ROMs, X and OpenGL through the CRIME rendering engine, and
Expand All @@ -28,7 +28,7 @@ serial_log = "serial.log"

[machine]
profile = "o2_ip32"
cpu = "r10000" # or "r5000"
cpu = "r10000" # or "r5000" or "r12000"

[o2]
control_port = 2293
Expand Down Expand Up @@ -65,14 +65,18 @@ in the file to silence the note.

## CPU, memory and clock

- **R10000 and R5000** both run the shadow cache model that IP28 uses, from
- **R5000, R10000 and R12000** all run the shadow cache model that IP28 uses, from
reset: loads and stores go straight to memory. The PROM tests physical page
0 uncached while its stack sits in cache tags for that same page, so while
those tags stand the bus answers for the page from a separate 4 KB page.
- Linux supports only R5000-class O2s.
- `[clock] fixed_mhz` is CP0 Count, 90 MHz unless set; the PROM measures and
reports twice it as the CPU clock. R10000 O2s shipped at 175 and 195 MHz
(87.5 and 97.5), R5000s at 180 to 300.
(87.5 and 97.5), R5000s at 180 to 300. The R12000 (revision 3.5, 2 MB secondary
cache) shipped at 400 MHz; it defaults to 200 MHz Count (400 MHz). Its
secondary cache has 64-byte lines like the R10000's, because the PROM fails
with 128-byte ones. The 270 and 300 MHz revision 2.3 part (1 MB) is not
offered.
- `banks` is the kit in each of the four DIMM pairs from A: 32, 64, 128 or
256 MB each, largest first, up to 1 GB. The default `[128, 128]` is 256 MB.
IRIX boots with any of them, from a single 32 MB kit up. NetBSD's 32-bit
Expand Down
8 changes: 5 additions & 3 deletions docs/octane-ip30.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ overlay = true
```

Paths are relative to the directory iris runs in. Every key is listed in
[FEATURES.md](../FEATURES.md). `machine.cpu` must be `r10000`. `[clock]
fixed_mhz` sets CP0 Count; unset it is 97.5 MHz, half of the R10000's
195 MHz pipeline clock.
[FEATURES.md](../FEATURES.md). `machine.cpu` is `r10000` (revision 2.7),
`r12000` (revision 3.5, 2 MB secondary cache) or `r14000` (revision 2.4, 2 MB).
`[clock] fixed_mhz` sets CP0 Count, half the pipeline clock. Unset it is
97.5 MHz for the 195 MHz R10000, 180 MHz for a 360 MHz R12000 and 300 MHz for a
600 MHz R14000.

## PROM and flash

Expand Down
19 changes: 17 additions & 2 deletions src/bin/iris_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,13 @@ const CELLS: &[Cell] = &[
Cell { name: "r5000-jitv2", features: "jitv2", cpu: "r5000", expect_cpu: "R5000" },
Cell { name: "r4400-lightning", features: "lightning", cpu: "r4400", expect_cpu: "R4400" },
Cell { name: "r4400-jitv2-lightning", features: "jitv2,lightning", cpu: "r4400", expect_cpu: "R4400" },
// The R10000 family. It always runs the shadow caches, so the default
// build covers it.
Cell { name: "r10000-interp", features: "", cpu: "r10000", expect_cpu: "R10000" },
Cell { name: "r12000-interp", features: "", cpu: "r12000", expect_cpu: "R12000" },
Cell { name: "r14000-interp", features: "", cpu: "r14000", expect_cpu: "R14000" },
Cell { name: "r16000-interp", features: "", cpu: "r16000", expect_cpu: "R16000" },
Cell { name: "r16000-700-interp", features: "", cpu: "r16000_700", expect_cpu: "R16000" },
];

fn build_cell(cell: &Cell, root: &Path, force: bool) -> Result<PathBuf, String> {
Expand Down Expand Up @@ -1077,12 +1084,20 @@ mod tests {
#[test]
fn every_cell_name_is_unique_and_maps_to_a_cpu() {
for (i, a) in CELLS.iter().enumerate() {
assert!(a.expect_cpu == "R4400" || a.expect_cpu == "R5000");
assert!(["R4400", "R5000", "R10000", "R12000", "R14000", "R16000"].contains(&a.expect_cpu));
// `expect_cpu` is what the `matrix` guard compares the guest's
// `#machine cpu=` line against, so it must describe the `--cpu`
// this cell actually passes. CPU is a runtime flag (see `Cell::cpu`),
// not a cargo feature, so the two agreeing is the whole contract.
let want = match a.cpu { "r4400" => "R4400", "r5000" => "R5000", c => panic!("{}: unknown --cpu {}", a.name, c) };
let want = match a.cpu {
"r4400" => "R4400",
"r5000" => "R5000",
"r10000" => "R10000",
"r12000" => "R12000",
"r14000" => "R14000",
"r16000" | "r16000_700" => "R16000",
c => panic!("{}: unknown --cpu {}", a.name, c),
};
assert_eq!(a.expect_cpu, want, "{}", a.name);
for b in CELLS.iter().skip(i + 1) { assert_ne!(a.name, b.name); }
}
Expand Down
Loading
Loading