using Core.Interfaces; using System; using System.Diagnostics; namespace Core.Memory { public class MemoryBus : IMemory { // 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) { //Debug.WriteLine($"[PAGING] 0x7FFD Triggered! Hex: 0x{value:X2} | Bank: {value & 0x07}"); 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; } private int GetBankForSlot(int slot) { // Amstrad +2A / +3 Special Paging Mode bool specialPaging = (_port1FFD & 0x01) != 0; if (specialPaging) { // Bits 1 and 2 dictate the specific Special RAM layout int config = (_port1FFD >> 1) & 0x03; switch (config) { case 0: return slot == 0 ? 0 : slot == 1 ? 1 : slot == 2 ? 2 : 3; case 1: return slot == 0 ? 4 : slot == 1 ? 5 : slot == 2 ? 6 : 7; case 2: return slot == 0 ? 4 : slot == 1 ? 5 : slot == 2 ? 6 : 3; case 3: return slot == 0 ? 4 : slot == 1 ? 7 : slot == 2 ? 6 : 3; } } // Standard 128K Paging switch (slot) { case 1: return 5; case 2: return 2; case 3: return _currentRamBankSlot3; default: return -1; // -1 means it is pointing to a ROM chip! } } public byte Read(ushort address) { int slot = address >> 14; // Find the 16KB Slot (0, 1, 2, or 3) int offset = address & 0x3FFF; int bank = GetBankForSlot(slot); if (bank == -1) { return _romBanks[_currentRomBank][offset]; } else { return _ramBanks[bank][offset]; } } public void Write(ushort address, byte value) { int slot = address >> 14; int offset = address & 0x3FFF; int bank = GetBankForSlot(slot); // If the bank is NOT a ROM chip, we are allowed to write to it! // (This allows the BIOS to write to 0x0000 during the self-test) if (bank != -1) { _ramBanks[bank][offset] = value; } } public byte[] GetVideoRam() { // Bit 3 of 7FFD dictates the active screen. 0 = Bank 5 (Standard), 1 = Bank 7 (Shadow). return (_port7FFD & 0x08) != 0 ? _ramBanks[7] : _ramBanks[5]; } public void LoadRom(byte[] romData, int bankIndex) { Array.Copy(romData, 0, _romBanks[bankIndex], 0, Math.Min(romData.Length, 0x4000)); } public void CleanRAMData() { for (int bank = 0; bank < 8; bank++) { 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); } } } }