feat: implement ARM read open bus
This commit is contained in:
parent
6d5c30ac25
commit
9b9b6c0d6f
35
src/Bus.zig
35
src/Bus.zig
|
@ -1,6 +1,7 @@
|
|||
const std = @import("std");
|
||||
|
||||
const AudioDeviceId = @import("sdl2").SDL_AudioDeviceID;
|
||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||
const Bios = @import("bus/Bios.zig");
|
||||
const Ewram = @import("bus/Ewram.zig");
|
||||
const GamePak = @import("bus/GamePak.zig");
|
||||
|
@ -18,7 +19,6 @@ const Allocator = std.mem.Allocator;
|
|||
const log = std.log.scoped(.Bus);
|
||||
|
||||
const rotr = @import("util.zig").rotr;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const panic_on_und_bus: bool = false;
|
||||
|
@ -33,19 +33,21 @@ iwram: Iwram,
|
|||
ewram: Ewram,
|
||||
io: Io,
|
||||
|
||||
cpu: ?*Arm7tdmi,
|
||||
sched: *Scheduler,
|
||||
|
||||
pub fn init(alloc: Allocator, sched: *Scheduler, dev: AudioDeviceId, paths: FilePaths) !Self {
|
||||
pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self {
|
||||
return Self{
|
||||
.pak = try GamePak.init(alloc, paths.rom, paths.save),
|
||||
.bios = try Bios.init(alloc, paths.bios),
|
||||
.ppu = try Ppu.init(alloc, sched),
|
||||
.apu = Apu.init(dev),
|
||||
.apu = Apu.init(),
|
||||
.iwram = try Iwram.init(alloc),
|
||||
.ewram = try Ewram.init(alloc),
|
||||
.dma = DmaControllers.init(),
|
||||
.tim = Timers.init(sched),
|
||||
.io = Io.init(),
|
||||
.cpu = null,
|
||||
.sched = sched,
|
||||
};
|
||||
}
|
||||
|
@ -74,6 +76,29 @@ fn isDmaRunning(self: *const Self) bool {
|
|||
self.dma._3.active;
|
||||
}
|
||||
|
||||
pub fn debugRead(self: *const Self, comptime T: type, address: u32) T {
|
||||
const cached = self.sched.tick;
|
||||
defer self.sched.tick = cached;
|
||||
|
||||
return self.read(T, address);
|
||||
}
|
||||
|
||||
fn readOpenBus(self: *const Self, comptime T: type, address: u32) T {
|
||||
if (self.cpu.?.cpsr.t.read()) {
|
||||
log.err("TODO: {} open bus read in THUMB", .{T});
|
||||
return 0;
|
||||
}
|
||||
|
||||
const word = self.debugRead(u32, self.cpu.?.r[15] + 4);
|
||||
return @truncate(T, rotr(u32, word, 8 * (address & 3)));
|
||||
}
|
||||
|
||||
fn readBios(self: *const Self, comptime T: type, address: u32) T {
|
||||
if (address < Bios.size) return self.bios.read(T, alignAddress(T, address));
|
||||
|
||||
return self.readOpenBus(T, address);
|
||||
}
|
||||
|
||||
pub fn read(self: *const Self, comptime T: type, address: u32) T {
|
||||
const page = @truncate(u8, address >> 24);
|
||||
const align_addr = alignAddress(T, address);
|
||||
|
@ -81,7 +106,7 @@ pub fn read(self: *const Self, comptime T: type, address: u32) T {
|
|||
|
||||
return switch (page) {
|
||||
// General Internal Memory
|
||||
0x00 => self.bios.read(T, align_addr),
|
||||
0x00 => self.readBios(T, address),
|
||||
0x02 => self.ewram.read(T, align_addr),
|
||||
0x03 => self.iwram.read(T, align_addr),
|
||||
0x04 => io.read(self, T, align_addr),
|
||||
|
@ -105,7 +130,7 @@ pub fn read(self: *const Self, comptime T: type, address: u32) T {
|
|||
|
||||
break :blk @as(T, value) * multiplier;
|
||||
},
|
||||
else => undRead("Tried to read {} from 0x{X:0>8}", .{ T, address }),
|
||||
else => readOpenBus(self, T, address),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
12
src/apu.zig
12
src/apu.zig
|
@ -24,9 +24,9 @@ pub const Apu = struct {
|
|||
dma_cnt: io.DmaSoundControl,
|
||||
cnt: io.SoundControl,
|
||||
|
||||
dev: AudioDeviceId,
|
||||
dev: ?AudioDeviceId,
|
||||
|
||||
pub fn init(dev: AudioDeviceId) Self {
|
||||
pub fn init() Self {
|
||||
return .{
|
||||
.ch1 = ToneSweep.init(),
|
||||
.ch2 = Tone.init(),
|
||||
|
@ -40,10 +40,14 @@ pub const Apu = struct {
|
|||
.cnt = .{ .raw = 0 },
|
||||
.bias = .{ .raw = 0x0200 },
|
||||
|
||||
.dev = dev,
|
||||
.dev = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn attachAudioDevice(self: *Self, dev: AudioDeviceId) void {
|
||||
self.dev = dev;
|
||||
}
|
||||
|
||||
pub fn setDmaCnt(self: *Self, value: u16) void {
|
||||
const new: io.DmaSoundControl = .{ .raw = value };
|
||||
|
||||
|
@ -83,7 +87,7 @@ pub const Apu = struct {
|
|||
},
|
||||
};
|
||||
|
||||
_ = SDL.SDL_QueueAudio(self.dev, &samples, 2);
|
||||
if (self.dev) |dev| _ = SDL.SDL_QueueAudio(dev, &samples, 2);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@ const std = @import("std");
|
|||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const log = std.log.scoped(.Bios);
|
||||
|
||||
/// Size of the BIOS in bytes
|
||||
pub const size = 0x4000;
|
||||
const Self = @This();
|
||||
|
||||
buf: ?[]u8,
|
||||
|
|
20
src/cpu.zig
20
src/cpu.zig
|
@ -5,7 +5,9 @@ const Bus = @import("Bus.zig");
|
|||
const Bit = @import("bitfield").Bit;
|
||||
const Bitfield = @import("bitfield").Bitfield;
|
||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||
const FilePaths = @import("util.zig").FilePaths;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const File = std.fs.File;
|
||||
|
||||
// ARM Instruction Groups
|
||||
|
@ -59,7 +61,7 @@ pub const Arm7tdmi = struct {
|
|||
|
||||
r: [16]u32,
|
||||
sched: *Scheduler,
|
||||
bus: *Bus,
|
||||
bus: Bus,
|
||||
cpsr: PSR,
|
||||
spsr: PSR,
|
||||
|
||||
|
@ -77,11 +79,11 @@ pub const Arm7tdmi = struct {
|
|||
log_buf: [0x100]u8,
|
||||
binary_log: bool,
|
||||
|
||||
pub fn init(sched: *Scheduler, bus: *Bus) Self {
|
||||
return .{
|
||||
pub fn init(alloc: Allocator, sched: *Scheduler, paths: FilePaths) !Self {
|
||||
var cpu: Arm7tdmi = .{
|
||||
.r = [_]u32{0x00} ** 16,
|
||||
.sched = sched,
|
||||
.bus = bus,
|
||||
.bus = try Bus.init(alloc, sched, paths),
|
||||
.cpsr = .{ .raw = 0x0000_001F },
|
||||
.spsr = .{ .raw = 0x0000_0000 },
|
||||
.banked_fiq = [_]u32{0x00} ** 10,
|
||||
|
@ -91,6 +93,12 @@ pub const Arm7tdmi = struct {
|
|||
.log_buf = undefined,
|
||||
.binary_log = false,
|
||||
};
|
||||
cpu.bus.cpu = &cpu;
|
||||
return cpu;
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
self.bus.deinit();
|
||||
}
|
||||
|
||||
pub fn useLogger(self: *Self, file: *const File, is_binary: bool) void {
|
||||
|
@ -250,13 +258,13 @@ pub const Arm7tdmi = struct {
|
|||
const opcode = self.thumbFetch();
|
||||
if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode);
|
||||
|
||||
thumb_lut[thumbIdx(opcode)](self, self.bus, opcode);
|
||||
thumb_lut[thumbIdx(opcode)](self, &self.bus, opcode);
|
||||
} else {
|
||||
const opcode = self.fetch();
|
||||
if (enable_logging) if (self.log_file) |file| self.debug_log(file, opcode);
|
||||
|
||||
if (checkCond(self.cpsr, @truncate(u4, opcode >> 28))) {
|
||||
arm_lut[armIdx(opcode)](self, self.bus, opcode);
|
||||
arm_lut[armIdx(opcode)](self, &self.bus, opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
42
src/emu.zig
42
src/emu.zig
|
@ -32,42 +32,42 @@ const RunKind = enum {
|
|||
LimitedBusy,
|
||||
};
|
||||
|
||||
pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn run(kind: RunKind, quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
switch (kind) {
|
||||
.Unlimited => runUnsync(quit, sched, cpu, bus),
|
||||
.Limited => runSync(quit, sched, cpu, bus),
|
||||
.UnlimitedFPS => runUnsyncFps(quit, fps, sched, cpu, bus),
|
||||
.LimitedFPS => runSyncFps(quit, fps, sched, cpu, bus),
|
||||
.LimitedBusy => runBusyLoop(quit, sched, cpu, bus),
|
||||
.Unlimited => runUnsync(quit, sched, cpu),
|
||||
.Limited => runSync(quit, sched, cpu),
|
||||
.UnlimitedFPS => runUnsyncFps(quit, fps, sched, cpu),
|
||||
.LimitedFPS => runSyncFps(quit, fps, sched, cpu),
|
||||
.LimitedBusy => runBusyLoop(quit, sched, cpu),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
const frame_end = sched.tick + cycles_per_frame;
|
||||
|
||||
while (sched.tick < frame_end) {
|
||||
if (bus.io.haltcnt == .Halt) sched.tick += 1;
|
||||
if (bus.io.haltcnt == .Execute) cpu.step();
|
||||
bus.handleDMATransfers();
|
||||
if (cpu.bus.io.haltcnt == .Halt) sched.tick += 1;
|
||||
if (cpu.bus.io.haltcnt == .Execute) cpu.step();
|
||||
cpu.bus.handleDMATransfers();
|
||||
|
||||
while (sched.tick >= sched.nextTimestamp()) {
|
||||
sched.handleEvent(cpu, bus);
|
||||
sched.handleEvent(cpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn runUnsync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
log.info("Unsynchronized EmuThread has begun", .{});
|
||||
while (!quit.load(.Unordered)) runFrame(sched, cpu, bus);
|
||||
while (!quit.load(.Unordered)) runFrame(sched, cpu);
|
||||
}
|
||||
|
||||
pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
log.info("Synchronized EmuThread has begun", .{});
|
||||
var timer = Timer.start() catch unreachable;
|
||||
var wake_time: u64 = frame_period;
|
||||
|
||||
while (!quit.load(.Unordered)) {
|
||||
runFrame(sched, cpu, bus);
|
||||
runFrame(sched, cpu);
|
||||
|
||||
// Put the Thread to Sleep + Backup Spin Loop
|
||||
// This saves on resource usage when frame limiting
|
||||
|
@ -78,24 +78,24 @@ pub fn runSync(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus
|
|||
}
|
||||
}
|
||||
|
||||
pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn runUnsyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
log.info("Unsynchronized EmuThread with FPS Tracking has begun", .{});
|
||||
var fps_timer = Timer.start() catch unreachable;
|
||||
|
||||
while (!quit.load(.Unordered)) {
|
||||
runFrame(sched, cpu, bus);
|
||||
runFrame(sched, cpu);
|
||||
fps.add(fps_timer.lap());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
log.info("Synchronized EmuThread has begun", .{});
|
||||
var timer = Timer.start() catch unreachable;
|
||||
var fps_timer = Timer.start() catch unreachable;
|
||||
var wake_time: u64 = frame_period;
|
||||
|
||||
while (!quit.load(.Unordered)) {
|
||||
runFrame(sched, cpu, bus);
|
||||
runFrame(sched, cpu);
|
||||
|
||||
// Put the Thread to Sleep + Backup Spin Loop
|
||||
// This saves on resource usage when frame limiting
|
||||
|
@ -109,13 +109,13 @@ pub fn runSyncFps(quit: *Atomic(bool), fps: *FpsAverage, sched: *Scheduler, cpu:
|
|||
}
|
||||
}
|
||||
|
||||
pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn runBusyLoop(quit: *Atomic(bool), sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||
log.info("Run EmuThread with spin-loop sync", .{});
|
||||
var timer = Timer.start() catch unreachable;
|
||||
var wake_time: u64 = frame_period;
|
||||
|
||||
while (!quit.load(.Unordered)) {
|
||||
runFrame(sched, cpu, bus);
|
||||
runFrame(sched, cpu);
|
||||
spinLoop(&timer, wake_time);
|
||||
|
||||
// Update to the new wake time
|
||||
|
|
54
src/main.zig
54
src/main.zig
|
@ -92,10 +92,10 @@ pub fn main() anyerror!void {
|
|||
defer scheduler.deinit();
|
||||
|
||||
const paths = .{ .bios = bios_path, .rom = rom_path, .save = save_path };
|
||||
var bus = try Bus.init(alloc, &scheduler, audio_dev, paths);
|
||||
defer bus.deinit();
|
||||
var cpu = try Arm7tdmi.init(alloc, &scheduler, paths);
|
||||
defer cpu.deinit();
|
||||
|
||||
var cpu = Arm7tdmi.init(&scheduler, &bus);
|
||||
cpu.bus.apu.attachAudioDevice(audio_dev);
|
||||
cpu.fastBoot();
|
||||
|
||||
const log_file: ?File = if (enable_logging) blk: {
|
||||
|
@ -110,10 +110,10 @@ pub fn main() anyerror!void {
|
|||
var emu_rate = FpsAverage.init();
|
||||
|
||||
// Create Emulator Thread
|
||||
const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu, &bus });
|
||||
const emu_thread = try Thread.spawn(.{}, emu.run, .{ .LimitedFPS, &quit, &emu_rate, &scheduler, &cpu });
|
||||
defer emu_thread.join();
|
||||
|
||||
const title = correctTitle(bus.pak.title);
|
||||
const title = correctTitle(cpu.bus.pak.title);
|
||||
|
||||
var title_buf: [0x20]u8 = std.mem.zeroes([0x20]u8);
|
||||
const window_title = try std.fmt.bufPrint(&title_buf, "ZBA | {s}", .{title});
|
||||
|
@ -145,36 +145,38 @@ pub fn main() anyerror!void {
|
|||
switch (event.type) {
|
||||
SDL.SDL_QUIT => break :emu_loop,
|
||||
SDL.SDL_KEYDOWN => {
|
||||
const io = &cpu.bus.io;
|
||||
const key_code = event.key.keysym.sym;
|
||||
|
||||
switch (key_code) {
|
||||
SDL.SDLK_UP => bus.io.keyinput.up.unset(),
|
||||
SDL.SDLK_DOWN => bus.io.keyinput.down.unset(),
|
||||
SDL.SDLK_LEFT => bus.io.keyinput.left.unset(),
|
||||
SDL.SDLK_RIGHT => bus.io.keyinput.right.unset(),
|
||||
SDL.SDLK_x => bus.io.keyinput.a.unset(),
|
||||
SDL.SDLK_z => bus.io.keyinput.b.unset(),
|
||||
SDL.SDLK_a => bus.io.keyinput.shoulder_l.unset(),
|
||||
SDL.SDLK_s => bus.io.keyinput.shoulder_r.unset(),
|
||||
SDL.SDLK_RETURN => bus.io.keyinput.start.unset(),
|
||||
SDL.SDLK_RSHIFT => bus.io.keyinput.select.unset(),
|
||||
SDL.SDLK_UP => io.keyinput.up.unset(),
|
||||
SDL.SDLK_DOWN => io.keyinput.down.unset(),
|
||||
SDL.SDLK_LEFT => io.keyinput.left.unset(),
|
||||
SDL.SDLK_RIGHT => io.keyinput.right.unset(),
|
||||
SDL.SDLK_x => io.keyinput.a.unset(),
|
||||
SDL.SDLK_z => io.keyinput.b.unset(),
|
||||
SDL.SDLK_a => io.keyinput.shoulder_l.unset(),
|
||||
SDL.SDLK_s => io.keyinput.shoulder_r.unset(),
|
||||
SDL.SDLK_RETURN => io.keyinput.start.unset(),
|
||||
SDL.SDLK_RSHIFT => io.keyinput.select.unset(),
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
SDL.SDL_KEYUP => {
|
||||
const io = &cpu.bus.io;
|
||||
const key_code = event.key.keysym.sym;
|
||||
|
||||
switch (key_code) {
|
||||
SDL.SDLK_UP => bus.io.keyinput.up.set(),
|
||||
SDL.SDLK_DOWN => bus.io.keyinput.down.set(),
|
||||
SDL.SDLK_LEFT => bus.io.keyinput.left.set(),
|
||||
SDL.SDLK_RIGHT => bus.io.keyinput.right.set(),
|
||||
SDL.SDLK_x => bus.io.keyinput.a.set(),
|
||||
SDL.SDLK_z => bus.io.keyinput.b.set(),
|
||||
SDL.SDLK_a => bus.io.keyinput.shoulder_l.set(),
|
||||
SDL.SDLK_s => bus.io.keyinput.shoulder_r.set(),
|
||||
SDL.SDLK_RETURN => bus.io.keyinput.start.set(),
|
||||
SDL.SDLK_RSHIFT => bus.io.keyinput.select.set(),
|
||||
SDL.SDLK_UP => io.keyinput.up.set(),
|
||||
SDL.SDLK_DOWN => io.keyinput.down.set(),
|
||||
SDL.SDLK_LEFT => io.keyinput.left.set(),
|
||||
SDL.SDLK_RIGHT => io.keyinput.right.set(),
|
||||
SDL.SDLK_x => io.keyinput.a.set(),
|
||||
SDL.SDLK_z => io.keyinput.b.set(),
|
||||
SDL.SDLK_a => io.keyinput.shoulder_l.set(),
|
||||
SDL.SDLK_s => io.keyinput.shoulder_r.set(),
|
||||
SDL.SDLK_RETURN => io.keyinput.start.set(),
|
||||
SDL.SDLK_RSHIFT => io.keyinput.select.set(),
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
@ -183,7 +185,7 @@ pub fn main() anyerror!void {
|
|||
}
|
||||
|
||||
// FIXME: Is it OK just to copy the Emulator's Frame Buffer to SDL?
|
||||
const buf_ptr = bus.ppu.framebuf.ptr;
|
||||
const buf_ptr = cpu.bus.ppu.framebuf.ptr;
|
||||
_ = SDL.SDL_UpdateTexture(texture, null, buf_ptr, framebuf_pitch);
|
||||
_ = SDL.SDL_RenderCopy(renderer, texture, null, null);
|
||||
SDL.SDL_RenderPresent(renderer);
|
||||
|
|
|
@ -366,7 +366,7 @@ pub const Ppu = struct {
|
|||
}
|
||||
|
||||
// See if HBlank DMA is present and not enabled
|
||||
pollBlankingDma(cpu.bus, .HBlank);
|
||||
pollBlankingDma(&cpu.bus, .HBlank);
|
||||
|
||||
self.dispstat.hblank.set();
|
||||
self.sched.push(.HBlank, self.sched.now() + (68 * 4) - late);
|
||||
|
@ -403,7 +403,7 @@ pub const Ppu = struct {
|
|||
}
|
||||
|
||||
// See if Vblank DMA is present and not enabled
|
||||
pollBlankingDma(cpu.bus, .VBlank);
|
||||
pollBlankingDma(&cpu.bus, .VBlank);
|
||||
}
|
||||
|
||||
if (scanline == 227) self.dispstat.vblank.unset();
|
||||
|
|
|
@ -29,7 +29,7 @@ pub const Scheduler = struct {
|
|||
return self.tick;
|
||||
}
|
||||
|
||||
pub fn handleEvent(self: *Self, cpu: *Arm7tdmi, bus: *Bus) void {
|
||||
pub fn handleEvent(self: *Self, cpu: *Arm7tdmi) void {
|
||||
if (self.queue.removeOrNull()) |event| {
|
||||
const late = self.tick - event.tick;
|
||||
|
||||
|
@ -40,19 +40,19 @@ pub const Scheduler = struct {
|
|||
},
|
||||
.Draw => {
|
||||
// The end of a VDraw
|
||||
bus.ppu.drawScanline();
|
||||
bus.ppu.handleHDrawEnd(cpu, late);
|
||||
cpu.bus.ppu.drawScanline();
|
||||
cpu.bus.ppu.handleHDrawEnd(cpu, late);
|
||||
},
|
||||
.TimerOverflow => |id| {
|
||||
switch (id) {
|
||||
0 => bus.tim._0.handleOverflow(cpu, late),
|
||||
1 => bus.tim._1.handleOverflow(cpu, late),
|
||||
2 => bus.tim._2.handleOverflow(cpu, late),
|
||||
3 => bus.tim._3.handleOverflow(cpu, late),
|
||||
0 => cpu.bus.tim._0.handleOverflow(cpu, late),
|
||||
1 => cpu.bus.tim._1.handleOverflow(cpu, late),
|
||||
2 => cpu.bus.tim._2.handleOverflow(cpu, late),
|
||||
3 => cpu.bus.tim._3.handleOverflow(cpu, late),
|
||||
}
|
||||
},
|
||||
.HBlank => bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank
|
||||
.VBlank => bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank
|
||||
.HBlank => cpu.bus.ppu.handleHBlankEnd(cpu, late), // The end of a HBlank
|
||||
.VBlank => cpu.bus.ppu.handleHDrawEnd(cpu, late), // The end of a VBlank
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue