feat: Initial Implementation of DMA Audio

This commit is contained in:
2022-04-10 04:28:05 -03:00
parent c100d64fcb
commit 76b4d56ca6
9 changed files with 233 additions and 71 deletions

View File

@@ -53,10 +53,13 @@ fn DmaController(comptime id: u2) type {
/// Internal. Word Count
_word_count: if (id == 3) u16 else u14,
// Internal. FIFO Word Count
_fifo_word_count: u8,
/// 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,
active: bool,
pub fn init() Self {
return .{
@@ -70,7 +73,8 @@ fn DmaController(comptime id: u2) type {
._sad = 0,
._dad = 0,
._word_count = 0,
.enabled = false,
._fifo_word_count = 4,
.active = false,
};
}
@@ -96,7 +100,7 @@ fn DmaController(comptime id: u2) type {
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.active = new.start_timing.read() == 0b00;
}
self.cnt.raw = halfword;
@@ -108,27 +112,50 @@ fn DmaController(comptime id: u2) type {
}
pub inline fn check(self: *Self, bus: *Bus) bool {
if (!self.enabled) return false; // FIXME: Check CNT register?
if (!self.active) return false; // FIXME: Check CNT register?
self.step(bus);
return true;
}
pub fn step(self: *Self, bus: *Bus) void {
@setCold(true);
pub fn step(self: *Self, bus: *Bus) bool {
if (!self.active) 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;
const is_fifo = (self.id == 1 or self.id == 2) and self.cnt.start_timing.read() == 0b11;
var offset: u32 = 0;
if (self.cnt.transfer_type.read()) {
offset = @sizeOf(u32); // 32-bit Transfer
const word = bus.read(u32, self._sad);
bus.write(u32, self._dad, word);
// // if (is_fifo) {
// // const offset = @sizeOf(u32);
// // bus.write(u32, self._dad, bus.read(u32, self._sad));
// // // TODO: Deduplicate
// // switch (sad_adj) {
// // .Increment => self._sad +%= offset,
// // .Decrement => self._sad -%= offset,
// // .Fixed => {},
// // // TODO: Figure out correct behaviour on Illegal Source Addr Control Type
// // .IncrementReload => std.debug.panic("panic(DmaTransfer): {} is an illegal src addr adjustment type", .{sad_adj}),
// // }
// // self._fifo_word_count -= 1;
// // if (self._fifo_word_count == 0) {
// // self._fifo_word_count = 4;
// // self.active = false;
// // }
// // return true;
// // }
const transfer_type = self.cnt.transfer_type.read() or is_fifo;
const offset: u32 = if (transfer_type) @sizeOf(u32) else @sizeOf(u16);
if (transfer_type) {
bus.write(u32, self._dad, bus.read(u32, self._sad));
} else {
offset = @sizeOf(u16); // 16-bit Transfer
const halfword = bus.read(u16, self._sad);
bus.write(u16, self._dad, halfword);
bus.write(u16, self._dad, bus.read(u16, self._sad));
}
switch (sad_adj) {
@@ -140,10 +167,12 @@ fn DmaController(comptime id: u2) type {
.IncrementReload => std.debug.panic("panic(DmaTransfer): {} is an illegal src addr adjustment type", .{sad_adj}),
}
switch (dad_adj) {
.Increment, .IncrementReload => self._dad +%= offset,
.Decrement => self._dad -%= offset,
.Fixed => {},
if (!is_fifo) {
switch (dad_adj) {
.Increment, .IncrementReload => self._dad +%= offset,
.Decrement => self._dad -%= offset,
.Fixed => {},
}
}
self._word_count -= 1;
@@ -165,8 +194,10 @@ fn DmaController(comptime id: u2) type {
// 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;
self.active = false;
}
return true;
}
pub fn isBlocking(self: *const Self) bool {
@@ -175,21 +206,31 @@ fn DmaController(comptime id: u2) type {
}
pub fn pollBlankingDma(self: *Self, comptime kind: DmaKind) void {
if (self.enabled) return;
if (self.active) 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,
.HBlank => self.active = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10,
.VBlank => self.active = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01,
.Immediate, .Special => {},
}
if (self.cnt.repeat.read() and self.enabled) {
if (self.cnt.repeat.read() and self.active) {
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 enableSoundDma(self: *Self, fifo_addr: u32) void {
comptime std.debug.assert(id == 1 or id == 2);
if (self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b11 and self.dad == fifo_addr) {
self.active = true;
self._word_count = 4;
self.cnt.repeat.set();
}
}
};
}

View File

@@ -143,8 +143,8 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
0x0400_001C => bus.ppu.setBgOffsets(3, value),
// Sound
0x0400_00A0 => log.warn("Wrote 0x{X:0>8} to FIFO_A", .{value}),
0x0400_00A4 => log.warn("Wrote 0x{X:0>8} to FIFO_B", .{value}),
0x0400_00A0 => bus.apu.chA.push(value),
0x0400_00A4 => bus.apu.chB.push(value),
// DMA Transfers
0x0400_00B0 => bus.dma._0.writeSad(value),
@@ -208,7 +208,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
// Sound
0x0400_0080 => bus.apu.ch_vol_cnt.raw = value,
0x0400_0082 => bus.apu.dma_cnt.raw = value,
0x0400_0082 => bus.apu.setDmaCnt(value),
0x0400_0084 => bus.apu.setSoundCntX(value >> 7 & 1 == 1),
0x0400_0088 => bus.apu.bias.raw = value,

View File

@@ -103,6 +103,17 @@ fn Timer(comptime id: u2) type {
cpu.handleInterrupt();
}
// DMA Sound Things
if (id == 0 or id == 1) {
const apu = &cpu.bus.apu;
const a_tim = @boolToInt(apu.dma_cnt.sa_timer.read());
const b_tim = @boolToInt(apu.dma_cnt.sb_timer.read());
if (a_tim == id) apu.handleTimerOverflow(.A, cpu);
if (b_tim == id) apu.handleTimerOverflow(.B, cpu);
}
// Perform Cascade Behaviour
switch (id) {
0 => if (tim._1.cnt.cascade.read()) {