diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 3d40ef5de1..9d9810b5e6 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -54,6 +54,7 @@ namespace BizHawk.Emulation.Common FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM"); FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM"); FirmwareAndOption("8CAFB292AF58617907B9E6B9093D3588A75849B8", 32768, "ZXSpectrum", "PLUS2ROM", "PLUS2.ROM", "Spectrum 128K +2 ROM"); + FirmwareAndOption("929BF1A5E5687EBD8D7245F9B513A596C0EC21A4", 65563, "ZXSpectrum", "PLUS3ROM", "PLUS3.ROM", "Spectrum 128K +3 ROM"); // for saturn, we think any bios region can pretty much run any iso // so, we're going to lay this out carefully so that we choose things in a sensible order, but prefer the correct region diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 2a18cd1e56..439b004828 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -277,6 +277,9 @@ + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs new file mode 100644 index 0000000000..76dbb31891 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus3 : SpectrumBase + { + /* 128k paging controlled by writes to port 0x7ffd + * + * + + #7FFD (32765) - decoded as A15=0, A1=0 and /IORQ=0. Bits 0..5 are latched. Bits 0..2 select RAM bank in secton D. Bit 3 selects RAM bank to dispay screen (0 - RAM5, 1 - RAM7). Bit 4 selects ROM bank (0 - ROM0, 1 - ROM1). Bit 5, when set locks future writing to #7FFD port until reset. Reading #7FFD port is the same as writing #FF into it. + #BFFD (49149) - write data byte into AY-3-8912 chip. + #FFFD (65533) - select AY-3-8912 addres (D4..D7 ignored) and reading data byte. + + * 0xffff +--------+--------+--------+--------+--------+--------+--------+--------+ + | Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 | + | | |(also at| | |(also at| | | + | | | 0x8000)| | | 0x4000)| | | + | | | | | | screen | | screen | + 0xc000 +--------+--------+--------+--------+--------+--------+--------+--------+ + | Bank 2 | Any one of these pages may be switched in. + | | + | | + | | + 0x8000 +--------+ + | Bank 5 | + | | + | | + | screen | + 0x4000 +--------+--------+ + | ROM 0 | ROM 1 | Either ROM may be switched in. + | | | + | | | + | | | + 0x0000 +--------+--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + byte result = 0xff; + switch (divisor) + { + // ROM 0x000 + case 0: + if (!ROMPaged) + result = Memory[0][addr % 0x4000]; + else + result = Memory[1][addr % 0x4000]; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + result = Memory[7][addr % 0x4000]; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + result = Memory[4][addr % 0x4000]; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + result = Memory[2][addr % 0x4000]; + break; + case 1: + result = Memory[3][addr % 0x4000]; + break; + case 2: + result = Memory[4][addr % 0x4000]; + break; + case 3: + result = Memory[5][addr % 0x4000]; + break; + case 4: + result = Memory[6][addr % 0x4000]; + break; + case 5: + result = Memory[7][addr % 0x4000]; + break; + case 6: + result = Memory[8][addr % 0x4000]; + break; + case 7: + result = Memory[9][addr % 0x4000]; + break; + } + break; + default: + break; + } + + return result; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + switch (divisor) + { + // ROM 0x000 + case 0: + if (!ROMPaged) + Memory[0][addr % 0x4000] = value; + else + Memory[1][addr % 0x4000] = value; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + Memory[7][addr % 0x4000] = value; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + Memory[4][addr % 0x4000] = value; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + Memory[2][addr % 0x4000] = value; + break; + case 1: + Memory[3][addr % 0x4000] = value; + break; + case 2: + Memory[4][addr % 0x4000] = value; + break; + case 3: + Memory[5][addr % 0x4000] = value; + break; + case 4: + Memory[6][addr % 0x4000] = value; + break; + case 5: + Memory[7][addr % 0x4000] = value; + break; + case 6: + Memory[8][addr % 0x4000] = value; + break; + case 7: + Memory[9][addr % 0x4000] = value; + break; + } + break; + default: + break; + } + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(addr); + if ((addr & 0xC000) == 0x4000) + { + // addr is in RAM not ROM - apply memory contention if neccessary + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr < 0xC000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = ROM1; + else + Memory.Add(1, ROM1); + + if (Memory.ContainsKey(2)) + Memory[2] = RAM0; + else + Memory.Add(2, RAM0); + + if (Memory.ContainsKey(3)) + Memory[3] = RAM1; + else + Memory.Add(3, RAM1); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM2; + else + Memory.Add(4, RAM2); + + if (Memory.ContainsKey(5)) + Memory[5] = RAM3; + else + Memory.Add(5, RAM3); + + if (Memory.ContainsKey(6)) + Memory[6] = RAM4; + else + Memory.Add(6, RAM4); + + if (Memory.ContainsKey(7)) + Memory[7] = RAM5; + else + Memory.Add(7, RAM5); + + if (Memory.ContainsKey(8)) + Memory[8] = RAM6; + else + Memory.Add(8, RAM6); + + if (Memory.ContainsKey(9)) + Memory[9] = RAM7; + else + Memory.Add(9, RAM7); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // 128k uses ROM0 and ROM1 + // 128k loader is in ROM0, and fallback 48k rom is in ROM1 + for (int i = 0; i < 0x4000; i++) + { + ROM0[i] = RomData.RomBytes[i]; + ROM1[i] = RomData.RomBytes[i + 0x4000]; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs new file mode 100644 index 0000000000..35fe0c3391 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus3 : SpectrumBase + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + public override byte ReadPort(ushort port) + { + int result = 0xFF; + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x0001) == 0; + + ContendPort((ushort)port); + + // Kempston Joystick + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + return (byte)KempstonDevice.JoyLine; + } + else if (lowBitReset) + { + // Even I/O address so get input + // The high byte indicates which half-row of keys is being polled + /* + IN: Reads keys (bit 0 to bit 4 inclusive) + 0xfefe SHIFT, Z, X, C, V 0xeffe 0, 9, 8, 7, 6 + 0xfdfe A, S, D, F, G 0xdffe P, O, I, U, Y + 0xfbfe Q, W, E, R, T 0xbffe ENTER, L, K, J, H + 0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B + */ + + if ((port & 0x8000) == 0) + result &= KeyboardDevice.KeyLine[7]; + + if ((port & 0x4000) == 0) + result &= KeyboardDevice.KeyLine[6]; + + if ((port & 0x2000) == 0) + result &= KeyboardDevice.KeyLine[5]; + + if ((port & 0x1000) == 0) + result &= KeyboardDevice.KeyLine[4]; + + if ((port & 0x800) == 0) + result &= KeyboardDevice.KeyLine[3]; + + if ((port & 0x400) == 0) + result &= KeyboardDevice.KeyLine[2]; + + if ((port & 0x200) == 0) + result &= KeyboardDevice.KeyLine[1]; + + if ((port & 0x100) == 0) + result &= KeyboardDevice.KeyLine[0]; + + result = result & 0x1f; //mask out lower 4 bits + result = result | 0xa0; //set bit 5 & 7 to 1 + + + if (TapeDevice.CurrentMode == TapeOperationMode.Load) + { + if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else + { + if (KeyboardDevice.IsIssue2Keyboard) + { + if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + else + { + if ((LastULAOutByte & EAR_BIT) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + } + + } + else + { + // devices other than the ULA will respond here + // (e.g. the AY sound chip in a 128k spectrum + + // AY register activate + // Kemptson Mouse + + + // if unused port the floating memory bus should be returned (still todo) + } + + return (byte)result; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + public override void WritePort(ushort port, byte value) + { + // paging + if (port == 0x7ffd) + { + // Bits 0, 1, 2 select the RAM page + var rp = value & 0x07; + if (rp < 8) + RAMPaged = rp; + + // ROM page + if ((value & 0x10) != 0) + { + // 48k ROM + ROMPaged = true; + } + else + { + ROMPaged = false; + } + + // Bit 5 signifies that paging is disabled until next reboot + if ((value & 0x20) != 0) + PagingDisabled = true; + + + return; + } + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x01) == 0; + + ContendPort(port); + + // Only even addresses address the ULA + if (lowBitReset) + { + // store the last OUT byte + LastULAOutByte = value; + + /* + Bit 7 6 5 4 3 2 1 0 + +-------------------------------+ + | | | | E | M | Border | + +-------------------------------+ + */ + + // Border - LSB 3 bits hold the border colour + BorderColour = value & BORDER_BIT; + + // Buzzer + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + + // Tape + TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs new file mode 100644 index 0000000000..2a75a600f7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -0,0 +1,56 @@ +using BizHawk.Emulation.Cores.Components.Z80A; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus3 : SpectrumBase + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, byte[] file) + { + Spectrum = spectrum; + CPU = cpu; + + ROMPaged = false; + SHADOWPaged = false; + RAMPaged = 0; + PagingDisabled = false; + + // init addressable memory from ROM and RAM banks + ReInitMemory(); + + //RAM = new byte[0x4000 + 0xC000]; + + //DisplayLineTime = 132; + VsyncNumerator = 3546900; + + InitScreenConfig(); + InitScreen(); + + ResetULACycle(); + + BuzzerDevice = new Buzzer(this); + BuzzerDevice.Init(44100, UlaFrameCycleCount); + + KeyboardDevice = new Keyboard48(this); + KempstonDevice = new KempstonJoystick(this); + + TapeProvider = new DefaultTapeProvider(file); + + TapeDevice = new Tape(TapeProvider); + TapeDevice.Init(this); + } + + #endregion + } +}