# `mfspr` — Move from Special-Purpose Register > **Category:** [Control / CR / SPR](../categories/control.md) · **Form:** [XFX](../forms/XFX.md) · **Opcode:** `0x7c0002a6` ## Assembler Mnemonics | Mnemonic | XML entry | Flags | Description | | --- | --- | --- | --- | | `mfspr` | `mfspr` | — | Move from Special-Purpose Register | ## Syntax ```asm mfspr [RD], [SPR] ``` ## Encoding ### `mfspr` — form `XFX` - **Opcode word:** `0x7c0002a6` - **Primary opcode (bits 0–5):** `31` - **Extended opcode:** `339` - **Synchronising:** no | Bits | Field | Meaning | | --- | --- | --- | | 0–5 | `OPCD` | primary opcode (31) | | 6–10 | `RT` | destination / source GPR | | 11–20 | `spr/tbr/FXM` | SPR/TBR number (byte-swapped halves) or CR field mask | | 21–30 | `XO` | extended opcode | | 31 | `—` | reserved | ## Operands | Field | Role | Description | | --- | --- | --- | | `SPR` | mfspr: read | Special-Purpose-Register number. Encoded with the two 5-bit halves swapped (bits 11-15 become the high half, bits 16-20 the low half). | | `RD` | mfspr: write | Destination GPR. | ## Register Effects ### `mfspr` - **Reads (always):** `SPR` - **Reads (conditional):** _none_ - **Writes (always):** `RD` - **Writes (conditional):** _none_ ## Status-Register Effects _No condition-register or status-register effects._ ## Operation (pseudocode) ``` n <- spr_number(SPR) ; SPR field has its two 5-bit halves swapped RT <- SPR(n) ``` ## C Translation Example ```c /* mfspr RT, SPR — SPR field has swapped halves */ uint32_t n = ((insn.SPR & 0x1F) << 5) | ((insn.SPR >> 5) & 0x1F); switch (n) { case 1: r[insn.RT] = xer_pack(); break; /* XER */ case 8: r[insn.RT] = lr; break; /* LR */ case 9: r[insn.RT] = ctr; break; /* CTR */ case 256: r[insn.RT] = vrsave; break; /* VRSAVE*/ case 268: r[insn.RT] = tb & 0xFFFFFFFFu; break; /* TBL */ case 269: r[insn.RT] = tb >> 32; break; /* TBU */ default: r[insn.RT] = 0; break; } ``` ## Implementation References **`mfspr`** - xenia-canary XML: [`tools/ppc-instructions.xml` — search for `mnem="mfspr"`](../../xenia-canary/tools/ppc-instructions.xml) - xenia-canary emit: [`src/xenia/cpu/ppc/ppc_emit_control.cc:666`](../../xenia-canary/src/xenia/cpu/ppc/ppc_emit_control.cc#L666) - xenia-rs opcode: [`crates/xenia-cpu/src/opcode.rs:53`](../../xenia-rs/crates/xenia-cpu/src/opcode.rs#L53) - xenia-rs decoder: [`crates/xenia-cpu/src/decoder.rs:799`](../../xenia-rs/crates/xenia-cpu/src/decoder.rs#L799) - xenia-rs interpreter: [`crates/xenia-cpu/src/interpreter.rs:1567-1595`](../../xenia-rs/crates/xenia-cpu/src/interpreter.rs#L1567-L1595)
xenia-rs interpreter body (frozen snapshot) ```rust PpcOpcode::mfspr => { let spr = instr.spr(); ctx.gpr[instr.rd()] = match spr { crate::context::spr::XER => ctx.xer() as u64, crate::context::spr::LR => ctx.lr, crate::context::spr::CTR => ctx.ctr, crate::context::spr::DEC => ctx.dec as u64, crate::context::spr::TBL => ctx.timebase & 0xFFFF_FFFF, crate::context::spr::TBU => ctx.timebase >> 32, crate::context::spr::VRSAVE => ctx.vrsave as u64, // Xbox 360 Xenon processor signature (from canary). crate::context::spr::PVR => 0x0071_0800, // Benign SPRs — titles read these but we don't model them. crate::context::spr::SPRG0 | crate::context::spr::SPRG1 | crate::context::spr::SPRG2 | crate::context::spr::SPRG3 | crate::context::spr::HID0 | crate::context::spr::HID1 | crate::context::spr::DAR | crate::context::spr::DSISR | crate::context::spr::PIR => 0, _ => { tracing::warn!("mfspr: unimplemented SPR {}", spr); 0 } }; ctx.pc += 4; } ```
## SPR Number Encoding — the "halves swap" The 10-bit `spr` field in the XFX form is **stored in a transposed order**: the bits that software names the *high* half (bits 5..9 of the SPR number) occupy instruction bits **16..20**, and the *low* half (bits 0..4) occupies instruction bits **11..15**. Software (and this manual) always refers to the logical, unswapped SPR number. ``` decoded_spr = ((field & 0x1F) << 5) | ((field >> 5) & 0x1F) ``` So a programmer writing `mfspr RT, 8` (read LR) encodes `spr-field = 0x100` — *not* `8`. Assemblers handle this transparently; disassemblers reverse it. When writing a translator that parses raw instruction words, swap the halves explicitly. ## SPR Map (Xenon subset modelled by xenia) | Decoded # | Name | Meaning | xenia-rs behaviour | | --- | --- | --- | --- | | 1 | `XER` | Fixed-point exception register (CA / OV / SO + length field) | packed with `ctx.xer()` | | 8 | `LR` | Link register | `ctx.lr` | | 9 | `CTR` | Count register | `ctx.ctr` | | 18 | `DSISR` | Data-storage interrupt syndrome | returns 0 (stubbed) | | 19 | `DAR` | Data-access register | returns 0 (stubbed) | | 256 | `VRSAVE` | Vector-register save mask | `ctx.vrsave` | | 268 | `TBL` | Time-base lower 32 bits | `ctx.timebase & 0xFFFFFFFF` | | 269 | `TBU` | Time-base upper 32 bits | `ctx.timebase >> 32` | | 272–275 | `SPRG0..3` | Software scratch registers (kernel) | returns 0 (stubbed) | | 287 | `PVR` | Processor-version register | `0x00710800` (Xenon signature) | | 1008–1009 | `HID0/1` | Hardware implementation registers | returns 0 (stubbed) | | 1023 | `PIR` | Processor-ID register | returns 0 (stubbed) | Unrecognised SPRs return 0 and log a warning. Games rarely read unmodelled SPRs; when they do it's usually clock-skew or sanity checks. ## Special Cases & Edge Conditions - **Privilege.** Some SPRs are privileged on real hardware (MSR, HID0/1, SPRG0..3, DSISR, DAR, PIR). Xbox 360 titles run in a mixed privilege model under the hypervisor; xenia exposes all SPRs without a privilege check because the captured title binaries never contain a real privileged read that should trap. - **`LR` and `CTR` have dedicated simplified mnemonics.** Assemblers recognise `mflr RT` ≡ `mfspr RT, 8` and `mfctr RT` ≡ `mfspr RT, 9`. Similarly `mfxer RT` ≡ `mfspr RT, 1`. Disassemblers emit the simplified forms; the translation agent should map both forms to the same abstract operation. - **`mftb` vs. `mfspr TBL/TBU`.** Reading the time-base has a dedicated X-form variant [`mftb`](mftb.md) that uses a separate opcode. Post-Xbox-360 PowerISA deprecated `mfspr TBL/TBU`, but xenia accepts both. Prefer `mftb` in new translations. - **Side-effect-free.** `mfspr` has no effect on any register beyond `RT`. It can be freely reordered with non-SPR-touching instructions. - **No `Rc` / `OE`.** This is an XFX-form instruction; bit 31 is reserved (0). ## Related Instructions - [`mtspr`](mtspr.md) — the inverse; write a GPR to an SPR. - [`mftb`](mftb.md) — read time-base (preferred over `mfspr TBL/TBU`). - [`mflr`](mfspr.md), [`mfctr`](mfspr.md), [`mfxer`](mfspr.md) — simplified mnemonics of this instruction. - [`mcrxr`](mcrxr.md) — move `XER[SO..CA]` to a CR field and clear them. ## Simplified Mnemonics | Simplified | Expansion | | --- | --- | | `mfxer RT` | `mfspr RT, 1` | | `mflr RT` | `mfspr RT, 8` | | `mfctr RT` | `mfspr RT, 9` | ## IBM Reference - [AIX 7.3 — `mfspr` (Move from Special Purpose Register)](https://www.ibm.com/docs/en/aix/7.3.0?topic=set-mfspr-move-from-special-purpose-register-instruction) - [PowerISA v2.07B — SPR number table and privilege rules](https://openpowerfoundation.org/specifications/isa/)