V1.0
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/* gs_gl_egl.c: an OpenGL 3.3 core context without a window for the GL
|
||||
* renderer's test harness (tools/gs_replay.c built with GS_GL=1, 64-bit):
|
||||
* EGL's surfaceless platform (Mesa, e.g. llvmpipe), loaded with dlopen so no
|
||||
* GL or EGL headers or link libraries are needed. */
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void *EGLDisplay, *EGLContext, *EGLConfig, *EGLSurface;
|
||||
typedef int32_t EGLint;
|
||||
typedef unsigned EGLBoolean, EGLenum;
|
||||
static void *egl;
|
||||
static void *(*eglGetProcAddress_)(const char *);
|
||||
void *(*gl_proc_override)(const char *); /* the harness's stubs (REPLAY_GLNULL) */
|
||||
void *plat_gl_proc(const char *name)
|
||||
{
|
||||
if (gl_proc_override) return gl_proc_override(name);
|
||||
if (!egl) return 0;
|
||||
void *p = eglGetProcAddress_ ? eglGetProcAddress_(name) : 0;
|
||||
if (!p && egl) p = dlsym(egl, name);
|
||||
return p;
|
||||
}
|
||||
|
||||
static EGLBoolean (*egl_mc)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
|
||||
static EGLDisplay egl_d; static EGLContext egl_c;
|
||||
int egl_gl_init(void)
|
||||
{
|
||||
egl = dlopen("libEGL.so.1", RTLD_NOW);
|
||||
if (!egl) { fprintf(stderr, "no libEGL: %s\n", dlerror()); return 0; }
|
||||
eglGetProcAddress_ = (void *(*)(const char *))dlsym(egl, "eglGetProcAddress");
|
||||
EGLDisplay (*getPlatformDisplay)(EGLenum, void *, const EGLint *) = (EGLDisplay (*)(EGLenum, void *, const EGLint *))eglGetProcAddress_("eglGetPlatformDisplayEXT");
|
||||
EGLBoolean (*initialize)(EGLDisplay, EGLint *, EGLint *) = (EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *))dlsym(egl, "eglInitialize");
|
||||
EGLBoolean (*bindAPI)(EGLenum) = (EGLBoolean (*)(EGLenum))dlsym(egl, "eglBindAPI");
|
||||
EGLBoolean (*chooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *) = (EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *))dlsym(egl, "eglChooseConfig");
|
||||
EGLContext (*createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *) = (EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *))dlsym(egl, "eglCreateContext");
|
||||
EGLBoolean (*makeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext) = (EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext))dlsym(egl, "eglMakeCurrent");
|
||||
if (!getPlatformDisplay || !initialize || !bindAPI || !chooseConfig || !createContext || !makeCurrent) { fprintf(stderr, "EGL entry points missing\n"); return 0; }
|
||||
EGLDisplay d = getPlatformDisplay(0x31DD /* EGL_PLATFORM_SURFACELESS_MESA */, 0, 0);
|
||||
EGLint ma, mi;
|
||||
if (!d || !initialize(d, &ma, &mi)) { fprintf(stderr, "eglInitialize failed\n"); return 0; }
|
||||
bindAPI(0x30A2 /* EGL_OPENGL_API */);
|
||||
EGLint ca[] = { 0x3040 /* RENDERABLE_TYPE */, 0x0008 /* OPENGL_BIT */, 0x3038 };
|
||||
EGLConfig cfg; EGLint n = 0;
|
||||
if (!chooseConfig(d, ca, &cfg, 1, &n) || n < 1) cfg = 0; /* EGL_KHR_no_config_context */
|
||||
EGLint xa[] = { 0x3098, 3, 0x30FB, 3, 0x30FD, 1 /* core */, 0x3038 };
|
||||
EGLContext c = createContext(d, cfg, 0, xa);
|
||||
if (!c) { fprintf(stderr, "eglCreateContext failed\n"); return 0; }
|
||||
if (!makeCurrent(d, 0, 0, c)) { fprintf(stderr, "eglMakeCurrent failed\n"); return 0; }
|
||||
egl_mc = makeCurrent; egl_d = d; egl_c = c;
|
||||
return 1;
|
||||
}
|
||||
/* the context current on the calling thread, or released (the GL thread takes it over) */
|
||||
void egl_gl_current(int on) { if (egl_mc && !egl_mc(egl_d, 0, 0, on ? egl_c : 0)) fprintf(stderr, "eglMakeCurrent(%d) failed\n", on); else if (getenv("REPLAY_GLDBG")) fprintf(stderr, "eglMakeCurrent(%d) ok\n", on); }
|
||||
@@ -0,0 +1,205 @@
|
||||
/* gs_replay.c: runs GS input recorded with UYA_GSREC (host/src/gs/gs.c)
|
||||
* through a build of gs.c and prints a hash of GS memory and of the shown
|
||||
* frame after every vsync, and the time the GS took. Two builds of gs.c that
|
||||
* print the same hashes drew the same pixels.
|
||||
*
|
||||
* build (32-bit like the game; GS_C is the gs.c to test):
|
||||
* zig cc -target x86-linux-musl -O2 -march=nehalem -mfpmath=sse -malign-double -fno-strict-aliasing -fwrapv \
|
||||
* -I host/include -I host/src/gs -DGS_C='"../host/src/gs/gs.c"' tools/gs_replay.c host/src/gs/gs_gl.c -o gs_replay -static -lpthread
|
||||
* (a gs.c elsewhere: an absolute path in GS_C and -I host/src/gs)
|
||||
* run: gs_replay rec.bin [frames_dir] (UYA_GS_THREADS=n: drawing threads, default 0;
|
||||
* REPLAY_QUIET=1: no GS log; REPLAY_PROF=file: a program counter histogram, as UYA_PROF)
|
||||
*
|
||||
* The GL renderer (host/src/gs/gs_gl.c) in a 64-bit build with an EGL
|
||||
* context without a window (Mesa llvmpipe), frames read back from the GPU:
|
||||
* gcc -O2 -DGS_GL -I host/include -I host/src/gs -DGS_C='"../host/src/gs/gs.c"' tools/gs_replay.c \
|
||||
* host/src/gs/gs_gl.c tools/gs_gl_egl.c -o gs_replay_gl -ldl -lpthread -lm
|
||||
* REPLAY_SOFT=1: the software GS in the same binary; REPLAY_GLNULL=1: GL calls do nothing (the
|
||||
* CPU cost of the front end and the batching alone); REPLAY_VRAMSYNC=1: GPU targets read back into
|
||||
* GS memory at each vsync, so the vram hash compares */
|
||||
#define _GNU_SOURCE
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/time.h>
|
||||
#include GS_C
|
||||
|
||||
/* REPLAY_PROF=file: a histogram of the program counter, 1 ms of CPU time apart ("eip tid count") */
|
||||
static u32 prof_eip[1 << 16], prof_n[1 << 16];
|
||||
static void on_prof(int sig, siginfo_t *si, void *ctxv)
|
||||
{
|
||||
u32 eip = (u32)((ucontext_t *)ctxv)->uc_mcontext.gregs[
|
||||
#ifdef __x86_64__
|
||||
REG_RIP
|
||||
#else
|
||||
REG_EIP
|
||||
#endif
|
||||
], h = (eip * 2654435761u) >> 16;
|
||||
(void)sig; (void)si;
|
||||
for (int k = 0; k < 64; k++) { u32 j = (h + k) & 0xFFFF; if (prof_n[j] && prof_eip[j] != eip) continue; prof_eip[j] = eip; prof_n[j]++; return; }
|
||||
}
|
||||
static void prof_dump(void)
|
||||
{
|
||||
FILE *f = fopen(getenv("REPLAY_PROF"), "w");
|
||||
if (f) { for (int j = 0; j < 1 << 16; j++) if (prof_n[j]) fprintf(f, "%08x 0 %u\n", prof_eip[j], prof_n[j]); fclose(f); }
|
||||
}
|
||||
|
||||
#ifdef GS_GL
|
||||
#include "gs_gl_api.h"
|
||||
int egl_gl_init(void);
|
||||
int plat_gl_active;
|
||||
static int gl_null;
|
||||
int plat_gl_init(void) { return gl_null ? 1 : egl_gl_init(); }
|
||||
void egl_gl_current(int on);
|
||||
void plat_gl_current(int on) { if (!gl_null) egl_gl_current(on); }
|
||||
void plat_gl_swap(void) {}
|
||||
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
|
||||
/* REPLAY_GLNULL: every GL call returns 0 (a few return what initialisation needs) */
|
||||
static long gl_nop(void) { return 0; }
|
||||
static void GLAPIENTRY gl_getiv(GLuint a, GLenum b, GLint *v) { (void)a; (void)b; *v = 1; }
|
||||
static char gl_mapbuf[8 << 20];
|
||||
static void *GLAPIENTRY gl_map(GLenum a, GLintptr b, GLsizeiptr c, GLbitfield d) { (void)a; (void)b; (void)c; (void)d; return gl_mapbuf; }
|
||||
static GLuint gl_names;
|
||||
static void GLAPIENTRY gl_gen(GLsizei n, GLuint *o) { for (int i = 0; i < n; i++) o[i] = ++gl_names; }
|
||||
static GLuint GLAPIENTRY gl_create(void) { return ++gl_names; }
|
||||
static GLuint GLAPIENTRY gl_create1(GLenum e) { (void)e; return ++gl_names; }
|
||||
void *plat_gl_proc(const char *name);
|
||||
static void *null_proc(const char *n)
|
||||
{
|
||||
if (!strcmp(n, "glGetShaderiv") || !strcmp(n, "glGetProgramiv")) return (void *)gl_getiv;
|
||||
if (!strcmp(n, "glMapBufferRange")) return (void *)gl_map;
|
||||
if (!strcmp(n, "glGenTextures") || !strcmp(n, "glGenBuffers") || !strcmp(n, "glGenVertexArrays") || !strcmp(n, "glGenFramebuffers")) return (void *)gl_gen;
|
||||
if (!strcmp(n, "glCreateProgram")) return (void *)gl_create;
|
||||
if (!strcmp(n, "glCreateShader")) return (void *)gl_create1;
|
||||
return (void *)gl_nop;
|
||||
}
|
||||
#else
|
||||
int plat_gl_active; /* (no GL in this build) */
|
||||
int plat_gl_init(void) { return 0; }
|
||||
void plat_gl_current(int on) { (void)on; }
|
||||
void plat_gl_swap(void) {}
|
||||
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
|
||||
void *plat_gl_proc(const char *name) { (void)name; return 0; }
|
||||
#endif
|
||||
static uint32_t replay_vs;
|
||||
uint32_t uya_frame_count(void) { return replay_vs; }
|
||||
u32 vu_nonfinite;
|
||||
const char *plat_exe_dir(void) { return "."; }
|
||||
void uya_log(const char *fmt, ...)
|
||||
{
|
||||
if (getenv("REPLAY_QUIET")) return;
|
||||
va_list ap; va_start(ap, fmt); fprintf(stderr, "[gs] "); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap);
|
||||
}
|
||||
int plat_present(const uint32_t *px, int w, int h) { (void)px; (void)w; (void)h; return 1; }
|
||||
unsigned long long nsends, uya_t_vu;
|
||||
void uya_vu_dump_kick(void) {}
|
||||
void uya_vif1_dump_hist(void) {}
|
||||
|
||||
static uint32_t hash32(const void *p, size_t n) /* FNV-1a over words */
|
||||
{
|
||||
const uint32_t *w = p; uint32_t h = 2166136261u;
|
||||
for (size_t i = 0; i < n / 4; i++) h = (h ^ w[i]) * 16777619u;
|
||||
return h;
|
||||
}
|
||||
static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec / 1e9; }
|
||||
static double cpu_s(void) { struct timespec t; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t); return t.tv_sec + t.tv_nsec / 1e9; }
|
||||
static void rd(FILE *f, void *p, size_t n) { if (fread(p, 1, n, f) != n) { fprintf(stderr, "short read\n"); exit(1); } }
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) { fprintf(stderr, "usage: gs_replay rec.bin [frames_dir]\n"); return 1; }
|
||||
if (!getenv("UYA_GS_THREADS")) setenv("UYA_GS_THREADS", "0", 1);
|
||||
if (getenv("REPLAY_PROF")) {
|
||||
struct sigaction sa; memset(&sa, 0, sizeof sa); sa.sa_flags = SA_SIGINFO | SA_RESTART; sa.sa_sigaction = on_prof; sigaction(SIGPROF, &sa, 0);
|
||||
struct itimerval it = { { 0, 1000 }, { 0, 1000 } }; setitimer(ITIMER_PROF, &it, 0); atexit(prof_dump);
|
||||
}
|
||||
setenv("UYA_MTGS", "0", 1);
|
||||
#ifdef GS_GL
|
||||
if (!getenv("REPLAY_SOFT")) setenv("UYA_RENDERER", "gl", 1);
|
||||
gl_null = getenv("REPLAY_GLNULL") != 0;
|
||||
if (gl_null) { extern void *(*gl_proc_override)(const char *); gl_proc_override = null_proc; } /* the loader hands out the stubs */
|
||||
#endif
|
||||
FILE *f = fopen(argv[1], "rb");
|
||||
if (!f) { perror(argv[1]); return 1; }
|
||||
uya_gs_init();
|
||||
u32 m; rd(f, &m, 4);
|
||||
if (m != 0x52534755u) { fprintf(stderr, "not a GS recording\n"); return 1; }
|
||||
#define X(fl) { u32 n_; rd(f, &n_, 4); if (n_ != sizeof fl) { fprintf(stderr, "field " #fl " size %u, expected %u\n", n_, (u32)sizeof fl); return 1; } rd(f, &fl, sizeof fl); }
|
||||
GS_SNAP_FIELDS(X)
|
||||
#undef X
|
||||
rd(f, vram, 4 << 20);
|
||||
static u32 buf[(0x10000 + 16) * 4];
|
||||
static u32 *big; static size_t bign;
|
||||
double t = 0, t0 = now_s(), c = 0;
|
||||
u32 k;
|
||||
while (fread(&k, 4, 1, f) == 1) {
|
||||
if (k == 1) {
|
||||
u32 h[2]; rd(f, h, 8);
|
||||
u32 *d = buf;
|
||||
if (h[1] > 0x10000) { if (bign < h[1] * 16) { free(big); big = malloc(h[1] * 16); bign = h[1] * 16; } d = big; }
|
||||
rd(f, d, h[1] * 16);
|
||||
double a = now_s(), ac = cpu_s();
|
||||
uya_gs_src = path_name[h[0] < 3 ? h[0] : 2];
|
||||
uya_gs_gif(d, h[1]);
|
||||
t += now_s() - a; c += cpu_s() - ac;
|
||||
} else if (k == 2) {
|
||||
u64 r[6]; rd(f, r, 48);
|
||||
double a = now_s(), ac = cpu_s();
|
||||
u32 w = 0, hh = 0;
|
||||
if (getenv("REPLAY_DISPFBP")) r[2] = (r[2] & ~0x1FFull) | ((u64)strtol(getenv("REPLAY_DISPFBP"), 0, 16) / 32); /* testing: show another buffer */
|
||||
gs_drain();
|
||||
int ok;
|
||||
#ifdef GS_GL
|
||||
if (gsgl_on) {
|
||||
gs_disp_t dd;
|
||||
ok = gs_disp(r[0], r[1], r[2], r[3], r[4], r[5], &dd);
|
||||
if (ok && !gl_null) ok = gsgl_capture(&dd, frame_px);
|
||||
else gsgl_flush();
|
||||
w = dd.w; hh = dd.h;
|
||||
if (getenv("REPLAY_VRAMSYNC")) gsgl_cpu_read(0, 0x4000);
|
||||
if (ok && !gl_null && getenv("REPLAY_FULL") && argc > 2) { /* the upscaled picture too: full_NNNNN.ppm */
|
||||
int gsgl_capture_full(const gs_disp_t *d, u32 *out, int *ow, int *oh);
|
||||
static u32 *big; int fw, fh;
|
||||
if (!big) big = malloc((size_t)4096 * 4096 * 4);
|
||||
if (gsgl_capture_full(&dd, big, &fw, &fh)) {
|
||||
char p[512]; snprintf(p, sizeof p, "%s/full_%05u.ppm", argv[2], replay_vs + 1);
|
||||
FILE *o = fopen(p, "wb");
|
||||
if (o) { fprintf(o, "P6 %d %d 255\n", fw, fh);
|
||||
for (int y = 0; y < fh; y++) for (int x = 0; x < fw; x++) { u32 c = big[(size_t)y * fw + x]; fputc(c & 255, o); fputc((c >> 8) & 255, o); fputc((c >> 16) & 255, o); }
|
||||
fclose(o); }
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
ok = gs_capture(r[0], r[1], r[2], r[3], r[4], r[5], frame_px, &w, &hh);
|
||||
t += now_s() - a; c += cpu_s() - ac;
|
||||
replay_vs++;
|
||||
printf("vsync %u vram %08X frame %08X\n", replay_vs, hash32(vram, 4 << 20), ok ? hash32(frame_px, (size_t)w * hh * 4) : 0);
|
||||
if (argc > 2 && ok) {
|
||||
char p[512]; snprintf(p, sizeof p, "%s/r_%05u.ppm", argv[2], replay_vs);
|
||||
FILE *o = fopen(p, "wb");
|
||||
if (o) { fprintf(o, "P6 %u %u 255\n", w, hh);
|
||||
for (u32 i = 0; i < w * hh; i++) { u32 c = frame_px[i]; fputc((c >> 16) & 255, o); fputc((c >> 8) & 255, o); fputc(c & 255, o); }
|
||||
fclose(o); }
|
||||
}
|
||||
} else if (k == 3) {
|
||||
u32 h[4]; rd(f, h, 16);
|
||||
u32 *px = malloc((size_t)h[2] * h[3] * 4); rd(f, px, (size_t)h[2] * h[3] * 4);
|
||||
double a = now_s();
|
||||
uya_gs_write_ct32(h[0], h[1], (int)h[2], (int)h[3], px);
|
||||
t += now_s() - a;
|
||||
free(px);
|
||||
} else { fprintf(stderr, "bad record %u\n", k); return 1; }
|
||||
}
|
||||
#ifdef GS_GL
|
||||
if (gsgl_on) gsgl_stats((int)replay_vs);
|
||||
#endif
|
||||
fprintf(stderr, "%u vsyncs: GS time %.3f s (%.2f ms/vsync), CPU %.2f ms/vsync, wall %.3f s\n", replay_vs, t, replay_vs ? 1000 * t / replay_vs : 0,
|
||||
replay_vs ? 1000 * c / replay_vs : 0, now_s() - t0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
#!/usr/bin/env python3
|
||||
"""vu_refcheck.py DECOMP_REPO IMAGE VU [vucap_before.bin vucap_after.bin]
|
||||
|
||||
Testing aid for tools/vu_recomp.py: a plain interpreter of VU micro code that
|
||||
replays a state the running wrapper captured (UYA_VUCAPTURE=image,n, see
|
||||
host/src/gfx/vu.c) and compares its result with what the translated C
|
||||
produced: registers, flags, Q and data memory. It works out stalls at run
|
||||
time (the translator does it statically), so it also checks that model. The
|
||||
MAC/status flags are only compared for images that read them (the
|
||||
translation skips them otherwise). Needs numpy.
|
||||
"""
|
||||
import os, struct, sys
|
||||
import numpy as np
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from vu_isa import decode_pair, XYZW
|
||||
|
||||
f32 = np.float32
|
||||
np.seterr(all="ignore")
|
||||
|
||||
def load_images():
|
||||
d = open(os.path.join(sys.argv[1], "baserom", "SCES_509.16"), "rb").read()
|
||||
e_shoff, = struct.unpack_from("<I", d, 0x20); shentsize, shnum, shstrndx = struct.unpack_from("<HHH", d, 0x2E)
|
||||
secs = [struct.unpack_from("<10I", d, e_shoff + i * shentsize) for i in range(shnum)]
|
||||
so = secs[shstrndx][4]
|
||||
sizes = {}
|
||||
for s in secs:
|
||||
n = d[so + s[0]:d.index(b"\0", so + s[0])].decode()
|
||||
if n.startswith(".DVP.overlay"): sizes[n] = s[5]
|
||||
if n == ".DVP.ovlytab": tab = d[s[4]:s[4] + s[5]]
|
||||
if n == ".DVP.ovlystrtab": st = d[s[4]:s[4] + s[5]]
|
||||
imgs = {}
|
||||
for i in range(0, len(tab) // 12 * 12, 12):
|
||||
no, lma, vma = struct.unpack_from("<3I", tab, i)
|
||||
n = st[no:st.index(b"\0", no)].decode()
|
||||
g = n.split(".")[5]
|
||||
imgs.setdefault(g, {})
|
||||
b = d[lma - 0x100080 + 0x1000: lma - 0x100080 + 0x1000 + sizes[n]]
|
||||
for k in range(0, len(b), 8):
|
||||
imgs[g][vma + k] = struct.unpack_from("<II", b, k)
|
||||
return imgs
|
||||
|
||||
FLAGREAD = {"fcand", "fcor", "fceq", "fcget", "fsand", "fsor", "fseq", "fmand", "fmor", "fmeq"}
|
||||
def vi_written(lw):
|
||||
n = lw.name
|
||||
if n in ("iadd", "isub", "iand", "ior"): return lw.id_ & 15
|
||||
if n in ("iaddi", "iaddiu", "isubiu", "ilw", "ilwr", "mtir", "xtop", "xitop", "bal", "jalr"): return lw.it & 15
|
||||
if n in ("lqi", "lqd"): return lw.is_ & 15
|
||||
if n in ("sqi", "sqd"): return lw.it & 15
|
||||
return 0
|
||||
|
||||
SZ = 748 # sizeof(RcVu0), host/include/rc.h (keep in step with the struct)
|
||||
def parse(path, vu):
|
||||
b = open(path, "rb").read()
|
||||
pc, = struct.unpack_from("<I", b, 0)
|
||||
st = b[4:4 + SZ]
|
||||
msz = 0x4000 if vu else 0x1000
|
||||
mem = bytearray(b[4 + SZ:4 + SZ + msz])
|
||||
top, itop = struct.unpack_from("<2I", b, 4 + SZ + msz) if len(b) >= 4 + SZ + msz + 8 else (0, 0)
|
||||
vf = np.frombuffer(st[0:512], dtype=np.uint32).reshape(32, 4).copy()
|
||||
acc = np.frombuffer(st[512:528], dtype=np.uint32).copy()
|
||||
q, i, p, r = struct.unpack_from("<4I", st, 528)
|
||||
vi = list(struct.unpack_from("<16I", st, 544))
|
||||
clip, status, mac = struct.unpack_from("<3I", st, 608)
|
||||
return dict(pc=pc, vf=vf, acc=acc, q=q, i=i, vi=vi, clip=clip, status=status, mac=mac, mem=mem, top=top, itop=itop)
|
||||
|
||||
def fl(u): return np.array([u], dtype=np.uint32).view(np.float32)[0]
|
||||
def bits(x): return int(np.array([x], dtype=np.float32).view(np.uint32)[0])
|
||||
|
||||
class VU:
|
||||
def __init__(s, st, code, vu):
|
||||
s.vf = st["vf"]; s.acc = st["acc"]; s.vi = st["vi"]; s.q = st["q"]; s.i = st["i"]
|
||||
s.clip = st["clip"]; s.status = st["status"]; s.mac = st["mac"]; s.mem = st["mem"]
|
||||
s.code = code; s.vu = vu; s.qmask = 0x3FF if vu else 0xFF
|
||||
s.cyc = 0; s.ready = {} # (reg, comp) -> cycle
|
||||
s.fpipe = [] # (rdy, mac, status)
|
||||
s.cpipe = []
|
||||
s.cl_int = s.clip; s.st_int = s.status
|
||||
s.qp = None # (rdy, val)
|
||||
s.kicks = []
|
||||
s.top, s.itop = st["top"], st["itop"]
|
||||
s.vhist = []
|
||||
s.targets = set()
|
||||
for pc_, w_ in code.items():
|
||||
l_ = decode_pair(*w_)[1]
|
||||
if l_ is not None and l_.name in ("b", "bal", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
|
||||
s.targets.add((pc_ + 8 + l_.imm * 8) & (0x3FF8 if vu else 0xFF8))
|
||||
def F(s, r, c): return fl(int(s.vf[r][c]))
|
||||
def setF(s, r, c, x):
|
||||
if r: s.vf[r][c] = bits(x)
|
||||
def memw(s, qa, c): o = ((qa & s.qmask) << 4) + 4 * c; return struct.unpack_from("<I", s.mem, o)[0]
|
||||
def setmem(s, qa, c, v): o = ((qa & s.qmask) << 4) + 4 * c; struct.pack_into("<I", s.mem, o, v & 0xFFFFFFFF)
|
||||
def flsync(s):
|
||||
while s.fpipe and s.fpipe[0][0] <= s.cyc:
|
||||
_, s.mac, s.status = s.fpipe.pop(0)
|
||||
while s.cpipe and s.cpipe[0][0] <= s.cyc:
|
||||
_, s.clip = s.cpipe.pop(0)
|
||||
if s.qp and s.qp[0] <= s.cyc:
|
||||
s.q = s.qp[1]; s.qp = None
|
||||
def setvi(s, r, v):
|
||||
if r & 15: s.vi[r & 15] = v & 0xFFFF
|
||||
def run(s, pc, maxsteps=2000000):
|
||||
bpending = None; ending = 0
|
||||
steps = 0
|
||||
while True:
|
||||
steps += 1
|
||||
if steps > maxsteps: raise Exception("too many steps")
|
||||
lo, hi = s.code[pc]
|
||||
u, l, flg, imm = decode_pair(lo, hi)
|
||||
# stalls
|
||||
reads = s.reads(u, l)
|
||||
t = s.cyc + 1
|
||||
need = max([s.ready.get(x, 0) for x in reads] + [0])
|
||||
t = max(t, need)
|
||||
if l is not None and l.name in ("waitq", "div", "sqrt", "rsqrt") and s.qp:
|
||||
t = max(t, s.qp[0])
|
||||
stalled = t > s.cyc + 1
|
||||
s.cyc = t
|
||||
s.flsync()
|
||||
# upper
|
||||
ures = s.upper(u)
|
||||
# lower
|
||||
nb = None
|
||||
before_vi = list(s.vi)
|
||||
if imm is not None:
|
||||
s.i = imm
|
||||
elif l is not None:
|
||||
# branch VI delay (see tools/vu_recomp.py): the branch reads the value
|
||||
# from before a run of writes to its register right before it
|
||||
patch = {}
|
||||
if l.name in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez", "jr", "jalr") and not stalled and pc not in s.targets:
|
||||
regs = [l.is_ & 15] + ([l.it & 15] if l.name in ("ibeq", "ibne") else [])
|
||||
for x in regs:
|
||||
if not x: continue
|
||||
h = s.vhist
|
||||
if not h or h[-1][0] != x or h[-1][2]: continue
|
||||
k = len(h) - 1
|
||||
while k > 0 and len(h) - k < 4 and h[k - 1][0] == x and not h[k - 1][2] and not h[k][3] and not h[k][4]:
|
||||
k -= 1
|
||||
patch[x] = h[k][1]
|
||||
break
|
||||
real = {x: s.vi[x] for x in patch}
|
||||
for x, val in patch.items(): s.vi[x] = val
|
||||
nb = s.lower(l, pc)
|
||||
for x, val in patch.items():
|
||||
if s.vi[x] == val: s.vi[x] = real[x]
|
||||
# history of VI writes: (reg, value before, reads flags, stalled, branch target)
|
||||
if l is not None and imm is None:
|
||||
wr = vi_written(l)
|
||||
s.vhist.append((wr, before_vi[wr] if wr else 0, l.name in FLAGREAD, stalled, pc in s.targets))
|
||||
if len(s.vhist) > 8: s.vhist.pop(0)
|
||||
else:
|
||||
s.vhist.append((0, 0, False, stalled, pc in s.targets))
|
||||
if len(s.vhist) > 8: s.vhist.pop(0)
|
||||
# commit upper
|
||||
if ures: ures()
|
||||
for x in s.writes(u, l):
|
||||
s.ready[x] = s.cyc + 4
|
||||
# control
|
||||
if ending:
|
||||
return pc + 8
|
||||
if bpending is not None:
|
||||
tgt = bpending; bpending = None
|
||||
if flg["E"]: ending = 1
|
||||
pc = tgt
|
||||
continue
|
||||
if flg["E"]:
|
||||
ending = 1
|
||||
if nb is not None:
|
||||
bpending = nb
|
||||
pc = pc + 8
|
||||
def reads(s, u, l):
|
||||
r = []
|
||||
k = u.kind
|
||||
if k == "arith":
|
||||
for c in u.comps:
|
||||
r.append((u.fs, c))
|
||||
if u.bc in ("x","y","z","w"): r.append((u.ft, XYZW.index(u.bc)))
|
||||
elif u.bc is None: r.append((u.ft, c))
|
||||
elif k in ("opmula", "opmsub"):
|
||||
for c in range(3): r += [(u.fs, c), (u.ft, c)]
|
||||
elif k in ("itof", "ftoi", "abs"): r += [(u.fs, c) for c in u.comps]
|
||||
elif k == "clip": r += [(u.fs, 0), (u.fs, 1), (u.fs, 2), (u.ft, 3)]
|
||||
if l is not None:
|
||||
n = l.name
|
||||
if n in ("sq", "sqi", "sqd", "move"): r += [(l.fs, c) for c in l.comps]
|
||||
elif n == "mr32": r += [(l.fs, (c + 1) & 3) for c in l.comps]
|
||||
elif n == "mtir": r.append((l.fs, l.fsf))
|
||||
elif n == "div" or n == "rsqrt": r += [(l.fs, l.fsf), (l.ft, l.ftf)]
|
||||
elif n == "sqrt": r.append((l.ft, l.ftf))
|
||||
return [x for x in r if x[0]]
|
||||
def writes(s, u, l):
|
||||
w = []
|
||||
k = u.kind
|
||||
if k == "arith" and not u.acc: w += [(u.fd, c) for c in u.comps]
|
||||
elif k == "opmsub": w += [(u.fd, c) for c in u.comps if c < 3]
|
||||
elif k in ("itof", "ftoi", "abs"): w += [(u.ft, c) for c in u.comps]
|
||||
if l is not None and l.name in ("lq", "lqi", "lqd", "move", "mr32", "mfir"):
|
||||
w += [(l.ft, c) for c in l.comps]
|
||||
return [x for x in w if x[0]]
|
||||
def upper(s, u):
|
||||
k = u.kind
|
||||
if k == "nop": return None
|
||||
if k == "arith":
|
||||
base = u.base if u.acc else u.name
|
||||
def B(c):
|
||||
if u.bc in ("x","y","z","w"): return s.F(u.ft, XYZW.index(u.bc))
|
||||
if u.bc == "q": return fl(s.q)
|
||||
if u.bc == "i": return fl(s.i)
|
||||
return s.F(u.ft, c)
|
||||
res = {}
|
||||
for c in u.comps:
|
||||
a, b = s.F(u.fs, c), B(c)
|
||||
accv = fl(int(s.acc[c]))
|
||||
if base == "add": x = f32(a + b)
|
||||
elif base == "sub": x = f32(a - b)
|
||||
elif base == "mul": x = f32(a * b)
|
||||
elif base == "madd": x = f32(accv + f32(a * b))
|
||||
elif base == "msub": x = f32(accv - f32(a * b))
|
||||
elif base == "max": x = np.fmax(a, b)
|
||||
elif base == "mini": x = np.fmin(a, b)
|
||||
res[c] = x
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.acc: s.acc[c] = bits(x)
|
||||
elif u.fd: s.vf[u.fd][c] = bits(x)
|
||||
if base in ("add", "sub", "mul", "madd", "msub"):
|
||||
s.push_flags(res)
|
||||
return commit
|
||||
if k in ("opmula", "opmsub"):
|
||||
ex = {0: (1, 2), 1: (2, 0), 2: (0, 1)}
|
||||
res = {}
|
||||
for c in [c for c in u.comps if c < 3]:
|
||||
a, b = ex[c]
|
||||
pr = f32(s.F(u.fs, a) * s.F(u.ft, b))
|
||||
res[c] = pr if k == "opmula" else f32(fl(int(s.acc[c])) - pr)
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if k == "opmula": s.acc[c] = bits(x)
|
||||
elif u.fd: s.vf[u.fd][c] = bits(x)
|
||||
s.push_flags(res)
|
||||
return commit
|
||||
if k == "itof":
|
||||
res = {c: bits(f32(np.int32(np.uint32(s.vf[u.fs][c]).view(np.int32)) / f32(1 << u.shift))) for c in u.comps}
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.ft: s.vf[u.ft][c] = x
|
||||
return commit
|
||||
if k == "ftoi":
|
||||
res = {}
|
||||
for c in u.comps:
|
||||
x = float(s.F(u.fs, c)) * (1 << u.shift)
|
||||
x = f32(x)
|
||||
if x != x: v = 0
|
||||
elif x >= 2147483647.0: v = 0x7FFFFFFF
|
||||
elif x <= -2147483648.0: v = -0x80000000
|
||||
else: v = int(x)
|
||||
res[c] = v & 0xFFFFFFFF
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.ft: s.vf[u.ft][c] = x
|
||||
return commit
|
||||
if k == "abs":
|
||||
res = {c: int(s.vf[u.fs][c]) & 0x7FFFFFFF for c in u.comps}
|
||||
def commit():
|
||||
for c, x in res.items():
|
||||
if u.ft: s.vf[u.ft][c] = x
|
||||
return commit
|
||||
if k == "clip":
|
||||
w = abs(s.F(u.ft, 3)); x, y, z = s.F(u.fs, 0), s.F(u.fs, 1), s.F(u.fs, 2)
|
||||
f = (x > w) | ((x < -w) << 1) | ((y > w) << 2) | ((y < -w) << 3) | ((z > w) << 4) | ((z < -w) << 5)
|
||||
def commit():
|
||||
s.cl_int = ((s.cl_int << 6) | int(f)) & 0xFFFFFF
|
||||
s.cpipe.append((s.cyc + 4, s.cl_int))
|
||||
return commit
|
||||
raise Exception("upper " + u.name)
|
||||
def push_flags(s, res):
|
||||
mac = 0
|
||||
for c, x in res.items():
|
||||
b = bits(x)
|
||||
if (b & 0x7FFFFFFF) == 0: mac |= 8 >> c
|
||||
if b >> 31: mac |= 0x80 >> c
|
||||
st = (s.st_int & 0xFC0) | (1 if mac & 0xF else 0) | (2 if mac & 0xF0 else 0)
|
||||
st |= (st & 3) << 6
|
||||
s.st_int = st
|
||||
s.fpipe.append((s.cyc + 4, mac, st))
|
||||
def lower(s, l, pc):
|
||||
n = l.name; vi = s.vi
|
||||
V = lambda r: vi[r & 15] if r & 15 else 0
|
||||
if n == "move":
|
||||
vals = [int(s.vf[l.fs][c]) for c in range(4)]
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = vals[c]
|
||||
elif n == "mr32":
|
||||
vals = [int(s.vf[l.fs][(c + 1) & 3]) for c in range(4)]
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = vals[c]
|
||||
elif n in ("lq", "lqi", "lqd"):
|
||||
if n == "lqd": s.setvi(l.is_, V(l.is_) - 1)
|
||||
a = V(l.is_) + (l.imm if n == "lq" else 0)
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = s.memw(a, c)
|
||||
if n == "lqi": s.setvi(l.is_, V(l.is_) + 1)
|
||||
elif n in ("sq", "sqi", "sqd"):
|
||||
if n == "sqd": s.setvi(l.it, V(l.it) - 1)
|
||||
a = V(l.it) + (l.imm if n == "sq" else 0)
|
||||
for c in l.comps: s.setmem(a, c, int(s.vf[l.fs][c]))
|
||||
if n == "sqi": s.setvi(l.it, V(l.it) + 1)
|
||||
elif n in ("ilw", "ilwr"):
|
||||
a = V(l.is_) + (l.imm if n == "ilw" else 0)
|
||||
s.setvi(l.it, s.memw(a, l.comps[0]))
|
||||
elif n in ("isw", "iswr"):
|
||||
a = V(l.is_) + (l.imm if n == "isw" else 0)
|
||||
for c in l.comps: s.setmem(a, c, V(l.it))
|
||||
elif n == "iadd": s.setvi(l.id_, V(l.is_) + V(l.it))
|
||||
elif n == "isub": s.setvi(l.id_, V(l.is_) - V(l.it))
|
||||
elif n == "iand": s.setvi(l.id_, V(l.is_) & V(l.it))
|
||||
elif n == "ior": s.setvi(l.id_, V(l.is_) | V(l.it))
|
||||
elif n == "iaddi": s.setvi(l.it, V(l.is_) + l.imm)
|
||||
elif n == "iaddiu": s.setvi(l.it, V(l.is_) + l.imm)
|
||||
elif n == "isubiu": s.setvi(l.it, V(l.is_) - l.imm)
|
||||
elif n == "mtir": s.setvi(l.it, int(s.vf[l.fs][l.fsf]))
|
||||
elif n == "mfir":
|
||||
v = V(l.is_); v = v - 0x10000 if v & 0x8000 else v
|
||||
for c in l.comps:
|
||||
if l.ft: s.vf[l.ft][c] = v & 0xFFFFFFFF
|
||||
elif n in ("div", "sqrt", "rsqrt"):
|
||||
if s.qp: s.q = s.qp[1]; s.qp = None
|
||||
if n == "div":
|
||||
a, b = s.F(l.fs, l.fsf), s.F(l.ft, l.ftf)
|
||||
v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if (bits(a) ^ bits(b)) & 0x80000000 else 0x7F7FFFFF)
|
||||
s.qp = (s.cyc + 7, v)
|
||||
elif n == "sqrt":
|
||||
s.qp = (s.cyc + 7, bits(f32(np.sqrt(abs(s.F(l.ft, l.ftf))))))
|
||||
else:
|
||||
a, b = s.F(l.fs, l.fsf), f32(np.sqrt(abs(s.F(l.ft, l.ftf))))
|
||||
v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if bits(a) & 0x80000000 else 0x7F7FFFFF)
|
||||
s.qp = (s.cyc + 13, v)
|
||||
elif n == "waitq": pass
|
||||
elif n == "fcand": s.setvi(1, 1 if (s.clip & l.imm) else 0)
|
||||
elif n == "fcor": s.setvi(1, 1 if ((s.clip | l.imm) & 0xFFFFFF) == 0xFFFFFF else 0)
|
||||
elif n == "fceq": s.setvi(1, 1 if (s.clip & 0xFFFFFF) == l.imm else 0)
|
||||
elif n == "fcset":
|
||||
s.cpipe = []; s.clip = s.cl_int = l.imm
|
||||
elif n == "fcget": s.setvi(l.it, s.clip & 0xFFF)
|
||||
elif n == "fsand": s.setvi(l.it, s.status & l.imm)
|
||||
elif n == "fmand": s.setvi(l.it, V(l.is_) & s.mac)
|
||||
elif n == "fmor": s.setvi(l.it, V(l.is_) | s.mac)
|
||||
elif n == "xgkick": s.kicks.append(V(l.is_) & 0x3FF)
|
||||
elif n == "xtop": s.setvi(l.it, s.top)
|
||||
elif n == "xitop": s.setvi(l.it, s.itop)
|
||||
elif n == "b": return (pc + 8 + l.imm * 8) & (0x3FF8 if s.vu else 0xFF8)
|
||||
elif n == "bal": s.setvi(l.it, (pc + 16) // 8); return (pc + 8 + l.imm * 8)
|
||||
elif n == "jr": return (V(l.is_) * 8)
|
||||
elif n == "jalr":
|
||||
t = V(l.is_) * 8; s.setvi(l.it, (pc + 16) // 8); return t
|
||||
elif n in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
|
||||
a, b = V(l.it), V(l.is_)
|
||||
sb = b - 0x10000 if b & 0x8000 else b
|
||||
c = {"ibeq": a == b, "ibne": a != b, "ibltz": sb < 0, "ibgtz": sb > 0, "iblez": sb <= 0, "ibgez": sb >= 0}[n]
|
||||
if c: return pc + 8 + l.imm * 8
|
||||
else:
|
||||
raise Exception("lower " + n)
|
||||
return None
|
||||
|
||||
def main():
|
||||
g, vu = sys.argv[2], int(sys.argv[3])
|
||||
bp = sys.argv[4] if len(sys.argv) > 4 else "vucap_before.bin"
|
||||
ap = sys.argv[5] if len(sys.argv) > 5 else "vucap_after.bin"
|
||||
code = load_images()[g]
|
||||
reads_flags = any(decode_pair(*w)[1] is not None and decode_pair(*w)[1].name in
|
||||
("fsand", "fsor", "fseq", "fmand", "fmor", "fmeq") for w in code.values())
|
||||
st = parse(bp, vu); af = parse(ap, vu)
|
||||
m = VU(st, code, vu)
|
||||
m.run(st["pc"])
|
||||
while m.fpipe: _, m.mac, m.status = m.fpipe.pop(0)
|
||||
while m.cpipe: _, m.clip = m.cpipe.pop(0)
|
||||
if m.qp: m.q = m.qp[1]
|
||||
diffs = 0
|
||||
for r in range(32):
|
||||
for c in range(4):
|
||||
if int(m.vf[r][c]) != int(af["vf"][r][c]):
|
||||
diffs += 1
|
||||
if diffs < 30: print("vf%d.%s ref %08X C %08X" % (r, XYZW[c], int(m.vf[r][c]), int(af["vf"][r][c])))
|
||||
for r in range(16):
|
||||
if m.vi[r] != af["vi"][r]: diffs += 1; print("vi%d ref %04X C %04X" % (r, m.vi[r], af["vi"][r]))
|
||||
for c in range(4):
|
||||
if int(m.acc[c]) != int(af["acc"][c]): diffs += 1; print("acc.%s ref %08X C %08X" % (XYZW[c], int(m.acc[c]), int(af["acc"][c])))
|
||||
for nm in ("q", "clip") + (("status", "mac") if reads_flags else ()):
|
||||
if getattr(m, nm) != af[nm]: diffs += 1; print("%s ref %08X C %08X" % (nm, getattr(m, nm), af[nm]))
|
||||
md = [k for k in range(0, len(m.mem), 4) if m.mem[k:k+4] != af["mem"][k:k+4]]
|
||||
if md:
|
||||
diffs += len(md)
|
||||
print("memory differs at %d words, first at qw 0x%X" % (len(md), md[0] // 16))
|
||||
for k in md[:10]:
|
||||
print(" qw %03X.%s ref %08X C %08X" % (k // 16, XYZW[(k // 4) & 3], struct.unpack_from("<I", m.mem, k)[0], struct.unpack_from("<I", af["mem"], k)[0]))
|
||||
print("kicks:", ["%03X" % k for k in m.kicks][:20], "cycles", m.cyc)
|
||||
print("DIFFS", diffs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user