599 lines
11 KiB
C++
599 lines
11 KiB
C++
![]() |
//---------------------------------------------------------------------------
|
||
|
// NEOPOP : Emulator as in Dreamland
|
||
|
//
|
||
|
// Copyright (c) 2001-2002 by neopop_uk
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
//---------------------------------------------------------------------------
|
||
|
// This program is free software; you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation; either version 2 of the License, or
|
||
|
// (at your option) any later version. See also the license.txt file for
|
||
|
// additional informations.
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
#include "neopop.h"
|
||
|
#include "mem.h"
|
||
|
#include "gfx.h"
|
||
|
#include "interrupt.h"
|
||
|
#include "Z80_interface.h"
|
||
|
#include "dma.h"
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
namespace MDFN_IEN_NGP
|
||
|
{
|
||
|
|
||
|
uint32 timer_hint;
|
||
|
static uint32 timer_clock[4];
|
||
|
static uint8 timer[4]; //Up-counters
|
||
|
static uint8 timer_threshold[4];
|
||
|
|
||
|
static uint8 TRUN;
|
||
|
static uint8 T01MOD, T23MOD;
|
||
|
static uint8 TRDC;
|
||
|
static uint8 TFFCR;
|
||
|
static uint8 HDMAStartVector[4];
|
||
|
|
||
|
static int32 ipending[24];
|
||
|
static int32 IntPrio[0xB]; // 0070-007a
|
||
|
static bool h_int, timer0, timer2;
|
||
|
|
||
|
// The way interrupt processing is set up is still written towards BIOS HLE emulation, which assumes
|
||
|
// that the interrupt handler will immediately call DI, clear the interrupt latch(so the interrupt won't happen again when interrupts are re-enabled),
|
||
|
// and then call the game's interrupt handler.
|
||
|
|
||
|
// We automatically clear the interrupt latch when the interrupt is accepted, and the interrupt latch is not checked every instruction,
|
||
|
// but only when: an EI/DI, POP SR, or RETI instruction occurs; after a write to an interrupt priority register occurs; and when
|
||
|
// a device sets the virual interrupt latch register, signaling it wants an interrupt.
|
||
|
|
||
|
// FIXME in the future if we ever add real bios support?
|
||
|
void interrupt(uint8 index)
|
||
|
{
|
||
|
//printf("INT: %d\n", index);
|
||
|
push32(pc);
|
||
|
push16(sr);
|
||
|
|
||
|
//Up the IFF
|
||
|
if (((sr & 0x7000) >> 12) < 7)
|
||
|
setStatusIFF(((sr & 0x7000) >> 12) + 1);
|
||
|
|
||
|
//Access the interrupt vector table to find the jump destination
|
||
|
pc = loadL(0x6FB8 + index * 4);
|
||
|
}
|
||
|
|
||
|
void set_interrupt(uint8 index, bool set)
|
||
|
{
|
||
|
assert(index < 24);
|
||
|
|
||
|
ipending[index] = set;
|
||
|
int_check_pending();
|
||
|
}
|
||
|
|
||
|
void int_check_pending(void)
|
||
|
{
|
||
|
uint8 prio;
|
||
|
uint8 curIFF = statusIFF();
|
||
|
|
||
|
// Technically, the BIOS should clear the interrupt pending flag by writing with IxxC set to "0", but
|
||
|
// we'll actually need to implement a BIOS to do that!
|
||
|
|
||
|
prio = IntPrio[0x1] & 0x07; // INT4
|
||
|
if (ipending[5] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[5] = 0;
|
||
|
interrupt(5);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = (IntPrio[0x1] & 0x70) >> 4; // INT5 (Z80)
|
||
|
if (ipending[6] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[6] = 0;
|
||
|
interrupt(6);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = IntPrio[0x3] & 0x07; // INTT0
|
||
|
if (ipending[7] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[7] = 0;
|
||
|
interrupt(7);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = (IntPrio[0x3] & 0x70) >> 4; // INTT1
|
||
|
if (ipending[8] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[8] = 0;
|
||
|
interrupt(8);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = (IntPrio[0x4] & 0x07); // INTT2
|
||
|
if (ipending[9] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[9] = 0;
|
||
|
interrupt(9);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = ((IntPrio[0x4] & 0x70) >> 4); // INTT3
|
||
|
if (ipending[10] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[10] = 0;
|
||
|
interrupt(10);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = (IntPrio[0x7] & 0x07); // INTRX0
|
||
|
if (ipending[11] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[11] = 0;
|
||
|
interrupt(11);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prio = ((IntPrio[0x7] & 0x70) >> 4); // INTTX0
|
||
|
if (ipending[12] && curIFF <= prio && prio && prio != 7)
|
||
|
{
|
||
|
ipending[12] = 0;
|
||
|
interrupt(12);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void int_write8(uint32 address, uint8 data)
|
||
|
{
|
||
|
switch (address)
|
||
|
{
|
||
|
case 0x71:
|
||
|
if (!(data & 0x08))
|
||
|
ipending[5] = 0;
|
||
|
if (!(data & 0x80))
|
||
|
ipending[6] = 0;
|
||
|
break;
|
||
|
case 0x73:
|
||
|
if (!(data & 0x08))
|
||
|
ipending[7] = 0;
|
||
|
if (!(data & 0x80))
|
||
|
ipending[8] = 0;
|
||
|
break;
|
||
|
case 0x74:
|
||
|
if (!(data & 0x08))
|
||
|
ipending[9] = 0;
|
||
|
if (!(data & 0x80))
|
||
|
ipending[10] = 0;
|
||
|
break;
|
||
|
case 0x77:
|
||
|
if (!(data & 0x08))
|
||
|
ipending[11] = 0;
|
||
|
if (!(data & 0x80))
|
||
|
ipending[12] = 0;
|
||
|
break;
|
||
|
case 0x7C:
|
||
|
HDMAStartVector[0] = data;
|
||
|
break;
|
||
|
case 0x7D:
|
||
|
HDMAStartVector[1] = data;
|
||
|
break;
|
||
|
case 0x7E:
|
||
|
HDMAStartVector[2] = data;
|
||
|
break;
|
||
|
case 0x7F:
|
||
|
HDMAStartVector[3] = data;
|
||
|
break;
|
||
|
}
|
||
|
if (address >= 0x70 && address <= 0x7A)
|
||
|
{
|
||
|
IntPrio[address - 0x70] = data;
|
||
|
int_check_pending();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint8 int_read8(uint32 address)
|
||
|
{
|
||
|
uint8 ret = 0;
|
||
|
switch (address)
|
||
|
{
|
||
|
case 0x71:
|
||
|
ret = ((ipending[5] ? 0x08 : 0x00) | (ipending[6] ? 0x80 : 0x00));
|
||
|
break;
|
||
|
case 0x73:
|
||
|
ret = ((ipending[7] ? 0x08 : 0x00) | (ipending[8] ? 0x80 : 0x00));
|
||
|
break;
|
||
|
case 0x74:
|
||
|
ret = ((ipending[9] ? 0x08 : 0x00) | (ipending[10] ? 0x80 : 0x00));
|
||
|
break;
|
||
|
case 0x77:
|
||
|
ret = ((ipending[11] ? 0x08 : 0x00) | (ipending[12] ? 0x80 : 0x00));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
void TestIntHDMA(int bios_num, int vec_num)
|
||
|
{
|
||
|
bool WasDMA = 0;
|
||
|
|
||
|
if (HDMAStartVector[0] == vec_num)
|
||
|
{
|
||
|
WasDMA = 1;
|
||
|
DMA_update(0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (HDMAStartVector[1] == vec_num)
|
||
|
{
|
||
|
WasDMA = 1;
|
||
|
DMA_update(1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (HDMAStartVector[2] == vec_num)
|
||
|
{
|
||
|
WasDMA = 1;
|
||
|
DMA_update(2);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (HDMAStartVector[3] == vec_num)
|
||
|
{
|
||
|
WasDMA = 1;
|
||
|
DMA_update(3);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!WasDMA)
|
||
|
set_interrupt(bios_num, TRUE);
|
||
|
}
|
||
|
|
||
|
extern int32 ngpc_soundTS;
|
||
|
extern bool NGPFrameSkip;
|
||
|
|
||
|
bool updateTimers(MDFN_Surface *surface, int cputicks)
|
||
|
{
|
||
|
bool ret = 0;
|
||
|
|
||
|
ngpc_soundTS += cputicks;
|
||
|
//increment H-INT timer
|
||
|
timer_hint += cputicks;
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//End of scanline / Start of Next one
|
||
|
if (timer_hint >= TIMER_HINT_RATE)
|
||
|
{
|
||
|
uint8 data;
|
||
|
|
||
|
// ============= END OF CURRENT SCANLINE =============
|
||
|
|
||
|
h_int = NGPGfx->hint();
|
||
|
ret = NGPGfx->draw(surface, NGPFrameSkip);
|
||
|
|
||
|
// ============= START OF NEXT SCANLINE =============
|
||
|
|
||
|
timer_hint -= TIMER_HINT_RATE; //Start of next scanline
|
||
|
|
||
|
//Comms. Read interrupt
|
||
|
if ((COMMStatus & 1) == 0 && system_comms_poll(&data))
|
||
|
{
|
||
|
storeB(0x50, data);
|
||
|
TestIntHDMA(12, 0x19);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//Tick the Clock Generator
|
||
|
timer_clock[0] += cputicks;
|
||
|
timer_clock[1] += cputicks;
|
||
|
|
||
|
timer0 = FALSE; //Clear the timer0 tick, for timer1 chain mode.
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//Run Timer 0 (TRUN)?
|
||
|
if ((TRUN & 0x01))
|
||
|
{
|
||
|
//T01MOD
|
||
|
switch (T01MOD & 0x03)
|
||
|
{
|
||
|
case 0:
|
||
|
if (h_int) //Horizontal interrupt trigger
|
||
|
{
|
||
|
timer[0]++;
|
||
|
|
||
|
timer_clock[0] = 0;
|
||
|
h_int = FALSE; // Stop h_int remaining active
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
while (timer_clock[0] >= TIMER_T1_RATE)
|
||
|
{
|
||
|
timer[0]++;
|
||
|
timer_clock[0] -= TIMER_T1_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
while (timer_clock[0] >= TIMER_T4_RATE)
|
||
|
{
|
||
|
timer[0]++;
|
||
|
timer_clock[0] -= TIMER_T4_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
while (timer_clock[0] >= TIMER_T16_RATE)
|
||
|
{
|
||
|
timer[0]++;
|
||
|
timer_clock[0] -= TIMER_T16_RATE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//Threshold check
|
||
|
if (timer_threshold[0] && timer[0] >= timer_threshold[0])
|
||
|
{
|
||
|
timer[0] = 0;
|
||
|
timer0 = TRUE;
|
||
|
|
||
|
TestIntHDMA(7, 0x10);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//Run Timer 1 (TRUN)?
|
||
|
if ((TRUN & 0x02))
|
||
|
{
|
||
|
//T01MOD
|
||
|
switch ((T01MOD & 0x0C) >> 2)
|
||
|
{
|
||
|
case 0:
|
||
|
if (timer0) //Timer 0 chain mode.
|
||
|
{
|
||
|
timer[1] += timer0;
|
||
|
timer_clock[1] = 0;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
while (timer_clock[1] >= TIMER_T1_RATE)
|
||
|
{
|
||
|
timer[1]++;
|
||
|
timer_clock[1] -= TIMER_T1_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
while (timer_clock[1] >= TIMER_T16_RATE)
|
||
|
{
|
||
|
timer[1]++;
|
||
|
timer_clock[1] -= TIMER_T16_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
while (timer_clock[1] >= TIMER_T256_RATE)
|
||
|
{
|
||
|
timer[1]++;
|
||
|
timer_clock[1] -= TIMER_T256_RATE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//Threshold check
|
||
|
if (timer_threshold[1] && timer[1] >= timer_threshold[1])
|
||
|
{
|
||
|
timer[1] = 0;
|
||
|
|
||
|
TestIntHDMA(8, 0x11);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//Tick the Clock Generator
|
||
|
timer_clock[2] += cputicks;
|
||
|
timer_clock[3] += cputicks;
|
||
|
|
||
|
timer2 = FALSE; //Clear the timer2 tick, for timer3 chain mode.
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//Run Timer 2 (TRUN)?
|
||
|
if ((TRUN & 0x04))
|
||
|
{
|
||
|
//T23MOD
|
||
|
switch (T23MOD & 0x03)
|
||
|
{
|
||
|
case 0: // -
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
while (timer_clock[2] >= TIMER_T1_RATE / 2) // Kludge :(
|
||
|
{
|
||
|
timer[2]++;
|
||
|
timer_clock[2] -= TIMER_T1_RATE / 2;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
while (timer_clock[2] >= TIMER_T4_RATE)
|
||
|
{
|
||
|
timer[2]++;
|
||
|
timer_clock[2] -= TIMER_T4_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
while (timer_clock[2] >= TIMER_T16_RATE)
|
||
|
{
|
||
|
timer[2]++;
|
||
|
timer_clock[2] -= TIMER_T16_RATE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//Threshold check
|
||
|
if (timer_threshold[2] && timer[2] >= timer_threshold[2])
|
||
|
{
|
||
|
timer[2] = 0;
|
||
|
timer2 = TRUE;
|
||
|
|
||
|
TestIntHDMA(9, 0x12);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=======================
|
||
|
|
||
|
//Run Timer 3 (TRUN)?
|
||
|
if ((TRUN & 0x08))
|
||
|
{
|
||
|
//T23MOD
|
||
|
switch ((T23MOD & 0x0C) >> 2)
|
||
|
{
|
||
|
case 0:
|
||
|
if (timer2) //Timer 2 chain mode.
|
||
|
{
|
||
|
timer[3] += timer2;
|
||
|
timer_clock[3] = 0;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
while (timer_clock[3] >= TIMER_T1_RATE)
|
||
|
{
|
||
|
timer[3]++;
|
||
|
timer_clock[3] -= TIMER_T1_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
while (timer_clock[3] >= TIMER_T16_RATE)
|
||
|
{
|
||
|
timer[3]++;
|
||
|
timer_clock[3] -= TIMER_T16_RATE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
while (timer_clock[3] >= TIMER_T256_RATE)
|
||
|
{
|
||
|
timer[3]++;
|
||
|
timer_clock[3] -= TIMER_T256_RATE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//Threshold check
|
||
|
if (timer_threshold[3] && timer[3] >= timer_threshold[3])
|
||
|
{
|
||
|
timer[3] = 0;
|
||
|
|
||
|
Z80_irq();
|
||
|
TestIntHDMA(10, 0x13);
|
||
|
}
|
||
|
}
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
void reset_timers(void)
|
||
|
{
|
||
|
timer_hint = 0;
|
||
|
|
||
|
memset(timer, 0, sizeof(timer));
|
||
|
memset(timer_clock, 0, sizeof(timer_clock));
|
||
|
memset(timer_threshold, 0, sizeof(timer_threshold));
|
||
|
|
||
|
timer0 = FALSE;
|
||
|
timer2 = FALSE;
|
||
|
}
|
||
|
|
||
|
void reset_int(void)
|
||
|
{
|
||
|
TRUN = 0;
|
||
|
T01MOD = 0;
|
||
|
T23MOD = 0;
|
||
|
TRDC = 0;
|
||
|
TFFCR = 0;
|
||
|
|
||
|
memset(HDMAStartVector, 0, sizeof(HDMAStartVector));
|
||
|
memset(ipending, 0, sizeof(ipending));
|
||
|
memset(IntPrio, 0, sizeof(IntPrio));
|
||
|
|
||
|
h_int = FALSE;
|
||
|
}
|
||
|
|
||
|
void timer_write8(uint32 address, uint8 data)
|
||
|
{
|
||
|
switch (address)
|
||
|
{
|
||
|
case 0x20:
|
||
|
TRUN = data;
|
||
|
if ((TRUN & 0x01) == 0)
|
||
|
timer[0] = 0;
|
||
|
if ((TRUN & 0x02) == 0)
|
||
|
timer[1] = 0;
|
||
|
if ((TRUN & 0x04) == 0)
|
||
|
timer[2] = 0;
|
||
|
if ((TRUN & 0x08) == 0)
|
||
|
timer[3] = 0;
|
||
|
break;
|
||
|
case 0x22:
|
||
|
timer_threshold[0] = data;
|
||
|
break;
|
||
|
case 0x23:
|
||
|
timer_threshold[1] = data;
|
||
|
break;
|
||
|
case 0x24:
|
||
|
T01MOD = data;
|
||
|
break;
|
||
|
case 0x25:
|
||
|
TFFCR = data & 0x33;
|
||
|
break;
|
||
|
case 0x26:
|
||
|
timer_threshold[2] = data;
|
||
|
break;
|
||
|
case 0x27:
|
||
|
timer_threshold[3] = data;
|
||
|
break;
|
||
|
case 0x28:
|
||
|
T23MOD = data;
|
||
|
break;
|
||
|
case 0x29:
|
||
|
TRDC = data & 0x3;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint8 timer_read8(uint32 address)
|
||
|
{
|
||
|
uint8 ret = 0;
|
||
|
|
||
|
switch (address)
|
||
|
{
|
||
|
//default: printf("Baaaad: %08x\n", address); break;
|
||
|
// Cool boarders is stupid and tries to read from a write-only register >_<
|
||
|
// Returning 4 makes the game run ok, so 4 it is!
|
||
|
default:
|
||
|
ret = 0x4;
|
||
|
break;
|
||
|
case 0x20:
|
||
|
ret = TRUN;
|
||
|
break;
|
||
|
case 0x29:
|
||
|
ret = TRDC;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//printf("UNK B R: %08x\n", address);
|
||
|
return (ret);
|
||
|
}
|
||
|
}
|