Interrupts added at 50fps. Dummy keyboard. Ready for graphics!

This commit is contained in:
2026-04-16 13:09:27 +01:00
parent 960f2b85cc
commit 92625498bf
4 changed files with 123 additions and 12 deletions

View File

@@ -31,6 +31,16 @@ namespace Core.Memory
_memory[address] = value; _memory[address] = value;
} }
//Put some initial random data into RAM for authenticity
public void CrapRAMData()
{
Random random = new Random();
for (int i = 0x4000; i < 0x5AFF; i++)
{
_memory[i] = (byte)random.Next(0x00, 0xFF);
}
}
// Load the ROM file // Load the ROM file
public void LoadRom(byte[] romData) public void LoadRom(byte[] romData)
{ {

View File

@@ -10,20 +10,23 @@ namespace Desktop
{ {
private readonly Z80 _cpu; private readonly Z80 _cpu;
private readonly MemoryBus _memoryBus; private readonly MemoryBus _memoryBus;
private readonly Form1 _mainForm;
private bool _isRunning = false; private bool _isRunning = false;
private ushort? _breakpoint = null; private ushort? _breakpoint = null;
public DebuggerForm(Z80 cpu, MemoryBus memoryBus) public DebuggerForm(Z80 cpu, MemoryBus memoryBus, Form1 mainForm)
{ {
InitializeComponent(); InitializeComponent();
_cpu = cpu; _cpu = cpu;
_memoryBus = memoryBus; _memoryBus = memoryBus;
_mainForm = mainForm;
// Set default memory view address // Set default memory view address
txtMemoryStart.Text = "0000"; txtMemoryStart.Text = "0000";
UpdateDisplay(); UpdateDisplay();
UpdateStackView(); UpdateStackView();
UpdateDisassemblyView(); UpdateDisassemblyView();
_mainForm = mainForm;
} }
private void btnStep_Click(object sender, EventArgs e) private void btnStep_Click(object sender, EventArgs e)
@@ -119,8 +122,14 @@ namespace Desktop
nextFrameTargetTStates += TStatesPerFrame; nextFrameTargetTStates += TStatesPerFrame;
frameCount++; frameCount++;
// 3. Throttle to real-time (50 frames per second = 20ms per frame) //3 Render the screen
long targetTimeMs = frameCount * 20; _mainForm.Invoke((MethodInvoker)delegate
{
_mainForm.RenderScreen();
});
// 4. Throttle to real-time (50 frames per second = 20ms per frame)
long targetTimeMs = frameCount * 20;
long elapsedMs = stopwatch.ElapsedMilliseconds; long elapsedMs = stopwatch.ElapsedMilliseconds;
if (elapsedMs < targetTimeMs) if (elapsedMs < targetTimeMs)
@@ -131,7 +140,7 @@ namespace Desktop
// Optional: Update the UI every 10 frames so you can watch it run safely // Optional: Update the UI every 10 frames so you can watch it run safely
// without overwhelming the WinForms rendering engine. // without overwhelming the WinForms rendering engine.
if (frameCount % 10 == 0) if (frameCount % 1 == 0)
{ {
this.Invoke((MethodInvoker)delegate this.Invoke((MethodInvoker)delegate
{ {

View File

@@ -28,18 +28,34 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
picScreen = new PictureBox();
((System.ComponentModel.ISupportInitialize)picScreen).BeginInit();
SuspendLayout(); SuspendLayout();
// //
// picScreen
//
picScreen.BackColor = Color.Black;
picScreen.Location = new Point(12, 12);
picScreen.Name = "picScreen";
picScreen.Size = new Size(768, 576);
picScreen.SizeMode = PictureBoxSizeMode.Zoom;
picScreen.TabIndex = 0;
picScreen.TabStop = false;
//
// Form1 // Form1
// //
AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450); ClientSize = new Size(791, 596);
Controls.Add(picScreen);
Name = "Form1"; Name = "Form1";
Text = "Parsons Sinclair ZX Spectrum 48K - 2026"; Text = "Parsons Sinclair ZX Spectrum 48K - 2026";
((System.ComponentModel.ISupportInitialize)picScreen).EndInit();
ResumeLayout(false); ResumeLayout(false);
} }
#endregion #endregion
private PictureBox picScreen;
} }
} }

View File

@@ -1,4 +1,7 @@
using System; using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms; using System.Windows.Forms;
using Core.Cpu; using Core.Cpu;
using Core.Io; using Core.Io;
@@ -12,6 +15,30 @@ namespace Desktop
private MemoryBus _memoryBus = null!; private MemoryBus _memoryBus = null!;
private SimpleIoBus _simpleIoBus = null!; private SimpleIoBus _simpleIoBus = null!;
// The 16 physical colors of the ZX Spectrum (ARGB format)
private readonly int[] SpectrumColors = new int[]
{
// Normal Colors (Bright = 0)
unchecked((int)0xFF000000), // 0: Black
unchecked((int)0xFF0000D7), // 1: Blue
unchecked((int)0xFFD70000), // 2: Red
unchecked((int)0xFFD700D7), // 3: Magenta
unchecked((int)0xFF00D700), // 4: Green
unchecked((int)0xFF00D7D7), // 5: Cyan
unchecked((int)0xFFD7D700), // 6: Yellow
unchecked((int)0xFFD7D7D7), // 7: White
// Bright Colors (Bright = 1)
unchecked((int)0xFF000000), // 8: Bright Black
unchecked((int)0xFF0000FF), // 9: Bright Blue
unchecked((int)0xFFFF0000), // 10: Bright Red
unchecked((int)0xFFFF00FF), // 11: Bright Magenta
unchecked((int)0xFF00FF00), // 12: Bright Green
unchecked((int)0xFF00FFFF), // 13: Bright Cyan
unchecked((int)0xFFFFFF00), // 14: Bright Yellow
unchecked((int)0xFFFFFFFF) // 15: Bright White
};
public Form1() public Form1()
{ {
InitializeComponent(); InitializeComponent();
@@ -22,27 +49,76 @@ namespace Desktop
{ {
try try
{ {
// 1. Initialize the memory bus
_memoryBus = new MemoryBus(); _memoryBus = new MemoryBus();
_simpleIoBus = new SimpleIoBus(); _simpleIoBus = new SimpleIoBus();
// 2. Load the ROM _memoryBus.CrapRAMData();
byte[] romData = RomLoader.Load("48.rom"); byte[] romData = RomLoader.Load("48.rom");
// 3. Inject the ROM into the memory bus
_memoryBus.LoadRom(romData); _memoryBus.LoadRom(romData);
// 4. Initialize the CPU with the populated memory
_cpu = new Z80(_memoryBus, _simpleIoBus); _cpu = new Z80(_memoryBus, _simpleIoBus);
DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus); // Pass 'this' so the DebuggerForm can talk back to this main window
DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus, this);
debugger.Show(); debugger.Show();
//MessageBox.Show("ROM loaded and CPU initialized successfully!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show($"Failed to initialize emulator:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show($"Failed to initialize emulator:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
} }
// Public so the Debugger's background thread can call it 50 times a second
public void RenderScreen()
{
int[] pixelData = new int[256 * 192];
// Loop through the 6144 bytes of Pixel RAM
for (int offset = 0; offset < 6144; offset++)
{
ushort address = (ushort)(0x4000 + offset);
byte pixels = _memoryBus.Read(address);
// Unwind the Sinclair interlace
int y = ((offset & 0x0700) >> 8) |
((offset & 0x00E0) >> 2) |
((offset & 0x1800) >> 5);
int x = (offset & 0x001F) * 8;
// Fetch Color Attributes
int attrRow = y / 8;
int attrCol = x / 8;
ushort attrAddress = (ushort)(0x5800 + (attrRow * 32) + attrCol);
byte attr = _memoryBus.Read(attrAddress);
int ink = attr & 0x07;
int paper = (attr >> 3) & 0x07;
int brightOffset = (attr & 0x40) != 0 ? 8 : 0;
int inkColor = SpectrumColors[ink + brightOffset];
int paperColor = SpectrumColors[paper + brightOffset];
// Draw the 8 pixels
for (int bit = 0; bit < 8; bit++)
{
bool isPixelSet = (pixels & (1 << (7 - bit))) != 0;
pixelData[(y * 256) + (x + bit)] = isPixelSet ? inkColor : paperColor;
}
}
// Blast it to the PictureBox
Bitmap bmp = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, 256, 192),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
Marshal.Copy(pixelData, 0, bmpData.Scan0, pixelData.Length);
bmp.UnlockBits(bmpData);
if (picScreen.Image != null) picScreen.Image.Dispose();
picScreen.Image = bmp;
}
} }
} }