/**
* This file is a part of JaC64 - a Java C64 Emulator
* Main Developer: Joakim Eriksson (Dreamfabric.com)
* Contact: joakime@sics.se
* Web: http://www.dreamfabric.com/c64
* ---------------------------------------------------
*/
package com.dreamfabric.jac64;
/**
* CIA emulation for JaC64.
*
* Created: Sat Jul 30 05:38:32 2005
*
* @author Joakim Eriksson
* @version 1.0
*/
public class CIA {
public static final boolean TIMER_DEBUG = false; //true;
public static final boolean WRITE_DEBUG = false; //true;
public static final int PRA = 0x00;
public static final int PRB = 0x01;
public static final int DDRA = 0x02;
public static final int DDRB = 0x03;
public static final int TIMALO = 0x04;
public static final int TIMAHI = 0x05;
public static final int TIMBLO = 0x06;
public static final int TIMBHI = 0x07;
public static final int TODTEN = 0x08;
public static final int TODSEC = 0x09;
public static final int TODMIN = 0x0a;
public static final int TODHRS = 0x0b;
public static final int SDR = 0x0c;
public static final int ICR = 0x0d;
public static final int CRA = 0x0e;
public static final int CRB = 0x0f;
CIATimer timerA;
CIATimer timerB;
int pra = 0;
int prb = 0;
int ddra = 0;
int ddrb = 0;
int tod10sec = 0;
int todsec = 0;
int todmin = 0;
int todhour = 0;
int sdr;
// For the CPU to read (contains status)
int ciaicrRead;
int ciaie = 0; // interrupt enable
// B-Div is set if mode (bit 5,6 of CIACRB) = 10
public static final int TIMER_B_DIV_MASK = 0x60;
public static final int TIMER_B_DIV_VAL = 0x40;
public long nextCIAUpdate = 0;
private MOS6510Core cpu;
private int offset;
public int serialFake = 0;
private ExtChip chips;
public TimeEvent todEvent = new TimeEvent(0) {
public void execute(long cycle) {
time = time + 100000; // Approx a tenth of a second...
int tmp = (tod10sec & 0x0f) + 1;
tod10sec = tmp % 10;
if (tmp > 9) {
// Maxval == 0x59
tmp = (todsec & 0x7f) + 1;
if ((tmp & 0x0f) > 9)
tmp += 0x06;
if (tmp > 0x59)
tmp = 0;
todsec = tmp;
// Wrapped seconds...
// Minutes inc - max 0x59
if (tmp == 0) {
tmp = (todmin & 0x7f) + 1;
if ((tmp & 0x0f) > 9)
tmp += 0x06;
if (tmp > 0x59)
tmp = 0;
todmin = tmp;
// Hours, max 0x12
if (tmp == 0) {
tmp = (todhour & 0x1f) + 1;
if ((tmp & 0x0f) > 9)
tmp += 0x06;
if (tmp > 0x11)
tmp = 0;
// how is hour? 1 - 12 or 0 - 11 ??
todhour = tmp;
}
}
}
// TODO: fix alarms and latches !!
// Since this should continue run, just reschedule...
// System.out.println("TOD ticked..." + (todhour>>4) + (todhour & 0xf) + ":" +
// (todmin>>4) + (todmin&0xf) + ":" + (todsec>>4) + (todsec&0xf));
cpu.scheduler.addEvent(this);
}
};
/**
* Creates a new <code>CIA</code> instance.
*
*/
public CIA(MOS6510Core cpu, int offset, ExtChip chips) {
this.cpu = cpu;
this.offset = offset;
this.chips = chips;
timerA = new CIATimer("TimerA", 1, true, null);
timerB = new CIATimer("TimerB", 2, false, timerA);
timerA.otherTimer = timerB;
todEvent.time = cpu.cycles + 10000;
cpu.scheduler.addEvent(todEvent);
}
public void reset() {
ciaicrRead = 0;
ciaie = 0;
timerA.reset();
timerB.reset();
tod10sec = 0;
todsec = 0;
todmin = 0;
todhour = 0;
updateInterrupts();
}
public String ciaID() {
return offset == 0x10c00 ? "CIA 1" : "CIA 2";
}
public int performRead(int address, long cycles) {
address -= offset;
switch(address) {
case DDRA:
return ddra;
case DDRB:
return ddrb;
case PRA:
return (pra | ~ddra) & 0xff;
case PRB:
int data = (prb | ~ddrb) & 0xff;
if ((timerA.cr & 0x02) > 0) {
data &= 0xbf;
if ((timerA.cr & 0x04) > 0) {
data |= timerA.flipflop ? 0x40 : 0;
} else {
data |= timerA.underflow ? 0x40 : 0;
}
}
if ((timerB.cr & 0x02) > 0) {
data &= 0x7f;
if ((timerB.cr & 0x04) > 0) {
data |= timerB.flipflop ? 0x80 : 0;
} else {
data |= timerB.underflow ? 0x80 : 0;
}
}
return data;
case TIMALO:
// System.out.println(ciaID() + " getTimerA LO, timer = " + timerA.getTimer(cycles));
return timerA.getTimer(cycles) & 0xff;
case TIMAHI:
return timerA.getTimer(cycles) >> 8;
case TIMBLO:
// System.out.println(ciaID() + " getTimerB LO, timer = " + timerB.getTimer(cycles));
return timerB.getTimer(cycles) & 0xff;
case TIMBHI:
return timerB.getTimer(cycles) & 0xff;
case TODTEN:
return tod10sec;
case TODSEC:
return todsec;
case TODMIN:
return todmin;
case TODHRS:
return todhour;
case SDR:
return sdr;
case CRA:
return timerA.cr;
case CRB:
return timerB.cr;
case ICR:
// Clear interrupt register (flags)!
if (TIMER_DEBUG && offset == 0x10d00) println("clear Interrupt register, was: " +
Hex.hex2(ciaicrRead));
int val = ciaicrRead;
ciaicrRead = 0;
// Latch off the IRQ/NMI immediately!!!
updateInterrupts();
return val;
default:
return 0xff;
}
}
public void performWrite(int address, int data, long cycles) {
address -= offset;
if (WRITE_DEBUG) println(ciaID() + " Write to :" +
Integer.toString(address, 16) + " = " +
Integer.toString(data, 16));
switch (address) {
//monitor.println("Set Keyboard:" + data);
case DDRA:
ddra = data;
break;
case DDRB:
ddrb = data;
break;
case PRA:
pra = data;
break;
case PRB:
prb = data;
break;
case TIMALO:
// Update latch value
timerA.latch = (timerA.latch & 0xff00) | data;
break;
case TIMAHI:
timerA.latch = (timerA.latch & 0xff) | (data << 8);
if (timerA.state == CIATimer.STOP) {
timerA.timer = timerA.latch;
}
if (TIMER_DEBUG && offset == 0x10d00) println(ciaID() + ": Timer A latch: " +
timerA.latch + ": " + cycles);
break;
case TIMBLO:
timerB.latch = (timerB.latch & 0xff00) | data;
break;
case TIMBHI:
timerB.latch = (timerB.latch & 0xff) | (data << 8);
if (timerB.state == CIATimer.STOP) {
timerB.timer = timerB.latch;
}
if (TIMER_DEBUG && offset == 0x10d00) println(ciaID() + ": Timer B latch: " +
timerB.latch + ": " + cycles);
break;
case TODTEN:
tod10sec = data;
break;
case TODSEC:
todsec = data;
break;
case TODMIN:
todmin = data;
break;
case TODHRS:
todhour = data;
break;
case ICR: // dc0d - CIAICR - CIA Interrupt Control Register
boolean val = (data & 0x80) != 0;
if (val) {
// Set the 1 bits if val = 1
ciaie |= data & 0x7f;
} else {
// Clear the 1 bits
ciaie &= ~data;
}
// Trigger interrupts if needed...
updateInterrupts();
System.out.println(ciaID() + " ====> IE = " + ciaie);
break;
case CRA:
timerA.writeCR(cycles, data);
timerA.countCycles = (data & 0x20) == 0;
break;
case CRB:
timerB.writeCR(cycles, data);
timerB.countCycles = (data & 0x60) == 0;
timerB.countUnderflow = (data & 0x60) == 0x40;
break;
}
}
private void updateInterrupts() {
if ((ciaie & ciaicrRead & 0x1f) != 0) {
ciaicrRead |= 0x80;
// Trigger the IRQ/NMI immediately!!!
if (offset == 0x10c00) {
// cpu.log("CIA 1 *** TRIGGERING CIA TIMER!!!: " +
// ciaie + " " + chips.getIRQFlags() + " " + cpu.getIRQLow());
chips.setIRQ(ExtChip.CIA_TIMER_IRQ);
} else {
chips.setNMI(ExtChip.CIA_TIMER_NMI);
}
} else {
if (offset == 0x10c00) {
// System.out.println("*** CLEARING CIA TIMER!!!");
chips.clearIRQ(ExtChip.CIA_TIMER_IRQ);
} else {
chips.clearNMI(ExtChip.CIA_TIMER_NMI);
}
}
}
private void println(String s) {
System.out.println(ciaID() + ": " + s);
}
public void printStatus() {
System.out.println("--------------------------");
println(" status");
System.out.println("Timer A state: " + timerA.state);
System.out.println("Timer A next trigger: " + timerA.nextZero);
System.out.println("CIA CRA: " + Hex.hex2(timerA.cr) + " => " +
(((timerA.cr & 0x08) == 0) ?
"cont" : "one-shot"));
// System.out.println("Timer A Interrupt: " + timerATrigg);
System.out.println("Timer A Latch: " + timerA.latch);
System.out.println("Timer B state: " + timerB.state);
System.out.println("Timer B next trigger: " + timerB.nextZero);
System.out.println("CIA CRB: " + Hex.hex2(timerA.cr) + " => " +
(((timerB.cr & 0x08) == 0) ?
"cont" : "one-shot"));
// System.out.println("Timer B Interrupt: " + timerBTrigg);
System.out.println("Timer B Latch: " + timerB.latch);
System.out.println("--------------------------");
}
// A timer class for handling the Timer state machines
// The CIATimer is inspired by the CIA Timer State Machine in the
// Frodo emulator
private class CIATimer {
// The states of the timer
private static final int STOP = 0;
private static final int WAIT = 1;
private static final int LOAD_STOP = 2;
private static final int LOAD_COUNT = 3;
private static final int LOAD_WAIT_COUNT = 5;
private static final int COUNT = 6;
private static final int COUNT_STOP = 7;
// If timer is connected...
CIATimer otherTimer;
int state = STOP;
// The latch for this timer
int latch = 0;
int timer = 0;
// When is an update needed for this timer?
long nextUpdate = 0;
long nextZero = 0;
long lastLatch = 0;
boolean interruptNext = false;
boolean underflow = false;
boolean flipflop = false;
boolean countCycles = false;
boolean countUnderflow = false;
TimeEvent updateEvent = new TimeEvent(0) {
public void execute(long cycles) {
doUpdate(cycles);
if (state != STOP) {
// System.out.println(ciaID() + " Adding Timer update event at " + cycles +
// " with time: " + nextUpdate + " state: " + state);
cpu.scheduler.addEvent(this, nextUpdate);
}
}
};
int writeCR = -1;
int cr = 0;
String id;
int iflag;
boolean updateOther;
CIATimer(String id, int flag, boolean uo, CIATimer other) {
this.id = id;
this.otherTimer = other;
this.iflag = flag;
updateOther = uo;
}
private void reset() {
latch = 0xffff;
timer = 0xffff;
countUnderflow = false;
flipflop = false;
state = STOP;
nextZero = 0;
nextUpdate = 0;
writeCR = -1;
cpu.scheduler.removeEvent(updateEvent);
}
private int getTimer(long cycles) {
if (state != COUNT) return timer;
int t = (int) (nextZero - cycles);
if (t < 0) {
// Unexpected underflow...
t = 0;
}
timer = t;
return t;
}
private void loadTimer(long cycles) {
timer = latch;
nextZero = cycles + latch;
if (TIMER_DEBUG && offset == 0x10d00) {
System.out.println(ciaID() + ": " + id + " - timer loaded at "
+ cycles + " with: " + latch + " diff " +
(cycles - lastLatch));
lastLatch = cycles;
}
}
private void triggerInterrupt(long cycles) {
if (TIMER_DEBUG && offset == 0x10d00)
System.out.println(ciaID() + ": " + id + " - trigger interrupt at: "
+ cycles + " nextZero: " + nextZero +
" nextUpdate: " + nextUpdate);
interruptNext = true;
underflow = true;
flipflop = !flipflop;
// One shot?
if ((cr & 8) != 0) {
cr &= 0xfe;
writeCR &= 0xfe;
state = LOAD_STOP;
} else {
state = LOAD_COUNT;
} // TODO: fix update of tb.
// if (updateOther) {
// otherTimer.update(cycles);
// }
}
void writeCR(long cycles, int data) {
writeCR = data;
if (nextUpdate > cycles + 1 || !updateEvent.scheduled) {
nextUpdate = cycles + 1;
cpu.scheduler.addEvent(updateEvent, nextUpdate);
}
}
public void doUpdate(long cycles) {
if (nextUpdate == 0) {
nextUpdate = cycles;
nextZero = cycles;
}
if (cycles == nextUpdate) {
// If the call is on "spot" then just do it!
update(cycles);
} else {
// As long as cycles is larger than next update, just continue
// call it!
while (cycles >= nextUpdate) {
System.out.println(ciaID() + ": " + id +
" ** update at: " + cycles + " expected: " + nextUpdate + " state: " + state);
update(nextUpdate);
}
}
}
// maybe only update when "needed" and when registers read???
// NEED TO CHECK IF WE ARE CALLED WHEN EXPECTED!!!
// No - since BA Low will cause no updates of IO units..??
public void update(long cycles) {
// set a default
underflow = false;
nextUpdate = cycles + 1;
if (interruptNext) {
ciaicrRead |= iflag;
interruptNext = false;
// Trigg the stuff...
updateInterrupts();
}
// Update timer...
getTimer(cycles);
// Timer state machine!
switch (state) {
case STOP:
// Nothing...
break;
case WAIT:
// Go to count next time!
state = COUNT;
break;
case LOAD_STOP:
loadTimer(cycles);
// Stop timer!
state = STOP;
break;
case LOAD_COUNT:
loadTimer(cycles);
state = COUNT;
break;
case LOAD_WAIT_COUNT:
if (nextZero == cycles + 1) {
triggerInterrupt(cycles);
}
state = WAIT;
loadTimer(cycles);
break;
case COUNT_STOP:
if (!countUnderflow) {
timer = (int) (cycles - nextZero);
if (timer < 0) timer = 0;
}
state = STOP;
break;
case COUNT:
// perform count - this assumes that we are counting
// cycles!!!
if (countUnderflow) {
if (otherTimer.underflow)
timer--;
if (timer <= 0) {
triggerInterrupt(cycles);
}
} else {
// TODO: check this!!!
if (cycles >= nextZero && state != STOP) {
state = LOAD_COUNT;
triggerInterrupt(cycles);
} else {
// We got here too early... what now?
nextUpdate = nextZero;
}
}
break;
}
// Delayed write of CR - is that only for timers?
if (writeCR != -1) {
delayedWrite(cycles);
writeCR = -1;
}
}
void delayedWrite(long cycles) {
nextUpdate = cycles + 1;
switch(state) {
case STOP:
case LOAD_STOP:
if ((writeCR & 1) > 0) {
// Start timer
if ((writeCR & 0x10) > 0) {
// force load
state = LOAD_WAIT_COUNT;
} else {
// Just count!
loadTimer(cycles);
state = WAIT;
}
} else {
if ((writeCR & 0x10) > 0) {
state = LOAD_STOP;
}
}
break;
case COUNT:
if ((writeCR & 1) > 0) {
if ((writeCR & 0x10) > 0) {
// force load
state = LOAD_WAIT_COUNT;
} // Otherwise just continue counting!
} else {
if ((writeCR & 0x10) > 0) {
state = LOAD_STOP;
} else {
state = COUNT_STOP;
}
}
break;
case LOAD_COUNT:
case WAIT:
if ((writeCR & 1) > 0) {
// Start timer
if ((writeCR & 8) > 0) {
// one-shot
writeCR = writeCR & 0xfe;
state = STOP;
} else if ((writeCR & 0x10) > 0) {
state = LOAD_WAIT_COUNT;
} // Otherwise just continue counting!
} else {
state = COUNT_STOP;
}
break;
}
cr = writeCR & 0xef;
}
}
}