Commodore 64: Add new CIA emulation (disconnected for this commit but it is a drop-in replacement). Fixed RAM writes underneath CPU IO port space ($00/$01)

This commit is contained in:
saxxonpike 2014-10-09 02:14:15 +00:00
parent 977f7ff0dd
commit 1b367c1873
9 changed files with 849 additions and 1 deletions

View File

@ -113,6 +113,9 @@
<Compile Include="Computers\Commodore64\Media\D64.cs" />
<Compile Include="Computers\Commodore64\Media\Disk.cs" />
<Compile Include="Computers\Commodore64\Media\G64.cs" />
<Compile Include="Computers\Commodore64\MOS\MOS6526-2.cs" />
<Compile Include="Computers\Commodore64\MOS\MOS6526-2.Interface.cs" />
<Compile Include="Computers\Commodore64\MOS\MOS6526-2.PortIO.cs" />
<Compile Include="Computers\Commodore64\MOS\MOS6526.cs" />
<Compile Include="Computers\Commodore64\MOS\MOS6567.cs" />
<Compile Include="Computers\Commodore64\MOS\MOS6581.cs" />
@ -555,6 +558,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Computers\Commodore64\MOS\MOS6526-2.Registers.cs" />
<None Include="Consoles\Atari\docs\stella.pdf" />
<None Include="Consoles\Coleco\docs\colecovision tech1.pdf" />
<None Include="Consoles\Coleco\docs\colecovision tech2.pdf" />

View File

@ -136,6 +136,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
cpu.ReadRDY = vic.ReadBABuffer;
cpu.ReadMemory = pla.Read;
cpu.WriteMemory = pla.Write;
cpu.WriteMemoryPort = Cpu_WriteMemoryPort;
pla.PeekBasicRom = basicRom.Peek;
pla.PeekCartridgeHi = cartPort.PeekHiRom;

View File

@ -66,6 +66,11 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
return data;
}
void Cpu_WriteMemoryPort(int addr, byte val)
{
pla.WriteMemory(addr, bus);
}
bool Glue_ReadIRQ()
{
return cia0.ReadIRQBuffer() & vic.ReadIRQBuffer() & cartPort.ReadIRQBuffer();

View File

@ -108,6 +108,31 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
//disk.Execute();
board.Execute();
#if false
if (board.cpu.PC == 0xE16F && (board.cpu.ReadPort() & 0x7) == 7)
{
// HUGE kernal hack to load files
// the only purpose for this is to be able to run the Lorenz
// test suite!
int fileNameLength = board.ram.Peek(0xB7);
int fileNameOffset = board.ram.Peek(0xBB) | ((int)board.ram.Peek(0xBC) << 8);
byte[] fileNameRaw = new byte[fileNameLength];
for (int i = 0; i < fileNameLength; i++)
{
fileNameRaw[i] = board.ram.Peek(fileNameOffset + i);
}
var enc = System.Text.Encoding.ASCII;
string fileName = enc.GetString(fileNameRaw);
string filePath = Path.Combine(@"E:\Programming\Visual Studio 2013\Vice\testprogs\general\Lorenz-2.15\src\", fileName + ".prg");
if (File.Exists(filePath))
{
PRG.Load(board.pla, File.ReadAllBytes(filePath));
}
board.cpu.PC = 0xE1B5;
}
#endif
// load PRG file if needed
if (loadPrg)
{

View File

@ -28,6 +28,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
public Func<int, byte> ReadMemory;
public Func<byte> ReadPort;
public Action<int, byte> WriteMemory;
public Action<int, byte> WriteMemoryPort;
// ------------------------------------
@ -196,10 +197,19 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
public void Write(ushort addr, byte val)
{
if (addr == 0x0000)
{
port.Direction = val;
WriteMemoryPort(addr, val);
}
else if (addr == 0x0001)
{
port.Latch = val;
WriteMemory(addr, val);
WriteMemoryPort(addr, val);
}
else
{
WriteMemory(addr, val);
}
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
sealed public partial class MOS6526_2
{
public void ExecutePhase1()
{
proc_a();
proc_b();
if (--tod_cycles <= 0)
{
//tod_cycles += tod_period;
tod();
}
}
public void ExecutePhase2()
{
}
public void HardReset()
{
reset();
}
public byte PortAData
{
get
{
return portA.ReadOutput();
}
}
public byte PortAMask
{
get;
set;
}
public byte PortADirection
{
get
{
return portA.Direction;
}
}
public byte PortALatch
{
get
{
return portA.Latch;
}
}
public byte PortBData
{
get
{
return portB.ReadOutput();
}
}
public byte PortBDirection
{
get
{
return portB.Direction;
}
}
public byte PortBLatch
{
get
{
return portB.Latch;
}
}
public byte PortBMask
{
get;
set;
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
sealed public partial class MOS6526_2
{
public Func<bool> ReadCNT;
public Func<bool> ReadFlag;
public bool ReadIRQBuffer() {
return (idr & 0x80) == 0;
}
public Func<byte> ReadPortA = (() => { return 0xFF; });
public Func<byte> ReadPortB = (() => { return 0xFF; });
public Func<bool> ReadSP;
}
}

View File

@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
sealed public partial class MOS6526_2
{
int read_data;
public byte Peek(int addr)
{
addr &= 0xF;
switch (addr)
{
case 0x0:
return (byte)(portA.ReadInput(ReadPortA()) & PortAMask);
case 0x1:
return (byte)(portB.ReadInput(ReadPortB()) & PortBMask);
case 0x2:
return (byte)portA.Direction;
case 0x3:
return (byte)portB.Direction;
case 0x4:
return (byte)(a.getTimer() & 0xFF);
case 0x5:
return (byte)((a.getTimer() >> 8) & 0xFF);
case 0x6:
return (byte)(b.getTimer() & 0xFF);
case 0x7:
return (byte)((b.getTimer() >> 8) & 0xFF);
case 0x8:
case 0x9:
case 0xA:
case 0xB:
return (byte)(tod_clock[addr - 0x08] & 0xFF);
case 0xC:
return (byte)(sdr & 0xFF);
case 0xD:
return (byte)(idr & 0xFF);
case 0xE:
return (byte)((cra & 0xEE) | (a.state & 1));
case 0xF:
return (byte)((crb & 0xEE) | (b.state & 1));
}
return 0xFF;
}
public void Poke(int addr, byte val)
{
// TODO
}
public byte Read(int addr)
{
return Read(addr, 0xFF);
}
public byte Read(int addr, byte mask)
{
addr &= 0xF;
switch (addr)
{
case 0x0:
return (byte)(portA.ReadInput(ReadPortA()) & PortAMask);
case 0x1:
read_data = portB.ReadInput(ReadPortB()) & PortBMask;
if ((cra & 0x02) != 0)
{
read_data &= 0xBF;
if (((cra & 0x04) != 0) ? (a.getPbToggle()) : ((a.state & CiaTimer.TIMER_OUT) != 0))
{
read_data |= 0x40;
}
}
if ((crb & 0x02) != 0)
{
read_data &= 0x7F;
if (((crb & 0x04) != 0) ? (b.getPbToggle()) : ((b.state & CiaTimer.TIMER_OUT) != 0))
{
read_data |= 0x80;
}
}
return (byte)(read_data & 0xFF);
case 0x2:
return (byte)portA.Direction;
case 0x3:
return (byte)portB.Direction;
case 0x4:
return (byte)(a.getTimer() & 0xFF);
case 0x5:
return (byte)((a.getTimer() >> 8) & 0xFF);
case 0x6:
return (byte)(b.getTimer() & 0xFF);
case 0x7:
return (byte)((b.getTimer() >> 8) & 0xFF);
case 0x8:
case 0x9:
case 0xA:
case 0xB:
if (!tod_latched)
{
tod_latch[0] = tod_clock[0];
tod_latch[1] = tod_clock[1];
tod_latch[2] = tod_clock[2];
tod_latch[3] = tod_clock[3];
}
if (addr == 0x8)
{
tod_latched = false;
}
else if (addr == 0xB)
{
tod_latched = true;
}
return (byte)(tod_latch[addr - 0x08] & 0xFF);
case 0xC:
return (byte)(sdr & 0xFF);
case 0xD:
int_clear();
return (byte)(icr & 0xFF);
case 0xE:
return (byte)((cra & 0xEE) | (a.state & 1));
case 0xF:
return (byte)((crb & 0xEE) | (b.state & 1));
}
return 0xFF;
}
public bool ReadCNTBuffer()
{
return true;
}
public bool ReadPCBuffer()
{
return true;
}
public bool ReadSPBuffer()
{
return true;
}
public void Write(int addr, byte val)
{
Write(addr, val, 0xFF);
}
public void Write(int addr, byte val, byte mask)
{
addr &= 0xF;
switch (addr)
{
case 0x0:
portA.Latch = val;
pra = val;
break;
case 0x1:
portB.Latch = val;
prb = val;
break;
case 0x2:
portA.Direction = val;
ddra = val;
break;
case 0x3:
portB.Direction = val;
ddrb = val;
break;
case 0x4:
a.setLatchLow(val);
ta = ((int)val | (ta & 0xFF00));
break;
case 0x5:
a.setLatchHigh(val);
ta = (((int)val << 8) | (ta & 0xFF));
break;
case 0x6:
b.setLatchLow(val);
tb = ((int)val | (tb & 0xFF00));
break;
case 0x7:
b.setLatchHigh(val);
tb = (((int)val << 8) | (tb & 0xFF));
break;
case 0x8:
case 0x9:
case 0xA:
case 0xB:
if (addr == 0xB)
{
val &= 0x9F;
if ((val & 0x1F) == 0x12 && (crb & 0x80) == 0)
{
val ^= 0x80;
}
}
if ((crb & 0x80) != 0)
{
tod_alarm[addr - 0x8] = val;
}
else
{
if (addr == 0x8)
{
tod_stopped = false;
}
else if (addr == 0xB)
{
tod_stopped = true;
}
}
tod_clock[addr - 0x8] = val;
break;
case 0xC:
if ((cra & 0x40) != 0)
{
sdr_buffered = true;
}
break;
case 0xD:
if ((val & 0x80) != 0)
{
int_setEnabled(val);
}
else
{
int_clearEnabled(val);
}
break;
case 0xE:
if ((val & 0x1) != 0 && (cra & 0x1) == 0)
{
a.setPbToggle(true);
}
a.setControlRegister(val);
break;
case 0xF:
if ((val & 0x1) != 0 && (crb & 0x1) == 0)
{
b.setPbToggle(true);
}
b.setControlRegister((val | (val & 0x40) >> 1));
break;
}
}
}
}

View File

@ -0,0 +1,444 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Common;
// thanks to these fine folks for their research on this buggy as hell chip:
// Simon White (s_a_white@email.com)
// Antti S. Lankila "alankila"
// Andreas Boose (viceteam@t-online.de)
// Alexander Bluhm (mam96ehy@studserv.uni-leipzig.de)
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
sealed public partial class MOS6526_2
{
sealed private class CiaTimer
{
public const int TIMER_CR_START = 0x01;
public const int TIMER_STEP = 0x04;
public const int TIMER_CR_ONESHOT = 0x08;
public const int TIMER_CR_FLOAD = 0x10;
public const int TIMER_PHI2IN = 0x20;
public const int TIMER_MASK = TIMER_CR_START | TIMER_CR_ONESHOT | TIMER_CR_FLOAD | TIMER_PHI2IN;
public const int TIMER_COUNT2 = 0x100;
public const int TIMER_COUNT3 = 0x200;
public const int TIMER_ONESHOT0 = 0x800;
public const int TIMER_ONESHOT = 0x80000;
public const int TIMER_LOAD1 = 0x1000;
public const int TIMER_LOAD = 0x100000;
public const int TIMER_OUT = 0x40000000 << 1;
const int WANTED = TIMER_CR_START | TIMER_PHI2IN | TIMER_COUNT2 | TIMER_COUNT3;
const int UNWANTED = TIMER_OUT | TIMER_CR_FLOAD | TIMER_LOAD1 | TIMER_LOAD;
const int UNWANTED1 = TIMER_CR_START | TIMER_PHI2IN;
const int UNWANTED2 = TIMER_CR_START | TIMER_STEP;
int adj;
bool toggle;
public int state;
int lastControlValue;
int timer;
int latch;
bool pbToggle;
int ciaEventPauseTime;
bool phi1tod;
bool phi1run;
int cycleSkippingEvent = -1;
int nextTick = 0;
Action serialPort;
Action underFlow;
public CiaTimer(Action serialPortCallback, Action underFlowCallback)
{
this.serialPort = serialPortCallback;
this.underFlow = underFlowCallback;
}
public void clock()
{
if (timer != 0 && (state & TIMER_COUNT3) != 0)
{
timer--;
}
adj = state & (TIMER_CR_START | TIMER_CR_ONESHOT | TIMER_PHI2IN);
if ((state & (TIMER_CR_START | TIMER_PHI2IN)) == (TIMER_CR_START | TIMER_PHI2IN))
{
adj |= TIMER_COUNT2;
}
if ((state & TIMER_COUNT2) != 0 || (state & (TIMER_STEP | TIMER_CR_START)) == (TIMER_STEP | TIMER_CR_START))
{
adj |= TIMER_COUNT3;
}
adj |= (state & (TIMER_CR_FLOAD | TIMER_CR_ONESHOT | TIMER_LOAD1 | TIMER_ONESHOT0)) << 8;
state = adj;
if (timer == 0 && (state & TIMER_COUNT3) != 0)
{
state |= TIMER_LOAD | TIMER_OUT;
if ((state & (TIMER_ONESHOT | TIMER_ONESHOT0)) != 0)
{
state &= ~(TIMER_CR_START | TIMER_COUNT2);
}
toggle = (lastControlValue & 0x06) == 0x06;
pbToggle = toggle && !pbToggle;
serialPort();
underFlow();
}
if ((state & TIMER_LOAD) != 0)
{
timer = latch;
state &= ~TIMER_COUNT3;
}
}
public bool getPbToggle()
{
return pbToggle;
}
public int getTimer()
{
return timer;
}
public void reschedule()
{
if ((state & UNWANTED) != 0)
{
return;
}
if ((state & TIMER_COUNT3) != 0)
{
if ((timer & 0xFFFF) > 2 && (state & UNWANTED) == WANTED)
{
ciaEventPauseTime = 1;
cycleSkippingEvent = (timer - 1) & 0xFFFF;
return;
}
nextTick = 1;
return;
}
else
{
if ((state & UNWANTED1) == UNWANTED1 || (state & UNWANTED2) == UNWANTED2)
{
nextTick = 1;
return;
}
ciaEventPauseTime = -1;
return;
}
}
public void reset()
{
timer = 0xFFFF;
latch = 0xFFFF;
pbToggle = false;
state = 0;
ciaEventPauseTime = 0;
cycleSkippingEvent = -1;
}
public void setControlRegister(int cr)
{
state &= ~TIMER_MASK;
state |= cr & TIMER_MASK ^ TIMER_PHI2IN;
lastControlValue = cr;
}
public void setLatchLow(int low)
{
latch = ((latch & 0xFF00) | (low & 0xFF));
if ((state & TIMER_LOAD) != 0)
{
timer = ((timer & 0xFF00) | (low & 0xFF));
}
}
public void setLatchHigh(int high)
{
latch = ((latch & 0xFF) | ((high & 0xFF) << 8));
if ((state & TIMER_LOAD) != 0 || (state & TIMER_CR_START) == 0)
{
timer = latch;
}
}
public void setPbToggle(bool st)
{
pbToggle = st;
}
public void SyncState(Serializer ser)
{
SaveState.SyncObject(ser, this);
}
}
const int INT_NONE = 0x00;
const int INT_UNDERFLOW_A = 0x01;
const int INT_UNDERFLOW_B = 0x02;
const int INT_ALARM = 0x04;
const int INT_SP = 0x08;
const int INT_FLAG = 0x10;
int pra;
int prb;
int ddra;
int ddrb;
int ta;
int tb;
int tod_ten;
int tod_sec;
int tod_min;
int tod_hr;
int sdr;
int icr;
int idr;
int cra;
int crb;
int sdr_out;
bool sdr_buffered;
int sdr_count;
bool tod_latched;
bool tod_stopped;
int[] tod_clock = new int[4];
int[] tod_alarm = new int[4];
int[] tod_latch = new int[4];
int tod_cycles = -1;
int tod_period = -1;
bool postTimerBEvent;
int idr_old;
bool int_delayed;
int bcd_internal;
CiaTimer a;
CiaTimer b;
LatchedPort portA;
LatchedPort portB;
public MOS6526_2(Region region)
{
a = new CiaTimer(serialPortA, underFlowA);
b = new CiaTimer(serialPortB, underFlowB);
portA = new LatchedPort();
portB = new LatchedPort();
switch (region)
{
case Region.NTSC:
tod_period = 14318181 / 140;
break;
case Region.PAL:
tod_period = 17734472 / 180;
break;
}
}
int bcdToByte(int input)
{
return 10 * ((input & 0xF0) >> 4) + (input & 0x0F);
}
int byteToBcd(int input)
{
return (((input / 10) << 4) + (input % 10)) & 0xFF;
}
int int_clear()
{
int_delayed = false;
idr_old = idr;
idr = 0;
return idr_old;
}
void int_clearEnabled(int i)
{
icr &= ~i;
}
void int_reset()
{
int_delayed = false;
}
void int_set(int i)
{
idr |= i;
if ((icr & idr) != 0 && (idr & 0x80) == 0)
{
int_delayed = true;
}
}
void int_setEnabled(int i)
{
icr |= i & ~0x80;
}
void proc_a()
{
if (int_delayed)
{
int_delayed = false;
idr |= 0x80;
}
if (postTimerBEvent)
{
postTimerBEvent = false;
b.state |= CiaTimer.TIMER_STEP;
}
a.clock();
}
void proc_b()
{
b.clock();
}
void reset()
{
a.reset();
b.reset();
sdr_out = 0;
sdr_count = 0;
sdr_buffered = false;
icr = 0;
idr = 0;
idr_old = 0;
int_reset();
portA.Latch = 0xFF;
portB.Latch = 0xFF;
portA.Direction = 0xFF;
portB.Direction = 0xFF;
PortAMask = 0xFF;
PortBMask = 0xFF;
}
void serialPortA()
{
if ((cra & 0x40) != 0)
{
if (sdr_count != 0)
{
if (--sdr_count == 0)
{
triggerInterruptSP();
}
}
if (sdr_count == 0 && sdr_buffered)
{
sdr_out = sdr;
sdr_buffered = false;
sdr_count = 16;
}
}
}
void serialPortB()
{
// nop
}
void tod()
{
tod_cycles += tod_period;
if (!tod_stopped)
{
int todpos = 0;
int t = bcdToByte(tod_clock[todpos]) + 1;
tod_clock[todpos++] = byteToBcd(t % 10);
if (t >= 10)
{
t = bcdToByte(tod_clock[todpos]) + 1;
tod_clock[todpos++] = byteToBcd(t % 60);
if (t >= 60)
{
t = bcdToByte(tod_clock[todpos]) + 1;
tod_clock[todpos++] = byteToBcd(t % 60);
if (t >= 60)
{
int pm = tod_clock[todpos] & 0x80;
t = bcdToByte(tod_clock[todpos] & 0x1F);
if (t == 0x11)
{
pm ^= 0x80;
}
if (t == 0x12)
{
t = 1;
}
else if (++t == 10)
{
t = 0x10;
}
t &= 0x1F;
tod_clock[todpos] = t | pm;
}
}
}
if (tod_clock[0] == tod_alarm[0] &&
tod_clock[1] == tod_alarm[1] &&
tod_clock[2] == tod_alarm[2] &&
tod_clock[3] == tod_alarm[3])
{
triggerInterruptAlarm();
}
}
}
void triggerInterruptAlarm()
{
int_set(INT_ALARM);
}
void triggerInterruptSP()
{
int_set(INT_SP);
}
void triggerInterruptUnderFlowA()
{
int_set(INT_UNDERFLOW_A);
}
void triggerInterruptUnderFlowB()
{
int_set(INT_UNDERFLOW_B);
}
void underFlowA()
{
triggerInterruptUnderFlowA();
if ((crb & 0x41) == 0x41)
{
if ((b.state & CiaTimer.TIMER_CR_START) != 0)
{
postTimerBEvent = true;
}
}
}
void underFlowB()
{
triggerInterruptUnderFlowB();
}
public void SyncState(Serializer ser)
{
SaveState.SyncObject(ser, this);
}
}
}