Got main system and VDP working! There is a display!
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
//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
194
Core/Video/SmsVdp.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
147
Desktop/DebuggerForm.Designer.cs
generated
147
Desktop/DebuggerForm.Designer.cs
generated
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
24
Desktop/Form1.Designer.cs
generated
24
Desktop/Form1.Designer.cs
generated
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user