mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2024-06-02 16:41:33 +00:00
Tidy up EightBit.GameBoy namespace definition.
Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
parent
0ada703504
commit
5a7a3b5019
|
@ -1,19 +1,16 @@
|
||||||
// <copyright file="AbstractColourPalette.cs" company="Adrian Conlon">
|
// <copyright file="AbstractColourPalette.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
789
LR35902/Bus.cs
789
LR35902/Bus.cs
|
@ -1,439 +1,436 @@
|
||||||
// <copyright file="Bus.cs" company="Adrian Conlon">
|
// <copyright file="Bus.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
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;
|
this.IO = new IoRegisters(this);
|
||||||
public const int FramesPerSecond = 60;
|
this.CPU = new LR35902(this);
|
||||||
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
|
public LR35902 CPU { get; }
|
||||||
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;
|
public Ram VRAM { get; } = new Ram(0x2000);
|
||||||
|
|
||||||
private bool disabledGameRom = false;
|
public Ram OAMRAM { get; } = new Ram(0xa0);
|
||||||
|
|
||||||
private bool rom = false;
|
public IoRegisters IO { get; }
|
||||||
private bool banked = false;
|
|
||||||
private bool ram = false;
|
|
||||||
private bool battery = false;
|
|
||||||
|
|
||||||
private bool higherRomBank = true;
|
public bool GameRomDisabled => this.disabledGameRom;
|
||||||
private bool ramBankSwitching = false;
|
|
||||||
|
|
||||||
private int romBank = 1;
|
public bool GameRomEnabled => !this.GameRomDisabled;
|
||||||
private int ramBank = 0;
|
|
||||||
|
|
||||||
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());
|
this.gameRomBanks.Add(new Rom());
|
||||||
var size = this.gameRomBanks[0].Load(path, 0, 0, bankSize);
|
this.gameRomBanks[bank].Load(path, 0, bankSize * bank, 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return new MemoryMapping(this.bootRom, 0x0000, Mask.Mask16, AccessLevel.ReadOnly);
|
||||||
this.IO.ResetLY();
|
|
||||||
return this.RunRasterLines(Display.RasterHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int RunVerticalBlankLines()
|
if ((address < 0x4000) && this.GameRomEnabled)
|
||||||
{
|
{
|
||||||
var lines = TotalLineCount - Display.RasterHeight;
|
return new MemoryMapping(this.gameRomBanks[0], 0x0000, 0xffff, AccessLevel.ReadOnly);
|
||||||
return this.RunVerticalBlankLines(lines);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MemoryMapping Mapping(ushort address)
|
if ((address < 0x8000) && this.GameRomEnabled)
|
||||||
{
|
{
|
||||||
if ((address < 0x100) && this.IO.BootRomEnabled)
|
return new MemoryMapping(this.gameRomBanks[this.romBank], 0x4000, 0xffff, AccessLevel.ReadOnly);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnWrittenByte()
|
if (address < 0xa000)
|
||||||
{
|
{
|
||||||
base.OnWrittenByte();
|
return new MemoryMapping(this.VRAM, 0x8000, 0xffff, AccessLevel.ReadWrite);
|
||||||
|
|
||||||
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()
|
if (address < 0xc000)
|
||||||
{
|
{
|
||||||
this.rom = this.banked = this.ram = this.battery = false;
|
if (this.ramBanks.Count == 0)
|
||||||
|
|
||||||
// ROM type
|
|
||||||
switch (this.gameRomBanks[0].Peek(0x147))
|
|
||||||
{
|
{
|
||||||
case (byte)CartridgeType.ROM:
|
return new MemoryMapping(this.unmapped2000, 0xa000, 0xffff, AccessLevel.ReadOnly);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
else
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
// <copyright file="CartridgeType.cs" company="Adrian Conlon">
|
// <copyright file="CartridgeType.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
namespace GameBoy
|
public enum CartridgeType
|
||||||
{
|
{
|
||||||
public enum CartridgeType
|
ROM = 0,
|
||||||
{
|
ROM_MBC1 = 1,
|
||||||
ROM = 0,
|
ROM_MBC1_RAM = 2,
|
||||||
ROM_MBC1 = 1,
|
ROM_MBC1_RAM_BATTERY = 3,
|
||||||
ROM_MBC1_RAM = 2,
|
|
||||||
ROM_MBC1_RAM_BATTERY = 3,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,39 @@
|
||||||
// <copyright file="CharacterDefinition.cs" company="Adrian Conlon">
|
// <copyright file="CharacterDefinition.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </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;
|
this.vram = vram;
|
||||||
private readonly ushort address;
|
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;
|
var mask = 1 << bit;
|
||||||
this.address = address;
|
|
||||||
|
var bitLow = (planeLow & mask) != 0 ? 1 : 0;
|
||||||
|
var bitHigh = (planeHigh & mask) != 0 ? 0b10 : 0;
|
||||||
|
|
||||||
|
returned[7 - bit] = bitHigh | bitLow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] Get(int row)
|
return returned;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
// <copyright file="ColourShades.cs" company="Adrian Conlon">
|
// <copyright file="ColourShades.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
namespace GameBoy
|
public enum ColourShades
|
||||||
{
|
{
|
||||||
public enum ColourShades
|
Off,
|
||||||
{
|
Light,
|
||||||
Off,
|
Medium,
|
||||||
Light,
|
Dark,
|
||||||
Medium,
|
|
||||||
Dark,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,176 +1,173 @@
|
||||||
// <copyright file="Display.cs" company="Adrian Conlon">
|
// <copyright file="Display.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </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;
|
this.colours = colours;
|
||||||
public static readonly int BufferHeight = 256;
|
this.bus = bus;
|
||||||
public static readonly int BufferCharacterWidth = BufferWidth / 8;
|
this.oam = oam;
|
||||||
public static readonly int BufferCharacterHeight = BufferHeight / 8;
|
this.vram = vram;
|
||||||
public static readonly int RasterWidth = 160;
|
}
|
||||||
public static readonly int RasterHeight = 144;
|
|
||||||
public static readonly int PixelCount = RasterWidth * RasterHeight;
|
|
||||||
|
|
||||||
private readonly Bus bus;
|
public uint[] Pixels { get; } = new uint[PixelCount];
|
||||||
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 void Render()
|
||||||
|
{
|
||||||
|
this.scanLine = this.bus.IO.Peek(IoRegisters.LY);
|
||||||
|
if (this.scanLine < RasterHeight)
|
||||||
{
|
{
|
||||||
this.colours = colours;
|
this.control = this.bus.IO.Peek(IoRegisters.LCDC);
|
||||||
this.bus = bus;
|
if ((this.control & (byte)LcdcControl.LcdEnable) != 0)
|
||||||
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.DisplayBackground) != 0)
|
||||||
if ((this.control & (byte)LcdcControl.LcdEnable) != 0)
|
|
||||||
{
|
{
|
||||||
if ((this.control & (byte)LcdcControl.DisplayBackground) != 0)
|
this.RenderBackground();
|
||||||
{
|
}
|
||||||
this.RenderBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((this.control & (byte)LcdcControl.ObjectEnable) != 0)
|
if ((this.control & (byte)LcdcControl.ObjectEnable) != 0)
|
||||||
{
|
{
|
||||||
this.RenderObjects();
|
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);
|
cy = ~cy & flipMaskY;
|
||||||
return new int[4]
|
|
||||||
{
|
|
||||||
raw & 0b11,
|
|
||||||
(raw & 0b1100) >> 2,
|
|
||||||
(raw & 0b110000) >> 4,
|
|
||||||
(raw & 0b11000000) >> 6,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 x = drawX + (flipX ? ~cx & flipMaskX : cx);
|
||||||
|
if (x >= RasterWidth)
|
||||||
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++);
|
break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rowDefinition = definition.Get(cy);
|
var colour = rowDefinition[cx];
|
||||||
|
if (!allowTransparencies || (allowTransparencies && (colour > 0)))
|
||||||
var lineAddress = y * RasterWidth;
|
|
||||||
for (var cx = 0; cx < width; ++cx)
|
|
||||||
{
|
{
|
||||||
var x = drawX + (flipX ? ~cx & flipMaskX : cx);
|
var outputPixel = lineAddress + x;
|
||||||
if (x >= RasterWidth)
|
this.Pixels[outputPixel] = this.colours.Colour(palette[colour]);
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var colour = rowDefinition[cx];
|
|
||||||
if (!allowTransparencies || (allowTransparencies && (colour > 0)))
|
|
||||||
{
|
|
||||||
var outputPixel = lineAddress + x;
|
|
||||||
this.Pixels[outputPixel] = this.colours.Colour(palette[colour]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
// <copyright file="Interrupts.cs" company="Adrian Conlon">
|
// <copyright file="Interrupts.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
namespace GameBoy
|
// IF and IE flags
|
||||||
|
[System.Flags]
|
||||||
|
public enum Interrupts
|
||||||
{
|
{
|
||||||
// IF and IE flags
|
None = 0,
|
||||||
[System.Flags]
|
VerticalBlank = Bits.Bit0, // VBLANK
|
||||||
public enum Interrupts
|
DisplayControlStatus = Bits.Bit1, // LCDC Status
|
||||||
{
|
TimerOverflow = Bits.Bit2, // Timer Overflow
|
||||||
None = 0,
|
SerialTransfer = Bits.Bit3, // Serial Transfer
|
||||||
VerticalBlank = Bits.Bit0, // VBLANK
|
KeypadPressed = Bits.Bit4, // Hi-Lo transition of P10-P13
|
||||||
DisplayControlStatus = Bits.Bit1, // LCDC Status
|
|
||||||
TimerOverflow = Bits.Bit2, // Timer Overflow
|
|
||||||
SerialTransfer = Bits.Bit3, // Serial Transfer
|
|
||||||
KeypadPressed = Bits.Bit4, // Hi-Lo transition of P10-P13
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,397 +1,394 @@
|
||||||
// <copyright file="IoRegisters.cs" company="Adrian Conlon">
|
// <copyright file="IoRegisters.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </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 event EventHandler<LcdStatusModeEventArgs> DisplayStatusModeUpdated;
|
||||||
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 bool BootRomDisabled { get; private set; } = false;
|
||||||
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 bool BootRomEnabled => !this.BootRomDisabled;
|
||||||
public const int IF = 0xF; // R/W Mask5
|
|
||||||
public const int IE = 0xFF; // R/W Mask5
|
|
||||||
|
|
||||||
// Sound Registers
|
public int TimerClock => this.Peek((ushort)TAC) & (byte)Mask.Mask2;
|
||||||
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 bool TimerEnabled => !this.TimerDisabled;
|
||||||
public const int WAVE_PATTERN_RAM_END = 0x3F;
|
|
||||||
|
|
||||||
// LCD Display Registers
|
public bool TimerDisabled => (this.Peek((ushort)TAC) & (byte)Bits.Bit2) == 0;
|
||||||
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 int TimerClockTicks
|
||||||
public const int BOOT_DISABLE = 0x50;
|
{
|
||||||
|
get
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
this.bus = bus;
|
switch (this.TimerClock)
|
||||||
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)
|
case 0b00:
|
||||||
{
|
return 1024; // 4.096 Khz
|
||||||
case 0b00:
|
case 0b01:
|
||||||
return 1024; // 4.096 Khz
|
return 16; // 262.144 Khz
|
||||||
case 0b01:
|
case 0b10:
|
||||||
return 16; // 262.144 Khz
|
return 64; // 65.536 Khz
|
||||||
case 0b10:
|
case 0b11:
|
||||||
return 64; // 65.536 Khz
|
return 256; // 16.384 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.timerCounter -= cycles;
|
||||||
this.Poke((ushort)STAT, (byte)(current | (int)mode));
|
if (this.timerCounter <= 0)
|
||||||
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;
|
this.timerCounter += this.timerRate;
|
||||||
if (this.timerCounter <= 0)
|
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);
|
var port = (ushort)(address - BASE);
|
||||||
|
|
||||||
switch (port)
|
switch (port)
|
||||||
{
|
{
|
||||||
case P1:
|
// Port/Mode Registers
|
||||||
this.scanP14 = (value & (byte)Bits.Bit4) == 0;
|
case P1:
|
||||||
this.scanP15 = (value & (byte)Bits.Bit5) == 0;
|
{
|
||||||
break;
|
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
|
break;
|
||||||
case SC: // R/W
|
case SB:
|
||||||
break;
|
break;
|
||||||
|
case SC:
|
||||||
|
this.ApplyMask(port, (byte)(Bits.Bit7 | Bits.Bit0));
|
||||||
|
break;
|
||||||
|
|
||||||
case DIV: // R/W
|
// Timer control
|
||||||
this.Poke(port, 0);
|
case DIV:
|
||||||
this.timerCounter = this.divCounter.Word = 0;
|
case TIMA:
|
||||||
break;
|
case TMA:
|
||||||
case TIMA: // R/W
|
break;
|
||||||
case TMA: // R/W
|
case TAC:
|
||||||
break;
|
this.ApplyMask(port, (byte)Mask.Mask3);
|
||||||
case TAC: // R/W
|
break;
|
||||||
this.timerRate = this.TimerClockTicks;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IF: // R/W
|
// Interrupt Flags
|
||||||
break;
|
case IF:
|
||||||
|
this.ApplyMask(port, (byte)Mask.Mask5);
|
||||||
|
break;
|
||||||
|
|
||||||
case LCDC:
|
// LCD Display Registers
|
||||||
case STAT:
|
case LCDC:
|
||||||
case SCY:
|
break;
|
||||||
case SCX:
|
case STAT:
|
||||||
break;
|
this.ApplyMask(port, (byte)Mask.Mask7);
|
||||||
case DMA:
|
break;
|
||||||
this.dmaAddress.Word = Chip.PromoteByte(value);
|
case SCY:
|
||||||
this.dmaTransferActive = true;
|
case SCX:
|
||||||
break;
|
case LY:
|
||||||
case LY: // R/O
|
case LYC:
|
||||||
this.Poke(port, 0);
|
case DMA:
|
||||||
break;
|
case BGP:
|
||||||
case BGP:
|
case OBP0:
|
||||||
case OBP0:
|
case OBP1:
|
||||||
case OBP1:
|
case WY:
|
||||||
case WY:
|
case WX:
|
||||||
case WX:
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case BOOT_DISABLE:
|
default:
|
||||||
this.BootRomDisabled = value != 0;
|
this.ApplyMask(port, 0);
|
||||||
break;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,26 +11,27 @@ namespace LR35902.BlarggTest
|
||||||
var configuration = new Configuration();
|
var configuration = new Configuration();
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
configuration.DebugMode = true;
|
//configuration.DebugMode = true;
|
||||||
#endif
|
#endif
|
||||||
|
//configuration.DebugMode = true;
|
||||||
|
|
||||||
var computer = new Computer(configuration);
|
var computer = new Computer(configuration);
|
||||||
|
|
||||||
computer.Plug("blargg/cpu_instrs.gb"); // Passed
|
computer.Plug("blargg/cpu_instrs.gb"); // Passed
|
||||||
////computer.plug("blargg/01-special.gb"); // Passed
|
////computer.Plug("blargg/01-special.gb"); // Passed
|
||||||
////computer.plug("blargg/02-interrupts.gb"); // Passed
|
////computer.Plug("blargg/02-interrupts.gb"); // Passed
|
||||||
////computer.plug("blargg/03-op sp,hl.gb"); // Passed
|
////computer.Plug("blargg/03-op sp,hl.gb"); // Passed
|
||||||
////computer.plug("blargg/04-op r,imm.gb"); // Passed
|
////computer.Plug("blargg/04-op r,imm.gb"); // Passed
|
||||||
////computer.plug("blargg/05-op rp.gb"); // Passed
|
////computer.Plug("blargg/05-op rp.gb"); // Passed
|
||||||
////computer.plug("blargg/06-ld r,r.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/07-jr,jp,call,ret,rst.gb"); // Passed
|
||||||
////computer.plug("blargg/08-misc instrs.gb"); // Passed
|
////computer.Plug("blargg/08-misc instrs.gb"); // Passed
|
||||||
////computer.plug("blargg/09-op r,r.gb"); // Passed
|
////computer.Plug("blargg/09-op r,r.gb"); // Passed
|
||||||
////computer.plug("blargg/10-bit ops.gb"); // Passed
|
////computer.Plug("blargg/10-bit ops.gb"); // Passed
|
||||||
////computer.plug("blargg/11-op a,(hl).gb"); // Passed
|
////computer.Plug("blargg/11-op a,(hl).gb"); // Passed
|
||||||
|
|
||||||
////computer.plug("blargg/instr_timing.gb"); // Failed #255
|
////computer.Plug("blargg/instr_timing.gb"); // Failed #255
|
||||||
////computer.plug("blargg/interrupt_time.gb"); // Failed
|
////computer.Plug("blargg/interrupt_time.gb"); // Failed
|
||||||
|
|
||||||
computer.RaisePOWER();
|
computer.RaisePOWER();
|
||||||
computer.Run();
|
computer.Run();
|
||||||
|
|
2189
LR35902/LR35902.cs
2189
LR35902/LR35902.cs
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,13 @@
|
||||||
// <copyright file="LcdStatusMode.cs" company="Adrian Conlon">
|
// <copyright file="LcdStatusMode.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
namespace GameBoy
|
public enum LcdStatusMode
|
||||||
{
|
{
|
||||||
public enum LcdStatusMode
|
HBlank = 0b00,
|
||||||
{
|
VBlank = 0b01,
|
||||||
HBlank = 0b00,
|
SearchingOamRam = 0b10,
|
||||||
VBlank = 0b01,
|
TransferringDataToLcd = 0b11,
|
||||||
SearchingOamRam = 0b10,
|
|
||||||
TransferringDataToLcd = 0b11,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
// <copyright file="LcdStatusModeEventArgs.cs" company="Adrian Conlon">
|
// <copyright file="LcdStatusModeEventArgs.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </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 LcdStatusMode LcdStatusMode { get; }
|
||||||
{
|
|
||||||
public LcdStatusModeEventArgs(LcdStatusMode value) => this.LcdStatusMode = value;
|
|
||||||
|
|
||||||
public LcdStatusMode LcdStatusMode { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
// <copyright file="LcdcControl.cs" company="Adrian Conlon">
|
// <copyright file="LcdcControl.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
namespace GameBoy
|
[System.Flags]
|
||||||
|
public enum LcdcControl
|
||||||
{
|
{
|
||||||
[System.Flags]
|
None = 0,
|
||||||
public enum LcdcControl
|
DisplayBackground = Bits.Bit0,
|
||||||
{
|
ObjectEnable = Bits.Bit1,
|
||||||
None = 0,
|
ObjectBlockCompositionSelection = Bits.Bit2,
|
||||||
DisplayBackground = Bits.Bit0,
|
BackgroundCodeAreaSelection = Bits.Bit3,
|
||||||
ObjectEnable = Bits.Bit1,
|
BackgroundCharacterDataSelection = Bits.Bit4,
|
||||||
ObjectBlockCompositionSelection = Bits.Bit2,
|
WindowEnable = Bits.Bit5,
|
||||||
BackgroundCodeAreaSelection = Bits.Bit3,
|
WindowCodeAreaSelection = Bits.Bit6,
|
||||||
BackgroundCharacterDataSelection = Bits.Bit4,
|
LcdEnable = Bits.Bit7,
|
||||||
WindowEnable = Bits.Bit5,
|
|
||||||
WindowCodeAreaSelection = Bits.Bit6,
|
|
||||||
LcdEnable = Bits.Bit7,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,40 @@
|
||||||
// <copyright file="ObjectAttribute.cs" company="Adrian Conlon">
|
// <copyright file="ObjectAttribute.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </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!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
// <copyright file="StatusBits.cs" company="Adrian Conlon">
|
// <copyright file="StatusBits.cs" company="Adrian Conlon">
|
||||||
// Copyright (c) Adrian Conlon. All rights reserved.
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
namespace EightBit
|
namespace EightBit.GameBoy
|
||||||
{
|
{
|
||||||
namespace GameBoy
|
[System.Flags]
|
||||||
|
public enum StatusBits
|
||||||
{
|
{
|
||||||
[System.Flags]
|
None = 0,
|
||||||
public enum StatusBits
|
CF = Bits.Bit4,
|
||||||
{
|
HC = Bits.Bit5,
|
||||||
None = 0,
|
NF = Bits.Bit6,
|
||||||
CF = Bits.Bit4,
|
ZF = Bits.Bit7,
|
||||||
HC = Bits.Bit5,
|
|
||||||
NF = Bits.Bit6,
|
|
||||||
ZF = Bits.Bit7,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user