Files
libretroadmin 248c2a06e3 tests: score the DMAC CPCOND0 signal
tests/dma/dmac/cpcond.expected, which nothing read. CPCOND0 is what the
EE's BC0F and BC0T branch on, and it is a pure function of two DMAC
registers: set when every channel PCR.CPC selects has raised its
interrupt in STAT.CIS, with unselected channels reading as already
satisfied. The capture walks six states and this scores all of them
through COP0.cpp's own CPCOND0.

Each half of the formula is pinned by different lines. Dropping the
inversion on CPC, or turning the or into an and, fails four of the six --
both break the vacuous cases, where nothing is selected and the signal is
set anyway. Narrowing the mask to nine bits fails two, since channel 9 is
the one the capture uses.

The two states either side of the DMA are what make the capture worth
having: selecting channel 9 drops the signal because that channel has not
finished, and the transfer raising CIS9 brings it back. Restoring PCR
afterwards leaves it set because CIS9 is still latched, and writing CIS9
back is what clears it -- STAT bits are acknowledged by writing one, so
the last line is a write that clears rather than sets.
2026-08-28 22:42:45 +02:00

91 lines
3.0 KiB
C++

/* DMAC CPCOND0 against console captures.
*
* Scores CPCOND0 in pcsx2/COP0.cpp against ps2autotests
* tests/dma/dmac/cpcond.expected, which nothing read.
*
* CPCOND0 is the signal the EE's BC0F and BC0T branch on, and it is a
* pure function of two DMAC registers: it is set when every channel the
* program is watching has raised its interrupt, which the hardware
* computes as
*
* ((STAT.CIS | ~PCR.CPC) & 0x3FF) == 0x3FF
*
* -- a channel contributes only if PCR.CPC selects it, and an unselected
* channel reads as already satisfied. That is why the signal starts set
* with PCR clear: nothing is being watched, so the condition is
* vacuously true.
*
* The capture walks six states, and the interesting ones are the pair
* either side of the DMA: selecting channel 9 in PCR drops the signal
* because that channel has not finished, and the transfer raising
* STAT.CIS9 brings it back. Clearing PCR again leaves it set for the
* same vacuous reason as the start, and restoring PCR leaves it set
* because CIS9 is still latched -- writing CIS9 back is what finally
* clears it, since STAT bits are acknowledged by writing one.
*
* Usage: tests/ee/hwcpcond
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "Common.h"
/* The DMAC registers the signal reads. Real ones, since COP0.cpp's
* CPCOND0 indexes them through dmacRegs. */
alignas(16) u8 eeHw[0x10000];
alignas(16) cpuRegisters cpuRegs;
/* COP0.cpp's other reachable symbols: the TLB array and the branch and
* memory paths CPCOND0 itself never enters. */
static EEVM_MemoryAllocMess s_eemem;
EEVM_MemoryAllocMess* eeMem = &s_eemem;
tlbs tlb[48];
void intDoBranch(u32) { }
void vtlb_VMap(u32, u32, u32) { }
void vtlb_VMapBuffer(u32, void*, u32) { }
void vtlb_VMapUnmap(u32, u32) { }
void intSetBranch() { }
static R5900cpu s_cpu = {};
R5900cpu* Cpu = &s_cpu;
#define CPC9 (1u << 9)
#define CIS9 (1u << 9)
namespace R5900 { namespace Interpreter { namespace OpcodeImpl { namespace COP0 {
int CPCOND0();
} } } }
int main(void)
{
static const struct { const char* what; u32 cpc, cis; int want; }
kSteps[] = {
/* Straight from the capture's six lines. */
{ "initial value", 0, 0, 1 },
{ "after setting PCR.CPC", CPC9, 0, 0 },
{ "after sending DMA", CPC9, CIS9, 1 },
{ "after clearing PCR.CPC", 0, CIS9, 1 },
{ "after restoring PCR.CPC", CPC9, CIS9, 1 },
{ "after resetting STAT.CIS", CPC9, 0, 0 },
};
const int n = (int)(sizeof(kSteps)/sizeof(kSteps[0]));
int i, pass = 0;
for (i = 0; i < n; i++)
{
int got;
memset(eeHw, 0, sizeof(eeHw));
dmacRegs.pcr.CPC = kSteps[i].cpc >> 0;
dmacRegs.stat.CIS = kSteps[i].cis >> 0;
got = R5900::Interpreter::OpcodeImpl::COP0::CPCOND0();
if (got == kSteps[i].want) pass++;
else
printf(" %-28s console %d, ours %d\n",
kSteps[i].what, kSteps[i].want, got);
}
printf("hwcpcond: %d/%d CPCOND0 states match the console\n", pass, n);
return pass != n;
}