Moving towards Vic-II implementation

This commit is contained in:
KallDrexx
2026-01-02 21:55:23 -05:00
parent ed4e0d6206
commit ac1aba72dd
7 changed files with 164 additions and 24 deletions
+1 -2
View File
@@ -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();
}
}
}
+11 -2
View File
@@ -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<byte>? RawBlockFromZero => null;
public Memory<byte> Vic2Registers => _data.AsMemory()[..0x400];
public Memory<byte> SidRegisters => _data.AsMemory()[0x400..0x400];
public Memory<byte> ColorRam => _data.AsMemory()[0x800..0x400];
public Memory<byte> Cia1 => _data.AsMemory()[0xc00..0x100];
public Memory<byte> Cia2 => _data.AsMemory()[0xd00..0x100];
public Memory<byte> Io1 => _data.AsMemory()[0xe00..0x100];
public Memory<byte> Io2 => _data.AsMemory()[0xf00..0x100];
public void Write(ushort offset, byte value)
{
_data[offset] = value;
@@ -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);
+111 -16
View File
@@ -7,34 +7,129 @@ namespace Dotnet6502.C64.Hardware;
/// </summary>
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<byte> _vic2Registers;
private int _lineCycleCount;
private int _lineDotCount;
private int _currentScanLine;
public Vic2(IC64Display c64Display)
/// <summary>
/// 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
/// </summary>
private ushort _videoCounter;
/// <summary>
/// AKA RC, 3-bit counter that tracks which scan line within a character row is currently being rendered
/// </summary>
private byte _rasterCounter;
/// <summary>
/// AKA VMLI, Serves as the base value for teh video counter. Stores the VC value at the beginning of each character row
/// </summary>
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<byte> 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<byte> 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
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
private bool IsBadline(Span<byte> 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;
}
}
@@ -0,0 +1,35 @@
namespace Dotnet6502.C64.Hardware;
public static class Vic2Registers
{
/// <summary>
/// - 7 = Raster bit 8
/// - 6 = ECM
/// - 5 = BMM
/// - 4 = Display Enable
/// - 3 = RSEL
/// - 2-0 = Yscroll
/// </summary>
public const ushort ControlRegister1 = 0x011;
/// <summary>
/// Raster bits 7-0
/// </summary>
public const ushort Raster = 0x012;
/// <summary>
/// - 7-6 = Unused
/// - 5 = RES
/// - 4 = MCM
/// - 3 = CSEL
/// - 2-0 = XScroll
/// </summary>
public const ushort ControlRegister2 = 0x016;
/// <summary>
/// - 7-4 = Screen pointer (A13-A10) (aka video screen matrix?)
/// - 3-1 = Bitmapt/Charset pointer (A13-A11)
/// - 0 = unused
/// </summary>
public const ushort VmCb = 0x018;
}
+3 -2
View File
@@ -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<ProgrammableLogicArray> 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);
@@ -5,6 +5,7 @@ namespace Dotnet6502.Common.Compilation;
/// </summary>
public static class Ir6502
{
// TODO: Should probably refactor this to not create so much garbage
public abstract record Instruction;
public abstract record JumpTarget;