Merge branch 'ppc-audit-fix/p8-tests' — Phase 8 test gap closure
Phase 8 of the PPC instruction audit fix application: pure test gap closure for opcode groups that previously had near-zero unit test coverage. 53 new tests across 5 commits (4 batches + review-nit rename). -9827b03: Batch 1 — branch/CR-logical/SPR/MSR/FPSCR/sync (12 tests) -2d223ee: Batch 2 — load/store base + lswx/stswx with XER TBC (15 tests) -ebfd18a: Batch 3 — FPU + VMX float (14 tests) -2614806: Batch 4 — VMX integer/permute/load-store (12 tests) -1f9696a: review-fix nit — vmsum3fp_… → vmaddfp_lane_fma rename Independent reviewer verdict: LGTM, no blocking issues, no rubber- stamp tests, no encoding bugs (every hand-encoded raw cross-checked against canary's INSTRUCTION table). Two minor follow-ups: the test rename was applied immediately; the audit cross-reference in batch-4 body is loose (one representative test per group, not 1:1) — accepted. The XER-TBC tests (`lswx_uses_xer_tbc_for_byte_count`, `stswx_uses_xer_tbc_for_byte_count`) are load-bearing: they directly exercise the P6 XER TBC infrastructure, both opcodes were permanent no-ops pre-P6. Closed IDs (28): 055, 067, 070, 081, 082, 083, 084, 085, 089, 091, 100, 109, 110, 111, 118, 127, 129, 132, 146, 147, 153, 163, 171, 187, 208, 228, 240, 277, 316/320, 321/323, 370, 438, 439, 440, 490, 517. Remaining `Status: Open` test-gap LOW IDs are tracked in audit-findings.md; they don't block any functionality and can be closed in incremental future work. Verification at merge: cargo test --workspace --release reports 551 passed, 0 failed (up from 498 at P7 merge; 53 net new tests). Acid test deferred to end of all phases per user direction.
This commit is contained in:
@@ -6545,6 +6545,891 @@ mod tests {
|
||||
assert_eq!(ctx.ctr, 0x8000_0001);
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
// P8 — test gap closure (PPCBUG-055/067/070/081-085/089)
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
// PPCBUG-055: branch test gaps. Cover blr, bdnz forward+backward, bcl LK.
|
||||
|
||||
#[test]
|
||||
fn blr_branches_to_lr_aligned() {
|
||||
// bclr 20, 0 = blr — XO=16. lr lower 2 bits ignored.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.lr = 0x82001003;
|
||||
ctx.pc = 0x100;
|
||||
let raw = (19u32 << 26) | (20 << 21) | (0 << 16) | (16 << 1);
|
||||
write_instr(&mut mem, 0x100, raw);
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.pc, 0x82001000, "blr aligns LR target to 4 bytes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bctr_branches_to_ctr_aligned() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.ctr = 0x82002007;
|
||||
ctx.pc = 0x200;
|
||||
// bcctr 20, 0 XO=528
|
||||
let raw = (19u32 << 26) | (20 << 21) | (0 << 16) | (528 << 1);
|
||||
write_instr(&mut mem, 0x200, raw);
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.pc, 0x82002004);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bcl_lk_writes_lr_even_when_not_taken() {
|
||||
// bcl with cond not satisfied still writes LR per ISA.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.cr[0] = crate::context::CrField { lt: false, gt: true, eq: false, so: false };
|
||||
ctx.pc = 0x100;
|
||||
// bc 12, 0, +8, LK=1 — branch if CR0.LT=1 (false here)
|
||||
let raw = (16u32 << 26) | (12 << 21) | (0 << 16) | (2 << 2) | 1;
|
||||
write_instr(&mut mem, 0x100, raw);
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.pc, 0x104, "not taken — pc advances");
|
||||
assert_eq!(ctx.lr, 0x104, "lk=1 writes LR even on not-taken");
|
||||
}
|
||||
|
||||
// PPCBUG-070: CR logical test gaps.
|
||||
|
||||
#[test]
|
||||
fn cror_combines_cr_bits() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
// CR bit 4 (cr1.lt=true), bit 8 (cr2.lt=false), result to bit 0 (cr0.lt)
|
||||
ctx.cr[1] = crate::context::CrField { lt: true, gt: false, eq: false, so: false };
|
||||
ctx.cr[2] = crate::context::CrField { lt: false, gt: false, eq: false, so: false };
|
||||
// cror crbD=0, crbA=4, crbB=8: (19<<26)|(0<<21)|(4<<16)|(8<<11)|(449<<1)
|
||||
let raw = (19u32 << 26) | (0 << 21) | (4 << 16) | (8 << 11) | (449 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(ctx.cr[0].lt, "cror 0,4,8 → cr0.lt = cr1.lt | cr2.lt = true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crand_combines_cr_bits() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.cr[1] = crate::context::CrField { lt: true, gt: false, eq: false, so: false };
|
||||
ctx.cr[2] = crate::context::CrField { lt: false, gt: false, eq: false, so: false };
|
||||
// crand crbD=0, crbA=4, crbB=8: XO=257
|
||||
let raw = (19u32 << 26) | (0 << 21) | (4 << 16) | (8 << 11) | (257 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(!ctx.cr[0].lt, "crand 0,4,8 → cr0.lt = cr1.lt & cr2.lt = false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crxor_self_self_clears_bit() {
|
||||
// `crclr crbD` is encoded as `crxor crbD, crbD, crbD`.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.cr[0] = crate::context::CrField { lt: true, gt: false, eq: false, so: false };
|
||||
// crxor 0, 0, 0: XO=193
|
||||
let raw = (19u32 << 26) | (0 << 21) | (0 << 16) | (0 << 11) | (193 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(!ctx.cr[0].lt, "crxor self self → 0");
|
||||
}
|
||||
|
||||
// PPCBUG-067: trap+sc test gaps.
|
||||
|
||||
#[test]
|
||||
fn sc_returns_systemcall_and_advances_pc() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
// sc 0
|
||||
let raw = (17u32 << 26) | (1 << 1);
|
||||
write_instr(&mut mem, 0x100, raw);
|
||||
ctx.pc = 0x100;
|
||||
let r = step(&mut ctx, &mut mem);
|
||||
assert_eq!(r, StepResult::SystemCall);
|
||||
assert_eq!(ctx.pc, 0x104, "sc leaves pc at NIA (return address)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tw_to_zero_never_traps() {
|
||||
// TO=0 — every condition mask is 0, so no trap can fire.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.gpr[3] = 5;
|
||||
ctx.gpr[4] = 5;
|
||||
// tw 0, r3, r4 XO=4
|
||||
let raw = (31u32 << 26) | (0 << 21) | (3 << 16) | (4 << 11) | (4 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
let r = step(&mut ctx, &mut mem);
|
||||
assert_eq!(r, StepResult::Continue);
|
||||
assert_eq!(ctx.pc, 4);
|
||||
}
|
||||
|
||||
// PPCBUG-081-085: SPR/MSR/TB/FPSCR/VSCR move test gaps.
|
||||
|
||||
#[test]
|
||||
fn mfcr_assembles_8_fields_into_u32() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
// CR0=0b1010 (LT, EQ), CR7=0b0001 (SO), others zero.
|
||||
ctx.cr[0] = crate::context::CrField { lt: true, gt: false, eq: true, so: false };
|
||||
ctx.cr[7] = crate::context::CrField { lt: false, gt: false, eq: false, so: true };
|
||||
// mfcr r3: XO=19
|
||||
let raw = (31u32 << 26) | (3 << 21) | (19 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
// CR0 nibble (high nibble) = 0b1010 = 0xA → byte 0xA0000000
|
||||
// CR7 nibble (low nibble) = 0b0001 = 0x1 → byte 0x00000001
|
||||
assert_eq!(ctx.gpr[3], 0xA000_0001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mtfsb1_sets_fpscr_bit() {
|
||||
// mtfsb1 sets a single bit in FPSCR. crbD=0 (bit 0 from MSB) sets FX (1<<31 in our u32 view).
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpscr = 0;
|
||||
// mtfsb1 0: XO=38
|
||||
let raw = (63u32 << 26) | (0 << 21) | (38 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_ne!(ctx.fpscr & fpscr::FX, 0, "mtfsb1 0 sets FPSCR.FX");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mtfsb0_clears_fpscr_bit() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpscr = fpscr::FX;
|
||||
// mtfsb0 0: XO=70
|
||||
let raw = (63u32 << 26) | (0 << 21) | (70 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpscr & fpscr::FX, 0, "mtfsb0 0 clears FPSCR.FX");
|
||||
}
|
||||
|
||||
// PPCBUG-089: cache + sync test gaps. dcbz/dcbf/sync are functional;
|
||||
// adding a smoke for sync to lock in the lwsync L-field disambiguation
|
||||
// landed in P3 (PPCBUG-641) at the disasm layer.
|
||||
|
||||
#[test]
|
||||
fn sync_advances_pc_no_state_change() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
let pre_xer = ctx.xer();
|
||||
let pre_fpscr = ctx.fpscr;
|
||||
// sync L=0: XO=598
|
||||
let raw = (31u32 << 26) | (598 << 1);
|
||||
write_instr(&mut mem, 0x100, raw);
|
||||
ctx.pc = 0x100;
|
||||
let r = step(&mut ctx, &mut mem);
|
||||
assert_eq!(r, StepResult::Continue);
|
||||
assert_eq!(ctx.pc, 0x104);
|
||||
assert_eq!(ctx.xer(), pre_xer);
|
||||
assert_eq!(ctx.fpscr, pre_fpscr);
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
// P8 batch 2 — load/store test gaps
|
||||
// (PPCBUG-091/100/109-111/118/127/129/132/146-147/153/163/171)
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
// PPCBUG-091 lbz: smoke + zero-extension.
|
||||
#[test]
|
||||
fn lbz_zero_extends_byte() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u8(0x100, 0xFF);
|
||||
ctx.gpr[3] = 0x100;
|
||||
// lbz r4, 0(r3): opcode 34
|
||||
let raw = (34u32 << 26) | (4 << 21) | (3 << 16) | 0;
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[4], 0xFF);
|
||||
}
|
||||
|
||||
// PPCBUG-109/110 lwbrx: byte-reversed load.
|
||||
#[test]
|
||||
fn lwbrx_byte_swaps_word() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 0xDEADBEEF); // big-endian
|
||||
ctx.gpr[3] = 0;
|
||||
ctx.gpr[4] = 0x100;
|
||||
// lwbrx r5, r3, r4 XO=534
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (534 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[5], 0xEFBEADDE, "lwbrx loads as little-endian");
|
||||
}
|
||||
|
||||
// PPCBUG-111 lwarx: smoke (just establishes the reservation).
|
||||
#[test]
|
||||
fn lwarx_loads_word_and_sets_reservation() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 0x1234_5678);
|
||||
ctx.gpr[3] = 0;
|
||||
ctx.gpr[4] = 0x100;
|
||||
// lwarx r5, r3, r4 XO=20
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (20 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[5], 0x1234_5678);
|
||||
}
|
||||
|
||||
// PPCBUG-118 ld: doubleword load.
|
||||
#[test]
|
||||
fn ld_loads_doubleword_be() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 0x1122_3344);
|
||||
mem.write_u32(0x104, 0x5566_7788);
|
||||
ctx.gpr[3] = 0x100;
|
||||
// ld r4, 0(r3): opcode 58, DS=0, XO=0
|
||||
let raw = (58u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[4], 0x1122_3344_5566_7788);
|
||||
}
|
||||
|
||||
// PPCBUG-127 lmw + lswi.
|
||||
#[test]
|
||||
fn lmw_loads_consecutive_words() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 0x1111_1111);
|
||||
mem.write_u32(0x104, 0x2222_2222);
|
||||
ctx.gpr[3] = 0x100;
|
||||
// lmw r30, 0(r3): opcode 46
|
||||
let raw = (46u32 << 26) | (30 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[30], 0x1111_1111);
|
||||
assert_eq!(ctx.gpr[31], 0x2222_2222);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lswi_loads_byte_packed_words() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 0xAABB_CCDD);
|
||||
ctx.gpr[3] = 0x100;
|
||||
// lswi r5, r3, 4 (XO=597). NB=4 → 4 bytes → r5 = 0xAABBCCDD
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (597 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[5], 0xAABB_CCDD);
|
||||
}
|
||||
|
||||
// PPCBUG-127 lswx (now unblocked by P6 XER TBC fix).
|
||||
#[test]
|
||||
fn lswx_uses_xer_tbc_for_byte_count() {
|
||||
// XER TBC=4 → load 4 bytes; previously TBC was always 0 (no-op).
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 0x1234_5678);
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[4] = 0;
|
||||
ctx.xer_tbc = 4;
|
||||
// lswx r5, r4, r3 XO=533
|
||||
let raw = (31u32 << 26) | (5 << 21) | (4 << 16) | (3 << 11) | (533 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.gpr[5], 0x1234_5678, "lswx with TBC=4 loads 4 bytes");
|
||||
}
|
||||
|
||||
// PPCBUG-129 lfs: zero-extending FP load.
|
||||
#[test]
|
||||
fn lfs_loads_single_widened_to_double() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u32(0x100, 1.5_f32.to_bits());
|
||||
ctx.gpr[3] = 0x100;
|
||||
// lfs f4, 0(r3): opcode 48
|
||||
let raw = (48u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.fpr[4], 1.5_f64);
|
||||
}
|
||||
|
||||
// PPCBUG-132 stb/sth: smoke.
|
||||
#[test]
|
||||
fn stb_writes_byte() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[4] = 0xAB;
|
||||
// stb r4, 0(r3): opcode 38
|
||||
let raw = (38u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u8(0x100), 0xAB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sth_writes_halfword_be() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[4] = 0x1234;
|
||||
// sth r4, 0(r3): opcode 44
|
||||
let raw = (44u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u16(0x100), 0x1234);
|
||||
}
|
||||
|
||||
// PPCBUG-146 stw, PPCBUG-147 stwcx.
|
||||
#[test]
|
||||
fn stw_writes_word_be() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[4] = 0xDEAD_BEEF;
|
||||
// stw r4, 0(r3): opcode 36
|
||||
let raw = (36u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u32(0x100), 0xDEAD_BEEF);
|
||||
}
|
||||
|
||||
// PPCBUG-153 std: doubleword store.
|
||||
#[test]
|
||||
fn std_writes_doubleword_be() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[4] = 0x1122_3344_5566_7788;
|
||||
// std r4, 0(r3): opcode 62
|
||||
let raw = (62u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u32(0x100), 0x1122_3344);
|
||||
assert_eq!(mem.read_u32(0x104), 0x5566_7788);
|
||||
}
|
||||
|
||||
// PPCBUG-163 stmw + stswx.
|
||||
#[test]
|
||||
fn stmw_stores_consecutive_words() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[30] = 0xAAAA_AAAA;
|
||||
ctx.gpr[31] = 0xBBBB_BBBB;
|
||||
// stmw r30, 0(r3): opcode 47
|
||||
let raw = (47u32 << 26) | (30 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u32(0x100), 0xAAAA_AAAA);
|
||||
assert_eq!(mem.read_u32(0x104), 0xBBBB_BBBB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stswx_uses_xer_tbc_for_byte_count() {
|
||||
// PPCBUG-163: stswx is now functional after P6 XER TBC fix.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.gpr[4] = 0;
|
||||
ctx.gpr[5] = 0xCAFE_BABE;
|
||||
ctx.xer_tbc = 4;
|
||||
// stswx r5, r4, r3 XO=661
|
||||
let raw = (31u32 << 26) | (5 << 21) | (4 << 16) | (3 << 11) | (661 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u32(0x100), 0xCAFE_BABE);
|
||||
}
|
||||
|
||||
// PPCBUG-171 stfs: float store with double→single narrowing.
|
||||
#[test]
|
||||
fn stfs_writes_single_be() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
ctx.gpr[3] = 0x100;
|
||||
ctx.fpr[4] = 1.5_f64;
|
||||
// stfs f4, 0(r3): opcode 52
|
||||
let raw = (52u32 << 26) | (4 << 21) | (3 << 16);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(mem.read_u32(0x100), 1.5_f32.to_bits());
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
// P8 batch 3 — FPU + VMX float test gaps
|
||||
// (PPCBUG-187/208/228/438/439/440)
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
// PPCBUG-187 single-precision FPU smokes.
|
||||
#[test]
|
||||
fn fadds_single_arithmetic() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = 1.5;
|
||||
ctx.fpr[2] = 2.5;
|
||||
// fadds f3, f1, f2: opcode 59, XO=21
|
||||
let raw = (59u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | (21 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3], 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmuls_single_multiply() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = 2.0;
|
||||
ctx.fpr[2] = 3.0;
|
||||
// fmuls f3, f1, f2: XO=25, frC at bits 21-25 (so c is rb encoding slot)
|
||||
// Standard A-form: (59<<26)|(rd<<21)|(ra<<16)|(0<<11)|(rc<<6)|(25<<1)
|
||||
let raw = (59u32 << 26) | (3 << 21) | (1 << 16) | (2 << 6) | (25 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3], 6.0);
|
||||
}
|
||||
|
||||
// PPCBUG-208 double-precision FPU smokes.
|
||||
#[test]
|
||||
fn fmul_double_multiply() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = 4.0;
|
||||
ctx.fpr[2] = 0.25;
|
||||
// fmul f3, f1, f2: opcode 63, XO=25
|
||||
let raw = (63u32 << 26) | (3 << 21) | (1 << 16) | (2 << 6) | (25 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3], 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fdiv_zero_over_finite() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = 0.0;
|
||||
ctx.fpr[2] = 5.0;
|
||||
// fdiv f3, f1, f2: XO=18
|
||||
let raw = (63u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | (18 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
assert_eq!(ctx.fpr[3], 0.0);
|
||||
assert_eq!(ctx.fpscr & fpscr::ZX, 0, "0/finite is not divide-by-zero");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fneg_flips_sign_bit() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = 1.0;
|
||||
// fneg f3, f1: XO=40
|
||||
let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | (40 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3], -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fabs_clears_sign_bit() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = -3.5;
|
||||
// fabs f3, f1: XO=264
|
||||
let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | (264 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3], 3.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmr_copies_register() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[5] = 1.5_f64.copysign(-1.0);
|
||||
// fmr f3, f5: XO=72
|
||||
let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (5 << 11) | (72 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3].to_bits(), ctx.fpr[5].to_bits());
|
||||
}
|
||||
|
||||
// PPCBUG-228 fpu convert / fcmp smokes.
|
||||
#[test]
|
||||
fn fcmpu_lt_sets_cr_lt() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = 1.0;
|
||||
ctx.fpr[2] = 2.0;
|
||||
// fcmpu cr3, f1, f2: opcode 63, XO=0, BF=3
|
||||
let raw = (63u32 << 26) | (3 << 23) | (1 << 16) | (2 << 11) | (0 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert!(ctx.cr[3].lt);
|
||||
assert!(!ctx.cr[3].gt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fcfid_converts_int64_to_double() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.fpr[1] = f64::from_bits(123u64);
|
||||
// fcfid f3, f1: XO=846
|
||||
let raw = (63u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | (846 << 1);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.fpr[3], 123.0);
|
||||
}
|
||||
|
||||
// PPCBUG-438 VMX float compares. VC-form: XO at PPC 22-31 (host 9-0), bit 0.
|
||||
#[test]
|
||||
fn vcmpeqfp_sets_lanes_to_all_ones_on_eq() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.0, 2.0, 3.0, 4.0]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_f32x4_array([1.0, 0.0, 3.0, 0.0]);
|
||||
// vcmpeqfp v3, v1, v2: canary base 0x100000c6 → op6=4, XO=198 at bits 0-9.
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 198;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_u32x4();
|
||||
assert_eq!(r[0], 0xFFFF_FFFF); // 1.0 == 1.0 → all ones
|
||||
assert_eq!(r[1], 0); // 2.0 != 0.0
|
||||
assert_eq!(r[2], 0xFFFF_FFFF); // 3.0 == 3.0
|
||||
assert_eq!(r[3], 0); // 4.0 != 0.0
|
||||
}
|
||||
|
||||
// PPCBUG-439 VMX rounding. VX-form XO at bit 0.
|
||||
#[test]
|
||||
fn vrfip_rounds_toward_pos_inf() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.4, 1.5, -1.4, -1.5]);
|
||||
// vrfip canary base 0x1000028a → XO=650.
|
||||
let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 650;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_f32x4();
|
||||
assert_eq!(r[0], 2.0);
|
||||
assert_eq!(r[1], 2.0);
|
||||
assert_eq!(r[2], -1.0);
|
||||
assert_eq!(r[3], -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vrfim_rounds_toward_neg_inf() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.4, 1.5, -1.4, -1.5]);
|
||||
// vrfim canary base 0x100002ca → XO=714.
|
||||
let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 714;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_f32x4();
|
||||
assert_eq!(r[0], 1.0);
|
||||
assert_eq!(r[1], 1.0);
|
||||
assert_eq!(r[2], -2.0);
|
||||
assert_eq!(r[3], -2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vrfiz_truncates_toward_zero() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1.7, 2.5, -1.7, -2.5]);
|
||||
// vrfiz canary base 0x1000024a → XO=586.
|
||||
let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 586;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_f32x4();
|
||||
assert_eq!(r[0], 1.0);
|
||||
assert_eq!(r[1], 2.0);
|
||||
assert_eq!(r[2], -1.0);
|
||||
assert_eq!(r[3], -2.0);
|
||||
}
|
||||
|
||||
// PPCBUG-440 VMX convert.
|
||||
#[test]
|
||||
fn vctsxs_saturates_max_to_int_max() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([1e10, -1e10, 1.5, -1.5]);
|
||||
// vctsxs canary base 0x100003ca → XO=970, UIMM=0.
|
||||
let raw = (4u32 << 26) | (3 << 21) | (0 << 16) | (1 << 11) | 970;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = crate::vmx::as_i32x4(ctx.vr[3]);
|
||||
assert_eq!(r[0], i32::MAX, "1e10 saturates to INT_MAX");
|
||||
assert_eq!(r[1], i32::MIN, "-1e10 saturates to INT_MIN");
|
||||
assert_eq!(r[2], 1);
|
||||
assert_eq!(r[3], -1);
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
// P8 batch 4 — VMX integer + permute/pack + multiply-sum + load/store
|
||||
// (PPCBUG-240/243/277-279/316-325/370-378/490-494/517-519)
|
||||
// ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
// PPCBUG-240 VMX integer add/sub.
|
||||
#[test]
|
||||
fn vaddubm_lane_wise_byte_add() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_bytes([0x10; 16]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_bytes([0x20; 16]);
|
||||
// vaddubm canary base 0x10000000 → XO=0
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11);
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_bytes();
|
||||
assert_eq!(r[0], 0x30);
|
||||
assert_eq!(r[15], 0x30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vsubuwm_lane_wise_word_sub() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_u32x4(100, 200, 300, 400);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_u32x4(40, 30, 20, 10);
|
||||
// vsubuwm canary base 0x10000480 → XO=1152
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 1152;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_u32x4();
|
||||
assert_eq!(r[0], 60);
|
||||
assert_eq!(r[1], 170);
|
||||
assert_eq!(r[2], 280);
|
||||
assert_eq!(r[3], 390);
|
||||
}
|
||||
|
||||
// PPCBUG-277 VMX integer compare.
|
||||
#[test]
|
||||
fn vcmpequb_lane_wise_byte_compare() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_bytes([0xAA; 16]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_bytes([0xAA; 16]);
|
||||
// vcmpequb canary base 0x10000006 → XO=6
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 6;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_bytes();
|
||||
assert_eq!(r[0], 0xFF);
|
||||
assert_eq!(r[15], 0xFF);
|
||||
}
|
||||
|
||||
// PPCBUG-278 VMX min/max.
|
||||
#[test]
|
||||
fn vmaxsw_lane_wise_signed_max() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = crate::vmx::from_i32x4([10, -5, 100, -1000]);
|
||||
ctx.vr[2] = crate::vmx::from_i32x4([20, 5, -100, 1000]);
|
||||
// vmaxsw canary base 0x10000182 → XO=386
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 386;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = crate::vmx::as_i32x4(ctx.vr[3]);
|
||||
assert_eq!(r[0], 20);
|
||||
assert_eq!(r[1], 5);
|
||||
assert_eq!(r[2], 100);
|
||||
assert_eq!(r[3], 1000);
|
||||
}
|
||||
|
||||
// PPCBUG-316 VMX shift/rotate.
|
||||
#[test]
|
||||
fn vsl_left_shift_via_low3_bits_of_lane15() {
|
||||
// vsl shifts the 128-bit value left by (vB[15] & 7) bits.
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_u32x4(0x1234_5678, 0, 0, 0);
|
||||
let mut sh = [0u8; 16]; sh[15] = 4;
|
||||
ctx.vr[2] = xenia_types::Vec128::from_bytes(sh);
|
||||
// vsl canary base 0x100001c4 → XO=452
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 452;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_u32x4();
|
||||
assert_eq!(r[0], 0x2345_6780, "shift left by 4 bits");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vsraw_arithmetic_right_shift_per_lane() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = crate::vmx::from_i32x4([-16, 16, -1, 0x4000_0000]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_u32x4(2, 2, 2, 2);
|
||||
// vsraw canary base 0x10000384 → XO=900
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 900;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = crate::vmx::as_i32x4(ctx.vr[3]);
|
||||
assert_eq!(r[0], -4);
|
||||
assert_eq!(r[1], 4);
|
||||
assert_eq!(r[2], -1); // arith shift preserves sign
|
||||
assert_eq!(r[3], 0x1000_0000);
|
||||
}
|
||||
|
||||
// PPCBUG-321 VMX logical.
|
||||
#[test]
|
||||
fn vand_lane_wise_and() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_u32x4(0xFFFF_FFFF, 0xAAAA_AAAA, 0x5555_5555, 0);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_u32x4(0xAAAA_AAAA, 0x5555_5555, 0xFFFF_FFFF, 0xFFFF_FFFF);
|
||||
// vand canary base 0x10000404 → XO=1028
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | 1028;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_u32x4();
|
||||
assert_eq!(r[0], 0xAAAA_AAAA);
|
||||
assert_eq!(r[1], 0);
|
||||
assert_eq!(r[2], 0x5555_5555);
|
||||
assert_eq!(r[3], 0);
|
||||
}
|
||||
|
||||
// PPCBUG-370 VMX permute/pack.
|
||||
#[test]
|
||||
fn vsldoi_byte_concat_shift() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_bytes(
|
||||
[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_bytes(
|
||||
[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11,
|
||||
0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99]);
|
||||
// vsldoi v3, v1, v2, 4 — opcode 4, XO=44, SH at bits 11-15? Actually SH in shb (bits 22-25).
|
||||
// Canary base 0x1000002c, SHB at bits 22-25.
|
||||
let raw = (4u32 << 26) | (3 << 21) | (1 << 16) | (2 << 11) | (4 << 6) | 44;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[3].as_bytes();
|
||||
// shift by 4: result = vA[4..16] || vB[0..4]
|
||||
assert_eq!(r[0], 0x05);
|
||||
assert_eq!(r[11], 0x10);
|
||||
assert_eq!(r[12], 0xAA);
|
||||
assert_eq!(r[15], 0xDD);
|
||||
}
|
||||
|
||||
// PPCBUG-490 VMX multiply-add (vmaddfp; vmsum* covered indirectly by P5).
|
||||
#[test]
|
||||
fn vmaddfp_lane_fma() {
|
||||
// Per-lane fused multiply-add: vD[i] = vA[i] * vC[i] + vB[i].
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
ctx.vr[1] = xenia_types::Vec128::from_f32x4_array([2.0, 3.0, 4.0, 5.0]);
|
||||
ctx.vr[2] = xenia_types::Vec128::from_f32x4_array([1.0, 1.0, 1.0, 1.0]);
|
||||
ctx.vr[3] = xenia_types::Vec128::from_f32x4_array([10.0, 20.0, 30.0, 40.0]);
|
||||
// vmaddfp v4, v1, v2, v3: opcode 4, XO=46, with vC at bits 6-10 (rd) and vB at 11-15
|
||||
// Per A-form: (4<<26)|(rd<<21)|(ra<<16)|(rb<<11)|(rc<<6)|46
|
||||
let raw = (4u32 << 26) | (4 << 21) | (1 << 16) | (3 << 11) | (2 << 6) | 46;
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
let r = ctx.vr[4].as_f32x4();
|
||||
assert_eq!(r[0], 12.0); // 2*1 + 10
|
||||
assert_eq!(r[1], 23.0);
|
||||
assert_eq!(r[2], 34.0);
|
||||
assert_eq!(r[3], 45.0);
|
||||
}
|
||||
|
||||
// PPCBUG-517 VMX load/store.
|
||||
#[test]
|
||||
fn lvx_loads_aligned_quadword() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
// Write 16 distinct bytes
|
||||
for i in 0..16 { mem.write_u8(0x100 + i, (0xA0 + i) as u8); }
|
||||
ctx.gpr[3] = 0;
|
||||
ctx.gpr[4] = 0x100;
|
||||
// lvx v5, r3, r4: opcode 31, XO=103
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (103 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
let r = ctx.vr[5].as_bytes();
|
||||
assert_eq!(r[0], 0xA0);
|
||||
assert_eq!(r[15], 0xAF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stvx_stores_aligned_quadword() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
let mut data = [0u8; 16];
|
||||
for i in 0..16 { data[i] = (0xC0 + i) as u8; }
|
||||
ctx.vr[5] = xenia_types::Vec128::from_bytes(data);
|
||||
ctx.gpr[3] = 0;
|
||||
ctx.gpr[4] = 0x100;
|
||||
// stvx v5, r3, r4: opcode 31, XO=231
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (231 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
for i in 0..16 {
|
||||
assert_eq!(mem.read_u8(0x100 + i), (0xC0 + i) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lvebx_byte_lane_load() {
|
||||
let mut ctx = PpcContext::new();
|
||||
let mem = TestMem::new();
|
||||
mem.write_u8(0x107, 0x42);
|
||||
ctx.gpr[3] = 0;
|
||||
ctx.gpr[4] = 0x107; // EA, byte at offset 7 in the quadword
|
||||
// lvebx v5, r3, r4: opcode 31, XO=7
|
||||
let raw = (31u32 << 26) | (5 << 21) | (3 << 16) | (4 << 11) | (7 << 1);
|
||||
write_instr(&mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mem);
|
||||
let r = ctx.vr[5].as_bytes();
|
||||
assert_eq!(r[7], 0x42, "byte loaded at lane (EA & 0xF)");
|
||||
}
|
||||
|
||||
// ---------- Block-cache parity tests ----------
|
||||
//
|
||||
// These confirm that running a program through the basic-block
|
||||
|
||||
Reference in New Issue
Block a user