mirror of
https://github.com/Oibaf66/frodo-wii.git
synced 2024-11-23 03:49:26 +01:00
764 lines
16 KiB
C++
764 lines
16 KiB
C++
/*
|
|
* CIA_SC.cpp - Single-cycle 6526 emulation
|
|
*
|
|
* Frodo (C) 1994-1997,2002 Christian Bauer
|
|
*
|
|
|
|
*
|
|
* Notes:
|
|
* ------
|
|
*
|
|
* - The Emulate() function is called for every emulated Phi2
|
|
* clock cycle. It counts down the timers and triggers
|
|
* interrupts if necessary.
|
|
* - The TOD clocks are counted by CountTOD() during the VBlank, so
|
|
* the input frequency is 50Hz
|
|
* - The fields KeyMatrix and RevMatrix contain one bit for each
|
|
* key on the C64 keyboard (0: key pressed, 1: key released).
|
|
* KeyMatrix is used for normal keyboard polling (PRA->PRB),
|
|
* RevMatrix for reversed polling (PRB->PRA).
|
|
*
|
|
* Incompatibilities:
|
|
* ------------------
|
|
*
|
|
* - The TOD clock should not be stopped on a read access, but be
|
|
* latched
|
|
* - The SDR interrupt is faked
|
|
* - Some small incompatibilities with the timers
|
|
*/
|
|
|
|
#include "sysdeps.h"
|
|
|
|
#include "CIA.h"
|
|
#include "CPUC64.h"
|
|
#include "CPU1541.h"
|
|
#include "VIC.h"
|
|
#include "Prefs.h"
|
|
|
|
|
|
// Timer states
|
|
enum {
|
|
T_STOP,
|
|
T_WAIT_THEN_COUNT,
|
|
T_LOAD_THEN_STOP,
|
|
T_LOAD_THEN_COUNT,
|
|
T_LOAD_THEN_WAIT_THEN_COUNT,
|
|
T_COUNT,
|
|
T_COUNT_THEN_STOP,
|
|
};
|
|
|
|
|
|
/*
|
|
* Constructors
|
|
*/
|
|
|
|
MOS6526::MOS6526(MOS6510 *CPU) : the_cpu(CPU) {}
|
|
MOS6526_1::MOS6526_1(MOS6510 *CPU, MOS6569 *VIC) : MOS6526(CPU), the_vic(VIC) {}
|
|
MOS6526_2::MOS6526_2(MOS6510 *CPU, MOS6569 *VIC, MOS6502_1541 *CPU1541) : MOS6526(CPU), the_vic(VIC), the_cpu_1541(CPU1541) {}
|
|
|
|
|
|
/*
|
|
* Reset the CIA
|
|
*/
|
|
|
|
void MOS6526::Reset(void)
|
|
{
|
|
pra = prb = ddra = ddrb = 0;
|
|
|
|
ta = tb = 0xffff;
|
|
latcha = latchb = 1;
|
|
|
|
tod_10ths = tod_sec = tod_min = tod_hr = 0;
|
|
alm_10ths = alm_sec = alm_min = alm_hr = 0;
|
|
|
|
sdr = icr = cra = crb = int_mask = 0;
|
|
|
|
tod_halt = false;
|
|
tod_divider = 0;
|
|
|
|
ta_cnt_phi2 = tb_cnt_phi2 = tb_cnt_ta = false;
|
|
|
|
ta_irq_next_cycle = tb_irq_next_cycle = false;
|
|
ta_state = tb_state = T_STOP;
|
|
}
|
|
|
|
void MOS6526_1::Reset(void)
|
|
{
|
|
MOS6526::Reset();
|
|
|
|
// Clear keyboard matrix and joystick states
|
|
for (int i=0; i<8; i++)
|
|
KeyMatrix[i] = RevMatrix[i] = 0xff;
|
|
|
|
Joystick1 = Joystick2 = 0xff;
|
|
prev_lp = 0x10;
|
|
}
|
|
|
|
void MOS6526_2::Reset(void)
|
|
{
|
|
MOS6526::Reset();
|
|
|
|
// VA14/15 = 0
|
|
the_vic->ChangedVA(0);
|
|
|
|
// IEC
|
|
IECLines = 0xd0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get CIA state
|
|
*/
|
|
|
|
void MOS6526::GetState(MOS6526State *cs)
|
|
{
|
|
cs->pra = pra;
|
|
cs->prb = prb;
|
|
cs->ddra = ddra;
|
|
cs->ddrb = ddrb;
|
|
|
|
cs->ta_lo = ta & 0xff;
|
|
cs->ta_hi = ta >> 8;
|
|
cs->tb_lo = tb & 0xff;
|
|
cs->tb_hi = tb >> 8;
|
|
cs->latcha = latcha;
|
|
cs->latchb = latchb;
|
|
cs->cra = cra;
|
|
cs->crb = crb;
|
|
|
|
cs->tod_10ths = tod_10ths;
|
|
cs->tod_sec = tod_sec;
|
|
cs->tod_min = tod_min;
|
|
cs->tod_hr = tod_hr;
|
|
cs->alm_10ths = alm_10ths;
|
|
cs->alm_sec = alm_sec;
|
|
cs->alm_min = alm_min;
|
|
cs->alm_hr = alm_hr;
|
|
|
|
cs->sdr = sdr;
|
|
|
|
cs->int_data = icr;
|
|
cs->int_mask = int_mask;
|
|
}
|
|
|
|
|
|
/*
|
|
* Restore CIA state
|
|
*/
|
|
|
|
void MOS6526::SetState(MOS6526State *cs)
|
|
{
|
|
pra = cs->pra;
|
|
prb = cs->prb;
|
|
ddra = cs->ddra;
|
|
ddrb = cs->ddrb;
|
|
|
|
ta = (cs->ta_hi << 8) | cs->ta_lo;
|
|
tb = (cs->tb_hi << 8) | cs->tb_lo;
|
|
latcha = cs->latcha;
|
|
latchb = cs->latchb;
|
|
cra = cs->cra;
|
|
crb = cs->crb;
|
|
|
|
tod_10ths = cs->tod_10ths;
|
|
tod_sec = cs->tod_sec;
|
|
tod_min = cs->tod_min;
|
|
tod_hr = cs->tod_hr;
|
|
alm_10ths = cs->alm_10ths;
|
|
alm_sec = cs->alm_sec;
|
|
alm_min = cs->alm_min;
|
|
alm_hr = cs->alm_hr;
|
|
|
|
sdr = cs->sdr;
|
|
|
|
icr = cs->int_data;
|
|
int_mask = cs->int_mask;
|
|
|
|
tod_halt = false;
|
|
ta_cnt_phi2 = ((cra & 0x20) == 0x00);
|
|
tb_cnt_phi2 = ((crb & 0x60) == 0x00);
|
|
tb_cnt_ta = ((crb & 0x60) == 0x40);
|
|
|
|
ta_state = (cra & 1) ? T_COUNT : T_STOP;
|
|
tb_state = (crb & 1) ? T_COUNT : T_STOP;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read from register (CIA 1)
|
|
*/
|
|
|
|
uint8 MOS6526_1::ReadRegister(uint16 adr)
|
|
{
|
|
switch (adr) {
|
|
case 0x00: {
|
|
uint8 ret = pra | ~ddra, tst = (prb | ~ddrb) & Joystick1;
|
|
if (!(tst & 0x01)) ret &= RevMatrix[0]; // AND all active columns
|
|
if (!(tst & 0x02)) ret &= RevMatrix[1];
|
|
if (!(tst & 0x04)) ret &= RevMatrix[2];
|
|
if (!(tst & 0x08)) ret &= RevMatrix[3];
|
|
if (!(tst & 0x10)) ret &= RevMatrix[4];
|
|
if (!(tst & 0x20)) ret &= RevMatrix[5];
|
|
if (!(tst & 0x40)) ret &= RevMatrix[6];
|
|
if (!(tst & 0x80)) ret &= RevMatrix[7];
|
|
return ret & Joystick2;
|
|
}
|
|
case 0x01: {
|
|
uint8 ret = ~ddrb, tst = (pra | ~ddra) & Joystick2;
|
|
if (!(tst & 0x01)) ret &= KeyMatrix[0]; // AND all active rows
|
|
if (!(tst & 0x02)) ret &= KeyMatrix[1];
|
|
if (!(tst & 0x04)) ret &= KeyMatrix[2];
|
|
if (!(tst & 0x08)) ret &= KeyMatrix[3];
|
|
if (!(tst & 0x10)) ret &= KeyMatrix[4];
|
|
if (!(tst & 0x20)) ret &= KeyMatrix[5];
|
|
if (!(tst & 0x40)) ret &= KeyMatrix[6];
|
|
if (!(tst & 0x80)) ret &= KeyMatrix[7];
|
|
return (ret | (prb & ddrb)) & Joystick1;
|
|
}
|
|
case 0x02: return ddra;
|
|
case 0x03: return ddrb;
|
|
case 0x04: return ta;
|
|
case 0x05: return ta >> 8;
|
|
case 0x06: return tb;
|
|
case 0x07: return tb >> 8;
|
|
case 0x08: tod_halt = false; return tod_10ths;
|
|
case 0x09: return tod_sec;
|
|
case 0x0a: return tod_min;
|
|
case 0x0b: tod_halt = true; return tod_hr;
|
|
case 0x0c: return sdr;
|
|
case 0x0d: {
|
|
uint8 ret = icr; // Read and clear ICR
|
|
icr = 0;
|
|
the_cpu->ClearCIAIRQ(); // Clear IRQ
|
|
return ret;
|
|
}
|
|
case 0x0e: return cra;
|
|
case 0x0f: return crb;
|
|
}
|
|
return 0; // Can't happen
|
|
}
|
|
|
|
|
|
/*
|
|
* Read from register (CIA 2)
|
|
*/
|
|
|
|
uint8 MOS6526_2::ReadRegister(uint16 adr)
|
|
{
|
|
switch (adr) {
|
|
case 0x00:
|
|
return (pra | ~ddra) & 0x3f
|
|
| IECLines & the_cpu_1541->IECLines;
|
|
case 0x01: return prb | ~ddrb;
|
|
case 0x02: return ddra;
|
|
case 0x03: return ddrb;
|
|
case 0x04: return ta;
|
|
case 0x05: return ta >> 8;
|
|
case 0x06: return tb;
|
|
case 0x07: return tb >> 8;
|
|
case 0x08: tod_halt = false; return tod_10ths;
|
|
case 0x09: return tod_sec;
|
|
case 0x0a: return tod_min;
|
|
case 0x0b: tod_halt = true; return tod_hr;
|
|
case 0x0c: return sdr;
|
|
case 0x0d: {
|
|
uint8 ret = icr; // Read and clear ICR
|
|
icr = 0;
|
|
the_cpu->ClearNMI();
|
|
return ret;
|
|
}
|
|
case 0x0e: return cra;
|
|
case 0x0f: return crb;
|
|
}
|
|
return 0; // Can't happen
|
|
}
|
|
|
|
|
|
/*
|
|
* Write to register (CIA 1)
|
|
*/
|
|
|
|
// Write to port B, check for lightpen interrupt
|
|
inline void MOS6526_1::check_lp(void)
|
|
{
|
|
if ((prb | ~ddrb) & 0x10 != prev_lp)
|
|
the_vic->TriggerLightpen();
|
|
prev_lp = (prb | ~ddrb) & 0x10;
|
|
}
|
|
|
|
void MOS6526_1::WriteRegister(uint16 adr, uint8 byte)
|
|
{
|
|
switch (adr) {
|
|
case 0x0: pra = byte; break;
|
|
case 0x1:
|
|
prb = byte;
|
|
check_lp();
|
|
break;
|
|
case 0x2: ddra = byte; break;
|
|
case 0x3:
|
|
ddrb = byte;
|
|
check_lp();
|
|
break;
|
|
|
|
case 0x4: latcha = (latcha & 0xff00) | byte; break;
|
|
case 0x5:
|
|
latcha = (latcha & 0xff) | (byte << 8);
|
|
if (!(cra & 1)) // Reload timer if stopped
|
|
ta = latcha;
|
|
break;
|
|
|
|
case 0x6: latchb = (latchb & 0xff00) | byte; break;
|
|
case 0x7:
|
|
latchb = (latchb & 0xff) | (byte << 8);
|
|
if (!(crb & 1)) // Reload timer if stopped
|
|
tb = latchb;
|
|
break;
|
|
|
|
case 0x8:
|
|
if (crb & 0x80)
|
|
alm_10ths = byte & 0x0f;
|
|
else
|
|
tod_10ths = byte & 0x0f;
|
|
break;
|
|
case 0x9:
|
|
if (crb & 0x80)
|
|
alm_sec = byte & 0x7f;
|
|
else
|
|
tod_sec = byte & 0x7f;
|
|
break;
|
|
case 0xa:
|
|
if (crb & 0x80)
|
|
alm_min = byte & 0x7f;
|
|
else
|
|
tod_min = byte & 0x7f;
|
|
break;
|
|
case 0xb:
|
|
if (crb & 0x80)
|
|
alm_hr = byte & 0x9f;
|
|
else
|
|
tod_hr = byte & 0x9f;
|
|
break;
|
|
|
|
case 0xc:
|
|
sdr = byte;
|
|
TriggerInterrupt(8); // Fake SDR interrupt for programs that need it
|
|
break;
|
|
|
|
case 0xd:
|
|
if (byte & 0x80)
|
|
int_mask |= byte & 0x7f;
|
|
else
|
|
int_mask &= ~byte;
|
|
if (icr & int_mask & 0x1f) { // Trigger IRQ if pending
|
|
icr |= 0x80;
|
|
the_cpu->TriggerCIAIRQ();
|
|
}
|
|
break;
|
|
|
|
case 0xe:
|
|
has_new_cra = true; // Delay write by 1 cycle
|
|
new_cra = byte;
|
|
ta_cnt_phi2 = ((byte & 0x20) == 0x00);
|
|
break;
|
|
|
|
case 0xf:
|
|
has_new_crb = true; // Delay write by 1 cycle
|
|
new_crb = byte;
|
|
tb_cnt_phi2 = ((byte & 0x60) == 0x00);
|
|
tb_cnt_ta = ((byte & 0x60) == 0x40);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Write to register (CIA 2)
|
|
*/
|
|
|
|
void MOS6526_2::WriteRegister(uint16 adr, uint8 byte)
|
|
{
|
|
switch (adr) {
|
|
case 0x0:{
|
|
pra = byte;
|
|
the_vic->ChangedVA(~(pra | ~ddra) & 3);
|
|
uint8 old_lines = IECLines;
|
|
IECLines = (~byte << 2) & 0x80 // DATA
|
|
| (~byte << 2) & 0x40 // CLK
|
|
| (~byte << 1) & 0x10; // ATN
|
|
if ((IECLines ^ old_lines) & 0x10) { // ATN changed
|
|
the_cpu_1541->NewATNState();
|
|
if (old_lines & 0x10) // ATN 1->0
|
|
the_cpu_1541->IECInterrupt();
|
|
}
|
|
break;
|
|
}
|
|
case 0x1: prb = byte; break;
|
|
|
|
case 0x2:
|
|
ddra = byte;
|
|
the_vic->ChangedVA(~(pra | ~ddra) & 3);
|
|
break;
|
|
case 0x3: ddrb = byte; break;
|
|
|
|
case 0x4: latcha = (latcha & 0xff00) | byte; break;
|
|
case 0x5:
|
|
latcha = (latcha & 0xff) | (byte << 8);
|
|
if (!(cra & 1)) // Reload timer if stopped
|
|
ta = latcha;
|
|
break;
|
|
|
|
case 0x6: latchb = (latchb & 0xff00) | byte; break;
|
|
case 0x7:
|
|
latchb = (latchb & 0xff) | (byte << 8);
|
|
if (!(crb & 1)) // Reload timer if stopped
|
|
tb = latchb;
|
|
break;
|
|
|
|
case 0x8:
|
|
if (crb & 0x80)
|
|
alm_10ths = byte & 0x0f;
|
|
else
|
|
tod_10ths = byte & 0x0f;
|
|
break;
|
|
case 0x9:
|
|
if (crb & 0x80)
|
|
alm_sec = byte & 0x7f;
|
|
else
|
|
tod_sec = byte & 0x7f;
|
|
break;
|
|
case 0xa:
|
|
if (crb & 0x80)
|
|
alm_min = byte & 0x7f;
|
|
else
|
|
tod_min = byte & 0x7f;
|
|
break;
|
|
case 0xb:
|
|
if (crb & 0x80)
|
|
alm_hr = byte & 0x9f;
|
|
else
|
|
tod_hr = byte & 0x9f;
|
|
break;
|
|
|
|
case 0xc:
|
|
sdr = byte;
|
|
TriggerInterrupt(8); // Fake SDR interrupt for programs that need it
|
|
break;
|
|
|
|
case 0xd:
|
|
if (byte & 0x80)
|
|
int_mask |= byte & 0x7f;
|
|
else
|
|
int_mask &= ~byte;
|
|
if (icr & int_mask & 0x1f) { // Trigger NMI if pending
|
|
icr |= 0x80;
|
|
the_cpu->TriggerNMI();
|
|
}
|
|
break;
|
|
|
|
case 0xe:
|
|
has_new_cra = true; // Delay write by 1 cycle
|
|
new_cra = byte;
|
|
ta_cnt_phi2 = ((byte & 0x20) == 0x00);
|
|
break;
|
|
|
|
case 0xf:
|
|
has_new_crb = true; // Delay write by 1 cycle
|
|
new_crb = byte;
|
|
tb_cnt_phi2 = ((byte & 0x60) == 0x00);
|
|
tb_cnt_ta = ((byte & 0x60) == 0x40);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Emulate CIA for one cycle/raster line
|
|
*/
|
|
|
|
void MOS6526::EmulateCycle(void)
|
|
{
|
|
bool ta_underflow = false;
|
|
|
|
// Timer A state machine
|
|
switch (ta_state) {
|
|
case T_WAIT_THEN_COUNT:
|
|
ta_state = T_COUNT; // fall through
|
|
case T_STOP:
|
|
goto ta_idle;
|
|
case T_LOAD_THEN_STOP:
|
|
ta_state = T_STOP;
|
|
ta = latcha; // Reload timer
|
|
goto ta_idle;
|
|
case T_LOAD_THEN_COUNT:
|
|
ta_state = T_COUNT;
|
|
ta = latcha; // Reload timer
|
|
goto ta_idle;
|
|
case T_LOAD_THEN_WAIT_THEN_COUNT:
|
|
ta_state = T_WAIT_THEN_COUNT;
|
|
if (ta == 1)
|
|
goto ta_interrupt; // Interrupt if timer == 1
|
|
else {
|
|
ta = latcha; // Reload timer
|
|
goto ta_idle;
|
|
}
|
|
case T_COUNT:
|
|
goto ta_count;
|
|
case T_COUNT_THEN_STOP:
|
|
ta_state = T_STOP;
|
|
goto ta_count;
|
|
}
|
|
|
|
// Count timer A
|
|
ta_count:
|
|
if (ta_cnt_phi2)
|
|
if (!ta || !--ta) { // Decrement timer, underflow?
|
|
if (ta_state != T_STOP) {
|
|
ta_interrupt:
|
|
ta = latcha; // Reload timer
|
|
ta_irq_next_cycle = true; // Trigger interrupt in next cycle
|
|
icr |= 1; // But set ICR bit now
|
|
|
|
if (cra & 8) { // One-shot?
|
|
cra &= 0xfe; // Yes, stop timer
|
|
new_cra &= 0xfe;
|
|
ta_state = T_LOAD_THEN_STOP; // Reload in next cycle
|
|
} else
|
|
ta_state = T_LOAD_THEN_COUNT; // No, delay one cycle (and reload)
|
|
}
|
|
ta_underflow = true;
|
|
}
|
|
|
|
// Delayed write to CRA?
|
|
ta_idle:
|
|
if (has_new_cra) {
|
|
switch (ta_state) {
|
|
case T_STOP:
|
|
case T_LOAD_THEN_STOP:
|
|
if (new_cra & 1) { // Timer started, wasn't running
|
|
if (new_cra & 0x10) // Force load
|
|
ta_state = T_LOAD_THEN_WAIT_THEN_COUNT;
|
|
else // No force load
|
|
ta_state = T_WAIT_THEN_COUNT;
|
|
} else { // Timer stopped, was already stopped
|
|
if (new_cra & 0x10) // Force load
|
|
ta_state = T_LOAD_THEN_STOP;
|
|
}
|
|
break;
|
|
case T_COUNT:
|
|
if (new_cra & 1) { // Timer started, was already running
|
|
if (new_cra & 0x10) // Force load
|
|
ta_state = T_LOAD_THEN_WAIT_THEN_COUNT;
|
|
} else { // Timer stopped, was running
|
|
if (new_cra & 0x10) // Force load
|
|
ta_state = T_LOAD_THEN_STOP;
|
|
else // No force load
|
|
ta_state = T_COUNT_THEN_STOP;
|
|
}
|
|
break;
|
|
case T_LOAD_THEN_COUNT:
|
|
case T_WAIT_THEN_COUNT:
|
|
if (new_cra & 1) {
|
|
if (new_cra & 8) { // One-shot?
|
|
new_cra &= 0xfe; // Yes, stop timer
|
|
ta_state = T_STOP;
|
|
} else if (new_cra & 0x10) // Force load
|
|
ta_state = T_LOAD_THEN_WAIT_THEN_COUNT;
|
|
} else {
|
|
ta_state = T_STOP;
|
|
}
|
|
break;
|
|
}
|
|
cra = new_cra & 0xef;
|
|
has_new_cra = false;
|
|
}
|
|
|
|
// Timer B state machine
|
|
switch (tb_state) {
|
|
case T_WAIT_THEN_COUNT:
|
|
tb_state = T_COUNT; // fall through
|
|
case T_STOP:
|
|
goto tb_idle;
|
|
case T_LOAD_THEN_STOP:
|
|
tb_state = T_STOP;
|
|
tb = latchb; // Reload timer
|
|
goto tb_idle;
|
|
case T_LOAD_THEN_COUNT:
|
|
tb_state = T_COUNT;
|
|
tb = latchb; // Reload timer
|
|
goto ta_idle;
|
|
case T_LOAD_THEN_WAIT_THEN_COUNT:
|
|
tb_state = T_WAIT_THEN_COUNT;
|
|
if (tb == 1)
|
|
goto tb_interrupt; // Interrupt if timer == 1
|
|
else {
|
|
tb = latchb; // Reload timer
|
|
goto tb_idle;
|
|
}
|
|
case T_COUNT:
|
|
goto tb_count;
|
|
case T_COUNT_THEN_STOP:
|
|
tb_state = T_STOP;
|
|
goto tb_count;
|
|
}
|
|
|
|
// Count timer B
|
|
tb_count:
|
|
if (tb_cnt_phi2 || (tb_cnt_ta && ta_underflow))
|
|
if (!tb || !--tb) { // Decrement timer, underflow?
|
|
if (tb_state != T_STOP) {
|
|
tb_interrupt:
|
|
tb = latchb; // Reload timer
|
|
tb_irq_next_cycle = true; // Trigger interrupt in next cycle
|
|
icr |= 2; // But set ICR bit now
|
|
|
|
if (crb & 8) { // One-shot?
|
|
crb &= 0xfe; // Yes, stop timer
|
|
new_crb &= 0xfe;
|
|
tb_state = T_LOAD_THEN_STOP; // Reload in next cycle
|
|
} else
|
|
tb_state = T_LOAD_THEN_COUNT; // No, delay one cycle (and reload)
|
|
}
|
|
}
|
|
|
|
// Delayed write to CRB?
|
|
tb_idle:
|
|
if (has_new_crb) {
|
|
switch (tb_state) {
|
|
case T_STOP:
|
|
case T_LOAD_THEN_STOP:
|
|
if (new_crb & 1) { // Timer started, wasn't running
|
|
if (new_crb & 0x10) // Force load
|
|
tb_state = T_LOAD_THEN_WAIT_THEN_COUNT;
|
|
else // No force load
|
|
tb_state = T_WAIT_THEN_COUNT;
|
|
} else { // Timer stopped, was already stopped
|
|
if (new_crb & 0x10) // Force load
|
|
tb_state = T_LOAD_THEN_STOP;
|
|
}
|
|
break;
|
|
case T_COUNT:
|
|
if (new_crb & 1) { // Timer started, was already running
|
|
if (new_crb & 0x10) // Force load
|
|
tb_state = T_LOAD_THEN_WAIT_THEN_COUNT;
|
|
} else { // Timer stopped, was running
|
|
if (new_crb & 0x10) // Force load
|
|
tb_state = T_LOAD_THEN_STOP;
|
|
else // No force load
|
|
tb_state = T_COUNT_THEN_STOP;
|
|
}
|
|
break;
|
|
case T_LOAD_THEN_COUNT:
|
|
case T_WAIT_THEN_COUNT:
|
|
if (new_crb & 1) {
|
|
if (new_crb & 8) { // One-shot?
|
|
new_crb &= 0xfe; // Yes, stop timer
|
|
tb_state = T_STOP;
|
|
} else if (new_crb & 0x10) // Force load
|
|
tb_state = T_LOAD_THEN_WAIT_THEN_COUNT;
|
|
} else {
|
|
tb_state = T_STOP;
|
|
}
|
|
break;
|
|
}
|
|
crb = new_crb & 0xef;
|
|
has_new_crb = false;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Count CIA TOD clock (called during VBlank)
|
|
*/
|
|
|
|
void MOS6526::CountTOD(void)
|
|
{
|
|
uint8 lo, hi;
|
|
|
|
// Decrement frequency divider
|
|
if (tod_divider)
|
|
tod_divider--;
|
|
else {
|
|
|
|
// Reload divider according to 50/60 Hz flag
|
|
if (cra & 0x80)
|
|
tod_divider = 4;
|
|
else
|
|
tod_divider = 5;
|
|
|
|
// 1/10 seconds
|
|
tod_10ths++;
|
|
if (tod_10ths > 9) {
|
|
tod_10ths = 0;
|
|
|
|
// Seconds
|
|
lo = (tod_sec & 0x0f) + 1;
|
|
hi = tod_sec >> 4;
|
|
if (lo > 9) {
|
|
lo = 0;
|
|
hi++;
|
|
}
|
|
if (hi > 5) {
|
|
tod_sec = 0;
|
|
|
|
// Minutes
|
|
lo = (tod_min & 0x0f) + 1;
|
|
hi = tod_min >> 4;
|
|
if (lo > 9) {
|
|
lo = 0;
|
|
hi++;
|
|
}
|
|
if (hi > 5) {
|
|
tod_min = 0;
|
|
|
|
// Hours
|
|
lo = (tod_hr & 0x0f) + 1;
|
|
hi = (tod_hr >> 4) & 1;
|
|
tod_hr &= 0x80; // Keep AM/PM flag
|
|
if (lo > 9) {
|
|
lo = 0;
|
|
hi++;
|
|
}
|
|
tod_hr |= (hi << 4) | lo;
|
|
if ((tod_hr & 0x1f) > 0x11)
|
|
tod_hr = tod_hr & 0x80 ^ 0x80;
|
|
} else
|
|
tod_min = (hi << 4) | lo;
|
|
} else
|
|
tod_sec = (hi << 4) | lo;
|
|
}
|
|
|
|
// Alarm time reached? Trigger interrupt if enabled
|
|
if (tod_10ths == alm_10ths && tod_sec == alm_sec &&
|
|
tod_min == alm_min && tod_hr == alm_hr)
|
|
TriggerInterrupt(4);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Trigger IRQ (CIA 1)
|
|
*/
|
|
|
|
void MOS6526_1::TriggerInterrupt(int bit)
|
|
{
|
|
icr |= bit;
|
|
if (int_mask & bit) {
|
|
icr |= 0x80;
|
|
the_cpu->TriggerCIAIRQ();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Trigger NMI (CIA 2)
|
|
*/
|
|
|
|
void MOS6526_2::TriggerInterrupt(int bit)
|
|
{
|
|
icr |= bit;
|
|
if (int_mask & bit) {
|
|
icr |= 0x80;
|
|
the_cpu->TriggerNMI();
|
|
}
|
|
}
|