From ac1aba72ddcb94c57b7ea707cb9bee615f0e331f Mon Sep 17 00:00:00 2001 From: KallDrexx Date: Fri, 2 Jan 2026 21:55:23 -0500 Subject: [PATCH] Moving towards Vic-II implementation --- src/Dotnet6502.C64/Hardware/C64Hal.cs | 3 +- src/Dotnet6502.C64/Hardware/IoMemoryArea.cs | 13 +- .../Hardware/ProgrammableLogicArray.cs | 4 +- src/Dotnet6502.C64/Hardware/Vic2.cs | 127 +++++++++++++++--- src/Dotnet6502.C64/Hardware/Vic2Registers.cs | 35 +++++ src/Dotnet6502.C64/Program.cs | 5 +- src/Dotnet6502.Common/Compilation/Ir6502.cs | 1 + 7 files changed, 164 insertions(+), 24 deletions(-) create mode 100644 src/Dotnet6502.C64/Hardware/Vic2Registers.cs diff --git a/src/Dotnet6502.C64/Hardware/C64Hal.cs b/src/Dotnet6502.C64/Hardware/C64Hal.cs index 2776a29..f1dc1a2 100644 --- a/src/Dotnet6502.C64/Hardware/C64Hal.cs +++ b/src/Dotnet6502.C64/Hardware/C64Hal.cs @@ -1,4 +1,3 @@ -using Dotnet6502.C64.Emulation; using Dotnet6502.Common.Hardware; namespace Dotnet6502.C64.Hardware; @@ -30,7 +29,7 @@ public class C64Hal : Base6502Hal for (var x = 0; x < count; x++) { - _vic2.RunSingleCpuCycle(); + _vic2.RunSingleCycle(); } } } \ No newline at end of file diff --git a/src/Dotnet6502.C64/Hardware/IoMemoryArea.cs b/src/Dotnet6502.C64/Hardware/IoMemoryArea.cs index dba310d..5ee0035 100644 --- a/src/Dotnet6502.C64/Hardware/IoMemoryArea.cs +++ b/src/Dotnet6502.C64/Hardware/IoMemoryArea.cs @@ -4,12 +4,21 @@ namespace Dotnet6502.C64.Hardware; public class IoMemoryArea : IMemoryDevice { - private readonly byte[] _data = new byte[0xdfff - 0xd000 + 1]; + private const int TotalSize = 0xdfff - 0xd000 + 1; + private readonly byte[] _data = new byte[TotalSize]; - public uint Size => 0xdfff - 0xd000 + 1; + public uint Size => TotalSize; public ReadOnlyMemory? RawBlockFromZero => null; + public Memory Vic2Registers => _data.AsMemory()[..0x400]; + public Memory SidRegisters => _data.AsMemory()[0x400..0x400]; + public Memory ColorRam => _data.AsMemory()[0x800..0x400]; + public Memory Cia1 => _data.AsMemory()[0xc00..0x100]; + public Memory Cia2 => _data.AsMemory()[0xd00..0x100]; + public Memory Io1 => _data.AsMemory()[0xe00..0x100]; + public Memory Io2 => _data.AsMemory()[0xf00..0x100]; + public void Write(ushort offset, byte value) { _data[offset] = value; diff --git a/src/Dotnet6502.C64/Hardware/ProgrammableLogicArray.cs b/src/Dotnet6502.C64/Hardware/ProgrammableLogicArray.cs index 93cb26e..c029012 100644 --- a/src/Dotnet6502.C64/Hardware/ProgrammableLogicArray.cs +++ b/src/Dotnet6502.C64/Hardware/ProgrammableLogicArray.cs @@ -21,7 +21,7 @@ public class ProgrammableLogicArray : IMemoryDevice private readonly BasicRamMemoryDevice _ramE000ToFfff; private readonly IoMemoryArea _ioMemoryArea; - public ProgrammableLogicArray() + public ProgrammableLogicArray(IoMemoryArea ioMemoryArea) { BasicRom = new BasicRamMemoryDevice(0xBFFF - 0xA000 + 1); KernelRom = new BasicRamMemoryDevice(0xFFFF - 0xE000 + 1); @@ -29,7 +29,7 @@ public class ProgrammableLogicArray : IMemoryDevice _ramD000ToDfff = new BasicRamMemoryDevice(0xDFFF - 0xD000 + 1); _ramE000ToFfff = new BasicRamMemoryDevice(0xFFFF - 0xE000 + 1); CharacterRom = new BasicRamMemoryDevice(0xDFFF - 0xD000 + 1); - _ioMemoryArea = new IoMemoryArea(); + _ioMemoryArea = ioMemoryArea; _a000ToBfffDevice = new SwappableMemoryDevice(_ramA000ToBfff); _d000ToDfffDevice = new SwappableMemoryDevice(_ramD000ToDfff); diff --git a/src/Dotnet6502.C64/Hardware/Vic2.cs b/src/Dotnet6502.C64/Hardware/Vic2.cs index 7cae4be..934bf3a 100644 --- a/src/Dotnet6502.C64/Hardware/Vic2.cs +++ b/src/Dotnet6502.C64/Hardware/Vic2.cs @@ -7,34 +7,129 @@ namespace Dotnet6502.C64.Hardware; /// public class Vic2 { + // Reference: https://zimmers.net/cbmpics/cbm/c64/vic-ii.txt private const int DotsPerCpuCycle = 8; private const int DotsPerScanline = 520; - private const int VisibleDotsPerScanline = DotsPerScanline - 56; + private const int VisibleDotsPerScanLine = 464; private const int TotalScanlines = 263; - private const int VisibleScanlines = 235; - private const int NonVisibleScanlines = TotalScanlines - VisibleScanlines; + private const int FirstVisibleScanline = 16; + private const int LastVisibleScanLine = 262; + private const int FirstDisplayWindowScanLine = 51; + private const int LastDisplayWindowScanLine = 250; + private const int VisibleScanLines = LastVisibleScanLine - FirstVisibleScanline; private readonly IC64Display _c64Display; - private int _dotCount; - private int _scanlineCount; + private readonly Memory _vic2Registers; + private int _lineCycleCount; + private int _lineDotCount; + private int _currentScanLine; - public Vic2(IC64Display c64Display) + /// + /// AKA VC, 10-bit counter that tracks which character/sprite data to fetch from memory. It acts as the main + /// "position" counter for the current display line + /// + private ushort _videoCounter; + + /// + /// AKA RC, 3-bit counter that tracks which scan line within a character row is currently being rendered + /// + private byte _rasterCounter; + + /// + /// AKA VMLI, Serves as the base value for teh video counter. Stores the VC value at the beginning of each character row + /// + private ushort _videoMatrixLineIndex; + + public Vic2(IC64Display c64Display, IoMemoryArea ioMemoryArea) { _c64Display = c64Display; + _vic2Registers = ioMemoryArea.Vic2Registers; } - public void RunSingleCpuCycle() + public void RunSingleCycle() { - _dotCount += DotsPerCpuCycle; - if (_dotCount >= DotsPerScanline) + var vic2RegisterSpan = _vic2Registers.Span; + + _lineCycleCount++; + _lineDotCount += DotsPerCpuCycle; + if (_lineDotCount >= DotsPerCpuCycle) { - _scanlineCount++; - _dotCount = 0; - if (_scanlineCount > TotalScanlines) - { - _scanlineCount = 0; - _c64Display.RenderFrame(new RgbColor[VisibleDotsPerScanline * VisibleScanlines]); - } + AdvanceToNextScanLine(vic2RegisterSpan); } } + + private void AdvanceToNextScanLine(Span registers) + { + _currentScanLine++; + _lineCycleCount = 0; + _lineDotCount = 0; + + if (_currentScanLine > LastVisibleScanLine) + { + // Vblank starts + _c64Display.RenderFrame(new RgbColor[VisibleDotsPerScanLine * VisibleScanLines]); + _currentScanLine = 0; // vblank period comes before first visible scanlines + } + + // Update the raster counter + var scanLineMsbIsSet = (_currentScanLine & 0b1_0000_0000) > 0; + registers[Vic2Registers.ControlRegister1] = scanLineMsbIsSet + ? (byte)(registers[Vic2Registers.ControlRegister1] & 0b1111_1111) + : (byte)(registers[Vic2Registers.ControlRegister1] & 0b0111_1111); + + registers[Vic2Registers.Raster] = (byte)(_currentScanLine & 0xFF); + + // Reset internal registers + _videoCounter = 0; + _videoMatrixLineIndex = 0; + _rasterCounter = 0; + } + + private void RunMemoryAccessPhase(Span registers) + { + if (_lineCycleCount < 10) + { + // TODO: Sprites (p-access for sprite pointers, s-access for sprite data) + } + + else if (_lineCycleCount < 14) + { + // TODO: Idle + } + + else if (_lineCycleCount < 54) + { + // TOOD: If badline, perform c-access (read screen memory for character codes) + if (IsBadline(registers)) + { + // TODO: implement + } + } + + else if (_lineCycleCount < 58) + { + // TODO: Graphics fetch - perform g-access (fetch character/bitmap data) using the character pointers + // from c-access + } + + else + { + // TODO: Refresh cycles, more sprite checks + } + } + + /// + /// Determines if the current scanline is considered a badline. Badlines are lines where the Vic-II halts the + /// CPU for 40 cycles in order to pull in new character and graphics data from memory. + /// + /// + private bool IsBadline(Span registers) + { + var displayEnable = (registers[Vic2Registers.ControlRegister1] & 0b0001_0000) > 0; + var yScroll = registers[Vic2Registers.ControlRegister1] & 0b0000_0111; + return displayEnable && + _currentScanLine >= 48 && + _currentScanLine <= 247 && + (_currentScanLine & 0b111) == yScroll; + } } \ No newline at end of file diff --git a/src/Dotnet6502.C64/Hardware/Vic2Registers.cs b/src/Dotnet6502.C64/Hardware/Vic2Registers.cs new file mode 100644 index 0000000..0629fe8 --- /dev/null +++ b/src/Dotnet6502.C64/Hardware/Vic2Registers.cs @@ -0,0 +1,35 @@ +namespace Dotnet6502.C64.Hardware; + +public static class Vic2Registers +{ + /// + /// - 7 = Raster bit 8 + /// - 6 = ECM + /// - 5 = BMM + /// - 4 = Display Enable + /// - 3 = RSEL + /// - 2-0 = Yscroll + /// + public const ushort ControlRegister1 = 0x011; + + /// + /// Raster bits 7-0 + /// + public const ushort Raster = 0x012; + + /// + /// - 7-6 = Unused + /// - 5 = RES + /// - 4 = MCM + /// - 3 = CSEL + /// - 2-0 = XScroll + /// + public const ushort ControlRegister2 = 0x016; + + /// + /// - 7-4 = Screen pointer (A13-A10) (aka video screen matrix?) + /// - 3-1 = Bitmapt/Charset pointer (A13-A11) + /// - 0 = unused + /// + public const ushort VmCb = 0x018; +} \ No newline at end of file diff --git a/src/Dotnet6502.C64/Program.cs b/src/Dotnet6502.C64/Program.cs index db99f67..da84997 100644 --- a/src/Dotnet6502.C64/Program.cs +++ b/src/Dotnet6502.C64/Program.cs @@ -9,10 +9,11 @@ var cancellationTokenSource = new CancellationTokenSource(); var cliArgs = CommandLineHandler.Parse(args); Console.WriteLine("Starting Dotnet6502 Commodore64 emulator"); +var ioMemoryArea = new IoMemoryArea(); var pla = await SetupPla(cliArgs); var memoryBus = SetupMemoryBus(); var app = new MonogameApp(false); -var vic2 = new Vic2(app); +var vic2 = new Vic2(app, ioMemoryArea); var hal = new C64Hal(memoryBus, cancellationTokenSource.Token, vic2); var jitCustomizer = new C64JitCustomizer(); var jitCompiler = new JitCompiler(hal, jitCustomizer, memoryBus); @@ -54,7 +55,7 @@ async Task SetupPla(CommandLineHandler.Values values) var kernelRomContents = await File.ReadAllBytesAsync(values.KernelRom.FullName); var charRomContents = await File.ReadAllBytesAsync(values.CharacterRom.FullName); - var programmableLogicArray = new ProgrammableLogicArray(); + var programmableLogicArray = new ProgrammableLogicArray(ioMemoryArea); programmableLogicArray.BasicRom.SetContent(basicRomContents); programmableLogicArray.KernelRom.SetContent(kernelRomContents); programmableLogicArray.CharacterRom.SetContent(charRomContents); diff --git a/src/Dotnet6502.Common/Compilation/Ir6502.cs b/src/Dotnet6502.Common/Compilation/Ir6502.cs index 6f5bb24..a5137ad 100644 --- a/src/Dotnet6502.Common/Compilation/Ir6502.cs +++ b/src/Dotnet6502.Common/Compilation/Ir6502.cs @@ -5,6 +5,7 @@ namespace Dotnet6502.Common.Compilation; /// public static class Ir6502 { + // TODO: Should probably refactor this to not create so much garbage public abstract record Instruction; public abstract record JumpTarget;