Files
libretroadmin 80c1251d80 SPR: the scratchpad DMA channels in C89
SPR.cpp becomes C. Hw.cpp's MFIFO entry points and vucpu_execute_block
get C linkage, THREAD_VU1 is asked through emu_config_thread_vu1(), and
VU.h and VUmicro.h lose their // comments, which a C89 lane read into
vu1RunCycles.

Copies between the scratchpad and memory now stay inside what they
address: a run past the end of main RAM reads on from the zero source,
one through VU0's 4 KiB windows mirrors instead of running into VU1, the
scratchpad's own window wraps, the scratchpad copied onto itself goes
front to back, interleave transfers to or from a bus error no longer
copy through NULL, and a source-chain tag at a bus error raises BEIS and
stops the channel instead of being read through NULL. tests/eespr replays
both channels against the transcript the C++ made and checks each of
these.
2026-11-09 04:22:07 +01:00

641 lines
24 KiB
C++

/* EE integer ops against console captures, driving the real code.
*
* tests/ee/hwalu.c and hwload.c and hwstore.c transcribe the semantics
* out of pcsx2/R5900OpcodeImpl.cpp and score the copy, which catches a
* wrong answer but not drift. This links that file and calls the ops
* through their real entry points, building the instruction word so the
* _Rd_, _Rs_, _Rt_, _Sa_ and _Imm_ macros decode what the harness
* intends.
*
* Its dependencies are stubbed rather than pulled in. Most are inert --
* the BIOS hacks, the savestate helpers, the COP2 print table -- but the
* vtlb accessors are backed by a real array, so the loads and stores
* execute against memory rather than being skipped. That is what lets
* the same harness cover alu.expected and lsu.expected.
*
* Three tables of ops, by shape:
* RRR rd = f(rs, rt) -- the register arithmetic
* RRI rt = f(rs, immediate) -- the immediate forms
* RRS rd = f(rt, sa) -- shift by immediate
*
* The load and store blocks reuse the same memory pattern the standalone
* scorers do, at the same two bases: the load harness bases at byte 16
* of the pattern and the store harness at byte 4, which is not a typo
* but a difference between the two test programs.
*
* Usage: tests/ee/hwrealee <dir-with-the-.expected-files>
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "Common.h"
#include "R5900.h"
#include "R5900OpcodeTables.h"
#include "GS.h"
#include "ps2/BiosTools.h"
alignas(16) cpuRegisters cpuRegs;
/* ---- stubs ------------------------------------------------------------
* Everything R5900OpcodeImpl.cpp reaches for that this harness does not
* exercise. The memory accessors are real; the rest are inert. */
#define MEMSIZE 4096
static u8 s_mem[MEMSIZE];
static u32 clampaddr(u32 a) { return a & (MEMSIZE - 1); }
u8 vtlb_memRead8 (u32 a) { return s_mem[clampaddr(a)]; }
u16 vtlb_memRead16(u32 a) { u16 v; memcpy(&v, s_mem + clampaddr(a), 2); return v; }
u32 vtlb_memRead32(u32 a) { u32 v; memcpy(&v, s_mem + clampaddr(a), 4); return v; }
u64 vtlb_memRead64(u32 a) { u64 v; memcpy(&v, s_mem + clampaddr(a), 8); return v; }
void vtlb_memWrite8 (u32 a, u8 v) { s_mem[clampaddr(a)] = v; }
void vtlb_memWrite16(u32 a, u16 v) { memcpy(s_mem + clampaddr(a), &v, 2); }
void vtlb_memWrite32(u32 a, u32 v) { memcpy(s_mem + clampaddr(a), &v, 4); }
void vtlb_memWrite64(u32 a, u64 v) { memcpy(s_mem + clampaddr(a), &v, 8); }
void* vtlb_GetPhyPtr(u32 a) { return s_mem + clampaddr(a); }
RETURNS_R128 vtlb_memRead128(u32 a)
{
r128 v;
memcpy(&v, s_mem + clampaddr(a), 16);
return v;
}
void vtlb_memWrite128(u32 a, r128 v) { memcpy(s_mem + clampaddr(a), &v, 16); }
/* Inert stubs: nothing here runs a BIOS, a savestate or the GS. */
bool g_SkipBiosHack = false, AllowParams1 = false, AllowParams2 = false, NoOSD = false;
void cpuException(u32, u32) { }
/* Cpu is only reached by the misaligned-access paths, which call
* CancelInstruction; the captures never take one. */
static void nop_v() { }
static void nop_u32(u32) { }
static void nop_u32_u32(u32, u32) { }
/* Set when an op takes its misaligned-address path. The captures use
* aligned addresses, so this firing means the harness set up the address
* wrongly -- it is recorded rather than ignored, and never null, because a
* null CancelInstruction turns that mistake into a segfault. */
static int s_cancelled;
static void harness_cancel() { s_cancelled = 1; }
static R5900cpu s_cpu = {};
R5900cpu* Cpu = &s_cpu;
static struct cpu_init_t { cpu_init_t() { s_cpu.CancelInstruction = harness_cancel; } } s_cpu_init;
BiosDebugInformation CurrentBiosInformation = {};
/* Referenced by inline helpers in GS.h and Dmac.h; only needed when the
* compiler does not elide them, which -O0 does not. */
alignas(16) u8 g_RealGSMem[0x2000];
alignas(16) u8 eeHw[0x10000];
void gsSetVideoMode(GS_VideoMode) { }
extern "C" void cdvdReadLanguageParams(u8*) { }
tDMA_TAG* dmaGetAddr(u32, bool) { return (tDMA_TAG*)s_mem; }
void SaveState_FreezeMem(SaveStateBase*, void*, int) { }
bool SaveState_FreezeTag(SaveStateBase*, const char*) { return true; }
/* ---- the harness ------------------------------------------------------ */
namespace R5900 { namespace Interpreter { namespace OpcodeImpl {
void ADDU(); void SUBU(); void AND(); void OR(); void XOR(); void NOR();
void SLT(); void SLTU(); void DADDU(); void DSUBU();
void MOVN(); void MOVZ();
void SLLV(); void SRLV(); void SRAV();
void DSLLV(); void DSRLV(); void DSRAV();
void ADDIU(); void ANDI(); void DADDIU(); void ORI(); void XORI();
void SLTI(); void SLTIU(); void LUI();
void SLL(); void SRL(); void SRA();
void DSLL(); void DSRL(); void DSRA();
void DSLL32(); void DSRL32(); void DSRA32();
void LB(); void LBU(); void LH(); void LHU(); void LW(); void LWU();
void LD(); void LQ(); void LWL(); void LWR(); void LDL(); void LDR();
void SB(); void SH(); void SW(); void SD(); void SQ();
void SWL(); void SWR(); void SDL(); void SDR();
void DIV(); void DIVU(); void MULT(); void MULTU();
void MFHI(); void MFLO(); void MTHI(); void MTLO();
} } }
using namespace R5900::Interpreter::OpcodeImpl;
/* HILO_MOVE: one operand, and the result is a pipeline register or rd
* depending on the direction. */
enum Shape { RRR, RRI, RRS, RI, LOAD, STORE, MULDIV, HILO_MOVE };
static const struct { const char* name; void (*fn)(); Shape shape; int swap; }
kOps[] = {
{ "addu",ADDU,RRR,0 },{ "subu",SUBU,RRR,0 },{ "and",AND,RRR,0 },
{ "or",OR,RRR,0 },{ "xor",XOR,RRR,0 },{ "nor",NOR,RRR,0 },
{ "slt",SLT,RRR,0 },{ "sltu",SLTU,RRR,0 },
{ "daddu",DADDU,RRR,0 },{ "dsubu",DSUBU,RRR,0 },
{ "movn",MOVN,RRR,0 },{ "movz",MOVZ,RRR,0 },
/* The variable shifts assemble as rd, rt, rs -- value first, amount
* second -- so the printed operands go in swapped. */
{ "sllv",SLLV,RRR,1 },{ "srlv",SRLV,RRR,1 },{ "srav",SRAV,RRR,1 },
{ "dsllv",DSLLV,RRR,1 },{ "dsrlv",DSRLV,RRR,1 },{ "dsrav",DSRAV,RRR,1 },
{ "addiu",ADDIU,RRI,0 },{ "andi",ANDI,RRI,0 },{ "daddiu",DADDIU,RRI,0 },
{ "ori",ORI,RRI,0 },{ "xori",XORI,RRI,0 },
{ "slti",SLTI,RRI,0 },{ "sltiu",SLTIU,RRI,0 },
{ "lui",LUI,RI,0 },
{ "sll",SLL,RRS,0 },{ "srl",SRL,RRS,0 },{ "sra",SRA,RRS,0 },
{ "dsll",DSLL,RRS,0 },{ "dsrl",DSRL,RRS,0 },{ "dsra",DSRA,RRS,0 },
{ "dsll32",DSLL32,RRS,0 },{ "dsrl32",DSRL32,RRS,0 },{ "dsra32",DSRA32,RRS,0 },
/* Loads and stores run against the backing array; the base register
* holds the pattern's base and the offset comes from the line. */
{ "lb",LB,LOAD,0 },{ "lbu",LBU,LOAD,0 },{ "lh",LH,LOAD,0 },
{ "lhu",LHU,LOAD,0 },{ "lw",LW,LOAD,0 },{ "lwu",LWU,LOAD,0 },
{ "ld",LD,LOAD,0 },{ "lq",LQ,LOAD,0 },
{ "sb",SB,STORE,0 },{ "sh",SH,STORE,0 },{ "sw",SW,STORE,0 },
{ "sd",SD,STORE,0 },{ "sq",SQ,STORE,0 },
{ "div",DIV,MULDIV,0 },{ "divu",DIVU,MULDIV,0 },
{ "mult",MULT,MULDIV,0 },{ "multu",MULTU,MULDIV,0 },
/* The HI/LO moves. Nothing in the tree scored these either -- the
* audit that turned up the second pipeline found them in the same
* pass. */
{ "mfhi",MFHI,HILO_MOVE,0 },{ "mflo",MFLO,HILO_MOVE,0 },
{ "mthi",MTHI,HILO_MOVE,0 },{ "mtlo",MTLO,HILO_MOVE,0 },
};
/* C_PATTERN from the lsu harness, and the two bases its load and store
* halves use -- byte 16 for loads, byte 4 for stores, which is a row index
* against a flat word index rather than a mistake. */
static const u32 kPattern[12] = {
0x45678123u, 0x9ABCDEF0u, 0xDEADBEEFu, 0xC0DE1337u,
0x23456789u, 0xABCDEF01u, 0xBEEFDEADu, 0xC0DEC0DEu,
0x8899AABBu, 0xCCDDEEFFu, 0x00112233u, 0x44556677u,
};
#define LOAD_BASE 16
/* The EE store harness bases at &buffer[1] of a u128 array, so byte 16 --
* the same base its loads use. The IOP one bases at byte 4 because its
* buffer is a flat u32 array; that difference cost a crash here, since SD
* and SQ at byte 4 take their misaligned path. */
#define STORE_BASE 16
/* C_HILO, the seed the muldiv harness restores before each case. */
#define HI0 0x0123456789ABCDEFull
#define LO0 0x123456789ABCDEF0ull
#define HI1 0x23456789ABCDEF01ull
#define LO1 0x456789ABCDEF0123ull
#define NOPS ((int)(sizeof(kOps)/sizeof(kOps[0])))
#define RD 1
#define RS 2
#define RT 3
typedef struct { u32 w[4]; } Q;
static const struct { const char* name; Q v; } kConst[] = {
{ "C_ZERO", {{0,0,0,0}} },
{ "C_S16_MAX", {{0x7FFFu,0,0,0}} },
{ "C_S16_MIN", {{0xFFFF8000u,0xFFFFFFFFu,0xFFFFFFFFu,0xFFFFFFFFu}} },
{ "C_S32_MAX", {{0x7FFFFFFFu,0,0,0}} },
{ "C_S32_MIN", {{0x80000000u,0xFFFFFFFFu,0xFFFFFFFFu,0xFFFFFFFFu}} },
{ "C_S64_MAX", {{0xFFFFFFFFu,0x7FFFFFFFu,0,0}} },
{ "C_S64_MIN", {{0,0x80000000u,0xFFFFFFFFu,0xFFFFFFFFu}} },
{ "C_NEGONE", {{0xFFFFFFFFu,0xFFFFFFFFu,0xFFFFFFFFu,0xFFFFFFFFu}} },
{ "C_ONE", {{1,0,0,0}} },
{ "C_GARBAGE1", {{0x1337u,0x1338u,0x1339u,0x133Au}} },
{ "C_GARBAGE2", {{0xDEADBEEFu,0xDEADBEEEu,0xDEADBEEDu,0xDEADBEECu}} },
{ "C_TWOTWOTWOTWO", {{2u,2u,2u,2u}} },
{ "C_ONEONEONEONE", {{1u,1u,1u,1u}} },
};
static int operand(char** pp, Q* out, int* named)
{
char* p = *pp;
size_t i, best = (size_t)-1, bestlen = 0;
while (*p == ' ' || *p == ',') p++;
for (i = 0; i < sizeof(kConst)/sizeof(kConst[0]); i++)
{
const size_t n = strlen(kConst[i].name);
if (!strncmp(p, kConst[i].name, n) && n > bestlen) { best = i; bestlen = n; }
}
if (best != (size_t)-1)
{ *out = kConst[best].v; *named = 1; p += bestlen; *pp = p; return 1; }
{
char* e;
long long v = strtoll(p, &e, 10);
if (e == p) return 0;
/* SET_U32: lui/ori, so the low doubleword sign-extended, nothing above. */
out->w[0] = (u32)v;
out->w[1] = (v < 0) ? 0xFFFFFFFFu : 0u;
out->w[2] = out->w[3] = 0;
*named = 0;
*pp = e;
return 1;
}
}
static void set_reg(int r, const Q* q)
{
cpuRegs.GPR.r[r].UL[0] = q->w[0]; cpuRegs.GPR.r[r].UL[1] = q->w[1];
cpuRegs.GPR.r[r].UL[2] = q->w[2]; cpuRegs.GPR.r[r].UL[3] = q->w[3];
}
/* Case count for a block, so the store seed can switch at its midpoint.
* Each store block runs its offsets twice, first from SET_U32<0xABCD4321>
* and then from SET_M(C_GARBAGE1), and the number of offsets is not
* constant -- three for the byte through doubleword stores, four for SQ.
* Assuming three seeds the wrong half of the larger block. */
static int count_block(const char* path, const char* name)
{
FILE* f = fopen(path, "r");
char buf[512];
int in = 0, n = 0;
if (!f) return 0;
while (fgets(buf, sizeof(buf), f))
{
if (buf[0] != ' ')
{
char* c = strchr(buf, ':');
if (in) break;
if (c && c[1] == '\n') { *c = '\0'; in = !strcmp(buf, name); *c = ':'; }
continue;
}
if (!in || strstr(buf, "-> $0")) continue;
n++;
}
fclose(f);
return n;
}
/* Memory backing for the load and store blocks. */
static void reset_pattern(void) { memcpy(s_mem, kPattern, sizeof(kPattern)); }
/* Read "+0", "-16" or "+-16": the second half of each block prints a
* negative offset through a "+%d" format, so the sign can follow the plus. */
static int read_off(char** pp, int* out)
{
char* p = *pp;
while (*p == ' ') p++;
if (*p == '+') p++;
if (*p != '-' && (*p < '0' || *p > '9')) return 0;
*out = (int)strtol(p, &p, 10);
*pp = p;
return 1;
}
/* The load, store and multiply/divide blocks: different files, different
* line shapes, and for the stores the result is memory rather than a
* register. Returns 0 when the line is not a case. */
static int run_special(int op, char* buf, int* cases, int* pass, int* failures,
int block_total)
{
unsigned w3, w2, w1, w0;
int off = 0;
char* p;
if (strstr(buf, "-> $0")) return 0;
p = buf + 2;
while (*p && *p != ' ') p++;
if (kOps[op].shape == HILO_MOVE)
{
/* "mfhi <n>: <rd quad> H: hi hi1 L: lo lo1". The MF forms write rd
* from a pipeline register and the MT forms write the pipeline from
* rs, so both directions are covered by comparing rd and the
* pipelines together -- which is also what tells a move from HI
* apart from a move from LO. */
unsigned long long wh, wh1, wl, wl1;
unsigned q3, q2, q1, q0;
Q rs_q;
char* q = strstr(buf, "H: ");
const int is_mt = kOps[op].name[1] == 't';
if (!q) return 0;
if (sscanf(q, "H: %llx %llx L: %llx %llx", &wh, &wh1, &wl, &wl1) != 4)
return 0;
{
char* c = strchr(buf, ':');
if (!c || sscanf(c + 2, "%x %x %x %x", &q3, &q2, &q1, &q0) != 4)
return 0;
/* Half these lines name a constant rather than a decimal --
* "mthi C_NEGONE:" -- and strtol reads those as zero, which
* makes the MT forms score 8 of 18 while looking like a
* pipeline-write bug. operand() resolves both. */
{
char* ap = p;
int dummy = 0;
if (!operand(&ap, &rs_q, &dummy)) return 0;
}
}
memset(&cpuRegs.GPR, 0, sizeof(cpuRegs.GPR));
cpuRegs.HI.UD[0] = HI0; cpuRegs.HI.UD[1] = HI1;
cpuRegs.LO.UD[0] = LO0; cpuRegs.LO.UD[1] = LO1;
cpuRegs.GPR.r[RS].UL[0] = rs_q.w[0];
cpuRegs.GPR.r[RS].UL[1] = rs_q.w[1];
cpuRegs.GPR.r[RS].UL[2] = rs_q.w[2];
cpuRegs.GPR.r[RS].UL[3] = rs_q.w[3];
/* rd carries the same seed the ee/muldiv program leaves. */
cpuRegs.GPR.r[RD].UD[1] = 0x0000133A00001339ull;
cpuRegs.code = ((u32)RS << 21) | ((u32)RD << 11);
kOps[op].fn();
(*cases)++;
{
const int hilo_ok = cpuRegs.HI.UD[0] == wh && cpuRegs.HI.UD[1] == wh1
&& cpuRegs.LO.UD[0] == wl && cpuRegs.LO.UD[1] == wl1;
/* The MT forms leave rd alone, so only the pipelines are the
* result there. */
const int rd_ok = is_mt
|| (cpuRegs.GPR.r[RD].UL[0] == q0
&& cpuRegs.GPR.r[RD].UL[1] == q1);
if (hilo_ok && rd_ok) (*pass)++;
else if ((*failures)++ < 6)
printf(" %-5s %d: console rd %08x %08x H %016llx %016llx\n"
" ours rd %08x %08x H %016llx %016llx\n",
kOps[op].name, (int)rs_q.w[0], q1, q0, wh, wh1,
cpuRegs.GPR.r[RD].UL[1], cpuRegs.GPR.r[RD].UL[0],
(unsigned long long)cpuRegs.HI.UD[0],
(unsigned long long)cpuRegs.HI.UD[1]);
}
return 0;
}
if (kOps[op].shape == MULDIV)
{
u32 a, b;
unsigned long long wh, wh1, wl, wl1;
char* q = p;
int na = 0, nb = 0;
Q qa, qb;
if (!operand(&q, &qa, &na)) return 0;
if (!operand(&q, &qb, &nb)) return 0;
a = qa.w[0]; b = qb.w[0];
/* Anchor on the H: marker rather than the first colon. The
* multiply lines print rd between the two -- "mult a, b: <quad>
* H: ... L: ..." -- while the divide lines go straight to H:, so
* scanning from the colon parses div and drops every mult and
* multu line. They scored 0 of 0 and the run still passed. */
q = strstr(buf, "H: ");
if (!q || sscanf(q, "H: %llx %llx L: %llx %llx",
&wh, &wh1, &wl, &wl1) != 4) return 0;
cpuRegs.HI.UD[0] = HI0; cpuRegs.HI.UD[1] = HI1;
cpuRegs.LO.UD[0] = LO0; cpuRegs.LO.UD[1] = LO1;
cpuRegs.GPR.r[RS].UD[0] = (u64)(s64)(s32)a;
cpuRegs.GPR.r[RT].UD[0] = (u64)(s64)(s32)b;
cpuRegs.code = ((u32)RS << 21) | ((u32)RT << 16);
kOps[op].fn();
(*cases)++;
if (cpuRegs.HI.UD[0] == wh && cpuRegs.HI.UD[1] == wh1
&& cpuRegs.LO.UD[0] == wl && cpuRegs.LO.UD[1] == wl1) (*pass)++;
else if ((*failures)++ < 6)
printf(" %-6s %d, %d: console H %016llx %016llx L %016llx %016llx\n"
" ours H %016llx %016llx L %016llx %016llx\n",
kOps[op].name, (int)a, (int)b, wh, wh1, wl, wl1,
(unsigned long long)cpuRegs.HI.UD[0],
(unsigned long long)cpuRegs.HI.UD[1],
(unsigned long long)cpuRegs.LO.UD[0],
(unsigned long long)cpuRegs.LO.UD[1]);
return 0;
}
if (!read_off(&p, &off)) return 0;
p = strchr(buf, ':');
if (!p) return 0;
reset_pattern();
if (kOps[op].shape == LOAD)
{
if (sscanf(p + 1, " %x %x %x %x", &w3, &w2, &w1, &w0) != 4) return 0;
/* rt carries between cases; seed it as the lsu harness leaves it. */
cpuRegs.GPR.r[RT].UD[1] = 0x0000133A00001339ull;
cpuRegs.GPR.r[RS].UD[0] = LOAD_BASE;
cpuRegs.code = ((u32)RS << 21) | ((u32)RT << 16) | (u32)(off & 0xFFFF);
kOps[op].fn();
(*cases)++;
{
const u32 g0 = cpuRegs.GPR.r[RT].UL[0], g1 = cpuRegs.GPR.r[RT].UL[1];
const u32 g2 = cpuRegs.GPR.r[RT].UL[2], g3 = cpuRegs.GPR.r[RT].UL[3];
if (g0 == w0 && g1 == w1 && g2 == w2 && g3 == w3) (*pass)++;
else if ((*failures)++ < 6)
printf(" %-4s %+d: console %08x %08x %08x %08x ours %08x %08x %08x %08x\n",
kOps[op].name, off, w3, w2, w1, w0, g3, g2, g1, g0);
}
return 0;
}
/* STORE: the line prints the whole sixteen-byte row the store landed
* in, high word first, which is row 1 + offset/16 of the pattern. */
{
unsigned m3, m2, m1, m0;
int row;
if (sscanf(p + 1, " %x %x %x %x", &m3, &m2, &m1, &m0) != 4) return 0;
cpuRegs.GPR.r[RT].UD[0] = (*cases < block_total / 2)
? (u64)(s64)(s32)0xABCD4321u
: ((u64)0x1338u << 32) | 0x1337u;
cpuRegs.GPR.r[RT].UD[1] = 0x0000133A00001339ull;
cpuRegs.GPR.r[RS].UD[0] = STORE_BASE;
cpuRegs.code = ((u32)RS << 21) | ((u32)RT << 16) | (u32)(off & 0xFFFF);
s_cancelled = 0;
kOps[op].fn();
(*cases)++;
row = 16 + (off / 16) * 16; /* base row plus the printed offset */
{
u32 g[4];
int i;
for (i = 0; i < 4; i++)
memcpy(&g[i], s_mem + ((row + i * 4) & (MEMSIZE - 1)), 4);
if (!s_cancelled && g[0] == m0 && g[1] == m1
&& g[2] == m2 && g[3] == m3) (*pass)++;
else if ((*failures)++ < 6)
printf(" %-4s %+d: console %08x %08x %08x %08x ours %08x %08x %08x %08x%s\n",
kOps[op].name, off, m3, m2, m1, m0,
g[3], g[2], g[1], g[0],
s_cancelled ? " (cancelled)" : "");
}
}
return 0;
}
int main(int argc, char** argv)
{
const char* dir = (argc > 1) ? argv[1] : ".";
int op, failures = 0, total = 0;
/* rd carries between cases across the whole file, as in hwalu.c. */
u64 rd_hi = 0;
for (op = 0; op < NOPS; op++)
{
char path[512], head[32], buf[512];
FILE* f;
int inblock = 0, pass = 0, cases = 0, block_total = 0;
snprintf(path, sizeof(path), "%s/%s.expected", dir,
(kOps[op].shape == LOAD || kOps[op].shape == STORE) ? "lsu"
: (kOps[op].shape == MULDIV
|| kOps[op].shape == HILO_MOVE) ? "muldiv" : "alu");
f = fopen(path, "r");
if (!f) { fprintf(stderr, "cannot open %s\n", path); return 2; }
snprintf(head, sizeof(head), "%s:", kOps[op].name);
block_total = count_block(path, kOps[op].name);
while (fgets(buf, sizeof(buf), f))
{
Q a, b; unsigned w3, w2, w1, w0;
int named_a = 0, named_b = 0;
char* p; u32 sa = 0, imm = 0;
int dest;
if (!inblock)
{ if (!strncmp(buf, head, strlen(head)) && buf[strlen(head)] == '\n')
inblock = 1;
continue; }
if (buf[0] != ' ') break;
if (strstr(buf, "-> $0")) continue;
if (kOps[op].shape == LOAD || kOps[op].shape == STORE
|| kOps[op].shape == MULDIV || kOps[op].shape == HILO_MOVE)
{
if (!run_special(op, buf, &cases, &pass, &failures, block_total)) continue;
continue;
}
p = buf + 2 + strlen(kOps[op].name);
if (kOps[op].shape == RI)
{
a.w[0] = a.w[1] = a.w[2] = a.w[3] = 0;
if (!operand(&p, &b, &named_b)) continue;
}
else
{
if (!operand(&p, &a, &named_a)) continue;
if (!operand(&p, &b, &named_b)) continue;
}
while (*p == ' ' || *p == ':') p++;
if (sscanf(p, "%x %x %x %x", &w3, &w2, &w1, &w0) != 4) continue;
/* rd's carried upper doubleword, as hwalu.c models it. */
if (named_a || named_b) rd_hi = 0x0000133A00001339ull;
cpuRegs.GPR.r[RD].UD[1] = rd_hi;
cpuRegs.GPR.r[RD].UD[0] = (named_a || named_b)
? ((u64)0x1338u << 32) | 0x1337u : 0x1337u;
switch (kOps[op].shape)
{
case RRR:
if (kOps[op].swap) { const Q t = a; a = b; b = t; }
set_reg(RS, &a); set_reg(RT, &b);
cpuRegs.code = ((u32)RS << 21) | ((u32)RT << 16) | ((u32)RD << 11);
dest = RD;
break;
case RRI:
set_reg(RS, &a);
imm = b.w[0] & 0xFFFF;
cpuRegs.code = ((u32)RS << 21) | ((u32)RT << 16) | imm;
dest = RT;
break;
case RI:
imm = b.w[0] & 0xFFFF;
cpuRegs.code = ((u32)RT << 16) | imm;
dest = RT;
break;
default: /* RRS */
set_reg(RT, &a);
sa = b.w[0] & 0x1F;
cpuRegs.code = ((u32)RT << 16) | ((u32)RD << 11) | (sa << 6);
dest = RD;
break;
}
/* The immediate forms write rt, so it has to carry the seed too. */
if (dest == RT)
{
cpuRegs.GPR.r[RT].UD[1] = rd_hi;
cpuRegs.GPR.r[RT].UD[0] = (named_a || named_b)
? ((u64)0x1338u << 32) | 0x1337u : 0x1337u;
}
kOps[op].fn();
cases++;
{
const u32 g0 = cpuRegs.GPR.r[dest].UL[0];
const u32 g1 = cpuRegs.GPR.r[dest].UL[1];
const u32 g2 = cpuRegs.GPR.r[dest].UL[2];
const u32 g3 = cpuRegs.GPR.r[dest].UL[3];
if (g0 == w0 && g1 == w1 && g2 == w2 && g3 == w3) pass++;
else if (failures++ < 6)
printf(" %-7s console %08x %08x %08x %08x ours %08x %08x %08x %08x\n",
kOps[op].name, w3, w2, w1, w0, g3, g2, g1, g0);
rd_hi = ((u64)w3 << 32) | w2;
}
}
fclose(f);
total += cases;
/* block_total is already the number of case lines the block has --
* it is computed above for the store seed's midpoint -- so compare
* against it rather than only checking for zero. Every
* under-coverage bug in this series was partial: a missing
* constant, an operand shape matching some lines and not others.
* Checking "did anything parse" would have caught none of them. */
if (block_total > 0 && cases != block_total)
{ printf("%-7s parsed %d of the %d lines its block has;"
" some operand shape does not match\n",
kOps[op].name, cases, block_total);
failures++; }
else if (cases == 0)
{ printf("%-7s parsed no cases; the block name or line shape changed\n",
kOps[op].name);
failures++; }
else if (pass != cases)
{ printf("%-7s %2d/%-3d console cases\n", kOps[op].name, pass, cases);
failures++; }
}
/* ---- cases the capture cannot supply -------------------------------
*
* MIPS splits the immediate forms: the arithmetic and compare ones --
* ADDIU, DADDIU, SLTI, SLTIU -- sign-extend their immediate, and the
* logical ones -- ANDI, ORI, XORI -- zero-extend it. Getting that
* split wrong is a classic implementation mistake and the capture
* cannot see it: not one of the 144 lines across those seven ops in
* tests/cpu/ee/alu.expected uses a negative or high-bit immediate, so
* swapping _Imm_ for _ImmU_ in SLTIU passes all 814 console cases.
*
* These come from the MIPS definition rather than from hardware, and
* every one uses an immediate with bit 15 set, which is the only place
* the two extensions differ.
*/
{
static const struct { const char* name; void (*fn)(); u64 rs; u32 imm; u64 want; }
kSpec[] = {
/* Sign-extending: 0xffff means -1. */
{ "addiu", ADDIU, 0x0000000000000001ull, 0xFFFFu, 0x0000000000000000ull },
{ "addiu", ADDIU, 0x0000000000000000ull, 0xFFFFu, 0xFFFFFFFFFFFFFFFFull },
{ "daddiu", DADDIU, 0x0000000000000001ull, 0xFFFFu, 0x0000000000000000ull },
{ "slti", SLTI, 0xFFFFFFFFFFFFFFFEull, 0xFFFFu, 1 }, /* -2 < -1 */
{ "slti", SLTI, 0x0000000000000000ull, 0xFFFFu, 0 },
{ "sltiu", SLTIU, 0x0000000000000000ull, 0xFFFFu, 1 }, /* 0 < ffff..ff */
{ "sltiu", SLTIU, 0x0000000000010000ull, 0xFFFFu, 1 }, /* zero-ext says 0 */
/* Zero-extending: 0xffff means 65535, not -1. */
{ "andi", ANDI, 0xFFFFFFFFFFFFFFFFull, 0xFFFFu, 0x000000000000FFFFull },
{ "ori", ORI, 0x0000000000000000ull, 0xFFFFu, 0x000000000000FFFFull },
{ "xori", XORI, 0x0000000000000000ull, 0xFFFFu, 0x000000000000FFFFull },
};
const int n = (int)(sizeof(kSpec)/sizeof(kSpec[0]));
int i, pass = 0;
for (i = 0; i < n; i++)
{
memset(&cpuRegs.GPR, 0, sizeof(cpuRegs.GPR));
cpuRegs.GPR.r[RS].UD[0] = kSpec[i].rs;
cpuRegs.code = ((u32)RS << 21) | ((u32)RT << 16) | kSpec[i].imm;
kSpec[i].fn();
if (cpuRegs.GPR.r[RT].UD[0] == kSpec[i].want) pass++;
else
printf(" %-7s %016llx, 0xffff: MIPS says %016llx, ours %016llx\n",
kSpec[i].name,
(unsigned long long)kSpec[i].rs,
(unsigned long long)kSpec[i].want,
(unsigned long long)cpuRegs.GPR.r[RT].UD[0]);
}
total += n;
printf("spec %2d/%-3d immediate-extension cases from the MIPS definition\n",
pass, n);
if (pass != n) failures++;
}
printf("hwrealee: %d cases across %d ops, driven through R5900OpcodeImpl.cpp\n",
total, NOPS);
return failures != 0;
}