mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
1670 lines
68 KiB
C
1670 lines
68 KiB
C
/* ------------------------------------------------------------------------------------
|
|
R3000A against the documented rules, independently recomputed.
|
|
|
|
WHY THIS EXISTS
|
|
|
|
tests/cpu_differential.c is DIFFERENTIAL: the interpreter against the cached
|
|
interpreter, both this project's own code, both written from one understanding. A rule
|
|
that is wrong in BOTH halves reads green forever. That blindness has already bitten
|
|
this project five times (GPU parity, the blend rule, the shift matrix, GPUINFO, the
|
|
GTE). tests/gte_matrix.c closed the hole for COP2; this file closes it for the integer
|
|
core, in the same style: the psx-spx / R3000A rules are re-implemented INSIDE the test
|
|
and the real emulator is compared against them.
|
|
|
|
Every vector runs through BOTH execution modes (interpreter and cached interpreter) and
|
|
both are checked against the reference, so a rule shared by the two dispatch paths is
|
|
still caught, and a divergence between them is caught as well.
|
|
|
|
Covered:
|
|
* load-extend LB/LBU/LH/LHU/LW: exhaustive over all 256 byte values x 4 byte
|
|
lanes and all 65536 halfword values x 2 lanes; sign vs zero
|
|
extension recomputed from the spec
|
|
* store-width SB/SH/SW: byte lanes and halfword lanes, surrounding bytes must
|
|
be untouched
|
|
* addr-error ADEL/ADES: every misaligned LH/LHU/LW/SH/SW alignment; EPC,
|
|
CAUSE.ExcCode, the destination register and memory must all be
|
|
left as the spec says
|
|
* unaligned LWL/LWR/SWL/SWR: exhaustive over 4 alignments x a pattern set x
|
|
prior-rt set, against the switch-form spec, plus the canonical
|
|
LWL+LWR / SWL+SWR unaligned-access idioms
|
|
* load-delay the R3000 load delay slot: for every consumer class (ALU, store
|
|
rt, store base, branch, shift, mult, MTC0/MTC2, second load) the
|
|
instruction after a load must observe the OLD rt
|
|
* shift-matrix SLL/SRL/SRA/SLLV/SRLV/SRAV over all 32 amounts (variable forms
|
|
swept to 63 to prove the & 31) x a boundary operand set
|
|
* alu-matrix ADD/ADDU/SUB/SUBU/AND/OR/XOR/NOR/SLT/SLTU and the immediate
|
|
forms over a boundary lattice; ADDI/ADD/SUB overflow must raise
|
|
and must NOT write the destination
|
|
* muldiv MULT/MULTU/DIV/DIVU over boundary pairs + a pseudo-random sweep;
|
|
div-by-zero (both signs) and INT_MIN / -1 from the spec
|
|
* branch-matrix BEQ/BNE/BLEZ/BGTZ/BLTZ/BGEZ/BLTZAL/BGEZAL/J/JAL/JR/JALR: taken
|
|
and not-taken targets, the delay slot, the link value, the
|
|
"link happens even when not taken" rule and rs==31/rd==rs
|
|
* exception-model SYSCALL/BREAK/overflow/address-error inside and outside a branch
|
|
delay slot: EPC, CAUSE.BD, the SR mode-stack push, both vectors
|
|
(BEV) and the RFE pop
|
|
* cop0-masks MTC0/MFC0 per-register write masks from the documented table
|
|
* cache-isolation with SR.IsC set, NO store may reach memory (SB/SH/SW/SWL/SWR)
|
|
|
|
Reference convention: "spec" means the psx-spx CPU chapter / the R3000A manual as
|
|
cross-confirmed by hardware-verified emulators. Where a rule is stated in the code
|
|
below it is written out from that source, never copied from psx/cpu.c.
|
|
|
|
MUTATION PROTOCOL (a gate that cannot fail is not a gate): temporarily perturb, one at
|
|
a time, in psx/cpu.c: (1) one LWL/LWR mask, (2) the SE8/SE16 sign extension of one
|
|
load, (3) the div-by-zero LO value, (4) the BD bit in psx_cpu_exception, (5) one COP0
|
|
write mask, (6) the & 31 on a variable shift. `make test-cpu-spec` must go red for each.
|
|
Revert byte-identically afterwards.
|
|
------------------------------------------------------------------------------------ */
|
|
|
|
#include <inttypes.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "psx/psx.h"
|
|
|
|
#define TEST_PC 0x80001000u
|
|
#define TEST_OFFSET 0x00001000u
|
|
#define DATA_VA 0x80002000u
|
|
#define DATA_PA 0x00002000u
|
|
|
|
#define EXC_VEC_NORMAL 0x80000080u
|
|
#define EXC_VEC_BEV 0xbfc00180u
|
|
|
|
static unsigned long g_checked;
|
|
static int g_failures;
|
|
|
|
static void fail(const char* fmt, ...);
|
|
|
|
#include <stdarg.h>
|
|
|
|
static void fail(const char* fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
fprintf(stderr, "CPU_SPEC failed ");
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
++g_failures;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Instruction encoders (field layout only -- no semantics).
|
|
================================================================================== */
|
|
|
|
static uint32_t enc_i(unsigned op, unsigned rs, unsigned rt, uint16_t imm) {
|
|
return ((op & 0x3fu) << 26) | ((rs & 0x1fu) << 21) | ((rt & 0x1fu) << 16) | imm;
|
|
}
|
|
|
|
static uint32_t enc_r(unsigned rs, unsigned rt, unsigned rd, unsigned sa, unsigned funct) {
|
|
return ((rs & 0x1fu) << 21) | ((rt & 0x1fu) << 16) | ((rd & 0x1fu) << 11) |
|
|
((sa & 0x1fu) << 6) | (funct & 0x3fu);
|
|
}
|
|
|
|
static uint32_t enc_j(unsigned op, uint32_t target26) {
|
|
return ((op & 0x3fu) << 26) | (target26 & 0x3ffffffu);
|
|
}
|
|
|
|
#define OP_SPECIAL 0x00u
|
|
#define OP_BCOND 0x01u
|
|
#define OP_J 0x02u
|
|
#define OP_JAL 0x03u
|
|
#define OP_BEQ 0x04u
|
|
#define OP_BNE 0x05u
|
|
#define OP_BLEZ 0x06u
|
|
#define OP_BGTZ 0x07u
|
|
#define OP_ADDI 0x08u
|
|
#define OP_ADDIU 0x09u
|
|
#define OP_SLTI 0x0au
|
|
#define OP_SLTIU 0x0bu
|
|
#define OP_ANDI 0x0cu
|
|
#define OP_ORI 0x0du
|
|
#define OP_XORI 0x0eu
|
|
#define OP_LUI 0x0fu
|
|
#define OP_COP0 0x10u
|
|
#define OP_LB 0x20u
|
|
#define OP_LH 0x21u
|
|
#define OP_LWL 0x22u
|
|
#define OP_LW 0x23u
|
|
#define OP_LBU 0x24u
|
|
#define OP_LHU 0x25u
|
|
#define OP_LWR 0x26u
|
|
#define OP_SB 0x28u
|
|
#define OP_SH 0x29u
|
|
#define OP_SWL 0x2au
|
|
#define OP_SW 0x2bu
|
|
#define OP_SWR 0x2eu
|
|
|
|
#define F_SLL 0x00u
|
|
#define F_SRL 0x02u
|
|
#define F_SRA 0x03u
|
|
#define F_SLLV 0x04u
|
|
#define F_SRLV 0x06u
|
|
#define F_SRAV 0x07u
|
|
#define F_JR 0x08u
|
|
#define F_JALR 0x09u
|
|
#define F_SYSCALL 0x0cu
|
|
#define F_BREAK 0x0du
|
|
#define F_MFHI 0x10u
|
|
#define F_MTHI 0x11u
|
|
#define F_MFLO 0x12u
|
|
#define F_MTLO 0x13u
|
|
#define F_MULT 0x18u
|
|
#define F_MULTU 0x19u
|
|
#define F_DIV 0x1au
|
|
#define F_DIVU 0x1bu
|
|
#define F_ADD 0x20u
|
|
#define F_ADDU 0x21u
|
|
#define F_SUB 0x22u
|
|
#define F_SUBU 0x23u
|
|
#define F_AND 0x24u
|
|
#define F_OR 0x25u
|
|
#define F_XOR 0x26u
|
|
#define F_NOR 0x27u
|
|
#define F_SLT 0x2au
|
|
#define F_SLTU 0x2bu
|
|
|
|
#define NOP 0x00000000u
|
|
|
|
/* ==================================================================================
|
|
Machine plumbing. Two machines, one per execution mode; every vector runs on both.
|
|
================================================================================== */
|
|
|
|
typedef struct {
|
|
psx_t* m[2]; /* [0] = interpreter, [1] = cached interpreter */
|
|
} pair_t;
|
|
|
|
static const char* const MODE_NAME[2] = { "interp", "cached" };
|
|
|
|
static int write_blank_bios(const char* path) {
|
|
FILE* file = fopen(path, "wb");
|
|
if (!file)
|
|
return 0;
|
|
|
|
uint8_t block[4096] = {0};
|
|
for (size_t offset = 0; offset < 512u * 1024u; offset += sizeof(block)) {
|
|
if (fwrite(block, 1, sizeof(block), file) != sizeof(block)) {
|
|
fclose(file);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return fclose(file) == 0;
|
|
}
|
|
|
|
static int pair_init(pair_t* p, const char* bios) {
|
|
for (int i = 0; i < 2; i++) {
|
|
p->m[i] = psx_create();
|
|
if (!p->m[i] || psx_init(p->m[i], bios, NULL) != 0)
|
|
return 0;
|
|
psx_cpu_set_execution_mode(p->m[i]->cpu,
|
|
i == 0 ? PSX_CPU_INTERPRETER : PSX_CPU_CACHED_INTERPRETER);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void pair_destroy(pair_t* p) {
|
|
for (int i = 0; i < 2; i++)
|
|
if (p->m[i])
|
|
psx_destroy(p->m[i]);
|
|
}
|
|
|
|
/* Light reset: architectural state only. Deliberately does NOT touch the decode cache --
|
|
the program writer below goes through psx_bus_write32, which is the same path the
|
|
emulator's own write observer watches, so a stale-cache bug stays visible. */
|
|
static void cpu_reset(psx_cpu_t* c) {
|
|
memset(c->r, 0, sizeof(c->r));
|
|
c->hi = 0;
|
|
c->lo = 0;
|
|
c->pc = TEST_PC;
|
|
c->next_pc = TEST_PC + 4u;
|
|
c->saved_pc = 0;
|
|
c->opcode = 0;
|
|
c->load_d = 0;
|
|
c->load_v = 0xffffffffu;
|
|
c->branch = 0;
|
|
c->delay_slot = 0;
|
|
c->branch_taken = 0;
|
|
memset(c->cop0_r, 0, sizeof(c->cop0_r));
|
|
c->cop0_r[COP0_SR] = 0x10000000u; /* CU0 only: IEc clear (no IRQs), BEV clear */
|
|
c->cop0_r[COP0_PRID] = 0x00000002u;
|
|
c->last_cycles = 0;
|
|
c->total_cycles = 0;
|
|
}
|
|
|
|
static void write_prog(psx_t* psx, const uint32_t* words, size_t n) {
|
|
for (size_t i = 0; i < n; i++)
|
|
psx_bus_write32(psx->bus, TEST_OFFSET + (uint32_t)(i * 4u), words[i]);
|
|
}
|
|
|
|
static void write_data(psx_t* psx, uint32_t off, uint32_t value) {
|
|
psx_bus_write32(psx->bus, DATA_PA + off, value);
|
|
}
|
|
|
|
static uint32_t read_data(psx_t* psx, uint32_t off) {
|
|
return psx_bus_read32(psx->bus, DATA_PA + off);
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Reference model -- written from the spec, never from psx/cpu.c.
|
|
================================================================================== */
|
|
|
|
static uint32_t ref_lwl(uint32_t rt, uint32_t mem, unsigned n) {
|
|
switch (n & 3u) {
|
|
case 0: return (rt & 0x00ffffffu) | (mem << 24);
|
|
case 1: return (rt & 0x0000ffffu) | (mem << 16);
|
|
case 2: return (rt & 0x000000ffu) | (mem << 8);
|
|
default: return mem;
|
|
}
|
|
}
|
|
|
|
static uint32_t ref_lwr(uint32_t rt, uint32_t mem, unsigned n) {
|
|
switch (n & 3u) {
|
|
case 0: return mem;
|
|
case 1: return (rt & 0xff000000u) | (mem >> 8);
|
|
case 2: return (rt & 0xffff0000u) | (mem >> 16);
|
|
default: return (rt & 0xffffff00u) | (mem >> 24);
|
|
}
|
|
}
|
|
|
|
static uint32_t ref_swl(uint32_t mem, uint32_t rt, unsigned n) {
|
|
switch (n & 3u) {
|
|
case 0: return (mem & 0xffffff00u) | (rt >> 24);
|
|
case 1: return (mem & 0xffff0000u) | (rt >> 16);
|
|
case 2: return (mem & 0xff000000u) | (rt >> 8);
|
|
default: return rt;
|
|
}
|
|
}
|
|
|
|
static uint32_t ref_swr(uint32_t mem, uint32_t rt, unsigned n) {
|
|
switch (n & 3u) {
|
|
case 0: return rt;
|
|
case 1: return (mem & 0x000000ffu) | (rt << 8);
|
|
case 2: return (mem & 0x0000ffffu) | (rt << 16);
|
|
default: return (mem & 0x00ffffffu) | (rt << 24);
|
|
}
|
|
}
|
|
|
|
static uint32_t ref_shift(unsigned funct, uint32_t value, unsigned amount) {
|
|
unsigned a = amount & 31u;
|
|
switch (funct) {
|
|
case F_SLL: case F_SLLV: return (uint32_t)(value << a);
|
|
case F_SRL: case F_SRLV: return (uint32_t)(value >> a);
|
|
case F_SRA: case F_SRAV: {
|
|
/* well-defined arithmetic shift right */
|
|
if (value & 0x80000000u)
|
|
return (uint32_t)(~((~value) >> a));
|
|
return value >> a;
|
|
}
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: load sign/zero extension, exhaustive.
|
|
================================================================================== */
|
|
|
|
static int case_load_extend(pair_t* p) {
|
|
printf("CPU_SPEC begin case=load-extend\n");
|
|
int ok = 1;
|
|
|
|
/* LB / LBU over every byte value in every lane. */
|
|
for (unsigned lane = 0; lane < 4u && ok; lane++) {
|
|
for (unsigned bv = 0; bv < 256u && ok; bv++) {
|
|
uint32_t word = 0xa5a5a5a5u;
|
|
((uint8_t*)&word)[lane] = (uint8_t)bv; /* host is LE; PSX RAM is LE */
|
|
|
|
for (int signed_load = 0; signed_load < 2 && ok; signed_load++) {
|
|
uint32_t op = enc_i(signed_load ? OP_LB : OP_LBU, 1, 2, (uint16_t)lane);
|
|
uint32_t prog[3] = { op, NOP, NOP };
|
|
uint32_t want = signed_load ? (uint32_t)(int32_t)(int8_t)bv : bv;
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
write_data(m, 0, word);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xdeadbeefu;
|
|
psx_cpu_cycle(m->cpu); /* the load */
|
|
psx_cpu_cycle(m->cpu); /* nop -- commits the pending load */
|
|
++g_checked;
|
|
if (m->cpu->r[2] != want) {
|
|
fail("case=load-extend mode=%s %s lane=%u byte=%02x want=%08x got=%08x",
|
|
MODE_NAME[mi], signed_load ? "lb" : "lbu", lane, bv,
|
|
want, m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* LH / LHU over every halfword value in both lanes. */
|
|
for (unsigned lane = 0; lane < 2u && ok; lane++) {
|
|
for (unsigned hv = 0; hv < 65536u && ok; hv++) {
|
|
uint32_t word = 0xa5a5a5a5u;
|
|
((uint16_t*)&word)[lane] = (uint16_t)hv;
|
|
|
|
for (int signed_load = 0; signed_load < 2 && ok; signed_load++) {
|
|
uint32_t op = enc_i(signed_load ? OP_LH : OP_LHU, 1, 2, (uint16_t)(lane * 2u));
|
|
uint32_t prog[3] = { op, NOP, NOP };
|
|
uint32_t want = signed_load ? (uint32_t)(int32_t)(int16_t)hv : hv;
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
write_data(m, 0, word);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xdeadbeefu;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != want) {
|
|
fail("case=load-extend mode=%s %s lane=%u half=%04x want=%08x got=%08x",
|
|
MODE_NAME[mi], signed_load ? "lh" : "lhu", lane, hv,
|
|
want, m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Negative offsets must sign-extend the 16-bit immediate. */
|
|
{
|
|
uint32_t op = enc_i(OP_LW, 1, 2, 0xfffcu); /* -4 */
|
|
uint32_t prog[3] = { op, NOP, NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
write_data(m, 0, 0x11223344u);
|
|
m->cpu->r[1] = DATA_VA + 4u;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != 0x11223344u) {
|
|
fail("case=load-extend mode=%s lw-negative-offset want=11223344 got=%08x",
|
|
MODE_NAME[mi], m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=load-extend\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: store widths -- the bytes around the target must not be disturbed.
|
|
================================================================================== */
|
|
|
|
static int case_store_width(pair_t* p) {
|
|
printf("CPU_SPEC begin case=store-width\n");
|
|
int ok = 1;
|
|
const uint32_t seed = 0xa5a5a5a5u;
|
|
|
|
for (unsigned lane = 0; lane < 4u && ok; lane++) {
|
|
for (unsigned bv = 0; bv < 256u && ok; bv++) {
|
|
uint32_t want = seed;
|
|
((uint8_t*)&want)[lane] = (uint8_t)bv;
|
|
uint32_t prog[2] = { enc_i(OP_SB, 1, 2, (uint16_t)lane), NOP };
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
write_data(m, 0, seed);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0x11223300u | bv; /* high bytes must be discarded */
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (read_data(m, 0) != want) {
|
|
fail("case=store-width mode=%s sb lane=%u byte=%02x want=%08x got=%08x",
|
|
MODE_NAME[mi], lane, bv, want, read_data(m, 0));
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (unsigned lane = 0; lane < 2u && ok; lane++) {
|
|
for (unsigned hv = 0; hv < 65536u; hv += 7u) {
|
|
uint32_t want = seed;
|
|
((uint16_t*)&want)[lane] = (uint16_t)hv;
|
|
uint32_t prog[2] = { enc_i(OP_SH, 1, 2, (uint16_t)(lane * 2u)), NOP };
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
write_data(m, 0, seed);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xbeef0000u | hv;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (read_data(m, 0) != want) {
|
|
fail("case=store-width mode=%s sh lane=%u half=%04x want=%08x got=%08x",
|
|
MODE_NAME[mi], lane, hv, want, read_data(m, 0));
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!ok) break;
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=store-width\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: address errors. Misaligned LH/LHU/LW -> ADEL, SH/SW -> ADES. The destination
|
|
register must be untouched, memory must be untouched, EPC must point at the faulting
|
|
instruction and CAUSE.ExcCode must carry the right code.
|
|
================================================================================== */
|
|
|
|
static int case_addr_error(pair_t* p) {
|
|
printf("CPU_SPEC begin case=addr-error\n");
|
|
int ok = 1;
|
|
|
|
struct { unsigned op; unsigned align_mask; uint32_t excode; const char* name; int is_store; } t[] = {
|
|
{ OP_LH, 1u, CAUSE_ADEL, "lh", 0 },
|
|
{ OP_LHU, 1u, CAUSE_ADEL, "lhu", 0 },
|
|
{ OP_LW, 3u, CAUSE_ADEL, "lw", 0 },
|
|
{ OP_SH, 1u, CAUSE_ADES, "sh", 1 },
|
|
{ OP_SW, 3u, CAUSE_ADES, "sw", 1 },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof t / sizeof t[0] && ok; i++) {
|
|
for (unsigned off = 0; off < 4u && ok; off++) {
|
|
int misaligned = (off & t[i].align_mask) != 0u;
|
|
uint32_t prog[2] = { enc_i(t[i].op, 1, 2, (uint16_t)off), NOP };
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
write_data(m, 0, 0x11223344u);
|
|
write_data(m, 4, 0x55667788u);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xcafef00du;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
|
|
if (!misaligned)
|
|
continue;
|
|
|
|
if (m->cpu->cop0_r[COP0_EPC] != TEST_PC) {
|
|
fail("case=addr-error mode=%s %s off=%u epc want=%08x got=%08x",
|
|
MODE_NAME[mi], t[i].name, off, TEST_PC, m->cpu->cop0_r[COP0_EPC]);
|
|
ok = 0;
|
|
}
|
|
if ((m->cpu->cop0_r[COP0_CAUSE] & 0x7cu) != t[i].excode) {
|
|
fail("case=addr-error mode=%s %s off=%u excode want=%02x got=%02x",
|
|
MODE_NAME[mi], t[i].name, off, t[i].excode,
|
|
m->cpu->cop0_r[COP0_CAUSE] & 0x7cu);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->pc != EXC_VEC_NORMAL) {
|
|
fail("case=addr-error mode=%s %s off=%u vector want=%08x got=%08x",
|
|
MODE_NAME[mi], t[i].name, off, EXC_VEC_NORMAL, m->cpu->pc);
|
|
ok = 0;
|
|
}
|
|
if (!t[i].is_store) {
|
|
/* the load must not have written rt, not even the pending slot */
|
|
if (m->cpu->load_d == 2u) {
|
|
fail("case=addr-error mode=%s %s off=%u pending-load-armed",
|
|
MODE_NAME[mi], t[i].name, off);
|
|
ok = 0;
|
|
}
|
|
} else {
|
|
if (read_data(m, 0) != 0x11223344u || read_data(m, 4) != 0x55667788u) {
|
|
fail("case=addr-error mode=%s %s off=%u memory-modified %08x %08x",
|
|
MODE_NAME[mi], t[i].name, off, read_data(m, 0), read_data(m, 4));
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=addr-error\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: LWL/LWR/SWL/SWR, exhaustive over alignment x pattern x prior value.
|
|
================================================================================== */
|
|
|
|
static int case_unaligned(pair_t* p) {
|
|
printf("CPU_SPEC begin case=unaligned\n");
|
|
int ok = 1;
|
|
|
|
static const uint32_t mem_patterns[] = {
|
|
0x00000000u, 0xffffffffu, 0x01020304u, 0x80000000u, 0x0000ffffu,
|
|
0xdeadbeefu, 0x12345678u, 0xa55aa55au, 0x7f7f7f7fu, 0x000000ffu
|
|
};
|
|
static const uint32_t rt_patterns[] = {
|
|
0x00000000u, 0xffffffffu, 0xcafef00du, 0x80000001u, 0x5a5a5a5au
|
|
};
|
|
|
|
for (size_t mp = 0; mp < sizeof mem_patterns / sizeof mem_patterns[0] && ok; mp++) {
|
|
for (size_t rp = 0; rp < sizeof rt_patterns / sizeof rt_patterns[0] && ok; rp++) {
|
|
for (unsigned n = 0; n < 4u && ok; n++) {
|
|
uint32_t mem = mem_patterns[mp];
|
|
uint32_t rtv = rt_patterns[rp];
|
|
|
|
/* --- LWL --- */
|
|
{
|
|
uint32_t prog[3] = { enc_i(OP_LWL, 1, 2, (uint16_t)n), NOP, NOP };
|
|
uint32_t want = ref_lwl(rtv, mem, n);
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
write_data(m, 0, mem);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = rtv;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != want) {
|
|
fail("case=unaligned mode=%s lwl n=%u mem=%08x rt=%08x want=%08x got=%08x",
|
|
MODE_NAME[mi], n, mem, rtv, want, m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- LWR --- */
|
|
{
|
|
uint32_t prog[3] = { enc_i(OP_LWR, 1, 2, (uint16_t)n), NOP, NOP };
|
|
uint32_t want = ref_lwr(rtv, mem, n);
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
write_data(m, 0, mem);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = rtv;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != want) {
|
|
fail("case=unaligned mode=%s lwr n=%u mem=%08x rt=%08x want=%08x got=%08x",
|
|
MODE_NAME[mi], n, mem, rtv, want, m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- SWL --- */
|
|
{
|
|
uint32_t prog[2] = { enc_i(OP_SWL, 1, 2, (uint16_t)n), NOP };
|
|
uint32_t want = ref_swl(mem, rtv, n);
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
write_data(m, 0, mem);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = rtv;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (read_data(m, 0) != want) {
|
|
fail("case=unaligned mode=%s swl n=%u mem=%08x rt=%08x want=%08x got=%08x",
|
|
MODE_NAME[mi], n, mem, rtv, want, read_data(m, 0));
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* --- SWR --- */
|
|
{
|
|
uint32_t prog[2] = { enc_i(OP_SWR, 1, 2, (uint16_t)n), NOP };
|
|
uint32_t want = ref_swr(mem, rtv, n);
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
write_data(m, 0, mem);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = rtv;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (read_data(m, 0) != want) {
|
|
fail("case=unaligned mode=%s swr n=%u mem=%08x rt=%08x want=%08x got=%08x",
|
|
MODE_NAME[mi], n, mem, rtv, want, read_data(m, 0));
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The canonical unaligned-word idiom: LWL hi ; LWR lo, back to back into the SAME
|
|
register with no interlock nop. On hardware the second one sees the first one's
|
|
(still pending) result, which is what makes the idiom work at all. This is the
|
|
single most load-bearing sequence for any engine that walks packed vertex data. */
|
|
for (unsigned base = 0; base < 4u && ok; base++) {
|
|
uint32_t w0 = 0x44332211u, w1 = 0x88776655u;
|
|
uint8_t bytes[8];
|
|
memcpy(bytes + 0, &w0, 4);
|
|
memcpy(bytes + 4, &w1, 4);
|
|
uint32_t want;
|
|
memcpy(&want, bytes + base, 4); /* the unaligned word the idiom must produce */
|
|
|
|
uint32_t prog[4] = {
|
|
enc_i(OP_LWL, 1, 2, (uint16_t)(base + 3u)),
|
|
enc_i(OP_LWR, 1, 2, (uint16_t)base),
|
|
NOP, NOP
|
|
};
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 4);
|
|
write_data(m, 0, w0);
|
|
write_data(m, 4, w1);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xffffffffu;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != want) {
|
|
fail("case=unaligned mode=%s lwl+lwr idiom base=%u want=%08x got=%08x",
|
|
MODE_NAME[mi], base, want, m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The store counterpart: SWL hi ; SWR lo must lay an unaligned word into memory. */
|
|
for (unsigned base = 0; base < 4u && ok; base++) {
|
|
uint32_t value = 0xddccbbaau;
|
|
uint8_t expect[8];
|
|
uint32_t seed0 = 0x11111111u, seed1 = 0x22222222u;
|
|
memcpy(expect + 0, &seed0, 4);
|
|
memcpy(expect + 4, &seed1, 4);
|
|
memcpy(expect + base, &value, 4);
|
|
uint32_t want0, want1;
|
|
memcpy(&want0, expect + 0, 4);
|
|
memcpy(&want1, expect + 4, 4);
|
|
|
|
uint32_t prog[3] = {
|
|
enc_i(OP_SWL, 1, 2, (uint16_t)(base + 3u)),
|
|
enc_i(OP_SWR, 1, 2, (uint16_t)base),
|
|
NOP
|
|
};
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
write_data(m, 0, seed0);
|
|
write_data(m, 4, seed1);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = value;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (read_data(m, 0) != want0 || read_data(m, 4) != want1) {
|
|
fail("case=unaligned mode=%s swl+swr idiom base=%u want=%08x/%08x got=%08x/%08x",
|
|
MODE_NAME[mi], base, want0, want1, read_data(m, 0), read_data(m, 4));
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=unaligned\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: the load delay slot. The instruction directly after a load must observe the OLD
|
|
value of the loaded register. Every consumer class is swept.
|
|
================================================================================== */
|
|
|
|
typedef enum {
|
|
CONS_ALU_RT, /* or rd, r0, rt -> rd = OLD rt */
|
|
CONS_ALU_RS, /* or rd, rt, r0 -> rd = OLD rt */
|
|
CONS_STORE_RT, /* sw rt, 8(r1) -> mem = OLD rt */
|
|
CONS_STORE_BASE, /* sw r3, 0(rt) -> address from OLD rt */
|
|
CONS_SWL_RT, /* swl rt, 8(r1) -> mem from OLD rt */
|
|
CONS_SWR_RT, /* swr rt, 11(r1) -> mem from OLD rt */
|
|
CONS_SHIFT_RS, /* sllv rd, r3, rt -> amount from OLD rt */
|
|
CONS_MULT_RS, /* mult rt, r3 -> OLD rt */
|
|
CONS_BRANCH, /* bne rt, r0, +2 -> OLD rt decides */
|
|
CONS_SB_RT, /* sb rt, 8(r1) */
|
|
CONS_LOAD_BASE, /* lw r4, 0(rt) -> address from OLD rt */
|
|
CONS_MTC2_RT, /* mtc2 rt, $0 -> GTE gets OLD rt */
|
|
CONS_CTC2_RT, /* ctc2 rt, $5 -> GTE gets OLD rt */
|
|
CONS_MTC0_RT, /* mtc0 rt, $3 (BPC) -> COP0 gets OLD rt */
|
|
CONS_MTHI_RS, /* mthi rt -> HI gets OLD rt */
|
|
CONS_MTLO_RS, /* mtlo rt -> LO gets OLD rt */
|
|
CONS_COUNT
|
|
} consumer_t;
|
|
|
|
/* COP0/COP2 move encodings (field layout only). */
|
|
#define ENC_MTC2(rt, rd) ((0x12u << 26) | (0x04u << 21) | ((rt) << 16) | ((rd) << 11))
|
|
#define ENC_MFC2(rt, rd) ((0x12u << 26) | (0x00u << 21) | ((rt) << 16) | ((rd) << 11))
|
|
#define ENC_CTC2(rt, rd) ((0x12u << 26) | (0x06u << 21) | ((rt) << 16) | ((rd) << 11))
|
|
#define ENC_CFC2(rt, rd) ((0x12u << 26) | (0x02u << 21) | ((rt) << 16) | ((rd) << 11))
|
|
#define ENC_MTC0(rt, rd) ((0x10u << 26) | (0x04u << 21) | ((rt) << 16) | ((rd) << 11))
|
|
|
|
static int case_load_delay(pair_t* p) {
|
|
printf("CPU_SPEC begin case=load-delay\n");
|
|
int ok = 1;
|
|
|
|
const uint32_t OLD = 0x00002010u; /* also a valid DATA_PA-ish offset when used as base */
|
|
const uint32_t NEWV = 0x7fffabcdu;
|
|
|
|
for (int c = 0; c < CONS_COUNT && ok; c++) {
|
|
uint32_t prog[6];
|
|
uint32_t old_rt = OLD;
|
|
|
|
/* r1 = DATA_VA, r2 = rt (loaded), r3 = 0x00000003, r5 = result */
|
|
prog[0] = enc_i(OP_LW, 1, 2, 0); /* lw r2, 0(r1) -> NEWV */
|
|
switch (c) {
|
|
case CONS_ALU_RT: prog[1] = enc_r(0, 2, 5, 0, F_OR); break;
|
|
case CONS_ALU_RS: prog[1] = enc_r(2, 0, 5, 0, F_OR); break;
|
|
case CONS_STORE_RT: prog[1] = enc_i(OP_SW, 1, 2, 8); break;
|
|
case CONS_STORE_BASE: prog[1] = enc_i(OP_SW, 2, 3, 0); break;
|
|
case CONS_SWL_RT: prog[1] = enc_i(OP_SWL, 1, 2, 8); break;
|
|
case CONS_SWR_RT: prog[1] = enc_i(OP_SWR, 1, 2, 11); break;
|
|
case CONS_SHIFT_RS: prog[1] = enc_r(2, 3, 5, 0, F_SLLV); break;
|
|
case CONS_MULT_RS: prog[1] = enc_r(2, 3, 0, 0, F_MULT); break;
|
|
case CONS_BRANCH: prog[1] = enc_i(OP_BNE, 2, 0, 2); break;
|
|
case CONS_SB_RT: prog[1] = enc_i(OP_SB, 1, 2, 8); break;
|
|
case CONS_LOAD_BASE: prog[1] = enc_i(OP_LW, 2, 4, 0); break;
|
|
case CONS_MTC2_RT: prog[1] = ENC_MTC2(2u, 0u); break;
|
|
case CONS_CTC2_RT: prog[1] = ENC_CTC2(2u, 5u); break;
|
|
case CONS_MTC0_RT: prog[1] = ENC_MTC0(2u, COP0_BPC); break;
|
|
case CONS_MTHI_RS: prog[1] = enc_r(2, 0, 0, 0, F_MTHI); break;
|
|
case CONS_MTLO_RS: prog[1] = enc_r(2, 0, 0, 0, F_MTLO); break;
|
|
default: prog[1] = NOP; break;
|
|
}
|
|
/* The COP2 readbacks need their own slot; everything else leaves it a nop. */
|
|
prog[2] = (c == CONS_MTC2_RT) ? ENC_MFC2(5u, 0u)
|
|
: (c == CONS_CTC2_RT) ? ENC_CFC2(5u, 5u)
|
|
: NOP;
|
|
/* Only reached when the CONS_BRANCH consumer was NOT taken; r9 is the witness. */
|
|
prog[3] = enc_i(OP_ORI, 0, 9, 0x4444);
|
|
prog[4] = NOP;
|
|
prog[5] = NOP;
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 6);
|
|
write_data(m, 0, NEWV);
|
|
write_data(m, 8, 0x99999999u);
|
|
write_data(m, 12, 0x99999999u);
|
|
write_data(m, 16, 0x5a5a5a5au); /* DATA_VA + 16 == 0x80002010 */
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = old_rt;
|
|
m->cpu->r[3] = 3u;
|
|
m->cpu->r[5] = 0xeeeeeeeeu;
|
|
m->cpu->r[4] = 0xeeeeeeeeu;
|
|
|
|
psx_cpu_cycle(m->cpu); /* load */
|
|
psx_cpu_cycle(m->cpu); /* consumer -- must see OLD */
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
|
|
uint32_t got = 0, want = 0;
|
|
const char* what = "";
|
|
switch (c) {
|
|
case CONS_ALU_RT: case CONS_ALU_RS:
|
|
got = m->cpu->r[5]; want = old_rt; what = "rd"; break;
|
|
case CONS_STORE_RT:
|
|
got = read_data(m, 8); want = old_rt; what = "mem8"; break;
|
|
case CONS_STORE_BASE:
|
|
/* base = OLD rt = 0x00002010 (KUSEG alias of DATA_PA+0x10) */
|
|
got = read_data(m, 16); want = 3u; what = "mem@oldbase"; break;
|
|
case CONS_SWL_RT:
|
|
got = read_data(m, 8); want = ref_swl(0x99999999u, old_rt, 0); what = "mem8"; break;
|
|
case CONS_SWR_RT:
|
|
got = read_data(m, 8); want = ref_swr(0x99999999u, old_rt, 3); what = "mem8"; break;
|
|
case CONS_SHIFT_RS:
|
|
got = m->cpu->r[5]; want = ref_shift(F_SLLV, 3u, old_rt); what = "rd"; break;
|
|
case CONS_MULT_RS:
|
|
got = m->cpu->lo;
|
|
want = (uint32_t)(((int64_t)(int32_t)old_rt * 3) & 0xffffffffu);
|
|
what = "lo"; break;
|
|
case CONS_BRANCH:
|
|
/* OLD rt is non-zero -> branch taken -> prog[3] is skipped.
|
|
If the branch saw the NEW value it is also non-zero, so the
|
|
discriminator is the OFFSET: taken skips r9. */
|
|
got = m->cpu->r[9]; want = 0u; what = "r9(branch-taken)"; break;
|
|
case CONS_SB_RT:
|
|
got = read_data(m, 8) & 0xffu; want = old_rt & 0xffu; what = "mem8.b"; break;
|
|
case CONS_LOAD_BASE:
|
|
got = m->cpu->r[4]; want = 0x5a5a5a5au; what = "r4"; break;
|
|
default: break;
|
|
}
|
|
|
|
if (got != want) {
|
|
fail("case=load-delay mode=%s consumer=%d %s want=%08x got=%08x "
|
|
"(old rt=%08x new rt=%08x)",
|
|
MODE_NAME[mi], c, what, want, got, old_rt, NEWV);
|
|
ok = 0;
|
|
}
|
|
|
|
/* After the delay slot the register must hold the loaded value (unless the
|
|
consumer itself wrote it -- none of these do). */
|
|
if (ok && c != CONS_LOAD_BASE && m->cpu->r[2] != NEWV) {
|
|
fail("case=load-delay mode=%s consumer=%d rt-after want=%08x got=%08x",
|
|
MODE_NAME[mi], c, NEWV, m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Two loads into the same register: the first result is DISCARDED, the register ends
|
|
up holding the second. */
|
|
{
|
|
uint32_t prog[4] = {
|
|
enc_i(OP_LW, 1, 2, 0),
|
|
enc_i(OP_LW, 1, 2, 4),
|
|
NOP, NOP
|
|
};
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 4);
|
|
write_data(m, 0, 0xaaaaaaaau);
|
|
write_data(m, 4, 0xbbbbbbbbu);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xccccccccu;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != 0xbbbbbbbbu) {
|
|
fail("case=load-delay mode=%s double-load want=bbbbbbbb got=%08x",
|
|
MODE_NAME[mi], m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* A load into a register followed by an ALU write of the SAME register: the ALU
|
|
result wins (the load's write-back is overwritten). */
|
|
{
|
|
uint32_t prog[4] = {
|
|
enc_i(OP_LW, 1, 2, 0),
|
|
enc_i(OP_ORI, 0, 2, 0x1234), /* ori r2, r0, 0x1234 */
|
|
NOP, NOP
|
|
};
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 4);
|
|
write_data(m, 0, 0xaaaaaaaau);
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xccccccccu;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[2] != 0x1234u) {
|
|
fail("case=load-delay mode=%s load-then-alu-same-reg want=00001234 got=%08x",
|
|
MODE_NAME[mi], m->cpu->r[2]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=load-delay\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: shift matrix (all amounts x boundary operands, variable forms swept past 31).
|
|
================================================================================== */
|
|
|
|
static int case_shift_matrix(pair_t* p) {
|
|
printf("CPU_SPEC begin case=shift-matrix\n");
|
|
int ok = 1;
|
|
|
|
static const uint32_t operands[] = {
|
|
0x00000000u, 0xffffffffu, 0x80000000u, 0x7fffffffu, 0x00000001u,
|
|
0x0000ffffu, 0xdeadbeefu, 0xa5a5a5a5u, 0x00000080u, 0xfffffffeu
|
|
};
|
|
static const unsigned imm_f[3] = { F_SLL, F_SRL, F_SRA };
|
|
static const unsigned var_f[3] = { F_SLLV, F_SRLV, F_SRAV };
|
|
|
|
for (size_t v = 0; v < sizeof operands / sizeof operands[0] && ok; v++) {
|
|
for (unsigned amount = 0; amount < 64u && ok; amount++) {
|
|
for (int f = 0; f < 3 && ok; f++) {
|
|
/* immediate forms only exist for 0..31 */
|
|
if (amount < 32u) {
|
|
uint32_t op = enc_r(0, 2, 5, amount, imm_f[f]);
|
|
uint32_t prog[2] = { op, NOP };
|
|
uint32_t want = ref_shift(imm_f[f], operands[v], amount);
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->r[2] = operands[v];
|
|
m->cpu->r[5] = 0xcafef00du;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[5] != want) {
|
|
fail("case=shift-matrix mode=%s funct=%02x imm amount=%u rt=%08x "
|
|
"want=%08x got=%08x",
|
|
MODE_NAME[mi], imm_f[f], amount, operands[v], want, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
uint32_t op = enc_r(1, 2, 5, 0, var_f[f]);
|
|
uint32_t prog[2] = { op, NOP };
|
|
uint32_t want = ref_shift(var_f[f], operands[v], amount);
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->r[2] = operands[v];
|
|
/* high bits in rs prove the mandatory & 31 is applied */
|
|
m->cpu->r[1] = amount | 0x5a5a0000u;
|
|
m->cpu->r[5] = 0xcafef00du;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[5] != want) {
|
|
fail("case=shift-matrix mode=%s funct=%02x var amount=%u rt=%08x "
|
|
"want=%08x got=%08x",
|
|
MODE_NAME[mi], var_f[f], amount, operands[v], want, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=shift-matrix\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: ALU matrix + overflow semantics.
|
|
================================================================================== */
|
|
|
|
static int case_alu_matrix(pair_t* p) {
|
|
printf("CPU_SPEC begin case=alu-matrix\n");
|
|
int ok = 1;
|
|
|
|
static const uint32_t vals[] = {
|
|
0x00000000u, 0x00000001u, 0xffffffffu, 0x7fffffffu, 0x80000000u,
|
|
0x00008000u, 0xffff8000u, 0x00007fffu, 0x12345678u, 0xdeadbeefu
|
|
};
|
|
|
|
for (size_t a = 0; a < sizeof vals / sizeof vals[0] && ok; a++) {
|
|
for (size_t b = 0; b < sizeof vals / sizeof vals[0] && ok; b++) {
|
|
uint32_t s = vals[a], t = vals[b];
|
|
int64_t ssum = (int64_t)(int32_t)s + (int64_t)(int32_t)t;
|
|
int64_t sdif = (int64_t)(int32_t)s - (int64_t)(int32_t)t;
|
|
int add_ovf = (ssum < -2147483648LL) || (ssum > 2147483647LL);
|
|
int sub_ovf = (sdif < -2147483648LL) || (sdif > 2147483647LL);
|
|
|
|
struct { unsigned funct; uint32_t want; int overflow; const char* n; } r[] = {
|
|
{ F_ADD, (uint32_t)(s + t), add_ovf, "add" },
|
|
{ F_ADDU, (uint32_t)(s + t), 0, "addu" },
|
|
{ F_SUB, (uint32_t)(s - t), sub_ovf, "sub" },
|
|
{ F_SUBU, (uint32_t)(s - t), 0, "subu" },
|
|
{ F_AND, s & t, 0, "and" },
|
|
{ F_OR, s | t, 0, "or" },
|
|
{ F_XOR, s ^ t, 0, "xor" },
|
|
{ F_NOR, ~(s | t), 0, "nor" },
|
|
{ F_SLT, ((int32_t)s < (int32_t)t) ? 1u : 0u, 0, "slt" },
|
|
{ F_SLTU, (s < t) ? 1u : 0u, 0, "sltu" },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof r / sizeof r[0] && ok; i++) {
|
|
uint32_t prog[2] = { enc_r(1, 2, 5, 0, r[i].funct), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->r[1] = s;
|
|
m->cpu->r[2] = t;
|
|
m->cpu->r[5] = 0xcafef00du;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (r[i].overflow) {
|
|
if (m->cpu->r[5] != 0xcafef00du) {
|
|
fail("case=alu-matrix mode=%s %s s=%08x t=%08x overflow wrote rd=%08x",
|
|
MODE_NAME[mi], r[i].n, s, t, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
if ((m->cpu->cop0_r[COP0_CAUSE] & 0x7cu) != CAUSE_OV) {
|
|
fail("case=alu-matrix mode=%s %s s=%08x t=%08x excode want=%02x got=%02x",
|
|
MODE_NAME[mi], r[i].n, s, t, CAUSE_OV,
|
|
m->cpu->cop0_r[COP0_CAUSE] & 0x7cu);
|
|
ok = 0;
|
|
}
|
|
} else if (m->cpu->r[5] != r[i].want) {
|
|
fail("case=alu-matrix mode=%s %s s=%08x t=%08x want=%08x got=%08x",
|
|
MODE_NAME[mi], r[i].n, s, t, r[i].want, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Immediate forms: the 16-bit field is SIGN-extended for ADDI/ADDIU/SLTI/SLTIU
|
|
and ZERO-extended for ANDI/ORI/XORI. */
|
|
{
|
|
uint16_t imm = (uint16_t)(t & 0xffffu);
|
|
int32_t simm = (int32_t)(int16_t)imm;
|
|
int64_t isum = (int64_t)(int32_t)s + (int64_t)simm;
|
|
int iovf = (isum < -2147483648LL) || (isum > 2147483647LL);
|
|
|
|
struct { unsigned op; uint32_t want; int overflow; const char* n; } q[] = {
|
|
{ OP_ADDI, (uint32_t)(s + (uint32_t)simm), iovf, "addi" },
|
|
{ OP_ADDIU, (uint32_t)(s + (uint32_t)simm), 0, "addiu" },
|
|
{ OP_SLTI, ((int32_t)s < simm) ? 1u : 0u, 0, "slti" },
|
|
{ OP_SLTIU, (s < (uint32_t)simm) ? 1u : 0u, 0, "sltiu" },
|
|
{ OP_ANDI, s & imm, 0, "andi" },
|
|
{ OP_ORI, s | imm, 0, "ori" },
|
|
{ OP_XORI, s ^ imm, 0, "xori" },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof q / sizeof q[0] && ok; i++) {
|
|
uint32_t prog[2] = { enc_i(q[i].op, 1, 5, imm), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->r[1] = s;
|
|
m->cpu->r[5] = 0xcafef00du;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (q[i].overflow) {
|
|
if (m->cpu->r[5] != 0xcafef00du) {
|
|
fail("case=alu-matrix mode=%s %s s=%08x imm=%04x overflow wrote rt=%08x",
|
|
MODE_NAME[mi], q[i].n, s, imm, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
} else if (m->cpu->r[5] != q[i].want) {
|
|
fail("case=alu-matrix mode=%s %s s=%08x imm=%04x want=%08x got=%08x",
|
|
MODE_NAME[mi], q[i].n, s, imm, q[i].want, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* LUI: imm goes in the high half, low half is zero. */
|
|
for (unsigned imm = 0; imm < 65536u && ok; imm += 13u) {
|
|
uint32_t prog[2] = { enc_i(OP_LUI, 0, 5, (uint16_t)imm), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->r[5] = 0xcafef00du;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[5] != (imm << 16)) {
|
|
fail("case=alu-matrix mode=%s lui imm=%04x want=%08x got=%08x",
|
|
MODE_NAME[mi], imm, imm << 16, m->cpu->r[5]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* r0 must stay zero no matter what is written to it. */
|
|
{
|
|
uint32_t prog[2] = { enc_i(OP_ORI, 0, 0, 0xffff), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[0] != 0u) {
|
|
fail("case=alu-matrix mode=%s r0-written got=%08x", MODE_NAME[mi], m->cpu->r[0]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=alu-matrix\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: MULT/MULTU/DIV/DIVU including the documented degenerate results.
|
|
================================================================================== */
|
|
|
|
static uint32_t xs_state = 0x13579bdfu;
|
|
static uint32_t xs(void) {
|
|
xs_state ^= xs_state << 13;
|
|
xs_state ^= xs_state >> 17;
|
|
xs_state ^= xs_state << 5;
|
|
return xs_state;
|
|
}
|
|
|
|
static int muldiv_one(pair_t* p, uint32_t s, uint32_t t) {
|
|
int ok = 1;
|
|
|
|
int64_t sm = (int64_t)(int32_t)s * (int64_t)(int32_t)t;
|
|
uint64_t um = (uint64_t)s * (uint64_t)t;
|
|
|
|
uint32_t div_hi, div_lo, divu_hi, divu_lo;
|
|
if (t == 0u) {
|
|
/* spec: LO = (S >= 0) ? 0xffffffff : 1 ; HI = S */
|
|
div_hi = s;
|
|
div_lo = ((int32_t)s >= 0) ? 0xffffffffu : 1u;
|
|
} else if (s == 0x80000000u && t == 0xffffffffu) {
|
|
div_hi = 0u;
|
|
div_lo = 0x80000000u;
|
|
} else {
|
|
div_hi = (uint32_t)((int32_t)s % (int32_t)t);
|
|
div_lo = (uint32_t)((int32_t)s / (int32_t)t);
|
|
}
|
|
if (t == 0u) {
|
|
divu_hi = s;
|
|
divu_lo = 0xffffffffu;
|
|
} else {
|
|
divu_hi = s % t;
|
|
divu_lo = s / t;
|
|
}
|
|
|
|
struct { unsigned funct; uint32_t hi, lo; const char* n; } c[] = {
|
|
{ F_MULT, (uint32_t)((uint64_t)sm >> 32), (uint32_t)((uint64_t)sm & 0xffffffffu), "mult" },
|
|
{ F_MULTU, (uint32_t)(um >> 32), (uint32_t)(um & 0xffffffffu), "multu" },
|
|
{ F_DIV, div_hi, div_lo, "div" },
|
|
{ F_DIVU, divu_hi, divu_lo, "divu" },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof c / sizeof c[0] && ok; i++) {
|
|
uint32_t prog[2] = { enc_r(1, 2, 0, 0, c[i].funct), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->r[1] = s;
|
|
m->cpu->r[2] = t;
|
|
m->cpu->hi = 0x11111111u;
|
|
m->cpu->lo = 0x22222222u;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->hi != c[i].hi || m->cpu->lo != c[i].lo) {
|
|
fail("case=muldiv mode=%s %s s=%08x t=%08x want hi=%08x lo=%08x got hi=%08x lo=%08x",
|
|
MODE_NAME[mi], c[i].n, s, t, c[i].hi, c[i].lo, m->cpu->hi, m->cpu->lo);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int case_muldiv(pair_t* p) {
|
|
printf("CPU_SPEC begin case=muldiv\n");
|
|
int ok = 1;
|
|
|
|
static const uint32_t vals[] = {
|
|
0x00000000u, 0x00000001u, 0xffffffffu, 0x7fffffffu, 0x80000000u,
|
|
0x00000002u, 0xfffffffeu, 0x0000ffffu, 0xffff0000u, 0x00010000u,
|
|
0x7ffffffeu, 0x80000001u
|
|
};
|
|
|
|
for (size_t a = 0; a < sizeof vals / sizeof vals[0] && ok; a++)
|
|
for (size_t b = 0; b < sizeof vals / sizeof vals[0] && ok; b++)
|
|
ok = muldiv_one(p, vals[a], vals[b]);
|
|
|
|
for (int i = 0; i < 4000 && ok; i++)
|
|
ok = muldiv_one(p, xs(), xs());
|
|
|
|
/* MFHI/MFLO/MTHI/MTLO round trip. */
|
|
{
|
|
uint32_t prog[5] = {
|
|
enc_r(0, 0, 5, 0, F_MFHI),
|
|
enc_r(0, 0, 6, 0, F_MFLO),
|
|
enc_r(1, 0, 0, 0, F_MTHI),
|
|
enc_r(2, 0, 0, 0, F_MTLO),
|
|
NOP
|
|
};
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 5);
|
|
m->cpu->hi = 0xaaaaaaaau;
|
|
m->cpu->lo = 0xbbbbbbbbu;
|
|
m->cpu->r[1] = 0x12345678u;
|
|
m->cpu->r[2] = 0x9abcdef0u;
|
|
for (int k = 0; k < 5; k++)
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->r[5] != 0xaaaaaaaau || m->cpu->r[6] != 0xbbbbbbbbu ||
|
|
m->cpu->hi != 0x12345678u || m->cpu->lo != 0x9abcdef0u) {
|
|
fail("case=muldiv mode=%s hi/lo move r5=%08x r6=%08x hi=%08x lo=%08x",
|
|
MODE_NAME[mi], m->cpu->r[5], m->cpu->r[6], m->cpu->hi, m->cpu->lo);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=muldiv\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: branch and jump semantics.
|
|
================================================================================== */
|
|
|
|
static int case_branch_matrix(pair_t* p) {
|
|
printf("CPU_SPEC begin case=branch-matrix\n");
|
|
int ok = 1;
|
|
|
|
static const uint32_t vals[] = {
|
|
0x00000000u, 0x00000001u, 0xffffffffu, 0x7fffffffu, 0x80000000u, 0x00000002u
|
|
};
|
|
|
|
/* Conditional branches: rs (and rt for BEQ/BNE) over the boundary set; both
|
|
directions of the offset. The delay slot ALWAYS executes. */
|
|
for (size_t a = 0; a < sizeof vals / sizeof vals[0] && ok; a++) {
|
|
for (size_t b = 0; b < sizeof vals / sizeof vals[0] && ok; b++) {
|
|
uint32_t s = vals[a], t = vals[b];
|
|
int32_t si = (int32_t)s;
|
|
|
|
struct { unsigned op; unsigned rt_field; int taken; int links; const char* n; } br[] = {
|
|
{ OP_BEQ, 2u, s == t, 0, "beq" },
|
|
{ OP_BNE, 2u, s != t, 0, "bne" },
|
|
{ OP_BLEZ, 0u, si <= 0, 0, "blez" },
|
|
{ OP_BGTZ, 0u, si > 0, 0, "bgtz" },
|
|
{ OP_BCOND, 0x00u, si < 0, 0, "bltz" },
|
|
{ OP_BCOND, 0x01u, si >= 0, 0, "bgez" },
|
|
{ OP_BCOND, 0x10u, si < 0, 1, "bltzal" },
|
|
{ OP_BCOND, 0x11u, si >= 0, 1, "bgezal" },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof br / sizeof br[0] && ok; i++) {
|
|
/* branch +4 instructions: target = TEST_PC + 4 + (4 << 2) */
|
|
uint32_t prog[10];
|
|
for (int k = 0; k < 10; k++)
|
|
prog[k] = NOP;
|
|
prog[0] = enc_i(br[i].op, 1, br[i].rt_field, 4);
|
|
prog[1] = enc_i(OP_ORI, 0, 6, 0x1111); /* delay slot: always runs */
|
|
prog[2] = enc_i(OP_ORI, 0, 7, 0x2222); /* skipped when taken */
|
|
prog[6] = enc_i(OP_ORI, 0, 8, 0x3333); /* the branch target */
|
|
|
|
uint32_t want_pc = br[i].taken ? (TEST_PC + 4u + (4u << 2)) : (TEST_PC + 8u);
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 10);
|
|
m->cpu->r[1] = s;
|
|
m->cpu->r[2] = t;
|
|
m->cpu->r[31] = 0xdeadbeefu;
|
|
psx_cpu_cycle(m->cpu); /* the branch */
|
|
psx_cpu_cycle(m->cpu); /* the delay slot */
|
|
++g_checked;
|
|
|
|
if (m->cpu->r[6] != 0x1111u) {
|
|
fail("case=branch-matrix mode=%s %s s=%08x t=%08x delay slot did not run",
|
|
MODE_NAME[mi], br[i].n, s, t);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->pc != want_pc) {
|
|
fail("case=branch-matrix mode=%s %s s=%08x t=%08x taken=%d "
|
|
"pc want=%08x got=%08x",
|
|
MODE_NAME[mi], br[i].n, s, t, br[i].taken, want_pc, m->cpu->pc);
|
|
ok = 0;
|
|
}
|
|
/* BLTZAL/BGEZAL link UNCONDITIONALLY (spec) -- ra = addr of the
|
|
instruction after the delay slot. */
|
|
if (br[i].links) {
|
|
if (m->cpu->r[31] != TEST_PC + 8u) {
|
|
fail("case=branch-matrix mode=%s %s s=%08x taken=%d ra want=%08x got=%08x",
|
|
MODE_NAME[mi], br[i].n, s, br[i].taken,
|
|
TEST_PC + 8u, m->cpu->r[31]);
|
|
ok = 0;
|
|
}
|
|
} else if (m->cpu->r[31] != 0xdeadbeefu) {
|
|
fail("case=branch-matrix mode=%s %s clobbered ra=%08x",
|
|
MODE_NAME[mi], br[i].n, m->cpu->r[31]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* J / JAL: target = (address-of-delay-slot & 0xf0000000) | (imm26 << 2). */
|
|
{
|
|
uint32_t target = 0x80001800u;
|
|
uint32_t imm26 = (target >> 2) & 0x3ffffffu;
|
|
struct { unsigned op; int links; const char* n; } jj[] = {
|
|
{ OP_J, 0, "j" }, { OP_JAL, 1, "jal" }
|
|
};
|
|
for (size_t i = 0; i < 2 && ok; i++) {
|
|
uint32_t prog[3] = { enc_j(jj[i].op, imm26), enc_i(OP_ORI, 0, 6, 0x1111), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
m->cpu->r[31] = 0xdeadbeefu;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->pc != target) {
|
|
fail("case=branch-matrix mode=%s %s target want=%08x got=%08x",
|
|
MODE_NAME[mi], jj[i].n, target, m->cpu->pc);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->r[6] != 0x1111u) {
|
|
fail("case=branch-matrix mode=%s %s delay slot did not run", MODE_NAME[mi], jj[i].n);
|
|
ok = 0;
|
|
}
|
|
if (jj[i].links && m->cpu->r[31] != TEST_PC + 8u) {
|
|
fail("case=branch-matrix mode=%s jal ra want=%08x got=%08x",
|
|
MODE_NAME[mi], TEST_PC + 8u, m->cpu->r[31]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* JR / JALR, including JALR with rd == rs (the link must not disturb the jump). */
|
|
{
|
|
uint32_t target = 0x80001900u;
|
|
struct { uint32_t op; int links; unsigned link_reg; const char* n; } jr[] = {
|
|
{ enc_r(1, 0, 0, 0, F_JR), 0, 0, "jr" },
|
|
{ enc_r(1, 0, 31, 0, F_JALR), 1, 31, "jalr" },
|
|
{ enc_r(1, 0, 1, 0, F_JALR), 1, 1, "jalr rd==rs" },
|
|
};
|
|
for (size_t i = 0; i < sizeof jr / sizeof jr[0] && ok; i++) {
|
|
uint32_t prog[3] = { jr[i].op, enc_i(OP_ORI, 0, 6, 0x1111), NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
m->cpu->r[1] = target;
|
|
m->cpu->r[31] = 0xdeadbeefu;
|
|
psx_cpu_cycle(m->cpu);
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->pc != target) {
|
|
fail("case=branch-matrix mode=%s %s target want=%08x got=%08x",
|
|
MODE_NAME[mi], jr[i].n, target, m->cpu->pc);
|
|
ok = 0;
|
|
}
|
|
if (jr[i].links && m->cpu->r[jr[i].link_reg] != TEST_PC + 8u) {
|
|
fail("case=branch-matrix mode=%s %s link want=%08x got=%08x",
|
|
MODE_NAME[mi], jr[i].n, TEST_PC + 8u, m->cpu->r[jr[i].link_reg]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=branch-matrix\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: the exception model -- EPC, CAUSE.BD, the SR mode stack, both vectors, RFE.
|
|
================================================================================== */
|
|
|
|
static int case_exception_model(pair_t* p) {
|
|
printf("CPU_SPEC begin case=exception-model\n");
|
|
int ok = 1;
|
|
|
|
struct { uint32_t op; uint32_t excode; const char* n; } faults[] = {
|
|
{ enc_r(0, 0, 0, 0, F_SYSCALL), CAUSE_SYSCALL, "syscall" },
|
|
{ enc_r(0, 0, 0, 0, F_BREAK), CAUSE_BP, "break" },
|
|
{ enc_i(OP_LW, 1, 2, 1), CAUSE_ADEL, "adel" },
|
|
{ enc_i(OP_SW, 1, 2, 1), CAUSE_ADES, "ades" },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof faults / sizeof faults[0] && ok; i++) {
|
|
for (int in_delay = 0; in_delay < 2 && ok; in_delay++) {
|
|
for (int bev = 0; bev < 2 && ok; bev++) {
|
|
uint32_t prog[6];
|
|
for (int k = 0; k < 6; k++)
|
|
prog[k] = NOP;
|
|
|
|
uint32_t fault_pc;
|
|
if (in_delay) {
|
|
prog[0] = enc_i(OP_BEQ, 0, 0, 4); /* always taken */
|
|
prog[1] = faults[i].op; /* fault in the delay slot */
|
|
fault_pc = TEST_PC + 4u;
|
|
} else {
|
|
prog[0] = faults[i].op;
|
|
fault_pc = TEST_PC;
|
|
}
|
|
|
|
uint32_t sr0 = 0x10000000u | (bev ? SR_BEV : 0u) | 0x0000003fu;
|
|
uint32_t want_sr = (sr0 & 0xffffffc0u) | ((sr0 << 2) & 0x3fu);
|
|
uint32_t want_epc = in_delay ? (fault_pc - 4u) : fault_pc;
|
|
uint32_t want_vec = bev ? EXC_VEC_BEV : EXC_VEC_NORMAL;
|
|
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 6);
|
|
m->cpu->cop0_r[COP0_SR] = sr0;
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0x12345678u;
|
|
psx_cpu_cycle(m->cpu);
|
|
if (in_delay)
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
|
|
uint32_t cause = m->cpu->cop0_r[COP0_CAUSE];
|
|
if ((cause & 0x7cu) != faults[i].excode) {
|
|
fail("case=exception-model mode=%s %s delay=%d excode want=%02x got=%02x",
|
|
MODE_NAME[mi], faults[i].n, in_delay, faults[i].excode, cause & 0x7cu);
|
|
ok = 0;
|
|
}
|
|
if (((cause >> 31) & 1u) != (uint32_t)in_delay) {
|
|
fail("case=exception-model mode=%s %s delay=%d BD want=%d got=%u",
|
|
MODE_NAME[mi], faults[i].n, in_delay, in_delay, (cause >> 31) & 1u);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->cop0_r[COP0_EPC] != want_epc) {
|
|
fail("case=exception-model mode=%s %s delay=%d epc want=%08x got=%08x",
|
|
MODE_NAME[mi], faults[i].n, in_delay, want_epc,
|
|
m->cpu->cop0_r[COP0_EPC]);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->pc != want_vec) {
|
|
fail("case=exception-model mode=%s %s bev=%d vector want=%08x got=%08x",
|
|
MODE_NAME[mi], faults[i].n, bev, want_vec, m->cpu->pc);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->next_pc != want_vec + 4u) {
|
|
fail("case=exception-model mode=%s %s next_pc want=%08x got=%08x",
|
|
MODE_NAME[mi], faults[i].n, want_vec + 4u, m->cpu->next_pc);
|
|
ok = 0;
|
|
}
|
|
if (m->cpu->cop0_r[COP0_SR] != want_sr) {
|
|
fail("case=exception-model mode=%s %s sr-push want=%08x got=%08x",
|
|
MODE_NAME[mi], faults[i].n, want_sr, m->cpu->cop0_r[COP0_SR]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* RFE pops the mode stack: bits[3:0] <- bits[5:2]; bits 4-5 are LEFT ALONE. */
|
|
for (unsigned mode = 0; mode < 64u && ok; mode++) {
|
|
uint32_t sr0 = 0x10000000u | mode;
|
|
uint32_t want = (sr0 & 0xfffffff0u) | ((sr0 >> 2) & 0xfu);
|
|
uint32_t prog[2] = { 0x42000010u, NOP }; /* rfe */
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
m->cpu->cop0_r[COP0_SR] = sr0;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->cop0_r[COP0_SR] != want) {
|
|
fail("case=exception-model mode=%s rfe sr0=%08x want=%08x got=%08x",
|
|
MODE_NAME[mi], sr0, want, m->cpu->cop0_r[COP0_SR]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=exception-model\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: COP0 register write masks (documented table).
|
|
================================================================================== */
|
|
|
|
static int case_cop0_masks(pair_t* p) {
|
|
printf("CPU_SPEC begin case=cop0-masks\n");
|
|
int ok = 1;
|
|
|
|
/* Only IP0/IP1 (bits 8-9) of CAUSE are software-writable; EPC/BadVaddr/PRID/JUMPDEST
|
|
are read-only; DCIC has reserved bits. */
|
|
static const uint32_t mask[16] = {
|
|
0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu,
|
|
0x00000000u, 0xffffffffu, 0x00000000u, 0xffc0f03fu,
|
|
0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu,
|
|
0xffffffffu, 0x00000300u, 0x00000000u, 0x00000000u
|
|
};
|
|
|
|
static const uint32_t writes[] = { 0xffffffffu, 0x00000000u, 0xa5a5a5a5u, 0x5a5a5a5au };
|
|
|
|
for (unsigned reg = 0; reg < 16u && ok; reg++) {
|
|
for (size_t w = 0; w < sizeof writes / sizeof writes[0] && ok; w++) {
|
|
uint32_t seed = 0x00000000u;
|
|
uint32_t want = (seed & ~mask[reg]) | (writes[w] & mask[reg]);
|
|
/* mtc0 r2, copreg */
|
|
uint32_t prog[3] = { ((OP_COP0) << 26) | (0x04u << 21) | (2u << 16) | (reg << 11), NOP, NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 3);
|
|
m->cpu->cop0_r[reg] = seed;
|
|
m->cpu->r[2] = writes[w];
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (m->cpu->cop0_r[reg] != want) {
|
|
fail("case=cop0-masks mode=%s mtc0 r%u <- %08x want=%08x got=%08x",
|
|
MODE_NAME[mi], reg, writes[w], want, m->cpu->cop0_r[reg]);
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=cop0-masks\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ==================================================================================
|
|
Case: cache isolation. With SR.IsC set, no store may reach memory -- the store is
|
|
absorbed by the (unimplemented) data cache. The BIOS flush routines rely on this, and a
|
|
store that leaks through corrupts RAM at a point no game can defend against.
|
|
================================================================================== */
|
|
|
|
static int case_cache_isolation(pair_t* p) {
|
|
printf("CPU_SPEC begin case=cache-isolation\n");
|
|
int ok = 1;
|
|
|
|
struct { uint32_t op; const char* n; } st[] = {
|
|
{ enc_i(OP_SB, 1, 2, 0), "sb" },
|
|
{ enc_i(OP_SH, 1, 2, 0), "sh" },
|
|
{ enc_i(OP_SW, 1, 2, 0), "sw" },
|
|
{ enc_i(OP_SWL, 1, 2, 2), "swl" },
|
|
{ enc_i(OP_SWR, 1, 2, 1), "swr" },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof st / sizeof st[0] && ok; i++) {
|
|
uint32_t prog[2] = { st[i].op, NOP };
|
|
for (int mi = 0; mi < 2 && ok; mi++) {
|
|
psx_t* m = p->m[mi];
|
|
cpu_reset(m->cpu);
|
|
write_prog(m, prog, 2);
|
|
write_data(m, 0, 0x11223344u);
|
|
m->cpu->cop0_r[COP0_SR] |= SR_ISC;
|
|
m->cpu->r[1] = DATA_VA;
|
|
m->cpu->r[2] = 0xdeadbeefu;
|
|
psx_cpu_cycle(m->cpu);
|
|
++g_checked;
|
|
if (read_data(m, 0) != 0x11223344u) {
|
|
fail("case=cache-isolation mode=%s %s leaked through IsC: mem=%08x (want 11223344)",
|
|
MODE_NAME[mi], st[i].n, read_data(m, 0));
|
|
ok = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
printf("CPU_SPEC passed case=cache-isolation\n");
|
|
return ok;
|
|
}
|
|
|
|
/* ================================================================================== */
|
|
|
|
int main(void) {
|
|
const char* bios_path = "build/tests/blank-bios.bin";
|
|
if (!write_blank_bios(bios_path)) {
|
|
fprintf(stderr, "CPU_SPEC failed reason=create-bios path=%s\n", bios_path);
|
|
return 1;
|
|
}
|
|
|
|
pair_t p;
|
|
memset(&p, 0, sizeof p);
|
|
if (!pair_init(&p, bios_path)) {
|
|
fprintf(stderr, "CPU_SPEC failed reason=init\n");
|
|
return 1;
|
|
}
|
|
|
|
int ok = 1;
|
|
ok &= case_load_extend(&p);
|
|
ok &= case_store_width(&p);
|
|
ok &= case_addr_error(&p);
|
|
ok &= case_unaligned(&p);
|
|
ok &= case_load_delay(&p);
|
|
ok &= case_shift_matrix(&p);
|
|
ok &= case_alu_matrix(&p);
|
|
ok &= case_muldiv(&p);
|
|
ok &= case_branch_matrix(&p);
|
|
ok &= case_exception_model(&p);
|
|
ok &= case_cop0_masks(&p);
|
|
ok &= case_cache_isolation(&p);
|
|
|
|
pair_destroy(&p);
|
|
|
|
if (!ok || g_failures) {
|
|
fprintf(stderr, "CPU_SPEC FAILED failures=%d checked=%lu\n", g_failures, g_checked);
|
|
return 1;
|
|
}
|
|
|
|
printf("CPU_SPEC all cases passed checked=%lu\n", g_checked);
|
|
return 0;
|
|
}
|