V1.0
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/* rac1_common.h: replaces OpenRAC's include/common.h when the game's C is
|
||||
* built for a PC (gen_host.py copies it to gen/include/common.h).
|
||||
*
|
||||
* The host is a 32-bit x86 program with PS2 RAM mapped at its real addresses,
|
||||
* so pointers are 4 bytes and structs keep their PS2 layout. `long` is
|
||||
* rewritten to `long long` by gen_host.py (the EE's long is 64-bit); build
|
||||
* with -malign-double so 64-bit fields keep 8-byte alignment. */
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include "uya_host.h"
|
||||
#include "names.h"
|
||||
|
||||
/* Matching-only placement attributes: no meaning on the host. */
|
||||
#define NOT_SDA
|
||||
#define MACRO_ADDR
|
||||
/* SDATA(D_x) names a scalar by another C name: an asm label to the address symbol
|
||||
* (expanded first: a level program's names carry its suffix, gen/lNN/lNN_names.h) */
|
||||
#define UYA_SYMSTR_(x) #x
|
||||
#define UYA_SYMSTR(x) UYA_SYMSTR_(x)
|
||||
#ifdef _WIN32
|
||||
#define SDATA(sym) __asm__("_" UYA_SYMSTR(sym))
|
||||
#else
|
||||
#define SDATA(sym) __asm__(UYA_SYMSTR(sym))
|
||||
#endif
|
||||
|
||||
/* The decomp's quadword copy and clear (lq/sq inline asm on the EE). */
|
||||
static __inline__ void qcopy(void *dst, void *src) { __builtin_memcpy(dst, src, 16); }
|
||||
static __inline__ void qcopy_nc(void *dst, void *src) { __builtin_memcpy(dst, src, 16); }
|
||||
static __inline__ void qzero(void *p) { __builtin_memset(p, 0, 16); }
|
||||
|
||||
/* EE FPU division for C patched with it (tools/source_patches.py): a division
|
||||
* by zero gives +-MAX (the sign of a XOR b), never inf or NaN. */
|
||||
static __inline__ float ee_fdiv(float a, float b)
|
||||
{
|
||||
if (b == 0.0f) {
|
||||
unsigned ua, ub; __builtin_memcpy(&ua, &a, 4); __builtin_memcpy(&ub, &b, 4);
|
||||
unsigned r = ((ua ^ ub) & 0x80000000u) | 0x7F7FFFFFu; float f; __builtin_memcpy(&f, &r, 4); return f;
|
||||
}
|
||||
return a / b;
|
||||
}
|
||||
|
||||
#endif /* COMMON_H */
|
||||
@@ -0,0 +1,33 @@
|
||||
/* rac1_levels.h: the level programs compiled into the wrapper (docs/LEVELS.md).
|
||||
*
|
||||
* Each level carries its own build of the game program; ParseBin copies it
|
||||
* over the executable's main segment. build.py compiles the levels it is
|
||||
* asked for (--levels) and lists them in gen/level_table.c; ParseBin
|
||||
* (host/src/rac1/levels.c) finds the one being loaded there and switches the
|
||||
* trampolines and the recompiled code's dispatch table to it. */
|
||||
#ifndef RAC1_LEVELS_H
|
||||
#define RAC1_LEVELS_H
|
||||
#include "uya_runtime.h"
|
||||
|
||||
typedef struct RcCtx RcCtx;
|
||||
typedef struct { uint32_t addr; void (*fn)(RcCtx *); } rac1_rc_row_t;
|
||||
|
||||
typedef struct {
|
||||
int id; /* level index (docs/OVERLAYS.md, Levels) */
|
||||
uint32_t entry; /* the entry point ParseBin returns */
|
||||
uint32_t text_addr, text_bytes; /* its text record: how a loaded program is recognised */
|
||||
const uya_tramp_t *tramps; /* uya_tramp_table_lNN */
|
||||
const int *tramp_count;
|
||||
const rac1_rc_row_t *rc_table; /* rcNN_table: its recompiled functions and thunks by address */
|
||||
const int *rc_count;
|
||||
} rac1_level_t;
|
||||
|
||||
extern const rac1_level_t rac1_levels[];
|
||||
extern const int rac1_level_count;
|
||||
|
||||
/* host/src/rac1/rc_runtime.c: the table rc_dispatch looks addresses up in */
|
||||
void rc_set_table(const rac1_rc_row_t *table, int count);
|
||||
/* host/src/runtime.c */
|
||||
void uya_install_tramp_table(const uya_tramp_t *table, int count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
/* rc.h: runtime for code produced by tools/recomp.py.
|
||||
*
|
||||
* recomp.py translates the game's assembly-only functions (MIPS R5900, with
|
||||
* MMI and VU0 macro instructions) into C ahead of time. Each becomes a C
|
||||
* function over an RcCtx (the EE's registers); it is compiled with the rest of
|
||||
* the program, so nothing is translated or interpreted while the game runs.
|
||||
* Memory is the PS2's own address space, mapped at the same addresses. */
|
||||
#ifndef RC_H
|
||||
#define RC_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* the helpers below are tiny: always inline them (the generated code is built
|
||||
* with -fno-builtin -fno-inline-functions, which otherwise leaves each one,
|
||||
* and the memcpy in it, a call) */
|
||||
#define RC_INLINE static inline __attribute__((always_inline))
|
||||
|
||||
typedef int8_t s8; typedef uint8_t u8; typedef int16_t s16; typedef uint16_t u16;
|
||||
typedef int32_t s32; typedef uint32_t u32; typedef int64_t s64; typedef uint64_t u64;
|
||||
typedef float f32;
|
||||
|
||||
typedef union {
|
||||
u64 d[2]; s64 sd[2]; u32 w[4]; s32 sw[4]; u16 h[8]; s16 sh[8]; u8 b[16]; s8 sb[16];
|
||||
} R128;
|
||||
|
||||
typedef struct RcCtx {
|
||||
R128 r[32];
|
||||
R128 hi, lo; /* hi/lo in the low doubleword, hi1/lo1 in the high one */
|
||||
u32 sa;
|
||||
f32 f[32];
|
||||
f32 facc; /* the EE FPU accumulator */
|
||||
int fcc; /* FPU condition */
|
||||
} RcCtx;
|
||||
|
||||
typedef struct {
|
||||
f32 vf[32][4];
|
||||
f32 acc[4];
|
||||
f32 q, i, p, r;
|
||||
u32 vi[16];
|
||||
u32 clip, status, mac;
|
||||
/* micro mode (tools/vu_recomp.py, host/include/vu.h): CMSAR0, where the
|
||||
* last microprogram ended (MSCNT), and the results still in the pipeline:
|
||||
* MAC/status and clip flags become visible 4 cycles after the op issues,
|
||||
* Q 7 (13 for RSQRT) cycles after the DIV. */
|
||||
u32 cmsar0, tpc;
|
||||
int susp; /* VU0: parked in a wait loop for the EE (tpc = where to resume) */
|
||||
u32 st_int, cl_int;
|
||||
u32 fp_n, fp_h, fp_rdy[4], fp_mac[4], fp_st[4];
|
||||
u32 cp_n, cp_h, cp_rdy[4], cp_val[4];
|
||||
u32 q_pend, q_rdy; f32 q_new;
|
||||
} RcVu0;
|
||||
extern RcVu0 rc_vu0;
|
||||
|
||||
/* the shared stack pointer for nested C <-> recompiled calls */
|
||||
extern u32 rc_sp;
|
||||
#define RC_GP 0x00166D00u
|
||||
|
||||
void rc_dispatch(RcCtx *c, u32 addr); /* call any function by PS2 address */
|
||||
void rc_vcallms(RcCtx *c, u32 addr);
|
||||
void rc_vu0_resume(void); /* a VU0 microprogram (host/src/gfx/vu.c) */
|
||||
void vu_run(int vu, u32 pc); /* run the translated microprogram at pc */
|
||||
void rc_syscall(RcCtx *c);
|
||||
void rc_break(u32 pc);
|
||||
void rc_bad(u32 pc, const char *what);
|
||||
void rc_enter(RcCtx *c);
|
||||
u32 rc_cop0(int r);
|
||||
|
||||
/* ---- memory: KSEG aliases of RAM (0x20000000-0x3FFFFFFF uncached/accelerated) fold back ---- */
|
||||
RC_INLINE uintptr_t RC_A(u32 a) { return (uintptr_t)((((a >> 28) == 2 || (a >> 28) == 3) && (a & 0x0FFFFFFFu) < 0x02000000u) ? (a & 0x0FFFFFFFu) : a); }
|
||||
#define M8(a) (*(u8 *)RC_A(a))
|
||||
#define M16(a) (*(u16 *)RC_A(a))
|
||||
#define M32(a) (*(u32 *)RC_A(a))
|
||||
#define M64(a) (*(u64 *)RC_A(a))
|
||||
#define MF(a) (*(f32 *)RC_A(a))
|
||||
/* The EE's hardware registers (0x10000000-0x1000FFFF): a store that starts a
|
||||
* DMA channel runs the transfer at once; FIFO writes go to the VIF/GIF. */
|
||||
void rc_io_write32(u32 a, u32 v);
|
||||
void rc_io_write128(u32 a, const void *v);
|
||||
RC_INLINE int rc_is_io(u32 a) { return (a & 0xFFFF0000u) == 0x10000000u; }
|
||||
RC_INLINE void rc_sw(u32 a, u32 v) { if (rc_is_io(a)) rc_io_write32(a, v); else M32(a) = v; }
|
||||
RC_INLINE void rc_sd(u32 a, u64 v) { if (rc_is_io(a)) { rc_io_write32(a, (u32)v); rc_io_write32(a + 4, (u32)(v >> 32)); } else M64(a) = v; }
|
||||
/* Quadword accesses (lq, sq, lqc2, sqc2) ignore the low 4 address bits on the
|
||||
* EE, and the game relies on it. The one exception on the host: a local array
|
||||
* of the C code on the x86 stack, which is not 16-byte aligned as the EE's
|
||||
* would be, is used where it is (rc_cstack_lo/hi: the x86 stack, set by
|
||||
* rc_enter). */
|
||||
extern u32 rc_cstack_lo, rc_cstack_hi;
|
||||
RC_INLINE uintptr_t RC_QA(u32 a)
|
||||
{
|
||||
return RC_A(a - rc_cstack_lo < rc_cstack_hi - rc_cstack_lo ? a : (a & ~15u));
|
||||
}
|
||||
RC_INLINE void rc_lq(R128 *d, u32 a) { __builtin_memcpy(d, (void *)RC_QA(a), 16); }
|
||||
RC_INLINE void rc_sq(const R128 *s, u32 a) { if (rc_is_io(a)) rc_io_write128(a & ~15u, s); else __builtin_memcpy((void *)RC_QA(a), s, 16); }
|
||||
|
||||
#define SE32(x) ((u64)(s64)(s32)(u32)(x))
|
||||
|
||||
/* unaligned loads/stores (little-endian EE) */
|
||||
RC_INLINE u64 rc_lwl(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 w = M32(a & ~3u);
|
||||
u32 keep = k == 24 ? 0 : (0x00FFFFFFu >> k); return SE32(((u32)rt & keep) | (w << (24 - k))); }
|
||||
RC_INLINE u64 rc_lwr(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 w = M32(a & ~3u);
|
||||
if (k == 0) return SE32(w);
|
||||
u32 keep = ~(0xFFFFFFFFu >> k); return (rt & 0xFFFFFFFF00000000ull) | (((u32)rt & keep) | (w >> k)); }
|
||||
RC_INLINE void rc_swl(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 *p = (u32 *)RC_A(a & ~3u);
|
||||
u32 keep = k == 24 ? 0 : (0xFFFFFF00u << k); *p = (*p & keep) | ((u32)rt >> (24 - k)); }
|
||||
RC_INLINE void rc_swr(u64 rt, u32 a) { u32 k = (a & 3) * 8; u32 *p = (u32 *)RC_A(a & ~3u);
|
||||
u32 keep = k == 0 ? 0 : (0x00FFFFFFu >> (24 - k)); *p = (*p & keep) | ((u32)rt << k); }
|
||||
RC_INLINE u64 rc_ldl(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 w = M64(a & ~7u);
|
||||
u64 keep = k == 56 ? 0 : (0x00FFFFFFFFFFFFFFull >> k); return (rt & keep) | (w << (56 - k)); }
|
||||
RC_INLINE u64 rc_ldr(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 w = M64(a & ~7u);
|
||||
u64 keep = k == 0 ? 0 : ~(0xFFFFFFFFFFFFFFFFull >> k); return (rt & keep) | (w >> k); }
|
||||
RC_INLINE void rc_sdl(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 *p = (u64 *)RC_A(a & ~7u);
|
||||
u64 keep = k == 56 ? 0 : (0xFFFFFFFFFFFFFF00ull << k); *p = (*p & keep) | (rt >> (56 - k)); }
|
||||
RC_INLINE void rc_sdr(u64 rt, u32 a) { u32 k = (a & 7) * 8; u64 *p = (u64 *)RC_A(a & ~7u);
|
||||
u64 keep = k == 0 ? 0 : (0x00FFFFFFFFFFFFFFull >> (56 - k)); *p = (*p & keep) | (rt << k); }
|
||||
|
||||
/* ---- integer helpers ---- */
|
||||
RC_INLINE s32 rc_plzcw1(u32 x) { u32 s = x >> 31; s32 n = 0; for (int b = 31; b >= 0 && ((x >> b) & 1) == s; b--) n++; return n - 1; }
|
||||
RC_INLINE s32 rc_clamp16(s32 v) { return v > 32767 ? 32767 : v < -32768 ? -32768 : v; }
|
||||
|
||||
/* ---- floats ---- */
|
||||
RC_INLINE u32 rc_fbits(f32 f) { u32 u; __builtin_memcpy(&u, &f, 4); return u; }
|
||||
RC_INLINE f32 rc_bitsf(u32 u) { f32 f; __builtin_memcpy(&f, &u, 4); return f; }
|
||||
RC_INLINE s32 rc_cvtws(f32 f) { /* EE: truncates, saturates */
|
||||
if (f >= 2147483647.0f) return 0x7FFFFFFF;
|
||||
if (f <= -2147483648.0f) return (s32)0x80000000u;
|
||||
return (s32)f;
|
||||
}
|
||||
RC_INLINE f32 rc_fsqrt(f32 x) { return __builtin_sqrtf(__builtin_fabsf(x)); }
|
||||
/* The EE FPU and the VUs have no infinities: a result that overflows is
|
||||
* +-MAX (0x7F7FFFFF). Without this, MAX * MAX (FastArcSin of a huge value
|
||||
* after a division by zero) becomes inf and then NaN, which a camera's
|
||||
* matrices keep for good. */
|
||||
RC_INLINE f32 rc_fc(f32 x) { u32 b = rc_fbits(x); return ((b & 0x7F800000u) == 0x7F800000u) ? rc_bitsf((b & 0x80000000u) | 0x7F7FFFFFu) : x; }
|
||||
RC_INLINE f32 rc_fdiv(f32 a, f32 b) { if (b == 0.0f) return (rc_fbits(a) ^ rc_fbits(b)) & 0x80000000u ? -3.4028235e38f : 3.4028235e38f; return a / b; }
|
||||
RC_INLINE s32 rc_ftoi(f32 f, int shift) { return rc_cvtws(f * (f32)(1 << shift)); }
|
||||
RC_INLINE f32 rc_itof(s32 i, int shift) { return (f32)i / (f32)(1 << shift); }
|
||||
|
||||
/* COP2 macro mode: an FMAC op (ADD/SUB/MUL/MADD/MSUB/OPMULA/OPMSUB and their
|
||||
* ACC forms) sets the MAC flags (cfc2 $vi17) from its result, per written
|
||||
* component (Z bits 3..0 = x..w, S bits 7..4), the others cleared, and the
|
||||
* status flag's Z/S/U/O bits (cfc2 $vi16) from them, with the sticky copies
|
||||
* (bits 6..9). The collision code (coll_sphere, func_L00_001F10E0) tests the
|
||||
* signs of a VSUB this way: `cfc2 $at, $vi17; andi $at, 0xE0`. */
|
||||
RC_INLINE void rc_vu0_flags(const f32 *v, u32 dest)
|
||||
{
|
||||
u32 mac = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!(dest & (8u >> i))) continue;
|
||||
u32 b; __builtin_memcpy(&b, &v[i], 4);
|
||||
if ((b & 0x7FFFFFFFu) == 0) mac |= 8u >> i;
|
||||
if (b >> 31) mac |= 0x80u >> i;
|
||||
if ((b & 0x7F800000u) == 0x7F800000u) mac |= 0x8000u >> i; /* overflow */
|
||||
}
|
||||
rc_vu0.mac = mac;
|
||||
u32 st = (mac & 0xF ? 1u : 0) | (mac & 0xF0 ? 2u : 0) | (mac & 0xF00 ? 4u : 0) | (mac & 0xF000 ? 8u : 0);
|
||||
rc_vu0.status = (rc_vu0.status & ~0xFu) | st | (st << 6);
|
||||
}
|
||||
|
||||
#define VF(n) rc_vu0.vf[n]
|
||||
#define VACC rc_vu0.acc
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
/* uya_host.h: replaces the decomp's common.h when frontbin's C is built for a PC.
|
||||
*
|
||||
* Memory model: the host is a 32-bit x86 program, so pointers are 4 bytes like
|
||||
* on the EE and every struct keeps its PS2 layout. PS2 main RAM is mapped at its
|
||||
* real addresses (0x00100000-0x01FFFFFF), so the decomp's globals (declared
|
||||
* extern, placed by address through abs_syms.s) and its absolute constants
|
||||
* point at real memory. long is rewritten to long long by gen_host.py (the
|
||||
* EE's long is 64-bit); build with -malign-double so 64-bit fields keep 8-byte
|
||||
* alignment.
|
||||
*/
|
||||
#ifndef UYA_HOST_H
|
||||
#define UYA_HOST_H
|
||||
|
||||
typedef signed char s8;
|
||||
typedef unsigned char u8;
|
||||
typedef signed short s16;
|
||||
typedef unsigned short u16;
|
||||
typedef signed int s32;
|
||||
typedef unsigned int u32;
|
||||
typedef signed long long s64;
|
||||
typedef unsigned long long u64;
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
|
||||
/* The decomp's assembly-inclusion macros have no meaning on the host;
|
||||
* gen_host.py removes their uses, these are a fallback. */
|
||||
#define INCLUDE_ASM(FOLDER, NAME)
|
||||
#define INCLUDE_RODATA(FOLDER, NAME)
|
||||
#define ASM_FUNC(FOLDER, NAME)
|
||||
#define LINKER_REMNANT(FOLDER, NAME)
|
||||
#define TEXT_PADDING(N)
|
||||
|
||||
/* The EE's MMI plzcw on one word: leading bits equal to the sign bit, minus one. */
|
||||
static inline s32 uya_plzcw(u32 x)
|
||||
{
|
||||
u32 s = x >> 31;
|
||||
s32 n = 0;
|
||||
for (int b = 31; b >= 0 && ((x >> b) & 1) == s; b--) n++;
|
||||
return n - 1;
|
||||
}
|
||||
|
||||
/* A function the host does not implement yet: logs once per name. */
|
||||
void uya_unimpl(const char *name);
|
||||
void uya_log(const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
/* uya_runtime.h: the host runtime's own interface (not seen by frontbin code). */
|
||||
#ifndef UYA_RUNTIME_H
|
||||
#define UYA_RUNTIME_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* PS2 address ranges the host maps at their real addresses. */
|
||||
#define PS2_RAM_LO 0x00100000u /* the game never touches the EE kernel area below 1 MB */
|
||||
#define PS2_RAM_HI 0x02000000u /* 32 MB of EE main RAM */
|
||||
#define PS2_SPR 0x70000000u /* scratchpad */
|
||||
#define PS2_SPR_SIZE 0x4000u
|
||||
#define PS2_EE_REGS 0x10000000u /* timers, IPU, GIF, VIF0/1, DMA, INTC... */
|
||||
#define PS2_EE_REGS_SZ 0x00010000u
|
||||
#define PS2_VU_MEM 0x11000000u /* VU0/VU1 code and data memory */
|
||||
#define PS2_VU_MEM_SZ 0x00010000u
|
||||
#define PS2_GS_PRIV 0x12000000u /* GS privileged registers */
|
||||
#define PS2_GS_PRIV_SZ 0x00002000u
|
||||
|
||||
typedef struct { uint32_t ps2_addr; void *host_fn; } uya_tramp_t;
|
||||
extern const uya_tramp_t uya_tramp_table[];
|
||||
extern const int uya_tramp_count;
|
||||
|
||||
/* platform layer */
|
||||
int plat_map_fixed(uint32_t addr, uint32_t size, int exec); /* 0 on success */
|
||||
int plat_install_trap_handler(void); /* calls into unmapped PS2 code */
|
||||
void plat_flush_icache(void *p, size_t n);
|
||||
const char *plat_exe_dir(void); /* folder of the running program */
|
||||
void plat_at_exit(void);
|
||||
uint64_t plat_time_us(void); /* monotonic clock */
|
||||
void plat_sleep_us(uint64_t us);
|
||||
/* video output: show a w x h 32-bit image (bytes B, G, R, X); returns 0 once the window was closed */
|
||||
int plat_present(const uint32_t *rgbx, int w, int h);
|
||||
void plat_movie_started(void); /* testing hook (Linux UYA_SKIPMOVIES) */
|
||||
int plat_key_skip(void); /* a skip key (Enter/Space/Esc) was pressed since the last call */
|
||||
void plat_poll(void); /* keep the window responsive; exits when it was closed */
|
||||
/* pad buttons held on the keyboard, PS2 bit order, 1 = pressed (see host/src/sdk/pad.c) */
|
||||
unsigned plat_pad_buttons(void);
|
||||
/* sound: plays `frames` interleaved stereo 16-bit samples once; plat_audio_stop ends it early.
|
||||
* `tag` names the dump file on Linux (UYA_DUMP). */
|
||||
void plat_audio_play(const int16_t *lr, size_t frames, int rate, const char *tag);
|
||||
void plat_audio_stop(void);
|
||||
/* continuous 48 kHz stereo output for the sound effects mixer (host/src/sdk/sound.c) */
|
||||
void plat_stream_write(const int16_t *lr, int frames);
|
||||
int plat_stream_queued(void); /* frames written but not played yet */
|
||||
int uya_disc_read(uint32_t lsn, uint32_t count, void *buf); /* 0 on success */
|
||||
uint32_t uya_frame_count(void); /* vsyncs so far (host/src/sdk/graph.c) */
|
||||
|
||||
/* runtime */
|
||||
int uya_load_elf_sections(const char *path, const char *const *sections);
|
||||
void uya_install_trampolines(void);
|
||||
void uya_fill_code_traps(uint32_t lo, uint32_t hi);
|
||||
void uya_log(const char *fmt, ...);
|
||||
void uya_log_open(void); /* also log to rac1_log.txt next to the program */
|
||||
void uya_trap_call(uint32_t ps2_addr, uint32_t ret_addr, const uint32_t *args); /* from the trap handler */
|
||||
void uya_report(void);
|
||||
int uya_main(int argc, char **argv);
|
||||
int uya_disc_open(const char *path);
|
||||
void uya_disc_boot(void);
|
||||
void uya_boot(void); /* boot_elf's set-up before the menu (host/src/boot.c) */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,146 @@
|
||||
/* vu.h: runtime for the VU microprograms translated by tools/vu_recomp.py.
|
||||
*
|
||||
* Each microprogram the game uploads (VU0 through VIF0, VU1 through VIF1) is
|
||||
* translated to C at build time from the user's executable. A translation is a
|
||||
* function over an RcVu0 (VU0 uses rc_vu0, the registers it shares with the
|
||||
* EE's macro mode; VU1 has rc_vu1). Data memory is the real address range:
|
||||
* VU0 at 0x11004000 (4 KB), VU1 at 0x1100C000 (16 KB).
|
||||
*
|
||||
* The VU pipeline is visible to these programs in three places and modelled
|
||||
* here: MAC/status and clip flags written by an op are seen by flag reads 4
|
||||
* cycles later, and Q from DIV/SQRT (RSQRT) 7 (13) cycles later. The
|
||||
* translator counts cycles (with the stalls it can see statically) in a local
|
||||
* `cyc`; results are queued with the cycle they become visible at. */
|
||||
#ifndef VU_H
|
||||
#define VU_H
|
||||
#include "rc.h"
|
||||
|
||||
extern RcVu0 rc_vu1;
|
||||
|
||||
#ifndef VU_TRACE
|
||||
#define VU_TRACE(pc) /* testing hook: each instruction pair's address */
|
||||
#endif
|
||||
|
||||
/* VU data memory, by quadword address */
|
||||
#define VU0_MEM(qa) ((u32 *)(uintptr_t)(0x11004000u + (((u32)(qa) & 0xFFu) << 4)))
|
||||
#define VU1_MEM(qa) ((u32 *)(uintptr_t)(0x1100C000u + (((u32)(qa) & 0x3FFu) << 4)))
|
||||
|
||||
RC_INLINE u32 vu_u(f32 f) { u32 u; __builtin_memcpy(&u, &f, 4); return u; }
|
||||
RC_INLINE f32 vu_f(u32 u) { f32 f; __builtin_memcpy(&f, &u, 4); return f; }
|
||||
|
||||
/* MAC flag bits for one component (x = 0 .. w = 3) of a result */
|
||||
/* an FMAC result as the VU has it: no inf or NaN, +-MAX instead (counted: FPSLOG) */
|
||||
extern u32 vu_nonfinite;
|
||||
RC_INLINE f32 vu_fc(f32 x)
|
||||
{
|
||||
u32 b = vu_u(x);
|
||||
if (__builtin_expect((b & 0x7F800000u) == 0x7F800000u, 0)) { vu_nonfinite++; b = (b & 0x80000000u) | 0x7F7FFFFFu; __builtin_memcpy(&x, &b, 4); }
|
||||
return x;
|
||||
}
|
||||
RC_INLINE u32 vu_macbits(f32 r, int c)
|
||||
{
|
||||
u32 u = vu_u(r), m = 0;
|
||||
if ((u & 0x7FFFFFFFu) == 0) m |= 8u >> c;
|
||||
if (u >> 31) m |= 0x80u >> c;
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ---- MAC/status flag pipeline ---- */
|
||||
RC_INLINE void vu_fl_sync(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
while (v->fp_n && (s32)(cyc - v->fp_rdy[v->fp_h]) >= 0) {
|
||||
v->mac = v->fp_mac[v->fp_h]; v->status = v->fp_st[v->fp_h];
|
||||
v->fp_h = (v->fp_h + 1) & 3; v->fp_n--;
|
||||
}
|
||||
}
|
||||
RC_INLINE void vu_fl_push(RcVu0 *v, u32 cyc, u32 mac)
|
||||
{
|
||||
u32 st = (v->st_int & 0xFC0u) | ((mac & 0xF) ? 1 : 0) | ((mac & 0xF0) ? 2 : 0);
|
||||
st |= (st & 3) << 6;
|
||||
v->st_int = st;
|
||||
if (v->fp_n == 4) { v->mac = v->fp_mac[v->fp_h]; v->status = v->fp_st[v->fp_h]; v->fp_h = (v->fp_h + 1) & 3; v->fp_n--; }
|
||||
u32 k = (v->fp_h + v->fp_n) & 3;
|
||||
v->fp_rdy[k] = cyc + 4; v->fp_mac[k] = mac; v->fp_st[k] = st; v->fp_n++;
|
||||
}
|
||||
RC_INLINE u32 vu_status(RcVu0 *v, u32 cyc) { vu_fl_sync(v, cyc); return v->status; }
|
||||
RC_INLINE u32 vu_mac(RcVu0 *v, u32 cyc) { vu_fl_sync(v, cyc); return v->mac; }
|
||||
RC_INLINE void vu_fsset(RcVu0 *v, u32 cyc, u32 imm)
|
||||
{
|
||||
vu_fl_sync(v, cyc + 8);
|
||||
v->status = (v->status & 0x3F) | (imm & 0xFC0); v->st_int = v->status;
|
||||
}
|
||||
|
||||
/* ---- clip flag pipeline ---- */
|
||||
RC_INLINE void vu_cl_sync(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
while (v->cp_n && (s32)(cyc - v->cp_rdy[v->cp_h]) >= 0) {
|
||||
v->clip = v->cp_val[v->cp_h]; v->cp_h = (v->cp_h + 1) & 3; v->cp_n--;
|
||||
}
|
||||
}
|
||||
RC_INLINE void vu_clipw(RcVu0 *v, u32 cyc, const f32 *fs, f32 w)
|
||||
{
|
||||
f32 a = __builtin_fabsf(w);
|
||||
u32 f = (fs[0] > a) | ((fs[0] < -a) << 1) | ((fs[1] > a) << 2) | ((fs[1] < -a) << 3) | ((fs[2] > a) << 4) | ((fs[2] < -a) << 5);
|
||||
v->cl_int = ((v->cl_int << 6) | f) & 0xFFFFFF;
|
||||
if (v->cp_n == 4) { v->clip = v->cp_val[v->cp_h]; v->cp_h = (v->cp_h + 1) & 3; v->cp_n--; }
|
||||
u32 k = (v->cp_h + v->cp_n) & 3;
|
||||
v->cp_rdy[k] = cyc + 4; v->cp_val[k] = v->cl_int; v->cp_n++;
|
||||
}
|
||||
RC_INLINE u32 vu_clip(RcVu0 *v, u32 cyc) { vu_cl_sync(v, cyc); return v->clip; }
|
||||
RC_INLINE void vu_fcset(RcVu0 *v, u32 cyc, u32 imm)
|
||||
{
|
||||
vu_cl_sync(v, cyc + 8);
|
||||
v->clip = v->cl_int = imm & 0xFFFFFF;
|
||||
}
|
||||
|
||||
/* ---- Q ---- */
|
||||
RC_INLINE f32 vu_q(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
if (v->q_pend && (s32)(cyc - v->q_rdy) >= 0) { v->q = v->q_new; v->q_pend = 0; }
|
||||
return v->q;
|
||||
}
|
||||
/* WAITQ, or a new DIV while one is in flight: stall until Q is written */
|
||||
RC_INLINE u32 vu_qwait(RcVu0 *v, u32 cyc)
|
||||
{
|
||||
if (v->q_pend) { if ((s32)(cyc - v->q_rdy) < 0) cyc = v->q_rdy; v->q = v->q_new; v->q_pend = 0; }
|
||||
return cyc;
|
||||
}
|
||||
RC_INLINE void vu_qset(RcVu0 *v, u32 cyc, int lat, f32 val) { v->q_new = val; v->q_pend = 1; v->q_rdy = cyc + lat; }
|
||||
|
||||
/* end of a microprogram: everything in flight lands */
|
||||
RC_INLINE void vu_settle(RcVu0 *v)
|
||||
{
|
||||
while (v->fp_n) { v->mac = v->fp_mac[v->fp_h]; v->status = v->fp_st[v->fp_h]; v->fp_h = (v->fp_h + 1) & 3; v->fp_n--; }
|
||||
while (v->cp_n) { v->clip = v->cp_val[v->cp_h]; v->cp_h = (v->cp_h + 1) & 3; v->cp_n--; }
|
||||
if (v->q_pend) { v->q = v->q_new; v->q_pend = 0; }
|
||||
v->fp_h = v->cp_h = 0;
|
||||
}
|
||||
|
||||
RC_INLINE f32 vu_div(f32 a, f32 b)
|
||||
{
|
||||
if (b == 0.0f) return ((vu_u(a) ^ vu_u(b)) & 0x80000000u) ? -3.4028235e38f : 3.4028235e38f;
|
||||
return a / b;
|
||||
}
|
||||
RC_INLINE f32 vu_sqrt(f32 a) { return __builtin_sqrtf(__builtin_fabsf(a)); }
|
||||
RC_INLINE f32 vu_rsqrt(f32 a, f32 b)
|
||||
{
|
||||
f32 s = __builtin_sqrtf(__builtin_fabsf(b));
|
||||
if (s == 0.0f) return (vu_u(a) & 0x80000000u) ? -3.4028235e38f : 3.4028235e38f;
|
||||
return a / s;
|
||||
}
|
||||
RC_INLINE s32 vu_ftoi(f32 f, f32 scale)
|
||||
{
|
||||
f32 x = f * scale;
|
||||
if (x >= 2147483647.0f) return 0x7FFFFFFF;
|
||||
if (x <= -2147483648.0f) return (s32)0x80000000u;
|
||||
if (x != x) return 0;
|
||||
return (s32)x;
|
||||
}
|
||||
RC_INLINE u32 vu_itof(u32 i, f32 inv) { return vu_u((f32)(s32)i * inv); }
|
||||
|
||||
/* host side (host/src/gfx/vu.c) */
|
||||
void vu_xgkick(u32 qaddr);
|
||||
u32 vu_vif_top(int vu);
|
||||
u32 vu_vif_itop(int vu);
|
||||
void vu_bad(int vu, u32 pc, const char *what);
|
||||
#endif
|
||||
Reference in New Issue
Block a user