fix(cpu): PPCBUG-040 PPCBUG-560 fix sh64() bit order and rldicl test helper
PPCBUG-040: decoder.rs sh64() assembled the XS-form shift amount as (SH[4:0] << 1) | SH[5] instead of (SH[5] << 5) | SH[4:0]. Every `sradi` with shift N ∈ 1..=62 executed with a completely wrong shift count (e.g. shift=32 executed as shift=1). PPCBUG-560: disasm_goldens.rs rldicl() test helper was encoding sh[5:1] at PPC bits 16-20 and sh[0] at PPC bit 30 — exactly backwards. The wrong encoder and wrong decoder cancelled out, hiding PPCBUG-040 from tests. Fix both together so tests validate ISA-correct encodings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use xenia_cpu::decoder::decode;
|
||||
use xenia_cpu::decoder::{DecodedInstr, decode};
|
||||
use xenia_cpu::disasm::format;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
@@ -158,20 +158,20 @@ fn rlwinm(rs: u32, ra: u32, sh: u32, mb: u32, me: u32, rc: u32) -> u32 {
|
||||
}
|
||||
|
||||
fn rldicl(rs: u32, ra: u32, sh: u32, mb: u32, rc: u32) -> u32 {
|
||||
// MD-form, op30 xo=0. sh split: bits 16-20 (high 5) + bit 30 (low bit).
|
||||
// mb split: bits 21-25 (low 5) + bit 26 (high bit).
|
||||
let sh_hi = (sh >> 1) & 0x1F;
|
||||
let sh_lo = sh & 1;
|
||||
// MD-form: sh[4:0] at PPC bits 16-20 (host bits 11-15); sh[5] at PPC bit 30 (host bit 1).
|
||||
// mb[4:0] at PPC bits 21-25 (host bits 6-10); mb[5] at PPC bit 26 (host bit 5).
|
||||
let sh_lo = sh & 0x1F;
|
||||
let sh_hi = (sh >> 5) & 1;
|
||||
let mb_lo = mb & 0x1F;
|
||||
let mb_hi = (mb >> 5) & 1;
|
||||
(30 << 26)
|
||||
| (rs << 21)
|
||||
| (ra << 16)
|
||||
| (sh_hi << 11)
|
||||
| (sh_lo << 11)
|
||||
| (mb_lo << 6)
|
||||
| (mb_hi << 5)
|
||||
| (0 << 2)
|
||||
| (sh_lo << 1)
|
||||
| (sh_hi << 1)
|
||||
| rc
|
||||
}
|
||||
|
||||
@@ -529,3 +529,26 @@ fn vmx128_registers() {
|
||||
all.extend_from_slice(&vmx128_4op);
|
||||
assert_or_regen("vmx128_registers.json", &all);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sradi_shift_32_decodes_to_32() {
|
||||
// sradi rA, rS, 32: sh=32 → sh[4:0]=0, sh[5]=1
|
||||
// After PPCBUG-040 fix, sh64() must return 32, not 1.
|
||||
let instr: DecodedInstr = decode(rldicl(3, 4, 32, 63, 0), 0);
|
||||
// rldicl with mb=63 is not sradi, but tests sh64() extraction.
|
||||
assert_eq!(instr.sh64(), 32, "sh64 must return 32 for sh=32 (sh5=1, sh_lo=0)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sh64_shift_1_decodes_correctly() {
|
||||
// sh=1: sh[4:0]=1, sh[5]=0 → sh64() must return 1
|
||||
let instr: DecodedInstr = decode(rldicl(3, 4, 1, 0, 0), 0);
|
||||
assert_eq!(instr.sh64(), 1, "sh64 must return 1 for sh=1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sh64_shift_63_decodes_correctly() {
|
||||
// sh=63: sh[4:0]=31=0x1F, sh[5]=1 → sh64() must return 63
|
||||
let instr: DecodedInstr = decode(rldicl(3, 4, 63, 0, 0), 0);
|
||||
assert_eq!(instr.sh64(), 63, "sh64 must return 63 for sh=63");
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
},
|
||||
{
|
||||
"label": "srdi r3, r4, 8",
|
||||
"raw": "0x7883E200",
|
||||
"raw": "0x7883C202",
|
||||
"addr": "0x82000000",
|
||||
"mnemonic": "rldicl",
|
||||
"operands": "r3, r4, 56, 8",
|
||||
@@ -191,7 +191,7 @@
|
||||
},
|
||||
{
|
||||
"label": "rotldi r3, r4, 8",
|
||||
"raw": "0x78832000",
|
||||
"raw": "0x78834000",
|
||||
"addr": "0x82000000",
|
||||
"mnemonic": "rldicl",
|
||||
"operands": "r3, r4, 8, 0",
|
||||
|
||||
Reference in New Issue
Block a user