Files
libretroadmin 5afbb85def VIF: the VIFcode interpreter in C89
Vif_Transfer.c and Vif_Codes.c are the VIFcode interpreter and its
command handlers in C89. Each handler is written once against the unit
index and the table holds a thin per-unit entry for it, the per-unit
state is reached through VIF_X, VIF_XCH and VIF_XREGS, and the C++ is
reached through small C entry points: vif_unpack_setup and vif_unpack
in Vif_Unpack.cpp, mtvu_write_col and mtvu_write_row in MTVU.cpp,
gif_unit_transfer_direct for DIRECT and DIRECTHL, and vu1ExecMicro and
vifCmdHandler now with C linkage.

tests/eevifcode feeds both units seeded VIFcode streams, every command
and unknown codes included, in random chunks and as tags, and checks
the result against the previous C++.
2026-11-09 09:13:02 +01:00

394 lines
11 KiB
C++

/* The VIFcode interpreter (pcsx2/Vif_Transfer.c, Vif_Codes.c), driven
* with seeded VIFcode streams on both units.
*
* Each scenario builds a stream of VIFcodes with their data -- every
* command, unknown ones and the interrupt bit included -- and feeds it to
* VIF0transfer/VIF1transfer in chunks of random size, as source data and
* as DMA tags, picking up after a stall where the interpreter left off.
* The GIF unit, the VU1 thread, the VUs and the unpackers are recorders
* that answer from a sequence; the unpacker stand-in does the real one's
* bookkeeping on the tag. Both VIF states, their registers and channels,
* the GIF registers, every call made and the VU micro memory written go
* into a running hash, written out every so many scenarios. The
* transcript is compared with expected.txt, which this harness made from
* the C++ these units were converted from (EEVIFCODE_TREE=<that
* checkout>): the conversion changes none of it. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Common.h"
#include "Config.h"
#include "R5900.h"
#include "Dmac.h"
#include "Vif.h"
#include "Vif_Dma.h"
#include "VUmicro.h"
#ifdef EEVIFCODE_CXX_TREE
#include "Gif_Unit.h"
#include "MTVU.h"
#else
#include "Gif_hw.h"
#endif
#include "ps2/HwInternal.h"
/* ---- the record ---------------------------------------------------- */
static unsigned long long s_hash = 1469598103934665603ULL;
static void mix(unsigned long long v)
{
int i;
for (i = 0; i < 8; i++)
{
s_hash ^= (v >> (i * 8)) & 0xff;
s_hash *= 1099511628211ULL;
}
}
static void mix_bytes(const void* p, size_t n)
{
const u8* b = (const u8*)p;
size_t i;
for (i = 0; i < n; i++)
{
s_hash ^= b[i];
s_hash *= 1099511628211ULL;
}
}
static u32 s_rng = 0x3c6ef372u;
static u32 rnd(void)
{
s_rng ^= s_rng << 13;
s_rng ^= s_rng >> 17;
s_rng ^= s_rng << 5;
return s_rng;
}
static u32 rnd_n(u32 n) { return rnd() % n; }
static u32 s_tick = 0xa54ff53au;
static u32 answer(u32 tag, u32 a, u32 b)
{
mix(tag); mix(a); mix(b);
s_tick = s_tick * 1664525u + 1013904223u;
return s_tick >> 8;
}
/* ---- what the units reach ------------------------------------------ */
alignas(16) cpuRegisters cpuRegs;
alignas(16) VURegs vuRegs[2];
alignas(4096) u8 eeHw[PS2MEM_HARDWARE];
EEVM_MemoryAllocMess* eeMem;
alignas(16) vifStruct vif0, vif1;
u32 g_vif0Cycles, g_vif1Cycles;
alignas(16) static u8 s_micro0[0x1000], s_micro1[0x4000];
static bool s_thread_vu1, s_instant_vu1, s_vif1stall;
alignas(16) u8 s_emuconfig[sizeof(Pcsx2Config)];
asm(".globl EmuConfig\n.set EmuConfig, s_emuconfig");
static void set_config(void)
{
Pcsx2Config* c = (Pcsx2Config*)s_emuconfig;
s_thread_vu1 = rnd_n(3) == 0;
s_instant_vu1 = rnd_n(2) == 0;
s_vif1stall = rnd_n(3) == 0;
c->Cpu.Recompiler.EnableVU1 = s_thread_vu1;
c->Speedhacks.vuThread = s_thread_vu1;
c->Cpu.Recompiler.EnableVu1SoftFloat = false;
c->Speedhacks.vu1Instant = s_instant_vu1;
c->Gamefixes.VIF1StallHack = s_vif1stall;
}
void CPU_INT(EE_EventType n, s32 ecycle) { answer(0x01, (u32)n, (u32)ecycle); }
void hwDmacIrq(int n) { answer(0x02, (u32)n, 0); }
void hwIntcIrq(int n) { answer(0x03, (u32)n, 0); }
void gifInterrupt(void) { answer(0x04, 0, 0); }
/* The VUs: starting a program may leave the unit busy, which holds the
* next one queued. */
void vu0ExecMicro(u32 addr)
{
if (answer(0x08, addr, 0) & 1)
vuRegs[0].VI[REG_VPU_STAT].UL |= 0x1;
}
void vu1ExecMicro(u32 addr)
{
if (answer(0x09, addr, 0) & 1)
vuRegs[0].VI[REG_VPU_STAT].UL |= 0x100;
}
static void clear0(u32 addr, u32 size) { answer(0x0a, addr, size); }
static void clear1(u32 addr, u32 size) { answer(0x0b, addr, size); }
static VUmicroCpu s_vu0cpu = { 0, 0, NULL, NULL, NULL, NULL, clear0, NULL };
static VUmicroCpu s_vu1cpu = { 1, 0, NULL, NULL, NULL, NULL, clear1, NULL };
const VUmicroCpu* CpuVU0 = &s_vu0cpu;
const VUmicroCpu* CpuVU1 = &s_vu1cpu;
void mtvu_write_micro_mem(u32 addr, const void* data, u32 size)
{
answer(0x0c, addr, size);
mix_bytes(data, size);
}
/* The VIF DMA's flush: the VU may still be running, and a VIF waiting on
* it stalls. */
static void flush(int idx)
{
vifStruct* v = idx ? &vif1 : &vif0;
if ((answer(0x0d, (u32)idx, v->waitforvu) & 7) == 0)
v->waitforvu = true;
if (v->waitforvu)
{
v->vifstalled.enabled = (idx ? vif1ch : vif0ch).chcr.STR;
v->vifstalled.value = VIF_TIMING_BREAK;
(idx ? vif1Regs : vif0Regs).stat.VEW = true;
}
}
void vif0FLUSH(void) { flush(0); }
void vif1FLUSH(void) { flush(1); }
/* The GIF unit and the VU1 thread. */
static int rec_check_paths(bool a, bool b, bool c) { return (int)((answer(0x10, a | (b << 1) | (c << 2), 0) % 3) == 0); }
static u32 rec_transfer(u8* mem, u32 size, bool hl)
{
const u32 a = answer(0x11, size, hl);
mix_bytes(mem, size);
return (a & 3) ? size : (a % (size / 16 + 1)) * 16;
}
static void rec_col(void) { answer(0x12, 0, 0); mix_bytes(&vif1.MaskCol, 16); }
static void rec_row(void) { answer(0x13, 0, 0); mix_bytes(&vif1.MaskRow, 16); }
/* The unpackers: setup sizes the transfer, the data is taken until it is
* all there, as the real ones count it. */
static void unpack_setup(int idx, const u32* data)
{
vifStruct* v = idx ? &vif1 : &vif0;
const u32 a = answer(0x14, (u32)idx, (idx ? vif1Regs : vif0Regs).code);
v->unpackcalls++;
v->tag.size = 1 + a % 24;
v->tag.addr = (a >> 8) & 0x3ff0;
v->pass = 1;
(void)data;
}
static int unpack_data(int idx, const u8* data)
{
vifStruct* v = idx ? &vif1 : &vif0;
const int ret = pcsx2_min_i(v->vifpacketsize, v->tag.size);
answer(0x15, (u32)idx, (u32)ret);
mix_bytes(data, (size_t)ret * 4);
if ((u32)ret == v->tag.size)
{
v->pass = 0;
v->tag.size = 0;
v->cmd = 0;
}
else
v->tag.size -= ret;
return ret;
}
#ifdef EEVIFCODE_CXX_TREE
int eevifcode_check_paths(bool p1, bool p2, bool p3) { return rec_check_paths(p1, p2, p3); }
u32 eevifcode_transfer(u8* mem, u32 size, bool hl) { return rec_transfer(mem, size, hl); }
void eevifcode_write_col(void) { rec_col(); }
void eevifcode_write_row(void) { rec_row(); }
EevifcodeUnit gifUnit;
EevifcodeVuThread vu1Thread;
template <> void vifUnpackSetup<0>(const u32* data) { unpack_setup(0, data); }
template <> void vifUnpackSetup<1>(const u32* data) { unpack_setup(1, data); }
template <> int nVifUnpack<0>(const u8* data) { return unpack_data(0, data); }
template <> int nVifUnpack<1>(const u8* data) { return unpack_data(1, data); }
#else
int gif_unit_check_paths(bool p1, bool p2, bool p3) { return rec_check_paths(p1, p2, p3); }
u32 gif_unit_transfer_direct(u8* mem, u32 size, bool hl) { return rec_transfer(mem, size, hl); }
void mtvu_write_col(void) { rec_col(); }
void mtvu_write_row(void) { rec_row(); }
void vif_unpack_setup(int idx, const u32* data) { unpack_setup(idx, data); }
int vif_unpack(int idx, const u8* data) { return unpack_data(idx, data); }
bool emu_config_thread_vu1(void) { return s_thread_vu1; }
bool emu_config_instant_vu1(void) { return s_instant_vu1; }
bool emu_config_vif1stall_hack(void) { return s_vif1stall; }
#endif
/* ---- the stream ---------------------------------------------------- */
static const u8 kCodes[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x10, 0x11, 0x13, 0x14, 0x15, 0x17,
0x20, 0x30, 0x31, 0x4a, 0x50, 0x51,
0x60, 0x65, 0x6c, 0x70, 0x75, 0x7c,
0x08, 0x12, 0x16, 0x21, 0x40, 0x67, 0x7f,
};
enum { kStreamWords = 4096 };
alignas(16) static u32 s_stream[kStreamWords + 64];
static u32 s_len;
static void put(u32 w)
{
if (s_len < kStreamWords)
s_stream[s_len++] = w;
}
static void build_stream(void)
{
s_len = 0;
while (s_len < kStreamWords - 600)
{
const u8 cmd = kCodes[rnd_n(sizeof(kCodes))];
u32 imm = rnd() & 0xffff, num = rnd_n(256), i, n = 0;
switch (cmd)
{
case 0x20: n = 1; break;
case 0x30:
case 0x31: n = 4; break;
case 0x4a: num = rnd_n(8) ? 1 + rnd_n(16) : 0; n = num ? num * 2 : 512; break;
case 0x50:
case 0x51: imm = 1 + rnd_n(4); n = imm * 4; break;
default:
if (cmd >= 0x60)
n = rnd_n(30);
break;
}
put(imm | (num << 16) | ((u32)cmd << 24) | (rnd_n(8) == 0 ? 0x80000000u : 0));
for (i = 0; i < n; i++)
put(rnd());
}
}
/* ---- the world ----------------------------------------------------- */
static void random_vif(int idx)
{
vifStruct* v = idx ? &vif1 : &vif0;
VIFregisters& regs = idx ? vif1Regs : vif0Regs;
DMACh& ch = idx ? vif1ch : vif0ch;
v->irq = rnd_n(8) == 0;
v->queued_program = rnd_n(3) == 0;
v->queued_pc = rnd() & 0x7ff;
v->queued_gif_wait = rnd() & 1;
v->unpackcalls = rnd_n(5);
v->irqoffset.enabled = rnd_n(4) == 0;
v->irqoffset.value = rnd_n(4);
v->inprogress = (u8)(rnd() & 0x13);
regs.err._u32 = rnd_n(3) == 0 ? (rnd() & 7) : 0;
regs.stat._u32 = rnd() & 0x3f0f;
regs.cycle.cl = (u8)rnd_n(5);
regs.cycle.wl = (u8)rnd_n(5);
regs.base = rnd() & 0x3ff;
regs.ofst = rnd() & 0x3ff;
regs.tops = rnd() & 0x3ff;
regs.itops = rnd() & 0x7ff;
ch.chcr._u32 = 0x100 | (rnd() & 0x5);
ch.qwc = rnd_n(64);
ch.madr = (rnd() & 0xffff0);
ch.tadr = (rnd() & 0xffff0);
}
static void reset_world(void)
{
memset(eeHw, 0, sizeof(eeHw));
memset(&cpuRegs, 0, sizeof(cpuRegs));
memset(&vif0, 0, sizeof(vif0));
memset(&vif1, 0, sizeof(vif1));
memset(s_micro0, 0, sizeof(s_micro0));
memset(s_micro1, 0, sizeof(s_micro1));
vuRegs[0].Micro = s_micro0;
vuRegs[1].Micro = s_micro1;
g_vif0Cycles = g_vif1Cycles = 0;
set_config();
random_vif(0);
random_vif(1);
vuRegs[0].VI[REG_VPU_STAT].UL = rnd() & 0x101;
dmacRegs.ctrl._u32 = rnd() & 0xc;
gifRegs.stat._u32 = rnd() & 0xc00;
build_stream();
}
static void mix_state(void)
{
mix_bytes(&eeHw[0x3000], 0x100);
mix_bytes(&eeHw[0x3800], 0x800);
mix_bytes(&eeHw[0x8000], 0x100);
mix_bytes(&eeHw[0x9000], 0x100);
mix_bytes(&vif0, sizeof(vif0));
mix_bytes(&vif1, sizeof(vif1));
mix(g_vif0Cycles);
mix(g_vif1Cycles);
mix(cpuRegs.dmastall);
mix(vuRegs[0].VI[REG_VPU_STAT].UL);
}
/* Resume after whatever stopped the last transfer, the way the DMA side
* and the VUs finishing would. */
static void unstall(vifStruct* v)
{
if (v->vifstalled.enabled && rnd_n(3))
v->vifstalled.enabled = false;
if (v->waitforvu && rnd_n(2))
v->waitforvu = false;
if (v->irq && rnd_n(3) == 0)
v->irq = 0;
if (rnd_n(4) == 0)
vuRegs[0].VI[REG_VPU_STAT].UL &= ~(rnd_n(2) ? 0x1u : 0x100u);
if (rnd_n(6) == 0)
vif1Regs.stat.VGW = false;
}
static void run(FILE* out)
{
int scen, i;
for (scen = 0; scen < 2000; scen++)
{
u32 pos[2] = { 0, 0 };
reset_world();
mix_state();
for (i = 0; i < 120; i++)
{
const int idx = (int)(rnd() & 1);
vifStruct* v = idx ? &vif1 : &vif0;
const bool tte = rnd_n(6) == 0;
int size, took;
bool ok;
unstall(v);
if (pos[idx] >= s_len - 64)
pos[idx] = 0;
if (tte)
size = rnd_n(2) ? 2 : 4;
else
size = 1 + (int)rnd_n(rnd_n(3) ? 16 : 64);
ok = idx ? VIF1transfer(&s_stream[pos[idx]], size, tte)
: VIF0transfer(&s_stream[pos[idx]], size, tte);
took = size - (int)v->vifpacketsize;
mix(ok);
mix((u32)took);
pos[idx] += (u32)took;
mix_state();
}
mix_bytes(s_micro0, sizeof(s_micro0));
mix_bytes(s_micro1, sizeof(s_micro1));
if ((scen + 1) % 50 == 0)
fprintf(out, "%4d %016llx\n", scen + 1, s_hash);
}
fprintf(out, " all %016llx\n", s_hash);
}
int main(int argc, char** argv)
{
FILE* out;
eeMem = (EEVM_MemoryAllocMess*)calloc(1, sizeof(*eeMem));
if (!eeMem || argc < 2)
return 2;
out = fopen(argv[1], "w");
if (!out)
return 2;
run(out);
fclose(out);
free(eeMem);
return 0;
}