Tidy up EightBit.GameBoy namespace definition.

Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2019-07-28 11:50:25 +01:00
parent 0ada703504
commit 5a7a3b5019
16 changed files with 2759 additions and 2803 deletions

View File

@ -1,19 +1,16 @@
// <copyright file="AbstractColourPalette.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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];
}
}

View File

@ -1,439 +1,436 @@
// <copyright file="Bus.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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<Rom> gameRomBanks = new List<Rom>(); // 0x0000 - 0x3fff, 0x4000 - 0x7fff (switchable)
private readonly List<Ram> ramBanks = new List<Ram>(); // 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<Rom> gameRomBanks = new List<Rom>(); // 0x0000 - 0x3fff, 0x4000 - 0x7fff (switchable)
private readonly List<Ram> ramBanks = new List<Ram>(); // 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;
}
}
}

View File

@ -1,16 +1,13 @@
// <copyright file="CartridgeType.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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,
}
}

View File

@ -1,42 +1,39 @@
// <copyright file="CharacterDefinition.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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;
}
}
}

View File

@ -1,16 +1,13 @@
// <copyright file="ColourShades.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace EightBit
namespace EightBit.GameBoy
{
namespace GameBoy
public enum ColourShades
{
public enum ColourShades
{
Off,
Light,
Medium,
Dark,
}
Off,
Light,
Medium,
Dark,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,176 +1,173 @@
// <copyright file="Display.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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]);
}
}
}

View File

@ -1,20 +1,17 @@
// <copyright file="Interrupts.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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
}
}

View File

@ -1,397 +1,394 @@
// <copyright file="IoRegisters.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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<LcdStatusModeEventArgs> 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<LcdStatusModeEventArgs> 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;
}
}
}

View File

@ -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();

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,13 @@
// <copyright file="LcdStatusMode.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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,
}
}

View File

@ -1,17 +1,14 @@
// <copyright file="LcdStatusModeEventArgs.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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; }
}
}
}

View File

@ -1,22 +1,19 @@
// <copyright file="LcdcControl.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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,
}
}

View File

@ -1,43 +1,40 @@
// <copyright file="ObjectAttribute.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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!
}
}

View File

@ -1,18 +1,15 @@
// <copyright file="StatusBits.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
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,
}
}