mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
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:
1 parent
73b0dcf07a
commit
8287d41676
17 files changed
+294
-57
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -127,3 +127,10 @@ _vjPerfActive
|
||||
# directly as well as through the private frontend channel.
|
||||
_VJDebug*
|
||||
_vjDebugFrame
|
||||
|
||||
_InitializeEventList
|
||||
_SubtractEventTimes
|
||||
_GetTimeToNextEvent
|
||||
_HandleNextEvent
|
||||
_EventStateSave
|
||||
_EventStateLoad
|
||||
@@ -39,6 +39,12 @@
|
||||
UART*;
|
||||
JLink*;
|
||||
VM*;
|
||||
InitializeEventList;
|
||||
SubtractEventTimes;
|
||||
GetTimeToNextEvent;
|
||||
HandleNextEvent;
|
||||
EventStateSave;
|
||||
EventStateLoad;
|
||||
TOM*;
|
||||
OP*;
|
||||
tomRam8;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in new issue
Block a user