More OpCodes - working towards ZEXALL perfection
This commit is contained in:
@@ -6,6 +6,7 @@ namespace Core.Cpu
|
|||||||
{
|
{
|
||||||
public partial class Z80
|
public partial class Z80
|
||||||
{
|
{
|
||||||
|
public bool IsZexDocMode { get; set; } = false;
|
||||||
//T-State counter
|
//T-State counter
|
||||||
public long TotalTStates { get; set; }
|
public long TotalTStates { get; set; }
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ namespace Core.Cpu
|
|||||||
public Func<ushort, long, int>? WaitStateCallback { get; set; }
|
public Func<ushort, long, int>? WaitStateCallback { get; set; }
|
||||||
|
|
||||||
//Misc Variables
|
//Misc Variables
|
||||||
|
public bool EnableFastLoad { get; set; } = true;
|
||||||
byte newFlags = 0;
|
byte newFlags = 0;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
@@ -194,10 +196,44 @@ namespace Core.Cpu
|
|||||||
|
|
||||||
public int Step()
|
public int Step()
|
||||||
{
|
{
|
||||||
if (PC == 0x0556 && _tapManager.HasBlocks)
|
if (IsZexDocMode && PC == 0x0005)
|
||||||
{
|
{
|
||||||
HandleInstantTapeLoad();
|
// CP/M System Call Hook
|
||||||
return 100; // Return a dummy number of T-States for the hijacking
|
if (BC.Low == 2) // C = 2: Print a single character stored in register E
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.Write((char)DE.Low);
|
||||||
|
}
|
||||||
|
else if (BC.Low == 9) // C = 9: Print a string starting at memory address DE, terminated by '$'
|
||||||
|
{
|
||||||
|
ushort addr = DE.Word;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
char c = (char)ReadMemory(addr++);
|
||||||
|
if (c == '$') break;
|
||||||
|
System.Diagnostics.Debug.Write(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate a 'RET' instruction to return from the CALL 0x0005
|
||||||
|
byte retLow = ReadMemory(SP);
|
||||||
|
SP++;
|
||||||
|
byte retHigh = ReadMemory(SP);
|
||||||
|
SP++;
|
||||||
|
PC = (ushort)((retHigh << 8) | retLow);
|
||||||
|
|
||||||
|
return 10; // Skip normal execution for this cycle
|
||||||
|
}
|
||||||
|
if (PC == 0x0556)
|
||||||
|
{
|
||||||
|
if (EnableFastLoad)
|
||||||
|
{
|
||||||
|
HandleInstantTapeLoad();
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tapManager.Play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the next opcode and increment the Program Counter
|
// Fetch the next opcode and increment the Program Counter
|
||||||
@@ -2819,7 +2855,7 @@ namespace Core.Cpu
|
|||||||
|
|
||||||
AF.Low = newFlags;
|
AF.Low = newFlags;
|
||||||
|
|
||||||
return 19; // 19 T-States
|
return 19;
|
||||||
case 0xCB: // The FD CB nested prefix
|
case 0xCB: // The FD CB nested prefix
|
||||||
{
|
{
|
||||||
sbyte displacement = (sbyte)FetchByte();
|
sbyte displacement = (sbyte)FetchByte();
|
||||||
@@ -2866,6 +2902,26 @@ namespace Core.Cpu
|
|||||||
throw new Exception("Invalid bitwise operation.");
|
throw new Exception("Invalid bitwise operation.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 0xE1: // POP IY
|
||||||
|
// 1. Read the Low byte from the current Stack Pointer, then increment SP
|
||||||
|
IY.Low = ReadMemory(SP);
|
||||||
|
SP++;
|
||||||
|
|
||||||
|
// 2. Read the High byte from the new Stack Pointer, then increment SP
|
||||||
|
IY.High = ReadMemory(SP);
|
||||||
|
SP++;
|
||||||
|
|
||||||
|
return 14; // 14 T-States
|
||||||
|
case 0xE5: // PUSH IY
|
||||||
|
// 1. Decrement SP and write the High byte
|
||||||
|
SP--;
|
||||||
|
WriteMemory(SP, IY.High);
|
||||||
|
|
||||||
|
// 2. Decrement SP again and write the Low byte
|
||||||
|
SP--;
|
||||||
|
WriteMemory(SP, IY.Low);
|
||||||
|
|
||||||
|
return 15; // 15 T-States
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!");
|
throw new NotImplementedException($"FD prefix opcode {opcode:X2} at PC 0x{(PC - 2):X4} not implemented!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,37 +5,47 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
|
|||||||
namespace Core.Io
|
namespace Core.Io
|
||||||
{
|
{
|
||||||
public class IO_Bus
|
public class IO_Bus
|
||||||
{
|
{
|
||||||
public byte BorderColorIndex { get; private set; } = 7;
|
public byte BorderColorIndex { get; private set; } = 7;
|
||||||
public bool BeeperState { get; private set; } = false;
|
public bool BeeperState { get; private set; } = false;
|
||||||
public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
TapManager _tapManager = new TapManager();
|
||||||
|
|
||||||
public byte ReadPort(ushort portAddress)
|
public IO_Bus(TapManager tapManager)
|
||||||
{
|
|
||||||
// The Spectrum ULA responds to any even port address (where the lowest bit is 0)
|
|
||||||
if ((portAddress & 0x01) == 0)
|
|
||||||
{
|
{
|
||||||
byte highByte = (byte)(portAddress >> 8); // The B register!
|
_tapManager = tapManager;
|
||||||
byte result = 0xFF; // Start assuming no keys are pressed
|
|
||||||
|
|
||||||
// The ROM pulls a specific bit low (0) in the high byte to request a row.
|
|
||||||
// Sometimes it pulls multiple bits low to scan multiple rows at once, so we AND the results.
|
|
||||||
if ((highByte & 0x01) == 0) result &= KeyboardRows[0]; // 0xFE: CAPS, Z, X, C, V
|
|
||||||
if ((highByte & 0x02) == 0) result &= KeyboardRows[1]; // 0xFD: A, S, D, F, G
|
|
||||||
if ((highByte & 0x04) == 0) result &= KeyboardRows[2]; // 0xFB: Q, W, E, R, T
|
|
||||||
if ((highByte & 0x08) == 0) result &= KeyboardRows[3]; // 0xF7: 1, 2, 3, 4, 5
|
|
||||||
if ((highByte & 0x10) == 0) result &= KeyboardRows[4]; // 0xEF: 0, 9, 8, 7, 6
|
|
||||||
if ((highByte & 0x20) == 0) result &= KeyboardRows[5]; // 0xDF: P, O, I, U, Y
|
|
||||||
if ((highByte & 0x40) == 0) result &= KeyboardRows[6]; // 0xBF: ENTER, L, K, J, H
|
|
||||||
if ((highByte & 0x80) == 0) result &= KeyboardRows[7]; // 0x7F: SPACE, SYM, M, N, B
|
|
||||||
|
|
||||||
// The top 3 bits (5, 6, 7) are unused by the keyboard and usually return 1 on a real Spectrum
|
|
||||||
return (byte)(result | 0xE0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return 0xFF for unhandled ports
|
public byte ReadPort(ushort portAddress)
|
||||||
return 0xFF;
|
{
|
||||||
}
|
// The Spectrum ULA responds to any even port address (where the lowest bit is 0)
|
||||||
|
if ((portAddress & 0x01) == 0)
|
||||||
|
{
|
||||||
|
byte highByte = (byte)(portAddress >> 8); // The B register!
|
||||||
|
byte result = 0xFF; // Start assuming no keys are pressed
|
||||||
|
|
||||||
|
// The ROM pulls a specific bit low (0) in the high byte to request a row.
|
||||||
|
// Sometimes it pulls multiple bits low to scan multiple rows at once, so we AND the results.
|
||||||
|
if ((highByte & 0x01) == 0) result &= KeyboardRows[0]; // 0xFE: CAPS, Z, X, C, V
|
||||||
|
if ((highByte & 0x02) == 0) result &= KeyboardRows[1]; // 0xFD: A, S, D, F, G
|
||||||
|
if ((highByte & 0x04) == 0) result &= KeyboardRows[2]; // 0xFB: Q, W, E, R, T
|
||||||
|
if ((highByte & 0x08) == 0) result &= KeyboardRows[3]; // 0xF7: 1, 2, 3, 4, 5
|
||||||
|
if ((highByte & 0x10) == 0) result &= KeyboardRows[4]; // 0xEF: 0, 9, 8, 7, 6
|
||||||
|
if ((highByte & 0x20) == 0) result &= KeyboardRows[5]; // 0xDF: P, O, I, U, Y
|
||||||
|
if ((highByte & 0x40) == 0) result &= KeyboardRows[6]; // 0xBF: ENTER, L, K, J, H
|
||||||
|
if ((highByte & 0x80) == 0) result &= KeyboardRows[7]; // 0x7F: SPACE, SYM, M, N, B
|
||||||
|
if (_tapManager.EarBit) result |= 0x40; // Set Bit 6 high
|
||||||
|
else result &= 0xBF; // Set Bit 6 low
|
||||||
|
|
||||||
|
|
||||||
|
//return result;
|
||||||
|
// The top 3 bits (5, 6, 7) are unused by the keyboard and usually return 1 on a real Spectrum
|
||||||
|
return (byte)(result | 0xE0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return 0xFF for unhandled ports
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
public void WritePort(ushort portAddress, byte portValue)
|
public void WritePort(ushort portAddress, byte portValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,8 +6,20 @@ namespace Core.Io
|
|||||||
public class TapManager
|
public class TapManager
|
||||||
{
|
{
|
||||||
private Queue<byte[]> _blocks = new Queue<byte[]>();
|
private Queue<byte[]> _blocks = new Queue<byte[]>();
|
||||||
|
private byte[] _currentBlock;
|
||||||
|
|
||||||
|
// State Machine Tracking
|
||||||
|
private enum TapeState { Idle, Pilot, Sync1, Sync2, Data, Pause }
|
||||||
|
private TapeState _state = TapeState.Idle;
|
||||||
|
|
||||||
|
public bool EarBit { get; private set; } = false; // The physical audio signal sent to the CPU
|
||||||
|
|
||||||
|
private int _pulseTimer = 0;
|
||||||
|
private int _pilotPulsesRemaining = 0;
|
||||||
|
private int _byteIndex = 0;
|
||||||
|
private int _bitIndex = 0;
|
||||||
|
private int _pulseCountForCurrentBit = 0;
|
||||||
|
|
||||||
|
|
||||||
public void LoadTapData(byte[] fileData)
|
public void LoadTapData(byte[] fileData)
|
||||||
{
|
{
|
||||||
_blocks.Clear();
|
_blocks.Clear();
|
||||||
@@ -15,20 +27,136 @@ namespace Core.Io
|
|||||||
|
|
||||||
while (position < fileData.Length)
|
while (position < fileData.Length)
|
||||||
{
|
{
|
||||||
// 1. Read the 16-bit block length (Little Endian)
|
|
||||||
int blockLength = fileData[position] | (fileData[position + 1] << 8);
|
int blockLength = fileData[position] | (fileData[position + 1] << 8);
|
||||||
position += 2;
|
position += 2;
|
||||||
|
|
||||||
// 2. Extract the block payload
|
|
||||||
byte[] blockData = new byte[blockLength];
|
byte[] blockData = new byte[blockLength];
|
||||||
Array.Copy(fileData, position, blockData, 0, blockLength);
|
Array.Copy(fileData, position, blockData, 0, blockLength);
|
||||||
position += blockLength;
|
position += blockLength;
|
||||||
|
|
||||||
// 3. Queue it up
|
|
||||||
_blocks.Enqueue(blockData);
|
_blocks.Enqueue(blockData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Play()
|
||||||
|
{
|
||||||
|
if (_blocks.Count > 0 && _state == TapeState.Idle)
|
||||||
|
{
|
||||||
|
LoadNextBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadNextBlock()
|
||||||
|
{
|
||||||
|
if (_blocks.Count == 0)
|
||||||
|
{
|
||||||
|
_state = TapeState.Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentBlock = _blocks.Dequeue();
|
||||||
|
_byteIndex = 0;
|
||||||
|
_bitIndex = 7; // MSB first
|
||||||
|
_pulseCountForCurrentBit = 0;
|
||||||
|
|
||||||
|
// Header blocks (flag 0x00) have longer pilot tones than Data blocks
|
||||||
|
bool isHeader = _currentBlock.Length > 0 && _currentBlock[0] == 0;
|
||||||
|
_pilotPulsesRemaining = isHeader ? 8063 : 3223;
|
||||||
|
|
||||||
|
_state = TapeState.Pilot;
|
||||||
|
_pulseTimer = 2168; // Length of a pilot pulse
|
||||||
|
EarBit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- THE ENGINE SYNC ---
|
||||||
|
// Call this every time the CPU steps, passing in how many T-States elapsed
|
||||||
|
public void Update(int tStatesElapsed)
|
||||||
|
{
|
||||||
|
if (_state == TapeState.Idle) return;
|
||||||
|
|
||||||
|
_pulseTimer -= tStatesElapsed;
|
||||||
|
|
||||||
|
if (_pulseTimer <= 0)
|
||||||
|
{
|
||||||
|
EarBit = !EarBit; // Flip the audio square wave
|
||||||
|
AdvanceState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AdvanceState()
|
||||||
|
{
|
||||||
|
switch (_state)
|
||||||
|
{
|
||||||
|
case TapeState.Pilot:
|
||||||
|
_pilotPulsesRemaining--;
|
||||||
|
if (_pilotPulsesRemaining > 0)
|
||||||
|
{
|
||||||
|
_pulseTimer += 2168;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_state = TapeState.Sync1;
|
||||||
|
_pulseTimer += 667;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TapeState.Sync1:
|
||||||
|
_state = TapeState.Sync2;
|
||||||
|
_pulseTimer += 735;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TapeState.Sync2:
|
||||||
|
_state = TapeState.Data;
|
||||||
|
CalculateNextDataPulse();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TapeState.Data:
|
||||||
|
_pulseCountForCurrentBit++;
|
||||||
|
if (_pulseCountForCurrentBit < 2)
|
||||||
|
{
|
||||||
|
// A bit requires 2 pulses (one high, one low)
|
||||||
|
CalculateNextDataPulse();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Move to next bit
|
||||||
|
_pulseCountForCurrentBit = 0;
|
||||||
|
_bitIndex--;
|
||||||
|
|
||||||
|
if (_bitIndex < 0)
|
||||||
|
{
|
||||||
|
// Move to next byte
|
||||||
|
_bitIndex = 7;
|
||||||
|
_byteIndex++;
|
||||||
|
|
||||||
|
if (_byteIndex >= _currentBlock.Length)
|
||||||
|
{
|
||||||
|
// Block finished! 1 second pause before the next block
|
||||||
|
_state = TapeState.Pause;
|
||||||
|
_pulseTimer += 3500000;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalculateNextDataPulse();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TapeState.Pause:
|
||||||
|
LoadNextBlock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateNextDataPulse()
|
||||||
|
{
|
||||||
|
// Extract the current bit from the current byte
|
||||||
|
bool isBitOne = (_currentBlock[_byteIndex] & (1 << _bitIndex)) != 0;
|
||||||
|
|
||||||
|
// Bit 0 = 855 T-States, Bit 1 = 1710 T-States
|
||||||
|
_pulseTimer += isBitOne ? 1710 : 855;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FAST LOAD METHODS (For ROM Hijack) ---
|
||||||
public byte[] GetNextBlock()
|
public byte[] GetNextBlock()
|
||||||
{
|
{
|
||||||
return _blocks.Count > 0 ? _blocks.Dequeue() : null;
|
return _blocks.Count > 0 ? _blocks.Dequeue() : null;
|
||||||
@@ -37,3 +165,46 @@ namespace Core.Io
|
|||||||
public bool HasBlocks => _blocks.Count > 0;
|
public bool HasBlocks => _blocks.Count > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//using System;
|
||||||
|
//using System.Collections.Generic;
|
||||||
|
|
||||||
|
//namespace Core.Io
|
||||||
|
//{
|
||||||
|
// public class TapManager
|
||||||
|
// {
|
||||||
|
// private Queue<byte[]> _blocks = new Queue<byte[]>();
|
||||||
|
|
||||||
|
|
||||||
|
// public void LoadTapData(byte[] fileData)
|
||||||
|
// {
|
||||||
|
// _blocks.Clear();
|
||||||
|
// int position = 0;
|
||||||
|
|
||||||
|
// while (position < fileData.Length)
|
||||||
|
// {
|
||||||
|
// // 1. Read the 16-bit block length (Little Endian)
|
||||||
|
// int blockLength = fileData[position] | (fileData[position + 1] << 8);
|
||||||
|
// position += 2;
|
||||||
|
|
||||||
|
// // 2. Extract the block payload
|
||||||
|
// byte[] blockData = new byte[blockLength];
|
||||||
|
// Array.Copy(fileData, position, blockData, 0, blockLength);
|
||||||
|
// position += blockLength;
|
||||||
|
|
||||||
|
// // 3. Queue it up
|
||||||
|
// _blocks.Enqueue(blockData);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public byte[] GetNextBlock()
|
||||||
|
// {
|
||||||
|
// return _blocks.Count > 0 ? _blocks.Dequeue() : null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public bool HasBlocks => _blocks.Count > 0;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|||||||
@@ -1285,6 +1285,16 @@ namespace Desktop
|
|||||||
mnemonic = $"LD (IY{sign}{d}), L";
|
mnemonic = $"LD (IY{sign}{d}), L";
|
||||||
instructionLength = 3;
|
instructionLength = 3;
|
||||||
}
|
}
|
||||||
|
else if (fdOpcode == 0xE1)
|
||||||
|
{
|
||||||
|
mnemonic = "POP IY";
|
||||||
|
instructionLength = 2;
|
||||||
|
}
|
||||||
|
else if(fdOpcode == 0xE5)
|
||||||
|
{
|
||||||
|
mnemonic = "PUSH IY";
|
||||||
|
instructionLength = 2;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})";
|
mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})";
|
||||||
|
|||||||
31
Desktop/Form1.Designer.cs
generated
31
Desktop/Form1.Designer.cs
generated
@@ -42,6 +42,9 @@
|
|||||||
resetToolStripMenuItem = new ToolStripMenuItem();
|
resetToolStripMenuItem = new ToolStripMenuItem();
|
||||||
stepToolStripMenuItem = new ToolStripMenuItem();
|
stepToolStripMenuItem = new ToolStripMenuItem();
|
||||||
resetToolStripMenuItem1 = new ToolStripMenuItem();
|
resetToolStripMenuItem1 = new ToolStripMenuItem();
|
||||||
|
optionsToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
fastLoadingToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
runZEXDOCToolStripMenuItem = new ToolStripMenuItem();
|
||||||
((System.ComponentModel.ISupportInitialize)picScreen).BeginInit();
|
((System.ComponentModel.ISupportInitialize)picScreen).BeginInit();
|
||||||
menuStrip1.SuspendLayout();
|
menuStrip1.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
@@ -59,7 +62,7 @@
|
|||||||
// menuStrip1
|
// menuStrip1
|
||||||
//
|
//
|
||||||
menuStrip1.ImageScalingSize = new Size(24, 24);
|
menuStrip1.ImageScalingSize = new Size(24, 24);
|
||||||
menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, viewToolStripMenuItem, machineToolStripMenuItem });
|
menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, viewToolStripMenuItem, machineToolStripMenuItem, optionsToolStripMenuItem });
|
||||||
menuStrip1.Location = new Point(0, 0);
|
menuStrip1.Location = new Point(0, 0);
|
||||||
menuStrip1.Name = "menuStrip1";
|
menuStrip1.Name = "menuStrip1";
|
||||||
menuStrip1.Padding = new Padding(5, 2, 0, 2);
|
menuStrip1.Padding = new Padding(5, 2, 0, 2);
|
||||||
@@ -151,6 +154,29 @@
|
|||||||
resetToolStripMenuItem1.Text = "Reset";
|
resetToolStripMenuItem1.Text = "Reset";
|
||||||
resetToolStripMenuItem1.Click += btnReset_Click;
|
resetToolStripMenuItem1.Click += btnReset_Click;
|
||||||
//
|
//
|
||||||
|
// optionsToolStripMenuItem
|
||||||
|
//
|
||||||
|
optionsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { fastLoadingToolStripMenuItem, runZEXDOCToolStripMenuItem });
|
||||||
|
optionsToolStripMenuItem.Name = "optionsToolStripMenuItem";
|
||||||
|
optionsToolStripMenuItem.Size = new Size(75, 24);
|
||||||
|
optionsToolStripMenuItem.Text = "Options";
|
||||||
|
//
|
||||||
|
// fastLoadingToolStripMenuItem
|
||||||
|
//
|
||||||
|
fastLoadingToolStripMenuItem.Checked = true;
|
||||||
|
fastLoadingToolStripMenuItem.CheckState = CheckState.Checked;
|
||||||
|
fastLoadingToolStripMenuItem.Name = "fastLoadingToolStripMenuItem";
|
||||||
|
fastLoadingToolStripMenuItem.Size = new Size(224, 26);
|
||||||
|
fastLoadingToolStripMenuItem.Text = "Fast TAP Loading";
|
||||||
|
fastLoadingToolStripMenuItem.Click += fastLoadingToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// runZEXDOCToolStripMenuItem
|
||||||
|
//
|
||||||
|
runZEXDOCToolStripMenuItem.Name = "runZEXDOCToolStripMenuItem";
|
||||||
|
runZEXDOCToolStripMenuItem.Size = new Size(224, 26);
|
||||||
|
runZEXDOCToolStripMenuItem.Text = "Run ZEXDOC";
|
||||||
|
runZEXDOCToolStripMenuItem.Click += btnRunZexDoc_Click;
|
||||||
|
//
|
||||||
// Form1
|
// Form1
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||||
@@ -185,5 +211,8 @@
|
|||||||
private ToolStripMenuItem resetToolStripMenuItem;
|
private ToolStripMenuItem resetToolStripMenuItem;
|
||||||
private ToolStripMenuItem stepToolStripMenuItem;
|
private ToolStripMenuItem stepToolStripMenuItem;
|
||||||
private ToolStripMenuItem resetToolStripMenuItem1;
|
private ToolStripMenuItem resetToolStripMenuItem1;
|
||||||
|
private ToolStripMenuItem optionsToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem fastLoadingToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem runZEXDOCToolStripMenuItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace Desktop
|
|||||||
public double FramesPerSecond = 0;
|
public double FramesPerSecond = 0;
|
||||||
public double TotalFrameTime = 0;
|
public double TotalFrameTime = 0;
|
||||||
public double FrameTime = 0;
|
public double FrameTime = 0;
|
||||||
|
public bool exceptionRaised = false;
|
||||||
|
|
||||||
|
|
||||||
public Form1()
|
public Form1()
|
||||||
@@ -40,10 +41,10 @@ namespace Desktop
|
|||||||
{
|
{
|
||||||
_baseTitle = this.Text;
|
_baseTitle = this.Text;
|
||||||
_memoryBus = new MemoryBus();
|
_memoryBus = new MemoryBus();
|
||||||
_simpleIoBus = new IO_Bus();
|
_tapManager = new TapManager();
|
||||||
|
_simpleIoBus = new IO_Bus(_tapManager);
|
||||||
_ula = new ULA(_memoryBus, _simpleIoBus);
|
_ula = new ULA(_memoryBus, _simpleIoBus);
|
||||||
_beeper = new BeeperDevice();
|
_beeper = new BeeperDevice();
|
||||||
_tapManager = new TapManager();
|
|
||||||
_memoryBus.CrapRAMData();
|
_memoryBus.CrapRAMData();
|
||||||
byte[] romData = RomLoader.Load("48.rom");
|
byte[] romData = RomLoader.Load("48.rom");
|
||||||
_memoryBus.LoadRom(romData);
|
_memoryBus.LoadRom(romData);
|
||||||
@@ -92,13 +93,19 @@ namespace Desktop
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long tStatesBefore = _cpu.TotalTStates;
|
||||||
|
|
||||||
// --- Execute Instruction ---
|
// --- Execute Instruction ---
|
||||||
_cpu.Step();
|
_cpu.Step();
|
||||||
|
|
||||||
|
int elapsedTStates = (int)(_cpu.TotalTStates - tStatesBefore);
|
||||||
|
_tapManager.Update(elapsedTStates);
|
||||||
|
|
||||||
//Process audio at the correct time
|
//Process audio at the correct time
|
||||||
while (_cpu.TotalTStates >= (long)(audioSampleCount * 79.365))
|
while (_cpu.TotalTStates >= (long)(audioSampleCount * 79.365))
|
||||||
{
|
{
|
||||||
_beeper.AddSample(_simpleIoBus.BeeperState);
|
bool finalAudioOutput = _simpleIoBus.BeeperState ^ _tapManager.EarBit;
|
||||||
|
_beeper.AddSample(finalAudioOutput);
|
||||||
audioSampleCount++;
|
audioSampleCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +126,7 @@ namespace Desktop
|
|||||||
this.Invoke((MethodInvoker)delegate
|
this.Invoke((MethodInvoker)delegate
|
||||||
{
|
{
|
||||||
UpdateScreenBitmap();
|
UpdateScreenBitmap();
|
||||||
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1}";
|
this.Text = $"{_baseTitle} - FPS: {FramesPerSecond:F1} - Fast Loading: {_cpu.EnableFastLoad.ToString()}";
|
||||||
});
|
});
|
||||||
TotalFrameCount++;
|
TotalFrameCount++;
|
||||||
|
|
||||||
@@ -129,7 +136,7 @@ namespace Desktop
|
|||||||
|
|
||||||
if (elapsedMs < targetTimeMs)
|
if (elapsedMs < targetTimeMs)
|
||||||
{
|
{
|
||||||
Thread.Sleep((int)(targetTimeMs - elapsedMs));
|
//Thread.Sleep((int)(targetTimeMs - elapsedMs));
|
||||||
}
|
}
|
||||||
TotalFrameTime += fpsStopwatch.Elapsed.TotalMilliseconds;
|
TotalFrameTime += fpsStopwatch.Elapsed.TotalMilliseconds;
|
||||||
if (TotalFrameCount % 50 == 0)
|
if (TotalFrameCount % 50 == 0)
|
||||||
@@ -150,6 +157,7 @@ namespace Desktop
|
|||||||
this.Invoke((MethodInvoker)delegate
|
this.Invoke((MethodInvoker)delegate
|
||||||
{
|
{
|
||||||
MessageBox.Show(ex.Message, "CPU Crash", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show(ex.Message, "CPU Crash", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -188,6 +196,15 @@ namespace Desktop
|
|||||||
}
|
}
|
||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fastLoadingToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Toggle the checkmark UI
|
||||||
|
fastLoadingToolStripMenuItem.Checked = !fastLoadingToolStripMenuItem.Checked;
|
||||||
|
|
||||||
|
// Tell the CPU to enable or disable the ROM hijack
|
||||||
|
_cpu.EnableFastLoad = fastLoadingToolStripMenuItem.Checked;
|
||||||
|
}
|
||||||
private void openSNAToolStripMenuItem_Click(object sender, EventArgs e)
|
private void openSNAToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_isPaused = true;
|
_isPaused = true;
|
||||||
@@ -203,6 +220,39 @@ namespace Desktop
|
|||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void btnRunZexDoc_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_isPaused = true;
|
||||||
|
|
||||||
|
using (OpenFileDialog ofd = new OpenFileDialog())
|
||||||
|
{
|
||||||
|
ofd.Filter = "CP/M Binaries (*.com, *.bin)|*.com;*.bin";
|
||||||
|
if (ofd.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
byte[] zexdocBytes = System.IO.File.ReadAllBytes(ofd.FileName);
|
||||||
|
|
||||||
|
// 1. Wipe the RAM completely clean
|
||||||
|
_memoryBus.CleanRAMData();
|
||||||
|
|
||||||
|
// 2. Load ZEXDOC exactly at CP/M start address 0x0100
|
||||||
|
for (int i = 0; i < zexdocBytes.Length; i++)
|
||||||
|
{
|
||||||
|
_memoryBus.Write((ushort)(0x0100 + i), zexdocBytes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Configure the CPU for CP/M
|
||||||
|
_cpu.IsZexDocMode = true;
|
||||||
|
_cpu.PC = 0x0100; // Execution starts here
|
||||||
|
_cpu.SP = 0xFFFF; // Put the stack at the very top of memory
|
||||||
|
|
||||||
|
// 4. Fake a RET address at 0x0000 so the test terminates gracefully when finished
|
||||||
|
_memoryBus.Write(0x0000, 0xD3); // OUT (n), A (A fake halt sequence)
|
||||||
|
|
||||||
|
// Unpause and watch the Visual Studio Output window!
|
||||||
|
_isPaused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private void btnRun_Click(object sender, EventArgs e) => _isPaused = false;
|
private void btnRun_Click(object sender, EventArgs e) => _isPaused = false;
|
||||||
|
|
||||||
private void btnPause_Click(object sender, EventArgs e) => _isPaused = true;
|
private void btnPause_Click(object sender, EventArgs e) => _isPaused = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user