//--------------------------------------------------------------------------- // 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); } }