feat: improve DMA Transfer support
This commit is contained in:
parent
dccd00782b
commit
85f0b13f4a
|
@ -35,6 +35,11 @@ pub fn DmaController(comptime id: u2) type {
|
|||
/// Internal. Word Count
|
||||
_word_count: if (id == 3) u16 else u14,
|
||||
|
||||
/// Some DMA Transfers are enabled during Hblank / VBlank and / or
|
||||
/// have delays. Thefore bit 15 of DMACNT isn't actually something
|
||||
/// we can use to control when we do or do not execute a step in a DMA Transfer
|
||||
enabled: bool,
|
||||
|
||||
pub fn init() Self {
|
||||
return .{
|
||||
.id = id,
|
||||
|
@ -47,6 +52,7 @@ pub fn DmaController(comptime id: u2) type {
|
|||
._sad = 0,
|
||||
._dad = 0,
|
||||
._word_count = 0,
|
||||
.enabled = false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -70,6 +76,9 @@ pub fn DmaController(comptime id: u2) type {
|
|||
self._sad = self.sad;
|
||||
self._dad = self.dad;
|
||||
self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count;
|
||||
|
||||
// Only a Start Timing of 00 has a DMA Transfer immediately begin
|
||||
self.enabled = new.start_timing.read() == 0b00;
|
||||
}
|
||||
|
||||
self.cnt.raw = halfword;
|
||||
|
@ -80,7 +89,9 @@ pub fn DmaController(comptime id: u2) type {
|
|||
self.writeCntHigh(@truncate(u16, word >> 16));
|
||||
}
|
||||
|
||||
pub fn step(self: *Self, bus: *Bus) void {
|
||||
pub fn step(self: *Self, bus: *Bus) bool {
|
||||
if (!self.enabled or !self.cnt.enabled.read()) return false;
|
||||
|
||||
const sad_adj = std.meta.intToEnum(Adjustment, self.cnt.sad_adj.read()) catch unreachable;
|
||||
const dad_adj = std.meta.intToEnum(Adjustment, self.cnt.dad_adj.read()) catch unreachable;
|
||||
|
||||
|
@ -96,8 +107,8 @@ pub fn DmaController(comptime id: u2) type {
|
|||
}
|
||||
|
||||
switch (sad_adj) {
|
||||
.Increment => self._sad += offset,
|
||||
.Decrement => self._sad -= offset,
|
||||
.Increment => self._sad +%= offset,
|
||||
.Decrement => self._sad -%= offset,
|
||||
.Fixed => {},
|
||||
|
||||
// TODO: Figure out correct behaviour on Illegal Source Addr Control Type
|
||||
|
@ -105,14 +116,16 @@ pub fn DmaController(comptime id: u2) type {
|
|||
}
|
||||
|
||||
switch (dad_adj) {
|
||||
.Increment, .IncrementReload => self._dad += offset,
|
||||
.Decrement => self._dad -= offset,
|
||||
.Increment, .IncrementReload => self._dad +%= offset,
|
||||
.Decrement => self._dad -%= offset,
|
||||
.Fixed => {},
|
||||
}
|
||||
|
||||
self._word_count -= 1;
|
||||
|
||||
if (self._word_count == 0) {
|
||||
if (!self.cnt.repeat.read()) {
|
||||
// If we're not repeating, Fire the IRQs and disable the DMA
|
||||
if (self.cnt.irq.read()) {
|
||||
switch (id) {
|
||||
0 => bus.io.irq.dma0.set(),
|
||||
|
@ -121,16 +134,59 @@ pub fn DmaController(comptime id: u2) type {
|
|||
3 => bus.io.irq.dma0.set(),
|
||||
}
|
||||
}
|
||||
|
||||
self.cnt.enabled.unset();
|
||||
}
|
||||
|
||||
// We want to disable our internal enabled flag regardless of repeat
|
||||
// because we only want to step A DMA that repeats during it's specific
|
||||
// timing window
|
||||
self.enabled = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn isBlocking(self: *const Self) bool {
|
||||
// A DMA Transfer is Blocking if it is Immediate
|
||||
return self.cnt.start_timing.read() == 0b00;
|
||||
}
|
||||
|
||||
pub fn pollBlankingDma(self: *Self, comptime kind: DmaKind) void {
|
||||
if (self.enabled) return;
|
||||
|
||||
switch (kind) {
|
||||
.HBlank => self.enabled = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10,
|
||||
.VBlank => self.enabled = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01,
|
||||
.Immediate, .Special => {},
|
||||
}
|
||||
|
||||
if (self.cnt.repeat.read() and self.enabled) {
|
||||
self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count;
|
||||
|
||||
const dad_adj = std.meta.intToEnum(Adjustment, self.cnt.dad_adj.read()) catch unreachable;
|
||||
if (dad_adj == .IncrementReload) self._dad = self.dad;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pollBlankingDma(bus: *Bus, comptime kind: DmaKind) void {
|
||||
bus.io.dma0.pollBlankingDma(kind);
|
||||
bus.io.dma1.pollBlankingDma(kind);
|
||||
bus.io.dma2.pollBlankingDma(kind);
|
||||
bus.io.dma3.pollBlankingDma(kind);
|
||||
}
|
||||
|
||||
const Adjustment = enum(u2) {
|
||||
Increment = 0,
|
||||
Decrement = 1,
|
||||
Fixed = 2,
|
||||
IncrementReload = 3,
|
||||
};
|
||||
|
||||
const DmaKind = enum(u2) {
|
||||
Immediate = 0,
|
||||
HBlank,
|
||||
VBlank,
|
||||
Special,
|
||||
};
|
||||
|
|
28
src/cpu.zig
28
src/cpu.zig
|
@ -296,30 +296,10 @@ pub const Arm7tdmi = struct {
|
|||
}
|
||||
|
||||
fn handleDMATransfers(self: *Self) bool {
|
||||
const dma0 = &self.bus.io.dma0;
|
||||
const dma1 = &self.bus.io.dma1;
|
||||
const dma2 = &self.bus.io.dma2;
|
||||
const dma3 = &self.bus.io.dma3;
|
||||
|
||||
if (dma0.cnt.enabled.read() and dma0.cnt.start_timing.read() == 0) {
|
||||
dma0.step(self.bus);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dma1.cnt.enabled.read() and dma1.cnt.start_timing.read() == 0) {
|
||||
dma1.step(self.bus);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dma2.cnt.enabled.read() and dma2.cnt.start_timing.read() == 0) {
|
||||
dma2.step(self.bus);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dma3.cnt.enabled.read() and dma3.cnt.start_timing.read() == 0) {
|
||||
dma3.step(self.bus);
|
||||
return true;
|
||||
}
|
||||
if (self.bus.io.dma0.step(self.bus)) return self.bus.io.dma0.isBlocking();
|
||||
if (self.bus.io.dma1.step(self.bus)) return self.bus.io.dma1.isBlocking();
|
||||
if (self.bus.io.dma2.step(self.bus)) return self.bus.io.dma2.isBlocking();
|
||||
if (self.bus.io.dma3.step(self.bus)) return self.bus.io.dma3.isBlocking();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const std = @import("std");
|
||||
|
||||
const pollBlankingDma = @import("bus/dma.zig").pollBlankingDma;
|
||||
|
||||
const Bus = @import("Bus.zig");
|
||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||
|
||||
|
@ -68,6 +70,9 @@ pub const Scheduler = struct {
|
|||
irq.vblank.set();
|
||||
cpu.handleInterrupt();
|
||||
}
|
||||
|
||||
// See if Vblank DMA is present and not enabled
|
||||
pollBlankingDma(bus, .VBlank);
|
||||
}
|
||||
|
||||
if (scanline == 227) stat.vblank.unset();
|
||||
|
@ -84,6 +89,9 @@ pub const Scheduler = struct {
|
|||
cpu.handleInterrupt();
|
||||
}
|
||||
|
||||
// See if Hblank DMA is present and not enabled
|
||||
pollBlankingDma(bus, .HBlank);
|
||||
|
||||
bus.ppu.dispstat.hblank.set();
|
||||
self.push(.HBlank, self.tick + (68 * 4));
|
||||
},
|
||||
|
@ -96,6 +104,9 @@ pub const Scheduler = struct {
|
|||
cpu.handleInterrupt();
|
||||
}
|
||||
|
||||
// See if Hblank DMA is present and not enabled
|
||||
pollBlankingDma(bus, .HBlank);
|
||||
|
||||
bus.ppu.dispstat.hblank.set();
|
||||
self.push(.HBlank, self.tick + (68 * 4));
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue