mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Merge pull request #691 from maliquem/fix/mdec-busy-drain
mdec: keep status bit 29 set until decode output is down to one OutFIFO load
This commit is contained in:
@@ -399,6 +399,14 @@ if(BUILD_TESTING)
|
||||
target_include_directories(guest_tty_test PRIVATE ../runtime/include)
|
||||
add_test(NAME guest_tty_test COMMAND guest_tty_test)
|
||||
|
||||
# MDEC status bit 29 stays set until a decode's output is down to the last
|
||||
# 32-word OutFIFO load (Beetle InCommand); libpress DecDCTin spins on it.
|
||||
add_executable(mdec_busy_drain_test
|
||||
../runtime/tests/test_mdec_busy_drain.c
|
||||
../runtime/src/mdec.c)
|
||||
target_include_directories(mdec_busy_drain_test PRIVATE ../runtime/include)
|
||||
add_test(NAME mdec_busy_drain_test COMMAND mdec_busy_drain_test)
|
||||
|
||||
add_executable(debug_trace_ranges_test
|
||||
../runtime/tests/test_debug_trace_ranges.c
|
||||
../runtime/src/debug_trace_ranges.c
|
||||
|
||||
+15
-1
@@ -27,6 +27,9 @@ extern uint64_t s_frame_count;
|
||||
#define MDEC_NEVER_DECODED ((uint64_t)0 - 1000u)
|
||||
static uint64_t mdec_last_color_decode_cycle = MDEC_NEVER_DECODED;
|
||||
|
||||
/* Beetle OutFIFO depth: 0x20 words (MDEC_DMACanRead's block size). */
|
||||
#define MDEC_OUT_FIFO_BYTES (0x20u * 4u)
|
||||
|
||||
enum {
|
||||
MDEC_CMD_NOP = 0,
|
||||
MDEC_CMD_DECODE = 1,
|
||||
@@ -879,7 +882,18 @@ uint32_t mdec_read(uint32_t addr) {
|
||||
if (!write_ready) status |= 1u << 30;
|
||||
if (mdec.enable_dma_out && mdec_dma_read_ready()) status |= 1u << 27;
|
||||
if (mdec.enable_dma_in && write_ready) status |= 1u << 28;
|
||||
if (mdec.busy) status |= 1u << 29;
|
||||
/* Bit 29 is Beetle's InCommand (mdec.c MDEC_Read). 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. This model decodes a whole command at once, so derive that
|
||||
* from the unread output instead of clearing the bit at execute time:
|
||||
* libpress DecDCTin spins on this bit before issuing the next frame, and
|
||||
* reporting ready early let Breath of Fire IV's player issue the next
|
||||
* DecDCTin/DecDCTout while a column DMA was mid-transfer. DecDCTout's
|
||||
* CHCR=0 restart then dropped the words already moved, misaligning every
|
||||
* later column (vertical stripes + one-macroblock wrap in the FMV). */
|
||||
if (mdec.busy || mdec.output_size - mdec.output_pos > MDEC_OUT_FIFO_BYTES)
|
||||
status |= 1u << 29;
|
||||
if (mdec.output_pos >= mdec.output_size) status |= 1u << 31;
|
||||
mdec.last_status = status;
|
||||
return status;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user