From 5eb04b9dd01bb4b25cf6a873714fad4c54ebc67c Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 26 Jul 2017 16:15:01 +0200 Subject: [PATCH] Memory bank controller v2 implementation --- Emux.GameBoy/Cartridge/EmulatedCartridge.cs | 2 + .../Cartridge/MemoryBankController2.cs | 57 +++++++++++++++++++ Emux.GameBoy/Cpu/GameBoyCpu.cs | 19 +++---- Emux.GameBoy/Emux.GameBoy.csproj | 1 + Emux.GameBoy/GameBoy.cs | 2 +- Emux.GameBoy/Graphics/GameBoyGpu.cs | 2 +- Emux.GameBoy/Timer/GameBoyTimer.cs | 10 ++-- 7 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 Emux.GameBoy/Cartridge/MemoryBankController2.cs diff --git a/Emux.GameBoy/Cartridge/EmulatedCartridge.cs b/Emux.GameBoy/Cartridge/EmulatedCartridge.cs index 845ac6a..5bc9654 100644 --- a/Emux.GameBoy/Cartridge/EmulatedCartridge.cs +++ b/Emux.GameBoy/Cartridge/EmulatedCartridge.cs @@ -17,6 +17,8 @@ namespace Emux.GameBoy.Cartridge BankController = new RomOnlyBankController(this); else if (CartridgeType.IsMbc1()) BankController = new MemoryBankController1(this); + else if (CartridgeType.IsMbc2()) + BankController = new MemoryBankController2(this); else throw new NotSupportedException("Unsupported cartridge type " + CartridgeType + "."); } diff --git a/Emux.GameBoy/Cartridge/MemoryBankController2.cs b/Emux.GameBoy/Cartridge/MemoryBankController2.cs new file mode 100644 index 0000000..4d5e043 --- /dev/null +++ b/Emux.GameBoy/Cartridge/MemoryBankController2.cs @@ -0,0 +1,57 @@ +using System; + +namespace Emux.GameBoy.Cartridge +{ + public class MemoryBankController2 : IMemoryBankController + { + private readonly IFullyAccessibleCartridge _cartridge; + private readonly byte[] _romBank = new byte[0x4000]; + private readonly byte[] _ram = new byte[0x200]; + private bool _ramEnabled = false; + + public MemoryBankController2(IFullyAccessibleCartridge cartridge) + { + if (cartridge == null) + throw new ArgumentNullException(nameof(cartridge)); + _cartridge = cartridge; + } + + public byte ReadByte(ushort address) + { + if (address < 0x4000) + return _cartridge.ReadFromAbsoluteAddress(address); + if (address < 0x8000) + return _romBank[address - 0x4000]; + if (_ramEnabled && address < 0xA200) + return _ram[address - 0xA000]; + return 0; + } + + public void ReadBytes(ushort address, byte[] buffer, int bufferOffset, int length) + { + if (address < 0x4000) + _cartridge.ReadFromAbsoluteAddress(address, buffer, bufferOffset, length); + else if (address < 0x8000) + Buffer.BlockCopy(_romBank, address - 0x4000, buffer, bufferOffset, length); + else if (_ramEnabled && address < 0xA200) + Buffer.BlockCopy(_ram, address - 0xA000, buffer, bufferOffset, length); + } + + public void WriteByte(ushort address, byte value) + { + if (address < 0x2000 && (address & 0x0100) == 0) + _ramEnabled = (value & 0xF) == 0xA; + else if (address < 0x4000 && (address & 0x0100) == 0x0100) + SwitchRomBank(value & 0b1111); + else if (_ramEnabled && address >= 0xA000 && address < 0xA200) + _ram[address - 0xA000] = (byte) (value & 0b1111); + } + + private void SwitchRomBank(int index) + { + if (index == 0) + index++; + _cartridge.ReadFromAbsoluteAddress(_romBank.Length * index, _romBank, 0, _romBank.Length); + } + } +} diff --git a/Emux.GameBoy/Cpu/GameBoyCpu.cs b/Emux.GameBoy/Cpu/GameBoyCpu.cs index fb39e7a..349df6c 100644 --- a/Emux.GameBoy/Cpu/GameBoyCpu.cs +++ b/Emux.GameBoy/Cpu/GameBoyCpu.cs @@ -18,10 +18,11 @@ namespace Emux.GameBoy.Cpu public event EventHandler Paused; public event EventHandler Terminated; + private readonly Z80Disassembler _disassembler; private readonly GameBoy _device; - private ulong _ticks; private readonly ManualResetEvent _continue = new ManualResetEvent(false); private readonly ManualResetEvent _terminate = new ManualResetEvent(false); + private ulong _ticks; private bool _break = true; private bool _halt = false; @@ -30,6 +31,8 @@ namespace Emux.GameBoy.Cpu if (device == null) throw new ArgumentNullException(nameof(device)); _device = device; + _disassembler = new Z80Disassembler(device.Memory); + Registers = new RegisterBank(); Alu = new GameBoyAlu(Registers); Breakpoints = new HashSet(); @@ -165,17 +168,9 @@ namespace Emux.GameBoy.Cpu private Z80Instruction ReadNextInstruction() { - ushort offset = Registers.PC; - byte code = _device.Memory.ReadByte(Registers.PC++); - - var opcode = code != 0xCB - ? Z80OpCodes.SingleByteOpCodes[code] - : Z80OpCodes.PrefixedOpCodes[_device.Memory.ReadByte(Registers.PC++)]; - - byte[] operand = _device.Memory.ReadBytes(Registers.PC, opcode.OperandLength); - Registers.PC += (ushort) operand.Length; - - var instruction = new Z80Instruction(offset, opcode, operand); + _disassembler.Position = Registers.PC; + var instruction = _disassembler.ReadNextInstruction(); + Registers.PC = _disassembler.Position; return instruction; } diff --git a/Emux.GameBoy/Emux.GameBoy.csproj b/Emux.GameBoy/Emux.GameBoy.csproj index 5a6b74b..01c028d 100644 --- a/Emux.GameBoy/Emux.GameBoy.csproj +++ b/Emux.GameBoy/Emux.GameBoy.csproj @@ -46,6 +46,7 @@ + diff --git a/Emux.GameBoy/GameBoy.cs b/Emux.GameBoy/GameBoy.cs index bf288e6..b8b8f7c 100644 --- a/Emux.GameBoy/GameBoy.cs +++ b/Emux.GameBoy/GameBoy.cs @@ -12,9 +12,9 @@ namespace Emux.GameBoy public GameBoy(ICartridge cartridge) { Cartridge = cartridge; + Memory = new GameBoyMemory(this); Cpu = new GameBoyCpu(this); Gpu = new GameBoyGpu(this); - Memory = new GameBoyMemory(this); KeyPad = new GameBoyPad(this); Timer = new GameBoyTimer(this); Reset(); diff --git a/Emux.GameBoy/Graphics/GameBoyGpu.cs b/Emux.GameBoy/Graphics/GameBoyGpu.cs index 6c535c5..6e89209 100644 --- a/Emux.GameBoy/Graphics/GameBoyGpu.cs +++ b/Emux.GameBoy/Graphics/GameBoyGpu.cs @@ -216,7 +216,7 @@ namespace Emux.GameBoy.Graphics { _modeClock -= HBlankCycles; LY++; - if (LY == FrameHeight) + if (LY == FrameHeight - 1) { currentMode = LcdStatusFlags.VBlankMode; VideoOutput.RenderFrame(_frameBuffer); diff --git a/Emux.GameBoy/Timer/GameBoyTimer.cs b/Emux.GameBoy/Timer/GameBoyTimer.cs index 49ba20b..561fe1d 100644 --- a/Emux.GameBoy/Timer/GameBoyTimer.cs +++ b/Emux.GameBoy/Timer/GameBoyTimer.cs @@ -6,7 +6,7 @@ namespace Emux.GameBoy.Timer public class GameBoyTimer { private readonly GameBoy _device; - public const int DivTimerSpeed = 16384; + public const int DivFrequency = 16384; private int _divClock; private int _timerClock; @@ -23,7 +23,7 @@ namespace Emux.GameBoy.Timer _device = device; } - public int GetTimaSpeed() + public int GetTimaFrequency() { switch (Tac & TimerControlFlags.ClockMask) { @@ -42,16 +42,16 @@ namespace Emux.GameBoy.Timer public void TimerStep(int cycles) { _divClock += cycles; - if (_divClock >= DivTimerSpeed) + if (_divClock >= DivFrequency) { - _divClock -= DivTimerSpeed; + _divClock -= DivFrequency; Div = (byte) ((Div + 1) % 0xFF); } if ((Tac & TimerControlFlags.EnableTimer) == TimerControlFlags.EnableTimer) { _timerClock += cycles; - int timaSpeed = GetTimaSpeed(); + int timaSpeed = GetTimaFrequency(); if (_timerClock > timaSpeed) { _timerClock -= timaSpeed;