From fc17b164717847ee0bed0c3e3de61634ef51b00b Mon Sep 17 00:00:00 2001 From: parsons Date: Wed, 29 Apr 2026 23:17:51 +0100 Subject: [PATCH] Plus 3A implemented but not running --- Core/Cpu/Z80.cs | 3 +- Core/Io/IO_Bus.cs | 21 ++++++- Core/Memory/MemoryBus.cs | 127 ++++++++++++++++++++++++++++----------- Core/SpectrumMachine.cs | 7 ++- Desktop/Form1.cs | 8 ++- 5 files changed, 119 insertions(+), 47 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index d2dc994..83f990c 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -120,7 +120,7 @@ namespace Core.Cpu Push(PC); // --- Interrupt Mode Dispatch --- - if (InterruptMode == 1) + if (InterruptMode == 0 || InterruptMode == 1) { // IM 1: Hardcoded jump to ROM address 0x0038 PC = 0x0038; @@ -143,7 +143,6 @@ namespace Core.Cpu } else { - // (IM 0 is theoretically possible but essentially unused on the standard Spectrum) throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!"); } } diff --git a/Core/Io/IO_Bus.cs b/Core/Io/IO_Bus.cs index 3223645..df8a97a 100644 --- a/Core/Io/IO_Bus.cs +++ b/Core/Io/IO_Bus.cs @@ -1,5 +1,6 @@ -using Core.Interfaces; -using System.Diagnostics; +using System.Diagnostics; +using Core.Interfaces; +using Core.Memory; using static System.Runtime.InteropServices.JavaScript.JSType; namespace Core.Io @@ -11,10 +12,12 @@ namespace Core.Io public bool BeeperState { get; private set; } = false; public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; TapManager _tapManager = new TapManager(); + MemoryBus _memory; - public IO_Bus(TapManager tapManager) + public IO_Bus(TapManager tapManager, MemoryBus memoryBus) { _tapManager = tapManager; + _memory = memoryBus; } public byte ReadPort(ushort portAddress) @@ -64,6 +67,18 @@ namespace Core.Io // Bit 3 handles the cassette MIC output } + + // 128K Standard Paging Port (0x7FFD) + if ((portAddress & 0x8002) == 0) + { + _memory.HandlePaging(0x7FFD, portValue); + } + + // +2A/+3 Extended Paging Port (0x1FFD) + if ((portAddress & 0xF002) == 0x1000) + { + _memory.HandlePaging(0x1FFD, portValue); + } } } } \ No newline at end of file diff --git a/Core/Memory/MemoryBus.cs b/Core/Memory/MemoryBus.cs index b5295fa..03df737 100644 --- a/Core/Memory/MemoryBus.cs +++ b/Core/Memory/MemoryBus.cs @@ -5,61 +5,116 @@ namespace Core.Memory { public class MemoryBus : IMemory { - // The flat 64KB memory space - private readonly byte[] _memory = new byte[0x10000]; + // 8 Banks of 16KB RAM (Banks 0 through 7) + private readonly byte[][] _ramBanks = new byte[8][]; + + // 5 Banks of 16KB ROM (0-3 for the +2A, and 4 for the pure 48K) + private readonly byte[][] _romBanks = new byte[5][]; + + // --- Hardware State --- + private int _currentRomBank = 4; // Defaults to 48K ROM + private int _currentRamBankSlot3 = 0; + private byte _port7FFD = 0; + private byte _port1FFD = 0; + private bool _pagingDisabled = false; + + public MemoryBus() + { + // Initialize the physical silicon chips! + for (int i = 0; i < 8; i++) _ramBanks[i] = new byte[0x4000]; + for (int i = 0; i < 5; i++) _romBanks[i] = new byte[0x4000]; + } + + // Called when the machine is turned on or reset + public void ResetPaging(MachineModel model) + { + _pagingDisabled = false; + _port7FFD = 0; + _port1FFD = 0; + _currentRamBankSlot3 = 0; + + if (model == MachineModel.Spectrum48K) + { + _currentRomBank = 4; // Lock to the pure 48.rom + } + else + { + _currentRomBank = 0; // Boot into the +3 DOS menu! + } + } + + // Called by the IO Bus when the CPU writes to a paging port + public void HandlePaging(ushort port, byte value) + { + if (_pagingDisabled || _currentRomBank == 4) return; // Ignore if locked or in 48K mode + + if (port == 0x7FFD) + { + _port7FFD = value; + _currentRamBankSlot3 = value & 0x07; // Bottom 3 bits select the RAM bank + if ((value & 0x20) != 0) _pagingDisabled = true; // Bit 5 permanently disables paging + } + else if (port == 0x1FFD) + { + _port1FFD = value; + } + + // Calculate which of the 4 Amstrad ROMs is active based on the ports + int romLow = (_port7FFD >> 4) & 0x01; + int romHigh = (_port1FFD >> 2) & 0x01; + _currentRomBank = (romHigh << 1) | romLow; + } public byte Read(ushort address) { - return _memory[address]; + int slot = address >> 14; // Divide by 16384 to find Slot 0, 1, 2, or 3 + int offset = address & 0x3FFF; // Find the exact byte inside that 16KB slot + + switch (slot) + { + case 0: return _romBanks[_currentRomBank][offset]; // 0x0000 - 0x3FFF (ROM) + case 1: return _ramBanks[5][offset]; // 0x4000 - 0x7FFF (Always RAM 5) + case 2: return _ramBanks[2][offset]; // 0x8000 - 0xBFFF (Always RAM 2) + case 3: return _ramBanks[_currentRamBankSlot3][offset]; // 0xC000 - 0xFFFF (Switchable RAM) + default: return 0xFF; + } } public void Write(ushort address, byte value) { - /* ZX Spectrum 48K Memory Map: - * 0x0000 - 0x3FFF: ROM (16KB) -> Cannot write here! - * 0x4000 - 0x57FF: Display File (Screen Pixels) - * 0x5800 - 0x5AFF: Color Attributes - * 0x5B00 - 0xFFFF: General Purpose RAM - */ + if (address < 0x4000) return; // Cannot write to ROM - if (address < 0x4000) + int slot = address >> 14; + int offset = address & 0x3FFF; + + switch (slot) { - // Cannot write to ROM - Do nothing - maybe throw an exception - return; + case 1: _ramBanks[5][offset] = value; break; + case 2: _ramBanks[2][offset] = value; break; + case 3: _ramBanks[_currentRamBankSlot3][offset] = value; break; } - - _memory[address] = value; } - //Put some initial random data into RAM for authenticity - public void CrapRAMData() + public void LoadRom(byte[] romData, int bankIndex) { - Random random = new Random(); - for (int i = 0x4000; i < 0xFFFF; i++) - { - _memory[i] = (byte)random.Next(0x00, 0xFF); - } + Array.Copy(romData, 0, _romBanks[bankIndex], 0, Math.Min(romData.Length, 0x4000)); } public void CleanRAMData() - { - for (int i = 0x4000; i < 0xFFFF; i++) - { - _memory[i] = 0x00; - } - } - - // Load the ROM file - public void LoadRom(byte[] romData) { - if (romData.Length > 0x4000) + for (int bank = 0; bank < 8; bank++) { - throw new ArgumentException("ROM file exceeds the 16KB capacity of Bank 0."); + for (int i = 0; i < 0x4000; i++) _ramBanks[bank][i] = 0x00; + } + } + + public void CrapRAMData() + { + Random random = new Random(); + for (int bank = 0; bank < 8; bank++) + { + for (int i = 0; i < 0x4000; i++) _ramBanks[bank][i] = (byte)random.Next(0x00, 0xFF); } - - // Copy the ROM - Array.Copy(romData, 0, _memory, 0, romData.Length); } - } } \ No newline at end of file diff --git a/Core/SpectrumMachine.cs b/Core/SpectrumMachine.cs index 65e2420..b26b46f 100644 --- a/Core/SpectrumMachine.cs +++ b/Core/SpectrumMachine.cs @@ -64,7 +64,7 @@ namespace Core Memory = new MemoryBus(); TapeDeck = new TapManager(); - IoBus = new IO_Bus(TapeDeck); + IoBus = new IO_Bus(TapeDeck, Memory); Ula = new ULA(Memory, IoBus); Memory.CrapRAMData(); @@ -75,9 +75,9 @@ namespace Core Cpu.WaitStateCallback = Ula.GetContentionDelay; } - public void LoadRom(byte[] romData) + public void LoadRom(byte[] romData, int bankIndex) // <-- Add the bank parameter { - Memory.LoadRom(romData); + Memory.LoadRom(romData, bankIndex); } public void Start() @@ -115,6 +115,7 @@ namespace Core { Cpu.Reset(); Memory.CrapRAMData(); + Memory.ResetPaging(CurrentModel); TotalFrameCount = 0; scanlineCount = 0; audioSampleCount = 0; diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 429685b..ed9122f 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -69,9 +69,11 @@ namespace Desktop fastLoadToolStripMenuItem.Checked = _machine.EnableFastLoad; HighSpeedToolStripMenuItem.Checked = _machine.HighSpeedMode; - // Load the ROM and start the engine - byte[] romData = RomLoader.Load("48.rom"); - _machine.LoadRom(romData); + _machine.LoadRom(RomLoader.Load("plus3-0.rom"), 0); + _machine.LoadRom(RomLoader.Load("plus3-1.rom"), 1); + _machine.LoadRom(RomLoader.Load("plus3-2.rom"), 2); + _machine.LoadRom(RomLoader.Load("plus3-3.rom"), 3); + _machine.LoadRom(RomLoader.Load("48.rom"), 4); _machine.Start(); }