mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
18c94a1394
New always-on ring (device_trace.{c,h}, compiles into both binaries like
parity_trace) recording every hardware IRQ-raise edge with its guest cycle,
source (I_STAT bit) and detail. All native device sources funnel their raise
through one choke point psx_irq_raise(bit,detail) in interrupts.c (VBLANK/GPU/
CDROM/DMA/timers/SIO routed; DMA passes DICR channel flags, timers pass index),
so the device-timing audit sees every event from one place. devtrace_dump/ctl
debug commands (cycle-window + source filters) + tools/devtrace_diff.py align
the two device-event timelines by source ordinal and flag where cycles drift.
The Beetle oracle side (mednafen IRQ_Assert rising-edge hook) lives in the main
checkout.
This is the level below parity_trace: parity names the divergent control-flow /
gate input; device_trace names the divergent HARDWARE EVENT behind it.
First result (MMX6 cutscene asset-load over-fast wedge): the load's CD+DMA
activity is compressed into ~232M cycles on native vs ~384M on Beetle (~1.65x),
completing the func_8001CB3C scene setup ~100M cycles / ~176 frames early. The
producer is CD-read / DMA-completion timing (DMA channels 2/3/4 = GPU/CD/SPU
asset streaming) -- confirming the device-completion-latency hypothesis and
ruling in the CDROM/DMA cycle model as the over-fast subsystem. (VBlank verified
correct: native VBlank edges are exactly 564480 cyc apart.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.9 KiB
C
74 lines
2.9 KiB
C
/* device_trace.h — general two-process DEVICE-EVENT cycle ring.
|
|
*
|
|
* Purpose: name the device/IRQ event whose guest-cycle timing diverges between
|
|
* psx-runtime (code under test) and psx-beetle (independent oracle). Where
|
|
* parity_trace answers "which control-flow / gate-input diverged", this answers
|
|
* the level below it: "which hardware event (CD sector IRQ / DMA completion /
|
|
* RootCounter / VBlank / SPU) fired at a different guest cycle" — the producer
|
|
* of an over-fast or over-slow wait.
|
|
*
|
|
* Model (CLAUDE.md ring-buffer doctrine): ALWAYS-ON from boot, records one row
|
|
* per hardware IRQ-raise edge (the single choke point each backend funnels its
|
|
* device interrupts through — native psx_irq_raise(); Beetle mednafen
|
|
* IRQ_Assert rising edge), stamped with the deterministic guest-cycle ruler.
|
|
* A probe QUERIES the ring for a cycle window; it never arms-then-times.
|
|
*
|
|
* Generality: nothing here is title-specific. The SAME TU compiles into both
|
|
* binaries and emits IDENTICAL rows, so tools/devtrace_diff.py pulls both
|
|
* timelines and aligns them by guest cycle to find the first divergent event.
|
|
* This is the reusable device-timing audit substrate for the whole ecosystem
|
|
* (see recomp-template); the MMX6 cutscene asset-load over-fast wedge is its
|
|
* first user.
|
|
*/
|
|
#ifndef DEVICE_TRACE_H
|
|
#define DEVICE_TRACE_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* IRQ source = the PSX I_STAT bit index. Identical on both backends (mednafen
|
|
* irq.h uses the same numbering). */
|
|
typedef enum {
|
|
DEV_IRQ_VBLANK = 0,
|
|
DEV_IRQ_GPU = 1,
|
|
DEV_IRQ_CDROM = 2,
|
|
DEV_IRQ_DMA = 3,
|
|
DEV_IRQ_TIMER0 = 4,
|
|
DEV_IRQ_TIMER1 = 5,
|
|
DEV_IRQ_TIMER2 = 6,
|
|
DEV_IRQ_SIO0 = 7,
|
|
DEV_IRQ_SIO1 = 8,
|
|
DEV_IRQ_SPU = 9,
|
|
DEV_IRQ_PIO = 10,
|
|
} dev_irq_source_t;
|
|
|
|
typedef struct {
|
|
uint64_t seq; /* monotonic event index since boot */
|
|
uint64_t cycle; /* absolute guest CPU cycles at the raise (the ruler) */
|
|
uint32_t frame; /* host video frame at the raise */
|
|
uint32_t source; /* dev_irq_source_t — the I_STAT bit raised */
|
|
uint32_t detail; /* source-specific: DMA channel / timer index / 0 */
|
|
} DevEvent;
|
|
|
|
void device_trace_arm(int on); /* on=1 begins recording (idempotent) */
|
|
void device_trace_reset(void); /* clear ring (keeps armed state) */
|
|
int device_trace_is_armed(void); /* hot-path gate: cheap one-branch check */
|
|
|
|
/* Record one device-IRQ raise edge. No-op unless armed. Stamps cycle+frame via
|
|
* the process's guest-cycle/frame accessors (shared with parity_trace). */
|
|
void device_trace_note(uint32_t source, uint32_t detail);
|
|
|
|
/* Readback: copies up to max_rows NEWEST events, oldest-first, into out[]. */
|
|
uint32_t device_trace_get(DevEvent* out, uint32_t max_rows);
|
|
uint64_t device_trace_total(void);
|
|
const char* device_source_str(uint32_t source);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* DEVICE_TRACE_H */
|