Merge pull request #435 from RetroPortingToolKit/fix/isc-icache-invalidate

Apply cache-isolated (IsC) stores to the caches, as Beetle does
This commit is contained in:
Matt Stanley
2026-09-30 15:00:19 -07:00
committed by GitHub
21 changed files with 554 additions and 38 deletions
+25 -10
View File
@@ -178,15 +178,28 @@ Status: PARTIAL.
Status: MODERATE-STRONG (regions games use).
- [ ] KUSEG/KSEG0/KSEG1 mirroring, scratchpad, cache-isolation (IsC) — psx-spx.
- [ ] IsC stores do not reach the I-cache model. memory.c drops every store made while
SR.IsC is set. Beetle's WriteMemory rewrites the tag and valid bits of the addressed
line when the I-cache is enabled and BIU has a tag-test, invalidate or lock mode bit
set; that is how FlushCache (A 44h) invalidates the cache. Natively the cached
kernel handlers keep hitting after a flush where Beetle refills them. Measured in the
axis-2 land gate: −42 cycles after each of three OpenBIOS flushes and −445 after four
SCPH-1001 flushes, all before the shell. A local prototype of Beetle's write (a few lines in the three
`psx_write_*_raw` IsC branches) makes both LLE boots match Beetle at every anchor to
the shell, apart from SCPH-1001's −9 KSEG1 kernel entry.
- [x] **IsC stores reach the caches, as in Beetle (2026-09-29).** memory.c dropped
every store made while SR.IsC was set. Beetle's WriteMemory
(mednafen/psx/cpu.cpp:482-512) handles them before any address decode. With the
I-cache on and a tag-test, invalidate or lock mode bit in BIU, the store rewrites
its line's tag and valid bits; FlushCache (A 44h) invalidates the cache this way.
With the D-cache on and lock mode off, the store lands in the scratchpad at
addr & 0x3FF. Nothing reaches the bus, not even the BIU register. Now
`psx_icache_isc_store` plus memory.c's `isc_store`, at the top of the three
`psx_write_*_raw` chokepoints that every backend stores through. DMA and host
stores (`psx_host_write_*`: mods, FMV skip, debug pokes) are not isolated.
Beetle land gate (LLE boot to the shell, both BIOS images): OpenBIOS 0 at every
anchor (was −126 from the first flushes on); SCPH-1001 −9 at every anchor from
kernel init 0x598 on (was −454), which is the KSEG1 entry of SEGMENT_AWARE_CODE.md
§3.3.
Rulers #1 and #2 are unchanged. Covered by ctest `isc_store_test`. Details:
FAITHFUL_TIMING_PLAN §5, 2026-09-29.
- [ ] IsC SWL/SWR: the emitters and both interpreters run SWL/SWR as a
read-modify-write, so an isolated one reaches `isc_store` as one aligned word.
Beetle's WriteMemory gets the partial width at the unaligned address. The results
differ in two cases. In tag-test mode, an SWR at addr & 3 ≠ 0 sets no valid bits in
Beetle; natively it takes lane 0 of the RAM word. With the D-cache on, native
also rewrites the scratchpad bytes the store leaves alone. No known code does either.
- [ ] BIU bit 11 (I-cache disable, 0xFFFE0130) does not reach the fetch model:
memory.c stores it, psx_icache.c and the interp ignore it. Beetle charges +4 per
fetch while the cache is disabled (CPU_SetBIU). This matters only for RAM code run
@@ -302,7 +315,9 @@ overlap the cycle axis or each other (different files), so they parallelize.
- **`accuracy/axis4_memory_mmio.md`.** GOOD: I_STAT/I_MASK/DICR ack semantics CORRECT.
P1: RAM mirror wrong (we gate `<2MB`; HW aliases 2MB DRAM across an 8MB window 4× —
mirror accesses silently read 0 / drop writes). P2: `IsC` over-broadly drops scratchpad
writes (scratchpad is the D-cache, must stay addressable). P3: level-IRQ relatch; P4:
writes (scratchpad is the D-cache, must stay addressable). (P2 resolved 2026-09-29 to
Beetle's rule: an isolated store reaches the scratchpad only while BIU has the
D-cache on and lock mode off, at addr & 0x3FF; see axis 4.) P3: level-IRQ relatch; P4:
per-segment addr masking; P5: open-bus high bits on I_STAT readback. Validate: MMIO
trace diff + RAM-mirror sentinel probe.
+71
View File
@@ -213,6 +213,77 @@ on a fixed region -> next.
## 5. Status / Log (update every session)
- **2026-09-29 (cache-isolated stores reach the caches, as in Beetle):**
memory.c dropped every store made while SR.IsC was set, so the I-cache model
never saw FlushCache (A 44h) or the boot cache init. Beetle's
PS_CPU::WriteMemory (mednafen/psx/cpu.cpp:482-512) handles an isolated store
before any address decode:
- With the I-cache on (BIU bit 11) and a tag-test, invalidate or lock mode bit,
the store rewrites the tag and valid bits of its line's four words. In
tag-test mode the valid bits come from the stored byte lane; otherwise none
are set.
- With the I-cache on and no mode bit, Beetle writes an instruction word. The
tag model holds no words, so nothing changes.
- With the D-cache on and lock mode off ((BIU & 0x81) == 0x80), the store
lands in the scratchpad at addr & 0x3FF, whatever the address.
- Nothing reaches the bus, the BIU register at 0xFFFE0130 included: Beetle
routes it through MemRW, which only non-isolated stores reach. Beetle does
not model SR.SwC.
- DMA is not a CPU store. Beetle's DMA writes RAM directly, so native DMA now
bypasses IsC instead of being dropped. Host stores (mods, FMV skip, debug
pokes, enhancement fills) are not CPU stores either: they go through
`psx_host_write_*`, which bypasses IsC the same way.
- Implementation: `psx_icache_isc_store` (psx_icache.c) is the tag write, and
memory.c's `isc_store` runs at the top of the three `psx_write_*_raw`
chokepoints. Static code, overlay shards and both interpreters all store
through those chokepoints. A render pass applies the same effect, and its
checkpoint restores tags and scratchpad afterwards. The overlay shadow
record (PSX_OVERLAY_DIFF) fails closed on an isolated store, because its
replay cannot redo it. Lockstep replay leaves the shared tags alone, as it
does for refills.
- LAND GATE (live psx-beetle, same images and method as the #429 gate below).
The LLE boot to the shell was run on four native builds. "master" is
1ec24e9d (#429 merged), "pre" is 9a8737d5 (master just before #429), and
each was also run with this fix. Deltas are native − Beetle:
- OpenBIOS, 15 anchors from `_boot` to shell main: master is 0 up to the
first flushes and −126 from initEvents on; master + fix is 0 at all 15.
pre goes from −2,165,591 to −2,165,478 at the shell entry with the fix:
113 of master's 126 flush cycles (36, 40 and 37 per flush, against 42).
The remaining gap is #429's.
- SCPH-1001, 14 anchors from cache init to the shell entry: master is −9
from kernel init 0x598 and −438/−454 from the boot functions on. master
+ fix is −9 at every anchor from 0x598 on. pre goes from −7,049,462 to
−7,049,017 at the shell entry, the same 445 cycles the fix removes on
master.
- The −9 is the retail kernel entry through its KSEG1 alias
(SEGMENT_AWARE_CODE.md §3.3, PR D). It is the only residual left before
the shell.
- Every hit, counted separately: FlushCache (5 hits on each BIOS), the A0
gate (12 OpenBIOS, 17 SCPH-1001) and the B0 gate (147 SCPH-1001). On
master the gap grew after each flush: −42 per OpenBIOS flush, and −52,
−149, −149, −95 on SCPH-1001. With the fix it stays at 0 (OpenBIOS)
and −9 (SCPH-1001) on every hit.
- Ruler #1 [0x80001C5C→0x80001CA4], SCPH-1001, 64 passes: all four builds
equal Beetle pass for pass (53 × 56, 9 × 77, 2 × 84).
- Ruler #2 (15-loop ROM, OpenBIOS): all four builds equal Beetle on all 14
components, on an HLE boot and on an LLE boot.
- R4 (SLUS-00797, OpenBIOS): with the default boot (recompiled LLE kernel,
HLE boot-skip), the game entry 0x8007D8F4 moves from 100,589,404 to
100,589,746 (+342). With the LLE boot, it moves from 268,764,292 to
268,764,310 (+18); the CD-ROM waits absorb most of it. Two boots of each
build gave the same numbers. R4 reached a race with 0 dispatch misses, and
R4's 10 developer tests pass.
- Every title changes timing, toward Beetle: each boot-time and in-game cache
flush now costs the refills Beetle charges. The change is runtime-only. The
codegen hash is unchanged (04ed5e51), generated BIOS and game C are
byte-identical, and overlay caches stay valid. Savestates and the netplay
digest already carry the tags, so their formats do not change.
- Test: ctest `isc_store_test` drives the real memory.c and psx_icache.c
stores. It covers flush invalidation, tag-test valid bits, data mode, the
scratchpad write, the BIU, DMA and host-store exclusions, and lockstep
replay. 17 of its checks fail on master.
- Left: IsC SWL/SWR differ from Beetle in two corner cases (ACCURACY_BURNDOWN
axis 4). The BIU bit 11 fetch cost is still open.
- **2026-09-29 (uncached KSEG1 fetch charged per instruction, both emitters):**
Beetle ReadInstruction never fills a line for a fetch at 0xA0000000 or above:
each one costs +4 and clears the load give-back. The interp fetches at every PC,
+23
View File
@@ -227,6 +227,29 @@ if(BUILD_TESTING)
-flto -Wl,--gc-sections -Wno-lto-type-mismatch)
target_link_libraries(psx_ram_runtime_map_test PRIVATE ZLIB::ZLIB)
add_test(NAME psx_ram_runtime_map_test COMMAND psx_ram_runtime_map_test)
# Cache-isolated (SR.IsC) CPU stores through the REAL memory.c store
# paths and the REAL psx_icache.c tag model, against Beetle's
# WriteMemory IsC branch: FlushCache invalidation, tag-test valid bits,
# the D-cache scratchpad write, the BIU and DMA exclusions. Same link
# doubles as psx_ram_runtime_map_test, minus the I-cache ones.
add_executable(isc_store_test
tests/test_isc_store.c
tests/psx_ram_runtime_map_stubs.c
src/memory.c
src/dma.c
src/boot_state.c
src/psx_icache.c
src/psx_ram_geometry.c)
target_include_directories(isc_store_test PRIVATE include src)
target_compile_definitions(isc_store_test PRIVATE
PSX_ENABLE_BLOCK_CYCLES=1 PSX_STUBS_REAL_ICACHE=1)
target_compile_options(isc_store_test PRIVATE
-O2 -flto -ffunction-sections -fdata-sections -Wno-lto-type-mismatch)
target_link_options(isc_store_test PRIVATE -flto -Wno-lto-type-mismatch
$<IF:$<PLATFORM_ID:Darwin>,-Wl$<COMMA>-dead_strip,-Wl$<COMMA>--gc-sections>)
target_link_libraries(isc_store_test PRIVATE ZLIB::ZLIB)
add_test(NAME isc_store_test COMMAND isc_store_test)
endif()
add_executable(host_keymap_test
+5
View File
@@ -38,6 +38,11 @@ int ls_shadow_replay_end(uint32_t *ops, int *mismatch_kind,
uint32_t *pc, uint32_t *addr,
uint32_t *expected, uint32_t *actual);
void ls_shadow_abort(void);
/* A recorded effect the replay cannot reproduce fails the record closed (the
* comparison is skipped, as for a device touch). memory.c calls it for a
* cache-isolated store: its I-cache effect depends on BIU state, and the
* replay neither writes the BIU nor stores. No-op outside a shadow record. */
void ls_shadow_record_unreplayable(void);
/* Memory chokepoint hooks, called from the psx_read_ and psx_write_ funcs. */
uint32_t ls_read_hook(uint32_t addr, int size, uint32_t real_val);
+16
View File
@@ -14,6 +14,22 @@ void psx_icache_reset(void);
void psx_icache_fetch(CPUState *cpu, uint32_t addr);
void psx_icache_fetch_miss(CPUState *cpu, uint32_t addr);
/* BIU / cache control (0xFFFE0130) bits the cache model reads; names and values
* as Beetle's cpu.cpp BIU_* defines. */
#define PSX_BIU_ICACHE_ENABLE 0x800u /* BIU_ENABLE_ICACHE_S1 */
#define PSX_BIU_DCACHE_ENABLE 0x080u /* BIU_ENABLE_DCACHE */
#define PSX_BIU_TAG_TEST 0x004u /* BIU_TAG_TEST_MODE */
#define PSX_BIU_INVALIDATE 0x002u /* BIU_INVALIDATE_MODE */
#define PSX_BIU_LOCK 0x001u /* BIU_LOCK_MODE */
/* A CPU store made while SR.IsC is set, applied to the I-cache model: the
* I-cache half of Beetle PS_CPU::WriteMemory's IsC branch. With the I-cache
* enabled and a tag-test, invalidate or lock mode bit set, the store rewrites
* the tag and valid bits of the four words of its line; that is how FlushCache
* (A 44h) invalidates the cache. `biu` is the current 0xFFFE0130 value and
* `value` the stored register value (only its low byte lanes matter). */
void psx_icache_isc_store(uint32_t biu, uint32_t addr, uint32_t value);
/* Keep the interpreter's steady-state tag hit inside its translation unit.
* Misses use the shared slow path, preserving exact cache evolution/timing. */
static inline void psx_icache_fetch_interp(CPUState *cpu, uint32_t addr) {
+8
View File
@@ -86,6 +86,14 @@ static inline int psx_ram_resolve(uint32_t address, uint32_t width,
return 1;
}
/* Stores made by the host rather than the guest CPU (mods, FMV skip, debug
* pokes, enhancement fills). They take the psx_write_* path but are never
* cache-isolated: while SR.IsC is set they still reach memory, as DMA does,
* instead of acting on the caches as a CPU store would (memory.c isc_store). */
void psx_host_write_word(uint32_t addr, uint32_t val);
void psx_host_write_half(uint32_t addr, uint16_t val);
void psx_host_write_byte(uint32_t addr, uint8_t val);
#ifdef __cplusplus
}
#endif
+2 -2
View File
@@ -2666,7 +2666,7 @@ void debug_server_cyc_observe(uint32_t block_leader_phys) {
if (a2) {
uint32_t fl = psx_read_word(a2 + 184u);
if (fl & 0x80u)
psx_write_word(a2 + 184u, fl & ~0x80u);
psx_host_write_word(a2 + 184u, fl & ~0x80u);
}
}
}
@@ -5231,7 +5231,7 @@ static void handle_write_ram(int id, const char *json)
}
uint32_t addr = hex_to_u32(addr_str);
uint8_t val = (uint8_t)hex_to_u32(val_str);
psx_write_byte(addr, val);
psx_host_write_byte(addr, val);
send_ok(id);
}
+8 -2
View File
@@ -3489,6 +3489,7 @@ enum { LS_TRACE_CAP = 65536 };
static ls_op_t s_ls_trace[LS_TRACE_CAP];
static int s_ls_trace_n = 0, s_ls_trace_idx = 0;
static int s_ls_overflow = 0, s_ls_mismatch = 0;
static int s_ls_unreplayable = 0; /* whole-call record saw a non-replayable effect */
static int s_ls_replay_done = 0; /* trace exhausted: stop replay (benign count mismatch) */
static uint32_t s_ls_cur_pc = 0; /* pc of the instruction being replayed */
static int s_ls_shadow_owner = 0; /* 0 none, 1 record, 2 replay */
@@ -3557,6 +3558,7 @@ int ls_shadow_record_begin(void) {
s_ls_trace_n = 0;
s_ls_trace_idx = 0;
s_ls_overflow = 0;
s_ls_unreplayable = 0;
s_ls_mismatch = 0;
s_ls_m_kind = 0;
s_ls_replay_done = 0;
@@ -3572,12 +3574,16 @@ int ls_shadow_record_end(uint32_t *ops, int *saw_exception) {
s_ls_shadow_owner = 0;
if (ops) *ops = (uint32_t)s_ls_trace_n;
if (saw_exception) *saw_exception = s_ls_shadow_saw_exception;
return !s_ls_overflow;
return !s_ls_overflow && !s_ls_unreplayable;
}
void ls_shadow_record_unreplayable(void) {
if (s_ls_shadow_owner == 1) s_ls_unreplayable = 1;
}
int ls_shadow_replay_begin(void) {
if (s_ls_shadow_owner || g_ls_mode != 0 || g_ls_replay_active ||
s_ls_overflow)
s_ls_overflow || s_ls_unreplayable)
return 0;
s_ls_trace_idx = 0;
s_ls_mismatch = 0;
+2 -2
View File
@@ -1339,7 +1339,7 @@ static void bg2d_clear_column(int layer, int ringcol, int worldY) {
uint32_t cell = ringbase
+ (uint32_t)((ringcol & (int)(g_bg2d_ring_cols - 1u)) * 2)
+ (uint32_t)((ringrow & 0x1f) * (int)(g_bg2d_ring_cols * 2u));
psx_write_half(cell, 0);
psx_host_write_half(cell, 0);
ringrow = (ringrow + 1) & 0x1f;
}
}
@@ -1420,7 +1420,7 @@ static int bg2d_fill_column(int layer, int worldX, int worldY, int scrollX, int
uint16_t tile = psx_read_half(metaBase + (uint32_t)metaIdx * 0x200u
+ (uint32_t)(trowInMeta * 0x20) + (uint32_t)(tcolInMeta * 2));
if (write) {
psx_write_half(cell, tile);
psx_host_write_half(cell, tile);
} else if (cmp_total) {
(*cmp_total)++;
if (psx_read_half(cell) != tile && cmp_bad) (*cmp_bad)++;
+1 -1
View File
@@ -1634,7 +1634,7 @@ irq_deliver_eval:
g_exc_escape_reason = PSX_EXC_ESCAPE_NONE; /* set at the actual RFE/SYSCALL return */
} else {
uint32_t sentinel = PSX_EXC_SENTINEL_PC;
cpu->write_word(sentinel, 0x00000000u); /* NOP, read by the handler's BD check */
psx_host_write_word(sentinel, 0x00000000u); /* NOP, read by the handler's BD check */
cpu->cop0[COP0_EPC] = sentinel;
g_exception_real_epc = sentinel;
g_exc_escape_reason = PSX_EXC_ESCAPE_LEGACY_SENTINEL;
+3 -2
View File
@@ -366,6 +366,7 @@ extern "C" uint16_t psx_read_half(uint32_t addr);
extern "C" void psx_write_half(uint32_t addr, uint16_t val);
extern "C" uint8_t psx_read_byte(uint32_t addr);
extern "C" void psx_write_byte(uint32_t addr, uint8_t val);
extern "C" void psx_host_write_half(uint32_t addr, uint16_t val);
/* Guest-side data-read wrappers: same as psx_read_* but charge PS1 main-RAM
* read wait states (R3000A has no D-cache). Wired to cpu->read_* below so the
* timing applies to recompiled + interpreted guest loads, not debug/device reads. */
@@ -7210,8 +7211,8 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
if (g_fmv_skip_total_table) {
/* End the active movie via its own frame-count teardown. */
uint8_t mid = psx_read_byte(g_fmv_skip_movie_id);
psx_write_half(g_fmv_skip_total_table + (uint32_t)mid * 2u,
(uint16_t)g_fmv_skip_end_total);
psx_host_write_half(g_fmv_skip_total_table + (uint32_t)mid * 2u,
(uint16_t)g_fmv_skip_end_total);
} else {
/* Generic fallback: hold START (PSX pad word is active-low; START
* is bit 3) so the game's FMV handler aborts the movie itself. */
+71 -10
View File
@@ -25,6 +25,7 @@
#include "dirty_ram_interp.h"
#include "guest_tty.h"
#include "psx_cycles.h"
#include "psx_icache.h"
#include "psx_memory.h"
#include "render_pass.h"
#include "render_pass_plan.h"
@@ -962,9 +963,8 @@ uint64_t g_kseg2_ignored_writes;
static uint32_t cache_ctrl;
/* Pointer to cpu->cop0[12] (SR). Set once at init.
* Used by write functions to check the IsC (Isolate Cache) bit.
* When IsC is set, RAM/scratchpad writes are silently dropped — the
* real R3000A sends them to the data cache only. */
* Used by write functions to check the IsC (Isolate Cache) bit: while it is
* set, a CPU store goes to the caches and never reaches the bus (isc_store). */
static const uint32_t *sr_ptr;
/* Interrupt controller — non-static so hardware subsystems can set I_STAT bits. */
@@ -1717,6 +1717,47 @@ static inline void d44_note(uint32_t phys, uint32_t old, uint32_t val) {
* counted instead. */
uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
/* SR.IsC (Isolate Cache, bit 16). While it is set, a CPU store goes to the
* caches, never to the bus. This is Beetle PS_CPU::WriteMemory's IsC branch
* (mednafen/psx/cpu.cpp:482-512), which runs before any address decode:
* - I-cache enabled (BIU bit 11) with a tag-test, invalidate or lock mode
* bit: the store rewrites the tag and valid bits of its line
* (psx_icache_isc_store). FlushCache (A 44h) invalidates the I-cache this
* way.
* - I-cache enabled, no mode bit: Beetle writes an instruction word, which
* the model does not hold.
* - D-cache enabled and lock mode clear ((BIU & 0x81) == 0x80): the store
* lands in the scratchpad, the D-cache array, at addr & 0x3FF, whatever
* region addr is in.
* Nothing else happens: an isolated store to RAM, MMIO or the BIU register
* itself is dropped (Beetle routes 0xFFFE0130 through MemRW, libretro.cpp:1053,
* which only non-isolated stores reach). DMA is not a CPU store; Beetle's DMA
* writes RAM directly, so IsC does not apply while a transfer moves data.
* Neither is a host store (psx_host_write_*: mods, FMV skip, debug pokes,
* enhancement fills); like DMA it reaches memory whatever SR says. */
int g_host_store_depth; /* >0 inside psx_host_write_* */
static inline int cpu_store_isolated(void) {
return sr_ptr && (*sr_ptr & 0x10000u) && g_dma_exec_depth == 0 &&
g_host_store_depth == 0;
}
static void isc_store(uint32_t addr, uint32_t val, uint32_t width) {
/* The overlay shadow replay cannot redo this (lockstep.h). */
if (g_ls_mode == 1) ls_shadow_record_unreplayable();
psx_icache_isc_store(cache_ctrl, addr, val);
if ((cache_ctrl & (PSX_BIU_DCACHE_ENABLE | PSX_BIU_LOCK)) ==
PSX_BIU_DCACHE_ENABLE) {
uint32_t off = addr & 0x3FFu;
uint32_t old = 0;
for (uint32_t i = 0; i < width; i++)
old |= (uint32_t)scratchpad[(off + i) & 0x3FFu] << (8u * i);
debug_server_trace_write_check(0x1F800000u + off, old, val, (uint8_t)width);
for (uint32_t i = 0; i < width; i++)
scratchpad[(off + i) & 0x3FFu] = (uint8_t)(val >> (8u * i));
}
}
static void render_pass_mmio_write(uint32_t phys, uint32_t val,
uint32_t width) {
if (width == 4) mmio_write32(phys, val);
@@ -1733,7 +1774,10 @@ static void render_pass_store(uint32_t addr, uint32_t val, uint32_t width) {
t.ram_size = psx_ram_live_bytes(); /* live geometry, as psx_ram_map_write */
t.scratchpad = scratchpad;
t.scratchpad_size = SCRATCHPAD_SIZE;
t.isolate_cache = (sr_ptr && (*sr_ptr & 0x10000u)) ? 1 : 0;
t.isolate_cache = cpu_store_isolated();
/* Same cache effect as outside a pass; the pass checkpoint restores the
* I-cache tags and the scratchpad when it ends. */
if (t.isolate_cache) isc_store(addr, val, width);
t.mmio_write = render_pass_mmio_write;
cls = render_pass_store_to(&t, addr, val, width);
if (cls >= 0) g_render_pass_dropped_writes[cls]++;
@@ -1760,15 +1804,13 @@ static void psx_write_word_raw(uint32_t addr, uint32_t val) {
/* (pgxp) plain-store shadow invalidation retired: the PGXP engine
* validates tracked words against the actual packet word on read, so an
* overwritten word can never be believed (docs/ENHANCEMENTS.md G1). */
/* IsC first, before any decode (cpu_store_isolated). */
if (cpu_store_isolated()) { isc_store(addr, val, 4); return; }
/* KSEG2 cache control — before physical translation. */
if (addr == 0xFFFE0130u) { cache_ctrl = val; return; }
/* KSEG2 guard — see psx_read_word_raw. */
if (addr >= 0xC0000000u) { g_kseg2_ignored_writes++; return; }
/* IsC (Isolate Cache): when set, writes go to D-cache only.
* We have no cache model, so silently discard RAM/scratchpad writes. */
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
uint32_t phys = psx_phys_addr_store(addr);
/* The generated BIOS mirrors its exception trampoline to 0x80000000 during
@@ -1960,7 +2002,7 @@ void psx_write_half(uint32_t addr, uint16_t val) {
}
static void psx_write_half_raw(uint32_t addr, uint16_t val) {
g_guest_store_count++;
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
if (cpu_store_isolated()) { isc_store(addr, val, 2); return; }
/* KSEG2 guard — see psx_read_word_raw. */
if (addr >= 0xC0000000u) { g_kseg2_ignored_writes++; return; }
@@ -2298,9 +2340,28 @@ void psx_write_byte(uint32_t addr, uint8_t val) {
psx_write_byte_raw(addr, val);
s_ls_op_active = 0;
}
/* Host stores: the same paths as a guest store, but never cache-isolated
* (cpu_store_isolated). */
void psx_host_write_word(uint32_t addr, uint32_t val) {
g_host_store_depth++;
psx_write_word(addr, val);
g_host_store_depth--;
}
void psx_host_write_half(uint32_t addr, uint16_t val) {
g_host_store_depth++;
psx_write_half(addr, val);
g_host_store_depth--;
}
void psx_host_write_byte(uint32_t addr, uint8_t val) {
g_host_store_depth++;
psx_write_byte(addr, val);
g_host_store_depth--;
}
static void psx_write_byte_raw(uint32_t addr, uint8_t val) {
g_guest_store_count++;
if (sr_ptr && (*sr_ptr & 0x10000u)) return;
if (cpu_store_isolated()) { isc_store(addr, val, 1); return; }
/* KSEG2 guard — see psx_read_word_raw. */
if (addr >= 0xC0000000u) { g_kseg2_ignored_writes++; return; }
+7 -7
View File
@@ -208,8 +208,8 @@ void set_error(const std::string& error) {
void apply_main_write(const ModResolution::Write& write) {
if (write.fields.empty()) {
for (size_t i = 0; i < write.replacement.size(); ++i)
psx_write_byte((uint32_t)write.location + (uint32_t)i,
write.replacement[i]);
psx_host_write_byte((uint32_t)write.location + (uint32_t)i,
write.replacement[i]);
dirty_ram_mark_executable_range(
(uint32_t)write.location & 0x1FFFFFFFu,
(uint32_t)write.replacement.size());
@@ -217,7 +217,7 @@ void apply_main_write(const ModResolution::Write& write) {
}
for (const ModResolution::Write::Field& field : write.fields) {
for (size_t i = 0; i < field.replacement.size(); ++i)
psx_write_byte(
psx_host_write_byte(
(uint32_t)write.location +
(uint32_t)field.offset + (uint32_t)i,
field.replacement[i]);
@@ -1485,7 +1485,7 @@ extern "C" uint8_t psx_mod_read_byte(uint32_t address) {
}
extern "C" void psx_mod_write_byte(uint32_t address, uint8_t value) {
psx_write_byte(address, value);
psx_host_write_byte(address, value);
}
extern "C" uint16_t psx_mod_read_half(uint32_t address) {
@@ -1493,7 +1493,7 @@ extern "C" uint16_t psx_mod_read_half(uint32_t address) {
}
extern "C" void psx_mod_write_half(uint32_t address, uint16_t value) {
psx_write_half(address, value);
psx_host_write_half(address, value);
}
extern "C" uint32_t psx_mod_read_word(uint32_t address) {
@@ -1501,11 +1501,11 @@ extern "C" uint32_t psx_mod_read_word(uint32_t address) {
}
extern "C" void psx_mod_write_word(uint32_t address, uint32_t value) {
psx_write_word(address, value);
psx_host_write_word(address, value);
}
extern "C" void psx_mod_write_code_word(uint32_t address, uint32_t value) {
psx_write_word(address, value);
psx_host_write_word(address, value);
dirty_ram_mark_executable_range(address & 0x1FFFFFFFu, 4u);
}
+40
View File
@@ -108,6 +108,46 @@ void psx_icache_fetch_miss(CPUState* cpu, uint32_t addr) {
#endif
}
/* IsC store -> I-cache, transcribed from Beetle PS_CPU::WriteMemory
* (mednafen/psx/cpu.cpp:484-501):
*
* if(BIU & BIU_ENABLE_ICACHE_S1) {
* if(BIU & (BIU_TAG_TEST_MODE | BIU_INVALIDATE_MODE | BIU_LOCK_MODE)) {
* valid_bits = (BIU & BIU_TAG_TEST_MODE) ? ((value << ((address & 0x3) * 8)) & 0x0F) : 0x00;
* ICI = &ICache[(address & 0xFF0) >> 2];
* for(i = 0; i < 4; i++)
* ICI[i].TV = ((valid_bits & (1U << i)) ? 0x00 : 0x02) | (address & 0xFFFFFFF0) | (i << 2);
* } else
* ICache[(address & 0xFFC) >> 2].Data = value << ((address & 0x3) * 8);
* }
*
* The else arm writes an instruction word and leaves TV alone. This model keeps
* no instruction words (compiled code supplies them), so it has nothing to do.
* Beetle does not model SR.SwC (swapped caches), so neither does this. The
* store keeps the full virtual address as the tag, as Beetle's refill does. */
void psx_icache_isc_store(uint32_t biu, uint32_t addr, uint32_t value) {
#ifdef PSX_ENABLE_BLOCK_CYCLES
/* Same gate as a refill: lockstep replay must not move the shared tags;
* the whole-call shadow evolves its private view. */
if (g_ls_replay_active && s_icache_shadow_state != 2) return;
if (g_psx_icache_active < 0) g_psx_icache_active = psx_icache_enabled();
if (!g_psx_icache_active) return;
if (!(biu & PSX_BIU_ICACHE_ENABLE)) return;
if (!(biu & (PSX_BIU_TAG_TEST | PSX_BIU_INVALIDATE | PSX_BIU_LOCK))) return;
uint32_t valid = (biu & PSX_BIU_TAG_TEST)
? ((value << ((addr & 0x3u) * 8u)) & 0xFu) : 0u;
uint32_t line = addr & 0xFFFFFFF0u;
uint32_t bidx = (addr & 0xFF0u) >> 2;
for (uint32_t i = 0; i < 4u; i++)
g_psx_icache_tv[bidx + i] =
((valid & (1u << i)) ? 0x0u : 0x2u) | line | (i << 2);
#else
(void)biu;
(void)addr;
(void)value;
#endif
}
void psx_icache_fetch(CPUState* cpu, uint32_t addr) {
psx_icache_fetch_miss(cpu, addr);
}
+7 -1
View File
@@ -64,6 +64,7 @@ extern void (*g_overlay_flush_pending_cycles)(void);
/* Host nesting that runtime frames set on entry and put back on exit. */
extern int g_call_unit_depth; /* overlay_loader_call_native */
extern int g_dma_exec_depth; /* dma.c kick / async writes */
extern int g_host_store_depth; /* memory.c psx_host_write_* */
extern int g_dma_cur_ch;
extern uint32_t g_dma_cur_madr;
extern uint32_t g_dma_cur_bcr;
@@ -99,7 +100,7 @@ extern void mdec_snapshot_write(uint8_t *p);
* pass that returns normally all of it is already balanced. */
typedef struct RenderPassNesting {
int call_unit_depth;
int dma_exec_depth, dma_cur_ch;
int dma_exec_depth, dma_cur_ch, host_store_depth;
uint32_t dma_cur_madr, dma_cur_bcr, dma_initiator_pc;
int dirty_interp_active, exec_phase, precise_mode;
uint32_t dirty_safe_resume_pc;
@@ -112,6 +113,7 @@ typedef struct RenderPassNesting {
static void nesting_save(RenderPassNesting *n) {
n->call_unit_depth = g_call_unit_depth;
n->dma_exec_depth = g_dma_exec_depth;
n->host_store_depth = g_host_store_depth;
n->dma_cur_ch = g_dma_cur_ch;
n->dma_cur_madr = g_dma_cur_madr;
n->dma_cur_bcr = g_dma_cur_bcr;
@@ -128,6 +130,7 @@ static void nesting_save(RenderPassNesting *n) {
static void nesting_restore(const RenderPassNesting *n) {
g_call_unit_depth = n->call_unit_depth;
g_dma_exec_depth = n->dma_exec_depth;
g_host_store_depth = n->host_store_depth;
g_dma_cur_ch = n->dma_cur_ch;
g_dma_cur_madr = n->dma_cur_madr;
g_dma_cur_bcr = n->dma_cur_bcr;
@@ -150,6 +153,7 @@ static int nesting_balanced(const RenderPassNesting *n) {
nesting_save(&now);
return now.call_unit_depth == n->call_unit_depth &&
now.dma_exec_depth == n->dma_exec_depth &&
now.host_store_depth == n->host_store_depth &&
now.dirty_interp_active == n->dirty_interp_active &&
now.exec_phase == n->exec_phase &&
now.precise_mode == n->precise_mode &&
@@ -182,6 +186,8 @@ static int nesting_describe(const RenderPassNesting *n, int bb_defer_ck,
RP_NOTE(now.ov_flush != n->ov_flush, "shard cycle hook");
RP_NOTE(now.dma_exec_depth != n->dma_exec_depth, "DMA depth %+d",
now.dma_exec_depth - n->dma_exec_depth);
RP_NOTE(now.host_store_depth != n->host_store_depth, "host store depth %+d",
now.host_store_depth - n->host_store_depth);
RP_NOTE(now.dirty_interp_active != n->dirty_interp_active, "interpreter flag");
RP_NOTE(now.precise_mode != n->precise_mode, "precise flag");
RP_NOTE(now.exec_phase != n->exec_phase, "exec phase %d->%d",
@@ -26,7 +26,13 @@ void (*g_overlay_flush_pending_cycles)(void);
uint32_t g_psx_cyc_batch;
uint32_t g_psx_cyc_batch_limit;
uint32_t *g_psx_cyc_local_acc;
#ifndef PSX_STUBS_REAL_ICACHE
/* The I-cache model (psx_icache.c). isc_store_test links the real one. */
uint32_t g_psx_icache_tv[1024];
void psx_icache_isc_store(uint32_t biu, uint32_t addr, uint32_t value) {
(void)biu; (void)addr; (void)value; __builtin_trap(); /* SR.IsC is never set here */
}
#endif
uint64_t psx_cycle_count;
uint64_t s_frame_count;
uint64_t psx_next_service_cycle;
@@ -48,6 +54,7 @@ void audio_trace_event(uint16_t k, uint32_t a, uint32_t b) { (void)k; (void)a; (
void parity_trace_note_write(uint32_t a, uint32_t w, uint32_t pc) { (void)a; (void)w; (void)pc; }
uint32_t ls_read_hook(uint32_t a, int s, uint32_t v) { (void)a; (void)s; return v; }
void ls_write_hook(uint32_t a, int s, uint32_t v) { (void)a; (void)s; (void)v; }
void ls_shadow_record_unreplayable(void) {}
int fntrace_is_game_started(void) { return 0; }
void overlay_loader_note_code_write(void) {}
void overlay_loader_resync_validation_after_restore(void) {}
@@ -164,6 +164,10 @@ void psx_write_half(uint32_t addr, uint16_t val) {
(void)val;
}
void psx_host_write_half(uint32_t addr, uint16_t val) {
psx_write_half(addr, val);
}
int mdec_recently_active(uint32_t within_frames) {
(void)within_frames;
return 0;
+234
View File
@@ -0,0 +1,234 @@
/* Cache-isolated (SR.IsC) CPU stores, end to end through the REAL memory.c
* store paths and the REAL psx_icache.c tag model, against Beetle's
* PS_CPU::WriteMemory IsC branch (mednafen/psx/cpu.cpp:482-512):
*
* if(BIU & BIU_ENABLE_ICACHE_S1) {
* if(BIU & (TAG_TEST | INVALIDATE | LOCK)) -> rewrite the line's tag + valid bits
* else -> write an I-cache data word
* }
* if((BIU & 0x081) == 0x080) -> write the scratchpad at addr & 0x3FF
*
* and nothing reaches the bus, the BIU register at 0xFFFE0130 included.
*
* Load-bearing claims (each fails on a memory.c that drops isolated stores):
* 1. The boot FlushCache loop (BIU 0x804, sw zero to every line) invalidates
* a warm line: the next fetch pays a refill again (7 cycles at a line
* start) instead of hitting. Invalidate and lock mode do the same.
* 2. Tag-test mode loads the valid bits from the stored byte lane: a line
* can be marked valid without a refill, and a byte/half store off lane 0
* loads no valid bits.
* 3. Data mode (BIU 0x800) and a disabled I-cache leave the tags alone.
* 4. With the D-cache on and lock mode off, the store lands in the
* scratchpad at addr & 0x3FF; RAM never changes.
* 5. An isolated store to 0xFFFE0130 does not change the BIU.
* 6. A DMA write is not a CPU store: it reaches RAM while IsC is set and
* leaves the tags alone. Neither is a host store (psx_host_write_*: mods,
* FMV skip, debug pokes), even one that would load valid bits.
* 7. A non-isolated store never touches the I-cache.
* 8. Lockstep replay does not move the shared tags. */
#include "cpu_state.h"
#include "psx_icache.h"
#include "psx_memory.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
extern void psx_write_word(uint32_t addr, uint32_t val);
extern void psx_write_half(uint32_t addr, uint16_t val);
extern void psx_write_byte(uint32_t addr, uint8_t val);
extern int g_host_store_depth;
extern uint32_t psx_read_word(uint32_t addr);
extern uint8_t *memory_get_ram_ptr(void);
extern uint8_t *memory_get_scratchpad_ptr(void);
extern void memory_set_sr_ptr(const uint32_t *p);
extern int g_dma_exec_depth;
extern uint64_t psx_cycle_count;
/* BIU values the kernel and the tests use (Beetle cpu.cpp BIU_* bits). */
#define BIU_FLUSH_TAGS 0x804u /* I-cache on + tag-test (boot / FlushCache) */
#define BIU_FLUSH_DATA 0x800u /* I-cache on, no mode bit */
#define BIU_DEFAULT 0x1E988u /* the kernel's run-time value */
#define SR_ISC 0x10000u
static uint32_t sr;
static CPUState cpu;
static int failures;
static void check(int cond, const char *label) {
if (!cond) {
fprintf(stderr, "FAIL: %s\n", label);
failures++;
}
}
static uint64_t fetch_cost(uint32_t pc) {
uint64_t before = psx_cycle_count;
psx_icache_fetch(&cpu, pc);
return psx_cycle_count - before;
}
/* Refill the line holding pc and confirm it now hits. */
static void warm(uint32_t pc) {
(void)fetch_cost(pc & ~0xFu);
check(fetch_cost(pc) == 0u, "warm line hits");
}
static void set_biu(uint32_t v) {
sr = 0;
psx_write_word(0xFFFE0130u, v);
}
static uint32_t ram_word(uint32_t off) {
uint32_t v;
memcpy(&v, memory_get_ram_ptr() + off, sizeof v);
return v;
}
static void flush_loop(uint32_t biu, uint32_t value) {
set_biu(biu);
sr = SR_ISC;
for (uint32_t a = 0; a < 0x1000u; a += 0x10u) psx_write_word(a, value);
sr = 0;
}
int main(void) {
psx_ram_apply_size_request();
memory_set_sr_ptr(&sr);
psx_icache_reset();
g_psx_icache_active = 1;
/* 1. FlushCache invalidates; the stores never reach RAM. */
memcpy(memory_get_ram_ptr() + 0x500u, "\x11\x22\x33\x44", 4);
warm(0x80000500u);
flush_loop(BIU_FLUSH_TAGS, 0u);
check(fetch_cost(0x80000500u) == 7u, "tag-test flush: warm line refills (7)");
check(fetch_cost(0x80000504u) == 0u, "tag-test flush: refilled line hits");
check(ram_word(0x500u) == 0x44332211u, "isolated stores leave RAM alone");
warm(0x80000510u);
flush_loop(0x802u, 0xFFFFFFFFu);
check(fetch_cost(0x80000510u) == 7u, "invalidate mode: no valid bits, refill");
warm(0x80000520u);
flush_loop(0x801u, 0xFFFFFFFFu);
check(fetch_cost(0x80000520u) == 7u, "lock mode: no valid bits, refill");
/* 2. Tag-test valid bits come from the stored byte lane. */
psx_icache_reset();
g_psx_icache_active = 1;
set_biu(BIU_FLUSH_TAGS);
sr = SR_ISC;
psx_write_word(0x80000600u, 0x5u); /* words 0 and 2 valid */
sr = 0;
check(g_psx_icache_tv[0x600u >> 2] == 0x80000600u, "tag word 0 valid");
check(g_psx_icache_tv[(0x600u >> 2) + 1] == (0x80000604u | 2u), "tag word 1 invalid");
check(fetch_cost(0x80000600u) == 0u, "valid bit set by store: hit");
check(fetch_cost(0x80000608u) == 0u, "valid bit set by store: hit (word 2)");
check(fetch_cost(0x80000604u) == 6u, "invalid word: refill from word 1 (6)");
warm(0x80000700u);
sr = SR_ISC;
psx_write_byte(0x80000701u, 0xFFu); /* (0xFF << 8) & 0xF == 0 */
sr = 0;
check(fetch_cost(0x80000700u) == 7u, "byte off lane 0: no valid bits");
warm(0x80000740u);
sr = SR_ISC;
psx_write_half(0x80000742u, 0xFFFFu); /* (0xFFFF << 16) & 0xF == 0 */
sr = 0;
check(fetch_cost(0x80000740u) == 7u, "half off lane 0: no valid bits");
sr = SR_ISC;
psx_write_byte(0x80000780u, 0x03u); /* words 0 and 1 valid */
sr = 0;
check(fetch_cost(0x80000784u) == 0u, "byte lane 0 loads valid bits");
/* 3. Data mode and a disabled I-cache leave the tags alone. */
warm(0x80000800u);
flush_loop(BIU_FLUSH_DATA, 0u);
check(fetch_cost(0x80000800u) == 0u, "data mode keeps the tags");
/* (The tag itself, not a fetch: fetch costs with the cache disabled are
* the separate BIU bit 11 item, ACCURACY_BURNDOWN axis 4.) */
warm(0x80000810u);
flush_loop(0x004u, 0u);
check(g_psx_icache_tv[0x810u >> 2] == 0x80000810u, "I-cache disabled keeps the tags");
/* 4. D-cache on, lock off: the store lands in the scratchpad. */
memset(memory_get_scratchpad_ptr(), 0, 1024);
set_biu(BIU_DEFAULT);
sr = SR_ISC;
psx_write_word(0x00000104u, 0xDEADBEEFu);
psx_write_byte(0xA0001203u, 0x7Eu);
psx_write_half(0x1F800012u, 0xBEEFu);
sr = 0;
{
const uint8_t *sp = memory_get_scratchpad_ptr();
check(memcmp(sp + 0x104u, "\xEF\xBE\xAD\xDE", 4) == 0, "D-cache word -> scratchpad[addr & 0x3FF]");
check(sp[0x203u] == 0x7Eu, "D-cache byte -> scratchpad[addr & 0x3FF]");
check(sp[0x012u] == 0xEFu && sp[0x013u] == 0xBEu, "D-cache half -> scratchpad");
check(ram_word(0x104u) == 0u, "D-cache store leaves RAM alone");
}
set_biu(BIU_DEFAULT | 0x1u); /* lock mode: no scratchpad write */
sr = SR_ISC;
psx_write_word(0x00000200u, 0x01020304u);
sr = 0;
check(memory_get_scratchpad_ptr()[0x200u] == 0u, "lock mode keeps the scratchpad");
set_biu(BIU_FLUSH_TAGS); /* D-cache off */
sr = SR_ISC;
psx_write_word(0x00000208u, 0x01020304u);
sr = 0;
check(memory_get_scratchpad_ptr()[0x208u] == 0u, "D-cache off keeps the scratchpad");
/* 5. An isolated store to the BIU register does not reach it. */
set_biu(BIU_FLUSH_TAGS);
sr = SR_ISC;
psx_write_word(0xFFFE0130u, 0u);
sr = 0;
check(psx_read_word(0xFFFE0130u) == BIU_FLUSH_TAGS, "isolated store keeps the BIU");
/* 6. DMA is not isolated. */
warm(0x80000900u);
set_biu(BIU_FLUSH_TAGS);
sr = SR_ISC;
g_dma_exec_depth = 1;
psx_write_word(0x80000900u, 0x12345678u);
g_dma_exec_depth = 0;
sr = 0;
check(ram_word(0x900u) == 0x12345678u, "DMA write reaches RAM under IsC");
check(fetch_cost(0x80000900u) == 0u, "DMA write keeps the tags");
/* ... nor is a host store. Tag-test mode with lane-0 value 0xF would mark
* a cold line valid if the store were taken as isolated. */
psx_icache_reset();
g_psx_icache_active = 1;
set_biu(BIU_FLUSH_TAGS);
sr = SR_ISC;
psx_host_write_half(0x80000944u, 0xBEEFu);
psx_host_write_byte(0x80000946u, 0x5Au);
psx_host_write_word(0x80000940u, 0x0000000Fu); /* last: its tags stand */
sr = 0;
check(ram_word(0x940u) == 0x0000000Fu, "host word reaches RAM under IsC");
check(ram_word(0x944u) == 0x005ABEEFu, "host half/byte reach RAM under IsC");
check(fetch_cost(0x80000940u) == 7u, "host store loads no valid bits");
check(g_host_store_depth == 0, "host store depth unwinds");
/* 7. Without IsC a store is a bus store; the I-cache does not snoop. */
warm(0x80000A00u);
psx_write_word(0x80000A00u, 0u);
check(ram_word(0xA00u) == 0u && fetch_cost(0x80000A00u) == 0u,
"plain store: RAM written, tags kept");
/* 8. Lockstep replay does not move the shared tags. */
warm(0x80000B00u);
g_ls_replay_active = 1;
sr = SR_ISC;
psx_write_word(0x00000B00u, 0u);
sr = 0;
g_ls_replay_active = 0;
check(fetch_cost(0x80000B00u) == 0u, "lockstep replay keeps the tags");
if (failures) {
fprintf(stderr, "isc_store_test: %d failure(s)\n", failures);
return 1;
}
puts("PASS: isolated stores follow Beetle's WriteMemory IsC branch");
return 0;
}
+13
View File
@@ -58,6 +58,19 @@ extern "C" void psx_write_word(uint32_t address, uint32_t value) {
ram[offset + 3] = (uint8_t)(value >> 24);
}
/* Mod writes are host stores (memory.c psx_host_write_*). */
extern "C" void psx_host_write_byte(uint32_t address, uint8_t value) {
psx_write_byte(address, value);
}
extern "C" void psx_host_write_half(uint32_t address, uint16_t value) {
psx_write_half(address, value);
}
extern "C" void psx_host_write_word(uint32_t address, uint32_t value) {
psx_write_word(address, value);
}
extern "C" uint32_t psx_mod_memory_alloc(uint32_t, uint32_t) { return 0; }
extern "C" uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t, uint32_t) {
return 0;
+6 -1
View File
@@ -42,6 +42,7 @@ uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
void (*g_overlay_flush_pending_cycles)(void) = NULL;
int g_call_unit_depth = 0;
int g_dma_exec_depth = 0;
int g_host_store_depth = 0;
int g_dma_cur_ch = -1;
uint32_t g_dma_cur_madr = 0, g_dma_cur_bcr = 0, g_dma_initiator_pc = 0;
int g_dirty_interp_active = 0;
@@ -201,6 +202,7 @@ static void guest_frame(CPUState *cpu, const Frames *f, int level) {
g_overlay_flush_pending_cycles = shard_flush;
g_exec_phase = 2;
g_dma_exec_depth++; /* dma.c kick */
g_host_store_depth++; /* psx_host_write_* */
g_dma_cur_ch = 2;
g_dirty_interp_active = 1; /* dirty_ram_dispatch */
g_precise_mode = 1; /* psx_run_precise */
@@ -224,6 +226,7 @@ static void guest_frame(CPUState *cpu, const Frames *f, int level) {
g_precise_mode = prev_precise;
g_dirty_interp_active = prev_active;
g_dma_exec_depth--;
g_host_store_depth--;
g_dma_cur_ch = -1;
g_exec_phase = prev_phase;
shard_flush();
@@ -246,7 +249,7 @@ static int leaky_pass_fn(struct CPUState *cpu, void *user, uint32_t alpha_q16) {
}
typedef struct Live {
int bb_defer, unit, dma, dma_ch, active, phase, precise, dispatch;
int bb_defer, unit, dma, host, dma_ch, active, phase, precise, dispatch;
int ov_depth;
uint32_t ov_ip, batch, resume;
void (*flush)(void);
@@ -259,6 +262,7 @@ static void snap(Live *l) {
l->bb_defer = g_psx_cyc_bb_defer;
l->unit = g_call_unit_depth;
l->dma = g_dma_exec_depth;
l->host = g_host_store_depth;
l->dma_ch = g_dma_cur_ch;
l->active = g_dirty_interp_active;
l->phase = g_exec_phase;
@@ -285,6 +289,7 @@ static void check_live(const Live *a, const char *when) {
SAME(bb_defer, "g_psx_cyc_bb_defer");
SAME(unit, "g_call_unit_depth");
SAME(dma, "g_dma_exec_depth");
SAME(host, "g_host_store_depth");
SAME(dma_ch, "g_dma_cur_ch");
SAME(active, "g_dirty_interp_active");
SAME(phase, "g_exec_phase");
+1
View File
@@ -56,6 +56,7 @@ uint64_t g_render_pass_dropped_writes[RENDER_PASS_DROP_CLASSES];
void (*g_overlay_flush_pending_cycles)(void) = NULL;
int g_call_unit_depth = 0;
int g_dma_exec_depth = 0;
int g_host_store_depth = 0;
int g_dma_cur_ch = -1;
uint32_t g_dma_cur_madr = 0, g_dma_cur_bcr = 0, g_dma_initiator_pc = 0;
int g_dirty_interp_active = 0;