feat(ppu): implement vblank, hblank and coincidence interrupts

This commit is contained in:
Rekai Nyangadzayi Musuka 2023-11-04 03:22:11 -05:00
parent bf08e93508
commit 37aff56c22
4 changed files with 63 additions and 28 deletions

View File

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const Bus9 = @import("nds9/Bus.zig"); const System = @import("emu.zig").System;
const Bus7 = @import("nds7/Bus.zig");
const PriorityQueue = std.PriorityQueue(Event, void, Event.lessThan); const PriorityQueue = std.PriorityQueue(Event, void, Event.lessThan);
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@ -52,25 +51,25 @@ pub inline fn check(self: *@This()) ?Event {
return self.queue.remove(); return self.queue.remove();
} }
pub fn handle(self: *@This(), bus_ptr: ?*anyopaque, event: Event, late: u64) void { pub fn handle(self: *@This(), system: System, event: Event, late: u64) void {
switch (event.kind) { switch (event.kind) {
.heat_death => unreachable, .heat_death => unreachable,
.nds7 => |ev| { .nds7 => |ev| {
const bus: *Bus7 = @ptrCast(@alignCast(bus_ptr)); const bus = system.bus7;
_ = bus; _ = bus;
switch (ev) {} switch (ev) {}
}, },
.nds9 => |ev| { .nds9 => |ev| {
const bus: *Bus9 = @ptrCast(@alignCast(bus_ptr)); const bus = system.bus9;
switch (ev) { switch (ev) {
.draw => { .draw => {
bus.ppu.drawScanline(bus); bus.ppu.drawScanline(bus);
bus.ppu.onHdrawEnd(self, late); bus.ppu.onHdrawEnd(system, self, late);
}, },
.hblank => bus.ppu.onHblankEnd(self, late), .hblank => bus.ppu.onHblankEnd(system, self, late),
.vblank => bus.ppu.onVblankEnd(self, late), .vblank => bus.ppu.onVblankEnd(system, self, late),
.sqrt => bus.io.sqrt.onSqrtCalc(), .sqrt => bus.io.sqrt.onSqrtCalc(),
.div => bus.io.div.onDivCalc(), .div => bus.io.div.onDivCalc(),
} }

View File

@ -129,8 +129,9 @@ pub fn runFrame(scheduler: *Scheduler, system: System) void {
.nds7 => system.bus7, .nds7 => system.bus7,
.nds9 => system.bus9, .nds9 => system.bus9,
}; };
_ = bus_ptr;
scheduler.handle(bus_ptr, ev, late); scheduler.handle(system, ev, late);
} }
} }
} }

View File

@ -320,6 +320,10 @@ pub const masks = struct {
// FIXME: bitfields depends on NDS9 / NDS7 // FIXME: bitfields depends on NDS9 / NDS7
pub const IntEnable = extern union { pub const IntEnable = extern union {
vblank: Bit(u32, 0),
hblank: Bit(u32, 1),
coincidence: Bit(u32, 2),
ipcsync: Bit(u32, 16), ipcsync: Bit(u32, 16),
ipc_send_empty: Bit(u32, 17), ipc_send_empty: Bit(u32, 17),
ipc_recv_not_empty: Bit(u32, 18), ipc_recv_not_empty: Bit(u32, 18),

View File

@ -8,6 +8,8 @@ const Vram = @import("ppu/Vram.zig");
const EngineA = @import("ppu/engine.zig").EngineA; const EngineA = @import("ppu/engine.zig").EngineA;
const EngineB = @import("ppu/engine.zig").EngineB; const EngineB = @import("ppu/engine.zig").EngineB;
const handleInterrupt = @import("emu.zig").handleInterrupt;
pub const screen_width = 256; pub const screen_width = 256;
pub const screen_height = 192; pub const screen_height = 192;
const KiB = 0x400; const KiB = 0x400;
@ -59,35 +61,49 @@ pub const Ppu = struct {
} }
/// HDraw -> HBlank /// HDraw -> HBlank
pub fn onHdrawEnd(self: *@This(), scheduler: *Scheduler, late: u64) void { pub fn onHdrawEnd(self: *@This(), system: System, scheduler: *Scheduler, late: u64) void {
std.debug.assert(self.io.nds9.dispstat.hblank.read() == false); if (self.io.nds9.dispstat.hblank_irq.read()) {
std.debug.assert(self.io.nds9.dispstat.vblank.read() == false); system.bus9.io.irq.hblank.set();
handleInterrupt(.nds9, system.arm946es);
}
if (self.io.nds7.dispstat.hblank_irq.read()) {
system.bus7.io.irq.hblank.set();
handleInterrupt(.nds7, system.arm7tdmi);
}
// TODO: Run DMAs on HBlank
self.io.nds9.dispstat.hblank.set(); self.io.nds9.dispstat.hblank.set();
self.io.nds7.dispstat.hblank.set(); self.io.nds7.dispstat.hblank.set();
// TODO: Signal HBlank IRQ
const dots_in_hblank = 99; const dots_in_hblank = 99;
scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late); scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late);
} }
// VBlank -> HBlank (Still VBlank) /// VBlank -> HBlank (Still VBlank)
pub fn onVblankEnd(self: *@This(), scheduler: *Scheduler, late: u64) void { pub fn onVblankEnd(self: *@This(), system: System, scheduler: *Scheduler, late: u64) void {
std.debug.assert(!self.io.nds9.dispstat.hblank.read()); if (self.io.nds9.dispstat.hblank_irq.read()) {
std.debug.assert(self.io.nds9.vcount.scanline.read() == 262 or self.io.nds9.dispstat.vblank.read()); system.bus9.io.irq.hblank.set();
handleInterrupt(.nds9, system.arm946es);
}
if (self.io.nds7.dispstat.hblank_irq.read()) {
system.bus7.io.irq.hblank.set();
handleInterrupt(.nds7, system.arm7tdmi);
}
self.io.nds9.dispstat.hblank.set(); self.io.nds9.dispstat.hblank.set();
self.io.nds7.dispstat.hblank.set(); self.io.nds7.dispstat.hblank.set();
// TODO: Signal HBlank IRQ // TODO: Run DMAs on HBlank
const dots_in_hblank = 99; const dots_in_hblank = 99;
scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late); scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late);
} }
/// HBlank -> HDraw / VBlank /// HBlank -> HDraw / VBlank
pub fn onHblankEnd(self: *@This(), scheduler: *Scheduler, late: u64) void { pub fn onHblankEnd(self: *@This(), system: System, scheduler: *Scheduler, late: u64) void {
const scanline_total = 263; // 192 visible, 71 blanking const scanline_total = 263; // 192 visible, 71 blanking
const prev_scanline = self.io.nds9.vcount.scanline.read(); const prev_scanline = self.io.nds9.vcount.scanline.read();
@ -102,18 +118,24 @@ pub const Ppu = struct {
{ {
const coincidence = scanline == self.io.nds9.dispstat.lyc.read(); const coincidence = scanline == self.io.nds9.dispstat.lyc.read();
self.io.nds9.dispstat.coincidence.write(coincidence); self.io.nds9.dispstat.coincidence.write(coincidence);
if (coincidence and self.io.nds9.dispstat.vcount_irq.read()) {
system.bus9.io.irq.coincidence.set();
handleInterrupt(.nds9, system.arm946es);
} }
}
{ {
const coincidence = scanline == self.io.nds7.dispstat.lyc.read(); const coincidence = scanline == self.io.nds7.dispstat.lyc.read();
self.io.nds7.dispstat.coincidence.write(coincidence); self.io.nds7.dispstat.coincidence.write(coincidence);
if (coincidence and self.io.nds7.dispstat.vcount_irq.read()) {
system.bus7.io.irq.coincidence.set();
handleInterrupt(.nds7, system.arm7tdmi);
}
} }
// TODO: LYC == LY IRQ
if (scanline < 192) { if (scanline < 192) {
std.debug.assert(self.io.nds9.dispstat.vblank.read() == false);
std.debug.assert(self.io.nds9.dispstat.hblank.read() == false);
// Draw Another Scanline // Draw Another Scanline
const dots_in_hdraw = 256; const dots_in_hdraw = 256;
return scheduler.push(.{ .nds9 = .draw }, dots_in_hdraw * cycles_per_dot -| late); return scheduler.push(.{ .nds9 = .draw }, dots_in_hdraw * cycles_per_dot -| late);
@ -123,17 +145,26 @@ pub const Ppu = struct {
// Transition from Hblank to Vblank // Transition from Hblank to Vblank
self.fb.swap(); self.fb.swap();
if (self.io.nds9.dispstat.vblank_irq.read()) {
system.bus9.io.irq.vblank.set();
handleInterrupt(.nds9, system.arm946es);
}
if (self.io.nds7.dispstat.vblank_irq.read()) {
system.bus7.io.irq.vblank.set();
handleInterrupt(.nds7, system.arm7tdmi);
}
self.io.nds9.dispstat.vblank.set(); self.io.nds9.dispstat.vblank.set();
self.io.nds7.dispstat.vblank.set(); self.io.nds7.dispstat.vblank.set();
// TODO: Signal VBlank IRQ // TODO: Affine BG Latches
// TODO: VBlank DMA Transfers
} }
if (scanline == 262) { if (scanline == 262) {
self.io.nds9.dispstat.vblank.unset(); self.io.nds9.dispstat.vblank.unset();
self.io.nds7.dispstat.vblank.unset(); self.io.nds7.dispstat.vblank.unset();
std.debug.assert(self.io.nds9.dispstat.vblank.read() == (scanline != 262));
} }
const dots_in_vblank = 256; const dots_in_vblank = 256;