diff --git a/LR35902/AbstractColourPalette.cs b/LR35902/AbstractColourPalette.cs index 9289ea1..5afbb90 100644 --- a/LR35902/AbstractColourPalette.cs +++ b/LR35902/AbstractColourPalette.cs @@ -1,19 +1,16 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public class AbstractColourPalette { - public class AbstractColourPalette + private readonly uint[] colours = new uint[4]; + + protected AbstractColourPalette() { - private readonly uint[] colours = new uint[4]; - - protected AbstractColourPalette() - { - } - - public uint Colour(int index) => this.colours[index]; } + + public uint Colour(int index) => this.colours[index]; } } diff --git a/LR35902/Bus.cs b/LR35902/Bus.cs index 6301512..b4086d2 100644 --- a/LR35902/Bus.cs +++ b/LR35902/Bus.cs @@ -1,439 +1,436 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { using System; using System.Collections.Generic; - namespace GameBoy + public abstract class Bus : EightBit.Bus { - public abstract class Bus : EightBit.Bus + public const int CyclesPerSecond = 4 * 1024 * 1024; + public const int FramesPerSecond = 60; + public const int CyclesPerFrame = CyclesPerSecond / FramesPerSecond; + public const int TotalLineCount = 154; + public const int CyclesPerLine = CyclesPerFrame / TotalLineCount; + public const int RomPageSize = 0x4000; + + private readonly Rom bootRom = new Rom(0x100); // 0x0000 - 0x00ff + private readonly List gameRomBanks = new List(); // 0x0000 - 0x3fff, 0x4000 - 0x7fff (switchable) + private readonly List ramBanks = new List(); // 0xa000 - 0xbfff (switchable) + private readonly UnusedMemory unmapped2000 = new UnusedMemory(0x2000, 0xff); // 0xa000 - 0xbfff + private readonly Ram lowInternalRam = new Ram(0x2000); // 0xc000 - 0xdfff (mirrored at 0xe000) + private readonly UnusedMemory unmapped60 = new UnusedMemory(0x60, 0xff); // 0xfea0 - 0xfeff + private readonly Ram highInternalRam = new Ram(0x80); // 0xff80 - 0xffff + + private bool enabledLCD = false; + + private bool disabledGameRom = false; + + private bool rom = false; + private bool banked = false; + private bool ram = false; + private bool battery = false; + + private bool higherRomBank = true; + private bool ramBankSwitching = false; + + private int romBank = 1; + private int ramBank = 0; + + protected Bus() { - public const int CyclesPerSecond = 4 * 1024 * 1024; - public const int FramesPerSecond = 60; - public const int CyclesPerFrame = CyclesPerSecond / FramesPerSecond; - public const int TotalLineCount = 154; - public const int CyclesPerLine = CyclesPerFrame / TotalLineCount; - public const int RomPageSize = 0x4000; + this.IO = new IoRegisters(this); + this.CPU = new LR35902(this); + } - private readonly Rom bootRom = new Rom(0x100); // 0x0000 - 0x00ff - private readonly List gameRomBanks = new List(); // 0x0000 - 0x3fff, 0x4000 - 0x7fff (switchable) - private readonly List ramBanks = new List(); // 0xa000 - 0xbfff (switchable) - private readonly UnusedMemory unmapped2000 = new UnusedMemory(0x2000, 0xff); // 0xa000 - 0xbfff - private readonly Ram lowInternalRam = new Ram(0x2000); // 0xc000 - 0xdfff (mirrored at 0xe000) - private readonly UnusedMemory unmapped60 = new UnusedMemory(0x60, 0xff); // 0xfea0 - 0xfeff - private readonly Ram highInternalRam = new Ram(0x80); // 0xff80 - 0xffff + public LR35902 CPU { get; } - private bool enabledLCD = false; + public Ram VRAM { get; } = new Ram(0x2000); - private bool disabledGameRom = false; + public Ram OAMRAM { get; } = new Ram(0xa0); - private bool rom = false; - private bool banked = false; - private bool ram = false; - private bool battery = false; + public IoRegisters IO { get; } - private bool higherRomBank = true; - private bool ramBankSwitching = false; + public bool GameRomDisabled => this.disabledGameRom; - private int romBank = 1; - private int ramBank = 0; + public bool GameRomEnabled => !this.GameRomDisabled; - protected Bus() + public override void RaisePOWER() + { + base.RaisePOWER(); + this.CPU.RaisePOWER(); + this.CPU.RaiseINT(); + this.Reset(); + } + + public override void LowerPOWER() + { + this.CPU.LowerPOWER(); + base.LowerPOWER(); + } + + public void Reset() + { + this.IO.Reset(); + this.CPU.LowerRESET(); + } + + public void DisableGameRom() => this.disabledGameRom = true; + + public void EnableGameRom() => this.disabledGameRom = false; + + public void LoadBootRom(string path) => this.bootRom.Load(path); + + public void LoadGameRom(string path) + { + const int bankSize = 0x4000; + this.gameRomBanks.Clear(); + this.gameRomBanks.Add(new Rom()); + var size = this.gameRomBanks[0].Load(path, 0, 0, bankSize); + var banks = size / bankSize; + for (var bank = 1; bank < banks; ++bank) { - this.IO = new IoRegisters(this); - this.CPU = new LR35902(this); - } - - public LR35902 CPU { get; } - - public Ram VRAM { get; } = new Ram(0x2000); - - public Ram OAMRAM { get; } = new Ram(0xa0); - - public IoRegisters IO { get; } - - public bool GameRomDisabled => this.disabledGameRom; - - public bool GameRomEnabled => !this.GameRomDisabled; - - public override void RaisePOWER() - { - base.RaisePOWER(); - this.CPU.RaisePOWER(); - this.CPU.RaiseINT(); - this.Reset(); - } - - public override void LowerPOWER() - { - this.CPU.LowerPOWER(); - base.LowerPOWER(); - } - - public void Reset() - { - this.IO.Reset(); - this.CPU.LowerRESET(); - } - - public void DisableGameRom() => this.disabledGameRom = true; - - public void EnableGameRom() => this.disabledGameRom = false; - - public void LoadBootRom(string path) => this.bootRom.Load(path); - - public void LoadGameRom(string path) - { - const int bankSize = 0x4000; - this.gameRomBanks.Clear(); this.gameRomBanks.Add(new Rom()); - var size = this.gameRomBanks[0].Load(path, 0, 0, bankSize); - var banks = size / bankSize; - for (var bank = 1; bank < banks; ++bank) - { - this.gameRomBanks.Add(new Rom()); - this.gameRomBanks[bank].Load(path, 0, bankSize * bank, bankSize); - } - - this.ValidateCartridgeType(); + this.gameRomBanks[bank].Load(path, 0, bankSize * bank, bankSize); } - public int RunRasterLines() + this.ValidateCartridgeType(); + } + + public int RunRasterLines() + { + this.enabledLCD = (this.IO.Peek(IoRegisters.LCDC) & (byte)LcdcControl.LcdEnable) != 0; + this.IO.ResetLY(); + return this.RunRasterLines(Display.RasterHeight); + } + + public int RunVerticalBlankLines() + { + var lines = TotalLineCount - Display.RasterHeight; + return this.RunVerticalBlankLines(lines); + } + + public override MemoryMapping Mapping(ushort address) + { + if ((address < 0x100) && this.IO.BootRomEnabled) { - this.enabledLCD = (this.IO.Peek(IoRegisters.LCDC) & (byte)LcdcControl.LcdEnable) != 0; - this.IO.ResetLY(); - return this.RunRasterLines(Display.RasterHeight); + return new MemoryMapping(this.bootRom, 0x0000, Mask.Mask16, AccessLevel.ReadOnly); } - public int RunVerticalBlankLines() + if ((address < 0x4000) && this.GameRomEnabled) { - var lines = TotalLineCount - Display.RasterHeight; - return this.RunVerticalBlankLines(lines); + return new MemoryMapping(this.gameRomBanks[0], 0x0000, 0xffff, AccessLevel.ReadOnly); } - public override MemoryMapping Mapping(ushort address) + if ((address < 0x8000) && this.GameRomEnabled) { - if ((address < 0x100) && this.IO.BootRomEnabled) - { - return new MemoryMapping(this.bootRom, 0x0000, Mask.Mask16, AccessLevel.ReadOnly); - } - - if ((address < 0x4000) && this.GameRomEnabled) - { - return new MemoryMapping(this.gameRomBanks[0], 0x0000, 0xffff, AccessLevel.ReadOnly); - } - - if ((address < 0x8000) && this.GameRomEnabled) - { - return new MemoryMapping(this.gameRomBanks[this.romBank], 0x4000, 0xffff, AccessLevel.ReadOnly); - } - - if (address < 0xa000) - { - return new MemoryMapping(this.VRAM, 0x8000, 0xffff, AccessLevel.ReadWrite); - } - - if (address < 0xc000) - { - if (this.ramBanks.Count == 0) - { - return new MemoryMapping(this.unmapped2000, 0xa000, 0xffff, AccessLevel.ReadOnly); - } - else - { - return new MemoryMapping(this.ramBanks[this.ramBank], 0xa000, 0xffff, AccessLevel.ReadWrite); - } - } - - if (address < 0xe000) - { - return new MemoryMapping(this.lowInternalRam, 0xc000, 0xffff, AccessLevel.ReadWrite); - } - - if (address < 0xfe00) - { - return new MemoryMapping(this.lowInternalRam, 0xe000, 0xffff, AccessLevel.ReadWrite); // Low internal RAM mirror - } - - if (address < 0xfea0) - { - return new MemoryMapping(this.OAMRAM, 0xfe00, 0xffff, AccessLevel.ReadWrite); - } - - if (address < IoRegisters.BASE) - { - return new MemoryMapping(this.unmapped60, 0xfea0, 0xffff, AccessLevel.ReadOnly); - } - - if (address < 0xff80) - { - return new MemoryMapping(this.IO, IoRegisters.BASE, 0xffff, AccessLevel.ReadWrite); - } - - return new MemoryMapping(this.highInternalRam, 0xff80, 0xffff, AccessLevel.ReadWrite); + return new MemoryMapping(this.gameRomBanks[this.romBank], 0x4000, 0xffff, AccessLevel.ReadOnly); } - protected override void OnWrittenByte() + if (address < 0xa000) { - base.OnWrittenByte(); - - var address = this.Address.Word; - var value = this.Data; - - switch (address & 0xe000) - { - case 0x0000: - // Register 0: RAMCS gate data - if (this.ram) - { - throw new InvalidOperationException("Register 0: RAMCS gate data: Not handled!"); - } - - break; - case 0x2000: - // Register 1: ROM bank code - if (this.banked && this.higherRomBank) - { - // assert((address >= 0x2000) && (address < 0x4000)); - // assert((value > 0) && (value < 0x20)); - this.romBank = value & (byte)Mask.Mask5; - } - - break; - case 0x4000: - // Register 2: ROM bank selection - if (this.banked) - { - throw new InvalidOperationException("Register 2: ROM bank selection: Not handled!"); - } - - break; - case 0x6000: - // Register 3: ROM/RAM change - if (this.banked) - { - switch (value & (byte)Mask.Mask1) - { - case 0: - this.higherRomBank = true; - this.ramBankSwitching = false; - break; - case 1: - this.higherRomBank = false; - this.ramBankSwitching = true; - break; - default: - throw new InvalidOperationException("Unreachable"); - } - } - - break; - } + return new MemoryMapping(this.VRAM, 0x8000, 0xffff, AccessLevel.ReadWrite); } - private void ValidateCartridgeType() + if (address < 0xc000) { - this.rom = this.banked = this.ram = this.battery = false; - - // ROM type - switch (this.gameRomBanks[0].Peek(0x147)) + if (this.ramBanks.Count == 0) { - case (byte)CartridgeType.ROM: - this.rom = true; - break; - case (byte)CartridgeType.ROM_MBC1: - this.rom = this.banked = true; - break; - case (byte)CartridgeType.ROM_MBC1_RAM: - this.rom = this.banked = this.ram = true; - break; - case (byte)CartridgeType.ROM_MBC1_RAM_BATTERY: - this.rom = this.banked = this.ram = this.battery = true; - break; - default: - throw new InvalidOperationException("Unhandled cartridge ROM type"); - } - - // ROM size - { - var gameRomBanks = 0; - var romSizeSpecification = this.Peek(0x148); - switch (romSizeSpecification) - { - case 0x52: - gameRomBanks = 72; - break; - case 0x53: - gameRomBanks = 80; - break; - case 0x54: - gameRomBanks = 96; - break; - default: - if (romSizeSpecification > 6) - { - throw new InvalidOperationException("Invalid ROM size specification"); - } - - gameRomBanks = 1 << (romSizeSpecification + 1); - if (gameRomBanks != this.gameRomBanks.Count) - { - throw new InvalidOperationException("ROM size specification mismatch"); - } - - break; - } - - // RAM size - { - var ramSizeSpecification = this.gameRomBanks[0].Peek(0x149); - switch (ramSizeSpecification) - { - case 0: - break; - case 1: - this.ramBanks.Clear(); - this.ramBanks.Add(new Ram(2 * 1024)); - break; - case 2: - this.ramBanks.Clear(); - this.ramBanks.Add(new Ram(8 * 1024)); - break; - case 3: - this.ramBanks.Clear(); - for (var i = 0; i < 4; ++i) - { - this.ramBanks.Add(new Ram(8 * 1024)); - } - - break; - case 4: - this.ramBanks.Clear(); - for (var i = 0; i < 16; ++i) - { - this.ramBanks.Add(new Ram(8 * 1024)); - } - - break; - default: - throw new InvalidOperationException("Invalid RAM size specification"); - } - } - } - } - - private int RunRasterLines(int lines) - { - var count = 0; - var allowed = CyclesPerLine; - for (var line = 0; line < lines; ++line) - { - var executed = this.RunRasterLine(allowed); - count += executed; - allowed = CyclesPerLine - (executed - CyclesPerLine); - } - - return count; - } - - private int RunVerticalBlankLines(int lines) - { - /* - Vertical Blank interrupt is triggered when the LCD - controller enters the VBL screen mode (mode 1, LY=144). - This happens once per frame, so this interrupt is - triggered 59.7 times per second. During this period the - VRAM and OAM can be accessed freely, so it's the best - time to update graphics (for example, use the OAM DMA to - update sprites for next frame, or update tiles to make - animations). - This period lasts 4560 clocks in normal speed mode and - 9120 clocks in double speed mode. That's exactly the - time needed to draw 10 scanlines. - The VBL interrupt isn't triggered when the LCD is - powered off or on, even when it was on VBL mode. - It's only triggered when the VBL period starts. - */ - if (this.enabledLCD) - { - this.IO.UpdateLcdStatusMode(LcdStatusMode.VBlank); - if ((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit4) != 0) - { - this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); - } - - this.IO.TriggerInterrupt(Interrupts.VerticalBlank); - } - - return this.RunRasterLines(lines); - } - - private int RunRasterLine(int limit) - { - /* - A scanline normally takes 456 clocks (912 clocks in double speed - mode) to complete. A scanline starts in mode 2, then goes to - mode 3 and, when the LCD controller has finished drawing the - line (the timings depend on lots of things) it goes to mode 0. - During lines 144-153 the LCD controller is in mode 1. - Line 153 takes only a few clocks to complete (the exact - timings are below). The rest of the clocks of line 153 are - spent in line 0 in mode 1! - - During mode 0 and mode 1 the CPU can access both VRAM and OAM. - During mode 2 the CPU can only access VRAM, not OAM. - During mode 3 OAM and VRAM can't be accessed. - In GBC mode the CPU can't access Palette RAM(FF69h and FF6Bh) - during mode 3. - A scanline normally takes 456 clocks(912 clocks in double speed mode) to complete. - A scanline starts in mode 2, then goes to mode 3 and , when the LCD controller has - finished drawing the line(the timings depend on lots of things) it goes to mode 0. - During lines 144 - 153 the LCD controller is in mode 1. - Line 153 takes only a few clocks to complete(the exact timings are below). - The rest of the clocks of line 153 are spent in line 0 in mode 1! - */ - - var count = 0; - if (this.enabledLCD) - { - if (((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit6) != 0) && (this.IO.Peek(IoRegisters.LYC) == this.IO.Peek(IoRegisters.LY))) - { - this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); - } - - // Mode 2, OAM unavailable - this.IO.UpdateLcdStatusMode(LcdStatusMode.SearchingOamRam); - if ((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit5) != 0) - { - this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); - } - - count += this.CPU.Run(80); // ~19us - - // Mode 3, OAM/VRAM unavailable - this.IO.UpdateLcdStatusMode(LcdStatusMode.TransferringDataToLcd); - count += this.CPU.Run(170); // ~41us - - // Mode 0 - this.IO.UpdateLcdStatusMode(LcdStatusMode.HBlank); - if ((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit3) != 0) - { - this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); - } - - count += this.CPU.Run(limit - count); // ~48.6us - - this.IO.IncrementLY(); + return new MemoryMapping(this.unmapped2000, 0xa000, 0xffff, AccessLevel.ReadOnly); } else { - count += this.CPU.Run(CyclesPerLine); + return new MemoryMapping(this.ramBanks[this.ramBank], 0xa000, 0xffff, AccessLevel.ReadWrite); + } + } + + if (address < 0xe000) + { + return new MemoryMapping(this.lowInternalRam, 0xc000, 0xffff, AccessLevel.ReadWrite); + } + + if (address < 0xfe00) + { + return new MemoryMapping(this.lowInternalRam, 0xe000, 0xffff, AccessLevel.ReadWrite); // Low internal RAM mirror + } + + if (address < 0xfea0) + { + return new MemoryMapping(this.OAMRAM, 0xfe00, 0xffff, AccessLevel.ReadWrite); + } + + if (address < IoRegisters.BASE) + { + return new MemoryMapping(this.unmapped60, 0xfea0, 0xffff, AccessLevel.ReadOnly); + } + + if (address < 0xff80) + { + return new MemoryMapping(this.IO, IoRegisters.BASE, 0xffff, AccessLevel.ReadWrite); + } + + return new MemoryMapping(this.highInternalRam, 0xff80, 0xffff, AccessLevel.ReadWrite); + } + + protected override void OnWrittenByte() + { + base.OnWrittenByte(); + + var address = this.Address.Word; + var value = this.Data; + + switch (address & 0xe000) + { + case 0x0000: + // Register 0: RAMCS gate data + if (this.ram) + { + throw new InvalidOperationException("Register 0: RAMCS gate data: Not handled!"); + } + + break; + case 0x2000: + // Register 1: ROM bank code + if (this.banked && this.higherRomBank) + { + // assert((address >= 0x2000) && (address < 0x4000)); + // assert((value > 0) && (value < 0x20)); + this.romBank = value & (byte)Mask.Mask5; + } + + break; + case 0x4000: + // Register 2: ROM bank selection + if (this.banked) + { + throw new InvalidOperationException("Register 2: ROM bank selection: Not handled!"); + } + + break; + case 0x6000: + // Register 3: ROM/RAM change + if (this.banked) + { + switch (value & (byte)Mask.Mask1) + { + case 0: + this.higherRomBank = true; + this.ramBankSwitching = false; + break; + case 1: + this.higherRomBank = false; + this.ramBankSwitching = true; + break; + default: + throw new InvalidOperationException("Unreachable"); + } + } + + break; + } + } + + private void ValidateCartridgeType() + { + this.rom = this.banked = this.ram = this.battery = false; + + // ROM type + switch (this.gameRomBanks[0].Peek(0x147)) + { + case (byte)CartridgeType.ROM: + this.rom = true; + break; + case (byte)CartridgeType.ROM_MBC1: + this.rom = this.banked = true; + break; + case (byte)CartridgeType.ROM_MBC1_RAM: + this.rom = this.banked = this.ram = true; + break; + case (byte)CartridgeType.ROM_MBC1_RAM_BATTERY: + this.rom = this.banked = this.ram = this.battery = true; + break; + default: + throw new InvalidOperationException("Unhandled cartridge ROM type"); + } + + // ROM size + { + var gameRomBanks = 0; + var romSizeSpecification = this.Peek(0x148); + switch (romSizeSpecification) + { + case 0x52: + gameRomBanks = 72; + break; + case 0x53: + gameRomBanks = 80; + break; + case 0x54: + gameRomBanks = 96; + break; + default: + if (romSizeSpecification > 6) + { + throw new InvalidOperationException("Invalid ROM size specification"); + } + + gameRomBanks = 1 << (romSizeSpecification + 1); + if (gameRomBanks != this.gameRomBanks.Count) + { + throw new InvalidOperationException("ROM size specification mismatch"); + } + + break; } - return count; + // RAM size + { + var ramSizeSpecification = this.gameRomBanks[0].Peek(0x149); + switch (ramSizeSpecification) + { + case 0: + break; + case 1: + this.ramBanks.Clear(); + this.ramBanks.Add(new Ram(2 * 1024)); + break; + case 2: + this.ramBanks.Clear(); + this.ramBanks.Add(new Ram(8 * 1024)); + break; + case 3: + this.ramBanks.Clear(); + for (var i = 0; i < 4; ++i) + { + this.ramBanks.Add(new Ram(8 * 1024)); + } + + break; + case 4: + this.ramBanks.Clear(); + for (var i = 0; i < 16; ++i) + { + this.ramBanks.Add(new Ram(8 * 1024)); + } + + break; + default: + throw new InvalidOperationException("Invalid RAM size specification"); + } + } } } + + private int RunRasterLines(int lines) + { + var count = 0; + var allowed = CyclesPerLine; + for (var line = 0; line < lines; ++line) + { + var executed = this.RunRasterLine(allowed); + count += executed; + allowed = CyclesPerLine - (executed - CyclesPerLine); + } + + return count; + } + + private int RunVerticalBlankLines(int lines) + { + /* + Vertical Blank interrupt is triggered when the LCD + controller enters the VBL screen mode (mode 1, LY=144). + This happens once per frame, so this interrupt is + triggered 59.7 times per second. During this period the + VRAM and OAM can be accessed freely, so it's the best + time to update graphics (for example, use the OAM DMA to + update sprites for next frame, or update tiles to make + animations). + This period lasts 4560 clocks in normal speed mode and + 9120 clocks in double speed mode. That's exactly the + time needed to draw 10 scanlines. + The VBL interrupt isn't triggered when the LCD is + powered off or on, even when it was on VBL mode. + It's only triggered when the VBL period starts. + */ + if (this.enabledLCD) + { + this.IO.UpdateLcdStatusMode(LcdStatusMode.VBlank); + if ((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit4) != 0) + { + this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); + } + + this.IO.TriggerInterrupt(Interrupts.VerticalBlank); + } + + return this.RunRasterLines(lines); + } + + private int RunRasterLine(int limit) + { + /* + A scanline normally takes 456 clocks (912 clocks in double speed + mode) to complete. A scanline starts in mode 2, then goes to + mode 3 and, when the LCD controller has finished drawing the + line (the timings depend on lots of things) it goes to mode 0. + During lines 144-153 the LCD controller is in mode 1. + Line 153 takes only a few clocks to complete (the exact + timings are below). The rest of the clocks of line 153 are + spent in line 0 in mode 1! + + During mode 0 and mode 1 the CPU can access both VRAM and OAM. + During mode 2 the CPU can only access VRAM, not OAM. + During mode 3 OAM and VRAM can't be accessed. + In GBC mode the CPU can't access Palette RAM(FF69h and FF6Bh) + during mode 3. + A scanline normally takes 456 clocks(912 clocks in double speed mode) to complete. + A scanline starts in mode 2, then goes to mode 3 and , when the LCD controller has + finished drawing the line(the timings depend on lots of things) it goes to mode 0. + During lines 144 - 153 the LCD controller is in mode 1. + Line 153 takes only a few clocks to complete(the exact timings are below). + The rest of the clocks of line 153 are spent in line 0 in mode 1! + */ + + var count = 0; + if (this.enabledLCD) + { + if (((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit6) != 0) && (this.IO.Peek(IoRegisters.LYC) == this.IO.Peek(IoRegisters.LY))) + { + this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); + } + + // Mode 2, OAM unavailable + this.IO.UpdateLcdStatusMode(LcdStatusMode.SearchingOamRam); + if ((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit5) != 0) + { + this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); + } + + count += this.CPU.Run(80); // ~19us + + // Mode 3, OAM/VRAM unavailable + this.IO.UpdateLcdStatusMode(LcdStatusMode.TransferringDataToLcd); + count += this.CPU.Run(170); // ~41us + + // Mode 0 + this.IO.UpdateLcdStatusMode(LcdStatusMode.HBlank); + if ((this.IO.Peek(IoRegisters.STAT) & (byte)Bits.Bit3) != 0) + { + this.IO.TriggerInterrupt(Interrupts.DisplayControlStatus); + } + + count += this.CPU.Run(limit - count); // ~48.6us + + this.IO.IncrementLY(); + } + else + { + count += this.CPU.Run(CyclesPerLine); + } + + return count; + } } } diff --git a/LR35902/CartridgeType.cs b/LR35902/CartridgeType.cs index e5e5b44..2508497 100644 --- a/LR35902/CartridgeType.cs +++ b/LR35902/CartridgeType.cs @@ -1,16 +1,13 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public enum CartridgeType { - public enum CartridgeType - { - ROM = 0, - ROM_MBC1 = 1, - ROM_MBC1_RAM = 2, - ROM_MBC1_RAM_BATTERY = 3, - } + ROM = 0, + ROM_MBC1 = 1, + ROM_MBC1_RAM = 2, + ROM_MBC1_RAM_BATTERY = 3, } } diff --git a/LR35902/CharacterDefinition.cs b/LR35902/CharacterDefinition.cs index d146ea7..3462299 100644 --- a/LR35902/CharacterDefinition.cs +++ b/LR35902/CharacterDefinition.cs @@ -1,42 +1,39 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public sealed class CharacterDefinition { - public sealed class CharacterDefinition + private readonly Ram vram; + private readonly ushort address; + + public CharacterDefinition(Ram vram, ushort address) { - private readonly Ram vram; - private readonly ushort address; + this.vram = vram; + this.address = address; + } - public CharacterDefinition(Ram vram, ushort address) + public int[] Get(int row) + { + var returned = new int[8]; + + var planeAddress = (ushort)(this.address + (row * 2)); + + var planeLow = this.vram.Peek(planeAddress); + var planeHigh = this.vram.Peek(++planeAddress); + + for (var bit = 0; bit < 8; ++bit) { - this.vram = vram; - this.address = address; + var mask = 1 << bit; + + var bitLow = (planeLow & mask) != 0 ? 1 : 0; + var bitHigh = (planeHigh & mask) != 0 ? 0b10 : 0; + + returned[7 - bit] = bitHigh | bitLow; } - public int[] Get(int row) - { - var returned = new int[8]; - - var planeAddress = (ushort)(this.address + (row * 2)); - - var planeLow = this.vram.Peek(planeAddress); - var planeHigh = this.vram.Peek(++planeAddress); - - for (var bit = 0; bit < 8; ++bit) - { - var mask = 1 << bit; - - var bitLow = (planeLow & mask) != 0 ? 1 : 0; - var bitHigh = (planeHigh & mask) != 0 ? 0b10 : 0; - - returned[7 - bit] = bitHigh | bitLow; - } - - return returned; - } + return returned; } } } diff --git a/LR35902/ColourShades.cs b/LR35902/ColourShades.cs index 5f8c083..f08964e 100644 --- a/LR35902/ColourShades.cs +++ b/LR35902/ColourShades.cs @@ -1,16 +1,13 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public enum ColourShades { - public enum ColourShades - { - Off, - Light, - Medium, - Dark, - } + Off, + Light, + Medium, + Dark, } } diff --git a/LR35902/Disassembler.cs b/LR35902/Disassembler.cs index 4790b16..2dcbdcc 100644 --- a/LR35902/Disassembler.cs +++ b/LR35902/Disassembler.cs @@ -2,703 +2,700 @@ // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public enum IoRegister { - public enum IoRegister + Abbreviated, // FF00 + dd + Absolute, // FFdd + Register, // C + Unused, // Unused! + } + + public class Disassembler + { + private bool prefixCB = false; + + public Disassembler(Bus bus) => this.Bus = bus; + + public Bus Bus { get; } + + public static string AsFlag(byte value, byte flag, string represents) => (value & flag) != 0 ? represents : "-"; + + public static string AsFlag(byte value, StatusBits flag, string represents) => AsFlag(value, (byte)flag, represents); + + public static string AsFlag(byte value, Bits flag, string represents) => AsFlag(value, (byte)flag, represents); + + public static string AsFlags(byte value) => + $"{AsFlag(value, StatusBits.ZF, "Z")}" + + $"{AsFlag(value, StatusBits.NF, "N")}" + + $"{AsFlag(value, StatusBits.HC, "H")}" + + $"{AsFlag(value, StatusBits.CF, "C")}" + + $"{AsFlag(value, Bits.Bit3, "+")}" + + $"{AsFlag(value, Bits.Bit2, "+")}" + + $"{AsFlag(value, Bits.Bit1, "+")}" + + $"{AsFlag(value, Bits.Bit0, "+")}"; + + public static string State(LR35902 cpu) { - Abbreviated, // FF00 + dd - Absolute, // FFdd - Register, // C - Unused, // Unused! + var pc = cpu.PC; + var sp = cpu.SP; + + var a = cpu.A; + var f = cpu.F; + + var b = cpu.B; + var c = cpu.C; + + var d = cpu.D; + var e = cpu.E; + + var h = cpu.H; + var l = cpu.L; + + return + $"PC={pc.Word:x4} SP={sp.Word:x4} " + + $"A={a:x2} F={AsFlags(f)} " + + $"B={b:x2} C={c:x2} " + + $"D={d:x2} E={e:x2} " + + $"H={h:x2} L={l:x2}"; } - public class Disassembler + public string Disassemble(LR35902 cpu) { - private bool prefixCB = false; + this.prefixCB = false; + return this.Disassemble(cpu, cpu.PC.Word); + } - public Disassembler(Bus bus) => this.Bus = bus; - - public Bus Bus { get; } - - public static string AsFlag(byte value, byte flag, string represents) => (value & flag) != 0 ? represents : "-"; - - public static string AsFlag(byte value, StatusBits flag, string represents) => AsFlag(value, (byte)flag, represents); - - public static string AsFlag(byte value, Bits flag, string represents) => AsFlag(value, (byte)flag, represents); - - public static string AsFlags(byte value) => - $"{AsFlag(value, StatusBits.ZF, "Z")}" - + $"{AsFlag(value, StatusBits.NF, "N")}" - + $"{AsFlag(value, StatusBits.HC, "H")}" - + $"{AsFlag(value, StatusBits.CF, "C")}" - + $"{AsFlag(value, Bits.Bit3, "+")}" - + $"{AsFlag(value, Bits.Bit2, "+")}" - + $"{AsFlag(value, Bits.Bit1, "+")}" - + $"{AsFlag(value, Bits.Bit0, "+")}"; - - public static string State(LR35902 cpu) + private static string RP(int rp) + { + switch (rp) { - var pc = cpu.PC; - var sp = cpu.SP; - - var a = cpu.A; - var f = cpu.F; - - var b = cpu.B; - var c = cpu.C; - - var d = cpu.D; - var e = cpu.E; - - var h = cpu.H; - var l = cpu.L; - - return - $"PC={pc.Word:x4} SP={sp.Word:x4} " - + $"A={a:x2} F={AsFlags(f)} " - + $"B={b:x2} C={c:x2} " - + $"D={d:x2} E={e:x2} " - + $"H={h:x2} L={l:x2}"; + case 0: + return "BC"; + case 1: + return "DE"; + case 2: + return "HL"; + case 3: + return "SP"; } - public string Disassemble(LR35902 cpu) + throw new System.ArgumentOutOfRangeException(nameof(rp)); + } + + private static string RP2(int rp) + { + switch (rp) { - this.prefixCB = false; - return this.Disassemble(cpu, cpu.PC.Word); + case 0: + return "BC"; + case 1: + return "DE"; + case 2: + return "HL"; + case 3: + return "AF"; } - private static string RP(int rp) - { - switch (rp) - { - case 0: - return "BC"; - case 1: - return "DE"; - case 2: - return "HL"; - case 3: - return "SP"; - } + throw new System.ArgumentOutOfRangeException(nameof(rp)); + } - throw new System.ArgumentOutOfRangeException(nameof(rp)); + private static string R(int r) + { + switch (r) + { + case 0: + return "B"; + case 1: + return "C"; + case 2: + return "D"; + case 3: + return "E"; + case 4: + return "H"; + case 5: + return "L"; + case 6: + return "(HL)"; + case 7: + return "A"; } - private static string RP2(int rp) - { - switch (rp) - { - case 0: - return "BC"; - case 1: - return "DE"; - case 2: - return "HL"; - case 3: - return "AF"; - } + throw new System.ArgumentOutOfRangeException(nameof(r)); + } - throw new System.ArgumentOutOfRangeException(nameof(rp)); + private static string IO(byte value) + { + switch (value) + { + // Port/Mode Registers + case IoRegisters.P1: + return "P1"; + case IoRegisters.SB: + return "SB"; + case IoRegisters.SC: + return "SC"; + case IoRegisters.DIV: + return "DIV"; + case IoRegisters.TIMA: + return "TIMA"; + case IoRegisters.TMA: + return "TMA"; + case IoRegisters.TAC: + return "TAC"; + + // Interrupt Flags + case IoRegisters.IF: + return "IF"; + case IoRegisters.IE: + return "IE"; + + // LCD Display Registers + case IoRegisters.LCDC: + return "LCDC"; + case IoRegisters.STAT: + return "STAT"; + case IoRegisters.SCY: + return "SCY"; + case IoRegisters.SCX: + return "SCX"; + case IoRegisters.LY: + return "LY"; + case IoRegisters.LYC: + return "LYC"; + case IoRegisters.DMA: + return "DMA"; + case IoRegisters.BGP: + return "BGP"; + case IoRegisters.OBP0: + return "OBP0"; + case IoRegisters.OBP1: + return "OBP1"; + case IoRegisters.WY: + return "WY"; + case IoRegisters.WX: + return "WX"; + + // Sound Registers + case IoRegisters.NR10: + return "NR10"; + case IoRegisters.NR11: + return "NR11"; + case IoRegisters.NR12: + return "NR12"; + case IoRegisters.NR13: + return "NR13"; + case IoRegisters.NR14: + return "NR14"; + case IoRegisters.NR21: + return "NR21"; + case IoRegisters.NR22: + return "NR22"; + case IoRegisters.NR23: + return "NR23"; + case IoRegisters.NR24: + return "NR24"; + case IoRegisters.NR30: + return "NR30"; + case IoRegisters.NR31: + return "NR31"; + case IoRegisters.NR32: + return "NR32"; + case IoRegisters.NR33: + return "NR33"; + case IoRegisters.NR34: + return "NR34"; + case IoRegisters.NR41: + return "NR41"; + case IoRegisters.NR42: + return "NR42"; + case IoRegisters.NR43: + return "NR43"; + case IoRegisters.NR44: + return "NR44"; + case IoRegisters.NR50: + return "NR50"; + case IoRegisters.NR51: + return "NR51"; + case IoRegisters.NR52: + return "NR52"; + + case IoRegisters.WAVE_PATTERN_RAM_START: + return "WAVE_PATTERN_RAM_START"; + case IoRegisters.WAVE_PATTERN_RAM_END: + return "WAVE_PATTERN_RAM_END"; + + // Boot rom control + case IoRegisters.BOOT_DISABLE: + return "BOOT_DISABLE"; + + default: + return $"{value:x2}"; + } + } + + private static string CC(int flag) + { + switch (flag) + { + case 0: + return "NZ"; + case 1: + return "Z"; + case 2: + return "NC"; + case 3: + return "C"; + case 4: + return "PO"; + case 5: + return "PE"; + case 6: + return "P"; + case 7: + return "M"; } - private static string R(int r) - { - switch (r) - { - case 0: - return "B"; - case 1: - return "C"; - case 2: - return "D"; - case 3: - return "E"; - case 4: - return "H"; - case 5: - return "L"; - case 6: - return "(HL)"; - case 7: - return "A"; - } + throw new System.ArgumentOutOfRangeException(nameof(flag)); + } - throw new System.ArgumentOutOfRangeException(nameof(r)); + private static string ALU(int which) + { + switch (which) + { + case 0: // ADD A,n + return "ADD"; + case 1: // ADC + return "ADC"; + case 2: // SUB n + return "SUB"; + case 3: // SBC A,n + return "SBC"; + case 4: // AND n + return "AND"; + case 5: // XOR n + return "XOR"; + case 6: // OR n + return "OR"; + case 7: // CP n + return "CP"; } - private static string IO(byte value) + throw new System.ArgumentOutOfRangeException(nameof(which)); + } + + private static string DisassembleCB(ref string specification, int x, int y, int z) + { + var output = string.Empty; + switch (x) { - switch (value) - { - // Port/Mode Registers - case IoRegisters.P1: - return "P1"; - case IoRegisters.SB: - return "SB"; - case IoRegisters.SC: - return "SC"; - case IoRegisters.DIV: - return "DIV"; - case IoRegisters.TIMA: - return "TIMA"; - case IoRegisters.TMA: - return "TMA"; - case IoRegisters.TAC: - return "TAC"; + case 0: // rot[y] r[z] + switch (y) + { + case 0: + specification = $"RLC {R(z)}"; + break; + case 1: + specification = $"RRC {R(z)}"; + break; + case 2: + specification = $"RL {R(z)}"; + break; + case 3: + specification = $"RR {R(z)}"; + break; + case 4: + specification = $"SLA {R(z)}"; + break; + case 5: + specification = $"SRA {R(z)}"; + break; + case 6: + specification = $"SWAP {R(z)}"; + break; + case 7: + specification = $"SRL {R(z)}"; + break; + } - // Interrupt Flags - case IoRegisters.IF: - return "IF"; - case IoRegisters.IE: - return "IE"; - - // LCD Display Registers - case IoRegisters.LCDC: - return "LCDC"; - case IoRegisters.STAT: - return "STAT"; - case IoRegisters.SCY: - return "SCY"; - case IoRegisters.SCX: - return "SCX"; - case IoRegisters.LY: - return "LY"; - case IoRegisters.LYC: - return "LYC"; - case IoRegisters.DMA: - return "DMA"; - case IoRegisters.BGP: - return "BGP"; - case IoRegisters.OBP0: - return "OBP0"; - case IoRegisters.OBP1: - return "OBP1"; - case IoRegisters.WY: - return "WY"; - case IoRegisters.WX: - return "WX"; - - // Sound Registers - case IoRegisters.NR10: - return "NR10"; - case IoRegisters.NR11: - return "NR11"; - case IoRegisters.NR12: - return "NR12"; - case IoRegisters.NR13: - return "NR13"; - case IoRegisters.NR14: - return "NR14"; - case IoRegisters.NR21: - return "NR21"; - case IoRegisters.NR22: - return "NR22"; - case IoRegisters.NR23: - return "NR23"; - case IoRegisters.NR24: - return "NR24"; - case IoRegisters.NR30: - return "NR30"; - case IoRegisters.NR31: - return "NR31"; - case IoRegisters.NR32: - return "NR32"; - case IoRegisters.NR33: - return "NR33"; - case IoRegisters.NR34: - return "NR34"; - case IoRegisters.NR41: - return "NR41"; - case IoRegisters.NR42: - return "NR42"; - case IoRegisters.NR43: - return "NR43"; - case IoRegisters.NR44: - return "NR44"; - case IoRegisters.NR50: - return "NR50"; - case IoRegisters.NR51: - return "NR51"; - case IoRegisters.NR52: - return "NR52"; - - case IoRegisters.WAVE_PATTERN_RAM_START: - return "WAVE_PATTERN_RAM_START"; - case IoRegisters.WAVE_PATTERN_RAM_END: - return "WAVE_PATTERN_RAM_END"; - - // Boot rom control - case IoRegisters.BOOT_DISABLE: - return "BOOT_DISABLE"; - - default: - return $"{value:x2}"; - } + break; + case 1: // BIT y, r[z] + specification = $"BIT {y},{R(z)}"; + break; + case 2: // RES y, r[z] + specification = $"RES {y},{R(z)}"; + break; + case 3: // SET y, r[z] + specification = $"SET {y},{R(z)}"; + break; } - private static string CC(int flag) - { - switch (flag) - { - case 0: - return "NZ"; - case 1: - return "Z"; - case 2: - return "NC"; - case 3: - return "C"; - case 4: - return "PO"; - case 5: - return "PE"; - case 6: - return "P"; - case 7: - return "M"; - } + return output; + } - throw new System.ArgumentOutOfRangeException(nameof(flag)); + private string Disassemble(LR35902 cpu, ushort pc) + { + var opCode = this.Bus.Peek(pc); + + var decoded = cpu.GetDecodedOpCode(opCode); + + var x = decoded.X; + var y = decoded.Y; + var z = decoded.Z; + + var p = decoded.P; + var q = decoded.Q; + + var immediate = this.Bus.Peek((ushort)(pc + 1)); + var absolute = cpu.PeekWord((ushort)(pc + 1)).Word; + var displacement = (sbyte)immediate; + var relative = pc + displacement + 2; + var indexedImmediate = this.Bus.Peek((ushort)(pc + 1)); + + var dumpCount = 0; + var ioRegister = IoRegister.Unused; + + var output = $"{opCode:x2}"; + + var specification = string.Empty; + if (this.prefixCB) + { + output += DisassembleCB(ref specification, x, y, z); + } + else + { + output += this.DisassembleOther(cpu, pc, ref specification, ref dumpCount, ref ioRegister, x, y, z, p, q); } - private static string ALU(int which) + for (var i = 0; i < dumpCount; ++i) { - switch (which) - { - case 0: // ADD A,n - return "ADD"; - case 1: // ADC - return "ADC"; - case 2: // SUB n - return "SUB"; - case 3: // SBC A,n - return "SBC"; - case 4: // AND n - return "AND"; - case 5: // XOR n - return "XOR"; - case 6: // OR n - return "OR"; - case 7: // CP n - return "CP"; - } - - throw new System.ArgumentOutOfRangeException(nameof(which)); + output += $"{this.Bus.Peek((ushort)(pc + i + 1)):x2}"; } - private static string DisassembleCB(ref string specification, int x, int y, int z) + output += '\t'; + output += string.Format(specification, (int)immediate, (int)absolute, relative, (int)displacement, indexedImmediate); + + switch (ioRegister) { - var output = string.Empty; - switch (x) - { - case 0: // rot[y] r[z] - switch (y) - { - case 0: - specification = $"RLC {R(z)}"; - break; - case 1: - specification = $"RRC {R(z)}"; - break; - case 2: - specification = $"RL {R(z)}"; - break; - case 3: - specification = $"RR {R(z)}"; - break; - case 4: - specification = $"SLA {R(z)}"; - break; - case 5: - specification = $"SRA {R(z)}"; - break; - case 6: - specification = $"SWAP {R(z)}"; - break; - case 7: - specification = $"SRL {R(z)}"; - break; - } - - break; - case 1: // BIT y, r[z] - specification = $"BIT {y},{R(z)}"; - break; - case 2: // RES y, r[z] - specification = $"RES {y},{R(z)}"; - break; - case 3: // SET y, r[z] - specification = $"SET {y},{R(z)}"; - break; - } - - return output; + case IoRegister.Abbreviated: + output += $"; register {IO(immediate)}"; + break; + case IoRegister.Absolute: + output += "; register (Absolute)"; + break; + case IoRegister.Register: + output += $"; register C:{IO(cpu.C)}"; + break; + case IoRegister.Unused: + break; } - private string Disassemble(LR35902 cpu, ushort pc) + return output; + } + + private string DisassembleOther(LR35902 cpu, ushort pc, ref string specification, ref int dumpCount, ref IoRegister ioRegister, int x, int y, int z, int p, int q) + { + var output = string.Empty; + switch (x) { - var opCode = this.Bus.Peek(pc); + case 0: + switch (z) + { + case 0: // Relative jumps and assorted ops + switch (y) + { + case 0: // NOP + specification = "NOP"; + break; + case 1: // GB: LD (nn),SP + specification = "LD ({1:X4}H),SP"; + dumpCount += 2; + break; + case 2: // GB: STOP + specification = "STOP"; + break; + case 3: // JR d + specification = "JR {2:X4}H"; + dumpCount++; + break; + default: // JR cc,d + specification = $"JR {CC(y - 4)}" + ",{2:X4}H"; + dumpCount++; + break; + } - var decoded = cpu.GetDecodedOpCode(opCode); + break; + case 1: // 16-bit load immediate/add + switch (q) + { + case 0: // LD rp,nn + specification = $"LD {RP(p)}," + "{1:X4}H"; + dumpCount += 2; + break; + case 1: // ADD HL,rp + specification = $"ADD HL,{RP(p)}"; + break; + } - var x = decoded.X; - var y = decoded.Y; - var z = decoded.Z; + break; + case 2: // Indirect loading + switch (q) + { + case 0: + switch (p) + { + case 0: // LD (BC),A + specification = "LD (BC),A"; + break; + case 1: // LD (DE),A + specification = "LD (DE),A"; + break; + case 2: // GB: LDI (HL),A + specification = "LDI (HL),A"; + break; + case 3: // GB: LDD (HL),A + specification = "LDD (HL),A"; + break; + } - var p = decoded.P; - var q = decoded.Q; + break; + case 1: + switch (p) + { + case 0: // LD A,(BC) + specification = "LD A,(BC)"; + break; + case 1: // LD A,(DE) + specification = "LD A,(DE)"; + break; + case 2: // GB: LDI A,(HL) + specification = "LDI A,(HL)"; + break; + case 3: // GB: LDD A,(HL) + specification = "LDD A,(HL)"; + break; + } - var immediate = this.Bus.Peek((ushort)(pc + 1)); - var absolute = cpu.PeekWord((ushort)(pc + 1)).Word; - var displacement = (sbyte)immediate; - var relative = pc + displacement + 2; - var indexedImmediate = this.Bus.Peek((ushort)(pc + 1)); + break; + } - var dumpCount = 0; - var ioRegister = IoRegister.Unused; + break; + case 3: // 16-bit INC/DEC + switch (q) + { + case 0: // INC rp + specification = $"INC {RP(p)}"; + break; + case 1: // DEC rp + specification = $"DEC {RP(p)}"; + break; + } - var output = $"{opCode:x2}"; + break; + case 4: // 8-bit INC + specification = $"INC {R(y)}"; + break; + case 5: // 8-bit DEC + specification = $"DEC {R(y)}"; + break; + case 6: // 8-bit load immediate + specification = $"LD {R(y)}," + "{0:X2}H"; + dumpCount++; + break; + case 7: // Assorted operations on accumulator/flags + switch (y) + { + case 0: + specification = "RLCA"; + break; + case 1: + specification = "RRCA"; + break; + case 2: + specification = "RLA"; + break; + case 3: + specification = "RRA"; + break; + case 4: + specification = "DAA"; + break; + case 5: + specification = "CPL"; + break; + case 6: + specification = "SCF"; + break; + case 7: + specification = "CCF"; + break; + } - var specification = string.Empty; - if (this.prefixCB) - { - output += DisassembleCB(ref specification, x, y, z); - } - else - { - output += this.DisassembleOther(cpu, pc, ref specification, ref dumpCount, ref ioRegister, x, y, z, p, q); - } + break; + } - for (var i = 0; i < dumpCount; ++i) - { - output += $"{this.Bus.Peek((ushort)(pc + i + 1)):x2}"; - } + break; + case 1: // 8-bit loading + if (z == 6 && y == 6) + { + specification = "HALT"; // Exception (replaces LD (HL), (HL)) + } + else + { + specification = $"LD {R(y)},{R(z)}"; + } - output += '\t'; - output += string.Format(specification, (int)immediate, (int)absolute, relative, (int)displacement, indexedImmediate); + break; + case 2: // Operate on accumulator and register/memory location + specification = $"{ALU(y)} A,{R(z)}"; + break; + case 3: + switch (z) + { + case 0: // Conditional return + switch (y) + { + case 0: + case 1: + case 2: + case 3: + specification = $"RET {CC(y)}"; + break; + case 4: + specification = "LD (FF00H+{0:X2}H),A"; + ioRegister = IoRegister.Abbreviated; + dumpCount++; + break; + case 5: + specification = "ADD SP,{4:X4}H"; + dumpCount++; + break; + case 6: + specification = "LD A,(FF00H+{0:X2}H)"; + ioRegister = IoRegister.Abbreviated; + dumpCount++; + break; + case 7: + specification = "LD HL,SP+{4}"; + dumpCount++; + break; + } - switch (ioRegister) - { - case IoRegister.Abbreviated: - output += $"; register {IO(immediate)}"; - break; - case IoRegister.Absolute: - output += "; register (Absolute)"; - break; - case IoRegister.Register: - output += $"; register C:{IO(cpu.C)}"; - break; - case IoRegister.Unused: - break; - } + break; + case 1: // POP & various ops + switch (q) + { + case 0: // POP rp2[p] + specification = $"POP {RP2(p)}"; + break; + case 1: + switch (p) + { + case 0: // RET + specification = "RET"; + break; + case 1: // GB: RETI + specification = "RETI"; + break; + case 2: // JP (HL) + specification = "JP (HL)"; + break; + case 3: // LD SP,HL + specification = "LD SP,HL"; + break; + } - return output; + break; + } + + break; + case 2: // Conditional jump + switch (y) + { + case 0: + case 1: + case 2: + case 3: + specification = $"JP {CC(y)}" + ",{1:X4}H"; + dumpCount += 2; + break; + case 4: + specification = "LD (FF00H+C),A"; + ioRegister = IoRegister.Register; + break; + case 5: + specification = "LD ({1:X4}H),A"; + dumpCount += 2; + break; + case 6: + specification = "LD A,(FF00H+C)"; + ioRegister = IoRegister.Register; + break; + case 7: + specification = "LD A,({1:X4}H)"; + dumpCount += 2; + break; + } + + break; + case 3: // Assorted operations + switch (y) + { + case 0: // JP nn + specification = "JP {1:X4}H"; + dumpCount += 2; + break; + case 1: // CB prefix + this.prefixCB = true; + output += this.Disassemble(cpu, ++pc); + break; + case 6: // DI + specification = "DI"; + break; + case 7: // EI + specification = "EI"; + break; + } + + break; + case 4: // Conditional call: CALL cc[y], nn + specification = $"CALL {CC(y)}" + ",{1:X4}H"; + dumpCount += 2; + break; + case 5: // PUSH & various ops + switch (q) + { + case 0: // PUSH rp2[p] + specification = $"PUSH {RP2(p)}"; + break; + case 1: + switch (p) + { + case 0: // CALL nn + specification = "CALL {1:X4}H"; + dumpCount += 2; + break; + } + + break; + } + + break; + case 6: // Operate on accumulator and immediate operand: alu[y] n + specification = ALU(y) + " A,{0:X2}H"; + dumpCount++; + break; + case 7: // Restart: RST y * 8 + specification = $"RST {y * 8:X2}"; + break; + } + + break; } - private string DisassembleOther(LR35902 cpu, ushort pc, ref string specification, ref int dumpCount, ref IoRegister ioRegister, int x, int y, int z, int p, int q) - { - var output = string.Empty; - switch (x) - { - case 0: - switch (z) - { - case 0: // Relative jumps and assorted ops - switch (y) - { - case 0: // NOP - specification = "NOP"; - break; - case 1: // GB: LD (nn),SP - specification = "LD ({1:X4}H),SP"; - dumpCount += 2; - break; - case 2: // GB: STOP - specification = "STOP"; - break; - case 3: // JR d - specification = "JR {2:X4}H"; - dumpCount++; - break; - default: // JR cc,d - specification = $"JR {CC(y - 4)}" + ",{2:X4}H"; - dumpCount++; - break; - } - - break; - case 1: // 16-bit load immediate/add - switch (q) - { - case 0: // LD rp,nn - specification = $"LD {RP(p)}," + "{1:X4}H"; - dumpCount += 2; - break; - case 1: // ADD HL,rp - specification = $"ADD HL,{RP(p)}"; - break; - } - - break; - case 2: // Indirect loading - switch (q) - { - case 0: - switch (p) - { - case 0: // LD (BC),A - specification = "LD (BC),A"; - break; - case 1: // LD (DE),A - specification = "LD (DE),A"; - break; - case 2: // GB: LDI (HL),A - specification = "LDI (HL),A"; - break; - case 3: // GB: LDD (HL),A - specification = "LDD (HL),A"; - break; - } - - break; - case 1: - switch (p) - { - case 0: // LD A,(BC) - specification = "LD A,(BC)"; - break; - case 1: // LD A,(DE) - specification = "LD A,(DE)"; - break; - case 2: // GB: LDI A,(HL) - specification = "LDI A,(HL)"; - break; - case 3: // GB: LDD A,(HL) - specification = "LDD A,(HL)"; - break; - } - - break; - } - - break; - case 3: // 16-bit INC/DEC - switch (q) - { - case 0: // INC rp - specification = $"INC {RP(p)}"; - break; - case 1: // DEC rp - specification = $"DEC {RP(p)}"; - break; - } - - break; - case 4: // 8-bit INC - specification = $"INC {R(y)}"; - break; - case 5: // 8-bit DEC - specification = $"DEC {R(y)}"; - break; - case 6: // 8-bit load immediate - specification = $"LD {R(y)}," + "{0:X2}H"; - dumpCount++; - break; - case 7: // Assorted operations on accumulator/flags - switch (y) - { - case 0: - specification = "RLCA"; - break; - case 1: - specification = "RRCA"; - break; - case 2: - specification = "RLA"; - break; - case 3: - specification = "RRA"; - break; - case 4: - specification = "DAA"; - break; - case 5: - specification = "CPL"; - break; - case 6: - specification = "SCF"; - break; - case 7: - specification = "CCF"; - break; - } - - break; - } - - break; - case 1: // 8-bit loading - if (z == 6 && y == 6) - { - specification = "HALT"; // Exception (replaces LD (HL), (HL)) - } - else - { - specification = $"LD {R(y)},{R(z)}"; - } - - break; - case 2: // Operate on accumulator and register/memory location - specification = $"{ALU(y)} A,{R(z)}"; - break; - case 3: - switch (z) - { - case 0: // Conditional return - switch (y) - { - case 0: - case 1: - case 2: - case 3: - specification = $"RET {CC(y)}"; - break; - case 4: - specification = "LD (FF00H+{0:X2}H),A"; - ioRegister = IoRegister.Abbreviated; - dumpCount++; - break; - case 5: - specification = "ADD SP,{4:X4}H"; - dumpCount++; - break; - case 6: - specification = "LD A,(FF00H+{0:X2}H)"; - ioRegister = IoRegister.Abbreviated; - dumpCount++; - break; - case 7: - specification = "LD HL,SP+{4}"; - dumpCount++; - break; - } - - break; - case 1: // POP & various ops - switch (q) - { - case 0: // POP rp2[p] - specification = $"POP {RP2(p)}"; - break; - case 1: - switch (p) - { - case 0: // RET - specification = "RET"; - break; - case 1: // GB: RETI - specification = "RETI"; - break; - case 2: // JP (HL) - specification = "JP (HL)"; - break; - case 3: // LD SP,HL - specification = "LD SP,HL"; - break; - } - - break; - } - - break; - case 2: // Conditional jump - switch (y) - { - case 0: - case 1: - case 2: - case 3: - specification = $"JP {CC(y)}" + ",{1:X4}H"; - dumpCount += 2; - break; - case 4: - specification = "LD (FF00H+C),A"; - ioRegister = IoRegister.Register; - break; - case 5: - specification = "LD ({1:X4}H),A"; - dumpCount += 2; - break; - case 6: - specification = "LD A,(FF00H+C)"; - ioRegister = IoRegister.Register; - break; - case 7: - specification = "LD A,({1:X4}H)"; - dumpCount += 2; - break; - } - - break; - case 3: // Assorted operations - switch (y) - { - case 0: // JP nn - specification = "JP {1:X4}H"; - dumpCount += 2; - break; - case 1: // CB prefix - this.prefixCB = true; - output += this.Disassemble(cpu, ++pc); - break; - case 6: // DI - specification = "DI"; - break; - case 7: // EI - specification = "EI"; - break; - } - - break; - case 4: // Conditional call: CALL cc[y], nn - specification = $"CALL {CC(y)}" + ",{1:X4}H"; - dumpCount += 2; - break; - case 5: // PUSH & various ops - switch (q) - { - case 0: // PUSH rp2[p] - specification = $"PUSH {RP2(p)}"; - break; - case 1: - switch (p) - { - case 0: // CALL nn - specification = "CALL {1:X4}H"; - dumpCount += 2; - break; - } - - break; - } - - break; - case 6: // Operate on accumulator and immediate operand: alu[y] n - specification = ALU(y) + " A,{0:X2}H"; - dumpCount++; - break; - case 7: // Restart: RST y * 8 - specification = $"RST {y * 8:X2}"; - break; - } - - break; - } - - return output; - } + return output; } } } diff --git a/LR35902/Display.cs b/LR35902/Display.cs index 3e9be27..20132a6 100644 --- a/LR35902/Display.cs +++ b/LR35902/Display.cs @@ -1,176 +1,173 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public sealed class Display { - public sealed class Display + public static readonly int BufferWidth = 256; + public static readonly int BufferHeight = 256; + public static readonly int BufferCharacterWidth = BufferWidth / 8; + public static readonly int BufferCharacterHeight = BufferHeight / 8; + public static readonly int RasterWidth = 160; + public static readonly int RasterHeight = 144; + public static readonly int PixelCount = RasterWidth * RasterHeight; + + private readonly Bus bus; + private readonly Ram oam; + private readonly Ram vram; + private readonly AbstractColourPalette colours; + private readonly ObjectAttribute[] objectAttributes = new ObjectAttribute[40]; + private byte control; + private byte scanLine = 0; + + public Display(AbstractColourPalette colours, Bus bus, Ram oam, Ram vram) { - public static readonly int BufferWidth = 256; - public static readonly int BufferHeight = 256; - public static readonly int BufferCharacterWidth = BufferWidth / 8; - public static readonly int BufferCharacterHeight = BufferHeight / 8; - public static readonly int RasterWidth = 160; - public static readonly int RasterHeight = 144; - public static readonly int PixelCount = RasterWidth * RasterHeight; + this.colours = colours; + this.bus = bus; + this.oam = oam; + this.vram = vram; + } - private readonly Bus bus; - private readonly Ram oam; - private readonly Ram vram; - private readonly AbstractColourPalette colours; - private readonly ObjectAttribute[] objectAttributes = new ObjectAttribute[40]; - private byte control; - private byte scanLine = 0; + public uint[] Pixels { get; } = new uint[PixelCount]; - public Display(AbstractColourPalette colours, Bus bus, Ram oam, Ram vram) + public void Render() + { + this.scanLine = this.bus.IO.Peek(IoRegisters.LY); + if (this.scanLine < RasterHeight) { - this.colours = colours; - this.bus = bus; - this.oam = oam; - this.vram = vram; - } - - public uint[] Pixels { get; } = new uint[PixelCount]; - - public void Render() - { - this.scanLine = this.bus.IO.Peek(IoRegisters.LY); - if (this.scanLine < RasterHeight) + this.control = this.bus.IO.Peek(IoRegisters.LCDC); + if ((this.control & (byte)LcdcControl.LcdEnable) != 0) { - this.control = this.bus.IO.Peek(IoRegisters.LCDC); - if ((this.control & (byte)LcdcControl.LcdEnable) != 0) + if ((this.control & (byte)LcdcControl.DisplayBackground) != 0) { - if ((this.control & (byte)LcdcControl.DisplayBackground) != 0) - { - this.RenderBackground(); - } + this.RenderBackground(); + } - if ((this.control & (byte)LcdcControl.ObjectEnable) != 0) - { - this.RenderObjects(); - } + if ((this.control & (byte)LcdcControl.ObjectEnable) != 0) + { + this.RenderObjects(); } } } + } - public void LoadObjectAttributes() + public void LoadObjectAttributes() + { + for (var i = 0; i < 40; ++i) { - for (var i = 0; i < 40; ++i) + this.objectAttributes[i] = new ObjectAttribute(this.oam, (ushort)(4 * i)); + } + } + + private int[] CreatePalette(ushort address) + { + var raw = this.bus.IO.Peek(address); + return new int[4] + { + raw & 0b11, + (raw & 0b1100) >> 2, + (raw & 0b110000) >> 4, + (raw & 0b11000000) >> 6, + }; + } + + private void RenderBackground() + { + var palette = this.CreatePalette(IoRegisters.BGP); + + var window = (this.control & (byte)LcdcControl.WindowEnable) != 0; + var bgArea = (this.control & (byte)LcdcControl.BackgroundCodeAreaSelection) != 0 ? 0x1c00 : 0x1800; + var bgCharacters = (this.control & (byte)LcdcControl.BackgroundCharacterDataSelection) != 0 ? 0 : 0x800; + + var wx = this.bus.IO.Peek(IoRegisters.WX); + var wy = this.bus.IO.Peek(IoRegisters.WY); + + var offsetX = window ? wx - 7 : 0; + var offsetY = window ? wy : 0; + + var scrollX = this.bus.IO.Peek(IoRegisters.SCX); + var scrollY = this.bus.IO.Peek(IoRegisters.SCY); + + this.RenderBackground(bgArea, bgCharacters, offsetX - scrollX, offsetY - scrollY, palette); + } + + private void RenderBackground(int bgArea, int bgCharacters, int offsetX, int offsetY, int[] palette) + { + var row = (this.scanLine - offsetY) / 8; + var address = bgArea + (row * BufferCharacterWidth); + + for (var column = 0; column < BufferCharacterWidth; ++column) + { + var character = this.vram.Peek((ushort)address++); + var definition = new CharacterDefinition(this.vram, (ushort)(bgCharacters + (16 * character))); + this.RenderTile(8, (column * 8) + offsetX, (row * 8) + offsetY, false, false, false, palette, definition); + } + } + + private void RenderObjects() + { + var objBlockHeight = (this.control & (byte)LcdcControl.ObjectBlockCompositionSelection) != 0 ? 16 : 8; + + var palettes = new int[2][]; + palettes[0] = this.CreatePalette(IoRegisters.OBP0); + palettes[1] = this.CreatePalette(IoRegisters.OBP1); + + var characterAddressMultiplier = objBlockHeight == 8 ? 16 : 8; + + for (var i = 0; i < 40; ++i) + { + var current = this.objectAttributes[i]; + + var spriteY = current.PositionY; + var drawY = spriteY - 16; + + if ((this.scanLine >= drawY) && (this.scanLine < (drawY + objBlockHeight))) { - this.objectAttributes[i] = new ObjectAttribute(this.oam, (ushort)(4 * i)); + var spriteX = current.PositionX; + var drawX = spriteX - 8; + + var sprite = current.Pattern; + var definition = new CharacterDefinition(this.vram, (ushort)(characterAddressMultiplier * sprite)); + var palette = palettes[current.Palette]; + var flipX = current.FlipX; + var flipY = current.FlipY; + + this.RenderTile(objBlockHeight, drawX, drawY, flipX, flipY, true, palette, definition); } } + } - private int[] CreatePalette(ushort address) + private void RenderTile(int height, int drawX, int drawY, bool flipX, bool flipY, bool allowTransparencies, int[] palette, CharacterDefinition definition) + { + const int width = 8; + const int flipMaskX = width - 1; + var flipMaskY = height - 1; + + var y = this.scanLine; + + var cy = y - drawY; + if (flipY) { - var raw = this.bus.IO.Peek(address); - return new int[4] - { - raw & 0b11, - (raw & 0b1100) >> 2, - (raw & 0b110000) >> 4, - (raw & 0b11000000) >> 6, - }; + cy = ~cy & flipMaskY; } - private void RenderBackground() + var rowDefinition = definition.Get(cy); + + var lineAddress = y * RasterWidth; + for (var cx = 0; cx < width; ++cx) { - var palette = this.CreatePalette(IoRegisters.BGP); - - var window = (this.control & (byte)LcdcControl.WindowEnable) != 0; - var bgArea = (this.control & (byte)LcdcControl.BackgroundCodeAreaSelection) != 0 ? 0x1c00 : 0x1800; - var bgCharacters = (this.control & (byte)LcdcControl.BackgroundCharacterDataSelection) != 0 ? 0 : 0x800; - - var wx = this.bus.IO.Peek(IoRegisters.WX); - var wy = this.bus.IO.Peek(IoRegisters.WY); - - var offsetX = window ? wx - 7 : 0; - var offsetY = window ? wy : 0; - - var scrollX = this.bus.IO.Peek(IoRegisters.SCX); - var scrollY = this.bus.IO.Peek(IoRegisters.SCY); - - this.RenderBackground(bgArea, bgCharacters, offsetX - scrollX, offsetY - scrollY, palette); - } - - private void RenderBackground(int bgArea, int bgCharacters, int offsetX, int offsetY, int[] palette) - { - var row = (this.scanLine - offsetY) / 8; - var address = bgArea + (row * BufferCharacterWidth); - - for (var column = 0; column < BufferCharacterWidth; ++column) + var x = drawX + (flipX ? ~cx & flipMaskX : cx); + if (x >= RasterWidth) { - var character = this.vram.Peek((ushort)address++); - var definition = new CharacterDefinition(this.vram, (ushort)(bgCharacters + (16 * character))); - this.RenderTile(8, (column * 8) + offsetX, (row * 8) + offsetY, false, false, false, palette, definition); - } - } - - private void RenderObjects() - { - var objBlockHeight = (this.control & (byte)LcdcControl.ObjectBlockCompositionSelection) != 0 ? 16 : 8; - - var palettes = new int[2][]; - palettes[0] = this.CreatePalette(IoRegisters.OBP0); - palettes[1] = this.CreatePalette(IoRegisters.OBP1); - - var characterAddressMultiplier = objBlockHeight == 8 ? 16 : 8; - - for (var i = 0; i < 40; ++i) - { - var current = this.objectAttributes[i]; - - var spriteY = current.PositionY; - var drawY = spriteY - 16; - - if ((this.scanLine >= drawY) && (this.scanLine < (drawY + objBlockHeight))) - { - var spriteX = current.PositionX; - var drawX = spriteX - 8; - - var sprite = current.Pattern; - var definition = new CharacterDefinition(this.vram, (ushort)(characterAddressMultiplier * sprite)); - var palette = palettes[current.Palette]; - var flipX = current.FlipX; - var flipY = current.FlipY; - - this.RenderTile(objBlockHeight, drawX, drawY, flipX, flipY, true, palette, definition); - } - } - } - - private void RenderTile(int height, int drawX, int drawY, bool flipX, bool flipY, bool allowTransparencies, int[] palette, CharacterDefinition definition) - { - const int width = 8; - const int flipMaskX = width - 1; - var flipMaskY = height - 1; - - var y = this.scanLine; - - var cy = y - drawY; - if (flipY) - { - cy = ~cy & flipMaskY; + break; } - var rowDefinition = definition.Get(cy); - - var lineAddress = y * RasterWidth; - for (var cx = 0; cx < width; ++cx) + var colour = rowDefinition[cx]; + if (!allowTransparencies || (allowTransparencies && (colour > 0))) { - var x = drawX + (flipX ? ~cx & flipMaskX : cx); - if (x >= RasterWidth) - { - break; - } - - var colour = rowDefinition[cx]; - if (!allowTransparencies || (allowTransparencies && (colour > 0))) - { - var outputPixel = lineAddress + x; - this.Pixels[outputPixel] = this.colours.Colour(palette[colour]); - } + var outputPixel = lineAddress + x; + this.Pixels[outputPixel] = this.colours.Colour(palette[colour]); } } } diff --git a/LR35902/Interrupts.cs b/LR35902/Interrupts.cs index a767bd6..a49946b 100644 --- a/LR35902/Interrupts.cs +++ b/LR35902/Interrupts.cs @@ -1,20 +1,17 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + // IF and IE flags + [System.Flags] + public enum Interrupts { - // IF and IE flags - [System.Flags] - public enum Interrupts - { - None = 0, - VerticalBlank = Bits.Bit0, // VBLANK - DisplayControlStatus = Bits.Bit1, // LCDC Status - TimerOverflow = Bits.Bit2, // Timer Overflow - SerialTransfer = Bits.Bit3, // Serial Transfer - KeypadPressed = Bits.Bit4, // Hi-Lo transition of P10-P13 - } + None = 0, + VerticalBlank = Bits.Bit0, // VBLANK + DisplayControlStatus = Bits.Bit1, // LCDC Status + TimerOverflow = Bits.Bit2, // Timer Overflow + SerialTransfer = Bits.Bit3, // Serial Transfer + KeypadPressed = Bits.Bit4, // Hi-Lo transition of P10-P13 } } diff --git a/LR35902/IoRegisters.cs b/LR35902/IoRegisters.cs index a8ca82a..b0f2ad9 100644 --- a/LR35902/IoRegisters.cs +++ b/LR35902/IoRegisters.cs @@ -1,397 +1,394 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + using System; + + public sealed class IoRegisters : EightBit.Ram { - using System; + public const int BASE = 0xFF00; - public sealed class IoRegisters : EightBit.Ram + // Port/Mode Registers + public const int P1 = 0x0; // R/W Mask5 + public const int SB = 0x1; // R/W Mask8 + public const int SC = 0x2; // R/W Bit7 | Bit0 + + // Timer control + public const int DIV = 0x4; // R/W Mask8 + public const int TIMA = 0x5; // R/W Mask8 + public const int TMA = 0x6; // R/W Mask8 + public const int TAC = 0x7; // R/W Mask3 + + // Interrupt Flags + public const int IF = 0xF; // R/W Mask5 + public const int IE = 0xFF; // R/W Mask5 + + // Sound Registers + public const int NR10 = 0x10; // R/W Mask7 + public const int NR11 = 0x11; // R/W Bit7 | Bit6 + public const int NR12 = 0x12; // R/W Mask8 + public const int NR13 = 0x13; // W 0 + public const int NR14 = 0x14; // R/W Bit6 + public const int NR21 = 0x16; // R/W Bit7 | Bit6 + public const int NR22 = 0x17; // R/W Mask8 + public const int NR23 = 0x18; // W 0 + public const int NR24 = 0x19; // R/W Bit6 + public const int NR30 = 0x1A; // R/W Bit7 + public const int NR31 = 0x1B; // R/W Mask8 + public const int NR32 = 0x1C; // R/W Bit6 | Bit5 + public const int NR33 = 0x1D; // W 0 + public const int NR34 = 0x1E; // R/W Bit6 + public const int NR41 = 0x20; // R/W Mask6 + public const int NR42 = 0x21; // R/W Mask8 + public const int NR43 = 0x22; // R/W Mask8 + public const int NR44 = 0x23; // R/W Bit6 + public const int NR50 = 0x24; // R/W Mask8 + public const int NR51 = 0x25; // R/W Mask8 + public const int NR52 = 0x26; // R/W Mask8 Mask8 + + public const int WAVE_PATTERN_RAM_START = 0x30; + public const int WAVE_PATTERN_RAM_END = 0x3F; + + // LCD Display Registers + public const int LCDC = 0x40; // R/W Mask8 + public const int STAT = 0x41; // R/W Mask7 + public const int SCY = 0x42; // R/W Mask8 + public const int SCX = 0x43; // R/W Mask8 + public const int LY = 0x44; // R Mask8 zeroed + public const int LYC = 0x45; // R/W Mask8 + public const int DMA = 0x46; // W 0 + public const int BGP = 0x47; // R/W Mask8 + public const int OBP0 = 0x48; // R/W Mask8 + public const int OBP1 = 0x49; // R/W Mask8 + public const int WY = 0x4A; // R/W Mask8 + public const int WX = 0x4B; // R/W Mask8 + + // Boot rom control + public const int BOOT_DISABLE = 0x50; + + private readonly Bus bus; + private readonly Register16 divCounter = new Register16(0xab, 0xcc); + private readonly Register16 dmaAddress = new Register16(); + + private int timerCounter = 0; + private int timerRate = 0; + + private bool dmaTransferActive = false; + + private bool scanP15 = false; + private bool scanP14 = false; + + private bool p15 = true; // misc keys + private bool p14 = true; // direction keys + private bool p13 = true; // down/start + private bool p12 = true; // up/select + private bool p11 = true; // left/b + private bool p10 = true; // right/a + + public IoRegisters(Bus bus) + : base(0x80) { - public const int BASE = 0xFF00; + this.bus = bus; + this.bus.ReadingByte += this.Bus_ReadingByte; + this.bus.WrittenByte += this.Bus_WrittenByte; + } - // Port/Mode Registers - public const int P1 = 0x0; // R/W Mask5 - public const int SB = 0x1; // R/W Mask8 - public const int SC = 0x2; // R/W Bit7 | Bit0 + public event EventHandler DisplayStatusModeUpdated; - // Timer control - public const int DIV = 0x4; // R/W Mask8 - public const int TIMA = 0x5; // R/W Mask8 - public const int TMA = 0x6; // R/W Mask8 - public const int TAC = 0x7; // R/W Mask3 + public bool BootRomDisabled { get; private set; } = false; - // Interrupt Flags - public const int IF = 0xF; // R/W Mask5 - public const int IE = 0xFF; // R/W Mask5 + public bool BootRomEnabled => !this.BootRomDisabled; - // Sound Registers - public const int NR10 = 0x10; // R/W Mask7 - public const int NR11 = 0x11; // R/W Bit7 | Bit6 - public const int NR12 = 0x12; // R/W Mask8 - public const int NR13 = 0x13; // W 0 - public const int NR14 = 0x14; // R/W Bit6 - public const int NR21 = 0x16; // R/W Bit7 | Bit6 - public const int NR22 = 0x17; // R/W Mask8 - public const int NR23 = 0x18; // W 0 - public const int NR24 = 0x19; // R/W Bit6 - public const int NR30 = 0x1A; // R/W Bit7 - public const int NR31 = 0x1B; // R/W Mask8 - public const int NR32 = 0x1C; // R/W Bit6 | Bit5 - public const int NR33 = 0x1D; // W 0 - public const int NR34 = 0x1E; // R/W Bit6 - public const int NR41 = 0x20; // R/W Mask6 - public const int NR42 = 0x21; // R/W Mask8 - public const int NR43 = 0x22; // R/W Mask8 - public const int NR44 = 0x23; // R/W Bit6 - public const int NR50 = 0x24; // R/W Mask8 - public const int NR51 = 0x25; // R/W Mask8 - public const int NR52 = 0x26; // R/W Mask8 Mask8 + public int TimerClock => this.Peek((ushort)TAC) & (byte)Mask.Mask2; - public const int WAVE_PATTERN_RAM_START = 0x30; - public const int WAVE_PATTERN_RAM_END = 0x3F; + public bool TimerEnabled => !this.TimerDisabled; - // LCD Display Registers - public const int LCDC = 0x40; // R/W Mask8 - public const int STAT = 0x41; // R/W Mask7 - public const int SCY = 0x42; // R/W Mask8 - public const int SCX = 0x43; // R/W Mask8 - public const int LY = 0x44; // R Mask8 zeroed - public const int LYC = 0x45; // R/W Mask8 - public const int DMA = 0x46; // W 0 - public const int BGP = 0x47; // R/W Mask8 - public const int OBP0 = 0x48; // R/W Mask8 - public const int OBP1 = 0x49; // R/W Mask8 - public const int WY = 0x4A; // R/W Mask8 - public const int WX = 0x4B; // R/W Mask8 + public bool TimerDisabled => (this.Peek((ushort)TAC) & (byte)Bits.Bit2) == 0; - // Boot rom control - public const int BOOT_DISABLE = 0x50; - - private readonly Bus bus; - private readonly Register16 divCounter = new Register16(0xab, 0xcc); - private readonly Register16 dmaAddress = new Register16(); - - private int timerCounter = 0; - private int timerRate = 0; - - private bool dmaTransferActive = false; - - private bool scanP15 = false; - private bool scanP14 = false; - - private bool p15 = true; // misc keys - private bool p14 = true; // direction keys - private bool p13 = true; // down/start - private bool p12 = true; // up/select - private bool p11 = true; // left/b - private bool p10 = true; // right/a - - public IoRegisters(Bus bus) - : base(0x80) + public int TimerClockTicks + { + get { - this.bus = bus; - this.bus.ReadingByte += this.Bus_ReadingByte; - this.bus.WrittenByte += this.Bus_WrittenByte; - } - - public event EventHandler DisplayStatusModeUpdated; - - public bool BootRomDisabled { get; private set; } = false; - - public bool BootRomEnabled => !this.BootRomDisabled; - - public int TimerClock => this.Peek((ushort)TAC) & (byte)Mask.Mask2; - - public bool TimerEnabled => !this.TimerDisabled; - - public bool TimerDisabled => (this.Peek((ushort)TAC) & (byte)Bits.Bit2) == 0; - - public int TimerClockTicks - { - get + switch (this.TimerClock) { - switch (this.TimerClock) - { - case 0b00: - return 1024; // 4.096 Khz - case 0b01: - return 16; // 262.144 Khz - case 0b10: - return 64; // 65.536 Khz - case 0b11: - return 256; // 16.384 Khz - } - - throw new InvalidOperationException("Invalid timer clock specification"); - } - } - - public void Reset() - { - this.Poke((ushort)NR52, 0xf1); - this.Poke((ushort)LCDC, (byte)(LcdcControl.DisplayBackground | LcdcControl.BackgroundCharacterDataSelection | LcdcControl.LcdEnable)); - this.divCounter.Word = 0xabcc; - this.timerCounter = 0; - } - - public void TransferDma() - { - if (this.dmaTransferActive) - { - this.bus.OAMRAM.Poke(this.dmaAddress.Low, this.bus.Peek(this.dmaAddress)); - this.dmaTransferActive = ++this.dmaAddress.Low < 0xa0; - } - } - - public void TriggerInterrupt(Interrupts cause) => this.Poke((ushort)IF, (byte)(this.Peek((ushort)IF) | (byte)cause)); - - public void CheckTimers(int cycles) - { - this.IncrementDIV(cycles); - this.CheckTimer(cycles); - } - - public void IncrementDIV(int cycles) - { - this.divCounter.Word += (ushort)cycles; - this.Poke((ushort)DIV, this.divCounter.High); - } - - public void IncrementTIMA() - { - var updated = this.Peek((ushort)TIMA) + 1; - if ((updated & (int)Bits.Bit8) != 0) - { - this.TriggerInterrupt(Interrupts.TimerOverflow); - updated = this.Peek((ushort)TMA); + case 0b00: + return 1024; // 4.096 Khz + case 0b01: + return 16; // 262.144 Khz + case 0b10: + return 64; // 65.536 Khz + case 0b11: + return 256; // 16.384 Khz } - this.Poke((ushort)TIMA, Chip.LowByte(updated)); + throw new InvalidOperationException("Invalid timer clock specification"); + } + } + + public void Reset() + { + this.Poke((ushort)NR52, 0xf1); + this.Poke((ushort)LCDC, (byte)(LcdcControl.DisplayBackground | LcdcControl.BackgroundCharacterDataSelection | LcdcControl.LcdEnable)); + this.divCounter.Word = 0xabcc; + this.timerCounter = 0; + } + + public void TransferDma() + { + if (this.dmaTransferActive) + { + this.bus.OAMRAM.Poke(this.dmaAddress.Low, this.bus.Peek(this.dmaAddress)); + this.dmaTransferActive = ++this.dmaAddress.Low < 0xa0; + } + } + + public void TriggerInterrupt(Interrupts cause) => this.Poke((ushort)IF, (byte)(this.Peek((ushort)IF) | (byte)cause)); + + public void CheckTimers(int cycles) + { + this.IncrementDIV(cycles); + this.CheckTimer(cycles); + } + + public void IncrementDIV(int cycles) + { + this.divCounter.Word += (ushort)cycles; + this.Poke((ushort)DIV, this.divCounter.High); + } + + public void IncrementTIMA() + { + var updated = this.Peek((ushort)TIMA) + 1; + if ((updated & (int)Bits.Bit8) != 0) + { + this.TriggerInterrupt(Interrupts.TimerOverflow); + updated = this.Peek((ushort)TMA); } - public void IncrementLY() => this.Poke((ushort)LY, (byte)((this.Peek((ushort)LY) + 1) % GameBoy.Bus.TotalLineCount)); + this.Poke((ushort)TIMA, Chip.LowByte(updated)); + } - public void ResetLY() => this.Poke((ushort)LY, 0); + public void IncrementLY() => this.Poke((ushort)LY, (byte)((this.Peek((ushort)LY) + 1) % GameBoy.Bus.TotalLineCount)); - public void UpdateLcdStatusMode(LcdStatusMode mode) + public void ResetLY() => this.Poke((ushort)LY, 0); + + public void UpdateLcdStatusMode(LcdStatusMode mode) + { + var current = this.Peek((ushort)STAT) & unchecked((byte)~Mask.Mask2); + this.Poke((ushort)STAT, (byte)(current | (int)mode)); + this.OnDisplayStatusModeUpdated(mode); + } + + public void DisableBootRom() => this.BootRomDisabled = true; + + public void EnableBootRom() => this.BootRomDisabled = false; + + public void PressRight() + { + this.p14 = this.p10 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseRight() => this.p14 = this.p10 = true; + + public void PressLeft() + { + this.p14 = this.p11 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseLeft() => this.p14 = this.p11 = true; + + public void PressUp() + { + this.p14 = this.p12 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseUp() => this.p14 = this.p12 = true; + + public void PressDown() + { + this.p14 = this.p13 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseDown() => this.p14 = this.p13 = true; + + public void PressA() + { + this.p15 = this.p10 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseA() => this.p15 = this.p10 = true; + + public void PressB() + { + this.p15 = this.p11 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseB() => this.p15 = this.p11 = true; + + public void PressSelect() + { + this.p15 = this.p12 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseSelect() => this.p15 = this.p12 = true; + + public void PressStart() + { + this.p15 = this.p13 = false; + this.TriggerKeypadInterrupt(); + } + + public void ReleaseStart() => this.p15 = this.p13 = true; + + private void OnDisplayStatusModeUpdated(LcdStatusMode mode) => this.DisplayStatusModeUpdated?.Invoke(this, new LcdStatusModeEventArgs(mode)); + + private void CheckTimer(int cycles) + { + if (this.TimerEnabled) { - var current = this.Peek((ushort)STAT) & unchecked((byte)~Mask.Mask2); - this.Poke((ushort)STAT, (byte)(current | (int)mode)); - this.OnDisplayStatusModeUpdated(mode); - } - - public void DisableBootRom() => this.BootRomDisabled = true; - - public void EnableBootRom() => this.BootRomDisabled = false; - - public void PressRight() - { - this.p14 = this.p10 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseRight() => this.p14 = this.p10 = true; - - public void PressLeft() - { - this.p14 = this.p11 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseLeft() => this.p14 = this.p11 = true; - - public void PressUp() - { - this.p14 = this.p12 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseUp() => this.p14 = this.p12 = true; - - public void PressDown() - { - this.p14 = this.p13 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseDown() => this.p14 = this.p13 = true; - - public void PressA() - { - this.p15 = this.p10 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseA() => this.p15 = this.p10 = true; - - public void PressB() - { - this.p15 = this.p11 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseB() => this.p15 = this.p11 = true; - - public void PressSelect() - { - this.p15 = this.p12 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseSelect() => this.p15 = this.p12 = true; - - public void PressStart() - { - this.p15 = this.p13 = false; - this.TriggerKeypadInterrupt(); - } - - public void ReleaseStart() => this.p15 = this.p13 = true; - - private void OnDisplayStatusModeUpdated(LcdStatusMode mode) => this.DisplayStatusModeUpdated?.Invoke(this, new LcdStatusModeEventArgs(mode)); - - private void CheckTimer(int cycles) - { - if (this.TimerEnabled) + this.timerCounter -= cycles; + if (this.timerCounter <= 0) { - this.timerCounter -= cycles; - if (this.timerCounter <= 0) - { - this.timerCounter += this.timerRate; - this.IncrementTIMA(); - } + this.timerCounter += this.timerRate; + this.IncrementTIMA(); } } + } - private void ApplyMask(ushort address, byte masking) => this.Poke(address, (byte)(this.Peek(address) | ~masking)); + private void ApplyMask(ushort address, byte masking) => this.Poke(address, (byte)(this.Peek(address) | ~masking)); - private void TriggerKeypadInterrupt() => this.TriggerInterrupt(Interrupts.KeypadPressed); + private void TriggerKeypadInterrupt() => this.TriggerInterrupt(Interrupts.KeypadPressed); - private void Bus_WrittenByte(object sender, System.EventArgs e) + private void Bus_WrittenByte(object sender, System.EventArgs e) + { + var address = this.bus.Address.Word; + var value = this.bus.Data; + var port = (ushort)(address - BASE); + + switch (port) + { + case P1: + this.scanP14 = (value & (byte)Bits.Bit4) == 0; + this.scanP15 = (value & (byte)Bits.Bit5) == 0; + break; + + case SB: // R/W + case SC: // R/W + break; + + case DIV: // R/W + this.Poke(port, 0); + this.timerCounter = this.divCounter.Word = 0; + break; + case TIMA: // R/W + case TMA: // R/W + break; + case TAC: // R/W + this.timerRate = this.TimerClockTicks; + break; + + case IF: // R/W + break; + + case LCDC: + case STAT: + case SCY: + case SCX: + break; + case DMA: + this.dmaAddress.Word = Chip.PromoteByte(value); + this.dmaTransferActive = true; + break; + case LY: // R/O + this.Poke(port, 0); + break; + case BGP: + case OBP0: + case OBP1: + case WY: + case WX: + break; + + case BOOT_DISABLE: + this.BootRomDisabled = value != 0; + break; + } + } + + private void Bus_ReadingByte(object sender, System.EventArgs e) + { + var address = this.bus.Address.Word; + var io = (address >= BASE) && (address < 0xff80); + if (io) { - var address = this.bus.Address.Word; - var value = this.bus.Data; var port = (ushort)(address - BASE); - switch (port) { - case P1: - this.scanP14 = (value & (byte)Bits.Bit4) == 0; - this.scanP15 = (value & (byte)Bits.Bit5) == 0; - break; + // Port/Mode Registers + case P1: + { + var p14 = this.scanP14 && !this.p14; + var p15 = this.scanP15 && !this.p15; + var live = p14 || p15; + var p10 = live && this.p10 ? 1 : 0; + var p11 = live && this.p11 ? 1 : 0; + var p12 = live && this.p12 ? 1 : 0; + var p13 = live && this.p13 ? 1 : 0; + this.Poke( + port, + (byte)(p10 | (p11 << 1) | (p12 << 2) | (p13 << 3) | (int)(Bits.Bit4 | Bits.Bit5 | Bits.Bit6 | Bits.Bit7))); + } - case SB: // R/W - case SC: // R/W - break; + break; + case SB: + break; + case SC: + this.ApplyMask(port, (byte)(Bits.Bit7 | Bits.Bit0)); + break; - case DIV: // R/W - this.Poke(port, 0); - this.timerCounter = this.divCounter.Word = 0; - break; - case TIMA: // R/W - case TMA: // R/W - break; - case TAC: // R/W - this.timerRate = this.TimerClockTicks; - break; + // Timer control + case DIV: + case TIMA: + case TMA: + break; + case TAC: + this.ApplyMask(port, (byte)Mask.Mask3); + break; - case IF: // R/W - break; + // Interrupt Flags + case IF: + this.ApplyMask(port, (byte)Mask.Mask5); + break; - case LCDC: - case STAT: - case SCY: - case SCX: - break; - case DMA: - this.dmaAddress.Word = Chip.PromoteByte(value); - this.dmaTransferActive = true; - break; - case LY: // R/O - this.Poke(port, 0); - break; - case BGP: - case OBP0: - case OBP1: - case WY: - case WX: - break; + // LCD Display Registers + case LCDC: + break; + case STAT: + this.ApplyMask(port, (byte)Mask.Mask7); + break; + case SCY: + case SCX: + case LY: + case LYC: + case DMA: + case BGP: + case OBP0: + case OBP1: + case WY: + case WX: + break; - case BOOT_DISABLE: - this.BootRomDisabled = value != 0; - break; - } - } - - private void Bus_ReadingByte(object sender, System.EventArgs e) - { - var address = this.bus.Address.Word; - var io = (address >= BASE) && (address < 0xff80); - if (io) - { - var port = (ushort)(address - BASE); - switch (port) - { - // Port/Mode Registers - case P1: - { - var p14 = this.scanP14 && !this.p14; - var p15 = this.scanP15 && !this.p15; - var live = p14 || p15; - var p10 = live && this.p10 ? 1 : 0; - var p11 = live && this.p11 ? 1 : 0; - var p12 = live && this.p12 ? 1 : 0; - var p13 = live && this.p13 ? 1 : 0; - this.Poke( - port, - (byte)(p10 | (p11 << 1) | (p12 << 2) | (p13 << 3) | (int)(Bits.Bit4 | Bits.Bit5 | Bits.Bit6 | Bits.Bit7))); - } - - break; - case SB: - break; - case SC: - this.ApplyMask(port, (byte)(Bits.Bit7 | Bits.Bit0)); - break; - - // Timer control - case DIV: - case TIMA: - case TMA: - break; - case TAC: - this.ApplyMask(port, (byte)Mask.Mask3); - break; - - // Interrupt Flags - case IF: - this.ApplyMask(port, (byte)Mask.Mask5); - break; - - // LCD Display Registers - case LCDC: - break; - case STAT: - this.ApplyMask(port, (byte)Mask.Mask7); - break; - case SCY: - case SCX: - case LY: - case LYC: - case DMA: - case BGP: - case OBP0: - case OBP1: - case WY: - case WX: - break; - - default: - this.ApplyMask(port, 0); - break; - } + default: + this.ApplyMask(port, 0); + break; } } } diff --git a/LR35902/LR35902.BlarggTest/Program.cs b/LR35902/LR35902.BlarggTest/Program.cs index 4f2d819..5c867d4 100644 --- a/LR35902/LR35902.BlarggTest/Program.cs +++ b/LR35902/LR35902.BlarggTest/Program.cs @@ -11,26 +11,27 @@ namespace LR35902.BlarggTest var configuration = new Configuration(); #if DEBUG - configuration.DebugMode = true; + //configuration.DebugMode = true; #endif + //configuration.DebugMode = true; var computer = new Computer(configuration); computer.Plug("blargg/cpu_instrs.gb"); // Passed - ////computer.plug("blargg/01-special.gb"); // Passed - ////computer.plug("blargg/02-interrupts.gb"); // Passed - ////computer.plug("blargg/03-op sp,hl.gb"); // Passed - ////computer.plug("blargg/04-op r,imm.gb"); // Passed - ////computer.plug("blargg/05-op rp.gb"); // Passed - ////computer.plug("blargg/06-ld r,r.gb"); // Passed - ////computer.plug("blargg/07-jr,jp,call,ret,rst.gb"); // Passed - ////computer.plug("blargg/08-misc instrs.gb"); // Passed - ////computer.plug("blargg/09-op r,r.gb"); // Passed - ////computer.plug("blargg/10-bit ops.gb"); // Passed - ////computer.plug("blargg/11-op a,(hl).gb"); // Passed + ////computer.Plug("blargg/01-special.gb"); // Passed + ////computer.Plug("blargg/02-interrupts.gb"); // Passed + ////computer.Plug("blargg/03-op sp,hl.gb"); // Passed + ////computer.Plug("blargg/04-op r,imm.gb"); // Passed + ////computer.Plug("blargg/05-op rp.gb"); // Passed + ////computer.Plug("blargg/06-ld r,r.gb"); // Passed + ////computer.Plug("blargg/07-jr,jp,call,ret,rst.gb"); // Passed + ////computer.Plug("blargg/08-misc instrs.gb"); // Passed + ////computer.Plug("blargg/09-op r,r.gb"); // Passed + ////computer.Plug("blargg/10-bit ops.gb"); // Passed + ////computer.Plug("blargg/11-op a,(hl).gb"); // Passed - ////computer.plug("blargg/instr_timing.gb"); // Failed #255 - ////computer.plug("blargg/interrupt_time.gb"); // Failed + ////computer.Plug("blargg/instr_timing.gb"); // Failed #255 + ////computer.Plug("blargg/interrupt_time.gb"); // Failed computer.RaisePOWER(); computer.Run(); diff --git a/LR35902/LR35902.cs b/LR35902/LR35902.cs index 02f77de..9d4060a 100644 --- a/LR35902/LR35902.cs +++ b/LR35902/LR35902.cs @@ -2,1137 +2,1134 @@ // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + using System; + + public class LR35902 : IntelProcessor { - using System; + private readonly Bus bus; + private readonly Register16 af = new Register16((int)Mask.Mask16); + private bool prefixCB = false; - public class LR35902 : IntelProcessor + public LR35902(Bus bus) + : base(bus) => this.bus = bus; + + public event EventHandler ExecutingInstruction; + + public event EventHandler ExecutedInstruction; + + public int ClockCycles => this.Cycles * 4; + + public override Register16 AF { - private readonly Bus bus; - private readonly Register16 af = new Register16((int)Mask.Mask16); - private bool prefixCB = false; - - public LR35902(Bus bus) - : base(bus) => this.bus = bus; - - public event EventHandler ExecutingInstruction; - - public event EventHandler ExecutedInstruction; - - public int ClockCycles => this.Cycles * 4; - - public override Register16 AF + get { - get - { - this.af.Low = (byte)HigherNibble(this.af.Low); - return this.af; - } + this.af.Low = (byte)HigherNibble(this.af.Low); + return this.af; + } + } + + public override Register16 BC { get; } = new Register16((int)Mask.Mask16); + + public override Register16 DE { get; } = new Register16((int)Mask.Mask16); + + public override Register16 HL { get; } = new Register16((int)Mask.Mask16); + + private bool IME { get; set; } = false; + + private bool Stopped { get; set; } = false; + + public override int Execute() + { + var decoded = this.GetDecodedOpCode(this.OpCode); + + var x = decoded.X; + var y = decoded.Y; + var z = decoded.Z; + + var p = decoded.P; + var q = decoded.Q; + + if (this.prefixCB) + { + this.ExecuteCB(x, y, z); + } + else + { + this.ExecuteOther(x, y, z, p, q); } - public override Register16 BC { get; } = new Register16((int)Mask.Mask16); + return this.ClockCycles; + } - public override Register16 DE { get; } = new Register16((int)Mask.Mask16); - - public override Register16 HL { get; } = new Register16((int)Mask.Mask16); - - private bool IME { get; set; } = false; - - private bool Stopped { get; set; } = false; - - public override int Execute() + public override int Step() + { + this.OnExecutingInstruction(); + this.prefixCB = false; + this.ResetCycles(); + if (this.Powered) { - var decoded = this.GetDecodedOpCode(this.OpCode); + var interruptEnable = this.Bus.Peek(IoRegisters.BASE + IoRegisters.IE); + var interruptFlags = this.bus.IO.Peek(IoRegisters.IF); - var x = decoded.X; - var y = decoded.Y; - var z = decoded.Z; - - var p = decoded.P; - var q = decoded.Q; - - if (this.prefixCB) + var masked = interruptEnable & interruptFlags; + if (masked != 0) { - this.ExecuteCB(x, y, z); - } - else - { - this.ExecuteOther(x, y, z, p, q); - } - - return this.ClockCycles; - } - - public override int Step() - { - this.OnExecutingInstruction(); - this.prefixCB = false; - this.ResetCycles(); - if (this.Powered) - { - var interruptEnable = this.Bus.Peek(IoRegisters.BASE + IoRegisters.IE); - var interruptFlags = this.bus.IO.Peek(IoRegisters.IF); - - var masked = interruptEnable & interruptFlags; - if (masked != 0) + if (this.IME) { - if (this.IME) - { - this.bus.IO.Poke(IoRegisters.IF, 0); - this.LowerINT(); - var index = Chip.FindFirstSet(masked); - this.Bus.Data = (byte)(0x38 + (index << 3)); - } - else - { - if (this.Halted) - { - this.Proceed(); - } - } - } - - if (this.RESET.Lowered()) - { - this.HandleRESET(); - } - else if (this.INT.Lowered()) - { - this.HandleINT(); - } - else if (this.Halted) - { - this.Execute(0); // NOP + this.bus.IO.Poke(IoRegisters.IF, 0); + this.LowerINT(); + var index = Chip.FindFirstSet(masked); + this.Bus.Data = (byte)(0x38 + (index << 3)); } else { - this.Execute(this.FetchByte()); + if (this.Halted) + { + this.Proceed(); + } } - - this.bus.IO.CheckTimers(this.ClockCycles); - this.bus.IO.TransferDma(); } - this.OnExecutedInstruction(); - return this.ClockCycles; - } - - protected virtual void OnExecutingInstruction() => this.ExecutingInstruction?.Invoke(this, EventArgs.Empty); - - protected virtual void OnExecutedInstruction() => this.ExecutedInstruction?.Invoke(this, EventArgs.Empty); - - protected override void HandleRESET() - { - base.HandleRESET(); - this.DI(); - this.SP.Word = (ushort)(Mask.Mask16 - 1); - this.Tick(4); - } - - protected override void HandleINT() - { - base.HandleINT(); - this.RaiseHALT(); - this.DI(); - this.Restart(this.Bus.Data); - this.Tick(4); - } - - private static byte SetBit(byte f, StatusBits flag) => SetBit(f, (byte)flag); - - private static byte SetBit(byte f, StatusBits flag, int condition) => SetBit(f, (byte)flag, condition); - - private static byte SetBit(byte f, StatusBits flag, bool condition) => SetBit(f, (byte)flag, condition); - - private static byte ClearBit(byte f, StatusBits flag) => ClearBit(f, (byte)flag); - - private static byte ClearBit(byte f, StatusBits flag, int condition) => ClearBit(f, (byte)flag, condition); - - private static byte AdjustZero(byte input, byte value) => ClearBit(input, StatusBits.ZF, value); - - private static byte AdjustHalfCarryAdd(byte input, byte before, byte value, int calculation) => SetBit(input, StatusBits.HC, CalculateHalfCarryAdd(before, value, calculation)); - - private static byte AdjustHalfCarrySub(byte input, byte before, byte value, int calculation) => SetBit(input, StatusBits.HC, CalculateHalfCarrySub(before, value, calculation)); - - private static byte Res(int n, byte operand) => (byte)(operand & ~(1 << n)); - - private static byte Set(int n, byte operand) => (byte)(operand | (1 << n)); - - private void DI() => this.IME = false; - - private void EI() => this.IME = true; - - private void Stop() => this.Stopped = true; - - private void Start() => this.Stopped = false; - - private byte R(int r) - { - switch (r) + if (this.RESET.Lowered()) { - case 0: - return this.B; - case 1: - return this.C; - case 2: - return this.D; - case 3: - return this.E; - case 4: - return this.H; - case 5: - return this.L; - case 6: - return this.BusRead(this.HL.Word); - case 7: - return this.A; - default: - throw new ArgumentOutOfRangeException(nameof(r)); + this.HandleRESET(); } - } - - private void R(int r, byte value) - { - switch (r) + else if (this.INT.Lowered()) { - case 0: - this.B = value; - break; - case 1: - this.C = value; - break; - case 2: - this.D = value; - break; - case 3: - this.E = value; - break; - case 4: - this.H = value; - break; - case 5: - this.L = value; - break; - case 6: - this.BusWrite(this.HL.Word, value); - break; - case 7: - this.A = value; - break; - default: - throw new ArgumentOutOfRangeException(nameof(r)); + this.HandleINT(); } - } - - private Register16 RP(int rp) - { - switch (rp) + else if (this.Halted) { - case 0: - return this.BC; - case 1: - return this.DE; - case 2: - return this.HL; - case 3: - return this.SP; - default: - throw new ArgumentOutOfRangeException(nameof(rp)); - } - } - - private Register16 RP2(int rp) - { - switch (rp) - { - case 0: - return this.BC; - case 1: - return this.DE; - case 2: - return this.HL; - case 3: - return this.AF; - default: - throw new ArgumentOutOfRangeException(nameof(rp)); - } - } - - private void ExecuteCB(int x, int y, int z) - { - switch (x) - { - case 0: // rot[y] r[z] - { - var operand = this.R(z); - switch (y) - { - case 0: - operand = this.RLC(operand); - break; - case 1: - operand = this.RRC(operand); - break; - case 2: - operand = this.RL(operand); - break; - case 3: - operand = this.RR(operand); - break; - case 4: - operand = this.SLA(operand); - break; - case 5: - operand = this.SRA(operand); - break; - case 6: // GB: SWAP r - operand = this.Swap(operand); - break; - case 7: - operand = this.SRL(operand); - break; - default: - throw new InvalidOperationException("Unreachable code block reached"); - } - - this.Tick(2); - this.R(z, operand); - this.F = AdjustZero(this.F, operand); - if (z == 6) - { - this.Tick(2); - } - - break; - } - - case 1: // BIT y, r[z] - this.Bit(y, this.R(z)); - this.Tick(2); - if (z == 6) - { - this.Tick(2); - } - - break; - - case 2: // RES y, r[z] - this.R(z, Res(y, this.R(z))); - this.Tick(2); - if (z == 6) - { - this.Tick(2); - } - - break; - - case 3: // SET y, r[z] - this.R(z, Set(y, this.R(z))); - this.Tick(2); - if (z == 6) - { - this.Tick(2); - } - - break; - - default: - throw new InvalidOperationException("Unreachable code block reached"); - } - } - - private void ExecuteOther(int x, int y, int z, int p, int q) - { - switch (x) - { - case 0: - switch (z) - { - case 0: // Relative jumps and assorted ops - switch (y) - { - case 0: // NOP - this.Tick(); - break; - case 1: // GB: LD (nn),SP - this.Bus.Address.Word = this.FetchWord().Word; - this.SetWord(this.SP); - this.Tick(5); - break; - case 2: // GB: STOP - this.Stop(); - this.Tick(); - break; - case 3: // JR d - this.JumpRelative((sbyte)this.FetchByte()); - this.Tick(4); - break; - case 4: // JR cc,d - case 5: - case 6: - case 7: - if (this.JumpRelativeConditionalFlag(y - 4)) - { - this.Tick(); - } - - this.Tick(2); - break; - default: - throw new InvalidOperationException("Unreachable code block reached"); - } - - break; - - case 1: // 16-bit load immediate/add - switch (q) - { - case 0: // LD rp,nn - this.RP(p).Word = this.FetchWord().Word; - this.Tick(3); - break; - - case 1: // ADD HL,rp - this.Add(this.HL, this.RP(p)); - this.Tick(2); - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - case 2: // Indirect loading - switch (q) - { - case 0: - switch (p) - { - case 0: // LD (BC),A - this.BusWrite(this.BC, this.A); - this.Tick(2); - break; - - case 1: // LD (DE),A - this.BusWrite(this.DE, this.A); - this.Tick(2); - break; - - case 2: // GB: LDI (HL),A - this.BusWrite(this.HL.Word++, this.A); - this.Tick(2); - break; - - case 3: // GB: LDD (HL),A - this.BusWrite(this.HL.Word--, this.A); - this.Tick(2); - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - case 1: - switch (p) - { - case 0: // LD A,(BC) - this.A = this.BusRead(this.BC); - this.Tick(2); - break; - - case 1: // LD A,(DE) - this.A = this.BusRead(this.DE); - this.Tick(2); - break; - - case 2: // GB: LDI A,(HL) - this.A = this.BusRead(this.HL.Word++); - this.Tick(2); - break; - - case 3: // GB: LDD A,(HL) - this.A = this.BusRead(this.HL.Word--); - this.Tick(2); - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - case 3: // 16-bit INC/DEC - switch (q) - { - case 0: // INC rp - ++this.RP(p).Word; - break; - - case 1: // DEC rp - --this.RP(p).Word; - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - this.Tick(2); - break; - - case 4: // 8-bit INC - this.R(y, this.Increment(this.R(y))); - this.Tick(); - if (y == 6) - { - this.Tick(2); - } - - break; - - case 5: // 8-bit DEC - this.R(y, this.Decrement(this.R(y))); - this.Tick(); - if (y == 6) - { - this.Tick(2); - } - - break; - - case 6: // 8-bit load immediate - this.R(y, this.FetchByte()); - this.Tick(2); - break; - - case 7: // Assorted operations on accumulator/flags - switch (y) - { - case 0: - this.A = this.RLC(this.A); - break; - case 1: - this.A = this.RRC(this.A); - break; - case 2: - this.A = this.RL(this.A); - break; - case 3: - this.A = this.RR(this.A); - break; - case 4: - this.DAA(); - break; - case 5: - this.Cpl(); - break; - case 6: - this.SCF(); - break; - case 7: - this.CCF(); - break; - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - this.Tick(); - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - case 1: // 8-bit loading - if (z == 6 && y == 6) - { - this.Halt(); // Exception (replaces LD (HL), (HL)) - } - else - { - this.R(y, this.R(z)); - if ((y == 6) || (z == 6)) - { - this.Tick(); // M operations - } - } - - this.Tick(); - break; - - case 2: // Operate on accumulator and register/memory location - switch (y) - { - case 0: // ADD A,r - this.A = this.Add(this.A, this.R(z)); - break; - case 1: // ADC A,r - this.A = this.ADC(this.A, this.R(z)); - break; - case 2: // SUB r - this.A = this.Subtract(this.A, this.R(z)); - break; - case 3: // SBC A,r - this.A = this.SBC(this.A, this.R(z)); - break; - case 4: // AND r - this.AndR(this.R(z)); - break; - case 5: // XOR r - this.XorR(this.R(z)); - break; - case 6: // OR r - this.OrR(this.R(z)); - break; - case 7: // CP r - this.Compare(this.R(z)); - break; - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - this.Tick(); - if (z == 6) - { - this.Tick(); - } - - break; - case 3: - switch (z) - { - case 0: // Conditional return - switch (y) - { - case 0: - case 1: - case 2: - case 3: - if (this.ReturnConditionalFlag(y)) - { - this.Tick(3); - } - - this.Tick(2); - break; - - case 4: // GB: LD (FF00 + n),A - this.BusWrite((ushort)(IoRegisters.BASE + this.FetchByte()), this.A); - this.Tick(3); - break; - - case 5: - { // GB: ADD SP,dd - var before = this.SP.Word; - var value = (sbyte)this.FetchByte(); - var result = before + value; - this.SP.Word = (ushort)result; - var carried = before ^ value ^ (result & (int)Mask.Mask16); - this.F = ClearBit(this.F, StatusBits.ZF | StatusBits.NF); - this.F = SetBit(this.F, StatusBits.CF, carried & (int)Bits.Bit8); - this.F = SetBit(this.F, StatusBits.HC, carried & (int)Bits.Bit4); - } - - this.Tick(4); - break; - - case 6: // GB: LD A,(FF00 + n) - this.A = this.BusRead((ushort)(IoRegisters.BASE + this.FetchByte())); - this.Tick(3); - break; - - case 7: - { // GB: LD HL,SP + dd - var before = this.SP.Word; - var value = (sbyte)this.FetchByte(); - var result = before + value; - this.HL.Word = (ushort)result; - var carried = before ^ value ^ (result & (int)Mask.Mask16); - this.F = ClearBit(this.F, StatusBits.ZF | StatusBits.NF); - this.F = SetBit(this.F, StatusBits.CF, carried & (int)Bits.Bit8); - this.F = SetBit(this.F, StatusBits.HC, carried & (int)Bits.Bit4); - } - - this.Tick(3); - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - case 1: // POP & various ops - switch (q) - { - case 0: // POP rp2[p] - this.RP2(p).Word = this.PopWord().Word; - this.Tick(3); - break; - case 1: - switch (p) - { - case 0: // RET - this.Return(); - this.Tick(4); - break; - case 1: // GB: RETI - this.RetI(); - this.Tick(4); - break; - case 2: // JP HL - this.Jump(this.HL.Word); - this.Tick(); - break; - case 3: // LD SP,HL - this.SP.Word = this.HL.Word; - this.Tick(2); - break; - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - case 2: // Conditional jump - switch (y) - { - case 0: - case 1: - case 2: - case 3: - this.JumpConditionalFlag(y); - this.Tick(3); - break; - case 4: // GB: LD (FF00 + C),A - this.BusWrite((ushort)(IoRegisters.BASE + this.C), this.A); - this.Tick(2); - break; - case 5: // GB: LD (nn),A - this.Bus.Address.Word = this.MEMPTR.Word = this.FetchWord().Word; - this.BusWrite(this.A); - this.Tick(4); - break; - case 6: // GB: LD A,(FF00 + C) - this.A = this.BusRead((ushort)(IoRegisters.BASE + this.C)); - this.Tick(2); - break; - case 7: // GB: LD A,(nn) - this.Bus.Address.Word = this.MEMPTR.Word = this.FetchWord().Word; - this.A = this.BusRead(); - this.Tick(4); - break; - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - case 3: // Assorted operations - switch (y) - { - case 0: // JP nn - this.Jump(this.FetchWord().Word); - this.Tick(4); - break; - case 1: // CB prefix - this.prefixCB = true; - this.Execute(this.FetchByte()); - break; - case 6: // DI - this.DI(); - this.Tick(); - break; - case 7: // EI - this.EI(); - this.Tick(); - break; - } - - break; - - case 4: // Conditional call: CALL cc[y], nn - if (this.CallConditionalFlag(y)) - { - this.Tick(3); - } - - this.Tick(3); - break; - - case 5: // PUSH & various ops - switch (q) - { - case 0: // PUSH rp2[p] - this.PushWord(this.RP2(p)); - this.Tick(4); - break; - - case 1: - switch (p) - { - case 0: // CALL nn - this.Call(this.FetchWord().Word); - this.Tick(6); - break; - } - - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - - case 6: // Operate on accumulator and immediate operand: alu[y] n - switch (y) - { - case 0: // ADD A,n - this.A = this.Add(this.A, this.FetchByte()); - break; - case 1: // ADC A,n - this.A = this.ADC(this.A, this.FetchByte()); - break; - case 2: // SUB n - this.A = this.Subtract(this.A, this.FetchByte()); - break; - case 3: // SBC A,n - this.A = this.SBC(this.A, this.FetchByte()); - break; - case 4: // AND n - this.AndR(this.FetchByte()); - break; - case 5: // XOR n - this.XorR(this.FetchByte()); - break; - case 6: // OR n - this.OrR(this.FetchByte()); - break; - case 7: // CP n - this.Compare(this.FetchByte()); - break; - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - this.Tick(2); - break; - - case 7: // Restart: RST y * 8 - this.Restart((byte)(y << 3)); - this.Tick(4); - break; - - default: - throw new InvalidOperationException("Invalid operation mode"); - } - - break; - } - } - - private byte Increment(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF); - this.F = AdjustZero(this.F, ++operand); - this.F = ClearBit(this.F, StatusBits.HC, LowNibble(operand)); - return operand; - } - - private byte Decrement(byte operand) - { - this.F = SetBit(this.F, StatusBits.NF); - this.F = ClearBit(this.F, StatusBits.HC, LowNibble(operand)); - this.F = AdjustZero(this.F, --operand); - return operand; - } - - private bool JumpConditionalFlag(int flag) - { - switch (flag) - { - case 0: // NZ - return this.JumpConditional((this.F & (byte)StatusBits.ZF) == 0); - case 1: // Z - return this.JumpConditional((this.F & (byte)StatusBits.ZF) != 0); - case 2: // NC - return this.JumpConditional((this.F & (byte)StatusBits.CF) == 0); - case 3: // C - return this.JumpConditional((this.F & (byte)StatusBits.CF) != 0); - default: - throw new ArgumentOutOfRangeException(nameof(flag)); - } - } - - private bool JumpRelativeConditionalFlag(int flag) - { - switch (flag) - { - case 0: // NZ - return this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) == 0); - case 1: // Z - return this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) != 0); - case 2: // NC - return this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) == 0); - case 3: // C - return this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) != 0); - default: - throw new ArgumentOutOfRangeException(nameof(flag)); - } - } - - private bool ReturnConditionalFlag(int flag) - { - switch (flag) - { - case 0: // NZ - return this.ReturnConditional((this.F & (byte)StatusBits.ZF) == 0); - case 1: // Z - return this.ReturnConditional((this.F & (byte)StatusBits.ZF) != 0); - case 2: // NC - return this.ReturnConditional((this.F & (byte)StatusBits.CF) == 0); - case 3: // C - return this.ReturnConditional((this.F & (byte)StatusBits.CF) != 0); - default: - throw new ArgumentOutOfRangeException(nameof(flag)); - } - } - - private bool CallConditionalFlag(int flag) - { - switch (flag) - { - case 0: // NZ - return this.CallConditional((this.F & (byte)StatusBits.ZF) == 0); - case 1: // Z - return this.CallConditional((this.F & (byte)StatusBits.ZF) != 0); - case 2: // NC - return this.CallConditional((this.F & (byte)StatusBits.CF) == 0); - case 3: // C - return this.CallConditional((this.F & (byte)StatusBits.CF) != 0); - default: - throw new ArgumentOutOfRangeException(nameof(flag)); - } - } - - private void Add(Register16 operand, Register16 value) - { - this.MEMPTR.Word = operand.Word; - - var result = this.MEMPTR.Word + value.Word; - - operand.Word = (ushort)result; - - this.F = ClearBit(this.F, StatusBits.NF); - this.F = SetBit(this.F, StatusBits.CF, result & (int)Bits.Bit16); - this.F = AdjustHalfCarryAdd(this.F, this.MEMPTR.High, value.High, operand.High); - } - - private byte Add(byte operand, byte value, int carry = 0) - { - this.MEMPTR.Word = (ushort)(operand + value + carry); - - this.F = AdjustHalfCarryAdd(this.F, operand, value, this.MEMPTR.Low); - - operand = this.MEMPTR.Low; - - this.F = ClearBit(this.F, StatusBits.NF); - this.F = SetBit(this.F, StatusBits.CF, this.MEMPTR.Word & (ushort)Bits.Bit8); - this.F = AdjustZero(this.F, operand); - - return operand; - } - - private byte ADC(byte operand, byte value) => this.Add(operand, value, (this.F & (byte)StatusBits.CF) >> 4); - - private byte Subtract(byte operand, byte value, int carry = 0) - { - this.MEMPTR.Word = (ushort)(operand - value - carry); - - this.F = AdjustHalfCarrySub(this.F, operand, value, this.MEMPTR.Low); - - var result = operand = this.MEMPTR.Low; - - this.F = SetBit(this.F, StatusBits.NF); - this.F = SetBit(this.F, StatusBits.CF, this.MEMPTR.High & (byte)Bits.Bit0); - this.F = AdjustZero(this.F, operand); - - return result; - } - - private byte SBC(byte operand, byte value) => this.Subtract(operand, value, (this.F & (byte)StatusBits.CF) >> 4); - - private byte AndR(byte operand, byte value) - { - this.F = SetBit(this.F, StatusBits.HC); - this.F = ClearBit(this.F, StatusBits.CF | StatusBits.NF); - this.F = AdjustZero(this.F, operand &= value); - return operand; - } - - private void AndR(byte value) => this.A = this.AndR(this.A, value); - - private byte XorR(byte operand, byte value) - { - this.F = ClearBit(this.F, StatusBits.HC | StatusBits.CF | StatusBits.NF); - this.F = AdjustZero(this.F, operand ^= value); - return operand; - } - - private void XorR(byte value) => this.A = this.XorR(this.A, value); - - private byte OrR(byte operand, byte value) - { - this.F = ClearBit(this.F, StatusBits.HC | StatusBits.CF | StatusBits.NF); - this.F = AdjustZero(this.F, operand |= value); - return operand; - } - - private void OrR(byte value) => this.A = this.OrR(this.A, value); - - private void Compare(byte value) => this.Subtract(this.A, value); - - private byte RLC(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - var carry = operand & (byte)Bits.Bit7; - this.F = SetBit(this.F, StatusBits.CF, carry); - return (byte)((operand << 1) | (carry >> 7)); - } - - private byte RRC(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - var carry = operand & (byte)Bits.Bit0; - this.F = SetBit(this.F, StatusBits.CF, carry); - return (byte)((operand >> 1) | (carry << 7)); - } - - private byte RL(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - var carry = this.F & (byte)StatusBits.CF; - this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit7); - return (byte)((operand << 1) | (carry >> 4)); // CF at Bit4 - } - - private byte RR(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - var carry = this.F & (byte)StatusBits.CF; - this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit0); - return (byte)((operand >> 1) | (carry << 3)); // CF at Bit4 - } - - private byte SLA(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit7); - return (byte)(operand << 1); - } - - private byte SRA(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit0); - return (byte)((operand >> 1) | (operand & (byte)Bits.Bit7)); - } - - private byte Swap(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.CF); - return (byte)(PromoteNibble(operand) | DemoteNibble(operand)); - } - - private byte SRL(byte operand) - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); - this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit0); - return (byte)((operand >> 1) & ~(byte)Bits.Bit7); - } - - private void Bit(int n, byte operand) - { - var carry = this.F & (byte)StatusBits.CF; - this.AndR(operand, (byte)(1 << n)); - this.F = SetBit(this.F, StatusBits.CF, carry); - } - - private void DAA() - { - int updated = this.A; - - if ((this.F & (byte)StatusBits.NF) != 0) - { - if ((this.F & (byte)StatusBits.HC) != 0) - { - updated = LowByte(updated - 6); - } - - if ((this.F & (byte)StatusBits.CF) != 0) - { - updated -= 0x60; - } + this.Execute(0); // NOP } else { - if (((this.F & (byte)StatusBits.HC) != 0) || LowNibble((byte)updated) > 9) - { - updated += 6; - } - - if (((this.F & (byte)StatusBits.CF) != 0) || updated > 0x9F) - { - updated += 0x60; - } + this.Execute(this.FetchByte()); } - this.F = ClearBit(this.F, (byte)StatusBits.HC | (byte)StatusBits.ZF); - this.F = SetBit(this.F, StatusBits.CF, ((this.F & (byte)StatusBits.CF) != 0) || ((updated & (int)Bits.Bit8) != 0)); - this.A = LowByte(updated); - - this.F = AdjustZero(this.F, this.A); + this.bus.IO.CheckTimers(this.ClockCycles); + this.bus.IO.TransferDma(); } - private void Cpl() + this.OnExecutedInstruction(); + return this.ClockCycles; + } + + protected virtual void OnExecutingInstruction() => this.ExecutingInstruction?.Invoke(this, EventArgs.Empty); + + protected virtual void OnExecutedInstruction() => this.ExecutedInstruction?.Invoke(this, EventArgs.Empty); + + protected override void HandleRESET() + { + base.HandleRESET(); + this.DI(); + this.SP.Word = (ushort)(Mask.Mask16 - 1); + this.Tick(4); + } + + protected override void HandleINT() + { + base.HandleINT(); + this.RaiseHALT(); + this.DI(); + this.Restart(this.Bus.Data); + this.Tick(4); + } + + private static byte SetBit(byte f, StatusBits flag) => SetBit(f, (byte)flag); + + private static byte SetBit(byte f, StatusBits flag, int condition) => SetBit(f, (byte)flag, condition); + + private static byte SetBit(byte f, StatusBits flag, bool condition) => SetBit(f, (byte)flag, condition); + + private static byte ClearBit(byte f, StatusBits flag) => ClearBit(f, (byte)flag); + + private static byte ClearBit(byte f, StatusBits flag, int condition) => ClearBit(f, (byte)flag, condition); + + private static byte AdjustZero(byte input, byte value) => ClearBit(input, StatusBits.ZF, value); + + private static byte AdjustHalfCarryAdd(byte input, byte before, byte value, int calculation) => SetBit(input, StatusBits.HC, CalculateHalfCarryAdd(before, value, calculation)); + + private static byte AdjustHalfCarrySub(byte input, byte before, byte value, int calculation) => SetBit(input, StatusBits.HC, CalculateHalfCarrySub(before, value, calculation)); + + private static byte Res(int n, byte operand) => (byte)(operand & ~(1 << n)); + + private static byte Set(int n, byte operand) => (byte)(operand | (1 << n)); + + private void DI() => this.IME = false; + + private void EI() => this.IME = true; + + private void Stop() => this.Stopped = true; + + private void Start() => this.Stopped = false; + + private byte R(int r) + { + switch (r) { - this.F = SetBit(this.F, StatusBits.HC | StatusBits.NF); - this.A = (byte)~this.A; + case 0: + return this.B; + case 1: + return this.C; + case 2: + return this.D; + case 3: + return this.E; + case 4: + return this.H; + case 5: + return this.L; + case 6: + return this.BusRead(this.HL.Word); + case 7: + return this.A; + default: + throw new ArgumentOutOfRangeException(nameof(r)); + } + } + + private void R(int r, byte value) + { + switch (r) + { + case 0: + this.B = value; + break; + case 1: + this.C = value; + break; + case 2: + this.D = value; + break; + case 3: + this.E = value; + break; + case 4: + this.H = value; + break; + case 5: + this.L = value; + break; + case 6: + this.BusWrite(this.HL.Word, value); + break; + case 7: + this.A = value; + break; + default: + throw new ArgumentOutOfRangeException(nameof(r)); + } + } + + private Register16 RP(int rp) + { + switch (rp) + { + case 0: + return this.BC; + case 1: + return this.DE; + case 2: + return this.HL; + case 3: + return this.SP; + default: + throw new ArgumentOutOfRangeException(nameof(rp)); + } + } + + private Register16 RP2(int rp) + { + switch (rp) + { + case 0: + return this.BC; + case 1: + return this.DE; + case 2: + return this.HL; + case 3: + return this.AF; + default: + throw new ArgumentOutOfRangeException(nameof(rp)); + } + } + + private void ExecuteCB(int x, int y, int z) + { + switch (x) + { + case 0: // rot[y] r[z] + { + var operand = this.R(z); + switch (y) + { + case 0: + operand = this.RLC(operand); + break; + case 1: + operand = this.RRC(operand); + break; + case 2: + operand = this.RL(operand); + break; + case 3: + operand = this.RR(operand); + break; + case 4: + operand = this.SLA(operand); + break; + case 5: + operand = this.SRA(operand); + break; + case 6: // GB: SWAP r + operand = this.Swap(operand); + break; + case 7: + operand = this.SRL(operand); + break; + default: + throw new InvalidOperationException("Unreachable code block reached"); + } + + this.Tick(2); + this.R(z, operand); + this.F = AdjustZero(this.F, operand); + if (z == 6) + { + this.Tick(2); + } + + break; + } + + case 1: // BIT y, r[z] + this.Bit(y, this.R(z)); + this.Tick(2); + if (z == 6) + { + this.Tick(2); + } + + break; + + case 2: // RES y, r[z] + this.R(z, Res(y, this.R(z))); + this.Tick(2); + if (z == 6) + { + this.Tick(2); + } + + break; + + case 3: // SET y, r[z] + this.R(z, Set(y, this.R(z))); + this.Tick(2); + if (z == 6) + { + this.Tick(2); + } + + break; + + default: + throw new InvalidOperationException("Unreachable code block reached"); + } + } + + private void ExecuteOther(int x, int y, int z, int p, int q) + { + switch (x) + { + case 0: + switch (z) + { + case 0: // Relative jumps and assorted ops + switch (y) + { + case 0: // NOP + this.Tick(); + break; + case 1: // GB: LD (nn),SP + this.Bus.Address.Word = this.FetchWord().Word; + this.SetWord(this.SP); + this.Tick(5); + break; + case 2: // GB: STOP + this.Stop(); + this.Tick(); + break; + case 3: // JR d + this.JumpRelative((sbyte)this.FetchByte()); + this.Tick(4); + break; + case 4: // JR cc,d + case 5: + case 6: + case 7: + if (this.JumpRelativeConditionalFlag(y - 4)) + { + this.Tick(); + } + + this.Tick(2); + break; + default: + throw new InvalidOperationException("Unreachable code block reached"); + } + + break; + + case 1: // 16-bit load immediate/add + switch (q) + { + case 0: // LD rp,nn + this.RP(p).Word = this.FetchWord().Word; + this.Tick(3); + break; + + case 1: // ADD HL,rp + this.Add(this.HL, this.RP(p)); + this.Tick(2); + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + case 2: // Indirect loading + switch (q) + { + case 0: + switch (p) + { + case 0: // LD (BC),A + this.BusWrite(this.BC, this.A); + this.Tick(2); + break; + + case 1: // LD (DE),A + this.BusWrite(this.DE, this.A); + this.Tick(2); + break; + + case 2: // GB: LDI (HL),A + this.BusWrite(this.HL.Word++, this.A); + this.Tick(2); + break; + + case 3: // GB: LDD (HL),A + this.BusWrite(this.HL.Word--, this.A); + this.Tick(2); + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + case 1: + switch (p) + { + case 0: // LD A,(BC) + this.A = this.BusRead(this.BC); + this.Tick(2); + break; + + case 1: // LD A,(DE) + this.A = this.BusRead(this.DE); + this.Tick(2); + break; + + case 2: // GB: LDI A,(HL) + this.A = this.BusRead(this.HL.Word++); + this.Tick(2); + break; + + case 3: // GB: LDD A,(HL) + this.A = this.BusRead(this.HL.Word--); + this.Tick(2); + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + case 3: // 16-bit INC/DEC + switch (q) + { + case 0: // INC rp + ++this.RP(p).Word; + break; + + case 1: // DEC rp + --this.RP(p).Word; + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + this.Tick(2); + break; + + case 4: // 8-bit INC + this.R(y, this.Increment(this.R(y))); + this.Tick(); + if (y == 6) + { + this.Tick(2); + } + + break; + + case 5: // 8-bit DEC + this.R(y, this.Decrement(this.R(y))); + this.Tick(); + if (y == 6) + { + this.Tick(2); + } + + break; + + case 6: // 8-bit load immediate + this.R(y, this.FetchByte()); + this.Tick(2); + break; + + case 7: // Assorted operations on accumulator/flags + switch (y) + { + case 0: + this.A = this.RLC(this.A); + break; + case 1: + this.A = this.RRC(this.A); + break; + case 2: + this.A = this.RL(this.A); + break; + case 3: + this.A = this.RR(this.A); + break; + case 4: + this.DAA(); + break; + case 5: + this.Cpl(); + break; + case 6: + this.SCF(); + break; + case 7: + this.CCF(); + break; + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + this.Tick(); + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + case 1: // 8-bit loading + if (z == 6 && y == 6) + { + this.Halt(); // Exception (replaces LD (HL), (HL)) + } + else + { + this.R(y, this.R(z)); + if ((y == 6) || (z == 6)) + { + this.Tick(); // M operations + } + } + + this.Tick(); + break; + + case 2: // Operate on accumulator and register/memory location + switch (y) + { + case 0: // ADD A,r + this.A = this.Add(this.A, this.R(z)); + break; + case 1: // ADC A,r + this.A = this.ADC(this.A, this.R(z)); + break; + case 2: // SUB r + this.A = this.Subtract(this.A, this.R(z)); + break; + case 3: // SBC A,r + this.A = this.SBC(this.A, this.R(z)); + break; + case 4: // AND r + this.AndR(this.R(z)); + break; + case 5: // XOR r + this.XorR(this.R(z)); + break; + case 6: // OR r + this.OrR(this.R(z)); + break; + case 7: // CP r + this.Compare(this.R(z)); + break; + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + this.Tick(); + if (z == 6) + { + this.Tick(); + } + + break; + case 3: + switch (z) + { + case 0: // Conditional return + switch (y) + { + case 0: + case 1: + case 2: + case 3: + if (this.ReturnConditionalFlag(y)) + { + this.Tick(3); + } + + this.Tick(2); + break; + + case 4: // GB: LD (FF00 + n),A + this.BusWrite((ushort)(IoRegisters.BASE + this.FetchByte()), this.A); + this.Tick(3); + break; + + case 5: + { // GB: ADD SP,dd + var before = this.SP.Word; + var value = (sbyte)this.FetchByte(); + var result = before + value; + this.SP.Word = (ushort)result; + var carried = before ^ value ^ (result & (int)Mask.Mask16); + this.F = ClearBit(this.F, StatusBits.ZF | StatusBits.NF); + this.F = SetBit(this.F, StatusBits.CF, carried & (int)Bits.Bit8); + this.F = SetBit(this.F, StatusBits.HC, carried & (int)Bits.Bit4); + } + + this.Tick(4); + break; + + case 6: // GB: LD A,(FF00 + n) + this.A = this.BusRead((ushort)(IoRegisters.BASE + this.FetchByte())); + this.Tick(3); + break; + + case 7: + { // GB: LD HL,SP + dd + var before = this.SP.Word; + var value = (sbyte)this.FetchByte(); + var result = before + value; + this.HL.Word = (ushort)result; + var carried = before ^ value ^ (result & (int)Mask.Mask16); + this.F = ClearBit(this.F, StatusBits.ZF | StatusBits.NF); + this.F = SetBit(this.F, StatusBits.CF, carried & (int)Bits.Bit8); + this.F = SetBit(this.F, StatusBits.HC, carried & (int)Bits.Bit4); + } + + this.Tick(3); + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + case 1: // POP & various ops + switch (q) + { + case 0: // POP rp2[p] + this.RP2(p).Word = this.PopWord().Word; + this.Tick(3); + break; + case 1: + switch (p) + { + case 0: // RET + this.Return(); + this.Tick(4); + break; + case 1: // GB: RETI + this.RetI(); + this.Tick(4); + break; + case 2: // JP HL + this.Jump(this.HL.Word); + this.Tick(); + break; + case 3: // LD SP,HL + this.SP.Word = this.HL.Word; + this.Tick(2); + break; + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + case 2: // Conditional jump + switch (y) + { + case 0: + case 1: + case 2: + case 3: + this.JumpConditionalFlag(y); + this.Tick(3); + break; + case 4: // GB: LD (FF00 + C),A + this.BusWrite((ushort)(IoRegisters.BASE + this.C), this.A); + this.Tick(2); + break; + case 5: // GB: LD (nn),A + this.Bus.Address.Word = this.MEMPTR.Word = this.FetchWord().Word; + this.BusWrite(this.A); + this.Tick(4); + break; + case 6: // GB: LD A,(FF00 + C) + this.A = this.BusRead((ushort)(IoRegisters.BASE + this.C)); + this.Tick(2); + break; + case 7: // GB: LD A,(nn) + this.Bus.Address.Word = this.MEMPTR.Word = this.FetchWord().Word; + this.A = this.BusRead(); + this.Tick(4); + break; + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + case 3: // Assorted operations + switch (y) + { + case 0: // JP nn + this.Jump(this.FetchWord().Word); + this.Tick(4); + break; + case 1: // CB prefix + this.prefixCB = true; + this.Execute(this.FetchByte()); + break; + case 6: // DI + this.DI(); + this.Tick(); + break; + case 7: // EI + this.EI(); + this.Tick(); + break; + } + + break; + + case 4: // Conditional call: CALL cc[y], nn + if (this.CallConditionalFlag(y)) + { + this.Tick(3); + } + + this.Tick(3); + break; + + case 5: // PUSH & various ops + switch (q) + { + case 0: // PUSH rp2[p] + this.PushWord(this.RP2(p)); + this.Tick(4); + break; + + case 1: + switch (p) + { + case 0: // CALL nn + this.Call(this.FetchWord().Word); + this.Tick(6); + break; + } + + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + + case 6: // Operate on accumulator and immediate operand: alu[y] n + switch (y) + { + case 0: // ADD A,n + this.A = this.Add(this.A, this.FetchByte()); + break; + case 1: // ADC A,n + this.A = this.ADC(this.A, this.FetchByte()); + break; + case 2: // SUB n + this.A = this.Subtract(this.A, this.FetchByte()); + break; + case 3: // SBC A,n + this.A = this.SBC(this.A, this.FetchByte()); + break; + case 4: // AND n + this.AndR(this.FetchByte()); + break; + case 5: // XOR n + this.XorR(this.FetchByte()); + break; + case 6: // OR n + this.OrR(this.FetchByte()); + break; + case 7: // CP n + this.Compare(this.FetchByte()); + break; + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + this.Tick(2); + break; + + case 7: // Restart: RST y * 8 + this.Restart((byte)(y << 3)); + this.Tick(4); + break; + + default: + throw new InvalidOperationException("Invalid operation mode"); + } + + break; + } + } + + private byte Increment(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF); + this.F = AdjustZero(this.F, ++operand); + this.F = ClearBit(this.F, StatusBits.HC, LowNibble(operand)); + return operand; + } + + private byte Decrement(byte operand) + { + this.F = SetBit(this.F, StatusBits.NF); + this.F = ClearBit(this.F, StatusBits.HC, LowNibble(operand)); + this.F = AdjustZero(this.F, --operand); + return operand; + } + + private bool JumpConditionalFlag(int flag) + { + switch (flag) + { + case 0: // NZ + return this.JumpConditional((this.F & (byte)StatusBits.ZF) == 0); + case 1: // Z + return this.JumpConditional((this.F & (byte)StatusBits.ZF) != 0); + case 2: // NC + return this.JumpConditional((this.F & (byte)StatusBits.CF) == 0); + case 3: // C + return this.JumpConditional((this.F & (byte)StatusBits.CF) != 0); + default: + throw new ArgumentOutOfRangeException(nameof(flag)); + } + } + + private bool JumpRelativeConditionalFlag(int flag) + { + switch (flag) + { + case 0: // NZ + return this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) == 0); + case 1: // Z + return this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) != 0); + case 2: // NC + return this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) == 0); + case 3: // C + return this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) != 0); + default: + throw new ArgumentOutOfRangeException(nameof(flag)); + } + } + + private bool ReturnConditionalFlag(int flag) + { + switch (flag) + { + case 0: // NZ + return this.ReturnConditional((this.F & (byte)StatusBits.ZF) == 0); + case 1: // Z + return this.ReturnConditional((this.F & (byte)StatusBits.ZF) != 0); + case 2: // NC + return this.ReturnConditional((this.F & (byte)StatusBits.CF) == 0); + case 3: // C + return this.ReturnConditional((this.F & (byte)StatusBits.CF) != 0); + default: + throw new ArgumentOutOfRangeException(nameof(flag)); + } + } + + private bool CallConditionalFlag(int flag) + { + switch (flag) + { + case 0: // NZ + return this.CallConditional((this.F & (byte)StatusBits.ZF) == 0); + case 1: // Z + return this.CallConditional((this.F & (byte)StatusBits.ZF) != 0); + case 2: // NC + return this.CallConditional((this.F & (byte)StatusBits.CF) == 0); + case 3: // C + return this.CallConditional((this.F & (byte)StatusBits.CF) != 0); + default: + throw new ArgumentOutOfRangeException(nameof(flag)); + } + } + + private void Add(Register16 operand, Register16 value) + { + this.MEMPTR.Word = operand.Word; + + var result = this.MEMPTR.Word + value.Word; + + operand.Word = (ushort)result; + + this.F = ClearBit(this.F, StatusBits.NF); + this.F = SetBit(this.F, StatusBits.CF, result & (int)Bits.Bit16); + this.F = AdjustHalfCarryAdd(this.F, this.MEMPTR.High, value.High, operand.High); + } + + private byte Add(byte operand, byte value, int carry = 0) + { + this.MEMPTR.Word = (ushort)(operand + value + carry); + + this.F = AdjustHalfCarryAdd(this.F, operand, value, this.MEMPTR.Low); + + operand = this.MEMPTR.Low; + + this.F = ClearBit(this.F, StatusBits.NF); + this.F = SetBit(this.F, StatusBits.CF, this.MEMPTR.Word & (ushort)Bits.Bit8); + this.F = AdjustZero(this.F, operand); + + return operand; + } + + private byte ADC(byte operand, byte value) => this.Add(operand, value, (this.F & (byte)StatusBits.CF) >> 4); + + private byte Subtract(byte operand, byte value, int carry = 0) + { + this.MEMPTR.Word = (ushort)(operand - value - carry); + + this.F = AdjustHalfCarrySub(this.F, operand, value, this.MEMPTR.Low); + + var result = operand = this.MEMPTR.Low; + + this.F = SetBit(this.F, StatusBits.NF); + this.F = SetBit(this.F, StatusBits.CF, this.MEMPTR.High & (byte)Bits.Bit0); + this.F = AdjustZero(this.F, operand); + + return result; + } + + private byte SBC(byte operand, byte value) => this.Subtract(operand, value, (this.F & (byte)StatusBits.CF) >> 4); + + private byte AndR(byte operand, byte value) + { + this.F = SetBit(this.F, StatusBits.HC); + this.F = ClearBit(this.F, StatusBits.CF | StatusBits.NF); + this.F = AdjustZero(this.F, operand &= value); + return operand; + } + + private void AndR(byte value) => this.A = this.AndR(this.A, value); + + private byte XorR(byte operand, byte value) + { + this.F = ClearBit(this.F, StatusBits.HC | StatusBits.CF | StatusBits.NF); + this.F = AdjustZero(this.F, operand ^= value); + return operand; + } + + private void XorR(byte value) => this.A = this.XorR(this.A, value); + + private byte OrR(byte operand, byte value) + { + this.F = ClearBit(this.F, StatusBits.HC | StatusBits.CF | StatusBits.NF); + this.F = AdjustZero(this.F, operand |= value); + return operand; + } + + private void OrR(byte value) => this.A = this.OrR(this.A, value); + + private void Compare(byte value) => this.Subtract(this.A, value); + + private byte RLC(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + var carry = operand & (byte)Bits.Bit7; + this.F = SetBit(this.F, StatusBits.CF, carry); + return (byte)((operand << 1) | (carry >> 7)); + } + + private byte RRC(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + var carry = operand & (byte)Bits.Bit0; + this.F = SetBit(this.F, StatusBits.CF, carry); + return (byte)((operand >> 1) | (carry << 7)); + } + + private byte RL(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + var carry = this.F & (byte)StatusBits.CF; + this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit7); + return (byte)((operand << 1) | (carry >> 4)); // CF at Bit4 + } + + private byte RR(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + var carry = this.F & (byte)StatusBits.CF; + this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit0); + return (byte)((operand >> 1) | (carry << 3)); // CF at Bit4 + } + + private byte SLA(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit7); + return (byte)(operand << 1); + } + + private byte SRA(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit0); + return (byte)((operand >> 1) | (operand & (byte)Bits.Bit7)); + } + + private byte Swap(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.CF); + return (byte)(PromoteNibble(operand) | DemoteNibble(operand)); + } + + private byte SRL(byte operand) + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC | StatusBits.ZF); + this.F = SetBit(this.F, StatusBits.CF, operand & (byte)Bits.Bit0); + return (byte)((operand >> 1) & ~(byte)Bits.Bit7); + } + + private void Bit(int n, byte operand) + { + var carry = this.F & (byte)StatusBits.CF; + this.AndR(operand, (byte)(1 << n)); + this.F = SetBit(this.F, StatusBits.CF, carry); + } + + private void DAA() + { + int updated = this.A; + + if ((this.F & (byte)StatusBits.NF) != 0) + { + if ((this.F & (byte)StatusBits.HC) != 0) + { + updated = LowByte(updated - 6); + } + + if ((this.F & (byte)StatusBits.CF) != 0) + { + updated -= 0x60; + } + } + else + { + if (((this.F & (byte)StatusBits.HC) != 0) || LowNibble((byte)updated) > 9) + { + updated += 6; + } + + if (((this.F & (byte)StatusBits.CF) != 0) || updated > 0x9F) + { + updated += 0x60; + } } - private void SCF() - { - this.F = SetBit(this.F, StatusBits.CF); - this.F = ClearBit(this.F, StatusBits.HC | StatusBits.NF); - } + this.F = ClearBit(this.F, (byte)StatusBits.HC | (byte)StatusBits.ZF); + this.F = SetBit(this.F, StatusBits.CF, ((this.F & (byte)StatusBits.CF) != 0) || ((updated & (int)Bits.Bit8) != 0)); + this.A = LowByte(updated); - private void CCF() - { - this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC); - this.F = ClearBit(this.F, StatusBits.CF, this.F & (byte)StatusBits.CF); - } + this.F = AdjustZero(this.F, this.A); + } - private void RetI() - { - this.Return(); - this.EI(); - } + private void Cpl() + { + this.F = SetBit(this.F, StatusBits.HC | StatusBits.NF); + this.A = (byte)~this.A; + } + + private void SCF() + { + this.F = SetBit(this.F, StatusBits.CF); + this.F = ClearBit(this.F, StatusBits.HC | StatusBits.NF); + } + + private void CCF() + { + this.F = ClearBit(this.F, StatusBits.NF | StatusBits.HC); + this.F = ClearBit(this.F, StatusBits.CF, this.F & (byte)StatusBits.CF); + } + + private void RetI() + { + this.Return(); + this.EI(); } } } diff --git a/LR35902/LcdStatusMode.cs b/LR35902/LcdStatusMode.cs index cb2c464..0d45c02 100644 --- a/LR35902/LcdStatusMode.cs +++ b/LR35902/LcdStatusMode.cs @@ -1,16 +1,13 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public enum LcdStatusMode { - public enum LcdStatusMode - { - HBlank = 0b00, - VBlank = 0b01, - SearchingOamRam = 0b10, - TransferringDataToLcd = 0b11, - } + HBlank = 0b00, + VBlank = 0b01, + SearchingOamRam = 0b10, + TransferringDataToLcd = 0b11, } } diff --git a/LR35902/LcdStatusModeEventArgs.cs b/LR35902/LcdStatusModeEventArgs.cs index 66f86dc..5c987e5 100644 --- a/LR35902/LcdStatusModeEventArgs.cs +++ b/LR35902/LcdStatusModeEventArgs.cs @@ -1,17 +1,14 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + using System; + + public class LcdStatusModeEventArgs : EventArgs { - using System; + public LcdStatusModeEventArgs(LcdStatusMode value) => this.LcdStatusMode = value; - public class LcdStatusModeEventArgs : EventArgs - { - public LcdStatusModeEventArgs(LcdStatusMode value) => this.LcdStatusMode = value; - - public LcdStatusMode LcdStatusMode { get; } - } + public LcdStatusMode LcdStatusMode { get; } } -} \ No newline at end of file +} diff --git a/LR35902/LcdcControl.cs b/LR35902/LcdcControl.cs index e8f7f32..5fee8f9 100644 --- a/LR35902/LcdcControl.cs +++ b/LR35902/LcdcControl.cs @@ -1,22 +1,19 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + [System.Flags] + public enum LcdcControl { - [System.Flags] - public enum LcdcControl - { - None = 0, - DisplayBackground = Bits.Bit0, - ObjectEnable = Bits.Bit1, - ObjectBlockCompositionSelection = Bits.Bit2, - BackgroundCodeAreaSelection = Bits.Bit3, - BackgroundCharacterDataSelection = Bits.Bit4, - WindowEnable = Bits.Bit5, - WindowCodeAreaSelection = Bits.Bit6, - LcdEnable = Bits.Bit7, - } + None = 0, + DisplayBackground = Bits.Bit0, + ObjectEnable = Bits.Bit1, + ObjectBlockCompositionSelection = Bits.Bit2, + BackgroundCodeAreaSelection = Bits.Bit3, + BackgroundCharacterDataSelection = Bits.Bit4, + WindowEnable = Bits.Bit5, + WindowCodeAreaSelection = Bits.Bit6, + LcdEnable = Bits.Bit7, } } diff --git a/LR35902/ObjectAttribute.cs b/LR35902/ObjectAttribute.cs index d6b9699..506bc11 100644 --- a/LR35902/ObjectAttribute.cs +++ b/LR35902/ObjectAttribute.cs @@ -1,43 +1,40 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + public class ObjectAttribute { - public class ObjectAttribute + public ObjectAttribute() { - public ObjectAttribute() - { - } - - public ObjectAttribute(Ram ram, ushort address) - { - this.PositionY = ram.Peek(address); - this.PositionX = ram.Peek(++address); - this.Pattern = ram.Peek(++address); - this.Flags = ram.Peek(++address); - } - - public byte PositionY { get; } - - public byte PositionX { get; } - - public byte Pattern { get; } - - public byte Flags { get; } - - public int Priority => this.Flags & (byte)Bits.Bit7; - - public bool HighPriority => this.Priority != 0; - - public bool LowPriority => this.Priority == 0; - - public bool FlipY => (this.Flags & (byte)Bits.Bit6) != 0; - - public bool FlipX => (this.Flags & (byte)Bits.Bit5) != 0; - - public int Palette => (this.Flags & (byte)Bits.Bit4) >> 3; // TODO: Check this! } + + public ObjectAttribute(Ram ram, ushort address) + { + this.PositionY = ram.Peek(address); + this.PositionX = ram.Peek(++address); + this.Pattern = ram.Peek(++address); + this.Flags = ram.Peek(++address); + } + + public byte PositionY { get; } + + public byte PositionX { get; } + + public byte Pattern { get; } + + public byte Flags { get; } + + public int Priority => this.Flags & (byte)Bits.Bit7; + + public bool HighPriority => this.Priority != 0; + + public bool LowPriority => this.Priority == 0; + + public bool FlipY => (this.Flags & (byte)Bits.Bit6) != 0; + + public bool FlipX => (this.Flags & (byte)Bits.Bit5) != 0; + + public int Palette => (this.Flags & (byte)Bits.Bit4) >> 3; // TODO: Check this! } } diff --git a/LR35902/StatusBits.cs b/LR35902/StatusBits.cs index 3240fc2..ba640c4 100644 --- a/LR35902/StatusBits.cs +++ b/LR35902/StatusBits.cs @@ -1,18 +1,15 @@ // // Copyright (c) Adrian Conlon. All rights reserved. // -namespace EightBit +namespace EightBit.GameBoy { - namespace GameBoy + [System.Flags] + public enum StatusBits { - [System.Flags] - public enum StatusBits - { - None = 0, - CF = Bits.Bit4, - HC = Bits.Bit5, - NF = Bits.Bit6, - ZF = Bits.Bit7, - } + None = 0, + CF = Bits.Bit4, + HC = Bits.Bit5, + NF = Bits.Bit6, + ZF = Bits.Bit7, } }