Files
VirtualC64-Core/C64/CIA.cpp
T
2015-11-18 00:34:41 -06:00

1114 lines
29 KiB
C++
Executable File

/*
* (C) 2006 Dirk W. Hoffmann. All rights reserved.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "C64.h"
CIA::CIA()
{
name = "CIA";
// Register sub components
VirtualComponent *subcomponents[] = { &tod, NULL };
registerSubComponents(subcomponents, sizeof(subcomponents));
// Register snapshot items
SnapshotItem items[] = {
{ &delay, sizeof(delay), CLEAR_ON_RESET },
{ &feed, sizeof(feed), CLEAR_ON_RESET },
{ &CRA, sizeof(CRA), CLEAR_ON_RESET },
{ &CRB, sizeof(CRB), CLEAR_ON_RESET },
{ &ICR, sizeof(ICR), CLEAR_ON_RESET },
{ &IMR, sizeof(IMR), CLEAR_ON_RESET },
{ &PB67TimerMode, sizeof(PB67TimerMode), CLEAR_ON_RESET },
{ &PB67TimerOut, sizeof(PB67TimerOut), CLEAR_ON_RESET },
{ &PB67Toggle, sizeof(PB67Toggle), CLEAR_ON_RESET },
{ &PALatch, sizeof(PALatch), CLEAR_ON_RESET },
{ &PBLatch, sizeof(PBLatch), CLEAR_ON_RESET },
{ &DDRA, sizeof(DDRA), CLEAR_ON_RESET },
{ &DDRB, sizeof(DDRB), CLEAR_ON_RESET },
{ &PA, sizeof(PA), CLEAR_ON_RESET },
{ &PB, sizeof(PB), CLEAR_ON_RESET },
{ &CNT, sizeof(CNT), CLEAR_ON_RESET },
{ &INT, sizeof(INT), CLEAR_ON_RESET },
{ &readICR, sizeof(readICR), CLEAR_ON_RESET },
{ &counterA, sizeof(counterA), CLEAR_ON_RESET },
{ &latchA, sizeof(latchA), CLEAR_ON_RESET },
{ &counterB, sizeof(counterB), CLEAR_ON_RESET },
{ &latchB, sizeof(latchB), CLEAR_ON_RESET },
{ NULL, 0, 0 }};
registerSnapshotItems(items, sizeof(items));
}
CIA::~CIA()
{
}
void
CIA::reset()
{
VirtualComponent::reset();
// Establish bindings
cpu = c64->cpu;
vic = c64->vic;
clearInterruptLine();
PA = 0xff;
PB = 0xff;
CNT = true; // CNT line is high by default
INT = 1;
latchA = 0xFFFF;
latchB = 0xFFFF;
}
#if 0
void
CIA::setFlagPin(uint8_t value)
{
if (value) // Note: FLAG pin is inverted
ICR &= ~0x10;
else
ICR |= 0x10;
}
#endif
void
CIA::triggerRisingEdgeOnFlagPin()
{
// ICR &= ~0x10; // Note: FLAG pin is inverted
}
void
CIA::triggerFallingEdgeOnFlagPin()
{
ICR |= 0x10; // Note: FLAG pin is inverted
// Trigger interrupt, if enabled
if (IMR & 0x10) {
INT = 0;
ICR |= 0x80;
raiseInterruptLine();
}
}
uint8_t
CIA::peek(uint16_t addr)
{
uint8_t result;
switch(addr) {
case CIA_DATA_DIRECTION_A:
result = DDRA;
break;
case CIA_DATA_DIRECTION_B:
result = DDRB;
break;
case CIA_TIMER_A_LOW:
result = getCounterALo();
break;
case CIA_TIMER_A_HIGH:
result = getCounterAHi();
break;
case CIA_TIMER_B_LOW:
result = getCounterBLo();
break;
case CIA_TIMER_B_HIGH:
result = getCounterBHi();
break;
case CIA_TIME_OF_DAY_SEC_FRAC:
result = tod.getTodTenth();
tod.defreeze();
break;
case CIA_TIME_OF_DAY_SECONDS:
result = tod.getTodSeconds();
break;
case CIA_TIME_OF_DAY_MINUTES:
result = tod.getTodMinutes();
break;
case CIA_TIME_OF_DAY_HOURS:
tod.freeze();
result = tod.getTodHours();
break;
case CIA_SERIAL_IO_BUFFER:
result = 0x00;
break;
case CIA_INTERRUPT_CONTROL:
result = ICR;
// get status of the Int line into bit 7 and draw Int high
if (INT == 0) {
result |= 0x80;
INT = 1;
clearInterruptLine(); // really?
}
// discard pending interrupts
delay &= ~(Interrupt0 | Interrupt1);
// Remember read access
readICR = true;
// set all events to 0
ICR = 0;
break;
case CIA_CONTROL_REG_A:
result = (uint8_t)(CRA & ~0x10); // Bit 4 is always 0 when read
break;
case CIA_CONTROL_REG_B:
result = (uint8_t)(CRB & ~0x10); // Bit 4 is always 0 when read
break;
default:
result = 0;
panic("Unknown CIA address %04X\n", addr);
break;
}
return result;
}
void CIA::poke(uint16_t addr, uint8_t value)
{
switch(addr) {
case CIA_TIMER_A_LOW:
setLatchALo(value);
// If timer A is currently in LOAD state, this value goes directly into the counter
if (delay & LoadA2) {
setCounterALo(value);
}
return;
case CIA_TIMER_A_HIGH:
setLatchAHi(value);
// load counter if timer is stopped
if ((CRA & 0x01) == 0) {
delay |= LoadA0;
}
// If timer A is currently in LOAD state, this value goes directly into the counter
if (delay & LoadA2) {
setCounterAHi(value);
}
return;
case CIA_TIMER_B_LOW:
setLatchBLo(value);
// If timer B is currently in LOAD state, this value goes directly into the counter
if (delay & LoadB2) {
setCounterBLo(value);
}
return;
case CIA_TIMER_B_HIGH:
setLatchBHi(value);
// load counter if timer is stopped
if ((CRB & 0x01) == 0) {
delay |= LoadB0;
}
// If timer B is currently in LOAD state, this value goes directly into the counter
if (delay & LoadB2) {
setCounterBHi(value);
}
return;
case CIA_TIME_OF_DAY_SEC_FRAC:
if (CRB & 0x80) {
tod.setAlarmTenth(value);
} else {
tod.setTodTenth(value);
tod.cont();
}
return;
case CIA_TIME_OF_DAY_SECONDS:
if (CRB & 0x80)
tod.setAlarmSeconds(value);
else
tod.setTodSeconds(value);
return;
case CIA_TIME_OF_DAY_MINUTES:
if (CRB & 0x80)
tod.setAlarmMinutes(value);
else
tod.setTodMinutes(value);
return;
case CIA_TIME_OF_DAY_HOURS:
if (CRB & 0x80) {
tod.setAlarmHours(value);
} else {
// Note: A real C64 shows strange behaviour when writing 0x12 or 0x92
// into this register. In this case, the AM/PM flag is inverted
if ((value & 0x1F) == 0x12)
value ^= 0x80;
tod.setTodHours(value);
tod.stop();
}
return;
case CIA_SERIAL_IO_BUFFER:
// Serial I/O communication is not (yet) implemented
//triggerInterrupt(0x08);
// debug("poke CIA_SERIAL_IO_BUFFER: %0x2X\n", value);
return;
case CIA_INTERRUPT_CONTROL:
//if ((value & 0x84) == 0x84)
// debug("SETTING TIME OF DAY ALARM (%02X)\n", value);
// bit 7 means set (1) or clear (0) the other bits
if ((value & 0x80) != 0) {
IMR |= (value & 0x1F);
} else {
IMR &= ~(value & 0x1F);
}
// raise an interrupt in the next cycle if condition matches
if ((IMR & ICR) != 0) {
if (INT) {
delay |= Interrupt0;
}
}
return;
case CIA_CONTROL_REG_A:
{
//
// Adapted from PC64Win by Wolfgang Lorenz
//
// set clock in o2 mode // todo cnt
if ((value & 0x21) == 0x01) {
delay |= CountA1 | CountA0;
feed |= CountA0;
} else {
delay &= ~(CountA1 | CountA0);
feed &= ~CountA0;
}
// set one shot mode
if ((value & 0x08) != 0) {
feed |= OneShotA0;
} else {
feed &= ~OneShotA0;
}
// set force load
if ((value & 0x10) != 0) {
delay |= LoadA0;
}
// set toggle high on rising edge of Start
if ((value & 0x01) != 0 && (CRA & 0x01) == 0) {
PB67Toggle |= 0x40;
}
// timer A output to PB6
if ((value & 0x02) == 0) {
PB67TimerMode &= ~0x40;
} else {
PB67TimerMode |= 0x40;
if ((value & 0x04) == 0) {
if ((delay & PB7Low1) == 0) {
PB67TimerOut &= ~0x40;
} else {
PB67TimerOut |= 0x40;
}
} else {
PB67TimerOut = (PB67TimerOut & ~0x40) | (PB67Toggle & 0x40);
}
}
// write PB67
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
// set the register
CRA = value;
return;
}
case CIA_CONTROL_REG_B:
{
//
// Adapted from PC64Win by Wolfgang Lorenz
//
// set clock in o2 mode // todo cnt
if ((value & 0x61) == 0x01) {
delay |= CountB1 | CountB0;
feed |= CountB0;
} else {
delay &= ~(CountB1 | CountB0);
feed &= ~CountB0;
}
// set one shot mode
if ((value & 0x08) != 0) {
feed |= OneShotB0;
} else {
feed &= ~OneShotB0;
}
// set force load
if ((value & 0x10) != 0) {
delay |= LoadB0;
}
// set toggle high on rising edge of Start
if ((value & 0x01) != 0 && (CRB & 0x01) == 0) {
PB67Toggle |= 0x80;
}
// timer B output to PB7
if ((value & 0x02) == 0) {
PB67TimerMode &= ~0x80;
} else {
PB67TimerMode |= 0x80;
if ((value & 0x04) == 0) {
if ((delay & PB7Low1) == 0) {
PB67TimerOut &= ~0x80;
} else {
PB67TimerOut |= 0x80;
}
} else {
PB67TimerOut = (PB67TimerOut & ~0x80) | (PB67Toggle & 0x80);
}
}
// write PB67
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
// set the register
CRB = value;
return;
}
default:
panic("PANIC: Unknown CIA address (poke) %04X\n", addr);
}
}
void
CIA::incrementTOD()
{
if (tod.increment()) {
// Set interrupt source
ICR |= 0x04;
// Trigger interrupt, if enabled
if (IMR & 0x04) {
// The uppermost bit indicates that an interrupt occured
// printf("Triggering CIA interrupt (source = %02X) at cycle %d\n", source, (int)cpu->getCycles());
ICR |= 0x80;
raiseInterruptLine();
}
}
}
void CIA::dumpTrace()
{
const char *indent = " ";
if (!tracingEnabled())
return;
debug(1, "%sICR: %02X IMR: %02X ", indent, ICR, IMR);
debug(1, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
delay & CountA0 ? "CntA0 " : "",
delay & CountA1 ? "CntA1 " : "",
delay & CountA2 ? "CntA2 " : "",
delay & CountA3 ? "CntA3 " : "",
delay & CountB0 ? "CntB0 " : "",
delay & CountB1 ? "CntB1 " : "",
delay & CountB2 ? "CntB2 " : "",
delay & CountB3 ? "CntB3 " : "",
delay & LoadA0 ? "LdA0 " : "",
delay & LoadA1 ? "LdA1 " : "",
delay & LoadA2 ? "LdA2 " : "",
delay & LoadB0 ? "LdB0 " : "",
delay & LoadB1 ? "LdB1 " : "",
delay & LoadB1 ? "LdB2 " : "",
delay & PB6Low0 ? "PB6Lo0 " : "",
delay & PB6Low1 ? "PB6Lo1 " : "",
delay & PB7Low0 ? "PB7Lo0 " : "",
delay & PB7Low1 ? "PB7Lo1 " : "",
delay & Interrupt0 ? "Int0 " : "",
delay & Interrupt1 ? "Int1 " : "",
delay & OneShotA0 ? "1ShotA0 " : "",
delay & OneShotB0 ? "1ShotB0 " : "");
debug(1, "%sA: %04X (%04X) PA: %02X (%02X) DDRA: %02X CRA: %02X\n",
indent, counterA, latchA, PA, PALatch, DDRA, CRA);
debug(1, "%sB: %04X (%04X) PB: %02X (%02X) DDRB: %02X CRB: %02X\n",
indent, counterB, latchB, PB, PBLatch, DDRB, CRB);
}
void CIA::dumpState()
{
// assert(0);
msg(" Counter A : %02X\n", getCounterA());
msg(" Latch A : %02X\n", getLatchA());
msg(" Data port A : %02X\n", getDataPortA());
msg(" Data port direction A : %02X\n", getDataPortDirectionA());
msg(" Control register A : %02X\n", getControlRegA());
msg(" Timer A interrupts : %s\n", isInterruptEnabledA() ? "enabled" : "disabled");
msg("\n");
msg(" Counter B : %02X\n", getCounterB());
msg(" Latch B : %02X\n", getLatchB());
msg(" Data port B : %02X\n", getDataPortB());
msg(" Data port direction B : %02X\n", getDataPortDirectionB());
msg(" Control register B : %02X\n", getControlRegB());
msg(" Timer B interrupts : %s\n", isInterruptEnabledB() ? "enabled" : "disabled");
msg("\n");
msg(" Interrupt control reg : %02X\n", ICR);
msg(" Interrupt mask reg : %02X\n", IMR);
msg("\n");
tod.dumpState();
}
void CIA::executeOneCycle()
{
//
// Layout of timer (A and B)
//
// Source: "A Software Model of the CIA6526" by Wolfgang Lorenz
//
// Phi2 Phi2 Phi2
// | | |
// timerA ----- ------v------ ------v------ ----------v-----------
// input ---->| & |------>| dwDelay & |-X-| dwDelay & |---->| decrement counter |
// --->| | | CountA2 | | | CountA3 | | (1) |
// | ----- ------------- | ------------- | |
// ----------------- ^ Clr | | |
// | bCRA & 0x01 | | | ------------------| new counter == 0? |
// | timer A start |<---- | | | | |
// ----------------- | | v v | |
// ----- | ----- | timer A |
// | & | | | & | | 16 bit counter |
// | | | | | | and latch |
// ----- | ----- | |
// ^ ^ | |(2) | |
// | | ---------|------------- | |
// | | | | | |
// timer A | | | ----- | | |
// output <-----------|-X----------------X--->|>=1|---X---->| load from latch |
// | --->| | | (4) |
// ----- | ----- ----------------------
// |>=1| |
// | | | Phi2
// ----- | |
// ^ ^ | ------v------ ----------------
// | | (3) ---| dwDelay & |<-----| bcRA & 0x10 |
// | ----------------- | LoadA1 | | force load |
// | Phi2 | ------------- ----------------
// | | | ^ Clr
// ----------------- | ------v------ | |
// | bCRA & 0x08 | | | dwDelay & | | Phi2
// | one shot |---X->| oneShotA0 |---
// ----------------- -------------
// Timer A
// Decrement counter
if (delay & CountA3)
counterA--; // (1)
// Check underflow condition
bool timerAOutput = (counterA == 0 && (delay & CountA2)); // (2)
if (timerAOutput) {
// Stop timer in one shot mode
if ((delay | feed) & OneShotA0) { // (3)
CRA &= ~0x01;
delay &= ~(CountA2 | CountA1 | CountA0);
feed &= ~CountA0;
}
// Timer A output to timer B in cascade mode
if ((CRB & 0x61) == 0x41 || ((CRB & 0x61) == 0x61 && CNT)) {
delay |= CountB1;
}
delay |= LoadA1;
}
// Load counter
if (delay & LoadA1) // (4)
reloadTimerA();
// Timer B
// Decrement counter
if (delay & CountB3) {
counterB--; // (1)
// debug("Counter B down to %04X \n", counterB);
}
// Check underflow condition
bool timerBOutput = (counterB == 0 && (delay & CountB2)); // (2)
if (timerBOutput) {
// Stop timer in one shot mode
if ((delay | feed) & OneShotB0) { // (3)
CRB &= ~0x01;
delay &= ~(CountB2 | CountB1 | CountB0);
feed &= ~CountB0;
}
delay |= LoadB1;
}
// Load counter
if (delay & LoadB1) // (4)
reloadTimerB();
//
// Timer output to PB6 (timer A) and PB7 (timer B)
//
// Source: "A Software Model of the CIA6526" by Wolfgang Lorenz
//
// (7) -----------------
// --------------------------->| 0x00 (pulse) |
// | | | ----------------
// | | bCRA & 0x04 |------>| 0x02 (timer) |
// timerA | Flip --------------- (8) | timer mode | | |
// output -X------>| bPB67Toggle |---->| 0x04 (toggle) | | bCRA & 0x02 |
// (5) | ^ 0x04 | | (6) | | output mode |----> PB6 output
// --------------- ----------------- | |
// ^ Set | 0x00 (port) |
// | ---->| |
// ----------------- 0->1 | ----------------- | ----------------
// | bCRA & 0x01 |------- | port B bit 6 |---
// | timer A start | | output |
// ----------------- -----------------
// Timer A output to PB6
if (timerAOutput) {
PB67Toggle ^= 0x40; // (5) toggle underflow counter bit
if (CRA & 0x02) { // (6)
if ((CRA & 0x04) == 0) {
// (7) set PB6 high for one clock cycle
PB67TimerOut |= 0x40;
delay |= PB6Low0;
delay &= ~PB6Low1;
} else {
// (8) toggle PB6 (copy bit 6 from PB67Toggle)
PB67TimerOut = (PB67TimerOut & 0xBF) | (PB67Toggle & 0x40);
}
}
}
// Timer B output to PB7
if (timerBOutput) {
PB67Toggle ^= 0x80; // (5) toggle underflow counter bit
if (CRB & 0x02) { // (6)
if ((CRB & 0x04) == 0) {
// (7) set PB7 high for one clock cycle
PB67TimerOut |= 0x80;
delay |= PB7Low0;
delay &= ~PB7Low1;
} else {
// (8) toggle PB7 (copy bit 7 from PB67Toggle)
PB67TimerOut = (PB67TimerOut & 0x7F) | (PB67Toggle & 0x80);
}
}
}
// Set PB67 back to low
if (delay & PB6Low1)
PB67TimerOut &= ~0x40;
if (delay & PB7Low1)
PB67TimerOut &= ~0x80;
// Write new PB
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
//
// Interrupt logic
//
// Source: "A Software Model of the CIA6526" by Wolfgang Lorenz
//
// ----------
// | bIMR & |----
// | 0x01 | | -----
// ---------- ---->| & |----
// timerA (9) Set ---------- ---->| | |
// output ------------>| bICR & | | ----- |
// ---------->| 0x01 |---- | -----
// | Clr ---------- -->|>=1|---
// | ---------- -->| | |
// | | bIMR & |---- | ----- |
// | | 0x02 | | ----- | |
// | ---------- ---->| & |---- |
// timerB | (10) Set ---------- ---->| | |
// output --|--------->| bICR & | | ----- |
// X--------->| 0x01 |---- |
// | Clr ---------- |
// read | |
// ICR ------X-------------X---------------- |
// | | |
// v Clr v Clr |
// ------ ---------- ---------------- | (11)
// Int <--| -1 |<---| bICR & |<---| dwDelay & |<-------
// ouptput | | | 0x80 |Set | Interrupt1 |
// ------ ---------- -------^--------
// |
// Phi2
// Set interrupt register and interrupt line
if (delay & Interrupt1) {
INT = 0;
raiseInterruptLine();
}
if (timerAOutput) { // (9)
// On a real C64, there is a race condition here. If ICR is currently read,
// the read access occurs *before* timer A sets bit 1. Hence, bit 1 always shows up.
ICR |= 0x01;
}
if (timerBOutput && !readICR) { // (10)
// On a real C64, there is a race condition here. If ICR is currently read,
// the read access occurs *after* timer B sets bit 2. Hence, bit 2 won't show up.
ICR |= 0x02;
}
if ((timerAOutput && (IMR & 0x01)) || (timerBOutput && (IMR & 0x02))) // (11)
delay |= Interrupt0;
readICR = false;
// move delay flags left and feed in new bits
delay = ((delay << 1) & DelayMask) | feed;
}
// -----------------------------------------------------------------------------------------
// Complex Interface Adapter 1
// -----------------------------------------------------------------------------------------
CIA1::CIA1()
{
name = "CIA1";
debug(2, " Creating CIA1 at address %p...\n", this);
}
CIA1::~CIA1()
{
this->c64 = c64;
debug(2, " Releasing CIA1\n");
}
void
CIA1::reset()
{
keyboard = c64->keyboard;
joy[0] = c64->joystick1;
joy[1] = c64->joystick2;
joystick[0] = 0xff;
joystick[1] = 0xff;
CIA::reset();
}
void
CIA1::dumpState()
{
msg("CIA 1:\n");
msg("------\n\n");
CIA::dumpState();
}
void
CIA1::raiseInterruptLine()
{
cpu->setIRQLineCIA();
}
void
CIA1::clearInterruptLine()
{
cpu->clearIRQLineCIA();
}
uint8_t
CIA1::getInterruptLine()
{
return cpu->getIRQLineCIA();
}
void
CIA1::pollJoystick(Joystick *joy, int joyDevNo)
{
JoystickAxisState leftRightState = joy->GetAxisX();
JoystickAxisState upDownState = joy->GetAxisY();
bool buttonState = joy->GetButtonPressed();
assert (joy != NULL);
// up/down
// set the down bit: 2, 2 and clear up bit: 2, 1
// Remember: clearJoystickBits(x, y) means pressed
// setJoystickBits( x, y ) means released
if(upDownState == JOYSTICK_AXIS_Y_UP) {
clearJoystickBits(joyDevNo, 1);
setJoystickBits(joyDevNo, 2);
} else if(upDownState == JOYSTICK_AXIS_Y_DOWN) {
clearJoystickBits(joyDevNo, 2);
setJoystickBits(joyDevNo, 1);
} else {
setJoystickBits(joyDevNo, 1);
setJoystickBits(joyDevNo, 2);
}
// left/right
if(leftRightState == JOYSTICK_AXIS_X_LEFT) {
clearJoystickBits(joyDevNo, 4);
setJoystickBits(joyDevNo, 8);
} else if(leftRightState == JOYSTICK_AXIS_X_RIGHT) {
clearJoystickBits(joyDevNo, 8);
setJoystickBits(joyDevNo, 4);
} else {
setJoystickBits(joyDevNo, 4);
setJoystickBits(joyDevNo, 8);
}
// fire
if(buttonState) {
clearJoystickBits(joyDevNo, 16);
} else {
setJoystickBits(joyDevNo, 16);
}
}
uint8_t
CIA1::peek(uint16_t addr)
{
uint8_t result;
assert(addr <= CIA1_END_ADDR - CIA1_START_ADDR);
switch(addr) {
case CIA_DATA_PORT_A:
pollJoystick(joy[1], 2);
// We change only those bits that are configured as outputs, all input bits are 1
result = PA; // iomem[addr] | ~iomem[CIA_DATA_DIRECTION_A];
// The external port lines can pull down any bit, even if it configured as output
// result &= portLinesA;
// Check joystick movement
result &= joystick[1];
break;
case CIA_DATA_PORT_B:
{
uint8_t bitmask = CIA1::peek(CIA_DATA_PORT_A);
uint8_t keyboardBits = keyboard->getRowValues(bitmask);
pollJoystick(joy[0], 1);
result = PB;
// Check joystick movement
result &= joystick[0];
// Check for pressed keys
result &= keyboardBits;
break;
}
default:
result = CIA::peek(addr);
break;
}
// log("PEEKING %04X: %02X\n", 0xDC00 + addr, result);
return result;
}
void
CIA1::poke(uint16_t addr, uint8_t value)
{
uint8_t PBold;
assert(addr <= CIA1_END_ADDR - CIA1_START_ADDR);
// log("Poking %02X to %04X\n", value, 0xDC00 + addr);
// The following registers need special handling
switch(addr) {
case CIA_DATA_PORT_A:
PALatch = value;
PA = PALatch | ~DDRA;
return;
case CIA_DATA_DIRECTION_A:
DDRA = value;
PA = PALatch | ~DDRA;
return;
case CIA_DATA_PORT_B:
PBold = PB;
PBLatch = value;
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
if ((PBold & 0x10) != (PB & 0x10)) { // edge on lightpen bit?
vic->triggerLightPenInterrupt();
}
return;
case CIA_DATA_DIRECTION_B:
PBold = PB;
DDRB = value;
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
if ((PBold & 0x10) != (PB & 0x10)) { // edge on lightpen bit?
vic->triggerLightPenInterrupt();
}
return;
default:
CIA::poke(addr, value);
}
}
void
CIA1::setJoystickBits(int nr, uint8_t mask)
{
assert(nr == 1 || nr == 2);
if (nr == 1) joystick[0] |= mask;
else if (nr == 2) joystick[1] |= mask;
}
void
CIA1::clearJoystickBits(int nr, uint8_t mask)
{
assert(nr == 1 || nr == 2);
if (nr == 1) joystick[0] &= (0xff-mask);
else if (nr == 2) joystick[1] &= (0xff-mask);
}
// -----------------------------------------------------------------------------------------
// Complex Interface Adapter 2
// -----------------------------------------------------------------------------------------
CIA2::CIA2()
{
name = "CIA2";
debug(2, " Creating CIA2 at address %p...\n", this);
}
CIA2::~CIA2()
{
debug(2, " Releasing CIA2...\n");
}
void CIA2::reset()
{
this->c64 = c64;
iec = c64->iec;
CIA::reset();
}
void
CIA2::dumpState()
{
msg("CIA 2:\n");
msg("------\n\n");
CIA::dumpState();
}
void
CIA2::raiseInterruptLine()
{
cpu->setNMILineCIA();
}
void
CIA2::clearInterruptLine()
{
cpu->clearNMILineCIA();
}
uint8_t
CIA2::getInterruptLine()
{
return cpu->getNMILineCIA();
}
uint8_t
CIA2::peek(uint16_t addr)
{
uint8_t result;
assert(addr <= CIA_END_ADDR - CIA_START_ADDR);
switch(addr) {
case CIA_DATA_PORT_A:
result = PA;
// The two upper bits are connected to the clock line and the data line
result &= 0x3F;
result |= (iec->getClockLine() ? 0x40 : 0x00);
result |= (iec->getDataLine() ? 0x80 : 0x00);
// The external port lines can pull down any bit, even if it configured as output.
// Note that bits 0 and 1 are not connected to the bus and determine the memory bank seen by the VIC chip
// result &= (portLinesB | 0x03);
return result;
case CIA_DATA_PORT_B:
result = PB;
return result;
default:
return CIA::peek(addr);
}
}
void
CIA2::poke(uint16_t addr, uint8_t value)
{
assert(addr <= CIA2_END_ADDR - CIA2_START_ADDR);
switch(addr) {
case CIA_DATA_PORT_A:
PALatch = value;
PA = PALatch | ~DDRA;
// Bits 0 and 1 determine the memory bank seen the VIC
vic->setMemoryBankAddr((~PA & 0x03) << 14);
// Bits 3 to 5 of PA are connected to the IEC bus
iec->updateCiaPins(PALatch, DDRA);
return;
case CIA_DATA_DIRECTION_A:
DDRA = value;
PA = PALatch | ~DDRA;
// Bits 0 and 1 determine the memory bank seen the VIC
vic->setMemoryBankAddr((~PA & 0x03) << 14);
// Bits 3 to 5 of PA are connected to the IEC bus
iec->updateCiaPins(PALatch, DDRA);
return;
case CIA_DATA_PORT_B:
PBLatch = value;
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
// oldPB = PB;
return;
case CIA_DATA_DIRECTION_B:
DDRB = value;
PB = ((PBLatch | ~DDRB) & ~PB67TimerMode) | (PB67TimerOut & PB67TimerMode);
// oldPB = PB;
return;
default:
CIA::poke(addr, value);
}
}