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

View File

@ -129,8 +129,9 @@ pub fn runFrame(scheduler: *Scheduler, system: System) void {
.nds7 => system.bus7,
.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
pub const IntEnable = extern union {
vblank: Bit(u32, 0),
hblank: Bit(u32, 1),
coincidence: Bit(u32, 2),
ipcsync: Bit(u32, 16),
ipc_send_empty: Bit(u32, 17),
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 EngineB = @import("ppu/engine.zig").EngineB;
const handleInterrupt = @import("emu.zig").handleInterrupt;
pub const screen_width = 256;
pub const screen_height = 192;
const KiB = 0x400;
@ -59,35 +61,49 @@ pub const Ppu = struct {
}
/// HDraw -> HBlank
pub fn onHdrawEnd(self: *@This(), scheduler: *Scheduler, late: u64) void {
std.debug.assert(self.io.nds9.dispstat.hblank.read() == false);
std.debug.assert(self.io.nds9.dispstat.vblank.read() == false);
pub fn onHdrawEnd(self: *@This(), system: System, scheduler: *Scheduler, late: u64) void {
if (self.io.nds9.dispstat.hblank_irq.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);
}
// TODO: Run DMAs on HBlank
self.io.nds9.dispstat.hblank.set();
self.io.nds7.dispstat.hblank.set();
// TODO: Signal HBlank IRQ
const dots_in_hblank = 99;
scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late);
}
// VBlank -> HBlank (Still VBlank)
pub fn onVblankEnd(self: *@This(), scheduler: *Scheduler, late: u64) void {
std.debug.assert(!self.io.nds9.dispstat.hblank.read());
std.debug.assert(self.io.nds9.vcount.scanline.read() == 262 or self.io.nds9.dispstat.vblank.read());
/// VBlank -> HBlank (Still VBlank)
pub fn onVblankEnd(self: *@This(), system: System, scheduler: *Scheduler, late: u64) void {
if (self.io.nds9.dispstat.hblank_irq.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.nds7.dispstat.hblank.set();
// TODO: Signal HBlank IRQ
// TODO: Run DMAs on HBlank
const dots_in_hblank = 99;
scheduler.push(.{ .nds9 = .hblank }, dots_in_hblank * cycles_per_dot -| late);
}
/// 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 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();
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();
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) {
std.debug.assert(self.io.nds9.dispstat.vblank.read() == false);
std.debug.assert(self.io.nds9.dispstat.hblank.read() == false);
// Draw Another Scanline
const dots_in_hdraw = 256;
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
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.nds7.dispstat.vblank.set();
// TODO: Signal VBlank IRQ
// TODO: Affine BG Latches
// TODO: VBlank DMA Transfers
}
if (scanline == 262) {
self.io.nds9.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;