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) 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 case 0x43: // LD (nn), BC
ushort dest43 = FetchWord(); ushort dest43 = FetchWord();
WriteMemory(dest43, BC.Low); WriteMemory(dest43, BC.Low);
@@ -1494,6 +1506,48 @@ namespace Core.Cpu
if ((n & 0x02) != 0) AF.Low |= 0x20; // Bit 5 from bit 1 if ((n & 0x02) != 0) AF.Low |= 0x20; // Bit 5 from bit 1
return 16; 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 case 0xB0: // LDIR
{ {
byte val00 = ReadMemory(HL.Word); byte val00 = ReadMemory(HL.Word);

View File

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

View File

@@ -1,6 +1,9 @@
using Core.Cpu; using Core.Cpu;
using Core.Io; using Core.Io;
using Core.Memory; using Core.Memory;
using System;
using System.IO;
using System.Collections.Generic;
namespace Core namespace Core
{ {
@@ -9,18 +12,17 @@ namespace Core
public Z80 Cpu { get; private set; } public Z80 Cpu { get; private set; }
public SmsMemoryBus MemoryBus { get; private set; } public SmsMemoryBus MemoryBus { get; private set; }
public SmsIoBus IoBus { get; private set; } public SmsIoBus IoBus { get; private set; }
public Core.Video.SmsVdp VideoProcessor { get; private set; }
public ushort? Breakpoint { get; set; } = null; public ushort? Breakpoint { get; set; } = null;
// NTSC SMS T-States per frame // NTSC SMS T-States per frame
public const int TStatesPerFrame = 59736; 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() public SmsMachine()
{ {
MemoryBus = new SmsMemoryBus(); MemoryBus = new SmsMemoryBus();
IoBus = new SmsIoBus(); VideoProcessor = new Core.Video.SmsVdp();
IoBus = new SmsIoBus { VideoProcessor = this.VideoProcessor };
Cpu = new Z80(MemoryBus, IoBus); Cpu = new Z80(MemoryBus, IoBus);
} }
@@ -34,8 +36,23 @@ namespace Core
{ {
MemoryBus.CleanRAMData(); MemoryBus.CleanRAMData();
Cpu.Reset(); 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() public void RunFrame()
@@ -44,18 +61,51 @@ namespace Core
while (currentFrameTStates < TStatesPerFrame) while (currentFrameTStates < TStatesPerFrame)
{ {
int tStates = Cpu.Step(); currentFrameTStates += StepMachine();
currentFrameTStates += tStates; string filePath = "captured_data.txt";
// --- FUTURE EXPANSION --- // Mock data to loop through
// VideoProcessor.Update(tStates); //List<ushort> sensorReadings = new List<ushort> { Cpu.PC, Cpu.AF.Word, Cpu.BC.Word, Cpu.DE.Word, Cpu.HL.Word, Cpu.SP};
// AudioProcessor.Update(tStates); //List<string> type = new List<string> {"PC: 0x", "AF: 0x", "BC: 0x", "DE: 0x", "HL: 0x", "SP: 0x" };
// if (VideoProcessor.IsVBlanking && VideoProcessor.InterruptsEnabled) //try
//{ //{
// Cpu.RequestInterrupt(); // // 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(); lblFrameTime = new Label();
richTextBox1 = new RichTextBox(); richTextBox1 = new RichTextBox();
button1 = new Button(); button1 = new Button();
button2 = new Button(); CpuRun = new Button();
SuspendLayout(); SuspendLayout();
// //
// lblAF // lblAF
// //
lblAF.AutoSize = true; lblAF.AutoSize = true;
lblAF.Location = new Point(10, 7); lblAF.Location = new Point(12, 9);
lblAF.Margin = new Padding(2, 0, 2, 0); lblAF.Margin = new Padding(2, 0, 2, 0);
lblAF.Name = "lblAF"; lblAF.Name = "lblAF";
lblAF.Size = new Size(26, 20); lblAF.Size = new Size(33, 25);
lblAF.TabIndex = 0; lblAF.TabIndex = 0;
lblAF.Text = "AF"; lblAF.Text = "AF";
// //
// lblBC // lblBC
// //
lblBC.AutoSize = true; lblBC.AutoSize = true;
lblBC.Location = new Point(9, 48); lblBC.Location = new Point(11, 60);
lblBC.Margin = new Padding(2, 0, 2, 0); lblBC.Margin = new Padding(2, 0, 2, 0);
lblBC.Name = "lblBC"; lblBC.Name = "lblBC";
lblBC.Size = new Size(27, 20); lblBC.Size = new Size(33, 25);
lblBC.TabIndex = 1; lblBC.TabIndex = 1;
lblBC.Text = "BC"; lblBC.Text = "BC";
// //
// lblDE // lblDE
// //
lblDE.AutoSize = true; lblDE.AutoSize = true;
lblDE.Location = new Point(10, 100); lblDE.Location = new Point(12, 125);
lblDE.Margin = new Padding(2, 0, 2, 0); lblDE.Margin = new Padding(2, 0, 2, 0);
lblDE.Name = "lblDE"; lblDE.Name = "lblDE";
lblDE.Size = new Size(28, 20); lblDE.Size = new Size(34, 25);
lblDE.TabIndex = 2; lblDE.TabIndex = 2;
lblDE.Text = "DE"; lblDE.Text = "DE";
// //
// lblHL // lblHL
// //
lblHL.AutoSize = true; lblHL.AutoSize = true;
lblHL.Location = new Point(10, 150); lblHL.Location = new Point(12, 188);
lblHL.Margin = new Padding(2, 0, 2, 0); lblHL.Margin = new Padding(2, 0, 2, 0);
lblHL.Name = "lblHL"; lblHL.Name = "lblHL";
lblHL.Size = new Size(27, 20); lblHL.Size = new Size(33, 25);
lblHL.TabIndex = 3; lblHL.TabIndex = 3;
lblHL.Text = "HL"; lblHL.Text = "HL";
// //
// lblPC // lblPC
// //
lblPC.AutoSize = true; lblPC.AutoSize = true;
lblPC.Location = new Point(9, 200); lblPC.Location = new Point(11, 250);
lblPC.Margin = new Padding(2, 0, 2, 0); lblPC.Margin = new Padding(2, 0, 2, 0);
lblPC.Name = "lblPC"; lblPC.Name = "lblPC";
lblPC.Size = new Size(26, 20); lblPC.Size = new Size(33, 25);
lblPC.TabIndex = 4; lblPC.TabIndex = 4;
lblPC.Text = "PC"; lblPC.Text = "PC";
// //
// lblSP // lblSP
// //
lblSP.AutoSize = true; lblSP.AutoSize = true;
lblSP.Location = new Point(9, 252); lblSP.Location = new Point(11, 315);
lblSP.Margin = new Padding(2, 0, 2, 0); lblSP.Margin = new Padding(2, 0, 2, 0);
lblSP.Name = "lblSP"; lblSP.Name = "lblSP";
lblSP.Size = new Size(25, 20); lblSP.Size = new Size(32, 25);
lblSP.TabIndex = 6; lblSP.TabIndex = 6;
lblSP.Text = "SP"; lblSP.Text = "SP";
// //
// lblFlags // lblFlags
// //
lblFlags.AutoSize = true; lblFlags.AutoSize = true;
lblFlags.Location = new Point(88, 52); lblFlags.Location = new Point(110, 65);
lblFlags.Margin = new Padding(2, 0, 2, 0); lblFlags.Margin = new Padding(2, 0, 2, 0);
lblFlags.Name = "lblFlags"; lblFlags.Name = "lblFlags";
lblFlags.Size = new Size(43, 20); lblFlags.Size = new Size(53, 25);
lblFlags.TabIndex = 7; lblFlags.TabIndex = 7;
lblFlags.Text = "Flags"; lblFlags.Text = "Flags";
// //
// lblTStates // lblTStates
// //
lblTStates.AutoSize = true; lblTStates.AutoSize = true;
lblTStates.Location = new Point(88, 7); lblTStates.Location = new Point(110, 9);
lblTStates.Margin = new Padding(2, 0, 2, 0); lblTStates.Margin = new Padding(2, 0, 2, 0);
lblTStates.Name = "lblTStates"; lblTStates.Name = "lblTStates";
lblTStates.Size = new Size(63, 20); lblTStates.Size = new Size(75, 25);
lblTStates.TabIndex = 8; lblTStates.TabIndex = 8;
lblTStates.Text = "T-States"; lblTStates.Text = "T-States";
// //
// txtMemoryStart // txtMemoryStart
// //
txtMemoryStart.Location = new Point(300, 21); txtMemoryStart.Location = new Point(375, 26);
txtMemoryStart.Margin = new Padding(2); txtMemoryStart.Margin = new Padding(2);
txtMemoryStart.Name = "txtMemoryStart"; txtMemoryStart.Name = "txtMemoryStart";
txtMemoryStart.Size = new Size(121, 27); txtMemoryStart.Size = new Size(150, 31);
txtMemoryStart.TabIndex = 9; txtMemoryStart.TabIndex = 9;
txtMemoryStart.Text = "Memory Start"; txtMemoryStart.Text = "Memory Start";
txtMemoryStart.TextAlign = HorizontalAlignment.Center; txtMemoryStart.TextAlign = HorizontalAlignment.Center;
@@ -153,10 +153,10 @@
// //
// btnRefreshMemory // btnRefreshMemory
// //
btnRefreshMemory.Location = new Point(425, 21); btnRefreshMemory.Location = new Point(531, 26);
btnRefreshMemory.Margin = new Padding(2); btnRefreshMemory.Margin = new Padding(2);
btnRefreshMemory.Name = "btnRefreshMemory"; btnRefreshMemory.Name = "btnRefreshMemory";
btnRefreshMemory.Size = new Size(90, 27); btnRefreshMemory.Size = new Size(112, 34);
btnRefreshMemory.TabIndex = 14; btnRefreshMemory.TabIndex = 14;
btnRefreshMemory.Text = "Refresh Memory"; btnRefreshMemory.Text = "Refresh Memory";
btnRefreshMemory.UseVisualStyleBackColor = true; btnRefreshMemory.UseVisualStyleBackColor = true;
@@ -164,109 +164,117 @@
// //
// txtMemoryView // txtMemoryView
// //
txtMemoryView.Location = new Point(88, 80); txtMemoryView.Location = new Point(110, 100);
txtMemoryView.Margin = new Padding(2); txtMemoryView.Margin = new Padding(2);
txtMemoryView.Name = "txtMemoryView"; txtMemoryView.Name = "txtMemoryView";
txtMemoryView.Size = new Size(443, 895); txtMemoryView.Size = new Size(553, 1118);
txtMemoryView.TabIndex = 15; txtMemoryView.TabIndex = 15;
txtMemoryView.Text = "Memory View Window"; txtMemoryView.Text = "Memory View Window";
// //
// lstDisassembly // lstDisassembly
// //
lstDisassembly.FormattingEnabled = true; lstDisassembly.FormattingEnabled = true;
lstDisassembly.Location = new Point(567, 8); lstDisassembly.ItemHeight = 25;
lstDisassembly.Location = new Point(709, 10);
lstDisassembly.Margin = new Padding(2); lstDisassembly.Margin = new Padding(2);
lstDisassembly.Name = "lstDisassembly"; lstDisassembly.Name = "lstDisassembly";
lstDisassembly.Size = new Size(252, 264); lstDisassembly.Size = new Size(314, 329);
lstDisassembly.TabIndex = 16; lstDisassembly.TabIndex = 16;
// //
// lstStack // lstStack
// //
lstStack.FormattingEnabled = true; lstStack.FormattingEnabled = true;
lstStack.Location = new Point(823, 8); lstStack.ItemHeight = 25;
lstStack.Location = new Point(1029, 10);
lstStack.Margin = new Padding(2); lstStack.Margin = new Padding(2);
lstStack.Name = "lstStack"; lstStack.Name = "lstStack";
lstStack.Size = new Size(130, 264); lstStack.Size = new Size(162, 329);
lstStack.TabIndex = 17; lstStack.TabIndex = 17;
// //
// label1 // label1
// //
label1.AutoSize = true; 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.Name = "label1";
label1.Size = new Size(81, 20); label1.Size = new Size(97, 25);
label1.TabIndex = 19; label1.TabIndex = 19;
label1.Text = "Breakpoint"; label1.Text = "Breakpoint";
// //
// txtBreakpoint // txtBreakpoint
// //
txtBreakpoint.Location = new Point(655, 295); txtBreakpoint.Location = new Point(819, 369);
txtBreakpoint.Margin = new Padding(4);
txtBreakpoint.Name = "txtBreakpoint"; txtBreakpoint.Name = "txtBreakpoint";
txtBreakpoint.Size = new Size(125, 27); txtBreakpoint.Size = new Size(155, 31);
txtBreakpoint.TabIndex = 20; txtBreakpoint.TabIndex = 20;
// //
// label2 // label2
// //
label2.AutoSize = true; 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.Name = "label2";
label2.Size = new Size(62, 20); label2.Size = new Size(77, 25);
label2.TabIndex = 21; label2.TabIndex = 21;
label2.Text = "Address"; label2.Text = "Address";
// //
// lblIX // lblIX
// //
lblIX.AutoSize = true; lblIX.AutoSize = true;
lblIX.Location = new Point(9, 298); lblIX.Location = new Point(11, 372);
lblIX.Margin = new Padding(2, 0, 2, 0); lblIX.Margin = new Padding(2, 0, 2, 0);
lblIX.Name = "lblIX"; lblIX.Name = "lblIX";
lblIX.Size = new Size(22, 20); lblIX.Size = new Size(28, 25);
lblIX.TabIndex = 22; lblIX.TabIndex = 22;
lblIX.Text = "IX"; lblIX.Text = "IX";
// //
// lblIY // lblIY
// //
lblIY.AutoSize = true; lblIY.AutoSize = true;
lblIY.Location = new Point(10, 344); lblIY.Location = new Point(12, 430);
lblIY.Margin = new Padding(2, 0, 2, 0); lblIY.Margin = new Padding(2, 0, 2, 0);
lblIY.Name = "lblIY"; lblIY.Name = "lblIY";
lblIY.Size = new Size(21, 20); lblIY.Size = new Size(27, 25);
lblIY.TabIndex = 23; lblIY.TabIndex = 23;
lblIY.Text = "IY"; lblIY.Text = "IY";
// //
// lblIff1 // lblIff1
// //
lblIff1.AutoSize = true; 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.Name = "lblIff1";
lblIff1.Size = new Size(35, 20); lblIff1.Size = new Size(45, 25);
lblIff1.TabIndex = 24; lblIff1.TabIndex = 24;
lblIff1.Text = "IFF1"; lblIff1.Text = "IFF1";
// //
// lblIff2 // lblIff2
// //
lblIff2.AutoSize = true; 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.Name = "lblIff2";
lblIff2.Size = new Size(35, 20); lblIff2.Size = new Size(45, 25);
lblIff2.TabIndex = 25; lblIff2.TabIndex = 25;
lblIff2.Text = "IFF2"; lblIff2.Text = "IFF2";
// //
// lblIE // lblIE
// //
lblIE.AutoSize = true; 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.Name = "lblIE";
lblIE.Size = new Size(109, 20); lblIE.Size = new Size(133, 25);
lblIE.TabIndex = 26; lblIE.TabIndex = 26;
lblIE.Text = "Interrupt Mode"; lblIE.Text = "Interrupt Mode";
// //
// btnReset // btnReset
// //
btnReset.Location = new Point(651, 327); btnReset.Location = new Point(814, 409);
btnReset.Margin = new Padding(2); btnReset.Margin = new Padding(2);
btnReset.Name = "btnReset"; btnReset.Name = "btnReset";
btnReset.Size = new Size(132, 27); btnReset.Size = new Size(165, 34);
btnReset.TabIndex = 27; btnReset.TabIndex = 27;
btnReset.Text = "Set Breakpoint"; btnReset.Text = "Set Breakpoint";
btnReset.UseVisualStyleBackColor = true; btnReset.UseVisualStyleBackColor = true;
@@ -281,69 +289,72 @@
// lblFrames // lblFrames
// //
lblFrames.AutoSize = true; lblFrames.AutoSize = true;
lblFrames.Location = new Point(567, 682); lblFrames.Location = new Point(709, 852);
lblFrames.Margin = new Padding(2, 0, 2, 0); lblFrames.Margin = new Padding(2, 0, 2, 0);
lblFrames.Name = "lblFrames"; lblFrames.Name = "lblFrames";
lblFrames.Size = new Size(124, 20); lblFrames.Size = new Size(149, 25);
lblFrames.TabIndex = 28; lblFrames.TabIndex = 28;
lblFrames.Text = "Frames Rendered"; lblFrames.Text = "Frames Rendered";
// //
// lblFPS // lblFPS
// //
lblFPS.AutoSize = true; lblFPS.AutoSize = true;
lblFPS.Location = new Point(659, 759); lblFPS.Location = new Point(824, 949);
lblFPS.Margin = new Padding(2, 0, 2, 0); lblFPS.Margin = new Padding(2, 0, 2, 0);
lblFPS.Name = "lblFPS"; lblFPS.Name = "lblFPS";
lblFPS.Size = new Size(32, 20); lblFPS.Size = new Size(41, 25);
lblFPS.TabIndex = 29; lblFPS.TabIndex = 29;
lblFPS.Text = "FPS"; lblFPS.Text = "FPS";
// //
// lblFrameTime // lblFrameTime
// //
lblFrameTime.AutoSize = true; lblFrameTime.AutoSize = true;
lblFrameTime.Location = new Point(604, 718); lblFrameTime.Location = new Point(755, 898);
lblFrameTime.Margin = new Padding(2, 0, 2, 0); lblFrameTime.Margin = new Padding(2, 0, 2, 0);
lblFrameTime.Name = "lblFrameTime"; lblFrameTime.Name = "lblFrameTime";
lblFrameTime.Size = new Size(87, 20); lblFrameTime.Size = new Size(104, 25);
lblFrameTime.TabIndex = 30; lblFrameTime.TabIndex = 30;
lblFrameTime.Text = "Frame Time"; lblFrameTime.Text = "Frame Time";
// //
// richTextBox1 // richTextBox1
// //
richTextBox1.Enabled = false; richTextBox1.Enabled = false;
richTextBox1.Location = new Point(567, 509); richTextBox1.Location = new Point(709, 636);
richTextBox1.Margin = new Padding(4);
richTextBox1.Name = "richTextBox1"; richTextBox1.Name = "richTextBox1";
richTextBox1.ReadOnly = true; richTextBox1.ReadOnly = true;
richTextBox1.Size = new Size(304, 128); richTextBox1.Size = new Size(379, 159);
richTextBox1.TabIndex = 32; 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"; 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
// //
button1.Location = new Point(555, 824); button1.Location = new Point(694, 1030);
button1.Margin = new Padding(4);
button1.Name = "button1"; button1.Name = "button1";
button1.Size = new Size(94, 29); button1.Size = new Size(118, 36);
button1.TabIndex = 33; button1.TabIndex = 33;
button1.Text = "CPU Step"; button1.Text = "CPU Step";
button1.UseVisualStyleBackColor = true; button1.UseVisualStyleBackColor = true;
button1.Click += btnStep_Click; button1.Click += btnStep_Click;
// //
// button2 // CpuRun
// //
button2.Location = new Point(555, 883); CpuRun.Location = new Point(694, 1104);
button2.Name = "button2"; CpuRun.Margin = new Padding(4);
button2.Size = new Size(94, 29); CpuRun.Name = "CpuRun";
button2.TabIndex = 34; CpuRun.Size = new Size(118, 36);
button2.Text = "CPU Run"; CpuRun.TabIndex = 34;
button2.UseVisualStyleBackColor = true; CpuRun.Text = "CPU Run";
button2.Click += btnStep_Click; CpuRun.UseVisualStyleBackColor = true;
CpuRun.Click += CpuRun_Click;
// //
// DebuggerForm // DebuggerForm
// //
AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(965, 990); ClientSize = new Size(1206, 1238);
Controls.Add(button2); Controls.Add(CpuRun);
Controls.Add(button1); Controls.Add(button1);
Controls.Add(richTextBox1); Controls.Add(richTextBox1);
Controls.Add(lblFrameTime); Controls.Add(lblFrameTime);
@@ -402,13 +413,13 @@
private Label lblIff2; private Label lblIff2;
private Label lblIE; private Label lblIE;
private Button btnReset; private Button btnReset;
private System.Windows.Forms.Timer uiUpdateTimer;
private Label lblFrames; private Label lblFrames;
private Label lblFPS; private Label lblFPS;
private Label lblFrameTime; private Label lblFrameTime;
private RichTextBox richTextBox1; private RichTextBox richTextBox1;
private Button button1; private Button button1;
private Button button2; private Button CpuRun;
public System.Windows.Forms.Timer uiUpdateTimer;
//private TextBox textBox4; //private TextBox textBox4;
} }
} }

View File

@@ -27,11 +27,36 @@ namespace Desktop
_mainForm = mainForm; _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) private void btnStep_Click(object sender, EventArgs e)
{ {
try try
{ {
_cpu.Step(); // Ask the main form to step the WHOLE machine, not just the Z80!
_mainForm.StepEmulator();
UpdateDisplay(); UpdateDisplay();
} }
catch (Exception ex) catch (Exception ex)
@@ -57,7 +82,7 @@ namespace Desktop
UpdateDisplay(); UpdateDisplay();
} }
private void uiUpdateTimer_Tick(object sender, EventArgs e) public void uiUpdateTimer_Tick(object sender, EventArgs e)
{ {
UpdateDisplay(); UpdateDisplay();
} }
@@ -180,6 +205,9 @@ namespace Desktop
case 0x02: mnemonic = "LD (BC), A"; break; case 0x02: mnemonic = "LD (BC), A"; break;
// --- 16-Bit Increments --- // --- 16-Bit Increments ---
case 0x03: mnemonic = "INC BC"; break; case 0x03: mnemonic = "INC BC"; break;
case 0x07:
mnemonic = "RLCA";
break;
case 0x08: case 0x08:
mnemonic = "EX AF, AF'"; mnemonic = "EX AF, AF'";
break; break;

View File

@@ -29,32 +29,48 @@
private void InitializeComponent() private void InitializeComponent()
{ {
button1 = new Button(); button1 = new Button();
pbScreen = new PictureBox();
((System.ComponentModel.ISupportInitialize)pbScreen).BeginInit();
SuspendLayout(); SuspendLayout();
// //
// button1 // 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.Name = "button1";
button1.Size = new Size(94, 29); button1.Size = new Size(118, 36);
button1.TabIndex = 0; button1.TabIndex = 0;
button1.Text = "button1"; button1.Text = "button1";
button1.UseVisualStyleBackColor = true; button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click; 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 // Form1
// //
AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450); ClientSize = new Size(1425, 842);
Controls.Add(pbScreen);
Controls.Add(button1); Controls.Add(button1);
Margin = new Padding(4, 4, 4, 4);
Name = "Form1"; Name = "Form1";
Text = "Form1"; Text = "Form1";
Click += button1_Click; Click += button1_Click;
((System.ComponentModel.ISupportInitialize)pbScreen).EndInit();
ResumeLayout(false); ResumeLayout(false);
} }
#endregion #endregion
private Button button1; private Button button1;
private PictureBox pbScreen;
} }
} }

View File

@@ -1,12 +1,21 @@
using System.Reflection; using System.Reflection;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using Core; using Core;
using System.Threading.Tasks;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Desktop namespace Desktop
{ {
public partial class Form1 : Form public partial class Form1 : Form
{ {
private SmsMachine _machine = null!; private SmsMachine _machine = null!;
private DebuggerForm _debugger; private DebuggerForm _debugger;
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
public bool IsRunning { get; private set; } = false;
public ushort? Breakpoint public ushort? Breakpoint
{ {
@@ -20,23 +29,67 @@ namespace Desktop
_machine = new SmsMachine(); _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) private void button1_Click(object sender, EventArgs e)
{ {
// 1. Load a commercial Master System ROM! // 1. Load a commercial Master System ROM!
byte[] rom = File.ReadAllBytes(@"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\Golden Axe Warrior.sms"); byte[] rom = File.ReadAllBytes(@"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\Golden Axe Warrior.sms");
try
{
// 2. Jam it into the Sega Mapper // 2. Jam it into the Sega Mapper
_machine.LoadCartridge(rom); _machine.LoadCartridge(rom);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// 3. Open the Debugger to look around // 3. Open the Debugger to look around
if (_debugger == null || _debugger.IsDisposed) if (_debugger == null || _debugger.IsDisposed)