using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace BizHawk.Emulation.Computers.Commodore64 { public class CiaRegs { public bool ALARM; // alarm enabled public int ALARM10; // alarm 10ths of a second public int ALARMHR; // alarm hours public int ALARMMIN; // alarm minutes public bool ALARMPM; // alarm AM/PM public int ALARMSEC; // alarm seconds public bool CNT; // external counter bit input public bool EIALARM; // enable alarm interrupt (internal) public bool EIFLG; // enable flag pin interrupt (internal) public bool EISP; // enable shift register interrupt (internal) public bool[] EIT = new bool[2]; // enable timer interrupt (internal) public bool FLG; // external flag bit input public bool IALARM; // alarm interrupt triggered public bool IFLG; // interrupt triggered on FLAG pin public int[] INMODE = new int[2]; // timer input mode public bool IRQ; // interrupt triggered public bool ISP; // shift register interrupt public bool[] IT = new bool[2]; // timer interrupt public bool[] LOAD = new bool[2]; // force load timer public bool[] OUTMODE = new bool[2]; // timer output mode public bool[] PBON = new bool[2]; // port bit modify on public bool[] RUNMODE = new bool[2]; // running mode public int SDR; // serial shift register public int SDRCOUNT; // serial shift register bit count public bool SPMODE; // shift register mode public bool[] START = new bool[2]; // timer enabled public int[] T = new int[2]; // timer counter public bool[] TICK = new bool[2]; // execute timer tick public int[] TLATCH = new int[2]; // timer latch (internal) public int TOD10; // time of day 10ths of a second public bool TODIN; // time of day/alarm set public int TODHR; // time of day hour public int TODMIN; // time of day minute public bool TODPM; // time of day AM/PM public bool TODREADLATCH; // read latch (internal) public int TODREADLATCH10; // tod read latch (internal) public int TODREADLATCHSEC; // tod read latch (internal) public int TODREADLATCHMIN; // tod read latch (internal) public int TODREADLATCHHR; // tod read latch (internal) public int TODSEC; // time of day seconds public DataPortBus[] ports; private DataPortConnector[] connectors; public CiaRegs() { ports = new DataPortBus[2]; ports[0] = new DataPortBus(); ports[1] = new DataPortBus(); connectors = new DataPortConnector[2]; connectors[0] = ports[0].Connect(); connectors[1] = ports[1].Connect(); HardReset(); } public byte this[int addr] { get { // value of open bits int result = 0x00; addr &= 0x0F; switch (addr) { case 0x00: result = connectors[0].Data; break; case 0x01: result = connectors[1].Data; break; case 0x02: result = connectors[0].Direction; break; case 0x03: result = connectors[1].Direction; break; case 0x04: result = (T[0] & 0xFF); break; case 0x05: result = ((T[0] >> 8) & 0xFF); break; case 0x06: result = (T[1] & 0xFF); break; case 0x07: result = ((T[1] >> 8) & 0xFF); break; case 0x08: result |= (TOD10 & 0x0F); break; case 0x09: result &= 0x80; result |= (TODSEC & 0x7F); break; case 0x0A: result &= 0x80; result |= (TODMIN & 0x7F); break; case 0x0B: result &= 0x40; result |= ((TODHR & 0x3F) | (TODPM ? 0x80 : 0x00)); break; case 0x0C: result = SDR; break; case 0x0D: result &= 0x9F; result |= (IT[0] ? 0x01 : 0x00); result |= (IT[1] ? 0x02 : 0x00); result |= (IALARM ? 0x04 : 0x00); result |= (ISP ? 0x08 : 0x00); result |= (IFLG ? 0x10 : 0x00); result |= (IRQ ? 0x80 : 0x00); break; case 0x0E: result = (START[0] ? 0x01 : 0x00); result = (PBON[0] ? 0x02 : 0x00); result = (OUTMODE[0] ? 0x04 : 0x00); result = (RUNMODE[0] ? 0x08 : 0x00); result = (LOAD[0] ? 0x10 : 0x00); result = ((INMODE[0] & 0x01) << 5); result = (SPMODE ? 0x40 : 0x00); result = (TODIN ? 0x80 : 0x00); break; case 0x0F: result = (START[1] ? 0x01 : 0x00); result = (PBON[1] ? 0x02 : 0x00); result = (OUTMODE[1] ? 0x04 : 0x00); result = (RUNMODE[1] ? 0x08 : 0x00); result = (LOAD[1] ? 0x10 : 0x00); result = ((INMODE[1] & 0x03) << 5); result = (ALARM ? 0x80 : 0x00); break; } return (byte)(result & 0xFF); } set { byte val = value; addr &= 0x0F; switch (addr) { case 0x00: connectors[0].Data = val; break; case 0x01: connectors[1].Data = val; break; case 0x02: connectors[0].Direction = val; break; case 0x03: connectors[1].Direction = val; break; case 0x04: T[0] &= 0xFF00; T[0] |= val; break; case 0x05: T[0] &= 0x00FF; T[0] |= ((int)val << 8); break; case 0x06: T[1] &= 0xFF00; T[1] |= val; break; case 0x07: T[1] &= 0x00FF; T[1] |= ((int)val << 8); break; case 0x08: TOD10 = val & 0x0F; break; case 0x09: TODSEC = val & 0x7F; break; case 0x0A: TODMIN = val & 0x7F; break; case 0x0B: val &= 0x9F; TODHR = val; TODPM = ((val & 0x80) != 0x00); break; case 0x0C: SDR = val; break; case 0x0D: IT[0] = ((val & 0x01) != 0x00); IT[1] = ((val & 0x02) != 0x00); IALARM = ((val & 0x04) != 0x00); ISP = ((val & 0x08) != 0x00); IFLG = ((val & 0x10) != 0x00); IRQ = ((val & 0x80) != 0x00); break; case 0x0E: START[0] = ((val & 0x01) != 0x00); PBON[0] = ((val & 0x02) != 0x00); OUTMODE[0] = ((val & 0x04) != 0x00); RUNMODE[0] = ((val & 0x08) != 0x00); LOAD[0] = ((val & 0x10) != 0x00); INMODE[0] = ((val & 0x20) >> 5); SPMODE = ((val & 0x40) != 0x00); TODIN = ((val & 0x80) != 0x00); break; case 0x0F: START[1] = ((val & 0x01) != 0x00); PBON[1] = ((val & 0x02) != 0x00); OUTMODE[1] = ((val & 0x04) != 0x00); RUNMODE[1] = ((val & 0x08) != 0x00); LOAD[1] = ((val & 0x10) != 0x00); INMODE[1] = ((val & 0x60) >> 5); ALARM = ((val & 0x80) != 0x00); break; } } } public void HardReset() { // power on state for (int i = 0; i < 0x10; i++) this[i] = 0x00; TLATCH[0] = 0xFFFF; TLATCH[1] = 0xFFFF; T[0] = TLATCH[0]; T[1] = TLATCH[1]; this[0x0B] = 0x01; connectors[0].Data = 0xFF; connectors[1].Data = 0xFF; connectors[0].Direction = 0xFF; connectors[1].Direction = 0xFF; } } public class Cia { public int intMask; public bool lastCNT; public byte[] outputBitMask; private CiaRegs regs = new CiaRegs(); public int todCounter; public int todFrequency; public bool[] underflow; public Func ReadSerial; public Action WriteSerial; public Cia(Region newRegion) { ReadSerial = ReadSerialDummy; WriteSerial = WriteSerialDummy; switch (newRegion) { case Region.NTSC: todFrequency = 14318181 / 14 / 10; break; case Region.PAL: todFrequency = 14318181 / 18 / 10; break; } HardReset(); } private void AdvanceTOD() { bool overflow; int tenths = regs.TOD10; int seconds = regs.TODSEC; int minutes = regs.TODMIN; int hours = regs.TODHR; bool ampm = regs.TODPM; todCounter = todFrequency; tenths = BCDAdd(tenths, 1, out overflow); if (tenths >= 10) { tenths = 0; seconds = BCDAdd(seconds, 1, out overflow); if (overflow) { seconds = 0; minutes = BCDAdd(minutes, 1, out overflow); if (overflow) { minutes = 0; hours = BCDAdd(hours, 1, out overflow); if (hours > 12) { hours = 1; ampm = !ampm; } } } } regs.TOD10 = tenths; regs.TODSEC = seconds; regs.TODMIN = minutes; regs.TODHR = hours; regs.TODPM = ampm; } public void AttachWriteHook(int index, Action act) { regs.ports[index].AttachWriteHook(act); } private int BCDAdd(int i, int j, out bool overflow) { int lo; int hi; int result; lo = (i & 0x0F) + (j & 0x0F); hi = (i & 0x70) + (j & 0x70); if (lo > 0x09) { hi += 0x10; lo += 0x06; } if (hi > 0x50) { hi += 0xA0; } overflow = hi >= 0x60; result = (hi & 0x70) + (lo & 0x0F); return result; } public DataPortConnector ConnectPort(int index) { return regs.ports[index].Connect(); } public DataPortConnector ConnectSerialPort(int index) { DataPortConnector result = regs.ports[index].Connect(); regs.ports[index].AttachInputConverter(result, new DataPortSerialInputConverter()); regs.ports[index].AttachOutputConverter(result, new DataPortSerialOutputConverter()); return result; } public void HardReset() { outputBitMask = new byte[] { 0x40, 0x80 }; regs.HardReset(); underflow = new bool[2]; todCounter = todFrequency; } public bool IRQ { get { return regs.IRQ; } } public byte Peek(int addr) { addr &= 0xF; return regs[addr]; } public void PerformCycle() { // process time of day counter todCounter--; if (todCounter <= 0) AdvanceTOD(); for (int i = 0; i < 2; i++) { if (regs.START[i]) { TimerTick(i); if (regs.PBON[i]) { // output the clock data to port B if (regs.OUTMODE[i]) { // clear bit if set regs[0x01] &= (byte)~outputBitMask[i]; } if (underflow[i]) { if (regs.OUTMODE[i]) { // toggle bit regs[0x01] ^= outputBitMask[i]; } else { // set for a cycle regs[0x01] |= outputBitMask[i]; } } } } } lastCNT = regs.CNT; regs.CNT = false; UpdateInterrupt(); } public void Poke(int addr, byte val) { addr &= 0xF; regs[addr] = val; } public byte Read(ushort addr) { byte result; addr &= 0xF; switch (addr) { case 0x08: regs.TODREADLATCH = false; return (byte)regs.TODREADLATCH10; case 0x09: if (!regs.TODREADLATCH) return regs[addr]; else return (byte)regs.TODREADLATCHSEC; case 0x0A: if (!regs.TODREADLATCH) return regs[addr]; else return (byte)regs.TODREADLATCHMIN; case 0x0B: regs.TODREADLATCH = true; regs.TODREADLATCH10 = regs.TOD10; regs.TODREADLATCHSEC = regs.TODSEC; regs.TODREADLATCHMIN = regs.TODMIN; regs.TODREADLATCHHR = regs.TODHR; return (byte)regs.TODREADLATCHHR; case 0x0D: // reading this reg clears it result = regs[0x0D]; regs[0x0D] = 0x00; UpdateInterrupt(); return result; default: return regs[addr]; } } private bool ReadSerialDummy() { return false; } public void TimerDec(int index) { int timer = regs.T[index]; timer--; if (timer < 0) { underflow[index] = true; if (regs.RUNMODE[index]) { // one shot timer regs.START[index] = false; } timer = regs.TLATCH[index]; } else { underflow[index] = false; } regs.IT[index] |= underflow[index]; regs.T[index] = timer; } public void TimerTick(int index) { switch (regs.INMODE[index]) { case 0: regs.TICK[index] = true; break; case 1: regs.TICK[index] |= (regs.CNT && !lastCNT); break; case 2: regs.TICK[index] |= underflow[0]; break; case 3: regs.TICK[index] |= (regs.CNT && !lastCNT) || underflow[0]; break; } if (regs.TICK[index]) { TimerDec(index); regs.TICK[index] = false; } } public void UpdateInterrupt() { bool irq = false; irq |= (regs.EIT[0] & regs.IT[0]); irq |= (regs.EIT[1] & regs.IT[1]); irq |= (regs.EIFLG & regs.IFLG); irq |= (regs.EISP & regs.ISP); irq |= (regs.EIALARM & regs.IALARM); regs.IRQ = irq; } public void Write(ushort addr, byte val) { addr &= 0xF; switch (addr) { case 0x04: regs.TLATCH[0] &= 0xFF00; regs.TLATCH[0] |= val; if (regs.LOAD[0]) regs.T[0] = regs.TLATCH[0]; break; case 0x05: regs.TLATCH[0] &= 0xFF; regs.TLATCH[0] |= (int)val << 8; if (regs.LOAD[0] || !regs.START[0]) regs.T[0] = regs.TLATCH[0]; break; case 0x06: regs.TLATCH[1] &= 0xFF00; regs.TLATCH[1] |= val; if (regs.LOAD[1]) regs.T[1] = regs.TLATCH[1]; break; case 0x07: regs.TLATCH[1] &= 0xFF; regs.TLATCH[1] |= (int)val << 8; if (regs.LOAD[1] || !regs.START[1]) regs.T[1] = regs.TLATCH[1]; break; case 0x08: if (regs.ALARM) regs.ALARM10 = val & 0x0F; else regs[addr] = val; break; case 0x09: if (regs.ALARM) regs.ALARMSEC = val & 0x7F; else regs[addr] = val; break; case 0x0A: if (regs.ALARM) regs.ALARMMIN = val & 0x7F; else regs[addr] = val; break; case 0x0B: if (regs.ALARM) { regs.ALARMHR = val & 0x1F; regs.ALARMPM = ((val & 0x80) != 0x00); } else { regs[addr] = val; } break; case 0x0D: intMask &= ~val; if ((val & 0x80) != 0x00) intMask ^= val; regs.EIT[0] = ((intMask & 0x01) != 0x00); regs.EIT[1] = ((intMask & 0x02) != 0x00); regs.EIALARM = ((intMask & 0x04) != 0x00); regs.EISP = ((intMask & 0x08) != 0x00); regs.EIFLG = ((intMask & 0x10) != 0x00); UpdateInterrupt(); break; default: regs[addr] = val; break; } } public void WriteCNT(bool val) { if (!lastCNT && val) { if (!regs.SPMODE) { // read bit into shift register bool inputBit = ReadSerial(); regs.SDR = ((regs.SDR << 1) | (inputBit ? 0x01 : 0x00)) & 0xFF; regs.SDRCOUNT = (regs.SDRCOUNT - 1) & 0x7; if (regs.SDRCOUNT == 0) { regs.ISP = true; } } else { // write bit out from shift register bool outputBit = ((regs.SDR & 0x01) != 0); regs.SDR >>= 1; WriteSerial(outputBit); regs.SDRCOUNT = (regs.SDRCOUNT - 1) & 0x7; if (regs.SDRCOUNT == 0) { regs.ISP = true; } } } regs.CNT = val; } public void WriteFLG(bool val) { regs.FLG = val; regs.IFLG |= val; } private void WriteSerialDummy(bool val) { // do nothing } } }