fix(tom): expose live PIT counters and hardware register readback (#1047)

* fix(tom): expose live PIT counters and hardware register readback

* test(tom): refresh Jagniccc baseline for live PIT readback

* test(tom): keep readback harness under the test ABI guard
This commit is contained in:
Joe Mattiello authored and GitHub committed 2026-10-20 04:14:00 -04:00
1 parent 73b0dcf07a
commit 8287d41676
17 files changed
+294 -57

No files matched your search

+8 -2
View File
@@ -1336,7 +1336,7 @@ $(CORE_DIR)/libretro.o: $(VERSION_H)
clean:
rm -f $(TARGET) $(OBJECTS) $(BUILD_CONFIG_STAMP) $(LEGACY_LINK_MODE_STAMP) \
test/test_cheat test/test_event_queue test/test_blitter_simd \
test/test_dsp_mac40 test/test_m68k_ops test/test_m68k_irq_ssp test/test_m68k_prefetch test/test_gpu_ops \
test/test_dsp_mac40 test/test_m68k_ops test/test_m68k_irq_ssp test/test_m68k_prefetch test/test_gpu_ops test/test_tom_readback \
test/test_dsp_ops test/test_dsp_unit test/test_hle_bios \
test/test_subsystem_init test/test_subsystem_timeline \
test/test_irq_cascade test/test_boot_patterns test/test_fountain_crash test/test_audio_pipeline \
@@ -1414,7 +1414,7 @@ test: export VJ_EXPECT_BUILD := $(shell ./scripts/build-id.sh)
test: EEPROM_GEN_TOOL := /tmp/vj_gen_eeprom_test_rom_$(shell echo $$PPID)
test: EEPROM_FIXTURE := /tmp/vj_eeprom_lifecycle_$(shell echo $$PPID).j64
test: test/test_dram_timing test/test_cheat test/test_event_queue test/test_jlink test/test_jlink_tcp test/test_jlink_discover test/test_voicechat test/test_jlink_netpacket test/test_voicemodem_netpacket test/test_voice_netpacket test/test_uart_loopback test/test_jlink_negotiate test/test_blitter_simd test/test_dsp_mac40 test/test_titledb test/test_titlehook test/test_biosdb test/tools/test_gdbstub_proto \
$(TARGET) test/test_m68k_ops test/test_m68k_irq_ssp test/test_m68k_prefetch test/test_gpu_ops test/test_dsp_ops \
$(TARGET) test/test_m68k_ops test/test_m68k_irq_ssp test/test_m68k_prefetch test/test_gpu_ops test/test_tom_readback test/test_dsp_ops \
test/test_dsp_unit test/test_hle_bios test/test_subsystem_init \
test/test_subsystem_timeline test/test_irq_cascade test/test_boot_patterns \
test/test_fountain_crash \
@@ -1585,6 +1585,7 @@ test: test/test_dram_timing test/test_cheat test/test_event_queue test/test_jlin
./test/test_m68k_irq_ssp
./test/test_m68k_prefetch
./test/test_gpu_ops
./test/test_tom_readback
./test/test_dsp_ops
./test/test_dsp_unit
./test/test_hle_bios
@@ -2713,6 +2714,11 @@ test/test_m68k_prefetch: test/test_m68k_prefetch.c
$(CC) -O2 -Wall -Wno-unused-function -std=c99 $(INCFLAGS) \
-o $@ test/test_m68k_prefetch.c -ldl
# TOM hardware readback regression (#1044, #1045).
test/test_tom_readback: test/test_tom_readback.c test/test_framework.h
$(CC) -O2 -Wall -Wno-unused-function -std=c99 $(INCFLAGS) -o $@ $< -ldl
test/test_gpu_ops: test/test_gpu_ops.c
$(CC) -O2 -Wall -Wno-unused-function -std=c99 $(INCFLAGS) \
-o $@ test/test_gpu_ops.c -ldl
+21
View File
@@ -676,3 +676,24 @@ section C.
p.47) -- that is a timing fact, not a bug. The actual divider bug is **consecutive divides** (v8 TOM #25,
p.139; HBW p.3 #9): two divides separated by fewer than 16 clocks, the second taking the first's quotient
as an operand with no scoreboard dependency in between, give a wrong second result.
### TOM read decode and PIT readback (manual corrections)
Source: original jag_sim `netlists/tom/IODEC.NET` read strobes and Float1-5,
`netlists/tom/MISC.NET` Dtp/Dt. The JTRM v8 p.15 table marks PIT0/PIT1 WO;
the actual chip exposes their **live down-counters** on reads at $F00050/52,
not the programmed reload values. Both stages divide the full system clock
by reload+1, with PIT0=0 disabling the timer.
When no readable target drives TOM's internal register read bus, the decode
selects MEMCON1. Thus INT2 ($F000E2), write-only video registers, and unmapped
TOM I/O addresses below $F02000 return MEMCON1, not the last value written there.
Readable MEMCON2, HC/VC, lightpen, OB, test registers, PIT, INT1, CLUT and
line buffers have their own decode; GPU/blitter reads remain in their own
subsystem handlers; the separate GB1/GB2 bus windows retain their previous
handling, including unmodeled holes. See #1044/#1045 and `test/test_tom_readback.c`.
The emulator derives PIT readback from the existing serialized event time.
It has scheduler-slice resolution; instruction-by-instruction PIT phase and
independent prescaler phase on partial timer reprogramming remain outside
this correction. Reads never advance the clock or reschedule IRQs.
+7
View File
@@ -127,3 +127,10 @@ _vjPerfActive
# directly as well as through the private frontend channel.
_VJDebug*
_vjDebugFrame
_InitializeEventList
_SubtractEventTimes
_GetTimeToNextEvent
_HandleNextEvent
_EventStateSave
_EventStateLoad
+6
View File
@@ -39,6 +39,12 @@
UART*;
JLink*;
VM*;
InitializeEventList;
SubtractEventTimes;
GetTimeToNextEvent;
HandleNextEvent;
EventStateSave;
EventStateLoad;
TOM*;
OP*;
tomRam8;
+13
View File
@@ -215,6 +215,19 @@ double GetTimeToNextEvent(int type/*= EVENT_MAIN*/)
}
/* Query a particular timer without changing the next-event selection. */
double GetCallbackTime(void (* callback)(void), int type)
{
struct Event *list = type == EVENT_MAIN ? eventList : eventListJERRY;
unsigned count = type == EVENT_MAIN ? eventCount : eventCountJERRY;
unsigned i;
for (i = 0; i < count; i++)
if (list[i].timerCallback == callback)
return ClampDueEventTime(list[i].eventTime);
return -1.0;
}
// If a caller invokes HandleNextEvent() when the relevant list is already
// empty (nothing left for the preceding GetTimeToNextEvent() to have
// found), there is nothing to dispatch: return without touching the list
+2
View File
@@ -31,6 +31,8 @@ void SetCallbackTime(void (* callback)(void), double time, int type);
void RemoveCallback(void (* callback)(void));
void AdjustCallbackTime(void (* callback)(void), double time);
double GetTimeToNextEvent(int type);
/* Remaining scheduler time, or -1 if this callback is not queued. */
double GetCallbackTime(void (* callback)(void), int type);
void HandleNextEvent(int type);
void SubtractEventTimes(double elapsed, int type);
+2 -2
View File
@@ -1350,8 +1350,8 @@ void HalflineCallback(void)
gpuPCSample[m68kPCSampleIdx & 0x1FFF] = GPUGetPC();
m68kPCSampleIdx++;
#endif
uint16_t vp = TOMReadWord(0xF0003E, JAGUAR) + 1;
uint16_t vi = TOMReadWord(0xF0004E, JAGUAR);
uint16_t vp = TOMGetVP() + 1;
uint16_t vi = TOMGetVI();
vc++;
+1 -1
View File
@@ -347,7 +347,7 @@ void DACPrepareFrame(int length)
* actually run: JaguarExecuteNew ends the frame when VC wraps after
* VP+1 halflines (HalflineCallback). Clamp a garbage VP (boot-time
* zero, mid-init writes) to the hardware default field. */
vp1 = (uint32_t)(TOMReadWord(0xF0003E, JAGUAR) & 0x7FF) + 1;
vp1 = (uint32_t)(TOMGetVP() & 0x7FF) + 1;
if (vp1 < 200 || vp1 > 1200)
vp1 = JaguarGetDefaultFieldHalflines();
halfline_us = JaguarGetHalflinePeriodUs();
+49 -7
View File
@@ -726,6 +726,11 @@ uint16_t TOMGetVP(void)
return GET16(tomRam8, VP);
}
uint16_t TOMGetVI(void)
{
return GET16(tomRam8, VI);
}
uint16_t TOMGetMEMCON1(void)
{
return GET16(tomRam8, MEMCON1);
@@ -2022,6 +2027,42 @@ void TOMReset(void)
tomHCReadPhase = 0;
}
void TOMPITCallback(void);
/* MISC.NET Dtp/Dt drive the live prescaler/divider onto the read bus.
* Derive them from the already-serialized scheduled expiration, in full
* system clocks (JTRM v8 p.15). Reads have scheduler-slice resolution;
* they must not advance time or reschedule the interrupt. */
static uint16_t TOMReadPIT(int divider)
{
double usecs = GetCallbackTime(TOMPITCallback, EVENT_MAIN);
double tick = vjs.hardwareTypeNTSC ? RISC_CYCLE_IN_USEC : RISC_CYCLE_PAL_IN_USEC;
uint64_t clocks;
uint32_t period = tomTimerPrescaler + 1;
if (!tomTimerPrescaler || usecs < 0.0)
return 0;
clocks = (uint64_t)(usecs / tick + 0.5);
if (!clocks)
return 0;
return divider ? (uint16_t)((clocks - 1) / period)
: (uint16_t)((clocks - 1) % period);
}
/* IODEC.NET Float1-5 selects MEMCON1 unless a readable target is
* decoded. GB1/GB2 select separate GPU bus windows at $2000 and
* above; preserve their existing handling here. Internal video timing
* uses accessors, not bus reads of write-only registers. */
static bool TOMReadable(uint32_t offset)
{
uint32_t word = offset & 0xFFFE;
if (word <= 0x000A || (word >= 0x0010 && word <= 0x0016)
|| word == 0x0020 || word == 0x0022 || word == 0x0050
|| word == 0x0052 || word == 0x00E0 || word == 0x0200)
return true;
return word >= 0x2000 || (word >= 0x0400 && word < 0x0800)
|| (word >= 0x1000 && word < 0x2000);
}
uint8_t TOMReadByte(uint32_t offset, uint32_t who)
{
//???Is this needed???
@@ -2037,18 +2078,20 @@ uint8_t TOMReadByte(uint32_t offset, uint32_t who)
else if ((offset >= 0xF02200) && (offset < 0xF022A0))
return BlitterReadByte(offset, who);
else if (offset == 0xF00050)
return tomTimerPrescaler >> 8;
return TOMReadPIT(0) >> 8;
else if (offset == 0xF00051)
return tomTimerPrescaler & 0xFF;
return TOMReadPIT(0) & 0xFF;
else if (offset == 0xF00052)
return tomTimerDivider >> 8;
return TOMReadPIT(1) >> 8;
else if (offset == 0xF00053)
return tomTimerDivider & 0xFF;
return TOMReadPIT(1) & 0xFF;
else if (offset == 0xF000E0)
return 0;
else if (offset == 0xF000E1)
return TOMIRQControlReg() & 0xFF;
if (!TOMReadable(offset))
return tomRam8[MEMCON1 + (offset & 1)];
return tomRam8[offset & 0x3FFF];
}
@@ -2087,11 +2130,10 @@ uint16_t TOMReadWord(uint32_t offset, uint32_t who)
else if ((offset >= 0xF02200) && (offset < 0xF022A0))
return BlitterReadWord(offset, who);
else if (offset == 0xF00050)
return tomTimerPrescaler;
return TOMReadPIT(0);
else if (offset == 0xF00052)
return tomTimerDivider;
return TOMReadPIT(1);
offset &= 0x3FFF;
return (TOMReadByte(offset, who) << 8) | TOMReadByte(offset + 1, who);
}
+1
View File
@@ -50,6 +50,7 @@ uint16_t TOMGetHDB(void);
uint16_t TOMGetVDB(void);
uint16_t TOMGetHC(void);
uint16_t TOMGetVP(void);
uint16_t TOMGetVI(void);
uint16_t TOMGetMEMCON1(void);
uint16_t TOMGetMEMCON2(void);
+12 -14
View File
@@ -1,18 +1,14 @@
;
; tests/hle/hle_border_color.s - HLE BIOS clears TOM border-color regs.
;
; HLE init zeros the two 16-bit border-color registers at TOM_BORD1
; ($F0002A, green/red) and TOM_BORD2 ($F0002C, blue). Verify both
; read back as zero. (Note: $F00040/$F00042 are VBB/VBE, not the
; border-color regs -- the prompt's address was wrong.)
;
; Detail codes:
; 1 = TOM_BORD1 ($F0002A) nonzero
; 2 = TOM_BORD2 ($F0002C) nonzero
; HLE border-register bus readback (#1045). BORD1/BORD2 are WO, so
; reads return MEMCON1, not the zero-initialized border latch.
; Primary source: jag_sim tom/IODEC.NET Float1-5. Latch initialization
; remains covered by test_hle_bios.c; it cannot be tested by bus reads.
; Detail 1/2 identifies BORD1/BORD2, observed/expected report the words.
;
include "include/jaguar_header.s"
include "include/acid_test.s"
TOM_MEMCON1 equ $F00000
TOM_BORD1 equ $F0002A
TOM_BORD2 equ $F0002C
@@ -20,17 +16,19 @@ TOM_BORD2 equ $F0002C
entry:
ACID_INIT
moveq #0,d7
move.w TOM_MEMCON1.l,d7
move.w TOM_BORD1.l,d5
and.l #$FFFF,d5
tst.l d5
cmp.l d7,d5
bne.s .bad1
move.w TOM_BORD2.l,d5
and.l #$FFFF,d5
tst.l d5
cmp.l d7,d5
bne.s .bad2
ACID_PASS
.bad1: ACID_FAIL #1,d5,#0
.bad2: ACID_FAIL #2,d5,#0
.bad1: ACID_FAIL #1,d5,d7
.bad2: ACID_FAIL #2,d5,d7
+5 -3
View File
@@ -41,9 +41,11 @@ MIN_WRAPS equ 3 ; check across >=3 frames
entry:
ACID_INIT
;; d7 = VP register value (masked) -- expected pre-wrap VC.
move.w TOM_VP,d7
and.w #VC_MASK,d7
;; VP is write-only: program a known period, do not read
;; MEMCON1 from its bus address (#1045). This ROM owns
;; the machine, so no previous video period needs restoring.
move.w #523,TOM_VP
move.w #523,d7
;; d6 = wrap-event counter
moveq #0,d6
+11
View File
@@ -0,0 +1,11 @@
# Regression screenshots
`regression_test.sh` compares the last screenshot dumped during its 600-frame
run (frame 500) against these images.
The Jagniccc baseline includes the live PIT1 timing readout introduced by
#1047. Its source (`JaguarDemos/jagniccc2000/jagniccc.S`, `again` loop) subtracts
PIT1 reads to display rendering and elapsed milliseconds. The old baseline
showed zeros because reads returned the timer reload value. The update changes
86 pixels within `(80,28)`–`(211,34)`; the scene and frame counter are unchanged.
Linux ARM64 and macOS ARM64 CI captures were pixel-identical.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

+11 -3
View File
@@ -293,13 +293,21 @@ static void partBC(int argc, char **argv)
}
/* registers: refused without the flag, written with it; nothing partial */
{
unsigned char before[4], after[4], w[2];
unsigned char before[4], after[4], w[2], saved[2], memcon[2];
unsigned char *tom = harness_dlsym(&cfg, "tomRam8");
CHECK(tom != NULL);
if (!tom) return;
memcpy(saved, tom + 0x2A, 2);
CHECK(g_if->peek(0xF00000, memcon, 2) == 2);
w[0] = 0x12; w[1] = 0x34;
CHECK(g_if->peek(0xF0002A, before, 2) == 2); /* BG */
CHECK(g_if->peek(0xF0002A, before, 2) == 2); /* BORD1 (WO) */
CHECK(g_if->poke(0xF0002A, w, 2, 0) == VJ_DBG_ERR_POLICY);
CHECK(g_if->peek(0xF0002A, after, 2) == 2 && memcmp(before, after, 2) == 0);
CHECK(memcmp(saved, tom + 0x2A, 2) == 0);
CHECK(g_if->poke(0xF0002A, w, 2, VJ_DBG_POKE_REGS) == 2);
CHECK(g_if->peek(0xF0002A, after, 2) == 2 && after[0] == 0x12 && after[1] == 0x34);
CHECK(tom[0x2A] == 0x12 && tom[0x2B] == 0x34);
/* Bus peeks of a WO register expose MEMCON1, not its latch (#1045). */
CHECK(g_if->peek(0xF0002A, after, 2) == 2 && memcmp(after, memcon, 2) == 0);
}
/* the side-effecting set needs the second flag; one byte of it is enough to be refused */
{
+33 -25
View File
@@ -1901,8 +1901,8 @@ static void test_tom_int1_multi_pending_selective_clear(void)
* Pin down the byte/word write decode for the TOM PIT prescaler ($F00050)
* and divider ($F00052). The PIT is observable through the same register
* pair: word reads return the full 16-bit value, byte reads return the
* high or low byte respectively. Writing zero to either field disables
* the PIT (TOMResetPIT removes the scheduled callback); rewriting a
* high or low byte respectively. Reads expose live counters (#1044).
* Writing zero to the prescaler disables the PIT; rewriting a
* non-zero value re-arms it. We exercise the side effect by toggling
* through several configurations and confirming the read-back register
* state matches the write semantics in tom.c.
@@ -1932,7 +1932,7 @@ static void test_tom_pit_reload_semantics(void)
v = p_TOMReadWord(0xF00052, WHO_M68K);
if (v == 0x0000)
PASS("divider=0 disables PIT, read-back=$0000");
PASS("disabled PIT divider reads $0000");
else
FAIL("divider read-back after zero write = $%04X", v);
@@ -2021,10 +2021,10 @@ static void test_tom_pit_reload_semantics(void)
else
FAIL("prescaler after clear = $%04X", v);
v = p_TOMReadWord(0xF00052, WHO_M68K);
if (v == 0xCAFE)
PASS("divider preserved while prescaler cleared");
if (v == 0)
PASS("disabled divider counter reads zero");
else
FAIL("divider after prescaler clear = $%04X (want $CAFE)", v);
FAIL("disabled divider counter = $%04X (want $0000)", v);
/* Re-arming via word write replaces the prescaler cleanly. */
p_TOMWriteWord(0xF00050, 0x00FF, WHO_M68K);
@@ -2034,6 +2034,12 @@ static void test_tom_pit_reload_semantics(void)
else
FAIL("re-armed prescaler = $%04X (want $00FF)", v);
v = p_TOMReadWord(0xF00052, WHO_M68K);
if (v == 0xCAFE)
PASS("divider reload preserved through disable/rearm");
else
FAIL("rearmed divider = $%04X (want $CAFE)", v);
/* Repeated reload writes do not corrupt the register state. */
for (i = 0; i < 4; i++)
{
@@ -2060,7 +2066,7 @@ static void test_tom_pit_reload_semantics(void)
}
/* ================================================================
* Test 10i: TOM Video Timing Register Symmetry
* Test 10i: TOM Video Timing Register Write Latches
* Pin byte/word read/write behavior of the 11-bit TOM video timing
* registers VP ($F0003E), VDB ($F00046), VDE ($F00048),
* HDB1 ($F00038), HDB2 ($F0003A), HDE ($F0003C).
@@ -2070,7 +2076,9 @@ static void test_tom_pit_reload_semantics(void)
* before being decomposed into two TOMWriteByte calls.
* - Byte writes go straight to tomRam8 (no mask), so a byte write to
* the high byte CAN deposit bits 11-15.
* - Reads are unconditional fetches from tomRam8.
* - Inspect tomRam8 to verify the write latch. Hardware bus reads of
* these WO registers return MEMCON1 (#1045), covered separately by
* test_tom_readback; they cannot recover a value for restoration.
*
* The test pins:
* 1. Word write of an in-range pattern round-trips exactly.
@@ -2100,11 +2108,11 @@ static void test_tom_video_timing_symmetry(void)
uint16_t v;
uint8_t bh, bl;
printf("\n=== Test 10i: TOM Video Timing Register Symmetry ===\n");
printf("\n=== Test 10i: TOM Video Timing Register Write Latches ===\n");
/* Snapshot original values for restoration. */
for (i = 0; i < 6; i++)
saved[i] = p_TOMReadWord(regs[i].addr, WHO_M68K);
saved[i] = tom_get16((uint16_t)(regs[i].addr - 0xF00000));
/* (1) Word write of an in-range (11-bit) pattern round-trips exactly.
* Use a different pattern per-register so no value gets lucky. */
@@ -2112,7 +2120,7 @@ static void test_tom_video_timing_symmetry(void)
{
pat = (uint16_t)(0x0500 + (i * 0x11)); /* all <= $07FF */
p_TOMWriteWord(regs[i].addr, pat, WHO_M68K);
v = p_TOMReadWord(regs[i].addr, WHO_M68K);
v = tom_get16((uint16_t)(regs[i].addr - 0xF00000));
if (v == pat)
PASS("%s: word write $%04X round-trips", regs[i].name, pat);
else
@@ -2123,7 +2131,7 @@ static void test_tom_video_timing_symmetry(void)
/* (2) Word write of an out-of-range pattern is masked to 11 bits.
* Pick one register (VP) to exercise the mask path. */
p_TOMWriteWord(0xF0003E, 0xF234, WHO_M68K);
v = p_TOMReadWord(0xF0003E, WHO_M68K);
v = tom_get16((uint16_t)(0xF0003E - 0xF00000));
if (v == (0xF234 & 0x07FF))
PASS("VP: word write $F234 masked to $%04X", v);
else
@@ -2135,7 +2143,7 @@ static void test_tom_video_timing_symmetry(void)
* write $05 to the high byte and expect $0534 (byte writes are
* not masked, so writing $05 lands cleanly). */
p_TOMWriteByte(0xF0003E, 0x05, WHO_M68K);
v = p_TOMReadWord(0xF0003E, WHO_M68K);
v = tom_get16((uint16_t)(0xF0003E - 0xF00000));
if (v == 0x0534)
PASS("VP: byte write to high byte updates high half only");
else
@@ -2143,7 +2151,7 @@ static void test_tom_video_timing_symmetry(void)
/* (4) Byte write to the low byte updates the low half only. */
p_TOMWriteByte(0xF0003F, 0xAB, WHO_M68K);
v = p_TOMReadWord(0xF0003E, WHO_M68K);
v = tom_get16((uint16_t)(0xF0003E - 0xF00000));
if (v == 0x05AB)
PASS("VP: byte write to low byte updates low half only");
else
@@ -2156,9 +2164,9 @@ static void test_tom_video_timing_symmetry(void)
{
pat = (uint16_t)(0x0640 + (i * 0x07)); /* in 11-bit range */
p_TOMWriteWord(regs[i].addr, pat, WHO_M68K);
v = p_TOMReadWord(regs[i].addr, WHO_M68K);
bh = p_TOMReadByte(regs[i].addr, WHO_M68K);
bl = p_TOMReadByte(regs[i].addr + 1, WHO_M68K);
v = tom_get16((uint16_t)(regs[i].addr - 0xF00000));
bh = p_tomRam8[regs[i].addr - 0xF00000];
bl = p_tomRam8[regs[i].addr + 1 - 0xF00000];
if (bh == ((v >> 8) & 0xFF) && bl == (v & 0xFF))
PASS("%s: byte reads mirror word halves ($%02X $%02X)",
regs[i].name, bh, bl);
@@ -2217,7 +2225,7 @@ static void test_tom_vmode_bitfields(void)
printf("\n=== Test 10l: TOM VMODE Bit-Field Read/Write ===\n");
/* (1) Snapshot original VMODE so we can restore. */
saved = p_TOMReadWord(addr_w, WHO_M68K);
saved = tom_get16((uint16_t)(addr_w - 0xF00000));
PASS("VMODE: snapshot original value $%04X", saved);
/* (2) Word write: PWIDTH=4 (field=011 -> bits 11..9 = 0x0600),
@@ -2225,7 +2233,7 @@ static void test_tom_vmode_bitfields(void)
* Pattern: $0601. Verify exact round-trip (no masking). */
pat = 0x0601;
p_TOMWriteWord(addr_w, pat, WHO_M68K);
v = p_TOMReadWord(addr_w, WHO_M68K);
v = tom_get16((uint16_t)(addr_w - 0xF00000));
if (v == pat)
PASS("VMODE: word write $%04X (PWIDTH=4,CRY) round-trips", pat);
else
@@ -2243,7 +2251,7 @@ static void test_tom_vmode_bitfields(void)
* (MODE bits 2..1 = 11 -> $0006), VIDEN=1. Pattern: $0207. */
pat = 0x0207;
p_TOMWriteWord(addr_w, pat, WHO_M68K);
v = p_TOMReadWord(addr_w, WHO_M68K);
v = tom_get16((uint16_t)(addr_w - 0xF00000));
if (v == pat)
PASS("VMODE: word write $%04X (PWIDTH=2,RGB16) round-trips", pat);
else
@@ -2258,7 +2266,7 @@ static void test_tom_vmode_bitfields(void)
* low byte (MODE/VIDEN/etc.) untouched. Current word is
* $0207; write $08 to high byte -> expect $0807. */
p_TOMWriteByte(addr_h, 0x08, WHO_M68K);
v = p_TOMReadWord(addr_w, WHO_M68K);
v = tom_get16((uint16_t)(addr_w - 0xF00000));
if (v == 0x0807)
PASS("VMODE: byte write to high byte updates only high half");
else
@@ -2267,7 +2275,7 @@ static void test_tom_vmode_bitfields(void)
/* (5) Byte write to low byte changes MODE/CRY-RGB bits;
* high byte (PWIDTH) untouched. Write $01 (CRY+VIDEN). */
p_TOMWriteByte(addr_l, 0x01, WHO_M68K);
v = p_TOMReadWord(addr_w, WHO_M68K);
v = tom_get16((uint16_t)(addr_w - 0xF00000));
if (v == 0x0801)
PASS("VMODE: byte write to low byte updates only low half");
else
@@ -2278,8 +2286,8 @@ static void test_tom_vmode_bitfields(void)
FAIL("VMODE: MODE bits = $%04X (want $0000)", v & 0x0006);
/* (6) Byte reads at $28/$29 mirror (word>>8)&FF / word&FF. */
bh = p_TOMReadByte(addr_h, WHO_M68K);
bl = p_TOMReadByte(addr_l, WHO_M68K);
bh = p_tomRam8[addr_h - 0xF00000];
bl = p_tomRam8[addr_l - 0xF00000];
if (bh == ((v >> 8) & 0xFF) && bl == (v & 0xFF))
PASS("VMODE: byte reads mirror word halves ($%02X $%02X)", bh, bl);
else
@@ -2288,7 +2296,7 @@ static void test_tom_vmode_bitfields(void)
/* (7) Restore original VMODE so the running render loop sees the
* same video mode it had before this test. */
p_TOMWriteWord(addr_w, saved, WHO_M68K);
v = p_TOMReadWord(addr_w, WHO_M68K);
v = tom_get16((uint16_t)(addr_w - 0xF00000));
if (v == saved)
PASS("VMODE: restored to original $%04X", saved);
else
+112
View File
@@ -0,0 +1,112 @@
/* TOM live PIT and MEMCON1 fallback (#1044, #1045).
* Primary sources: jag_sim tom/MISC.NET Dtp/Dt and IODEC.NET Float1-5.
* JTRM v8 p.15 lists PIT as WO; netlist read strobes correct that table.
*/
#include "test_framework.h"
#include "settings.h"
static struct vj_core c;
static void (*init_events)(void);
static void (*subtract_time)(double,int);
static double (*next_time)(int);
static void (*handle_event)(int);
static size_t (*save_events)(uint8_t *);
static size_t (*load_events)(const uint8_t *);
static uint8_t (*read_byte)(uint32_t,uint32_t);
static uint32_t (*read_long)(uint32_t,uint32_t);
static void reset_timer(void)
{
c.TOMReset(); init_events();
((struct VJSettings *)c.vjs)->hardwareTypeNTSC=true;
c.TOMWriteWord(0xF00050, 9, 0);
c.TOMWriteWord(0xF00052, 3, 0);
}
TEST(pit_live)
{
reset_timer();
ASSERT_EQ(c.TOMReadWord(0xF00052,0),3);
subtract_time(10 * 0.03760684198,0);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),2);
ASSERT_EQ(read_byte(0xF00052,0),0);
ASSERT_EQ(read_byte(0xF00053,0),2);
subtract_time(5 * 0.03760684198,0);
ASSERT_EQ(c.TOMReadWord(0xF00050,0),4);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),2);
}
TEST(pit_rollover_and_state)
{
uint8_t state[8192];
reset_timer();
subtract_time(15 * 0.03760684198,0);
save_events(state);
subtract_time(15 * 0.03760684198,0);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),0);
load_events(state);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),2);
ASSERT_EQ(c.TOMReadWord(0xF00050,0),4);
ASSERT_TRUE(next_time(0)>0);
handle_event(0);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),3);
ASSERT_EQ(c.TOMReadWord(0xF00050,0),9);
}
TEST(pit_full_width)
{
reset_timer();
c.TOMWriteWord(0xF00050,0xFFFF,0);
c.TOMWriteWord(0xF00052,0xFFFF,0);
subtract_time(65536.0 * 0.03760684198,0);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),0xFFFE);
c.TOMWriteWord(0xF00050,0,0);
ASSERT_EQ(c.TOMReadWord(0xF00050,0),0);
ASSERT_EQ(c.TOMReadWord(0xF00052,0),0);
}
TEST(memcon_fallback)
{
static const uint32_t offsets[]={0x0C,0x26,0x28,0x3E,0x4E,0xE2,0xFE,0x100,0x800};
unsigned i;
c.TOMReset();
c.TOMWriteWord(0xF00000,0x1865,0);
for(i=0;i<sizeof(offsets)/sizeof(offsets[0]);i++)
{
ASSERT_EQ(c.TOMReadWord(0xF00000+offsets[i],0),0x1865);
ASSERT_EQ(read_byte(0xF00000+offsets[i],0),0x18);
ASSERT_EQ(read_byte(0xF00001+offsets[i],0),0x65);
}
ASSERT_EQ(c.TOMReadWord(0xF000E2,1),0x1865);
ASSERT_EQ(read_long(0xF000E2,0),0x18651865);
c.TOMWriteWord(0xF00000,0x1234,0);
ASSERT_EQ(c.TOMReadWord(0xF000E2,0),0x1234);
}
TEST(readable_memory)
{
c.TOMWriteWord(0xF00002,0x1234,0);
ASSERT_EQ(c.TOMReadWord(0xF00002,0),0x1234);
c.TOMWriteWord(0xF00400,0xABCD,0);
ASSERT_EQ(c.TOMReadWord(0xF00400,0),0xABCD);
ASSERT_EQ(c.TOMReadWord(0xF00600,0),0xABCD);
c.TOMWriteWord(0xF00800,0xEEEE,0);
ASSERT_EQ(c.TOMReadWord(0xF00800,0),0x1234);
/* GB1/GB2 are outside the Float read-decode correction. Preserve
* the existing backing/dispatch behavior of their unmodeled holes. */
c.TOMWriteWord(0xF02020,0x5678,0);
ASSERT_EQ(c.TOMReadWord(0xF02020,0),0x5678);
}
int main(void)
{
TEST_INIT("TOM hardware readback");
if(!vj_core_load(&c)) return 1;
vj_core_init(&c); c.TOMInit();
#define RESOLVE(name,symbol) name=dlsym(c.handle,symbol); if(!name) return 1
RESOLVE(init_events,"InitializeEventList");
RESOLVE(subtract_time,"SubtractEventTimes");
RESOLVE(next_time,"GetTimeToNextEvent");
RESOLVE(handle_event,"HandleNextEvent");
RESOLVE(save_events,"EventStateSave");
RESOLVE(load_events,"EventStateLoad");
RESOLVE(read_byte,"TOMReadByte");
RESOLVE(read_long,"JaguarReadLong");
RUN_TEST(pit_live); RUN_TEST(pit_rollover_and_state);
RUN_TEST(pit_full_width); RUN_TEST(memcon_fallback);
RUN_TEST(readable_memory);
vj_core_unload(&c);
return TEST_REPORT();
}