Add Z80 CPU skeleton, RegisterPair struct, and MemoryBus implementation

This commit is contained in:
Marc Parsons
2026-04-08 16:34:49 +01:00
parent 81912da3cb
commit ea828aad2d
9 changed files with 211 additions and 31 deletions

17
Core/Cpu/RegisterPair.cs Normal file
View File

@@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace Core.Cpu
{
[StructLayout(LayoutKind.Explicit)]
public struct RegisterPair
{
[FieldOffset(0)]
public ushort Word;
[FieldOffset(0)]
public byte Low;
[FieldOffset(1)]
public byte High;
}
}

74
Core/Cpu/Z80.cs Normal file
View File

@@ -0,0 +1,74 @@
using System;
using Core.Interfaces;
namespace Core.Cpu
{
public partial class Z80
{
// Main Register Set
public RegisterPair AF;
public RegisterPair BC;
public RegisterPair DE;
public RegisterPair HL;
// Alternate Register Set
public RegisterPair AF_Prime;
public RegisterPair BC_Prime;
public RegisterPair DE_Prime;
public RegisterPair HL_Prime;
// Index Registers
public RegisterPair IX;
public RegisterPair IY;
// Special Purpose Registers
public ushort PC; // Program Counter
public ushort SP; // Stack Pointer
public byte I; // Interrupt Vector
public byte R; // Memory Refresh
// The Memory Bus
private readonly IMemory _memory;
public Z80(IMemory memory)
{
_memory = memory;
Reset();
}
public void Reset()
{
PC = 0x0000;
// The Z80 initializes SP to 0xFFFF on boot
SP = 0xFFFF;
AF.Word = 0;
BC.Word = 0;
DE.Word = 0;
HL.Word = 0;
}
public int Step()
{
// Fetch the next opcode and increment the Program Counter
byte opcode = _memory.Read(PC++);
// Decode and execute
return ExecuteOpcode(opcode);
}
private int ExecuteOpcode(byte opcode)
{
switch (opcode)
{
case 0x00: // NOP (No Operation)
return 4; // Takes 4 T-states
// We will expand this massive list soon!
default:
throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Core.Interfaces
{
public interface IMemory
{
byte Read(ushort address);
void Write(ushort address, byte value);
}
}

47
Core/Memory/MemoryBus.cs Normal file
View File

@@ -0,0 +1,47 @@
using System;
using Core.Interfaces;
namespace Core.Memory
{
public class MemoryBus : IMemory
{
// The flat 64KB memory space
private readonly byte[] _memory = new byte[0x10000];
public byte Read(ushort address)
{
return _memory[address];
}
public void Write(ushort address, byte value)
{
/* ZX Spectrum 48K Memory Map:
* 0x0000 - 0x3FFF: ROM (16KB) -> Cannot write here!
* 0x4000 - 0x57FF: Display File (Screen Pixels)
* 0x5800 - 0x5AFF: Color Attributes
* 0x5B00 - 0xFFFF: General Purpose RAM
*/
if (address < 0x4000)
{
// Attempted to write to the ROM area.
// We simply ignore the write command, just like real hardware.
return;
}
_memory[address] = value;
}
// Helper method to load the original Sinclair ROM file
public void LoadRom(byte[] romData)
{
if (romData.Length > 0x4000)
{
throw new ArgumentException("ROM file exceeds the 16KB capacity of Bank 0.");
}
// Copy the ROM data into the very beginning of the memory array
Array.Copy(romData, 0, _memory, 0, romData.Length);
}
}
}

View File

@@ -8,4 +8,8 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -28,10 +28,17 @@
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
SuspendLayout();
//
// Form1
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Name = "Form1";
Text = "Form1";
Load += Form1_Load;
ResumeLayout(false);
}
#endregion

View File

@@ -6,5 +6,10 @@ namespace Desktop
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->

View File

@@ -3,7 +3,25 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36811.4 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{0706292A-3AD4-4A04-A3CE-B692AC69D7A4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{57408F14-0EEC-4E9E-BC97-3764DEB5738D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0706292A-3AD4-4A04-A3CE-B692AC69D7A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0706292A-3AD4-4A04-A3CE-B692AC69D7A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0706292A-3AD4-4A04-A3CE-B692AC69D7A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0706292A-3AD4-4A04-A3CE-B692AC69D7A4}.Release|Any CPU.Build.0 = Release|Any CPU
{57408F14-0EEC-4E9E-BC97-3764DEB5738D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{57408F14-0EEC-4E9E-BC97-3764DEB5738D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57408F14-0EEC-4E9E-BC97-3764DEB5738D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57408F14-0EEC-4E9E-BC97-3764DEB5738D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection