First OpCode Decoding attempt
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
namespace ParsonsMegadrive.Core
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
21
ParsonsMegadrive.Core/Interfaces/IMemoryBus.cs
Normal file
21
ParsonsMegadrive.Core/Interfaces/IMemoryBus.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ParsonsMegadrive.Core.Interfaces
|
||||
{
|
||||
public interface IMemoryBus
|
||||
{
|
||||
// 8-bit (Byte)
|
||||
byte Read8(uint address);
|
||||
void Write8(uint address, byte value);
|
||||
|
||||
// 16-bit (Word)
|
||||
ushort Read16(uint address);
|
||||
void Write16(uint address, ushort value);
|
||||
|
||||
// 32-bit (Long)
|
||||
uint Read32(uint address);
|
||||
void Write32(uint address, uint value);
|
||||
}
|
||||
}
|
||||
183
ParsonsMegadrive.Core/M68000.cs
Normal file
183
ParsonsMegadrive.Core/M68000.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using ParsonsMegadrive.Core.Interfaces;
|
||||
|
||||
namespace ParsonsMegaDrive.Core
|
||||
{
|
||||
public partial class M68000
|
||||
{
|
||||
|
||||
public delegate int InstructionHandler(ushort opcode);
|
||||
|
||||
// 2. The master lookup table
|
||||
private readonly InstructionHandler[] _instructionTable = new InstructionHandler[65536];
|
||||
|
||||
// --- PUBLIC STATE FOR DEBUGGER ---
|
||||
public uint[] D { get; private set; } = new uint[8];
|
||||
public uint[] A { get; private set; } = new uint[8];
|
||||
public uint PC { get; private set; }
|
||||
public ushort SR { get; private set; }
|
||||
public long TotalCycles { get; private set; }
|
||||
|
||||
// --- STATUS REGISTER (SR) FLAGS ---
|
||||
public bool FlagC
|
||||
{
|
||||
get => (SR & 0x0001) != 0;
|
||||
internal set => SR = (ushort)(value ? (SR | 0x0001) : (SR & ~0x0001));
|
||||
}
|
||||
public bool FlagV
|
||||
{
|
||||
get => (SR & 0x0002) != 0;
|
||||
internal set => SR = (ushort)(value ? (SR | 0x0002) : (SR & ~0x0002));
|
||||
}
|
||||
public bool FlagZ
|
||||
{
|
||||
get => (SR & 0x0004) != 0;
|
||||
internal set => SR = (ushort)(value ? (SR | 0x0004) : (SR & ~0x0004));
|
||||
}
|
||||
public bool FlagN
|
||||
{
|
||||
get => (SR & 0x0008) != 0;
|
||||
internal set => SR = (ushort)(value ? (SR | 0x0008) : (SR & ~0x0008));
|
||||
}
|
||||
public bool FlagX
|
||||
{
|
||||
get => (SR & 0x0010) != 0;
|
||||
internal set => SR = (ushort)(value ? (SR | 0x0010) : (SR & ~0x0010));
|
||||
}
|
||||
|
||||
private readonly IMemoryBus _memory;
|
||||
|
||||
public M68000(IMemoryBus memory)
|
||||
{
|
||||
_memory = memory;
|
||||
BuildOpcodeTable();
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void BuildOpcodeTable()
|
||||
{
|
||||
for (int i = 0; i < 65536; i++)
|
||||
{
|
||||
ushort opcode = (ushort)i;
|
||||
int line = (opcode >> 12) & 0x0F; // Extract Bits 12-15
|
||||
|
||||
if (line == 0x07)
|
||||
{
|
||||
// Line 7 is MOVEQ!
|
||||
// However, bit 8 MUST be 0. If it's 1, it's a completely different instruction.
|
||||
if ((opcode & 0x0100) == 0)
|
||||
{
|
||||
_instructionTable[opcode] = ExecuteMoveQ;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If it matches nothing, point it to our fallback method
|
||||
_instructionTable[opcode] = ExecuteUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Array.Clear(D, 0, D.Length);
|
||||
Array.Clear(A, 0, A.Length);
|
||||
|
||||
PC = 0;
|
||||
SR = 0x2700; // Supervisor mode on, Interrupts masked to level 7
|
||||
TotalCycles = 0;
|
||||
BuildOpcodeTable();
|
||||
}
|
||||
|
||||
// --- INSTRUCTION FETCHING ---
|
||||
|
||||
// Fetches a 16-bit word from the current Program Counter and advances it.
|
||||
public ushort FetchWord()
|
||||
{
|
||||
ushort word = _memory.Read16(PC);
|
||||
PC += 2;
|
||||
return word;
|
||||
}
|
||||
|
||||
// Sometimes an instruction needs a 32-bit immediate value!
|
||||
public uint FetchLong()
|
||||
{
|
||||
uint highWord = FetchWord();
|
||||
uint lowWord = FetchWord();
|
||||
return (highWord << 16) | lowWord;
|
||||
}
|
||||
|
||||
// --- EXECUTION LOOP ---
|
||||
|
||||
public int Step()
|
||||
{
|
||||
ushort opcode = FetchWord();
|
||||
|
||||
// Absolute O(1) execution. No switch statements!
|
||||
int cyclesTaken = _instructionTable[opcode](opcode);
|
||||
|
||||
TotalCycles += cyclesTaken;
|
||||
return cyclesTaken;
|
||||
}
|
||||
|
||||
// Extracts bits 0-2 (The target register: 0-7)
|
||||
private static int GetEARegister(ushort opcode)
|
||||
{
|
||||
return opcode & 0x07;
|
||||
}
|
||||
|
||||
// Extracts bits 3-5 (The addressing mode: e.g., Address Indirect, Immediate, etc.)
|
||||
private static int GetEAMode(ushort opcode)
|
||||
{
|
||||
return (opcode >> 3) & 0x07;
|
||||
}
|
||||
|
||||
// Extracts bits 6-7 (Often used to define if an operation is Byte, Word, or Long)
|
||||
private static int GetSize(ushort opcode)
|
||||
{
|
||||
return (opcode >> 6) & 0x03;
|
||||
}
|
||||
|
||||
// Extracts bits 9-11 (Often used to specify a destination Data/Address register)
|
||||
private static int GetDestinationRegister(ushort opcode)
|
||||
{
|
||||
return (opcode >> 9) & 0x07;
|
||||
}
|
||||
|
||||
private int ExecuteMoveQ(ushort opcode)
|
||||
{
|
||||
// Bit 8 must be 0 for a valid MOVEQ. If it's 1, it's a different instruction.
|
||||
if ((opcode & 0x0100) != 0) return ExecuteUnknown(opcode);
|
||||
|
||||
// 1. Where is it going? (Bits 9-11)
|
||||
int registerIndex = GetDestinationRegister(opcode);
|
||||
|
||||
// 2. What is the data? (Bits 0-7). MOVEQ sign-extends the 8-bit data to 32-bits!
|
||||
sbyte data = (sbyte)(opcode & 0xFF);
|
||||
|
||||
// 3. Execute
|
||||
D[registerIndex] = (uint)data;
|
||||
|
||||
// 4. Update Flags (MOVEQ clears V and C, and updates N and Z based on the data)
|
||||
FlagV = false;
|
||||
FlagC = false;
|
||||
FlagN = data < 0;
|
||||
FlagZ = data == 0;
|
||||
|
||||
// MOVEQ always takes 4 cycles
|
||||
return 4;
|
||||
}
|
||||
|
||||
private int ExecuteUnknown(ushort opcode)
|
||||
{
|
||||
throw new NotImplementedException($"Opcode 0x{opcode:X4} at PC 0x{(PC - 2):X8} is not implemented.");
|
||||
}
|
||||
|
||||
private int ExecuteOpcode(ushort opcode)
|
||||
{
|
||||
// This is where the magic happens!
|
||||
// We will use bit-masking to figure out what this opcode is.
|
||||
|
||||
throw new NotImplementedException($"Opcode 0x{opcode:X4} at PC 0x{(PC - 2):X8} is not implemented.");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
ParsonsMegadrive.Core/Memory/MdMemoryBus.cs
Normal file
49
ParsonsMegadrive.Core/Memory/MdMemoryBus.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using ParsonsMegadrive.Core.Interfaces;
|
||||
|
||||
namespace ParsonsMegaDrive.Core.Memory
|
||||
{
|
||||
public class MdMemoryBus : IMemoryBus
|
||||
{
|
||||
// For testing, we'll just give it a flat 16MB array to represent the whole address space.
|
||||
// Later, we will map this properly to ROM (0x000000), Work RAM (0xFF0000), etc.
|
||||
private readonly byte[] _memory = new byte[16 * 1024 * 1024];
|
||||
|
||||
public byte Read8(uint address)
|
||||
{
|
||||
return _memory[address & 0xFFFFFF]; // 24-bit address mask
|
||||
}
|
||||
|
||||
public void Write8(uint address, byte value)
|
||||
{
|
||||
_memory[address & 0xFFFFFF] = value;
|
||||
}
|
||||
|
||||
public ushort Read16(uint address)
|
||||
{
|
||||
// BIG-ENDIAN: High byte comes first!
|
||||
byte high = Read8(address);
|
||||
byte low = Read8(address + 1);
|
||||
return (ushort)((high << 8) | low);
|
||||
}
|
||||
|
||||
public void Write16(uint address, ushort value)
|
||||
{
|
||||
Write8(address, (byte)(value >> 8)); // High byte
|
||||
Write8(address + 1, (byte)(value & 0xFF)); // Low byte
|
||||
}
|
||||
|
||||
public uint Read32(uint address)
|
||||
{
|
||||
ushort highWord = Read16(address);
|
||||
ushort lowWord = Read16(address + 2);
|
||||
return (uint)((highWord << 16) | lowWord);
|
||||
}
|
||||
|
||||
public void Write32(uint address, uint value)
|
||||
{
|
||||
Write16(address, (ushort)(value >> 16));
|
||||
Write16(address + 2, (ushort)(value & 0xFFFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="../ParsonsMegadrive.Desktop/ParsonsMegadrive.Desktop.csproj" />
|
||||
<Project Path="../ParsonsMegadrive.Tests/ParsonsMegadrive.Tests.csproj" />
|
||||
<Project Path="ParsonsMegadrive.Core.csproj" />
|
||||
</Solution>
|
||||
|
||||
34
ParsonsMegadrive.Tests/CpuTests.cs
Normal file
34
ParsonsMegadrive.Tests/CpuTests.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using ParsonsMegaDrive.Core;
|
||||
using ParsonsMegaDrive.Core.Memory;
|
||||
using Xunit;
|
||||
|
||||
namespace ParsonsMegaDrive.Tests
|
||||
{
|
||||
public class CpuTests
|
||||
{
|
||||
[Fact]
|
||||
public void MoveQ_ShouldLoadDataAndSetFlags()
|
||||
{
|
||||
// 1. Arrange: Boot the machine
|
||||
var bus = new MdMemoryBus();
|
||||
var cpu = new M68000(bus);
|
||||
|
||||
// 2. Load the opcode for "MOVEQ #5, D1" (0x7205) into memory at PC 0
|
||||
bus.Write16(0x000000, 0x7205);
|
||||
|
||||
// 3. Act: Step the CPU exactly once
|
||||
int cycles = cpu.Step();
|
||||
|
||||
// 4. Assert: Did it work?
|
||||
Assert.Equal(4, cycles); // Should take exactly 4 cycles
|
||||
Assert.Equal(2u, cpu.PC); // PC should advance by 2 bytes
|
||||
Assert.Equal(5u, cpu.D[1]); // D1 should now contain the number 5
|
||||
|
||||
// Assert Flags (5 is greater than 0, so Z and N should be false)
|
||||
Assert.False(cpu.FlagZ);
|
||||
Assert.False(cpu.FlagN);
|
||||
Assert.False(cpu.FlagV);
|
||||
Assert.False(cpu.FlagC);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace ParsonsMegadrive.Tests
|
||||
{
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user