From c4d22c44f1fb184eb31ed47b43e882ec6a0d0d00 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 26 Aug 2026 20:31:49 +0200 Subject: [PATCH] feat(core): wire emulator system bus and component integration --- src/GameBoy.Core/Emulator.cs | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/GameBoy.Core/Emulator.cs diff --git a/src/GameBoy.Core/Emulator.cs b/src/GameBoy.Core/Emulator.cs new file mode 100644 index 0000000..e427ee7 --- /dev/null +++ b/src/GameBoy.Core/Emulator.cs @@ -0,0 +1,109 @@ +using GameBoy.Audio; +using GameBoy.Cpu; +using GameBoy.Input; +using GameBoy.Memory; +using GameBoy.Serial; +using GameBoy.Timing; +using GameBoy.Video; +using CpuCore = GameBoy.Cpu.Cpu; +using CartridgeInfo = GameBoy.Cartridge.Cartridge; + +namespace GameBoy +{ + public sealed partial class Emulator : ISystemBus + { + public const int ScreenWidth = Ppu.ScreenWidth; + public const int ScreenHeight = Ppu.ScreenHeight; + + private const int CyclesPerFrame = 70224; + + private readonly InterruptController interrupts = new InterruptController(); + private readonly Ppu ppu; + private readonly Timer timer; + private readonly Joypad joypad; + private readonly SerialPort serial; + private readonly Apu apu; + private readonly Mmu mmu; + private readonly OamDma dma; + private readonly CpuCore cpu; + + public CpuCore Cpu => cpu; + public Ppu Ppu => ppu; + public Joypad Joypad => joypad; + public CartridgeInfo Cartridge { get; } + public byte[] Framebuffer => ppu.Framebuffer; + public string SerialOutput => serial.Output; + public InterruptController Interrupts => interrupts; + public long TotalCycles { get; private set; } + + public Emulator(byte[] rom) + { + Cartridge = new CartridgeInfo(rom); + ppu = new Ppu(interrupts); + timer = new Timer(interrupts); + joypad = new Joypad(interrupts); + serial = new SerialPort(interrupts); + apu = new Apu(); + mmu = new Mmu(Cartridge, ppu, timer, joypad, serial, apu, interrupts); + dma = new OamDma(mmu, ppu); + mmu.Dma = dma; + cpu = new CpuCore(this); + + cpu.ResetPostBoot(); + InitPostBootIo(); + } + + private void InitPostBootIo() + { + ppu.WriteRegister(0xFF47, 0xFC); + ppu.WriteRegister(0xFF48, 0xFF); + ppu.WriteRegister(0xFF49, 0xFF); + ppu.WriteRegister(0xFF40, 0x91); + } + + byte ISystemBus.Read(ushort address) + { + Tick4(); + return mmu.Read(address); + } + + void ISystemBus.Write(ushort address, byte value) + { + Tick4(); + mmu.Write(address, value); + } + + public void Tick4() + { + for (int i = 0; i < 4; i++) + { + timer.Step(); + ppu.Step(); + dma.Step(); + } + apu.Step(4); + Cartridge.Step(4); + TotalCycles += 4; + } + + public void StepInstruction() + { + cpu.Step(); + } + + public void RunFrame() + { + ppu.ConsumeFrame(); + long deadline = TotalCycles + CyclesPerFrame * 2; + while (!ppu.FrameReady && TotalCycles < deadline) + cpu.Step(); + } + + public void RunCycles(long cycles) + { + long deadline = TotalCycles + cycles; + while (TotalCycles < deadline) + cpu.Step(); + } + } +}