rsp-hle: NEAD commands, ABI1 overlap order, RE2/dlist signals

Backported from the parallax fork.

NEAD: the commands that were dispatched to UNKNOWN in several builds
are recovered from the cartridge microcode and wired into their tables:
- MULTIPLY (opcode 0x19 in Star Fox 64 and Wave Race 64 J RevB, 0x00
  in Majora's Mask / Pokemon Stadium 2 and Mario Artist Talent Studio):
  32 latched coefficients, saturating products, a whole 64-byte block
  read before any store.
- INDEXED (Animal Forest 0x00): indexed pair sums with 16-bit wrap and
  8-byte window swaps; an exact multiple of the period keeps the
  divisor.
- S8DEC for the builds alist_s8dec does not cover: the pipelined form
  (next row prefetched before each store) for 1080, Animal Forest,
  F-Zero X, Majora's Mask E Beta and Yoshi's Story, the plain one for
  Star Fox 64 J and Wave Race 64 J RevB.
- UNKCMD3 for 1080, Wave Race 64 J RevB and Yoshi's Story, loading its
  v31 constants first; F-Zero X 0x19 and Talent Studio 0x03 are no-ops.

ABI1 (aspMain): MIXER, DMEMMOVE and INTERLEAVE work on whole 16-byte
vectors with the microcode's lookahead, so overlapping source and
destination see the old samples where the RSP does.  The Blast Corps /
Diddy Kong Racing build gets its own four-vector MIXER, and a build
whose MIXER only loads is recognised from its ucode text.  RESAMPLE
reads every tap of an eight-lane block before storing it and treats a
zero count as one block, as the microcode's loop does.

RE2's video double-buffer task finishes with the microcode's SP_STATUS
write (clear SIG0, set SIG1), and completes a pre-empted fill (SIG0 set
on entry) without touching pixels.  A yielded display list relaunched
through the graphics plugin clears SIG1 like a fresh one, and ZSortp's
resident init clears SIG1 and sets SIG4.

Audio dumps of Blast Corps, Diddy Kong Racing, GoldenEye, Doubutsu no
Mori, Resident Evil 2 and Yoshi's Story are byte-identical to before
over 4000 frames.

Tests: test_alist_ops (NEAD commands through their task tables),
test_alist_abi1 (new: overlap order, RESAMPLE), test_re2_fill and
test_hle_dispatch (signals).  All fail on the previous code.
This commit is contained in:
libretroadmin committed 2026-10-26 05:17:51 +01:00
1 parent 38befe1e60
commit b4edcd0a81
11 files changed
+957 -63

No files matched your search

+5 -1
View File
@@ -1448,6 +1448,10 @@ test_alist_ops$(EXE_EXT): tools/test_alist_ops.c $(RSPDIR)/src/alist.c $(RSPDIR)
$(CC) $(CHECK_CFLAGS) $(CHECK_SANITIZE) -I$(RSPDIR)/src -I$(LIBRETRO_COMM_DIR)/include \
$< $(TEST_MUSYX_SRCS) -o $@ $(CHECK_LDFLAGS) $(CHECK_SANITIZE) -lm
test_alist_abi1$(EXE_EXT): tools/test_alist_abi1.c $(RSPDIR)/src/alist.c $(RSPDIR)/src/alist_audio.c $(TEST_MUSYX_SRCS)
$(CC) $(CHECK_CFLAGS) $(CHECK_SANITIZE) -I$(RSPDIR)/src -I$(LIBRETRO_COMM_DIR)/include \
$< $(TEST_MUSYX_SRCS) -o $@ $(CHECK_LDFLAGS) $(CHECK_SANITIZE) -lm
ZBOSS_TEST_OBJS := $(filter-out $(VIDEODIR_ANGRYLION)/rdp_emit_zboss.o,$(ANGRYLION_WALKER_OBJS))
angrylion_zboss_audio_test$(EXE_EXT): tools/angrylion_zboss_audio_test.c \
$(VIDEODIR_ANGRYLION)/rdp_emit_zboss.c $(ZBOSS_TEST_OBJS)
@@ -1464,7 +1468,7 @@ test_mouse_accum$(EXE_EXT): tools/test_mouse_accum.c \
CORE_CHECKS := test_rcp_bounds$(EXE_EXT) test_memory_map$(EXE_EXT) test_fb_write$(EXE_EXT) \
test_cp0_exceptions$(EXE_EXT) test_r4300_interp$(EXE_EXT) test_fpu_compare$(EXE_EXT) test_pi_status$(EXE_EXT) \
test_musyx_bounds$(EXE_EXT) test_musyx_aux$(EXE_EXT) test_alist_ops$(EXE_EXT) test_hle_dispatch$(EXE_EXT) angrylion_zboss_audio_test$(EXE_EXT) \
test_musyx_bounds$(EXE_EXT) test_musyx_aux$(EXE_EXT) test_alist_ops$(EXE_EXT) test_alist_abi1$(EXE_EXT) test_hle_dispatch$(EXE_EXT) angrylion_zboss_audio_test$(EXE_EXT) \
test_mouse_accum$(EXE_EXT) test_hle_dram_wrap$(EXE_EXT) test_pif_write$(EXE_EXT) \
test_re2_fill$(EXE_EXT)
+372 -25
View File
@@ -1474,6 +1474,140 @@ void alist_mix(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count
}
}
/* ABI1 (aspMain) block forms of MIXER / DMEMMOVE / INTERLEAVE. The
* microcode works on whole 16-byte vectors with explicit LQV lookahead,
* so when the source and destination ranges overlap, every load it has
* already issued sees the old bytes even though a sample-at-a-time walk
* would have read back its own output. Delay lines and in-place echo
* mixes depend on that order. */
static void mix8(int16_t* r, const int16_t* d, const int16_t* s, int16_t gain)
{
unsigned i;
for (i = 0; i < 8; ++i) {
r[i] = d[i];
sample_mix(&r[i], s[i], gain);
}
}
static void load8(struct hle_t* hle, int16_t* v, uint16_t dmem)
{
unsigned i;
for (i = 0; i < 8; ++i)
v[i] = *alist_s16(hle, (uint16_t)(dmem + 2*i));
}
static void store8(struct hle_t* hle, uint16_t dmem, const int16_t* v)
{
unsigned i;
for (i = 0; i < 8; ++i)
*alist_s16(hle, (uint16_t)(dmem + 2*i)) = v[i];
}
void alist_move_audio(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count)
{
unsigned remaining = count;
while (remaining != 0) {
uint8_t block[16];
unsigned i, n = remaining < 16 ? remaining : 16;
for (i = 0; i < n; ++i)
block[i] = *alist_u8(hle, (uint16_t)(dmemi + i));
for (i = 0; i < n; ++i)
*alist_u8(hle, (uint16_t)(dmemo + i)) = block[i];
dmemi = (uint16_t)(dmemi + n);
dmemo = (uint16_t)(dmemo + n);
remaining -= n;
}
}
void alist_interleave_audio(struct hle_t* hle, uint16_t dmemo, uint16_t left, uint16_t right, uint16_t count)
{
unsigned remaining = count;
if ((left | right) & 15) {
alist_interleave(hle, dmemo, left, right, count);
return;
}
while (remaining >= 2) {
int16_t l[8], r[8];
unsigned i, n = remaining < 16 ? remaining / 2 : 8;
for (i = 0; i < n; ++i) {
l[i] = *alist_s16(hle, (uint16_t)(left + 2*i));
r[i] = *alist_s16(hle, (uint16_t)(right + 2*i));
}
for (i = 0; i < n; ++i) {
*alist_s16(hle, (uint16_t)(dmemo + 4*i)) = l[i];
*alist_s16(hle, (uint16_t)(dmemo + 4*i + 2)) = r[i];
}
left = (uint16_t)(left + 2*n);
right = (uint16_t)(right + 2*n);
dmemo = (uint16_t)(dmemo + 4*n);
remaining -= 2*n;
}
}
/* Two vectors in flight: both destination and source pairs are loaded,
* the first result is stored before the next source pair is loaded, and
* the next destination pair is loaded only after both stores. */
void alist_mix_audio(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count, int16_t gain)
{
int16_t d0[8], d1[8], s0[8], s1[8], r0[8], r1[8];
int remaining = count;
if ((dmemo | dmemi) & 15) {
alist_mix(hle, dmemo, dmemi, count, gain);
return;
}
if (remaining == 0)
return;
load8(hle, d0, dmemo);
load8(hle, d1, (uint16_t)(dmemo + 16));
load8(hle, s0, dmemi);
load8(hle, s1, (uint16_t)(dmemi + 16));
do {
mix8(r0, d0, s0, gain);
mix8(r1, d1, s1, gain);
remaining -= 32;
dmemi = (uint16_t)(dmemi + 32);
store8(hle, dmemo, r0);
load8(hle, s0, dmemi);
load8(hle, s1, (uint16_t)(dmemi + 16));
store8(hle, (uint16_t)(dmemo + 16), r1);
dmemo = (uint16_t)(dmemo + 32);
load8(hle, d0, dmemo);
load8(hle, d1, (uint16_t)(dmemo + 16));
} while (remaining > 0);
}
/* Blast Corps / Diddy Kong Racing unroll the mixer with four vectors of
* lookahead: each SQV is followed by the LQVs of the vector four ahead,
* so an overlapping delay mix sees a different set of old samples than
* the standard two-vector loop above. */
void alist_mix_audio_bc(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count, int16_t gain)
{
int16_t dst[4][8], src[4][8], result[8];
unsigned block, blocks = (count + 15u) / 16u;
if ((dmemo | dmemi) & 15) {
alist_mix(hle, dmemo, dmemi, count, gain);
return;
}
for (block = 0; block < 4; ++block) {
load8(hle, dst[block], (uint16_t)(dmemo + 16*block));
load8(hle, src[block], (uint16_t)(dmemi + 16*block));
}
for (block = 0; block < blocks; ++block) {
unsigned slot = block & 3;
mix8(result, dst[slot], src[slot], gain);
store8(hle, (uint16_t)(dmemo + 16*block), result);
load8(hle, src[slot], (uint16_t)(dmemi + 16*(block + 4)));
load8(hle, dst[slot], (uint16_t)(dmemo + 16*(block + 4)));
}
}
void alist_multQ44(struct hle_t* hle, uint16_t dmem, uint16_t count, uint8_t gain)
{
/* The microcode splits the gain byte into unsigned nibbles: the
@@ -1687,6 +1821,11 @@ void alist_resample_audio(
uint16_t aligned;
unsigned i;
/* The microcode tests its remaining count after the first SQV, so a
* zero count still produces one 16-byte block. */
if (count == 0)
count = 16;
count >>= 1;
if (init) {
@@ -1724,37 +1863,43 @@ void alist_resample_audio(
ipos = in >> 1;
while (count != 0) {
const int16_t* lut = RESAMPLE_LUT + ((pitch_accu & 0xfc00) >> 8);
/* The microcode computes all taps of an eight-lane block before
* the SQV that stores it, so an in-place slow-pitch resample must
* not let a stored lane feed a later lane of the same block. */
int16_t block[8];
unsigned lane, batch = count < 8 ? count : 8;
/* Identical arithmetic to alist_resample: per-tap vmulf with
* a tree of saturating vadds. */
int16_t hq_out;
for (lane = 0; lane < batch; ++lane) {
const int16_t* lut = RESAMPLE_LUT + ((pitch_accu & 0xfc00) >> 8);
int16_t hq_out;
int32_t q0, q1, q2, q3;
if (resample_hq_sample(hle, ipos, pitch, pitch_accu, &hq_out)) {
block[lane] = hq_out;
}
else {
/* Identical arithmetic to alist_resample: per-tap vmulf
* with a tree of saturating vadds. */
q0 = (int32_t)(((int64_t)2 * *sample(hle, ipos ) * lut[0] + 0x8000) >> 16);
q1 = (int32_t)(((int64_t)2 * *sample(hle, ipos + 1) * lut[1] + 0x8000) >> 16);
q2 = (int32_t)(((int64_t)2 * *sample(hle, ipos + 2) * lut[2] + 0x8000) >> 16);
q3 = (int32_t)(((int64_t)2 * *sample(hle, ipos + 3) * lut[3] + 0x8000) >> 16);
q0 = clamp_s16(q0);
q1 = clamp_s16(q1);
q2 = clamp_s16(q2);
q3 = clamp_s16(q3);
block[lane] = clamp_s16(clamp_s16(q0 + q1) + clamp_s16(q2 + q3));
}
if (resample_hq_sample(hle, ipos, pitch, pitch_accu, &hq_out)) {
*sample(hle, opos++) = hq_out;
pitch_accu += pitch;
ipos += (pitch_accu >> 16);
ipos += (pitch_accu >> 16);
pitch_accu &= 0xffff;
--count;
continue;
}
int32_t q0 = (int32_t)(((int64_t)2 * *sample(hle, ipos ) * lut[0] + 0x8000) >> 16);
int32_t q1 = (int32_t)(((int64_t)2 * *sample(hle, ipos + 1) * lut[1] + 0x8000) >> 16);
int32_t q2 = (int32_t)(((int64_t)2 * *sample(hle, ipos + 2) * lut[2] + 0x8000) >> 16);
int32_t q3 = (int32_t)(((int64_t)2 * *sample(hle, ipos + 3) * lut[3] + 0x8000) >> 16);
q0 = clamp_s16(q0);
q1 = clamp_s16(q1);
q2 = clamp_s16(q2);
q3 = clamp_s16(q3);
*sample(hle, opos++) = clamp_s16(clamp_s16(q0 + q1) + clamp_s16(q2 + q3));
pitch_accu += pitch;
ipos += (pitch_accu >> 16);
pitch_accu &= 0xffff;
--count;
for (lane = 0; lane < batch; ++lane)
*sample(hle, opos++) = block[lane];
}
for (i = 0; i < 4; ++i)
@@ -2560,3 +2705,205 @@ void alist_unkcmd3(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t c
n -= 0x20;
} while (n > 0);
}
/* ---- NEAD commands recovered from the cartridge microcode ------------- */
static uint8_t nead_get8(struct hle_t* hle, uint32_t at)
{
return *alist_u8(hle, (uint16_t)(at & 0xfff));
}
static void nead_put8(struct hle_t* hle, uint32_t at, uint8_t value)
{
*alist_u8(hle, (uint16_t)(at & 0xfff)) = value;
}
static int16_t nead_get16(struct hle_t* hle, uint32_t at)
{
return (int16_t)(((uint16_t)nead_get8(hle, at) << 8) | nead_get8(hle, at + 1));
}
static void nead_put16(struct hle_t* hle, uint32_t at, uint16_t value)
{
nead_put8(hle, at, (uint8_t)(value >> 8));
nead_put8(hle, at + 1, (uint8_t)value);
}
/* Star Fox 64 opcode 0x19, Majora's Mask and Pokemon Stadium 2 opcode
* 0x00: the 32 coefficients are latched once, then every 64-byte block
* takes four vectors of signed products, each saturated (vmudh). All
* four input vectors are read before any result is stored. */
void alist_nead_multiply(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
uint32_t at = w2 >> 16;
uint32_t table = (w2 & 0xffff) + ((w1 >> 16) & 0xff);
int32_t count = (int32_t)(w1 & 0xffff);
int16_t coef[32], smp[32];
unsigned i;
for (i = 0; i < 32; ++i)
coef[i] = nead_get16(hle, table + 2 * i);
do
{
for (i = 0; i < 32; ++i)
smp[i] = nead_get16(hle, at + 2 * i);
for (i = 0; i < 32; ++i)
{
int32_t p = (int32_t)smp[i] * coef[i];
nead_put16(hle, at + 2 * i,
(uint16_t)(p > 32767 ? 32767 : p < -32768 ? -32768 : p));
}
at += 64;
count -= 64;
} while (count > 0);
}
/* Animal Forest's index reduction subtracts while the value stays
* strictly positive, so an exact multiple keeps the divisor. */
static uint32_t nead_indexed_address(struct hle_t* hle, uint32_t *indices,
uint32_t base, uint32_t period)
{
uint32_t value = (uint16_t)nead_get16(hle, *indices) & 0x7fff;
unsigned bit;
*indices += 2;
for (bit = 0; bit < 8; ++bit)
{
if (period && value)
value = ((value - 1) % period) + 1;
period >>= 1;
}
return base + value * 2;
}
/* Animal Forest opcode 0x00: adds indexed sample pairs in place with
* 16-bit wrap, then exchanges paired 8-byte windows. The index list
* shares the sample buffer, so each pair resolves against the buffer as
* it stands. */
void alist_nead_indexed(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
uint32_t base = w2 & 0xffff, indices = base;
uint32_t period = (w1 >> 16) & 0xff, count = w1 & 0xffff;
uint32_t swaps = w2 >> 16;
while (count--)
{
uint32_t first = nead_indexed_address(hle, &indices, base, period << 7);
uint32_t second = nead_indexed_address(hle, &indices, base, period << 7);
uint16_t sum = (uint16_t)((uint16_t)nead_get16(hle, first)
+ (uint16_t)nead_get16(hle, second));
nead_put16(hle, first, sum);
nead_put16(hle, second, sum);
}
period -= 8;
while (swaps--)
{
uint32_t first = nead_indexed_address(hle, &indices, base, period << 7);
uint32_t second = nead_indexed_address(hle, &indices, base, period << 7);
uint8_t a[8], b[8];
unsigned i;
for (i = 0; i < 8; ++i)
{
a[i] = nead_get8(hle, first + i);
b[i] = nead_get8(hle, second + i);
}
for (i = 0; i < 8; ++i)
nead_put8(hle, second + i, a[i]);
for (i = 0; i < 8; ++i)
nead_put8(hle, first + i, b[i]);
}
}
/* n source bytes as signed 8-bit samples in the high byte of each lane */
static void nead_s8_load(struct hle_t* hle, uint32_t at, uint8_t *lanes, unsigned n)
{
unsigned i;
for (i = 0; i < n; ++i)
{
lanes[2 * i] = nead_get8(hle, at + i);
lanes[2 * i + 1] = 0;
}
}
/* SQV: an unaligned store fills only the rest of its 16-byte row */
static void nead_s8_store(struct hle_t* hle, uint32_t at, const uint8_t *row)
{
unsigned i, bytes = 16 - (at & 15);
for (i = 0; i < bytes; ++i)
nead_put8(hle, at + i, row[i]);
}
static void nead_s8_dma(struct hle_t* hle, uint32_t dmem, uint32_t address, int write)
{
uint32_t da = dmem & 0xff8, ra = address & 0xfffff8;
unsigned i;
for (i = 0; i < 32; ++i)
{
uint8_t *d = alist_u8(hle, (uint16_t)((da + i) & 0xfff));
uint8_t *r = dram_u8(hle, ra + i);
if (write)
*r = *d;
else
*d = *r;
}
}
/* The NEAD signed 8-bit decoder of the builds alist_s8dec does not
* cover: the 32-byte state row is cleared, loaded unless w1 bit 16 says
* the stream starts fresh (from the loop address when bit 17 is set),
* the samples are expanded behind it and the last row is saved. The
* pipelined builds prefetch the next sixteen source bytes before each
* store, which shows when input and output overlap. */
void alist_nead_s8(struct hle_t* hle, uint32_t w1, uint32_t w2,
uint16_t input_dmem, uint16_t output_dmem, uint16_t count,
uint32_t loop, int pipelined)
{
uint32_t input = input_dmem, output = output_dmem;
uint32_t address = w2 & 0xffffff;
int32_t remaining = count;
uint8_t cur[32], next[32];
memset(cur, 0, sizeof(cur));
nead_s8_store(hle, output, cur);
nead_s8_store(hle, output + 16, cur + 16);
if (!(w1 & 0x10000))
nead_s8_dma(hle, output, (w1 & 0x20000) ? loop : address, 0);
output += 32;
if (remaining && !pipelined)
{
do
{
nead_s8_load(hle, input, cur, 16);
nead_s8_store(hle, output, cur);
nead_s8_store(hle, output + 16, cur + 16);
input += 16;
output += 32;
remaining -= 32;
} while (remaining > 0);
}
else if (remaining)
{
nead_s8_load(hle, input, cur, 16);
input += 16;
for (;;)
{
remaining -= 32;
nead_s8_load(hle, input, next, 8);
nead_s8_store(hle, output, cur);
nead_s8_load(hle, input + 8, next + 16, 8);
nead_s8_store(hle, output + 16, cur + 16);
output += 32;
if (remaining <= 0)
break;
remaining -= 32;
nead_s8_load(hle, input + 16, cur, 8);
nead_s8_store(hle, output, next);
nead_s8_load(hle, input + 24, cur + 16, 8);
nead_s8_store(hle, output + 16, next + 16);
output += 32;
input += 32;
if (remaining <= 0)
break;
}
}
nead_s8_dma(hle, output - 32, address, 1);
}
+9
View File
@@ -37,10 +37,17 @@ void alist_clear(struct hle_t* hle, uint16_t dmem, uint32_t count);
void alist_load(struct hle_t* hle, uint16_t dmem, uint32_t address, uint16_t count);
void alist_save(struct hle_t* hle, uint16_t dmem, uint32_t address, uint16_t count);
void alist_move(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count);
void alist_move_audio(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count);
void alist_copy_every_other_sample(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count);
void alist_repeat64(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint8_t count);
void alist_copy_blocks(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t block_size, uint8_t count);
void alist_interleave(struct hle_t* hle, uint16_t dmemo, uint16_t left, uint16_t right, uint16_t count);
void alist_interleave_audio(struct hle_t* hle, uint16_t dmemo, uint16_t left, uint16_t right, uint16_t count);
void alist_nead_multiply(struct hle_t* hle, uint32_t w1, uint32_t w2);
void alist_nead_indexed(struct hle_t* hle, uint32_t w1, uint32_t w2);
void alist_nead_s8(struct hle_t* hle, uint32_t w1, uint32_t w2,
uint16_t input_dmem, uint16_t output_dmem, uint16_t count,
uint32_t loop, int pipelined);
void alist_envmix_audio1(
struct hle_t* hle,
@@ -136,6 +143,8 @@ void alist_envmix_nead(
bool wet_bypass);
void alist_mix(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count, int16_t gain);
void alist_mix_audio(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count, int16_t gain);
void alist_mix_audio_bc(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count, int16_t gain);
void alist_multQ44(struct hle_t* hle, uint16_t dmem, uint16_t count, uint8_t gain);
void alist_add(struct hle_t* hle, uint16_t dmemo, uint16_t dmemi, uint16_t count);
+53 -4
View File
@@ -33,6 +33,40 @@
enum { DMEM_BASE = 0x5c0 };
/* One ABI1 microcode build replaces the MIXER arithmetic and stores with a
* loop that only loads. Recognise that executable body in the task's
* ucode text rather than the game that ships it. */
static int mixer_load_only;
static int has_load_only_mixer(struct hle_t* hle)
{
static const uint32_t body[17] = {
0xc81f2000u, 0x97120004u, 0x1240000eu, 0x00000000u,
0x3333ffffu, 0x227305c0u, 0x0019a402u, 0x229405c0u,
0x3351ffffu, 0x4891f000u, 0xca7b2000u, 0xca9d2000u,
0xca7a2001u, 0xca9c2001u, 0x2252ffe0u, 0x1e40fffeu,
0xca7a2001u
};
uint32_t text = *dmem_u32(hle, TASK_UCODE) & 0xfffff8u;
uint32_t offset;
unsigned k;
if (text > 0x800000u - 0xf80u)
return 0;
for (offset = 0; offset + 76u <= 0xf80u; offset += 4u) {
if (*dram_u32(hle, text + offset) != body[0])
continue;
for (k = 1; k < 17; ++k)
if (*dram_u32(hle, text + offset + 4*k) != body[k])
break;
if (k == 17
&& (*dram_u32(hle, text + offset + 68) & 0xfc000000u) == 0x08000000u
&& *dram_u32(hle, text + offset + 72) == 0)
return 1;
}
return 0;
}
/* helper functions */
static uint32_t get_address(struct hle_t* hle, uint32_t so)
{
@@ -212,7 +246,7 @@ static void DMEMMOVE(struct hle_t* hle, uint32_t w1, uint32_t w2)
if (count == 0)
return;
alist_move(hle, dmemo, dmemi, align(count, 16));
alist_move_audio(hle, dmemo, dmemi, align(count, 16));
}
/* the DMEM address the ADPCM codebook DMA lands at */
@@ -254,7 +288,7 @@ static void INTERLEAVE(struct hle_t* hle, uint32_t UNUSED(w1), uint32_t w2)
if (hle->alist_audio.count == 0)
return;
alist_interleave(hle, hle->alist_audio.out, left, right, align(hle->alist_audio.count, 16));
alist_interleave_audio(hle, hle->alist_audio.out, left, right, align(hle->alist_audio.count, 16));
}
static void MIXER(struct hle_t* hle, uint32_t w1, uint32_t w2)
@@ -263,10 +297,22 @@ static void MIXER(struct hle_t* hle, uint32_t w1, uint32_t w2)
uint16_t dmemi = (w2 >> 16) + DMEM_BASE;
uint16_t dmemo = w2 + DMEM_BASE;
if (hle->alist_audio.count == 0 || mixer_load_only)
return;
alist_mix_audio(hle, dmemo, dmemi, align(hle->alist_audio.count, 32), gain);
}
static void MIXER_BC(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
int16_t gain = w1;
uint16_t dmemi = (w2 >> 16) + DMEM_BASE;
uint16_t dmemo = w2 + DMEM_BASE;
if (hle->alist_audio.count == 0)
return;
alist_mix(hle, dmemo, dmemi, align(hle->alist_audio.count, 32), gain);
alist_mix_audio_bc(hle, dmemo, dmemi, align(hle->alist_audio.count, 32), gain);
}
static void SEGMENT(struct hle_t* hle, uint32_t UNUSED(w1), uint32_t w2)
@@ -305,6 +351,7 @@ void alist_process_audio(struct hle_t* hle)
};
clear_segments(hle);
mixer_load_only = has_load_only_mixer(hle);
alist_process(hle, ABI, 0x10);
rsp_break(hle, SP_STATUS_TASKDONE);
}
@@ -320,6 +367,7 @@ void alist_process_audio_ge(struct hle_t* hle)
envmix_ge_lanes = 1;
clear_segments(hle);
mixer_load_only = has_load_only_mixer(hle);
alist_process(hle, ABI, 0x10);
rsp_break(hle, SP_STATUS_TASKDONE);
}
@@ -330,11 +378,12 @@ void alist_process_audio_bc(struct hle_t* hle)
SPNOOP, ADPCM , CLEARBUFF, ENVMIXER_GE,
LOADBUFF, RESAMPLE, SAVEBUFF, SEGMENT,
SETBUFF, SETVOL, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, POLEF, SETLOOP
MIXER_BC, INTERLEAVE, POLEF, SETLOOP
};
envmix_ge_lanes = 1;
clear_segments(hle);
mixer_load_only = has_load_only_mixer(hle);
alist_process(hle, ABI, 0x10);
rsp_break(hle, SP_STATUS_TASKDONE);
}
+42 -19
View File
@@ -530,8 +530,30 @@ static void UNKCMD3(struct hle_t* hle, uint32_t w1, uint32_t w2)
uint16_t dmemi = (w2 >> 16);
uint16_t dmemo = w2;
/* the handler reloads the v31 constant row before its multiplies */
nead_v31_load(hle, 0x00);
alist_unkcmd3(hle, dmemo, dmemi, count);
}
/* the pipelined and the plain signed 8-bit decoders of the builds whose
* S8DEC alist_s8dec does not model */
static void S8DEC_P(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
alist_nead_s8(hle, w1, w2, hle->alist_nead.in, hle->alist_nead.out,
hle->alist_nead.count, hle->alist_nead.loop, 1);
}
static void S8DEC_SFJ(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
alist_nead_s8(hle, w1, w2, hle->alist_nead.in, hle->alist_nead.out,
hle->alist_nead.count, hle->alist_nead.loop, 0);
}
static void MULTIPLY(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
alist_nead_multiply(hle, w1, w2);
}
static void INDEXED(struct hle_t* hle, uint32_t w1, uint32_t w2)
{
alist_nead_indexed(hle, w1, w2);
}
static void SEGMENT(struct hle_t* UNUSED(hle), uint32_t UNUSED(w1), uint32_t UNUSED(w2))
{
@@ -601,7 +623,7 @@ void alist_process_nead_sf(struct hle_t* hle)
MIXER, INTERLEAVE_MK, POLEF, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, SPNOOP,
HILOGAIN, UNKNOWN, DUPLICATE, SPNOOP,
HILOGAIN, MULTIPLY, DUPLICATE, SPNOOP,
SPNOOP, SPNOOP, SPNOOP, SPNOOP
};
@@ -624,8 +646,8 @@ void alist_process_nead_sfj(struct hle_t* hle)
SETBUFF, SPNOOP, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE_MK, POLEF, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN,
HILOGAIN, UNKNOWN, DUPLICATE, SPNOOP,
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_SFJ,
HILOGAIN, MULTIPLY, DUPLICATE, SPNOOP,
SPNOOP, SPNOOP, SPNOOP, SPNOOP
};
@@ -648,8 +670,8 @@ void alist_process_nead_fz(struct hle_t* hle)
SETBUFF, SPNOOP, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, SPNOOP, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN,
SPNOOP, UNKNOWN, DUPLICATE, SPNOOP,
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_P,
SPNOOP, SPNOOP, DUPLICATE, SPNOOP,
SPNOOP, SPNOOP, SPNOOP, SPNOOP
};
@@ -667,13 +689,13 @@ void alist_process_nead_fz(struct hle_t* hle)
void alist_process_nead_wrjb(struct hle_t* hle)
{
static const acmd_callback_t ABI[0x20] = {
SPNOOP, ADPCM, CLEARBUFF, UNKNOWN,
SPNOOP, ADPCM, CLEARBUFF, UNKCMD3,
ADDMIXER, RESAMPLE, RESAMPLE_ZOH, SPNOOP,
SETBUFF, SPNOOP, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, SPNOOP, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN,
HILOGAIN, UNKNOWN, DUPLICATE, FILTER,
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_SFJ,
HILOGAIN, MULTIPLY, DUPLICATE, FILTER,
SPNOOP, SPNOOP, SPNOOP, SPNOOP
};
@@ -692,12 +714,12 @@ void alist_process_nead_wrjb(struct hle_t* hle)
void alist_process_nead_ys(struct hle_t* hle)
{
static const acmd_callback_t ABI[0x18] = {
UNKNOWN, ADPCM, CLEARBUFF, UNKNOWN,
UNKNOWN, ADPCM, CLEARBUFF, UNKCMD3,
ADDMIXER, RESAMPLE, RESAMPLE_ZOH, FILTER,
SETBUFF, DUPLICATE, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, HILOGAIN, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_P
};
nead_slab = 0xfc0;
@@ -717,12 +739,12 @@ void alist_process_nead_ys(struct hle_t* hle)
void alist_process_nead_1080(struct hle_t* hle)
{
static const acmd_callback_t ABI[0x18] = {
UNKNOWN, ADPCM, CLEARBUFF, UNKNOWN,
UNKNOWN, ADPCM, CLEARBUFF, UNKCMD3,
ADDMIXER, RESAMPLE, RESAMPLE_ZOH, FILTER,
SETBUFF, DUPLICATE, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, HILOGAIN, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_P
};
nead_slab = 0xfc0;
@@ -763,7 +785,7 @@ void alist_process_nead_oot(struct hle_t* hle)
void alist_process_nead_mm(struct hle_t* hle)
{
static const acmd_callback_t ABI[0x18] = {
UNKNOWN, ADPCM, CLEARBUFF, SPNOOP,
MULTIPLY, ADPCM, CLEARBUFF, SPNOOP,
ADDMIXER, RESAMPLE, RESAMPLE_ZOH, FILTER,
SETBUFF, DUPLICATE, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, HILOGAIN, SETLOOP,
@@ -791,7 +813,7 @@ void alist_process_nead_mmb(struct hle_t* hle)
SETBUFF, DUPLICATE, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, HILOGAIN, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_P
};
nead_slab = 0xfb0;
@@ -809,12 +831,12 @@ void alist_process_nead_mmb(struct hle_t* hle)
void alist_process_nead_ac(struct hle_t* hle)
{
static const acmd_callback_t ABI[0x18] = {
UNKNOWN, ADPCM, CLEARBUFF, SPNOOP,
INDEXED, ADPCM, CLEARBUFF, SPNOOP,
ADDMIXER, RESAMPLE, RESAMPLE_ZOH, FILTER,
SETBUFF, DUPLICATE, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, HILOGAIN, SETLOOP,
NEAD_16, INTERL, ENVSETUP1, ENVMIXER,
LOADBUFF, SAVEBUFF, ENVSETUP2, UNKNOWN
LOADBUFF, SAVEBUFF, ENVSETUP2, S8DEC_P
};
nead_slab = 0xfb0;
@@ -833,10 +855,11 @@ void alist_process_nead_mats(struct hle_t* hle)
{
/* Mario Artist: Talent Studio (64DD), ucode signature 0x1f701238.
* The command jump table (ucode_data + 0x10, indexed by cmd = w1 >> 24)
* was extracted from the running ucode and matches the nead_oot command
* layout for all 0x18 entries. */
* follows the nead_oot layout except opcode 0x00, the MULTIPLY that
* Majora's Mask also carries, and opcode 0x03, which points straight
* back to the command loop. */
static const acmd_callback_t ABI[0x18] = {
UNKNOWN, ADPCM, CLEARBUFF, UNKCMD3,
MULTIPLY, ADPCM, CLEARBUFF, SPNOOP,
ADDMIXER, RESAMPLE, RESAMPLE_ZOH, FILTER,
SETBUFF, DUPLICATE, DMEMMOVE, LOADADPCM,
MIXER, INTERLEAVE, HILOGAIN, SETLOOP,
+16
View File
@@ -234,6 +234,22 @@ static void send_alist_to_audio_plugin(struct hle_t* hle)
static void send_dlist_to_gfx_plugin(struct hle_t* hle)
{
uint32_t text = *dmem_u32(hle, TASK_UCODE) & 0x7fffffu;
/* A yielded task relaunched by osSpTaskStart starts from its boot code
* like a fresh one, which clears SIG1 (the yielded flag); left set,
* osSpTaskYielded reads the completion as another yield. */
if (*dmem_u32(hle, TASK_FLAGS) & 1)
*hle->sp_status &= ~SP_STATUS_SIG1;
/* ZSortp's resident initialisation writes 0x42800 to SP_STATUS:
* clear SIG1 and SIG2, set SIG4. SIG2 comes back with the task-done
* bits set below. */
if (text <= 0x800000u - 0x1cu
&& *dram_u32(hle, text + 0x10u) == 0x3c0b0004u
&& *dram_u32(hle, text + 0x14u) == 0x356b2800u
&& *dram_u32(hle, text + 0x18u) == 0x408b2000u)
*hle->sp_status = (*hle->sp_status & ~SP_STATUS_SIG1) | SP_STATUS_SIG4;
/* Since GFX_INFO version 2, these bits are set before calling the ProcessDlistList function.
* And the GFX plugin is responsible to unset them if needed.
* For GFX_INFO version < 2, the GFX plugin didn't have access to sp_status so
+23 -11
View File
@@ -245,20 +245,29 @@ void decode_video_frame_task(struct hle_t* hle)
void fill_video_double_buffer_task(struct hle_t* hle)
{
int data_ptr = *dmem_u32(hle, TASK_UCODE_DATA);
int pSrc = *dram_u32(hle, data_ptr);
int pDest = *dram_u32(hle, data_ptr + 0x4);
int width = *dram_u32(hle, data_ptr + 0x8) >> 1;
int height = *dram_u32(hle, data_ptr + 0x10) << 1;
int stride = *dram_u32(hle, data_ptr + 0x1c) >> 1;
assert((*dram_u32(hle, data_ptr + 0x28) >> 16) == 0x8000);
int data_ptr, pSrc, pDest, width, height, stride;
int i, j;
int r, g, b, a;
uint32_t pixel, pixel1, pixel2;
/* The microcode's boot entry checks SIG0 first: a fill that the CPU
* pre-empted is completed there without touching any pixels. */
if (*hle->sp_status & SP_STATUS_SIG0) {
*hle->sp_status &= ~SP_STATUS_SIG0;
rsp_break(hle, SP_STATUS_SIG1 | SP_STATUS_TASKDONE);
return;
}
data_ptr = *dmem_u32(hle, TASK_UCODE_DATA);
pSrc = *dram_u32(hle, data_ptr);
pDest = *dram_u32(hle, data_ptr + 0x4);
width = *dram_u32(hle, data_ptr + 0x8) >> 1;
height = *dram_u32(hle, data_ptr + 0x10) << 1;
stride = *dram_u32(hle, data_ptr + 0x1c) >> 1;
assert((*dram_u32(hle, data_ptr + 0x28) >> 16) == 0x8000);
for(i = 0; i < height; i++)
{
for(j = 0; j < width; j=j+4)
@@ -281,5 +290,8 @@ void fill_video_double_buffer_task(struct hle_t* hle)
pDest += stride;
}
rsp_break(hle, SP_STATUS_TASKDONE);
/* It finishes with an SP_STATUS write of 0x5200: clear SIG0, set
* SIG1 and SIG2 (task done). The game's frame loop waits on SIG1. */
*hle->sp_status &= ~SP_STATUS_SIG0;
rsp_break(hle, SP_STATUS_SIG1 | SP_STATUS_TASKDONE);
}
+236
View File
@@ -0,0 +1,236 @@
/* test_alist_abi1 -- the ABI1 (aspMain) buffer commands keep the
* microcode's load/store order when source and destination overlap.
*
* - MIXER loads two vectors ahead: the second vector of a pair mixes
* the old samples even when the first store has already overwritten
* them;
* - the Blast Corps / Diddy Kong Racing MIXER loads four vectors ahead;
* - DMEMMOVE moves whole 16-byte rows, so a forward overlapping move
* copies the source instead of smearing it;
* - INTERLEAVE reads eight samples of each side before storing;
* - RESAMPLE with a zero count still produces one 16-byte block, and
* it reads every tap of an eight-lane block before storing any lane.
*
* Built by `make check-core` (top-level Makefile). Exit status 0 on pass.
*/
#include "../mupen64plus-rsp-hle/src/alist.c"
#include "../mupen64plus-rsp-hle/src/alist_audio.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void HleVerboseMessage(void* user_defined, const char *message, ...)
{
(void)user_defined; (void)message;
}
void HleWarnMessage(void* user_defined, const char *message, ...)
{
(void)user_defined; (void)message;
}
void rsp_break(struct hle_t* hle, unsigned int setbits)
{
(void)hle; (void)setbits;
}
static int s_failures;
#define CHECK(cond, what) \
do { if (!(cond)) { printf("FAIL: %s\n", what); s_failures++; } } while (0)
static struct hle_t s_task;
static void task_setup(void)
{
static unsigned char* dram;
static unsigned char* dmem;
static unsigned int sp_status;
if (dram == NULL) {
dram = (unsigned char*)malloc(0x800000u);
dmem = (unsigned char*)malloc(0x1000u);
if (dram == NULL || dmem == NULL)
exit(2);
}
memset(dram, 0, 0x800000u);
memset(dmem, 0, 0x1000u);
memset(&s_task, 0, sizeof(s_task));
s_task.dram = dram;
s_task.dmem = dmem;
s_task.sp_status = &sp_status;
*dmem_u32(&s_task, TASK_UCODE) = 0x100000;
*dmem_u32(&s_task, TASK_DATA_PTR) = 0x1000;
*dmem_u32(&s_task, TASK_DATA_SIZE) = 0;
}
static void task_cmd(uint32_t w1, uint32_t w2)
{
uint32_t at = 0x1000 + *dmem_u32(&s_task, TASK_DATA_SIZE);
*dram_u32(&s_task, at) = w1;
*dram_u32(&s_task, at + 4) = w2;
*dmem_u32(&s_task, TASK_DATA_SIZE) += 8;
}
static int16_t get16(uint16_t at) { return *alist_s16(&s_task, at); }
static void put16(uint16_t at, int16_t v) { *alist_s16(&s_task, at) = v; }
/* vmulf(dst, 0x7fff) + vmacf(src, gain) */
static int16_t mixref(int16_t d, int16_t s, int16_t gain)
{
int64_t acc = 0x8000 + 2 * (int64_t)((int32_t)d * 0x7fff)
+ 2 * (int64_t)((int32_t)s * (int32_t)gain);
acc >>= 16;
return (int16_t)(acc > 32767 ? 32767 : acc < -32768 ? -32768 : acc);
}
/* in at DMEM 0x600, out at 0x620: the source runs two vectors behind */
enum { MIX_IN = 0x600, MIX_OUT = 0x620, MIX_GAIN = 0x4000 };
static int16_t s_old[48];
static void mix_setup(void)
{
unsigned k;
task_setup();
for (k = 0; k < 48; ++k) {
s_old[k] = (int16_t)(311 * k - 4000);
put16((uint16_t)(MIX_IN + 2*k), s_old[k]);
}
task_cmd(0x08000000u | (MIX_IN - DMEM_BASE),
((uint32_t)(MIX_OUT - DMEM_BASE) << 16) | 64); /* SETBUFF */
task_cmd(0x0c000000u | MIX_GAIN,
((uint32_t)(MIX_IN - DMEM_BASE) << 16) | (MIX_OUT - DMEM_BASE));
}
static void test_mixer(void)
{
int16_t block0[8], block1[8];
unsigned i;
int ok = 1;
mix_setup();
alist_process_audio(&s_task);
for (i = 0; i < 8; ++i) {
block0[i] = mixref(s_old[16 + i], s_old[i], MIX_GAIN);
block1[i] = mixref(s_old[24 + i], s_old[8 + i], MIX_GAIN);
if (get16((uint16_t)(MIX_OUT + 2*i)) != block0[i]
|| get16((uint16_t)(MIX_OUT + 16 + 2*i)) != block1[i])
ok = 0;
/* block 2 is loaded after block 0 is stored... */
if (get16((uint16_t)(MIX_OUT + 32 + 2*i)) != mixref(s_old[32 + i], block0[i], MIX_GAIN))
ok = 0;
/* ...but block 3's source was loaded before block 1 was stored */
if (get16((uint16_t)(MIX_OUT + 48 + 2*i)) != mixref(s_old[40 + i], s_old[24 + i], MIX_GAIN))
ok = 0;
}
CHECK(ok, "ABI1 MIXER: two vectors of lookahead over an overlapping source");
mix_setup();
alist_process_audio_bc(&s_task);
ok = 1;
for (i = 0; i < 32; ++i)
if (get16((uint16_t)(MIX_OUT + 2*i)) != mixref(s_old[16 + i], s_old[i], MIX_GAIN))
ok = 0;
CHECK(ok, "ABI1 MIXER (Blast Corps): four vectors of lookahead");
}
static void test_dmemmove(void)
{
unsigned k;
int ok = 1;
task_setup();
for (k = 0; k < 32; ++k)
*alist_u8(&s_task, (uint16_t)(0x600 + k)) = (uint8_t)(0x40 + k);
task_cmd(0x0a000000u | (0x600 - DMEM_BASE),
((uint32_t)(0x608 - DMEM_BASE) << 16) | 16); /* DMEMMOVE */
alist_process_audio(&s_task);
for (k = 0; k < 16; ++k)
if (*alist_u8(&s_task, (uint16_t)(0x608 + k)) != 0x40 + k)
ok = 0;
CHECK(ok, "ABI1 DMEMMOVE: a forward overlapping move copies a whole row");
}
static void test_interleave(void)
{
static const int16_t left_hi[8] = { 4, 104, 5, 105, 6, 106, 7, 107 };
unsigned i;
int ok = 1;
task_setup();
for (i = 0; i < 16; ++i) {
put16((uint16_t)(0x600 + 2*i), (int16_t)i); /* left, also out */
put16((uint16_t)(0x640 + 2*i), (int16_t)(100 + i)); /* right */
}
task_cmd(0x08000000u | 0, ((uint32_t)(0x600 - DMEM_BASE) << 16) | 32);
task_cmd(0x0d000000u, ((uint32_t)(0x600 - DMEM_BASE) << 16) | (0x640 - DMEM_BASE));
alist_process_audio(&s_task);
for (i = 0; i < 8; ++i) {
if (get16((uint16_t)(0x600 + 4*i)) != (int16_t)i
|| get16((uint16_t)(0x602 + 4*i)) != (int16_t)(100 + i))
ok = 0;
/* the second row of left samples was overwritten by the first
* block's stores before it was loaded */
if (get16((uint16_t)(0x620 + 4*i)) != left_hi[i]
|| get16((uint16_t)(0x622 + 4*i)) != (int16_t)(108 + i))
ok = 0;
}
CHECK(ok, "ABI1 INTERLEAVE: eight samples a side are loaded before the store");
}
static void test_resample_zero(void)
{
unsigned i;
int ok = 1;
task_setup();
for (i = 0; i < 16; ++i)
put16((uint16_t)(0x7c0 + 2*i), 0x5555);
task_cmd(0x08000000u | (0x6c0 - DMEM_BASE),
((uint32_t)(0x7c0 - DMEM_BASE) << 16) | 0); /* count 0 */
task_cmd(0x05000000u | ((uint32_t)A_INIT << 16) | 0x8000, 0x3000);
alist_process_audio(&s_task);
for (i = 0; i < 8; ++i)
if (get16((uint16_t)(0x7c0 + 2*i)) != 0)
ok = 0;
CHECK(ok, "ABI1 RESAMPLE: a zero count still writes one block");
CHECK(get16(0x7d0) == 0x5555, "ABI1 RESAMPLE: and only one block");
}
/* Pitch 0 resampled over its own window: every lane interpolates the
* same four taps, and the output starts on the first tap, so a lane
* stored early would feed the next lane of the same vector. */
static void test_resample_in_place(void)
{
unsigned i;
int ok = 1;
task_setup();
for (i = 0; i < 4; ++i)
*dram_u16(&s_task, 0x3000 + 2*i) = 10000; /* saved window */
task_cmd(0x08000000u | (0x6c8 - DMEM_BASE),
((uint32_t)(0x6c0 - DMEM_BASE) << 16) | 16);
task_cmd(0x05000000u | 0, 0x3000); /* pitch 0, resume */
alist_process_audio(&s_task);
for (i = 1; i < 8; ++i)
if (get16((uint16_t)(0x6c0 + 2*i)) != get16(0x6c0))
ok = 0;
CHECK(ok && get16(0x6c0) > 9000,
"ABI1 RESAMPLE: all taps of a block are read before its store");
}
int main(void)
{
test_mixer();
test_dmemmove();
test_interleave();
test_resample_zero();
test_resample_in_place();
if (s_failures) {
printf("test_alist_abi1: %d failure(s)\n", s_failures);
return 1;
}
printf("test_alist_abi1: all checks passed\n");
return 0;
}
+141 -1
View File
@@ -3,7 +3,12 @@
*
* - the ENVMIXER VMADN low clamp saturates on the whole of ACC[47:16],
* including values whose low half alone looks in range;
* - NEAD CLEARBUFF clears whole 16-byte blocks and takes a 16-bit count.
* - NEAD CLEARBUFF clears whole 16-byte blocks and takes a 16-bit count;
* - the NEAD commands recovered from the cartridge microcode are
* dispatched, not dropped: MULTIPLY saturates each product and reads
* a whole block before storing, INDEXED keeps an exact multiple of
* the period, and the pipelined S8DEC prefetches its next source row
* before each store while the plain one does not.
*
* Built by `make check-core` (top-level Makefile). Exit status 0 on pass.
*/
@@ -11,6 +16,7 @@
#include "../mupen64plus-rsp-hle/src/alist_nead.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void HleVerboseMessage(void* user_defined, const char *message, ...)
@@ -66,10 +72,144 @@ static void test_nead_clearbuff(void)
CHECK(ok, "NEAD CLEARBUFF: the count is sixteen bits wide");
}
/* ---- NEAD task harness: one alist in DRAM, run through a ucode table -- */
static struct hle_t s_task;
static void task_setup(void)
{
static unsigned char* dram;
static unsigned char* dmem;
static unsigned int sp_status;
if (dram == NULL) {
dram = (unsigned char*)malloc(0x800000u);
dmem = (unsigned char*)malloc(0x1000u);
if (dram == NULL || dmem == NULL)
exit(2);
}
memset(dram, 0, 0x800000u);
memset(dmem, 0, 0x1000u);
memset(&s_task, 0, sizeof(s_task));
s_task.dram = dram;
s_task.dmem = dmem;
s_task.sp_status = &sp_status;
*dmem_u32(&s_task, TASK_DATA_PTR) = 0x1000;
*dmem_u32(&s_task, TASK_DATA_SIZE) = 0;
}
static void task_cmd(uint32_t w1, uint32_t w2)
{
uint32_t at = 0x1000 + *dmem_u32(&s_task, TASK_DATA_SIZE);
*dram_u32(&s_task, at) = w1;
*dram_u32(&s_task, at + 4) = w2;
*dmem_u32(&s_task, TASK_DATA_SIZE) += 8;
}
static int16_t dmem16(uint16_t at)
{
return *alist_s16(&s_task, at);
}
static void test_nead_multiply(void)
{
unsigned i;
int ok = 1;
task_setup();
for (i = 0; i < 32; ++i) {
*alist_s16(&s_task, (uint16_t)(0x600 + 2*i)) = (int16_t)(i == 0 ? 3 : 2);
*alist_s16(&s_task, (uint16_t)(0x400 + 2*i)) = (int16_t)(100 * i);
*alist_s16(&s_task, (uint16_t)(0x440 + 2*i)) = (int16_t)(-7 * i);
}
*alist_s16(&s_task, 0x400) = 20000;
*alist_s16(&s_task, 0x402) = -20000;
task_cmd(0x00000000u | 64, (0x400u << 16) | 0x600); /* MULTIPLY */
alist_process_nead_mm(&s_task);
CHECK(dmem16(0x400) == 32767, "NEAD MULTIPLY: a positive product saturates");
CHECK(dmem16(0x402) == -32768, "NEAD MULTIPLY: a negative product saturates");
for (i = 2; i < 32; ++i)
if (dmem16((uint16_t)(0x400 + 2*i)) != (int16_t)(200 * i))
ok = 0;
CHECK(ok, "NEAD MULTIPLY: each lane takes its own coefficient");
CHECK(dmem16(0x440) == 0 && dmem16(0x442) == -7,
"NEAD MULTIPLY: a 64-byte count stops after one block");
/* a 128-byte run over the coefficient table itself: the coefficients
* are latched once, so the second block still sees the old ones */
task_setup();
for (i = 0; i < 64; ++i)
*alist_s16(&s_task, (uint16_t)(0x400 + 2*i)) = 2;
task_cmd(0x00000000u | 128, (0x400u << 16) | 0x400);
alist_process_nead_mm(&s_task);
CHECK(dmem16(0x400) == 4 && dmem16(0x43e) == 4 && dmem16(0x440) == 4,
"NEAD MULTIPLY: coefficients are latched before the first store");
}
static void test_nead_indexed(void)
{
task_setup();
/* the index list sits at the start of the sample buffer */
*alist_s16(&s_task, 0x400) = 6; /* 6 against period 3: stays 3 */
*alist_s16(&s_task, 0x402) = 2;
*alist_s16(&s_task, 0x404) = 0x7000;
*alist_s16(&s_task, 0x406) = (int16_t)0x9001;
task_cmd(0x00000000u | (3u << 16) | 1, 0x400); /* INDEXED, no swaps */
alist_process_nead_ac(&s_task);
CHECK((uint16_t)dmem16(0x406) == 0x0001 && (uint16_t)dmem16(0x404) == 0x0001,
"NEAD INDEXED: an exact multiple of the period keeps the divisor, sums wrap");
}
static void s8_fill(void)
{
unsigned k;
task_setup();
for (k = 0; k < 32; ++k)
*alist_u8(&s_task, (uint16_t)(0x220 + k)) = (uint8_t)(0x11 + k);
for (k = 0x240; k < 0x260; ++k)
*alist_u8(&s_task, (uint16_t)k) = 0xee;
task_cmd(0x08000000u | 0x220, (0x200u << 16) | 64); /* SETBUFF */
task_cmd(0x17000000u | 0x10000u, 0x2000); /* S8DEC, fresh */
}
static void test_nead_s8dec(void)
{
unsigned k;
int ok = 1;
/* input two rows ahead of the output: the pipelined decoder has the
* second source row in registers before its first store lands */
s8_fill();
alist_process_nead_1080(&s_task);
for (k = 0; k < 16; ++k)
if (*alist_u8(&s_task, (uint16_t)(0x240 + 2*k)) != 0x11 + 16 + k
|| *alist_u8(&s_task, (uint16_t)(0x241 + 2*k)) != 0)
ok = 0;
CHECK(ok, "NEAD S8DEC (pipelined): overlapping input is read before the store");
CHECK(*alist_u8(&s_task, 0x220) == 0x11 && *alist_u8(&s_task, 0x200) == 0,
"NEAD S8DEC (pipelined): state row cleared, first row expanded");
CHECK(*dram_u8(&s_task, 0x2000) == 0x11 + 16 && *dram_u8(&s_task, 0x2002) == 0x11 + 17,
"NEAD S8DEC (pipelined): the last row is saved as the state");
/* the plain build loads each source row after the previous store */
s8_fill();
alist_process_nead_sfj(&s_task);
ok = 1;
for (k = 0; k < 32; ++k)
if (*alist_u8(&s_task, (uint16_t)(0x240 + k)) != ((k & 3) ? 0 : 0x11 + 8 + k / 4))
ok = 0;
CHECK(ok, "NEAD S8DEC (plain): overlapping input is read after the store");
}
int main(void)
{
test_clamp_low();
test_nead_clearbuff();
test_nead_multiply();
test_nead_indexed();
test_nead_s8dec();
if (s_failures) {
printf("test_alist_ops: %d failure(s)\n", s_failures);
+33 -1
View File
@@ -5,7 +5,10 @@
* task, and it is not cached;
* - a yielded relaunch of the Rogue Squadron and Naboo graphics tasks
* clears SIG1 like a fresh launch, so osSpTaskYielded does not read
* the completion as another yield.
* the completion as another yield;
* - so does a yielded relaunch of a display list handed to the graphics
* plugin, and ZSortp's resident initialisation clears SIG1 and sets
* SIG4 as its SP_STATUS write does.
*
* Built by `make check-core` (top-level Makefile). Exit status 0 on pass.
*/
@@ -137,10 +140,39 @@ static void test_yield_relaunch(void)
"Naboo: a yielded relaunch clears SIG1");
}
static void test_dlist_signals(void)
{
setup();
*dmem_u32(&s_hle, TASK_UCODE) = 0x2000;
*s_hle.sp_status = SP_STATUS_SIG1;
*dmem_u32(&s_hle, TASK_FLAGS) = 0;
send_dlist_to_gfx_plugin(&s_hle);
CHECK(*s_hle.sp_status & SP_STATUS_SIG1, "dlist: a fresh launch leaves SIG1 alone");
setup();
*dmem_u32(&s_hle, TASK_UCODE) = 0x2000;
*s_hle.sp_status = SP_STATUS_SIG1;
*dmem_u32(&s_hle, TASK_FLAGS) = 1; /* OS_TASK_YIELDED relaunch */
send_dlist_to_gfx_plugin(&s_hle);
CHECK(!(*s_hle.sp_status & SP_STATUS_SIG1), "dlist: a yielded relaunch clears SIG1");
setup();
*dmem_u32(&s_hle, TASK_UCODE) = 0x2000;
*(uint32_t*)(s_dram + 0x2010) = 0x3c0b0004u; /* lui t3, 0x0004 */
*(uint32_t*)(s_dram + 0x2014) = 0x356b2800u; /* ori t3, 0x2800 */
*(uint32_t*)(s_dram + 0x2018) = 0x408b2000u; /* mtc0 t3, SP_STATUS */
*s_hle.sp_status = SP_STATUS_SIG1;
*dmem_u32(&s_hle, TASK_FLAGS) = 0;
send_dlist_to_gfx_plugin(&s_hle);
CHECK(!(*s_hle.sp_status & SP_STATUS_SIG1) && (*s_hle.sp_status & SP_STATUS_SIG4),
"dlist: ZSortp's resident init clears SIG1 and sets SIG4");
}
int main(void)
{
test_non_task();
test_yield_relaunch();
test_dlist_signals();
if (s_failures) {
printf("test_hle_dispatch: %d failure(s)\n", s_failures);
+27 -1
View File
@@ -5,6 +5,11 @@
* alpha byte is averaged like red, green and blue; the HLE version used
* to write it as zero.
*
* It signals the way the microcode does: the final SP_STATUS write
* clears SIG0 and sets SIG1 with task done, and a task started with SIG0
* already set (a fill the CPU pre-empted) completes without touching
* pixels.
*
* Built by `make check-core` (top-level Makefile). Exit status 0 on pass.
*/
#include "../mupen64plus-rsp-hle/src/re2.c"
@@ -19,9 +24,14 @@ void HleWarnMessage(void* user_defined, const char *message, ...)
{
(void)user_defined; (void)message;
}
static unsigned int s_break_bits;
static int s_breaks;
void rsp_break(struct hle_t* hle, unsigned int setbits)
{
(void)hle; (void)setbits;
*hle->sp_status |= setbits;
s_break_bits = setbits;
s_breaks++;
}
static int s_failures;
@@ -32,12 +42,14 @@ static int s_failures;
int main(void)
{
static struct hle_t hle;
static unsigned int sp_status;
unsigned i;
hle.dram = (unsigned char*)calloc(1, 0x800000u);
hle.dmem = (unsigned char*)calloc(1, 0x1000u);
if (hle.dram == NULL || hle.dmem == NULL)
return 2;
hle.sp_status = &sp_status;
*dmem_u32(&hle, TASK_UCODE_DATA) = 0x1000;
*dram_u32(&hle, 0x1000) = 0x2000; /* source frame */
@@ -54,6 +66,7 @@ int main(void)
*dram_u32(&hle, 0x3040 + i * 4) = 0x01000001u;
}
sp_status = 0;
fill_video_double_buffer_task(&hle);
CHECK(*dram_u32(&hle, 0x3000) == 0x20406080u && *dram_u32(&hle, 0x3004) == 0x20406080u,
@@ -61,6 +74,19 @@ int main(void)
CHECK(*dram_u32(&hle, 0x3040) == 0x80000080u && *dram_u32(&hle, 0x3044) == 0x80000080u,
"RE2 fill: the second row averages colour and alpha");
CHECK(*dram_u32(&hle, 0x3008) == 0, "RE2 fill: stops at the row width");
CHECK(s_breaks == 1 && s_break_bits == (SP_STATUS_SIG1 | SP_STATUS_TASKDONE),
"RE2 fill: completion sets SIG1 with task done");
/* pre-empted: SIG0 already set when the task starts */
*dram_u32(&hle, 0x3000) = 0x12345678u;
sp_status = SP_STATUS_SIG0;
s_breaks = 0;
fill_video_double_buffer_task(&hle);
CHECK(*dram_u32(&hle, 0x3000) == 0x12345678u,
"RE2 fill: a pre-empted task leaves the frame alone");
CHECK(s_breaks == 1 && !(sp_status & SP_STATUS_SIG0)
&& (sp_status & SP_STATUS_SIG1) && (sp_status & SP_STATUS_TASKDONE),
"RE2 fill: a pre-empted task clears SIG0 and sets SIG1");
free(hle.dram);
free(hle.dmem);