From 1e9564a337134aed40eb3c2a77851af3129544cb Mon Sep 17 00:00:00 2001 From: Kabuto Date: Mon, 28 Sep 2015 01:30:58 +0200 Subject: [PATCH] C64 core: tape loading added, lots of bugfixes and improvements --- BizHawk.Client.EmuHawk/MainForm.cs | 4 +- BizHawk.Emulation.Common/Database/Database.cs | 1 + .../BizHawk.Emulation.Cores.csproj | 3 +- .../CPUs/MOS 6502X/Execute.cs | 5 + .../Computers/Commodore64/C64.IDebuggable.cs | 113 +- .../Commodore64/C64.IDisassemblable.cs | 36 + .../Computers/Commodore64/C64.Motherboard.cs | 1 + .../Computers/Commodore64/C64.cs | 179 +- .../CassettePort/CassettePortDevice.cs | 43 +- .../Commodore64/CassettePort/Tape.cs | 93 + .../Computers/Commodore64/MOS/CassettePort.cs | 30 - .../Computers/Commodore64/MOS/MOS6510.cs | 9 +- .../Computers/Commodore64/MOS/MOS6526.cs | 1578 +++++++++-------- .../Computers/Commodore64/MOS/MOS6567.cs | 4 +- .../Computers/Commodore64/MOS/Vic.cs | 5 +- 15 files changed, 1174 insertions(+), 930 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index a289f4d627..80b635e87d 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -1965,7 +1965,7 @@ namespace BizHawk.Client.EmuHawk if (VersionInfo.DeveloperBuild) { return FormatFilter( - "Rom Files", "*.nes;*.fds;*unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.psf;*.minipsf;*.nsf;%ARCH%", + "Rom Files", "*.nes;*.fds;*unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.psf;*.minipsf;*.nsf;%ARCH%", "Music Files", "*.psf;*.minipsf;*.sid;*.nsf", "Disc Images", "*.cue;*.ccd;*.m3u", "NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%", @@ -1986,7 +1986,7 @@ namespace BizHawk.Client.EmuHawk "PlayStation", "*.cue;*.ccd;*.m3u", "PSX Executables (experimental)", "*.exe", "PSF Playstation Sound File", "*.psf;*.minipsf", - "Commodore 64 (experimental)", "*.prg; *.d64, *.g64; *.crt;%ARCH%", + "Commodore 64 (experimental)", "*.prg; *.d64, *.g64; *.crt; *.tap;%ARCH%", "SID Commodore 64 Music File", "*.sid;%ARCH%", "Nintendo 64", "*.z64;*.v64;*.n64", "WonderSwan", "*.ws;*.wsc;%ARCH%", diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index 16d6b65e85..41c4f8eaf2 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -315,6 +315,7 @@ namespace BizHawk.Emulation.Common case ".T64": case ".G64": case ".CRT": + case ".TAP": game.System = "C64"; break; diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 0876ed3bcb..5446a07a1c 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -151,6 +151,7 @@ C64.cs + C64.cs @@ -176,11 +177,11 @@ + - diff --git a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs index 30e0bec3fd..0c5e94c799 100644 --- a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs @@ -2931,5 +2931,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (!rdy_freeze) mi++; } //ExecuteOne + + public bool AtInstructionStart() + { + return Microcode[opcode][mi] >= Uop.End; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs index f01f915c14..6618958233 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs @@ -51,15 +51,110 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } } - public IMemoryCallbackSystem MemoryCallbacks - { - [FeatureNotImplemented] - get { throw new NotImplementedException(); } - } + public bool CanStep(StepType type) + { + switch (type) + { + case StepType.Into: + case StepType.Over: + case StepType.Out: + return true; + default: + return false; + } + } - [FeatureNotImplemented] - public void Step(StepType type) { throw new NotImplementedException(); } - public bool CanStep(StepType type) { return false; } - } + public void Step(StepType type) + { + switch (type) + { + case StepType.Into: + StepInto(); + break; + case StepType.Out: + StepOut(); + break; + case StepType.Over: + StepOver(); + break; + } + } + + private void StepInto() + { + while (board.cpu.AtInstructionStart()) + { + DoCycle(); + } + while (!board.cpu.AtInstructionStart()) + { + DoCycle(); + } + } + + private void StepOver() + { + var instruction = board.cpu.Peek(board.cpu.PC); + + if (instruction == JSR) + { + var destination = board.cpu.PC + JSRSize; + while (board.cpu.PC != destination) + { + StepInto(); + } + } + else + { + StepInto(); + } + } + + private void StepOut() + { + var instr = board.cpu.Peek(board.cpu.PC); + + JSRCount = instr == JSR ? 1 : 0; + + var bailOutFrame = Frame + 1; + + while (true) + { + StepInto(); + instr = board.cpu.Peek(board.cpu.PC); + if (instr == JSR) + { + JSRCount++; + } + else if ((instr == RTS || instr == RTI) && JSRCount <= 0) + { + StepInto(); + JSRCount = 0; + break; + } + else if (instr == RTS || instr == RTI) + { + JSRCount--; + } + else //Emergency Bailout Logic + { + if (Frame == bailOutFrame) + { + break; + } + } + } + } + + private int JSRCount = 0; + + private const byte JSR = 0x20; + private const byte RTI = 0x40; + private const byte RTS = 0x60; + + private const byte JSRSize = 3; + + public IMemoryCallbackSystem MemoryCallbacks { get; private set; } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs new file mode 100644 index 0000000000..a24503805c --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64 +{ + public partial class C64 : IDisassemblable + { + public string Cpu + { + get + { + return "6510"; + } + set + { + } + } + + public string PCRegisterName + { + get { return "PC"; } + } + + public IEnumerable AvailableCpus + { + get { yield return "6510"; } + } + + public string Disassemble(MemoryDomain m, uint addr, out int length) + { + return Components.M6502.MOS6502X.Disassemble((ushort)addr, out length, (a) => m.PeekByte(a)); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs index 99633d578a..e36539f7b7 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs @@ -102,6 +102,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 sid.HardReset(); vic.HardReset(); userPort.HardReset(); + cassPort.HardReset(); // because of how mapping works, the cpu needs to be hard reset twice cpu.HardReset(); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index 3d0028a017..81ab539f64 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -4,6 +4,7 @@ using System.IO; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Computers.Commodore64.MOS; +using System.Windows.Forms; namespace BizHawk.Emulation.Cores.Computers.Commodore64 { @@ -21,8 +22,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 isReleased: false )] [ServiceNotApplicable(typeof(IRegionable), typeof(ISettable<,>))] - sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable - { + sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable + { // framework public C64(CoreComm comm, GameInfo game, byte[] rom, string romextension) { @@ -33,13 +34,37 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 inputFileInfo.Data = rom; inputFileInfo.Extension = romextension; CoreComm = comm; - Init(Region.PAL); + Nullable region = queryUserForRegion(); + if (region == null) + { + throw new Exception("Can't construct new C64 because you didn't choose anything"); + } + Init(region.Value); cyclesPerFrame = board.vic.CyclesPerFrame; SetupMemoryDomains(); - HardReset(); + MemoryCallbacks = new MemoryCallbackSystem(); + HardReset(); (ServiceProvider as BasicServiceProvider).Register(board.vic); - } + } + + + private Nullable queryUserForRegion() + { + Form prompt = new Form() { Width = 160, Height = 120, FormBorderStyle = FormBorderStyle.FixedDialog, Text = "Region selector", StartPosition = FormStartPosition.CenterScreen }; + Label textLabel = new Label() { Left = 10, Top = 10, Width = 260, Text = "Please choose a region:" }; + RadioButton palButton = new RadioButton() { Left = 10, Top = 30, Width = 70, Text = "PAL", Checked = true }; + RadioButton ntscButton = new RadioButton() { Left = 80, Top = 30, Width = 70, Text = "NTSC" }; + Button confirmation = new Button() { Text = "Ok", Left = 40, Width = 80, Top = 60, DialogResult = DialogResult.OK }; + confirmation.Click += (sender, e) => { prompt.Close(); }; + prompt.Controls.Add(textLabel); + prompt.Controls.Add(palButton); + prompt.Controls.Add(ntscButton); + prompt.Controls.Add(confirmation); + prompt.AcceptButton = confirmation; + + return prompt.ShowDialog() == DialogResult.OK ? palButton.Checked ? new Nullable(Region.PAL) : ntscButton.Checked ? new Nullable(Region.NTSC) : null : null; + } // internal variables private int _frame = 0; @@ -63,7 +88,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 _frame = 0; _lagcount = 0; _islag = false; - } + frameCycles = 0; + } // audio/video public void EndAsyncSound() { } //TODO @@ -101,79 +127,69 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } } - // process frame - public void FrameAdvance(bool render, bool rendersound) + int frameCycles; + + private void DoCycle() + { + if (frameCycles == 0) { + board.inputRead = false; + board.PollInput(); + board.cpu.LagCycles = 0; + } + + //disk.Execute(); + board.Execute(); + frameCycles++; + + // load PRG file if needed + if (loadPrg) + { + // check to see if cpu PC is at the BASIC warm start vector + if (board.cpu.PC == ((board.ram.Peek(0x0303) << 8) | board.ram.Peek(0x0302))) + { + //board.ram.Poke(0x0302, 0xAE); + //board.ram.Poke(0x0303, 0xA7); + ////board.ram.Poke(0x0302, board.ram.Peek(0x0308)); + ////board.ram.Poke(0x0303, board.ram.Peek(0x0309)); + + //if (inputFileInfo.Data.Length >= 6) + //{ + // board.ram.Poke(0x0039, inputFileInfo.Data[4]); + // board.ram.Poke(0x003A, inputFileInfo.Data[5]); + //} + PRG.Load(board.pla, inputFileInfo.Data); + loadPrg = false; + } + } + + if (frameCycles == cyclesPerFrame) + { + board.Flush(); + _islag = !board.inputRead; + + if (_islag) + _lagcount++; + frameCycles -= cyclesPerFrame; + _frame++; + + //Console.WriteLine("CPUPC: " + C64Util.ToHex(board.cpu.PC, 4) + " 1541PC: " + C64Util.ToHex(disk.PC, 4)); + + int test = board.cpu.LagCycles; + DriveLightOn = DriveLED; + } + } + + // process frame + public void FrameAdvance(bool render, bool rendersound) { - board.inputRead = false; - board.PollInput(); - board.cpu.LagCycles = 0; + do + { + DoCycle(); + } + while (frameCycles != 0); + } - for (int count = 0; count < cyclesPerFrame; count++) - { - //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) - { - // check to see if cpu PC is at the BASIC warm start vector - if (board.cpu.PC == ((board.ram.Peek(0x0303) << 8) | board.ram.Peek(0x0302))) - { - //board.ram.Poke(0x0302, 0xAE); - //board.ram.Poke(0x0303, 0xA7); - ////board.ram.Poke(0x0302, board.ram.Peek(0x0308)); - ////board.ram.Poke(0x0303, board.ram.Peek(0x0309)); - - //if (inputFileInfo.Data.Length >= 6) - //{ - // board.ram.Poke(0x0039, inputFileInfo.Data[4]); - // board.ram.Poke(0x003A, inputFileInfo.Data[5]); - //} - PRG.Load(board.pla, inputFileInfo.Data); - loadPrg = false; - } - } - } - - board.Flush(); - _islag = !board.inputRead; - - if (_islag) - _lagcount++; - _frame++; - - //Console.WriteLine("CPUPC: " + C64Util.ToHex(board.cpu.PC, 4) + " 1541PC: " + C64Util.ToHex(disk.PC, 4)); - - int test = board.cpu.LagCycles; - DriveLightOn = DriveLED; - } - - private void HandleFirmwareError(string file) + private void HandleFirmwareError(string file) { System.Windows.Forms.MessageBox.Show("the C64 core is referencing a firmware file which could not be found. Please make sure it's in your configured C64 firmwares folder. The referenced filename is: " + file); throw new FileNotFoundException(); @@ -212,6 +228,13 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 { board.cartPort.Connect(cart); } + break; + case @".TAP": + CassettePort.Tape tape = CassettePort.Tape.Load(inputFileInfo.Data); + if (tape != null) + { + board.cassPort.Connect(tape); + } break; case @".PRG": if (inputFileInfo.Data.Length > 2) @@ -238,5 +261,5 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 board.HardReset(); //disk.HardReset(); } - } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs index d8f567713c..970b326e37 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs @@ -9,26 +9,33 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort { public class CassettePortDevice { - public Func ReadDataOutput; - public Func ReadMotor; + public Func ReadDataOutput; + public Func ReadMotor; + Commodore64.CassettePort.Tape tape; - public void HardReset() - { - } + public void HardReset() + { + if (tape != null) tape.rewind(); + } - virtual public bool ReadDataInputBuffer() - { - return true; - } + virtual public bool ReadDataInputBuffer() + { + return tape != null && !ReadMotor() ? tape.read() : true; + } - virtual public bool ReadSenseBuffer() - { - return true; - } + virtual public bool ReadSenseBuffer() + { + return tape == null; // Just assume that "play" is constantly pressed as long as a tape is inserted + } - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - } + public void SyncState(Serializer ser) + { + SaveState.SyncObject(ser, this); + } + + internal void Connect(Tape tape) + { + this.tape = tape; + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs new file mode 100644 index 0000000000..9b0c786f9d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +using BizHawk.Common; + + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort +{ + /** + * This class represents a tape. Only TAP-style tapes are supported for now. + */ + class Tape + { + private byte[] tapeData; + private byte version; + private uint pos, cycle, start, end; + + public Tape(byte version, byte[] tapeData, uint start, uint end) + { + this.version = version; + this.tapeData = tapeData; + this.start = start; + this.end = end; + rewind(); + } + + // Rewinds the tape back to start + public void rewind() + { + pos = start; + cycle = 0; + } + + // Reads from tape, this will tell the caller if the flag pin should be raised + public bool read() + { + if (cycle == 0) + { + Console.WriteLine("Tape @ " + pos.ToString()); + if (pos >= end) + { + return true; + } + else + { + cycle = ((uint)tapeData[pos++])*8; + if (cycle == 0) + { + if (version == 0) + { + cycle = 256 * 8; // unspecified overflow condition + } + else + { + cycle = BitConverter.ToUInt32(tapeData, (int)pos-1)>>8; + pos += 3; + if (cycle == 0) + { + throw new Exception("Bad tape data"); + } + } + } + } + } + + // Send a single negative pulse at the end of a cycle + return --cycle != 0; + } + + // Try to construct a tape file from file data. Returns null if not a tape file, throws exceptions for bad tape files. + // (Note that some error conditions aren't caught right here.) + static public Tape Load(byte[] tapeFile) + { + Tape result = null; + + if (System.Text.Encoding.ASCII.GetString(tapeFile, 0, 12) == "C64-TAPE-RAW") + { + byte version = tapeFile[12]; + if (version > 1) throw new Exception("This tape has an unsupported version"); + uint size = BitConverter.ToUInt32(tapeFile, 16); + if (size + 20 != tapeFile.Length) + { + throw new Exception("Tape file header specifies a length that doesn't match the file size"); + } + result = new Tape(version, tapeFile, 20, (uint)tapeFile.Length); + } + return result; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs deleted file mode 100644 index 26ddeb1b49..0000000000 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using BizHawk.Common; - -namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS -{ - public class CassettePort - { - public Func ReadDataOutput; - public Func ReadMotor; - - public void HardReset() - { - } - - virtual public bool ReadDataInputBuffer() - { - return true; - } - - virtual public bool ReadSenseBuffer() - { - return true; - } - - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs index 557312b0a9..6149c4cf42 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs @@ -103,9 +103,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS } } - // ------------------------------------ + internal bool AtInstructionStart() + { + return cpu.AtInstructionStart(); + } - public ushort PC + // ------------------------------------ + + public ushort PC { get { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs index 97a8ab2185..341fd8a728 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs @@ -3,791 +3,795 @@ using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { - // MOS technology 6526 "CIA" - // - // emulation notes: - // * CS, R/W and RS# pins are not emulated. (not needed) - // * A low RES pin is emulated via HardReset(). - - sealed public class MOS6526 - { - // ------------------------------------ - - enum InMode - { - Phase2, - CNT, - TimerAUnderflow, - TimerAUnderflowCNT - } - - enum OutMode - { - Pulse, - Toggle - } - - enum RunMode - { - Continuous, - Oneshot - } - - enum SPMode - { - Input, - Output - } - - static byte[] PBOnBit = new byte[] { 0x40, 0x80 }; - static byte[] PBOnMask = new byte[] { 0xBF, 0x7F }; - - // ------------------------------------ - - public Func ReadCNT; - public Func ReadFlag; - public Func ReadSP; - - // ------------------------------------ - - bool alarmSelect; - Region chipRegion; - bool cntPos; - bool enableIntAlarm; - bool enableIntFlag; - bool enableIntSP; - bool[] enableIntTimer; - bool intAlarm; - bool intFlag; - bool intSP; - bool[] intTimer; - bool pinCnt; - bool pinCntLast; - bool pinPC; - bool pinSP; - byte sr; - int[] timerDelay; - InMode[] timerInMode; - OutMode[] timerOutMode; - bool[] timerPortEnable; - bool[] timerPulse; - RunMode[] timerRunMode; - SPMode timerSPMode; - byte[] tod; - byte[] todAlarm; - bool todAlarmPM; - int todCounter; - int todCounterLatch; - bool todIn; - bool todPM; - - // ------------------------------------ - - public MOS6526(Region region) - { - chipRegion = region; - enableIntTimer = new bool[2]; - intTimer = new bool[2]; - timerDelay = new int[2]; - timerInMode = new InMode[2]; - timerOutMode = new OutMode[2]; - timerPortEnable = new bool[2]; - timerPulse = new bool[2]; - timerRunMode = new RunMode[2]; - tod = new byte[4]; - todAlarm = new byte[4]; - SetTodIn(chipRegion); - - portA = new LatchedPort(); - portB = new LatchedPort(); - timer = new int[2]; - timerLatch = new int[2]; - timerOn = new bool[2]; - underflow = new bool[2]; - - pinSP = true; - } - - // ------------------------------------ - - public void ExecutePhase1() - { - // unsure if the timer actually operates in ph1 - pinIRQ = !( - (intTimer[0] && enableIntTimer[0]) || - (intTimer[1] && enableIntTimer[1]) || - (intAlarm && enableIntAlarm) || - (intSP && enableIntSP) || - (intFlag && enableIntFlag) - ); - } - - public void ExecutePhase2() - { - { - bool sumCnt = ReadCNT(); - cntPos |= (!pinCntLast && sumCnt); - pinCntLast = sumCnt; - - pinPC = true; - TODRun(); - - if (timerPulse[0]) - { - portA.Latch &= PBOnMask[0]; - } - if (timerPulse[1]) - { - portB.Latch &= PBOnMask[1]; - } - - if (timerDelay[0] == 0) - TimerRun(0); - else - timerDelay[0]--; - - if (timerDelay[1] == 0) - TimerRun(1); - else - timerDelay[1]--; - - intAlarm |= ( - tod[0] == todAlarm[0] && - tod[1] == todAlarm[1] && - tod[2] == todAlarm[2] && - tod[3] == todAlarm[3] && - todPM == todAlarmPM); - - cntPos = false; - underflow[0] = false; - underflow[1] = false; - } - } - - public void HardReset() - { - HardResetInternal(); - alarmSelect = false; - cntPos = false; - enableIntAlarm = false; - enableIntFlag = false; - enableIntSP = false; - enableIntTimer[0] = false; - enableIntTimer[1] = false; - intAlarm = false; - intFlag = false; - intSP = false; - intTimer[0] = false; - intTimer[1] = false; - sr = 0; - timerDelay[0] = 0; - timerDelay[1] = 0; - timerInMode[0] = InMode.Phase2; - timerInMode[1] = InMode.Phase2; - timerOn[0] = false; - timerOn[1] = false; - timerOutMode[0] = OutMode.Pulse; - timerOutMode[1] = OutMode.Pulse; - timerPortEnable[0] = false; - timerPortEnable[1] = false; - timerPulse[0] = false; - timerPulse[1] = false; - timerRunMode[0] = RunMode.Continuous; - timerRunMode[1] = RunMode.Continuous; - timerSPMode = SPMode.Input; - tod[0] = 0; - tod[1] = 0; - tod[2] = 0; - tod[3] = 0x12; - todAlarm[0] = 0; - todAlarm[1] = 0; - todAlarm[2] = 0; - todAlarm[3] = 0; - todCounter = todCounterLatch; - todIn = (chipRegion == Region.PAL); - todPM = false; - - pinCnt = false; - pinPC = true; - } - - private void SetTodIn(Region region) - { - switch (region) - { - case Region.NTSC: - todCounterLatch = 14318181 / 140; - todIn = false; - break; - case Region.PAL: - todCounterLatch = 17734472 / 180; - todIn = true; - break; - } - } - - // ------------------------------------ - - private byte BCDAdd(byte i, byte 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 (byte)(result & 0xFF); - } - } - - private void TimerRun(int index) - { - - { - if (timerOn[index]) - { - int t = timer[index]; - bool u = false; - - { - switch (timerInMode[index]) - { - case InMode.CNT: - // CNT positive - if (cntPos) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - case InMode.Phase2: - // every clock - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - break; - case InMode.TimerAUnderflow: - // every underflow[0] - if (underflow[0]) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - case InMode.TimerAUnderflowCNT: - // every underflow[0] while CNT high - if (underflow[0] && pinCnt) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - } - - // underflow? - if (u) - { - timerDelay[index] = 1; - t = timerLatch[index]; - if (timerRunMode[index] == RunMode.Oneshot) - timerOn[index] = false; - - if (timerPortEnable[index]) - { - // force port B bit to output - portB.Direction |= PBOnBit[index]; - switch (timerOutMode[index]) - { - case OutMode.Pulse: - timerPulse[index] = true; - portB.Latch |= PBOnBit[index]; - break; - case OutMode.Toggle: - portB.Latch ^= PBOnBit[index]; - break; - } - } - } - - underflow[index] = u; - timer[index] = t; - } - } - } - } - - private void TODRun() - { - - { - bool todV; - - if (todCounter == 0) - { - todCounter = todCounterLatch; - tod[0] = BCDAdd(tod[0], 1, out todV); - if (tod[0] >= 10) - { - tod[0] = 0; - tod[1] = BCDAdd(tod[1], 1, out todV); - if (todV) - { - tod[1] = 0; - tod[2] = BCDAdd(tod[2], 1, out todV); - if (todV) - { - tod[2] = 0; - tod[3] = BCDAdd(tod[3], 1, out todV); - if (tod[3] > 12) - { - tod[3] = 1; - } - else if (tod[3] == 12) - { - todPM = !todPM; - } - } - } - } - } - todCounter--; - } - } - - // ------------------------------------ - - public byte Peek(long addr) - { - return ReadRegister((int)addr & 0xF); - } - - public void Poke(long addr, byte val) - { - WriteRegister((int)(addr & 0xF), val); - } - - public byte Peek(int addr) - { - return ReadRegister((int)addr & 0xF); - } - - public void Poke(int addr, byte val) - { - WriteRegister((int)(addr & 0xF), val); - } - - public byte Read(int addr) - { - return Read(addr, 0xFF); - } - - public byte Read(int addr, byte mask) - { - addr &= 0xF; - byte val; - - switch (addr) - { - case 0x01: - val = ReadRegister(addr); - pinPC = false; - break; - case 0x0D: - val = ReadRegister(addr); - intTimer[0] = false; - intTimer[1] = false; - intAlarm = false; - intSP = false; - intFlag = false; - pinIRQ = true; - break; - default: - val = ReadRegister(addr); - break; - } - - val &= mask; - return val; - } - - public bool ReadCNTBuffer() - { - return pinCnt; - } - - public bool ReadPCBuffer() - { - return pinPC; - } - - private byte ReadRegister(int addr) - { - byte val = 0x00; //unused pin value - int timerVal; - - switch (addr) - { - case 0x0: - val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask); - break; - case 0x1: - val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask); - break; - case 0x2: - val = portA.Direction; - break; - case 0x3: - val = portB.Direction; - break; - case 0x4: - timerVal = ReadTimerValue(0); - val = (byte)(timerVal & 0xFF); - break; - case 0x5: - timerVal = ReadTimerValue(0); - val = (byte)(timerVal >> 8); - break; - case 0x6: - timerVal = ReadTimerValue(1); - val = (byte)(timerVal & 0xFF); - break; - case 0x7: - timerVal = ReadTimerValue(1); - val = (byte)(timerVal >> 8); - break; - case 0x8: - val = tod[0]; - break; - case 0x9: - val = tod[1]; - break; - case 0xA: - val = tod[2]; - break; - case 0xB: - val = tod[3]; - break; - case 0xC: - val = sr; - break; - case 0xD: - val = (byte)( - (intTimer[0] ? 0x01 : 0x00) | - (intTimer[1] ? 0x02 : 0x00) | - (intAlarm ? 0x04 : 0x00) | - (intSP ? 0x08 : 0x00) | - (intFlag ? 0x10 : 0x00) | - (!pinIRQ ? 0x80 : 0x00) - ); - break; - case 0xE: - val = (byte)( - (timerOn[0] ? 0x01 : 0x00) | - (timerPortEnable[0] ? 0x02 : 0x00) | - (todIn ? 0x80 : 0x00)); - if (timerOutMode[0] == OutMode.Toggle) - val |= 0x04; - if (timerRunMode[0] == RunMode.Oneshot) - val |= 0x08; - if (timerInMode[0] == InMode.CNT) - val |= 0x20; - if (timerSPMode == SPMode.Output) - val |= 0x40; - break; - case 0xF: - val = (byte)( - (timerOn[1] ? 0x01 : 0x00) | - (timerPortEnable[1] ? 0x02 : 0x00) | - (alarmSelect ? 0x80 : 0x00)); - if (timerOutMode[1] == OutMode.Toggle) - val |= 0x04; - if (timerRunMode[1] == RunMode.Oneshot) - val |= 0x08; - switch (timerInMode[1]) - { - case InMode.CNT: - val |= 0x20; - break; - case InMode.TimerAUnderflow: - val |= 0x40; - break; - case InMode.TimerAUnderflowCNT: - val |= 0x60; - break; - } - break; - } - - return val; - } - - public bool ReadSPBuffer() - { - return pinSP; - } - - private int ReadTimerValue(int index) - { - if (timerOn[index]) - { - if (timer[index] == 0) - return timerLatch[index]; - else - return timer[index]; - } - else - { - return timer[index]; - } - } - - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - - public void Write(int addr, byte val) - { - Write(addr, val, 0xFF); - } - - public void Write(int addr, byte val, byte mask) - { - addr &= 0xF; - val &= mask; - val |= (byte)(ReadRegister(addr) & ~mask); - - switch (addr) - { - case 0x1: - WriteRegister(addr, val); - pinPC = false; - break; - case 0x5: - WriteRegister(addr, val); - if (!timerOn[0]) - timer[0] = timerLatch[0]; - break; - case 0x7: - WriteRegister(addr, val); - if (!timerOn[1]) - timer[1] = timerLatch[1]; - break; - case 0xE: - WriteRegister(addr, val); - if ((val & 0x10) != 0) - timer[0] = timerLatch[0]; - break; - case 0xF: - WriteRegister(addr, val); - if ((val & 0x10) != 0) - timer[1] = timerLatch[1]; - break; - default: - WriteRegister(addr, val); - break; - } - } - - public void WriteRegister(int addr, byte val) - { - bool intReg; - - switch (addr) - { - case 0x0: - portA.Latch = val; - break; - case 0x1: - portB.Latch = val; - break; - case 0x2: - portA.Direction = val; - break; - case 0x3: - portB.Direction = val; - break; - case 0x4: - timerLatch[0] &= 0xFF00; - timerLatch[0] |= val; - break; - case 0x5: - timerLatch[0] &= 0x00FF; - timerLatch[0] |= val << 8; - break; - case 0x6: - timerLatch[1] &= 0xFF00; - timerLatch[1] |= val; - break; - case 0x7: - timerLatch[1] &= 0x00FF; - timerLatch[1] |= val << 8; - break; - case 0x8: - if (alarmSelect) - todAlarm[0] = (byte)(val & 0xF); - else - tod[0] = (byte)(val & 0xF); - break; - case 0x9: - if (alarmSelect) - todAlarm[1] = (byte)(val & 0x7F); - else - tod[1] = (byte)(val & 0x7F); - break; - case 0xA: - if (alarmSelect) - todAlarm[2] = (byte)(val & 0x7F); - else - tod[2] = (byte)(val & 0x7F); - break; - case 0xB: - if (alarmSelect) - { - todAlarm[3] = (byte)(val & 0x1F); - todAlarmPM = ((val & 0x80) != 0); - } - else - { - tod[3] = (byte)(val & 0x1F); - todPM = ((val & 0x80) != 0); - } - break; - case 0xC: - sr = val; - break; - case 0xD: - intReg = ((val & 0x80) != 0); - if ((val & 0x01) != 0) - enableIntTimer[0] = intReg; - if ((val & 0x02) != 0) - enableIntTimer[1] = intReg; - if ((val & 0x04) != 0) - enableIntAlarm = intReg; - if ((val & 0x08) != 0) - enableIntSP = intReg; - if ((val & 0x10) != 0) - enableIntFlag = intReg; - break; - case 0xE: - if ((val & 0x01) != 0 && !timerOn[0]) - timerDelay[0] = 2; - timerOn[0] = ((val & 0x01) != 0); - timerPortEnable[0] = ((val & 0x02) != 0); - timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; - timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; - timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2; - timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input; - todIn = ((val & 0x80) != 0); - break; - case 0xF: - if ((val & 0x01) != 0 && !timerOn[1]) - timerDelay[1] = 2; - timerOn[1] = ((val & 0x01) != 0); - timerPortEnable[1] = ((val & 0x02) != 0); - timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; - timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; - switch (val & 0x60) - { - case 0x00: timerInMode[1] = InMode.Phase2; break; - case 0x20: timerInMode[1] = InMode.CNT; break; - case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break; - case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break; - } - alarmSelect = ((val & 0x80) != 0); - break; - } - } - - // ------------------------------------ - - public byte PortAMask = 0xFF; - public byte PortBMask = 0xFF; - - bool pinIRQ; - LatchedPort portA; - LatchedPort portB; - int[] timer; - int[] timerLatch; - bool[] timerOn; - bool[] underflow; - - public Func ReadPortA = (() => { return 0xFF; }); - public Func ReadPortB = (() => { return 0xFF; }); - - void HardResetInternal() - { - timer[0] = 0xFFFF; - timer[1] = 0xFFFF; - timerLatch[0] = timer[0]; - timerLatch[1] = timer[1]; - pinIRQ = true; - } - - public byte PortAData - { - get - { - return portA.ReadOutput(); - } - } - - 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 bool ReadIRQBuffer() { return pinIRQ; } - } + // MOS technology 6526 "CIA" + // + // emulation notes: + // * CS, R/W and RS# pins are not emulated. (not needed) + // * A low RES pin is emulated via HardReset(). + + sealed public class MOS6526 + { + // ------------------------------------ + + enum InMode + { + Phase2, + CNT, + TimerAUnderflow, + TimerAUnderflowCNT + } + + enum OutMode + { + Pulse, + Toggle + } + + enum RunMode + { + Continuous, + Oneshot + } + + enum SPMode + { + Input, + Output + } + + static byte[] PBOnBit = new byte[] { 0x40, 0x80 }; + static byte[] PBOnMask = new byte[] { 0xBF, 0x7F }; + + // ------------------------------------ + + public Func ReadCNT; + public Func ReadFlag; + public Func ReadSP; + + // ------------------------------------ + + bool alarmSelect; + Region chipRegion; + bool cntPos; + bool enableIntAlarm; + bool enableIntFlag; + bool enableIntSP; + bool[] enableIntTimer; + bool intAlarm; + bool intFlag; + bool intSP; + bool[] intTimer; + bool pinCnt; + bool pinCntLast; + bool pinPC; + bool pinSP; + byte sr; + int[] timerDelay; + InMode[] timerInMode; + OutMode[] timerOutMode; + bool[] timerPortEnable; + bool[] timerPulse; + RunMode[] timerRunMode; + SPMode timerSPMode; + byte[] tod; + byte[] todAlarm; + bool todAlarmPM; + int todCounter; + int todCounterLatch; + bool todIn; + bool todPM; + bool oldFlag; + // ------------------------------------ + + public MOS6526(Region region) + { + chipRegion = region; + enableIntTimer = new bool[2]; + intTimer = new bool[2]; + timerDelay = new int[2]; + timerInMode = new InMode[2]; + timerOutMode = new OutMode[2]; + timerPortEnable = new bool[2]; + timerPulse = new bool[2]; + timerRunMode = new RunMode[2]; + tod = new byte[4]; + todAlarm = new byte[4]; + SetTodIn(chipRegion); + + portA = new LatchedPort(); + portB = new LatchedPort(); + timer = new int[2]; + timerLatch = new int[2]; + timerOn = new bool[2]; + underflow = new bool[2]; + + pinSP = true; + } + + // ------------------------------------ + + public void ExecutePhase1() + { + // unsure if the timer actually operates in ph1 + pinIRQ = !( + (intTimer[0] && enableIntTimer[0]) || + (intTimer[1] && enableIntTimer[1]) || + (intAlarm && enableIntAlarm) || + (intSP && enableIntSP) || + (intFlag && enableIntFlag) + ); + } + + public void ExecutePhase2() + { + { + bool sumCnt = ReadCNT(); + cntPos |= (!pinCntLast && sumCnt); + pinCntLast = sumCnt; + + pinPC = true; + TODRun(); + + if (timerPulse[0]) + { + portA.Latch &= PBOnMask[0]; + } + if (timerPulse[1]) + { + portB.Latch &= PBOnMask[1]; + } + + if (timerDelay[0] == 0) + TimerRun(0); + else + timerDelay[0]--; + + if (timerDelay[1] == 0) + TimerRun(1); + else + timerDelay[1]--; + + intAlarm |= ( + tod[0] == todAlarm[0] && + tod[1] == todAlarm[1] && + tod[2] == todAlarm[2] && + tod[3] == todAlarm[3] && + todPM == todAlarmPM); + + cntPos = false; + underflow[0] = false; + underflow[1] = false; + + bool newFlag = ReadFlag(); + intFlag |= oldFlag && !newFlag; + oldFlag = newFlag; + } + } + + public void HardReset() + { + HardResetInternal(); + alarmSelect = false; + cntPos = false; + enableIntAlarm = false; + enableIntFlag = false; + enableIntSP = false; + enableIntTimer[0] = false; + enableIntTimer[1] = false; + intAlarm = false; + intFlag = false; + intSP = false; + intTimer[0] = false; + intTimer[1] = false; + sr = 0; + timerDelay[0] = 0; + timerDelay[1] = 0; + timerInMode[0] = InMode.Phase2; + timerInMode[1] = InMode.Phase2; + timerOn[0] = false; + timerOn[1] = false; + timerOutMode[0] = OutMode.Pulse; + timerOutMode[1] = OutMode.Pulse; + timerPortEnable[0] = false; + timerPortEnable[1] = false; + timerPulse[0] = false; + timerPulse[1] = false; + timerRunMode[0] = RunMode.Continuous; + timerRunMode[1] = RunMode.Continuous; + timerSPMode = SPMode.Input; + tod[0] = 0; + tod[1] = 0; + tod[2] = 0; + tod[3] = 0x12; + todAlarm[0] = 0; + todAlarm[1] = 0; + todAlarm[2] = 0; + todAlarm[3] = 0; + todCounter = todCounterLatch; + todIn = (chipRegion == Region.PAL); + todPM = false; + + pinCnt = false; + pinPC = true; + } + + private void SetTodIn(Region region) + { + switch (region) + { + case Region.NTSC: + todCounterLatch = 14318181 / 140; + todIn = false; + break; + case Region.PAL: + todCounterLatch = 17734472 / 180; + todIn = true; + break; + } + } + + // ------------------------------------ + + private byte BCDAdd(byte i, byte 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 (byte)(result & 0xFF); + } + } + + private void TimerRun(int index) + { + + { + if (timerOn[index]) + { + int t = timer[index]; + bool u = false; + + { + switch (timerInMode[index]) + { + case InMode.CNT: + // CNT positive + if (cntPos) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + case InMode.Phase2: + // every clock + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + break; + case InMode.TimerAUnderflow: + // every underflow[0] + if (underflow[0]) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + case InMode.TimerAUnderflowCNT: + // every underflow[0] while CNT high + if (underflow[0] && pinCnt) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + } + + // underflow? + if (u) + { + timerDelay[index] = 1; + t = timerLatch[index]; + if (timerRunMode[index] == RunMode.Oneshot) + timerOn[index] = false; + + if (timerPortEnable[index]) + { + // force port B bit to output + portB.Direction |= PBOnBit[index]; + switch (timerOutMode[index]) + { + case OutMode.Pulse: + timerPulse[index] = true; + portB.Latch |= PBOnBit[index]; + break; + case OutMode.Toggle: + portB.Latch ^= PBOnBit[index]; + break; + } + } + } + + underflow[index] = u; + timer[index] = t; + } + } + } + } + + private void TODRun() + { + + { + bool todV; + + if (todCounter == 0) + { + todCounter = todCounterLatch; + tod[0] = BCDAdd(tod[0], 1, out todV); + if (tod[0] >= 10) + { + tod[0] = 0; + tod[1] = BCDAdd(tod[1], 1, out todV); + if (todV) + { + tod[1] = 0; + tod[2] = BCDAdd(tod[2], 1, out todV); + if (todV) + { + tod[2] = 0; + tod[3] = BCDAdd(tod[3], 1, out todV); + if (tod[3] > 12) + { + tod[3] = 1; + } + else if (tod[3] == 12) + { + todPM = !todPM; + } + } + } + } + } + todCounter--; + } + } + + // ------------------------------------ + + public byte Peek(long addr) + { + return ReadRegister((int)addr & 0xF); + } + + public void Poke(long addr, byte val) + { + WriteRegister((int)(addr & 0xF), val); + } + + public byte Peek(int addr) + { + return ReadRegister((int)addr & 0xF); + } + + public void Poke(int addr, byte val) + { + WriteRegister((int)(addr & 0xF), val); + } + + public byte Read(int addr) + { + return Read(addr, 0xFF); + } + + public byte Read(int addr, byte mask) + { + addr &= 0xF; + byte val; + + switch (addr) + { + case 0x01: + val = ReadRegister(addr); + pinPC = false; + break; + case 0x0D: + val = ReadRegister(addr); + intTimer[0] = false; + intTimer[1] = false; + intAlarm = false; + intSP = false; + intFlag = false; + pinIRQ = true; + break; + default: + val = ReadRegister(addr); + break; + } + + val &= mask; + return val; + } + + public bool ReadCNTBuffer() + { + return pinCnt; + } + + public bool ReadPCBuffer() + { + return pinPC; + } + + private byte ReadRegister(int addr) + { + byte val = 0x00; //unused pin value + int timerVal; + + switch (addr) + { + case 0x0: + val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask); + break; + case 0x1: + val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask); + break; + case 0x2: + val = portA.Direction; + break; + case 0x3: + val = portB.Direction; + break; + case 0x4: + timerVal = ReadTimerValue(0); + val = (byte)(timerVal & 0xFF); + break; + case 0x5: + timerVal = ReadTimerValue(0); + val = (byte)(timerVal >> 8); + break; + case 0x6: + timerVal = ReadTimerValue(1); + val = (byte)(timerVal & 0xFF); + break; + case 0x7: + timerVal = ReadTimerValue(1); + val = (byte)(timerVal >> 8); + break; + case 0x8: + val = tod[0]; + break; + case 0x9: + val = tod[1]; + break; + case 0xA: + val = tod[2]; + break; + case 0xB: + val = tod[3]; + break; + case 0xC: + val = sr; + break; + case 0xD: + val = (byte)( + (intTimer[0] ? 0x01 : 0x00) | + (intTimer[1] ? 0x02 : 0x00) | + (intAlarm ? 0x04 : 0x00) | + (intSP ? 0x08 : 0x00) | + (intFlag ? 0x10 : 0x00) | + (!pinIRQ ? 0x80 : 0x00) + ); + break; + case 0xE: + val = (byte)( + (timerOn[0] ? 0x01 : 0x00) | + (timerPortEnable[0] ? 0x02 : 0x00) | + (todIn ? 0x80 : 0x00)); + if (timerOutMode[0] == OutMode.Toggle) + val |= 0x04; + if (timerRunMode[0] == RunMode.Oneshot) + val |= 0x08; + if (timerInMode[0] == InMode.CNT) + val |= 0x20; + if (timerSPMode == SPMode.Output) + val |= 0x40; + break; + case 0xF: + val = (byte)( + (timerOn[1] ? 0x01 : 0x00) | + (timerPortEnable[1] ? 0x02 : 0x00) | + (alarmSelect ? 0x80 : 0x00)); + if (timerOutMode[1] == OutMode.Toggle) + val |= 0x04; + if (timerRunMode[1] == RunMode.Oneshot) + val |= 0x08; + switch (timerInMode[1]) + { + case InMode.CNT: + val |= 0x20; + break; + case InMode.TimerAUnderflow: + val |= 0x40; + break; + case InMode.TimerAUnderflowCNT: + val |= 0x60; + break; + } + break; + } + + return val; + } + + public bool ReadSPBuffer() + { + return pinSP; + } + + private int ReadTimerValue(int index) + { + if (timerOn[index]) + { + if (timer[index] == 0) + return timerLatch[index]; + else + return timer[index]; + } + else + { + return timer[index]; + } + } + + public void SyncState(Serializer ser) + { + SaveState.SyncObject(ser, this); + } + + public void Write(int addr, byte val) + { + Write(addr, val, 0xFF); + } + + public void Write(int addr, byte val, byte mask) + { + addr &= 0xF; + val &= mask; + val |= (byte)(ReadRegister(addr) & ~mask); + + switch (addr) + { + case 0x1: + WriteRegister(addr, val); + pinPC = false; + break; + case 0x5: + WriteRegister(addr, val); + if (!timerOn[0]) + timer[0] = timerLatch[0]; + break; + case 0x7: + WriteRegister(addr, val); + if (!timerOn[1]) + timer[1] = timerLatch[1]; + break; + case 0xE: + WriteRegister(addr, val); + if ((val & 0x10) != 0) + timer[0] = timerLatch[0]; + break; + case 0xF: + WriteRegister(addr, val); + if ((val & 0x10) != 0) + timer[1] = timerLatch[1]; + break; + default: + WriteRegister(addr, val); + break; + } + } + + public void WriteRegister(int addr, byte val) + { + bool intReg; + + switch (addr) + { + case 0x0: + portA.Latch = val; + break; + case 0x1: + portB.Latch = val; + break; + case 0x2: + portA.Direction = val; + break; + case 0x3: + portB.Direction = val; + break; + case 0x4: + timerLatch[0] &= 0xFF00; + timerLatch[0] |= val; + break; + case 0x5: + timerLatch[0] &= 0x00FF; + timerLatch[0] |= val << 8; + break; + case 0x6: + timerLatch[1] &= 0xFF00; + timerLatch[1] |= val; + break; + case 0x7: + timerLatch[1] &= 0x00FF; + timerLatch[1] |= val << 8; + break; + case 0x8: + if (alarmSelect) + todAlarm[0] = (byte)(val & 0xF); + else + tod[0] = (byte)(val & 0xF); + break; + case 0x9: + if (alarmSelect) + todAlarm[1] = (byte)(val & 0x7F); + else + tod[1] = (byte)(val & 0x7F); + break; + case 0xA: + if (alarmSelect) + todAlarm[2] = (byte)(val & 0x7F); + else + tod[2] = (byte)(val & 0x7F); + break; + case 0xB: + if (alarmSelect) + { + todAlarm[3] = (byte)(val & 0x1F); + todAlarmPM = ((val & 0x80) != 0); + } + else + { + tod[3] = (byte)(val & 0x1F); + todPM = ((val & 0x80) != 0); + } + break; + case 0xC: + sr = val; + break; + case 0xD: + intReg = ((val & 0x80) != 0); + if ((val & 0x01) != 0) + enableIntTimer[0] = intReg; + if ((val & 0x02) != 0) + enableIntTimer[1] = intReg; + if ((val & 0x04) != 0) + enableIntAlarm = intReg; + if ((val & 0x08) != 0) + enableIntSP = intReg; + if ((val & 0x10) != 0) + enableIntFlag = intReg; + break; + case 0xE: + if ((val & 0x01) != 0 && !timerOn[0]) + timerDelay[0] = 2; + timerOn[0] = ((val & 0x01) != 0); + timerPortEnable[0] = ((val & 0x02) != 0); + timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; + timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; + timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2; + timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input; + todIn = ((val & 0x80) != 0); + break; + case 0xF: + if ((val & 0x01) != 0 && !timerOn[1]) + timerDelay[1] = 2; + timerOn[1] = ((val & 0x01) != 0); + timerPortEnable[1] = ((val & 0x02) != 0); + timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; + timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; + switch (val & 0x60) + { + case 0x00: timerInMode[1] = InMode.Phase2; break; + case 0x20: timerInMode[1] = InMode.CNT; break; + case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break; + case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break; + } + alarmSelect = ((val & 0x80) != 0); + break; + } + } + + // ------------------------------------ + + public byte PortAMask = 0xFF; + public byte PortBMask = 0xFF; + + bool pinIRQ; + LatchedPort portA; + LatchedPort portB; + int[] timer; + int[] timerLatch; + bool[] timerOn; + bool[] underflow; + + public Func ReadPortA = (() => { return 0xFF; }); + public Func ReadPortB = (() => { return 0xFF; }); + + void HardResetInternal() + { + timer[0] = 0xFFFF; + timer[1] = 0xFFFF; + timerLatch[0] = timer[0]; + timerLatch[1] = timer[1]; + pinIRQ = true; + } + + public byte PortAData + { + get + { + return portA.ReadOutput(); + } + } + + 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 bool ReadIRQBuffer() { return pinIRQ; } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs index 939e8211f8..b68dc7af8f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs @@ -11,8 +11,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS static int vblankstart = 0x00D % lines; static int vblankend = 0x018 % lines; static int hblankoffset = 20; - static int hblankstart = (0x18C + hblankoffset) % scanwidth; - static int hblankend = (0x1F0 + hblankoffset) % scanwidth; + static int hblankstart = (0x18C + hblankoffset) % scanwidth - 8; // -8 because the VIC repeats internal pixel cycles around 0x18C + static int hblankend = (0x1F0 + hblankoffset) % scanwidth - 8; static int[] timing = Vic.TimingBuilder_XRaster(0x19C, 0x200, scanwidth, 0x18C, 8); static int[] fetch = Vic.TimingBuilder_Fetch(timing, 0x174); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs index ba587c6360..f58517536b 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs @@ -74,7 +74,10 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS lastRasterLine = rasterLine; } - // display enable compare + // display enable compare + if (rasterLine == 0) + badlineEnable = false; + if (rasterLine == 0x030) badlineEnable |= displayEnable;