Files
psxrecomp/runtime/tests/test_mdec_busy_drain.c
T
maliquem 613112996f mdec: keep status bit 29 set until decode output is down to one OutFIFO load
Bit 29 is Beetle's InCommand. The hardware decodes progressively and stalls
while its 32-word OutFIFO is full, so a decode command stays in progress until
all but the last FIFO load of its output has been read. mdec.c decodes a whole
command at once and cleared the bit at execute time.

libpress DecDCTin spins on bit 29 before issuing the next frame. Reporting
ready early let Breath of Fire IV's FMV player issue the next DecDCTin and
DecDCTout while a column DMA was mid-transfer; DecDCTout's CHCR=0 restart
dropped the 205 words already moved, so every later column read started 820
bytes late (vertical stripes at macroblock edges, a one-macroblock vertical
wrap and shifted colour channels in the intro FMV).

Derive the bit from the unread output instead. No new state, so snapshots
are unchanged. Adds mdec_busy_drain_test (fails on the old behaviour).
2026-10-19 10:38:08 -03:00

71 lines
2.7 KiB
C

/*
* MDEC status bit 29 (Command Busy) across output drain.
*
* Beetle's MDEC_Read reports InCommand as bit 29, and a decode command stays
* InCommand until its last macroblock fits in the 32-word OutFIFO: the hardware
* decodes progressively and stalls while OutFIFO is full. mdec.c decodes a whole
* command at once, so bit 29 must be derived from the unread output.
*
* Regression: reporting ready right after the instant decode let libpress
* DecDCTin (which spins on bit 29) issue the next frame while a DecDCTout column
* DMA was mid-transfer. DecDCTout's CHCR=0 restart then dropped the words already
* moved, and every later column was misaligned (Breath of Fire IV intro FMV:
* vertical stripes, one-macroblock wrap, wrong colours).
*
* Build/run: ctest -R mdec_busy_drain_test
*/
#include "mdec.h"
#include <stdint.h>
#include <stdio.h>
uint64_t s_frame_count = 0;
uint64_t psx_cycle_count = 0;
int debug_server_fmv_quiet(void) { return 0; }
#define MDEC_DATA 0x1F801820u
#define MDEC_STATUS 0x1F801824u
#define BUSY (1u << 29)
#define OUT_EMPTY (1u << 31)
static int failures = 0;
#define CHECK(cond, ...) do { if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); failures++; } } while (0)
int main(void) {
mdec_init();
mdec_write(MDEC_STATUS, 0x80000000u); /* reset */
mdec_write(MDEC_STATUS, 0x60000000u); /* enable DMA in + out */
/* Decode, 24bpp (depth 2), 6 input words: one colour macroblock made of
* six DC-only blocks (Cr, Cb, Y0..Y3), each a 0x0000 DC word + 0xFE00 end. */
mdec_write(MDEC_DATA, 0x30000006u);
CHECK(mdec_read(MDEC_STATUS) & BUSY, "busy while receiving parameters");
for (int i = 0; i < 6; i++)
mdec_dma_write_word(0xFE000000u);
/* 16x16 pixels * 3 bytes = 768 bytes = 192 words of output. */
const uint32_t total_words = 192u, fifo_words = 0x20u;
for (uint32_t read = 0; read < total_words; read++) {
uint32_t remaining = total_words - read;
uint32_t st = mdec_read(MDEC_STATUS);
if (remaining > fifo_words)
CHECK(st & BUSY, "busy with %u words unread", remaining);
else
CHECK(!(st & BUSY), "ready with %u words unread (last FIFO load)", remaining);
CHECK(!(st & OUT_EMPTY), "output empty with %u words unread", remaining);
(void)mdec_dma_read_word();
}
uint32_t st = mdec_read(MDEC_STATUS);
CHECK(!(st & BUSY), "ready after drain");
CHECK(st & OUT_EMPTY, "output empty after drain");
if (failures) {
fprintf(stderr, "mdec_busy_drain_test: %d failure(s)\n", failures);
return 1;
}
printf("mdec_busy_drain_test: ok\n");
return 0;
}