Merge pull request #22703 from hrydgard/me-wait

Media Engine part 4: Wait loops and DMACplus
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-21 12:05:47 +02:00
commit 5bc6dcdc72
8 files changed
+572 -1

No files matched your search

+1
View File
@@ -97,6 +97,7 @@ void Init() {
MeMMIO::SysregDevice(),
MeMMIO::InterruptDevice(),
MeMMIO::MemProtectDevice(),
MeMMIO::DmacplusDevice(),
};
for (MMIODevice *device : g_devices) {
device->Reset();
+272
View File
@@ -15,7 +15,16 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <cstring>
#include <vector>
#include "Common/Log.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/HLE/sceMpegbase.h"
#include "GPU/GPUCommon.h"
#include "GPU/ge_constants.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/MeMMIO.h"
#include "Core/MemMap.h"
@@ -162,6 +171,265 @@ public:
void DoState(PointerWrap &p) override {}
};
// A PL080-like DMA controller with the LCD and colour space conversion registers below its
// channels (4-7, at 0x180). Transfers run at once, when a channel is enabled. Addresses are physical;
// on the ME's bus (a master bit in the control word), the low ones are its eDRAM. Each linked list
// item is five words (source, destination, next, control, configuration).
class MeDmacplusDevice : public MMIODevice {
public:
MeDmacplusDevice() : MMIODevice("DMACplus", 0xBC800000, 0x200) {}
bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
u32 *reg = Register(offset);
if (!reg)
return false;
*value = *reg;
return true;
}
bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
if (!g_meState || (offset & 3))
return false;
MeCore::MEState::Dmacplus &d = g_meState->dmacplus;
if (offset == 0x08) {
d.intTCStatus &= ~value;
return true;
}
if (offset == 0x10) {
// No errors to clear.
return true;
}
u32 *reg = Register(offset);
if (!reg)
return false;
*reg = value;
if (offset == 0x100) {
SetLCD(d);
} else if (offset == 0x160 && (value & 1)) {
RunCSC(d);
} else if (offset >= 0x180 && (offset & 0x1F) == 0x10 && (value & 1)) {
RunChannel(d, (offset - 0x180) / 0x20);
}
return true;
}
void Reset() override {}
void DoState(PointerWrap &p) override {}
private:
static u32 *Register(u32 offset) {
if (!g_meState || (offset & 3))
return nullptr;
MeCore::MEState::Dmacplus &d = g_meState->dmacplus;
if (offset == 0x00 || offset == 0x04 || offset == 0x14)
return &d.intTCStatus;
if (offset >= 0x100 && offset < 0x110)
return &d.lcd[(offset - 0x100) / 4];
if (offset >= 0x120 && offset <= 0x160)
return &d.csc[(offset - 0x120) / 4];
if (offset >= 0x180 && (offset & 0x1F) < 0x14)
return &d.channels[(offset - 0x180) / 0x20][(offset & 0x1F) / 4];
return nullptr;
}
// Pixel format 0 is 8888 (mcidclan's samples). The others aren't known.
static void SetLCD(const MeCore::MEState::Dmacplus &d) {
if (d.lcd[1] != 0) {
WARN_LOG(Log::ME, "DMACplus: LCD pixel format %d", d.lcd[1]);
}
__DisplaySetFramebuf(d.lcd[0], d.lcd[3], GE_FORMAT_8888, 1);
}
static u8 *PhysicalPointer(u32 addr, u32 size, bool meBus) {
addr &= 0x1FFFFFFF;
const Memory::AddressSpace *space = g_meState->cpu.space;
if (meBus && addr + size <= space->edramSize)
return space->GetPointerUnchecked(addr);
if (Memory::IsValidRange(addr, size))
return Memory::GetPointerWriteUnchecked(addr);
return nullptr;
}
// The colour space conversion, as the firmware's DMACplus driver programs it (lowio.prx 6.61,
// sceDmacplus_driver_d126494b and 9c492b9b; dmacplus.prx 1.50 the same). The frame is two sets of
// half its height: buffers 0, 1, 4 and 5, and 2, 3, 6 and 7. In AVC mode they're the even and odd
// rows of a frame in the decoder's tiled layout (see UntileYCbCr); in VME mode, planar Y at 0x120
// (0x128), Cb at 0x130 (0x138) and Cr at 0x134 (0x13C). Each set goes to its own destination, or
// both interleave by rows into the first. Not checked on hardware: what 0x140 bits 1 and 2 do
// (taken as chroma line replication and leaving out the second set, from mcidclan's samples),
// and 0x15C bit 8 (taken as chroma being offset by 128).
static void RunCSC(MeCore::MEState::Dmacplus &d) {
const u32 *r = d.csc;
auto reg = [&](u32 offset) { return r[(offset - 0x120) / 4]; };
const u32 config = reg(0x140);
const bool vme = (config & 1) != 0;
const bool replicateChroma = (config & 2) != 0;
const bool onlyFirst = (config & 4) != 0;
const int width = ((config >> 8) & 0x3F) * 16;
const int height = ((config >> 16) & 0x3F) * 16;
const u32 output = reg(0x14C);
const int stride = output >> 8;
const int format = (output >> 1) & 3;
const bool separate = (output & 1) != 0;
d.csc[(0x160 - 0x120) / 4] &= ~1;
d.intTCStatus |= 2;
if (width == 0 || height == 0 || stride < width) {
ERROR_LOG(Log::ME, "DMACplus: bad colour space conversion %dx%d, stride %d", width, height, stride);
return;
}
const int setHeight = height / 2;
const int width2 = width / 2;
// Each set's planes, as rows: luma, and chroma at half width.
const u8 *luma[2]{}, *cb[2]{}, *cr[2]{};
int lumaStride = width, chromaStride = width2, chromaRowShift = 0;
std::vector<u8> planes;
if (vme) {
const int chromaRows = replicateChroma ? setHeight / 2 : setHeight;
chromaRowShift = replicateChroma ? 1 : 0;
for (int s = 0; s < 2; ++s) {
luma[s] = PhysicalPointer(reg(0x120 + s * 8), width * setHeight, true);
cb[s] = PhysicalPointer(reg(0x130 + s * 8), width2 * chromaRows, true);
cr[s] = PhysicalPointer(reg(0x134 + s * 8), width2 * chromaRows, true);
}
} else {
const int blocks = (width / 16) * (height / 16);
const int sizes[8] = { 64 * blocks, 64 * blocks, 64 * blocks, 64 * blocks, 32 * blocks, 32 * blocks, 32 * blocks, 32 * blocks };
const u8 *src[8];
for (int i = 0; i < 8; ++i)
src[i] = PhysicalPointer(reg(0x120 + i * 4), sizes[i], true);
planes.resize(width * height + 2 * width2 * (height / 2));
u8 *frameY = planes.data();
u8 *frameCb = frameY + width * height;
u8 *frameCr = frameCb + width2 * (height / 2);
if (!separate) {
// The sets are the frame's even and odd rows, both with the frame's chroma rows.
UntileYCbCr(frameY, frameCb, frameCr, src, sizes, width, height);
lumaStride = width * 2;
for (int s = 0; s < 2; ++s) {
luma[s] = frameY + s * width;
cb[s] = frameCb;
cr[s] = frameCr;
}
} else {
// Each set is a picture of its own, with its own chroma at half its height (as
// mcidclan's me-avc-csc-hd lays out its data, n12ToHw.py).
std::vector<u8> tall(width * height + 2 * width2 * (height / 2));
for (int s = 0; s < 2; ++s) {
const u8 *setSrc[8] = { src[s * 2], src[s * 2 + 1], nullptr, nullptr, src[4 + s * 2], src[5 + s * 2], nullptr, nullptr };
// Untiled as the even rows of a picture twice as tall.
UntileYCbCr(tall.data(), tall.data() + width * height, tall.data() + width * height + width2 * (height / 2), setSrc, sizes, width, height);
u8 *setY = frameY + s * width * setHeight;
for (int y = 0; y < setHeight; ++y)
memcpy(setY + y * width, tall.data() + 2 * y * width, width);
u8 *cbOut = frameCb + s * width2 * (setHeight / 2);
u8 *crOut = frameCr + s * width2 * (setHeight / 2);
const u8 *tallCb = tall.data() + width * height;
const u8 *tallCr = tallCb + width2 * (height / 2);
for (int y = 0; y < setHeight / 2; ++y) {
memcpy(cbOut + y * width2, tallCb + 2 * y * width2, width2);
memcpy(crOut + y * width2, tallCr + 2 * y * width2, width2);
}
luma[s] = setY;
cb[s] = cbOut;
cr[s] = crOut;
}
chromaRowShift = 1;
}
}
auto coefficient = [](u32 value, int shift) {
int c = (value >> shift) & 0x3FF;
return c >= 0x200 ? c - 0x400 : c;
};
int coef[3][3];
for (int c = 0; c < 3; ++c) {
const u32 value = reg(0x150 + c * 4);
coef[c][0] = coefficient(value, 0);
coef[c][1] = coefficient(value, 10);
coef[c][2] = coefficient(value, 20);
}
const int lumaOffset = reg(0x15C) & 0xFF;
const int chromaOffset = (reg(0x15C) & 0x100) ? 128 : 0;
static const GEBufferFormat formats[4] = { GE_FORMAT_8888, GE_FORMAT_565, GE_FORMAT_5551, GE_FORMAT_4444 };
const int bpp = format == 0 ? 4 : 2;
const u32 rows = separate ? setHeight : height;
const u32 dstAddr[2] = { reg(0x144), reg(0x148) };
for (int s = 0; s < 2; ++s) {
if (separate && s == 1 && onlyFirst)
break;
if (!luma[s] || !cb[s] || !cr[s]) {
ERROR_LOG(Log::ME, "DMACplus: colour space conversion source %d not readable", s);
continue;
}
const u32 dst = separate ? dstAddr[s] : dstAddr[0];
u8 *out = PhysicalPointer(dst, stride * rows * bpp, false);
if (!out) {
ERROR_LOG(Log::ME, "DMACplus: colour space conversion destination %08x not writable", dst);
continue;
}
for (int y = 0; y < setHeight; ++y) {
const u8 *rowY = luma[s] + y * lumaStride;
const u8 *rowCb = cb[s] + (y >> chromaRowShift) * chromaStride;
const u8 *rowCr = cr[s] + (y >> chromaRowShift) * chromaStride;
const int outRow = separate ? y : y * 2 + s;
u8 *outPixels = out + outRow * stride * bpp;
for (int x = 0; x < width; ++x) {
const int Y = rowY[x] - lumaOffset;
const int Cb = rowCb[x >> 1] - chromaOffset;
const int Cr = rowCr[x >> 1] - chromaOffset;
int rgb[3];
for (int c = 0; c < 3; ++c)
rgb[c] = std::clamp((coef[c][0] * Y + coef[c][1] * Cb + coef[c][2] * Cr + 64) >> 7, 0, 255);
switch (format) {
case 0: ((u32_le *)outPixels)[x] = rgb[0] | (rgb[1] << 8) | (rgb[2] << 16); break;
case 1: ((u16_le *)outPixels)[x] = (rgb[0] >> 3) | ((rgb[1] >> 2) << 5) | ((rgb[2] >> 3) << 11); break;
case 2: ((u16_le *)outPixels)[x] = (rgb[0] >> 3) | ((rgb[1] >> 3) << 5) | ((rgb[2] >> 3) << 10); break;
default: ((u16_le *)outPixels)[x] = (rgb[0] >> 4) | ((rgb[1] >> 4) << 4) | ((rgb[2] >> 4) << 8); break;
}
}
}
const u32 size = stride * rows * bpp;
NotifyMemInfo(MemBlockFlags::WRITE, dst & 0x1FFFFFFF, size, "DMACplusCsc");
if (gpu)
gpu->PerformWriteFormattedFromMemory(dst & 0x1FFFFFFF, size, stride, formats[format], width);
}
}
static void RunChannel(MeCore::MEState::Dmacplus &d, int channel) {
u32 *regs = d.channels[channel];
// Bounded, in case of a list that loops.
for (int item = 0; item < 0x10000; ++item) {
const u32 control = regs[3];
const u32 width = 1 << std::min((control >> 18) & 7, 4U);
const u32 bytes = (control & 0xFFF) * width;
u8 *src = PhysicalPointer(regs[0], bytes, (control >> 24) & 1);
u8 *dst = PhysicalPointer(regs[1], bytes, (control >> 25) & 1);
if (src && dst) {
memmove(dst, src, bytes);
NotifyMemInfo(MemBlockFlags::WRITE, regs[1] & 0x1FFFFFFF, bytes, "DMACplus");
} else {
ERROR_LOG(Log::ME, "DMACplus: bad transfer of %d bytes from %08x to %08x", bytes, regs[0], regs[1]);
}
if (control & 0x80000000)
d.intTCStatus |= 1 << (channel + 4);
const u32 next = regs[2];
if (next == 0)
break;
const u8 *lli = PhysicalPointer(next & ~3, 20, false);
if (!lli) {
ERROR_LOG(Log::ME, "DMACplus: bad linked list item at %08x", next);
break;
}
for (int i = 0; i < 5; ++i)
regs[i] = ((const u32_le *)lli)[i];
}
regs[4] &= ~1;
}
};
static MeDmacplusDevice g_dmacplus;
static MeSysregDevice g_sysreg;
static MeInterruptDevice g_interrupts;
static MeMemProtectDevice g_memProtect;
@@ -174,6 +442,10 @@ MMIODevice *InterruptDevice() {
return &g_interrupts;
}
MMIODevice *DmacplusDevice() {
return &g_dmacplus;
}
MMIODevice *MemProtectDevice() {
return &g_memProtect;
}
+2
View File
@@ -28,6 +28,8 @@ namespace MeMMIO {
MMIODevice *SysregDevice();
// The ME's interrupt controller (0xBC300000). The main CPU has its own there.
MMIODevice *InterruptDevice();
// DMACplus (0xBC800000): DMA between the CPUs' memories, and the LCD.
MMIODevice *DmacplusDevice();
// Memory protection (0xBC000000), which the ME's startup code unlocks.
MMIODevice *MemProtectDevice();
+127 -1
View File
@@ -28,6 +28,7 @@
#include "Core/MIPS/MeCore.h"
#include "Core/MIPS/Interpreter.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSTables.h"
namespace MeCore {
@@ -93,6 +94,8 @@ static void PowerOn(MEState *me) {
cpu.cop0[22] = ME_CPU_ID;
cpu.r[MIPS_REG_SP] = 0x80000000 + cpu.space->edramSize - 0x20;
me->ramConfig = 0x20000002;
// The luma offset (16) and chroma offset of the firmware's default conversion (avcodec.prx).
me->dmacplus.csc[(0x15C - 0x120) / 4] = 0x110;
me->busClock = 0xD61D;
// Held in reset, as the register says: a read-modify-write of it mustn't release the ME.
me->resetFlags = 4;
@@ -166,6 +169,10 @@ void DoState(PointerWrap &p) {
Do(p, me->ramConfig);
Do(p, me->mutex);
DoArray(p, me->memProtect, ARRAY_SIZE(me->memProtect));
Do(p, me->dmacplus.intTCStatus);
DoArray(p, me->dmacplus.lcd, ARRAY_SIZE(me->dmacplus.lcd));
DoArray(p, me->dmacplus.csc, ARRAY_SIZE(me->dmacplus.csc));
DoArray(p, &me->dmacplus.channels[0][0], 4 * 5);
DoArray(p, me->cpu.space->GetPointerUnchecked(0), me->cpu.space->edramSize);
DoArray(p, me->cpu.space->GetPointerUnchecked(0x04000000), me->cpu.space->localSize);
Do(p, meIntrEvent);
@@ -193,6 +200,7 @@ static void CheckInterrupts(MEState *me) {
if ((status & (STATUS_IE | STATUS_EXL | STATUS_ERL)) != STATUS_IE) {
return;
}
me->waiting = false;
// Interrupts go straight to EBase (COP0 25), with ExcCode 0.
cpu.cop0[14] = cpu.pc;
cpu.cop0[13] &= ~CAUSE_EXCCODE_BD;
@@ -201,6 +209,117 @@ static void CheckInterrupts(MEState *me) {
cpu.llBit = 0;
}
// Most ME code that waits for the main CPU polls in a loop like lw t0, flag; beq t0, zero, loop; nop.
// Such a loop stores nothing and reads only what it wrote itself earlier in the same pass (or what
// it doesn't write), so every pass goes the same way while memory stays the same - and it does
// while the ME runs, since the main CPU doesn't. One that loops back once will loop until the end
// of the slice. Sony's firmware waits with halt instead, and homebrew loops that also store (a
// heartbeat) or span several blocks aren't recognized.
static const int MAX_WAIT_LOOP_INSTRUCTIONS = 16;
static bool IsPlainOp(MIPSOpcode op, MIPSInfo info) {
const u64 notPlain = BAD_INSTRUCTION | OUT_MEM | OUT_OTHER | IN_OTHER | IS_SYSCALL | IS_FPU | IS_VFPU |
IN_FPUFLAG | OUT_FPUFLAG | IN_VFPU_CC | OUT_VFPU_CC | IN_LO | IN_HI | OUT_LO | OUT_HI | OUT_RA |
IN_FS | IN_FT | OUT_FD | OUT_FS | OUT_FT | IN_VS | IN_VT | OUT_VD;
if (info & notPlain)
return false;
switch (op >> 26) {
case 0x10: // cop0, eret
case 0x1C: // halt, mfic, mtic
case 0x22: // lwl
case 0x26: // lwr
case 0x2F: // cache
case 0x30: // ll
return false;
default:
return true;
}
}
// Where a conditional branch or j goes, or 0 if it isn't one (or is a jr, jal or similar).
static u32 BranchTarget(u32 pc, MIPSOpcode op) {
const u32 code = op >> 26;
const bool regimm = code == 1 && ((op >> 16) & 0x1C) == 0; // bltz, bgez and their likely forms
if ((code >= 4 && code <= 7) || (code >= 0x14 && code <= 0x17) || regimm)
return pc + 4 + ((s32)(s16)(op & 0xFFFF) << 2);
if (code == 2)
return ((pc + 4) & 0xF0000000) | ((op & 0x03FFFFFF) << 2);
return 0;
}
// The end (after the delay slot) of a wait loop starting at start, or 0 if it isn't one.
static u32 FindWaitLoop(const Memory::AddressSpace &space, u32 start) {
MIPSOpcode ops[MAX_WAIT_LOOP_INSTRUCTIONS + 1];
int count = 0;
bool loops = false;
for (u32 pc = start; count < MAX_WAIT_LOOP_INSTRUCTIONS; pc += 4) {
if (!space.IsValid4AlignedAddress(pc))
return 0;
const MIPSOpcode op(space.ReadUnchecked_U32(pc));
const MIPSInfo info = MIPSGetInfo(op);
ops[count++] = op;
if (loops) {
// The back edge's delay slot.
if ((info & (IS_CONDBRANCH | IS_JUMP)) || !IsPlainOp(op, info))
return 0;
break;
}
if (info & (IS_CONDBRANCH | IS_JUMP)) {
const u32 target = BranchTarget(pc, op);
// An exit is fine, but likely branches skip their delay slot when not taken.
if (target == 0 || ((info & LIKELY) && target != start))
return 0;
loops = target == start;
} else if (!IsPlainOp(op, info)) {
return 0;
}
}
if (!loops)
return 0;
// Every register the loop writes has to be written before it's read in a pass.
u32 written = 0;
for (int i = 0; i < count; ++i) {
const MIPSGPReg out = MIPSAnalyst::GetOutGPReg(ops[i]);
if (out != MIPS_REG_INVALID && out != MIPS_REG_ZERO)
written |= 1U << out;
}
u32 writtenSoFar = 0;
for (int i = 0; i < count; ++i) {
for (int r = 1; r < 32; ++r) {
if ((written & (1U << r)) && !(writtenSoFar & (1U << r)) && MIPSAnalyst::ReadsFromGPReg(ops[i], (MIPSGPReg)r))
return 0;
}
const MIPSGPReg out = MIPSAnalyst::GetOutGPReg(ops[i]);
if (out != MIPS_REG_INVALID && out != MIPS_REG_ZERO)
writtenSoFar |= 1U << out;
}
return start + count * 4;
}
// At the top of a wait loop, runs a pass of it. If that comes back to the top, it would only go on
// doing so: true, and the ME waits out the rest of the slice.
static bool WaitsForMainCPU(MEState *me) {
MIPSState &cpu = me->cpu;
const u32 start = cpu.pc;
auto it = me->waitLoopEnds.find(start);
if (it == me->waitLoopEnds.end())
it = me->waitLoopEnds.emplace(start, FindWaitLoop(*cpu.space, start)).first;
const u32 end = it->second;
if (end == 0 || cpu.inDelaySlot)
return false;
for (int i = 0; i <= MAX_WAIT_LOOP_INSTRUCTIONS; ++i) {
cpu.downcount -= MIPS_InterpretSingleStep(&cpu);
if (cpu.inDelaySlot)
continue;
if (cpu.pc == start)
return true;
if (cpu.pc < start || cpu.pc >= end)
return false;
}
return false;
}
static void RunCPU(MIPSState &cpu) {
if (!cpu.jit) {
MIPSInterpret_RunDowncount(&cpu);
@@ -225,9 +344,16 @@ void RunFor(int cycles) {
}
MIPSState &cpu = me->cpu;
cpu.downcount += cycles;
if (!cpu.pendingInvalidates_.empty() || cpu.invalidateAll_) {
me->waitLoopEnds.clear();
}
// The main CPU may have given a waiting loop what it waits for.
me->waiting = false;
while (cpu.downcount >= 0 && coreState == CORE_RUNNING_CPU) {
CheckInterrupts(me);
if (me->halted) {
if (!me->halted && !me->waiting)
me->waiting = WaitsForMainCPU(me);
if (me->halted || me->waiting) {
cpu.downcount = -1;
} else {
// Run a part, with the rest set aside so the clock stays right.
+17
View File
@@ -22,6 +22,8 @@
#pragma once
#include <unordered_map>
#include "Common/CommonTypes.h"
#include "Core/MIPS/MIPS.h"
@@ -56,6 +58,21 @@ struct MEState {
u32 ramConfig = 0; // 0x40. Bits 0-1: RAM size, 16 << n MB.
u32 mutex = 0; // 0x48. Bit 0 is the main CPU's, bit 1 the ME's.
u32 memProtect[20]{}; // 0xBC000000-0xBC00004C
// DMACplus (0xBC800000), which moves data between the CPUs' memories, and drives the LCD and the
// colour space conversion. Only what homebrew's ME code uses.
struct Dmacplus {
u32 intTCStatus = 0; // 0x04, channels that finished with their interrupt bit set
u32 lcd[4]{}; // 0x100: framebuffer, pixel format, width, stride
u32 csc[0x11]{}; // 0x120-0x160: colour space conversion
u32 channels[4][5]{}; // 0x180-0x1FF: source, destination, next LLI, control, configuration
} dmacplus;
// Not saved: whether the code at an address is a wait loop (see IsWaitLoop), and where it ends.
// 0 for not one. Cleared when the ME's code changes.
std::unordered_map<u32, u32> waitLoopEnds;
// Waiting for the main CPU, for the rest of this RunFor.
bool waiting = false;
};
// Null when the ME isn't emulated.
+149
View File
@@ -918,6 +918,155 @@ bool TestMeInterrupts() {
return TestMeInterruptsOn(CPUCore::INTERPRETER) && TestMeInterruptsOn(CPUCore::IR_INTERPRETER) && TestMeInterruptsOn(CPUCore::JIT);
}
// The ME skips the rest of a slice spent polling for the main CPU, and only then.
static bool TestMeWaitOn(CPUCore core) {
Memory::g_PSPModel = PSP_MODEL_FAT;
SetupJitHarness();
PSP_CoreParameter().cpuCore = core;
MMIO::Init();
bool success = true;
if (!MeCore::Init()) {
printf("No Media Engine on this platform, skipping\n");
MMIO::Shutdown();
DestroyJitHarness();
return true;
}
MeCore::MEState *me = MeCore::g_meState;
const u32 code = 0x08900000;
const u32 flag = code + 0x200;
const u32 marker = code + 0x204;
const u32 heartbeat = code + 0x208;
const char *lines[] = {
"lui t0, 0x0890",
"lw t1, 0x200(t0)", // 0x08900004: waits for the flag
"beq t1, zero, 0x08900004",
"nop",
"ori t2, zero, 0x1234",
"sw t2, 0x204(t0)",
"lui t3, 0x0010", // then counts down, which isn't waiting
"addiu t3, t3, -1", // 0x0890001C
"bne t3, zero, 0x0890001C",
"nop",
"lw t4, 0x208(t0)", // 0x08900028: and beats, which isn't either
"addiu t4, t4, 1",
"sw t4, 0x208(t0)",
"j 0x08900028",
"nop",
};
u32 addr = code;
for (const char *line : lines) {
std::string error;
if (!MipsAssembleOpcode(line, currentDebugMIPS, addr, &error)) {
printf("ERROR: %s: %s\n", line, error.c_str());
success = false;
}
addr += 4;
}
const u32 jump[] = { MIPS_MAKE_LUI(MIPS_REG_T0, 0x0890), 0x01000008 /* jr t0 */, MIPS_MAKE_NOP() };
memcpy(Memory::GetBootPage() + 0x40, jump, sizeof(jump));
MeCore::SetReset(false);
MeCore::RunFor(100000);
EXPECT_TRUE(me->waiting);
EXPECT_EQ_HEX(me->cpu.pc, code + 4);
EXPECT_TRUE(MeCore::GetTicks() >= 100000 && MeCore::GetTicks() < 100010);
Memory::WriteUnchecked_U32(1, flag);
MeCore::RunFor(100000);
EXPECT_FALSE(me->waiting);
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(marker), 0x1234);
const u32 left = me->cpu.r[MIPS_REG_T3];
EXPECT_TRUE(left > 0 && left < 0x100000 - 1000);
MeCore::RunFor(10000000);
EXPECT_EQ_INT(me->cpu.r[MIPS_REG_T3], 0);
EXPECT_FALSE(me->waiting);
EXPECT_TRUE(Memory::ReadUnchecked_U32(heartbeat) > 1000);
MeCore::Shutdown();
MMIO::Shutdown();
DestroyJitHarness();
return success;
}
bool TestMeWait() {
return TestMeWaitOn(CPUCore::INTERPRETER) && TestMeWaitOn(CPUCore::IR_INTERPRETER) && TestMeWaitOn(CPUCore::JIT);
}
// DMACplus moves data between the ME's eDRAM and main memory, following linked list items.
bool TestMeDmacplus() {
Memory::g_PSPModel = PSP_MODEL_FAT;
SetupJitHarness();
MMIO::Init();
bool success = true;
if (!MeCore::Init()) {
printf("No Media Engine on this platform, skipping\n");
MMIO::Shutdown();
DestroyJitHarness();
return true;
}
const Memory::AddressSpace *space = MeCore::g_meState->cpu.space;
for (u32 i = 0; i < 0x2000; i += 4)
space->WriteUnchecked_U32(0xA0000000 | i, 0x80000000 + i);
// ME to main CPU (channel 5): 0x1000 bytes in 16-byte units, then 0x1000 more from the list.
auto control = [](bool srcME, bool dstME, u32 widthBit, u32 count) {
return (dstME ? 1U << 25 : 0) | (srcME ? 1U << 24 : 0) | (widthBit << 21) | (widthBit << 18) | (1U << 15) | (1U << 12) | count;
};
const u32 lli = 0x08900000;
const u32 item[5] = { 0x1000, 0x08A01000, 0, control(true, false, 2, 0x400), 1 };
for (int i = 0; i < 5; ++i)
Memory::WriteUnchecked_U32(item[i], lli + i * 4);
MMIO::Write(&mipsr4k, 0xBC8001B0, 4, 0);
MMIO::Write(&mipsr4k, 0xBC8001A0, 4, 0);
MMIO::Write(&mipsr4k, 0xBC8001A4, 4, 0x48A00000);
MMIO::Write(&mipsr4k, 0xBC8001A8, 4, lli);
MMIO::Write(&mipsr4k, 0xBC8001AC, 4, control(true, false, 4, 0x100) | 0x80000000);
MMIO::Write(&mipsr4k, 0xBC8001B0, 4, 1);
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC8001B0, 4), 0);
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08A00000), 0xA0000000);
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08A01FFC), 0xA0001FFC);
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC800004, 4), 1 << 5);
MMIO::Write(&mipsr4k, 0xBC800008, 4, 1 << 5);
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC800004, 4), 0);
// And back (channel 4), to further into the eDRAM.
MMIO::Write(&mipsr4k, 0xBC800180, 4, 0x08A00000);
MMIO::Write(&mipsr4k, 0xBC800184, 4, 0x10000);
MMIO::Write(&mipsr4k, 0xBC800188, 4, 0);
MMIO::Write(&mipsr4k, 0xBC80018C, 4, control(false, true, 2, 0x800));
MMIO::Write(&mipsr4k, 0xBC800190, 4, 1);
EXPECT_EQ_HEX(space->ReadUnchecked_U32(0x80011FFC), 0xA0001FFC);
// Colour space conversion: a planar (VME) 32x32 grey frame to 8888, with the default matrix.
for (u32 i = 0; i < 32 * 32; ++i)
space->WriteUnchecked_U8(128, 0x80020000 + i);
for (u32 i = 0; i < 16 * 16 * 2; ++i)
space->WriteUnchecked_U8(128, 0x80030000 + i);
const u32 csc[] = {
0x120, 0x20000, 0x128, 0x20000 + 32 * 16, 0x130, 0x30000, 0x134, 0x30000 + 16 * 8,
0x138, 0x30000 + 16 * 16, 0x13C, 0x30000 + 16 * 24,
0x140, (2 << 16) | (2 << 8) | (1 << 1) | 1,
0x144, 0x48B00000, 0x148, 0x48B00000 + 64 * 16 * 4,
0x14C, (64 << 8) | 1,
0x150, (0x0CC << 20) | 0x095, 0x154, (0x398 << 20) | (0x3CE << 10) | 0x095, 0x158, (0x102 << 10) | 0x095,
};
for (size_t i = 0; i < ARRAY_SIZE(csc); i += 2)
MMIO::Write(&mipsr4k, 0xBC800000 + csc[i], 4, csc[i + 1]);
MMIO::Write(&mipsr4k, 0xBC800160, 4, 1);
EXPECT_EQ_HEX(MMIO::Read(&mipsr4k, 0xBC800160, 4) & 1, 0);
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08B00000), 0x828282);
EXPECT_EQ_HEX(Memory::ReadUnchecked_U32(0x08B00000 + 64 * 31 * 4 + 31 * 4), 0x828282);
MeCore::Shutdown();
MMIO::Shutdown();
DestroyJitHarness();
return success;
}
// Differential fuzzing of the CPU cores. Random straight-line code (ALU, FPU, VFPU, loads and
// stores) runs from a random register state on each core, and every core has to end up with the
// interpreter's state, NaN payloads aside.
+2
View File
@@ -25,6 +25,8 @@ bool TestMMIO();
bool TestMESpace();
bool TestMeCore();
bool TestMeInterrupts();
bool TestMeWait();
bool TestMeDmacplus();
bool TestCPUEquivalence();
bool TestCPURoundingMode();
bool TestVFPUDotJit();
+2
View File
@@ -3738,6 +3738,8 @@ TestItem availableTests[] = {
TEST_ITEM(MESpace),
TEST_ITEM(MeCore),
TEST_ITEM(MeInterrupts),
TEST_ITEM(MeWait),
TEST_ITEM(MeDmacplus),
TEST_ITEM(CPUEquivalence),
TEST_ITEM(CPURoundingMode),
TEST_ITEM(VFPUDotJit),