Got main system and VDP working! There is a display!

This commit is contained in:
2026-05-10 02:43:11 +01:00
parent 778f03b55c
commit f4e279b9c8
8 changed files with 516 additions and 104 deletions

View File

@@ -1275,6 +1275,18 @@ namespace Core.Cpu
switch (extendedOpcode)
{
case 0x41: // OUT (C), B
_simpleIoBus.WritePort(BC.Word, BC.High);
return 12;
case 0x49: // OUT (C), C
_simpleIoBus.WritePort(BC.Word, BC.Low);
return 12;
case 0x61: // OUT (C), H
_simpleIoBus.WritePort(BC.Word, HL.High);
return 12;
case 0x69: // OUT (C), L
_simpleIoBus.WritePort(BC.Word, HL.Low);
return 12;
case 0x43: // LD (nn), BC
ushort dest43 = FetchWord();
WriteMemory(dest43, BC.Low);
@@ -1494,6 +1506,48 @@ namespace Core.Cpu
if ((n & 0x02) != 0) AF.Low |= 0x20; // Bit 5 from bit 1
return 16;
}
case 0xA3: // OUTI
{
// 1. Read data from memory at HL
byte valA3 = ReadMemory(HL.Word);
// 2. Decrement the B register
BC.High--;
// 3. Output the data to the port specified by C
_simpleIoBus.WritePort(BC.Word, valA3);
// 4. Increment the memory pointer
HL.Word++;
// 5. Update Flags (N is always set. Z is set if B reached 0)
AF.Low |= 0x02;
if (BC.High == 0) AF.Low |= 0x40;
else AF.Low &= 0xBF;
return 16;
}
case 0xB3: // OTIR
{
// This does exactly the same thing as OUTI, but loops until B == 0
byte valB3 = ReadMemory(HL.Word);
BC.High--;
_simpleIoBus.WritePort(BC.Word, valB3);
HL.Word++;
AF.Low |= 0x02;
if (BC.High != 0)
{
AF.Low &= 0xBF; // Z is reset
PC -= 2; // Loop back and execute ED B3 again!
return 21;
}
else
{
AF.Low |= 0x40; // Z is set
return 16;
}
}
case 0xB0: // LDIR
{
byte val00 = ReadMemory(HL.Word);

View File

@@ -1,11 +1,11 @@
using Core.Interfaces;
using Core.Video;
namespace Core.Io
{
public class SmsIoBus : IIoBus
{
// We will wire these up in the next phases!
// public Vdp VideoProcessor { get; set; }
public SmsVdp VideoProcessor { get; set; }
// public Psg AudioProcessor { get; set; }
// Joypad State (0xFF means no buttons pressed - the SMS uses Active-Low logic!)
@@ -18,11 +18,17 @@ namespace Core.Io
// hardware only physically wires up the bottom 8 bits.
byte lowerPort = (byte)(port & 0xFF);
if (lowerPort == 0x7E)
{
// VDP V-Counter (Vertical Scanline Position)
return VideoProcessor.ReadVCounter();
}
if (lowerPort >= 0x80 && lowerPort <= 0xBF)
{
// VDP Read (Usually 0xBE for VRAM Data, 0xBF for Status Flags)
// return VideoProcessor.ReadPort(lowerPort);
return 0x00;
// Even ports (like 0xBE) are Data. Odd ports (like 0xBF) are Control.
if ((lowerPort & 0x01) == 0) return VideoProcessor.ReadDataPort();
else return VideoProcessor.ReadControlPort();
}
if (lowerPort == 0xDC)
{
@@ -49,8 +55,8 @@ namespace Core.Io
}
else if (lowerPort >= 0x80 && lowerPort <= 0xBF)
{
// VDP Write (Usually 0xBE for VRAM Data, 0xBF for Control Registers)
// VideoProcessor.WritePort(lowerPort, value);
if ((lowerPort & 0x01) == 0) VideoProcessor.WriteDataPort(value);
else VideoProcessor.WriteControlPort(value);
}
else if (lowerPort <= 0x3F)
{

View File

@@ -1,6 +1,9 @@
using Core.Cpu;
using Core.Io;
using Core.Memory;
using System;
using System.IO;
using System.Collections.Generic;
namespace Core
{
@@ -9,18 +12,17 @@ namespace Core
public Z80 Cpu { get; private set; }
public SmsMemoryBus MemoryBus { get; private set; }
public SmsIoBus IoBus { get; private set; }
public Core.Video.SmsVdp VideoProcessor { get; private set; }
public ushort? Breakpoint { get; set; } = null;
// NTSC SMS T-States per frame
public const int TStatesPerFrame = 59736;
public long TotalFrameCount { get; private set; } = 0;
public double FramesPerSecond { get; private set; } = 0;
public double FrameTime { get; private set; } = 0;
public SmsMachine()
{
MemoryBus = new SmsMemoryBus();
IoBus = new SmsIoBus();
VideoProcessor = new Core.Video.SmsVdp();
IoBus = new SmsIoBus { VideoProcessor = this.VideoProcessor };
Cpu = new Z80(MemoryBus, IoBus);
}
@@ -34,8 +36,23 @@ namespace Core
{
MemoryBus.CleanRAMData();
Cpu.Reset();
}
// We will reset the VDP and PSG here later!
public int StepMachine()
{
// 1. Tick the CPU
int tStates = Cpu.Step();
// 2. Tell the VDP how much time just passed
VideoProcessor.Update(tStates);
// 3. Trigger interrupts if the VDP hit scanline 192
if (VideoProcessor.InterruptPending)
{
tStates += Cpu.RequestInterrupt();
}
return tStates;
}
public void RunFrame()
@@ -44,18 +61,51 @@ namespace Core
while (currentFrameTStates < TStatesPerFrame)
{
int tStates = Cpu.Step();
currentFrameTStates += tStates;
currentFrameTStates += StepMachine();
string filePath = "captured_data.txt";
// --- FUTURE EXPANSION ---
// VideoProcessor.Update(tStates);
// AudioProcessor.Update(tStates);
// Mock data to loop through
//List<ushort> sensorReadings = new List<ushort> { Cpu.PC, Cpu.AF.Word, Cpu.BC.Word, Cpu.DE.Word, Cpu.HL.Word, Cpu.SP};
//List<string> type = new List<string> {"PC: 0x", "AF: 0x", "BC: 0x", "DE: 0x", "HL: 0x", "SP: 0x" };
// if (VideoProcessor.IsVBlanking && VideoProcessor.InterruptsEnabled)
// {
// Cpu.RequestInterrupt();
// }
//try
//{
// // 2. Initialize StreamWriter within a 'using' block
// // The 'true' parameter means "append" to the file. Use 'false' to overwrite.
// using (StreamWriter writer = new StreamWriter(filePath, append: true))
// {
// foreach (int reading in sensorReadings)
// {
// string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// // 3. Construct your string and write it
// foreach (string _type in type)
// {
// string line = $"{timestamp} | {_type} {reading}";
// writer.WriteLine(line);
// }
// // Optional: Console feedback
// //Console.WriteLine($"Logged: {line}");
// }
// }
// // File is automatically closed and saved here
// //Console.WriteLine("Data capture complete.");
//}
//catch (IOException e)
//{
// Console.WriteLine($"An error occurred: {e.Message}");
//}
// THE TRIPWIRE: Check the breakpoint after EVERY single instruction!
if (Breakpoint.HasValue && Cpu.PC == Breakpoint.Value)
{
break; // Abort the frame loop immediately!
}
}
}
}
}

194
Core/Video/SmsVdp.cs Normal file
View File

@@ -0,0 +1,194 @@
using System;
namespace Core.Video
{
public class SmsVdp
{
// The VDP's private memory! The CPU cannot touch these arrays directly.
public byte[] VRAM { get; private set; } = new byte[0x4000]; // 16KB Video RAM
public byte[] CRAM { get; private set; } = new byte[0x20]; // 32 Bytes Color Palette
public byte[] Registers { get; private set; } = new byte[16]; // 11 Hardware Control Registers
public int[] FrameBuffer { get; private set; } = new int[256 * 192];
// The Control Port State Machine (Port 0xBF)
private bool _isSecondControlByte = false;
private ushort _controlWord = 0;
private byte _readBuffer = 0;
private int _tStateCounter = 0;
private int _currentScanline = 0;
private byte _statusRegister = 0x00;
public bool InterruptPending => (_statusRegister & 0x80) != 0 && (Registers[1] & 0x20) != 0;
public byte ReadDataPort() // Port 0xBE
{
_isSecondControlByte = false; // Reading data resets the control latch
byte value = _readBuffer;
_readBuffer = VRAM[_controlWord & 0x3FFF];
_controlWord++;
return value;
}
public byte ReadControlPort() // Port 0xBF
{
_isSecondControlByte = false;
byte currentStatus = _statusRegister;
// CRITICAL HARDWARE QUIRK: Reading the status port physically
// clears the flags inside the chip! If we don't clear this,
// the interrupt line gets stuck on forever.
_statusRegister = 0x00;
return currentStatus;
}
public void WriteDataPort(byte value) // Port 0xBE
{
_isSecondControlByte = false;
_readBuffer = value;
int address = _controlWord & 0x3FFF;
int command = (_controlWord >> 14) & 0x03;
if (command == 3) // Code 3: Write to Color Palette (CRAM)
{
CRAM[address & 0x1F] = value;
}
else // Code 0, 1, 2: Write to VRAM
{
VRAM[address] = value;
}
_controlWord++; // Auto-increment so the Z80 can blast data fast
}
public void WriteControlPort(byte value) // Port 0xBF
{
if (!_isSecondControlByte)
{
// First byte arrives: Store it in the lower 8 bits
_controlWord = (ushort)((_controlWord & 0xFF00) | value);
_isSecondControlByte = true;
}
else
{
// Second byte arrives: Store it in the upper 8 bits and execute!
_controlWord = (ushort)((_controlWord & 0x00FF) | (value << 8));
_isSecondControlByte = false;
int command = (_controlWord >> 14) & 0x03;
if (command == 0) // Code 0: Prep for VRAM Read
{
_readBuffer = VRAM[_controlWord & 0x3FFF];
_controlWord++;
}
else if (command == 2) // Code 2: Write to Internal VDP Register
{
int regIndex = value & 0x0F;
byte regData = (byte)(_controlWord & 0xFF);
if (regIndex < 16) Registers[regIndex] = regData;
}
}
}
public byte ReadVCounter()
{
// Note: On real NTSC hardware, the V-Counter jumps slightly around
// the VBlank period to keep the math 8-bit, but simply returning
// the raw current scanline is perfectly fine to get us booting!
return (byte)_currentScanline;
}
public void Update(int tStates)
{
_tStateCounter += tStates;
// 228 T-States per scanline
if (_tStateCounter >= 228)
{
_tStateCounter -= 228;
_currentScanline++;
// Line 192 is the exact moment the screen finishes drawing!
if (_currentScanline == 192)
{
_statusRegister |= 0x80; // Set Bit 7 (VBlank Flag) to 1
RenderBackground(); // <--- DRAW THE FRAME!
}
// End of the NTSC frame (262 lines)
if (_currentScanline > 261)
{
_currentScanline = 0;
}
}
}
private void RenderBackground()
{
// The Name Table base address is stored in VDP Register 2.
// It tells us where in VRAM the 32x24 screen grid starts.
ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10);
// Loop through all 24 rows and 32 columns of the screen
for (int row = 0; row < 24; row++)
{
for (int col = 0; col < 32; col++)
{
// 1. Read the 16-bit Tile instruction from the Name Table
ushort nameTableAddr = (ushort)(nameTableBase + (row * 64) + (col * 2));
byte lowByte = VRAM[nameTableAddr];
byte highByte = VRAM[nameTableAddr + 1];
ushort tileData = (ushort)((highByte << 8) | lowByte);
// 2. Extract the Tile Index and Palette Info
int tileIndex = tileData & 0x01FF;
bool useSpritePalette = (tileData & 0x0800) != 0;
// 3. Find the actual pixel data for this tile in VRAM
// Each 8x8 tile takes exactly 32 bytes in memory
ushort tileAddress = (ushort)(tileIndex * 32);
// 4. Draw the 8x8 block of pixels!
for (int y = 0; y < 8; y++)
{
// The SMS uses 4 bitplanes to make a single row of pixels.
byte bp0 = VRAM[tileAddress + (y * 4) + 0];
byte bp1 = VRAM[tileAddress + (y * 4) + 1];
byte bp2 = VRAM[tileAddress + (y * 4) + 2];
byte bp3 = VRAM[tileAddress + (y * 4) + 3];
for (int x = 0; x < 8; x++)
{
// Combine 1 bit from each bitplane to get a color index (0-15)
int shift = 7 - x;
int colorIndex = ((bp0 >> shift) & 1) |
(((bp1 >> shift) & 1) << 1) |
(((bp2 >> shift) & 1) << 2) |
(((bp3 >> shift) & 1) << 3);
// Find the raw SMS color in CRAM
int paletteOffset = useSpritePalette ? 16 : 0;
byte smsColor = CRAM[paletteOffset + colorIndex];
// Translate SMS 00BBGGRR format to Windows 32-bit ARGB
int r = (smsColor & 0x03) * 85;
int g = ((smsColor >> 2) & 0x03) * 85;
int b = ((smsColor >> 4) & 0x03) * 85;
// Calculate where this pixel goes on the final 256x192 screen
int pixelX = (col * 8) + x;
int pixelY = (row * 8) + y;
FrameBuffer[(pixelY * 256) + pixelX] = (255 << 24) | (r << 16) | (g << 8) | b;
}
}
}
}
}
}
}

View File

@@ -57,95 +57,95 @@
lblFrameTime = new Label();
richTextBox1 = new RichTextBox();
button1 = new Button();
button2 = new Button();
CpuRun = new Button();
SuspendLayout();
//
// lblAF
//
lblAF.AutoSize = true;
lblAF.Location = new Point(10, 7);
lblAF.Location = new Point(12, 9);
lblAF.Margin = new Padding(2, 0, 2, 0);
lblAF.Name = "lblAF";
lblAF.Size = new Size(26, 20);
lblAF.Size = new Size(33, 25);
lblAF.TabIndex = 0;
lblAF.Text = "AF";
//
// lblBC
//
lblBC.AutoSize = true;
lblBC.Location = new Point(9, 48);
lblBC.Location = new Point(11, 60);
lblBC.Margin = new Padding(2, 0, 2, 0);
lblBC.Name = "lblBC";
lblBC.Size = new Size(27, 20);
lblBC.Size = new Size(33, 25);
lblBC.TabIndex = 1;
lblBC.Text = "BC";
//
// lblDE
//
lblDE.AutoSize = true;
lblDE.Location = new Point(10, 100);
lblDE.Location = new Point(12, 125);
lblDE.Margin = new Padding(2, 0, 2, 0);
lblDE.Name = "lblDE";
lblDE.Size = new Size(28, 20);
lblDE.Size = new Size(34, 25);
lblDE.TabIndex = 2;
lblDE.Text = "DE";
//
// lblHL
//
lblHL.AutoSize = true;
lblHL.Location = new Point(10, 150);
lblHL.Location = new Point(12, 188);
lblHL.Margin = new Padding(2, 0, 2, 0);
lblHL.Name = "lblHL";
lblHL.Size = new Size(27, 20);
lblHL.Size = new Size(33, 25);
lblHL.TabIndex = 3;
lblHL.Text = "HL";
//
// lblPC
//
lblPC.AutoSize = true;
lblPC.Location = new Point(9, 200);
lblPC.Location = new Point(11, 250);
lblPC.Margin = new Padding(2, 0, 2, 0);
lblPC.Name = "lblPC";
lblPC.Size = new Size(26, 20);
lblPC.Size = new Size(33, 25);
lblPC.TabIndex = 4;
lblPC.Text = "PC";
//
// lblSP
//
lblSP.AutoSize = true;
lblSP.Location = new Point(9, 252);
lblSP.Location = new Point(11, 315);
lblSP.Margin = new Padding(2, 0, 2, 0);
lblSP.Name = "lblSP";
lblSP.Size = new Size(25, 20);
lblSP.Size = new Size(32, 25);
lblSP.TabIndex = 6;
lblSP.Text = "SP";
//
// lblFlags
//
lblFlags.AutoSize = true;
lblFlags.Location = new Point(88, 52);
lblFlags.Location = new Point(110, 65);
lblFlags.Margin = new Padding(2, 0, 2, 0);
lblFlags.Name = "lblFlags";
lblFlags.Size = new Size(43, 20);
lblFlags.Size = new Size(53, 25);
lblFlags.TabIndex = 7;
lblFlags.Text = "Flags";
//
// lblTStates
//
lblTStates.AutoSize = true;
lblTStates.Location = new Point(88, 7);
lblTStates.Location = new Point(110, 9);
lblTStates.Margin = new Padding(2, 0, 2, 0);
lblTStates.Name = "lblTStates";
lblTStates.Size = new Size(63, 20);
lblTStates.Size = new Size(75, 25);
lblTStates.TabIndex = 8;
lblTStates.Text = "T-States";
//
// txtMemoryStart
//
txtMemoryStart.Location = new Point(300, 21);
txtMemoryStart.Location = new Point(375, 26);
txtMemoryStart.Margin = new Padding(2);
txtMemoryStart.Name = "txtMemoryStart";
txtMemoryStart.Size = new Size(121, 27);
txtMemoryStart.Size = new Size(150, 31);
txtMemoryStart.TabIndex = 9;
txtMemoryStart.Text = "Memory Start";
txtMemoryStart.TextAlign = HorizontalAlignment.Center;
@@ -153,10 +153,10 @@
//
// btnRefreshMemory
//
btnRefreshMemory.Location = new Point(425, 21);
btnRefreshMemory.Location = new Point(531, 26);
btnRefreshMemory.Margin = new Padding(2);
btnRefreshMemory.Name = "btnRefreshMemory";
btnRefreshMemory.Size = new Size(90, 27);
btnRefreshMemory.Size = new Size(112, 34);
btnRefreshMemory.TabIndex = 14;
btnRefreshMemory.Text = "Refresh Memory";
btnRefreshMemory.UseVisualStyleBackColor = true;
@@ -164,109 +164,117 @@
//
// txtMemoryView
//
txtMemoryView.Location = new Point(88, 80);
txtMemoryView.Location = new Point(110, 100);
txtMemoryView.Margin = new Padding(2);
txtMemoryView.Name = "txtMemoryView";
txtMemoryView.Size = new Size(443, 895);
txtMemoryView.Size = new Size(553, 1118);
txtMemoryView.TabIndex = 15;
txtMemoryView.Text = "Memory View Window";
//
// lstDisassembly
//
lstDisassembly.FormattingEnabled = true;
lstDisassembly.Location = new Point(567, 8);
lstDisassembly.ItemHeight = 25;
lstDisassembly.Location = new Point(709, 10);
lstDisassembly.Margin = new Padding(2);
lstDisassembly.Name = "lstDisassembly";
lstDisassembly.Size = new Size(252, 264);
lstDisassembly.Size = new Size(314, 329);
lstDisassembly.TabIndex = 16;
//
// lstStack
//
lstStack.FormattingEnabled = true;
lstStack.Location = new Point(823, 8);
lstStack.ItemHeight = 25;
lstStack.Location = new Point(1029, 10);
lstStack.Margin = new Padding(2);
lstStack.Name = "lstStack";
lstStack.Size = new Size(130, 264);
lstStack.Size = new Size(162, 329);
lstStack.TabIndex = 17;
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(568, 298);
label1.Location = new Point(710, 372);
label1.Margin = new Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new Size(81, 20);
label1.Size = new Size(97, 25);
label1.TabIndex = 19;
label1.Text = "Breakpoint";
//
// txtBreakpoint
//
txtBreakpoint.Location = new Point(655, 295);
txtBreakpoint.Location = new Point(819, 369);
txtBreakpoint.Margin = new Padding(4);
txtBreakpoint.Name = "txtBreakpoint";
txtBreakpoint.Size = new Size(125, 27);
txtBreakpoint.Size = new Size(155, 31);
txtBreakpoint.TabIndex = 20;
//
// label2
//
label2.AutoSize = true;
label2.Location = new Point(233, 21);
label2.Location = new Point(291, 26);
label2.Margin = new Padding(4, 0, 4, 0);
label2.Name = "label2";
label2.Size = new Size(62, 20);
label2.Size = new Size(77, 25);
label2.TabIndex = 21;
label2.Text = "Address";
//
// lblIX
//
lblIX.AutoSize = true;
lblIX.Location = new Point(9, 298);
lblIX.Location = new Point(11, 372);
lblIX.Margin = new Padding(2, 0, 2, 0);
lblIX.Name = "lblIX";
lblIX.Size = new Size(22, 20);
lblIX.Size = new Size(28, 25);
lblIX.TabIndex = 22;
lblIX.Text = "IX";
//
// lblIY
//
lblIY.AutoSize = true;
lblIY.Location = new Point(10, 344);
lblIY.Location = new Point(12, 430);
lblIY.Margin = new Padding(2, 0, 2, 0);
lblIY.Name = "lblIY";
lblIY.Size = new Size(21, 20);
lblIY.Size = new Size(27, 25);
lblIY.TabIndex = 23;
lblIY.Text = "IY";
//
// lblIff1
//
lblIff1.AutoSize = true;
lblIff1.Location = new Point(567, 411);
lblIff1.Location = new Point(709, 514);
lblIff1.Margin = new Padding(4, 0, 4, 0);
lblIff1.Name = "lblIff1";
lblIff1.Size = new Size(35, 20);
lblIff1.Size = new Size(45, 25);
lblIff1.TabIndex = 24;
lblIff1.Text = "IFF1";
//
// lblIff2
//
lblIff2.AutoSize = true;
lblIff2.Location = new Point(567, 458);
lblIff2.Location = new Point(709, 572);
lblIff2.Margin = new Padding(4, 0, 4, 0);
lblIff2.Name = "lblIff2";
lblIff2.Size = new Size(35, 20);
lblIff2.Size = new Size(45, 25);
lblIff2.TabIndex = 25;
lblIff2.Text = "IFF2";
//
// lblIE
//
lblIE.AutoSize = true;
lblIE.Location = new Point(568, 370);
lblIE.Location = new Point(710, 462);
lblIE.Margin = new Padding(4, 0, 4, 0);
lblIE.Name = "lblIE";
lblIE.Size = new Size(109, 20);
lblIE.Size = new Size(133, 25);
lblIE.TabIndex = 26;
lblIE.Text = "Interrupt Mode";
//
// btnReset
//
btnReset.Location = new Point(651, 327);
btnReset.Location = new Point(814, 409);
btnReset.Margin = new Padding(2);
btnReset.Name = "btnReset";
btnReset.Size = new Size(132, 27);
btnReset.Size = new Size(165, 34);
btnReset.TabIndex = 27;
btnReset.Text = "Set Breakpoint";
btnReset.UseVisualStyleBackColor = true;
@@ -281,69 +289,72 @@
// lblFrames
//
lblFrames.AutoSize = true;
lblFrames.Location = new Point(567, 682);
lblFrames.Location = new Point(709, 852);
lblFrames.Margin = new Padding(2, 0, 2, 0);
lblFrames.Name = "lblFrames";
lblFrames.Size = new Size(124, 20);
lblFrames.Size = new Size(149, 25);
lblFrames.TabIndex = 28;
lblFrames.Text = "Frames Rendered";
//
// lblFPS
//
lblFPS.AutoSize = true;
lblFPS.Location = new Point(659, 759);
lblFPS.Location = new Point(824, 949);
lblFPS.Margin = new Padding(2, 0, 2, 0);
lblFPS.Name = "lblFPS";
lblFPS.Size = new Size(32, 20);
lblFPS.Size = new Size(41, 25);
lblFPS.TabIndex = 29;
lblFPS.Text = "FPS";
//
// lblFrameTime
//
lblFrameTime.AutoSize = true;
lblFrameTime.Location = new Point(604, 718);
lblFrameTime.Location = new Point(755, 898);
lblFrameTime.Margin = new Padding(2, 0, 2, 0);
lblFrameTime.Name = "lblFrameTime";
lblFrameTime.Size = new Size(87, 20);
lblFrameTime.Size = new Size(104, 25);
lblFrameTime.TabIndex = 30;
lblFrameTime.Text = "Frame Time";
//
// richTextBox1
//
richTextBox1.Enabled = false;
richTextBox1.Location = new Point(567, 509);
richTextBox1.Location = new Point(709, 636);
richTextBox1.Margin = new Padding(4);
richTextBox1.Name = "richTextBox1";
richTextBox1.ReadOnly = true;
richTextBox1.Size = new Size(304, 128);
richTextBox1.Size = new Size(379, 159);
richTextBox1.TabIndex = 32;
richTextBox1.Text = "Sega Master System Memory Map:\n0x0000 - 0x3FFF: ROM Slot 0 (16KB)\n0x4000 - 0x7FFF: ROM Slot 1 (16KB)\n0x8000 - 0xBFFF: ROM Slot 2 (16KB)\n0xC000 - 0xDFFF: System RAM (8KB)\n0xE000 - 0xFFFF: RAM Mirror";
//
// button1
//
button1.Location = new Point(555, 824);
button1.Location = new Point(694, 1030);
button1.Margin = new Padding(4);
button1.Name = "button1";
button1.Size = new Size(94, 29);
button1.Size = new Size(118, 36);
button1.TabIndex = 33;
button1.Text = "CPU Step";
button1.UseVisualStyleBackColor = true;
button1.Click += btnStep_Click;
//
// button2
// CpuRun
//
button2.Location = new Point(555, 883);
button2.Name = "button2";
button2.Size = new Size(94, 29);
button2.TabIndex = 34;
button2.Text = "CPU Run";
button2.UseVisualStyleBackColor = true;
button2.Click += btnStep_Click;
CpuRun.Location = new Point(694, 1104);
CpuRun.Margin = new Padding(4);
CpuRun.Name = "CpuRun";
CpuRun.Size = new Size(118, 36);
CpuRun.TabIndex = 34;
CpuRun.Text = "CPU Run";
CpuRun.UseVisualStyleBackColor = true;
CpuRun.Click += CpuRun_Click;
//
// DebuggerForm
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(965, 990);
Controls.Add(button2);
ClientSize = new Size(1206, 1238);
Controls.Add(CpuRun);
Controls.Add(button1);
Controls.Add(richTextBox1);
Controls.Add(lblFrameTime);
@@ -402,13 +413,13 @@
private Label lblIff2;
private Label lblIE;
private Button btnReset;
private System.Windows.Forms.Timer uiUpdateTimer;
private Label lblFrames;
private Label lblFPS;
private Label lblFrameTime;
private RichTextBox richTextBox1;
private Button button1;
private Button button2;
private Button CpuRun;
public System.Windows.Forms.Timer uiUpdateTimer;
//private TextBox textBox4;
}
}

View File

@@ -27,11 +27,36 @@ namespace Desktop
_mainForm = mainForm;
}
private void CpuRun_Click(object sender, EventArgs e)
{
if (_mainForm.IsRunning)
{
// Stop the machine
_mainForm.StopEmulator();
CpuRun.Text = "CPU Run";
// Stop the live UI updates and do one final manual refresh
uiUpdateTimer.Stop();
UpdateDisplay();
}
else
{
// Start the machine
_mainForm.StartEmulator();
CpuRun.Text = "CPU Stop";
// Start the timer so the debugger screen updates automatically
// (Make sure your uiUpdateTimer Interval in the designer is set to something like 100ms)
uiUpdateTimer.Start();
}
}
private void btnStep_Click(object sender, EventArgs e)
{
try
{
_cpu.Step();
// Ask the main form to step the WHOLE machine, not just the Z80!
_mainForm.StepEmulator();
UpdateDisplay();
}
catch (Exception ex)
@@ -57,7 +82,7 @@ namespace Desktop
UpdateDisplay();
}
private void uiUpdateTimer_Tick(object sender, EventArgs e)
public void uiUpdateTimer_Tick(object sender, EventArgs e)
{
UpdateDisplay();
}
@@ -180,6 +205,9 @@ namespace Desktop
case 0x02: mnemonic = "LD (BC), A"; break;
// --- 16-Bit Increments ---
case 0x03: mnemonic = "INC BC"; break;
case 0x07:
mnemonic = "RLCA";
break;
case 0x08:
mnemonic = "EX AF, AF'";
break;

View File

@@ -29,32 +29,48 @@
private void InitializeComponent()
{
button1 = new Button();
pbScreen = new PictureBox();
((System.ComponentModel.ISupportInitialize)pbScreen).BeginInit();
SuspendLayout();
//
// button1
//
button1.Location = new Point(304, 268);
button1.Location = new Point(1294, 13);
button1.Margin = new Padding(4, 4, 4, 4);
button1.Name = "button1";
button1.Size = new Size(94, 29);
button1.Size = new Size(118, 36);
button1.TabIndex = 0;
button1.Text = "button1";
button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click;
//
// pbScreen
//
pbScreen.Location = new Point(23, 59);
pbScreen.Name = "pbScreen";
pbScreen.Size = new Size(1390, 760);
pbScreen.SizeMode = PictureBoxSizeMode.Zoom;
pbScreen.TabIndex = 1;
pbScreen.TabStop = false;
//
// Form1
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
ClientSize = new Size(1425, 842);
Controls.Add(pbScreen);
Controls.Add(button1);
Margin = new Padding(4, 4, 4, 4);
Name = "Form1";
Text = "Form1";
Click += button1_Click;
((System.ComponentModel.ISupportInitialize)pbScreen).EndInit();
ResumeLayout(false);
}
#endregion
private Button button1;
private PictureBox pbScreen;
}
}

View File

@@ -1,12 +1,21 @@
using System.Reflection;
using System.Reflection.PortableExecutable;
using Core;
using System.Threading.Tasks;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Desktop
{
public partial class Form1 : Form
{
private SmsMachine _machine = null!;
private DebuggerForm _debugger;
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
public bool IsRunning { get; private set; } = false;
public ushort? Breakpoint
{
@@ -20,23 +29,67 @@ namespace Desktop
_machine = new SmsMachine();
}
private void DrawScreen()
{
// Rapidly copy our VDP FrameBuffer into the Windows Bitmap
var data = _screenBitmap.LockBits(new Rectangle(0, 0, 256, 192), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(_machine.VideoProcessor.FrameBuffer, 0, data.Scan0, _machine.VideoProcessor.FrameBuffer.Length);
_screenBitmap.UnlockBits(data);
// Update the PictureBox
pbScreen.Image = _screenBitmap;
}
public void StartEmulator()
{
if (IsRunning) return;
IsRunning = true;
// Fire off a background task so we don't freeze the Windows UI!
Task.Run(() =>
{
while (IsRunning)
{
_machine.RunFrame();
Invoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(); });
// Safety catch: If we hit a breakpoint while running, stop the loop!
if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value)
{
IsRunning = false;
// Optional: Force the debugger UI to update immediately so you see it!
Invoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); });
}
else
{
// Only throttle the speed if we are actively running
Thread.Sleep(16);
}
}
});
}
public void StopEmulator()
{
IsRunning = false;
}
public void StepEmulator()
{
_machine.StepMachine();
}
private void button1_Click(object sender, EventArgs e)
{
// 1. Load a commercial Master System ROM!
byte[] rom = File.ReadAllBytes(@"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\Golden Axe Warrior.sms");
try
{
// 2. Jam it into the Sega Mapper
_machine.LoadCartridge(rom);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// 3. Open the Debugger to look around
if (_debugger == null || _debugger.IsDisposed)