Compare commits

...

2 Commits

4 changed files with 108 additions and 36 deletions

View File

@ -35,6 +35,11 @@ pub fn DmaController(comptime id: u2) type {
/// Internal. Word Count /// Internal. Word Count
_word_count: if (id == 3) u16 else u14, _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 { pub fn init() Self {
return .{ return .{
.id = id, .id = id,
@ -47,6 +52,7 @@ pub fn DmaController(comptime id: u2) type {
._sad = 0, ._sad = 0,
._dad = 0, ._dad = 0,
._word_count = 0, ._word_count = 0,
.enabled = false,
}; };
} }
@ -70,6 +76,9 @@ pub fn DmaController(comptime id: u2) type {
self._sad = self.sad; self._sad = self.sad;
self._dad = self.dad; self._dad = self.dad;
self._word_count = if (self.word_count == 0) std.math.maxInt(@TypeOf(self._word_count)) else self.word_count; 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; self.cnt.raw = halfword;
@ -80,7 +89,9 @@ pub fn DmaController(comptime id: u2) type {
self.writeCntHigh(@truncate(u16, word >> 16)); 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 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; 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) { switch (sad_adj) {
.Increment => self._sad += offset, .Increment => self._sad +%= offset,
.Decrement => self._sad -= offset, .Decrement => self._sad -%= offset,
.Fixed => {}, .Fixed => {},
// TODO: Figure out correct behaviour on Illegal Source Addr Control Type // TODO: Figure out correct behaviour on Illegal Source Addr Control Type
@ -105,32 +116,77 @@ pub fn DmaController(comptime id: u2) type {
} }
switch (dad_adj) { switch (dad_adj) {
.Increment, .IncrementReload => self._dad += offset, .Increment, .IncrementReload => self._dad +%= offset,
.Decrement => self._dad -= offset, .Decrement => self._dad -%= offset,
.Fixed => {}, .Fixed => {},
} }
self._word_count -= 1; self._word_count -= 1;
if (self._word_count == 0) { if (self._word_count == 0) {
if (self.cnt.irq.read()) { if (!self.cnt.repeat.read()) {
switch (id) { // If we're not repeating, Fire the IRQs and disable the DMA
0 => bus.io.irq.dma0.set(), if (self.cnt.irq.read()) {
1 => bus.io.irq.dma0.set(), switch (id) {
2 => bus.io.irq.dma0.set(), 0 => bus.io.irq.dma0.set(),
3 => bus.io.irq.dma0.set(), 1 => bus.io.irq.dma0.set(),
2 => bus.io.irq.dma0.set(),
3 => bus.io.irq.dma0.set(),
}
} }
self.cnt.enabled.unset();
} }
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) { const Adjustment = enum(u2) {
Increment = 0, Increment = 0,
Decrement = 1, Decrement = 1,
Fixed = 2, Fixed = 2,
IncrementReload = 3, IncrementReload = 3,
}; };
const DmaKind = enum(u2) {
Immediate = 0,
HBlank,
VBlank,
Special,
};

View File

@ -296,30 +296,10 @@ pub const Arm7tdmi = struct {
} }
fn handleDMATransfers(self: *Self) bool { fn handleDMATransfers(self: *Self) bool {
const dma0 = &self.bus.io.dma0; if (self.bus.io.dma0.step(self.bus)) return self.bus.io.dma0.isBlocking();
const dma1 = &self.bus.io.dma1; if (self.bus.io.dma1.step(self.bus)) return self.bus.io.dma1.isBlocking();
const dma2 = &self.bus.io.dma2; if (self.bus.io.dma2.step(self.bus)) return self.bus.io.dma2.isBlocking();
const dma3 = &self.bus.io.dma3; if (self.bus.io.dma3.step(self.bus)) return self.bus.io.dma3.isBlocking();
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;
}
return false; return false;
} }

View File

@ -366,3 +366,28 @@ const ScreenEntry = extern union {
palette_bank: Bitfield(u16, 12, 4), palette_bank: Bitfield(u16, 12, 4),
raw: u16, raw: u16,
}; };
const Attr0 = extern union {
y: Bitfield(u16, 0, 8),
rot_scaling: Bit(u16, 8), // This SBZ
disabled: Bit(u16, 9),
mode: Bitfield(u16, 10, 2),
mosaic: Bit(u16, 12),
is_8bpp: Bit(u16, 13),
shape: Bit(u16, 14, 2),
raw: u16,
};
const Attr1 = extern union {
x: Bitfield(u16, 0, 9),
h_flip: Bit(u16, 12),
v_flip: Bit(u16, 13),
size: Bitfield(u16, 14, 2),
raw: u16,
};
const Attr2 = extern union {
tile_id: Bitfield(u16, 0, 10),
rel_prio: Bitfield(u16, 10, 2),
pal_id: Bitfield(u16, 12, 3),
};

View File

@ -1,5 +1,7 @@
const std = @import("std"); const std = @import("std");
const pollBlankingDma = @import("bus/dma.zig").pollBlankingDma;
const Bus = @import("Bus.zig"); const Bus = @import("Bus.zig");
const Arm7tdmi = @import("cpu.zig").Arm7tdmi; const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
@ -68,6 +70,9 @@ pub const Scheduler = struct {
irq.vblank.set(); irq.vblank.set();
cpu.handleInterrupt(); cpu.handleInterrupt();
} }
// See if Vblank DMA is present and not enabled
pollBlankingDma(bus, .VBlank);
} }
if (scanline == 227) stat.vblank.unset(); if (scanline == 227) stat.vblank.unset();
@ -84,6 +89,9 @@ pub const Scheduler = struct {
cpu.handleInterrupt(); cpu.handleInterrupt();
} }
// See if Hblank DMA is present and not enabled
pollBlankingDma(bus, .HBlank);
bus.ppu.dispstat.hblank.set(); bus.ppu.dispstat.hblank.set();
self.push(.HBlank, self.tick + (68 * 4)); self.push(.HBlank, self.tick + (68 * 4));
}, },
@ -96,6 +104,9 @@ pub const Scheduler = struct {
cpu.handleInterrupt(); cpu.handleInterrupt();
} }
// See if Hblank DMA is present and not enabled
pollBlankingDma(bus, .HBlank);
bus.ppu.dispstat.hblank.set(); bus.ppu.dispstat.hblank.set();
self.push(.HBlank, self.tick + (68 * 4)); self.push(.HBlank, self.tick + (68 * 4));
}, },