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,
}
}