diff options
Diffstat (limited to 'sidplay-libs-2.1.0/libsidplay/src/mos6526/mos6526.cpp')
-rw-r--r-- | sidplay-libs-2.1.0/libsidplay/src/mos6526/mos6526.cpp | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/sidplay-libs-2.1.0/libsidplay/src/mos6526/mos6526.cpp b/sidplay-libs-2.1.0/libsidplay/src/mos6526/mos6526.cpp new file mode 100644 index 00000000..32b6f1f2 --- /dev/null +++ b/sidplay-libs-2.1.0/libsidplay/src/mos6526/mos6526.cpp @@ -0,0 +1,322 @@ +/*************************************************************************** + mos6526.cpp - CIA Timer + ------------------- + begin : Wed Jun 7 2000 + copyright : (C) 2000 by Simon White + email : s_a_white@email.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +/*************************************************************************** + * $Log: mos6526.cpp,v $ + * Revision 1.10 2002/12/16 22:12:24 s_a_white + * Simulate serial input from data port A to prevent kernel lockups. + * + * Revision 1.9 2002/11/20 22:50:27 s_a_white + * Reload count when timers are stopped + * + * Revision 1.8 2002/10/02 19:49:21 s_a_white + * Revert previous change as was incorrect. + * + * Revision 1.7 2002/09/11 22:30:47 s_a_white + * Counter interval writes now go to a new register call prescaler. This is + * copied to the timer latch/counter as appropriate. + * + * Revision 1.6 2002/09/09 22:49:06 s_a_white + * Proper idr clear if interrupt was only internally pending. + * + * Revision 1.5 2002/07/20 08:34:52 s_a_white + * Remove unnecessary and pointless conts. + * + * Revision 1.4 2002/03/03 22:04:08 s_a_white + * Tidy. + * + * Revision 1.3 2001/07/14 13:03:33 s_a_white + * Now uses new component classes and event generation. + * + * Revision 1.2 2001/03/23 23:21:38 s_a_white + * Removed redundant reset funtion. Timer b now gets initialised properly. + * Switch case now allows write/read from timer b. + * + * Revision 1.1 2001/03/21 22:41:45 s_a_white + * Non faked CIA emulation with NMI support. Removal of Hacked VIC support + * off CIA timer. + * + * Revision 1.8 2001/03/09 23:44:30 s_a_white + * Integrated more 6526 features. All timer modes and interrupts correctly + * supported. + * + * Revision 1.7 2001/02/21 22:07:10 s_a_white + * Prevent re-triggering of interrupt if it's already active. + * + * Revision 1.6 2001/02/13 21:00:01 s_a_white + * Support for real interrupts. + * + * Revision 1.4 2000/12/11 18:52:12 s_a_white + * Conversion to AC99 + * + ***************************************************************************/ + +#include "sidendian.h" +#include "mos6526.h" + +enum +{ + INTERRUPT_TA = 1 << 0, + INTERRUPT_TB = 1 << 1, + INTERRUPT_ALARM = 1 << 2, + INTERRUPT_SP = 1 << 3, + INTERRUPT_FLAG = 1 << 4, + INTERRUPT_REQUEST = 1 << 7 +}; + +const char *MOS6526::credit = +{ // Optional information + "*MOS6526 (CIA) Emulation:\0" + "\tCopyright (C) 2001 Simon White <sidplay2@email.com>\0" +}; + + +MOS6526::MOS6526 (EventContext *context) +:idr(0), + event_context(*context), + event_ta(this), + event_tb(this) +{ + reset (); +} + +void MOS6526::reset (void) +{ + ta = ta_latch = 0xffff; + tb = tb_latch = 0xffff; + cra = crb = 0; + // Clear off any IRQs + trigger (0); + cnt_high = true; + icr = idr = 0; + m_accessClk = 0; + dpa = 0xf0; +} + +uint8_t MOS6526::read (uint_least8_t addr) +{ + event_clock_t cycles; + if (addr > 0x0f) return 0; + + cycles = event_context.getTime (m_accessClk); + m_accessClk += cycles; + + // Sync up timers + if ((cra & 0x21) == 0x01) + ta -= cycles; + if ((crb & 0x61) == 0x01) + tb -= cycles; + + switch (addr) + { + case 0x0: // Simulate a serial port + dpa = ((dpa << 1) | (dpa >> 7)) & 0xff; + if (dpa & 0x80) + return 0xc0; + return 0; + case 0x4: return endian_16lo8 (ta); + case 0x5: return endian_16hi8 (ta); + case 0x6: return endian_16lo8 (tb); + case 0x7: return endian_16hi8 (tb); + + case 0xd: + { // Clear IRQs, and return interrupt + // data register + uint8_t ret = idr; + trigger (0); + return ret; + } + + case 0x0e: return cra; + case 0x0f: return crb; + default: return regs[addr]; + } +} + +void MOS6526::write (uint_least8_t addr, uint8_t data) +{ + event_clock_t cycles; + if (addr > 0x0f) return; + + regs[addr] = data; + cycles = event_context.getTime (m_accessClk); + m_accessClk += cycles; + + // Sync up timers + if ((cra & 0x21) == 0x01) + ta -= cycles; + if ((crb & 0x61) == 0x01) + tb -= cycles; + + switch (addr) + { + case 0x4: endian_16lo8 (ta_latch, data); break; + case 0x5: + endian_16hi8 (ta_latch, data); + if (!(cra & 0x01)) // Reload timer if stopped + ta = ta_latch; + break; + + case 0x6: endian_16lo8 (tb_latch, data); break; + case 0x7: + endian_16hi8 (tb_latch, data); + if (!(crb & 0x01)) // Reload timer if stopped + tb = tb_latch; + break; + + case 0xd: + if (data & 0x80) + icr |= data & 0x1f; + else + icr &= ~data; + trigger (idr); + break; + + case 0x0e: + // Check for forced load + cra = data; + if (data & 0x10) + { + cra &= (~0x10); + ta = ta_latch; + } + + if ((data & 0x21) == 0x01) + { // Active + event_context.schedule (&event_ta, (event_clock_t) ta + 1); + } else + { // Inactive + ta = ta_latch; + event_context.cancel (&event_ta); + } + break; + + case 0x0f: + // Check for forced load + crb = data; + if (data & 0x10) + { + crb &= (~0x10); + tb = tb_latch; + } + + if ((data & 0x61) == 0x01) + { // Active + event_context.schedule (&event_tb, (event_clock_t) tb + 1); + } else + { // Inactive + tb = tb_latch; + event_context.cancel (&event_tb); + } + break; + + default: + break; + } +} + +void MOS6526::trigger (int irq) +{ + if (!irq) + { // Clear any requested IRQs + if (idr & INTERRUPT_REQUEST) + interrupt (false); + idr = 0; + return; + } + + idr |= irq; + if (icr & idr) + { + if (!(idr & INTERRUPT_REQUEST)) + { + idr |= INTERRUPT_REQUEST; + interrupt (true); + } + } +} + +void MOS6526::ta_event (void) +{ // Timer Modes + event_clock_t cycles; + uint8_t mode = cra & 0x21; + + if (mode == 0x21) + { + if (ta--) + return; + } + + cycles = event_context.getTime (m_accessClk); + m_accessClk += cycles; + + ta = ta_latch; + if (cra & 0x08) + { // one shot, stop timer A + cra &= (~0x01); + } else if (mode == 0x01) + { // Reset event + event_context.schedule (&event_ta, (event_clock_t) ta + 1); + } + trigger (INTERRUPT_TA); + + switch (crb & 0x61) + { + case 0x01: tb -= cycles; break; + case 0x41: + case 0x61: + tb_event (); + break; + } +} + +void MOS6526::tb_event (void) +{ // Timer Modes + uint8_t mode = crb & 0x61; + switch (mode) + { + case 0x01: + break; + + case 0x21: + case 0x41: + if (tb--) + return; + break; + + case 0x61: + if (cnt_high) + { + if (tb--) + return; + } + break; + + default: + return; + } + + m_accessClk = event_context.getTime (); + tb = tb_latch; + if (crb & 0x08) + { // one shot, stop timer A + crb &= (~0x01); + } else if (mode == 0x01) + { // Reset event + event_context.schedule (&event_tb, (event_clock_t) tb + 1); + } + trigger (INTERRUPT_TB); +} |