Compare commits
8 Commits
460f8308a7
...
7e15e83d38
Author | SHA1 | Date |
---|---|---|
Rekai Nyangadzayi Musuka | 7e15e83d38 | |
Rekai Nyangadzayi Musuka | c9ea80e03b | |
Rekai Nyangadzayi Musuka | 4cd722e447 | |
Rekai Nyangadzayi Musuka | adfb23fab4 | |
Rekai Nyangadzayi Musuka | 5bbbdc3469 | |
Rekai Nyangadzayi Musuka | 47adc0c5ae | |
Rekai Nyangadzayi Musuka | 601b0b2aae | |
Rekai Nyangadzayi Musuka | 3becd790cf |
10
README.md
10
README.md
|
@ -22,10 +22,10 @@ An in-progress Gameboy Advance Emulator written in Zig ⚡!
|
||||||
- [ ] `line_timing.gba`
|
- [ ] `line_timing.gba`
|
||||||
- [ ] `lyc_midline.gba`
|
- [ ] `lyc_midline.gba`
|
||||||
- [ ] `window_midframe.gba`
|
- [ ] `window_midframe.gba`
|
||||||
- [ ] [ladystarbreeze's GBA Test Collection](https://github.com/ladystarbreeze/GBA-Test-Collection)
|
- [x] [ladystarbreeze's GBA Test Collection](https://github.com/ladystarbreeze/GBA-Test-Collection)
|
||||||
- [x] `retAddr.gba`
|
- [x] `retAddr.gba`
|
||||||
- [x] `helloWorld.gba`
|
- [x] `helloWorld.gba`
|
||||||
- [ ] `helloAudio.gba`
|
- [x] `helloAudio.gba`
|
||||||
- [x] [`armwrestler-gba-fixed.gba`](https://github.com/destoer/armwrestler-gba-fixed)
|
- [x] [`armwrestler-gba-fixed.gba`](https://github.com/destoer/armwrestler-gba-fixed)
|
||||||
- [x] [FuzzARM](https://github.com/DenSinH/FuzzARM)
|
- [x] [FuzzARM](https://github.com/DenSinH/FuzzARM)
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ An in-progress Gameboy Advance Emulator written in Zig ⚡!
|
||||||
* [ARM7TDMI Data Sheet](https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/ARM7TDMIDataSheet.pdf)
|
* [ARM7TDMI Data Sheet](https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/ARM7TDMIDataSheet.pdf)
|
||||||
|
|
||||||
## Compiling
|
## Compiling
|
||||||
Most recently built on Zig [0.10.0-dev.2424+b3672e073](https://github.com/ziglang/zig/tree/b3672e073)
|
Most recently built on Zig [0.10.0-dev.2624+d506275a0](https://github.com/ziglang/zig/tree/d506275a0)
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
* [SDL.zig](https://github.com/MasterQ32/SDL.zig)
|
* [SDL.zig](https://github.com/MasterQ32/SDL.zig)
|
||||||
|
@ -63,8 +63,8 @@ Key | Button
|
||||||
--- | ---
|
--- | ---
|
||||||
<kbd>X</kbd> | A
|
<kbd>X</kbd> | A
|
||||||
<kbd>Z</kbd> | B
|
<kbd>Z</kbd> | B
|
||||||
<kbd>A</kbd> | Left Shoulder
|
<kbd>A</kbd> | L
|
||||||
<kbd>S</kbd> | Right Shoulder
|
<kbd>S</kbd> | R
|
||||||
<kbd>Return</kbd> | Start
|
<kbd>Return</kbd> | Start
|
||||||
<kbd>RShift</kbd> | Select
|
<kbd>RShift</kbd> | Select
|
||||||
Arrow Keys | D-Pad
|
Arrow Keys | D-Pad
|
||||||
|
|
81
src/apu.zig
81
src/apu.zig
|
@ -64,10 +64,16 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
|
||||||
|
|
||||||
switch (T) {
|
switch (T) {
|
||||||
u32 => switch (byte) {
|
u32 => switch (byte) {
|
||||||
0x80 => {
|
0x60 => apu.ch1.setSoundCnt(value),
|
||||||
apu.psg_cnt.raw = @truncate(u16, value); // SOUNDCNT_L
|
0x64 => apu.ch1.setSoundCntX(&apu.fs, @truncate(u16, value)),
|
||||||
apu.dma_cnt.raw = @truncate(u16, value >> 16); // SOUNDCNT_H
|
0x68 => apu.ch2.setSoundCntL(@truncate(u16, value)),
|
||||||
},
|
0x6C => apu.ch2.setSoundCntH(&apu.fs, @truncate(u16, value)),
|
||||||
|
0x70 => apu.ch3.setSoundCnt(value),
|
||||||
|
0x74 => apu.ch3.setSoundCntX(&apu.fs, @truncate(u16, value)),
|
||||||
|
0x78 => apu.ch4.setSoundCntL(@truncate(u16, value)),
|
||||||
|
0x7C => apu.ch4.setSoundCntH(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
|
0x80 => apu.setSoundCnt(value),
|
||||||
// WAVE_RAM
|
// WAVE_RAM
|
||||||
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
||||||
0xA0 => apu.chA.push(value), // FIFO_A
|
0xA0 => apu.chA.push(value), // FIFO_A
|
||||||
|
@ -75,7 +81,7 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
|
||||||
else => writeUndefined(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
|
else => writeUndefined(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
},
|
},
|
||||||
u16 => switch (byte) {
|
u16 => switch (byte) {
|
||||||
0x60 => apu.ch1.sweep.raw = @truncate(u8, value), // SOUND1CNT_L
|
0x60 => apu.ch1.setSoundCntL(@truncate(u8, value)), // SOUND1CNT_L
|
||||||
0x62 => apu.ch1.setSoundCntH(value),
|
0x62 => apu.ch1.setSoundCntH(value),
|
||||||
0x64 => apu.ch1.setSoundCntX(&apu.fs, value),
|
0x64 => apu.ch1.setSoundCntX(&apu.fs, value),
|
||||||
|
|
||||||
|
@ -98,7 +104,7 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
|
||||||
else => writeUndefined(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
|
else => writeUndefined(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
},
|
},
|
||||||
u8 => switch (byte) {
|
u8 => switch (byte) {
|
||||||
0x60 => apu.ch1.sweep.raw = value, // NR10
|
0x60 => apu.ch1.setSoundCntL(value),
|
||||||
0x62 => apu.ch1.setNr11(value),
|
0x62 => apu.ch1.setNr11(value),
|
||||||
0x63 => apu.ch1.setNr12(value),
|
0x63 => apu.ch1.setNr12(value),
|
||||||
0x64 => apu.ch1.setNr13(value),
|
0x64 => apu.ch1.setNr13(value),
|
||||||
|
@ -196,6 +202,12 @@ pub const Apu = struct {
|
||||||
self.ch4.reset();
|
self.ch4.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SOUNDCNT
|
||||||
|
fn setSoundCnt(self: *Self, value: u32) void {
|
||||||
|
self.psg_cnt.raw = @truncate(u16, value);
|
||||||
|
self.setSoundCntH(@truncate(u16, value >> 16));
|
||||||
|
}
|
||||||
|
|
||||||
/// SOUNDCNT_H_L
|
/// SOUNDCNT_H_L
|
||||||
fn setSoundCntHL(self: *Self, value: u8) void {
|
fn setSoundCntHL(self: *Self, value: u8) void {
|
||||||
const merged = (self.dma_cnt.raw & 0xFF00) | value;
|
const merged = (self.dma_cnt.raw & 0xFF00) | value;
|
||||||
|
@ -208,6 +220,7 @@ pub const Apu = struct {
|
||||||
self.setSoundCntH(merged);
|
self.setSoundCntH(merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SOUNDCNT_H
|
||||||
pub fn setSoundCntH(self: *Self, value: u16) void {
|
pub fn setSoundCntH(self: *Self, value: u16) void {
|
||||||
const new: io.DmaSoundControl = .{ .raw = value };
|
const new: io.DmaSoundControl = .{ .raw = value };
|
||||||
|
|
||||||
|
@ -320,7 +333,8 @@ pub const Apu = struct {
|
||||||
const final_right = (tmp_right << 5) | (tmp_right >> 6);
|
const final_right = (tmp_right << 5) | (tmp_right >> 6);
|
||||||
|
|
||||||
if (self.sampling_cycle != self.bias.sampling_cycle.read()) {
|
if (self.sampling_cycle != self.bias.sampling_cycle.read()) {
|
||||||
log.info("Sampling Cycle changed from {} to {}", .{ self.sampling_cycle, self.bias.sampling_cycle.read() });
|
const new_sample_rate = Self.sampleRate(self.bias.sampling_cycle.read());
|
||||||
|
log.info("Sample Rate changed from {}Hz to {}Hz", .{ Self.sampleRate(self.sampling_cycle), new_sample_rate });
|
||||||
|
|
||||||
// Sample Rate Changed, Create a new Resampler since i can't figure out how to change
|
// Sample Rate Changed, Create a new Resampler since i can't figure out how to change
|
||||||
// the parameters of the old one
|
// the parameters of the old one
|
||||||
|
@ -328,7 +342,7 @@ pub const Apu = struct {
|
||||||
defer SDL.SDL_FreeAudioStream(old);
|
defer SDL.SDL_FreeAudioStream(old);
|
||||||
|
|
||||||
self.sampling_cycle = self.bias.sampling_cycle.read();
|
self.sampling_cycle = self.bias.sampling_cycle.read();
|
||||||
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, self.sampleRate()), SDL.AUDIO_U16, 2, host_sample_rate) orelse unreachable;
|
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, new_sample_rate), SDL.AUDIO_U16, 2, host_sample_rate) orelse unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ final_left, final_right }, 2 * @sizeOf(u16));
|
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ final_left, final_right }, 2 * @sizeOf(u16));
|
||||||
|
@ -336,11 +350,11 @@ pub const Apu = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sampleTicks(self: *const Self) u64 {
|
fn sampleTicks(self: *const Self) u64 {
|
||||||
return (1 << 24) / self.sampleRate();
|
return (1 << 24) / Self.sampleRate(self.bias.sampling_cycle.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sampleRate(self: *const Self) u64 {
|
fn sampleRate(cycle: u2) u64 {
|
||||||
return @as(u64, 1) << (15 + @as(u6, self.bias.sampling_cycle.read()));
|
return @as(u64, 1) << (15 + @as(u6, cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tickFrameSequencer(self: *Self, late: u64) void {
|
pub fn tickFrameSequencer(self: *Self, late: u64) void {
|
||||||
|
@ -386,17 +400,6 @@ pub const Apu = struct {
|
||||||
if (self.chB.len() <= 15) cpu.bus.dma[2].requestSoundDma(0x0400_00A4);
|
if (self.chB.len() <= 15) cpu.bus.dma[2].requestSoundDma(0x0400_00A4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highPass(self: *Self, sample: f32, enabled: bool) f32 {
|
|
||||||
return if (enabled) blk: {
|
|
||||||
const out = sample - self.capacitor;
|
|
||||||
const charge_factor =
|
|
||||||
std.math.pow(f32, 0.999958, @intToFloat(f32, (1 << 22) / self.sampleRate()));
|
|
||||||
|
|
||||||
self.capacitor = sample - out * charge_factor;
|
|
||||||
break :blk out;
|
|
||||||
} else 0.0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToneSweep = struct {
|
const ToneSweep = struct {
|
||||||
|
@ -430,11 +433,14 @@ const ToneSweep = struct {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
shadow: u11,
|
shadow: u11,
|
||||||
|
|
||||||
|
calc_performed: bool,
|
||||||
|
|
||||||
pub fn init() This {
|
pub fn init() This {
|
||||||
return .{
|
return .{
|
||||||
.timer = 0,
|
.timer = 0,
|
||||||
.enabled = false,
|
.enabled = false,
|
||||||
.shadow = 0,
|
.shadow = 0,
|
||||||
|
.calc_performed = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,6 +450,7 @@ const ToneSweep = struct {
|
||||||
if (this.timer == 0) {
|
if (this.timer == 0) {
|
||||||
const period = ch1.sweep.period.read();
|
const period = ch1.sweep.period.read();
|
||||||
this.timer = if (period == 0) 8 else period;
|
this.timer = if (period == 0) 8 else period;
|
||||||
|
if (!this.calc_performed) this.calc_performed = true;
|
||||||
|
|
||||||
if (this.enabled and period != 0) {
|
if (this.enabled and period != 0) {
|
||||||
const new_freq = this.calcFrequency(ch1);
|
const new_freq = this.calcFrequency(ch1);
|
||||||
|
@ -489,6 +496,8 @@ const ToneSweep = struct {
|
||||||
|
|
||||||
fn reset(self: *Self) void {
|
fn reset(self: *Self) void {
|
||||||
self.sweep.raw = 0;
|
self.sweep.raw = 0;
|
||||||
|
self.sweep_dev.calc_performed = false;
|
||||||
|
|
||||||
self.duty.raw = 0;
|
self.duty.raw = 0;
|
||||||
self.envelope.raw = 0;
|
self.envelope.raw = 0;
|
||||||
self.freq.raw = 0;
|
self.freq.raw = 0;
|
||||||
|
@ -521,11 +530,32 @@ const ToneSweep = struct {
|
||||||
return @as(i16, self.sample);
|
return @as(i16, self.sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NR10, NR11, NR12
|
||||||
|
fn setSoundCnt(self: *Self, value: u32) void {
|
||||||
|
self.setSoundCntL(@truncate(u8, value));
|
||||||
|
self.setSoundCntH(@truncate(u16, value >> 16));
|
||||||
|
}
|
||||||
|
|
||||||
/// NR10
|
/// NR10
|
||||||
pub fn getSoundCntL(self: *const Self) u8 {
|
pub fn getSoundCntL(self: *const Self) u8 {
|
||||||
return self.sweep.raw & 0x7F;
|
return self.sweep.raw & 0x7F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NR10
|
||||||
|
fn setSoundCntL(self: *Self, value: u8) void {
|
||||||
|
const new = io.Sweep{ .raw = value };
|
||||||
|
|
||||||
|
if (self.sweep.direction.read() and !new.direction.read()) {
|
||||||
|
// Sweep Negate bit has been cleared
|
||||||
|
// If At least 1 Sweep Calculation has been made since
|
||||||
|
// the last trigger, the channel is immediately disabled
|
||||||
|
|
||||||
|
if (self.sweep_dev.calc_performed) self.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sweep.raw = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// NR11, NR12
|
/// NR11, NR12
|
||||||
pub fn getSoundCntH(self: *const Self) u16 {
|
pub fn getSoundCntH(self: *const Self) u16 {
|
||||||
return @as(u16, self.envelope.raw) << 8 | (self.duty.raw & 0xC0);
|
return @as(u16, self.envelope.raw) << 8 | (self.duty.raw & 0xC0);
|
||||||
|
@ -589,6 +619,7 @@ const ToneSweep = struct {
|
||||||
const sw_period = self.sweep.period.read();
|
const sw_period = self.sweep.period.read();
|
||||||
const sw_shift = self.sweep.shift.read();
|
const sw_shift = self.sweep.shift.read();
|
||||||
|
|
||||||
|
self.sweep_dev.calc_performed = false;
|
||||||
self.sweep_dev.shadow = self.freq.frequency.read();
|
self.sweep_dev.shadow = self.freq.frequency.read();
|
||||||
self.sweep_dev.timer = if (sw_period == 0) 8 else sw_period;
|
self.sweep_dev.timer = if (sw_period == 0) 8 else sw_period;
|
||||||
self.sweep_dev.enabled = sw_period != 0 or sw_shift != 0;
|
self.sweep_dev.enabled = sw_period != 0 or sw_shift != 0;
|
||||||
|
@ -789,6 +820,12 @@ const Wave = struct {
|
||||||
self.len_dev.tick(self.freq.length_enable.read(), &self.enabled);
|
self.len_dev.tick(self.freq.length_enable.read(), &self.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NR30, NR31, NR32
|
||||||
|
fn setSoundCnt(self: *Self, value: u32) void {
|
||||||
|
self.setSoundCntL(@truncate(u8, value));
|
||||||
|
self.setSoundCntH(@truncate(u16, value >> 16));
|
||||||
|
}
|
||||||
|
|
||||||
/// NR30
|
/// NR30
|
||||||
pub fn setSoundCntL(self: *Self, value: u8) void {
|
pub fn setSoundCntL(self: *Self, value: u8) void {
|
||||||
self.select.raw = value;
|
self.select.raw = value;
|
||||||
|
|
|
@ -98,9 +98,6 @@ fn DmaController(comptime id: u2) type {
|
||||||
const sad_mask: u32 = if (id == 0) 0x07FF_FFFF else 0x0FFF_FFFF;
|
const sad_mask: u32 = if (id == 0) 0x07FF_FFFF else 0x0FFF_FFFF;
|
||||||
const dad_mask: u32 = if (id != 3) 0x07FF_FFFF else 0x0FFF_FFFF;
|
const dad_mask: u32 = if (id != 3) 0x07FF_FFFF else 0x0FFF_FFFF;
|
||||||
|
|
||||||
/// Determines whether DMAController is for DMA0, DMA1, DMA2 or DMA3
|
|
||||||
/// Note: Determined at comptime
|
|
||||||
id: u2,
|
|
||||||
/// Write-only. The first address in a DMA transfer. (DMASAD)
|
/// Write-only. The first address in a DMA transfer. (DMASAD)
|
||||||
/// Note: use writeSrc instead of manipulating src_addr directly
|
/// Note: use writeSrc instead of manipulating src_addr directly
|
||||||
sad: u32,
|
sad: u32,
|
||||||
|
@ -126,11 +123,10 @@ fn DmaController(comptime id: u2) type {
|
||||||
/// Some DMA Transfers are enabled during Hblank / VBlank and / or
|
/// Some DMA Transfers are enabled during Hblank / VBlank and / or
|
||||||
/// have delays. Thefore bit 15 of DMACNT isn't actually something
|
/// 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
|
/// we can use to control when we do or do not execute a step in a DMA Transfer
|
||||||
active: bool,
|
in_progress: bool,
|
||||||
|
|
||||||
pub fn init() Self {
|
pub fn init() Self {
|
||||||
return .{
|
return .{
|
||||||
.id = id,
|
|
||||||
.sad = 0,
|
.sad = 0,
|
||||||
.dad = 0,
|
.dad = 0,
|
||||||
.word_count = 0,
|
.word_count = 0,
|
||||||
|
@ -141,7 +137,7 @@ fn DmaController(comptime id: u2) type {
|
||||||
._dad = 0,
|
._dad = 0,
|
||||||
._word_count = 0,
|
._word_count = 0,
|
||||||
._fifo_word_count = 4,
|
._fifo_word_count = 4,
|
||||||
.active = false,
|
.in_progress = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +163,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;
|
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
|
// Only a Start Timing of 00 has a DMA Transfer immediately begin
|
||||||
self.active = new.start_timing.read() == 0b00;
|
self.in_progress = new.start_timing.read() == 0b00;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cnt.raw = halfword;
|
self.cnt.raw = halfword;
|
||||||
|
@ -179,7 +175,7 @@ fn DmaController(comptime id: u2) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(self: *Self, cpu: *Arm7tdmi) void {
|
pub fn step(self: *Self, cpu: *Arm7tdmi) void {
|
||||||
const is_fifo = (self.id == 1 or self.id == 2) and self.cnt.start_timing.read() == 0b11;
|
const is_fifo = (id == 1 or id == 2) and self.cnt.start_timing.read() == 0b11;
|
||||||
const sad_adj = Self.adjustment(self.cnt.sad_adj.read());
|
const sad_adj = Self.adjustment(self.cnt.sad_adj.read());
|
||||||
const dad_adj = if (is_fifo) .Fixed else Self.adjustment(self.cnt.dad_adj.read());
|
const dad_adj = if (is_fifo) .Fixed else Self.adjustment(self.cnt.dad_adj.read());
|
||||||
|
|
||||||
|
@ -209,57 +205,63 @@ fn DmaController(comptime id: u2) type {
|
||||||
self._word_count -= 1;
|
self._word_count -= 1;
|
||||||
|
|
||||||
if (self._word_count == 0) {
|
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()) {
|
if (self.cnt.irq.read()) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
0 => cpu.bus.io.irq.dma0.set(),
|
0 => cpu.bus.io.irq.dma0.set(),
|
||||||
1 => cpu.bus.io.irq.dma0.set(),
|
1 => cpu.bus.io.irq.dma1.set(),
|
||||||
2 => cpu.bus.io.irq.dma0.set(),
|
2 => cpu.bus.io.irq.dma2.set(),
|
||||||
3 => cpu.bus.io.irq.dma0.set(),
|
3 => cpu.bus.io.irq.dma3.set(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.handleInterrupt();
|
cpu.handleInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cnt.enabled.unset();
|
// If we're not repeating, Fire the IRQs and disable the DMA
|
||||||
}
|
if (!self.cnt.repeat.read()) self.cnt.enabled.unset();
|
||||||
|
|
||||||
// We want to disable our internal enabled flag regardless of repeat
|
// 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
|
// because we only want to step A DMA that repeats during it's specific
|
||||||
// timing window
|
// timing window
|
||||||
self.active = false;
|
self.in_progress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pollBlankingDma(self: *Self, comptime kind: DmaKind) void {
|
pub fn pollBlankingDma(self: *Self, comptime kind: DmaKind) void {
|
||||||
if (self.active) return;
|
if (self.in_progress) return; // If there's an ongoing DMA Transfer, exit early
|
||||||
|
|
||||||
|
// No ongoing DMA Transfer, We want to check if we should repeat an existing one
|
||||||
|
// Determined by the repeat bit and whether the DMA is in the right start_timing
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
.HBlank => self.active = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10,
|
.VBlank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01,
|
||||||
.VBlank => self.active = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b01,
|
.HBlank => self.in_progress = self.cnt.enabled.read() and self.cnt.start_timing.read() == 0b10,
|
||||||
.Immediate, .Special => {},
|
.Immediate, .Special => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.cnt.repeat.read() and self.active) {
|
// If we determined that the repeat bit is set (and now the Hblank / Vblank DMA is now in progress)
|
||||||
|
// Reload internal word count latch
|
||||||
|
// Reload internal DAD latch if we are in IncrementRelaod
|
||||||
|
if (self.in_progress) {
|
||||||
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;
|
||||||
if (Self.adjustment(self.cnt.dad_adj.read()) == .IncrementReload) self._dad = self.dad;
|
if (Self.adjustment(self.cnt.dad_adj.read()) == .IncrementReload) self._dad = self.dad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requestSoundDma(self: *Self, fifo_addr: u32) void {
|
pub fn requestSoundDma(self: *Self, _: u32) void {
|
||||||
comptime std.debug.assert(id == 1 or id == 2);
|
comptime std.debug.assert(id == 1 or id == 2);
|
||||||
|
if (self.in_progress) return; // APU must wait their turn
|
||||||
|
|
||||||
const is_enabled = self.cnt.enabled.read();
|
// DMA May not be configured for handling DMAs
|
||||||
const is_special = self.cnt.start_timing.read() == 0b11;
|
if (self.cnt.start_timing.read() != 0b11) return;
|
||||||
const is_repeating = self.cnt.repeat.read();
|
|
||||||
const is_fifo = self.dad == fifo_addr;
|
|
||||||
|
|
||||||
if (is_enabled and is_special and is_repeating and is_fifo) {
|
// We Assume the Repeat Bit is Set
|
||||||
|
// We Assume that DAD is set to 0x0400_00A0 or 0x0400_00A4 (fifo_addr)
|
||||||
|
// We Assume DMACNT_L is set to 4
|
||||||
|
|
||||||
|
// FIXME: Safe to just assume whatever DAD is set to is the FIFO Address?
|
||||||
|
// self._dad = fifo_addr;
|
||||||
|
self.cnt.repeat.set();
|
||||||
self._word_count = 4;
|
self._word_count = 4;
|
||||||
self.cnt.transfer_type.set();
|
self.in_progress = true;
|
||||||
self.active = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adjustment(idx: u2) Adjustment {
|
fn adjustment(idx: u2) Adjustment {
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_0006 => @as(T, bus.ppu.bg[0].cnt.raw) << 16 | bus.ppu.vcount.raw,
|
0x0400_0006 => @as(T, bus.ppu.bg[0].cnt.raw) << 16 | bus.ppu.vcount.raw,
|
||||||
|
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
0x0400_00B8...0x0400_00DC => dma.read(T, &bus.dma, address),
|
0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address),
|
||||||
|
|
||||||
// Timers
|
// Timers
|
||||||
0x0400_0100...0x0400_010C => timer.read(T, &bus.tim, address),
|
0x0400_0100...0x0400_010C => timer.read(T, &bus.tim, address),
|
||||||
|
@ -83,10 +83,10 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_004C => readTodo("Read {} from MOSAIC", .{T}),
|
0x0400_004C => readTodo("Read {} from MOSAIC", .{T}),
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_009F => apu.read(T, &bus.apu, address),
|
0x0400_0060...0x0400_009E => apu.read(T, &bus.apu, address),
|
||||||
|
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
0x0400_00BA...0x0400_00DE => dma.read(T, &bus.dma, address),
|
0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address),
|
||||||
|
|
||||||
// Timers
|
// Timers
|
||||||
0x0400_0100...0x0400_010E => timer.read(T, &bus.tim, address),
|
0x0400_0100...0x0400_010E => timer.read(T, &bus.tim, address),
|
||||||
|
@ -119,7 +119,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
||||||
0x0400_000B => @truncate(T, bus.ppu.bg[1].cnt.raw >> 8),
|
0x0400_000B => @truncate(T, bus.ppu.bg[1].cnt.raw >> 8),
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_0089 => apu.read(T, &bus.apu, address),
|
0x0400_0060...0x0400_00A7 => apu.read(T, &bus.apu, address),
|
||||||
|
|
||||||
// Serial Communication 1
|
// Serial Communication 1
|
||||||
0x0400_0128 => readTodo("Read {} from SIOCNT_L", .{T}),
|
0x0400_0128 => readTodo("Read {} from SIOCNT_L", .{T}),
|
||||||
|
@ -171,7 +171,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0058...0x0400_005C => {}, // Unused
|
0x0400_0058...0x0400_005C => {}, // Unused
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0080...0x0400_00A4 => apu.write(T, &bus.apu, address, value),
|
0x0400_0060...0x0400_00A4 => apu.write(T, &bus.apu, address, value),
|
||||||
0x0400_00A8, 0x0400_00AC => {}, // Unused
|
0x0400_00A8, 0x0400_00AC => {}, // Unused
|
||||||
|
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
|
@ -190,6 +190,8 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
|
|
||||||
// Keypad Input
|
// Keypad Input
|
||||||
0x0400_0130 => log.debug("Wrote 0x{X:0>8} to KEYINPUT and KEYCNT", .{value}),
|
0x0400_0130 => log.debug("Wrote 0x{X:0>8} to KEYINPUT and KEYCNT", .{value}),
|
||||||
|
0x0400_0134 => log.debug("Wrote 0x{X:0>8} to RCNT and IR", .{value}),
|
||||||
|
0x0400_0138, 0x0400_013C => {}, // Unused
|
||||||
|
|
||||||
// Serial Communication 2
|
// Serial Communication 2
|
||||||
0x0400_0140 => log.debug("Wrote 0x{X:0>8} to JOYCNT", .{value}),
|
0x0400_0140 => log.debug("Wrote 0x{X:0>8} to JOYCNT", .{value}),
|
||||||
|
@ -197,6 +199,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0154 => log.debug("Wrote 0x{X:0>8} to JOY_TRANS", .{value}),
|
0x0400_0154 => log.debug("Wrote 0x{X:0>8} to JOY_TRANS", .{value}),
|
||||||
0x0400_0158 => log.debug("Wrote 0x{X:0>8} to JOYSTAT (?)", .{value}),
|
0x0400_0158 => log.debug("Wrote 0x{X:0>8} to JOYSTAT (?)", .{value}),
|
||||||
0x0400_0144...0x0400_014C, 0x0400_015C => {}, // Unused
|
0x0400_0144...0x0400_014C, 0x0400_015C => {}, // Unused
|
||||||
|
0x0400_0160...0x0400_01FC => {},
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200 => bus.io.setIrqs(value),
|
0x0400_0200 => bus.io.setIrqs(value),
|
||||||
|
@ -251,7 +254,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_004E, 0x0400_0056 => {}, // Not used
|
0x0400_004E, 0x0400_0056 => {}, // Not used
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_009F => apu.write(T, &bus.apu, address, value),
|
0x0400_0060...0x0400_009E => apu.write(T, &bus.apu, address, value),
|
||||||
|
|
||||||
// Dma Transfers
|
// Dma Transfers
|
||||||
0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value),
|
0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value),
|
||||||
|
@ -301,7 +304,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0054 => log.debug("Wrote 0x{X:0>2} to BLDY_L", .{value}),
|
0x0400_0054 => log.debug("Wrote 0x{X:0>2} to BLDY_L", .{value}),
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_009F => apu.write(T, &bus.apu, address, value),
|
0x0400_0060...0x0400_00A7 => apu.write(T, &bus.apu, address, value),
|
||||||
|
|
||||||
// Serial Communication 1
|
// Serial Communication 1
|
||||||
0x0400_0120 => log.debug("Wrote 0x{X:0>2} to SIODATA32_L_L", .{value}),
|
0x0400_0120 => log.debug("Wrote 0x{X:0>2} to SIODATA32_L_L", .{value}),
|
||||||
|
@ -432,10 +435,10 @@ const InterruptRequest = extern union {
|
||||||
vblank: Bit(u16, 0),
|
vblank: Bit(u16, 0),
|
||||||
hblank: Bit(u16, 1),
|
hblank: Bit(u16, 1),
|
||||||
coincidence: Bit(u16, 2),
|
coincidence: Bit(u16, 2),
|
||||||
tim0_overflow: Bit(u16, 3),
|
tim0: Bit(u16, 3),
|
||||||
tim1_overflow: Bit(u16, 4),
|
tim1: Bit(u16, 4),
|
||||||
tim2_overflow: Bit(u16, 5),
|
tim2: Bit(u16, 5),
|
||||||
tim3_overflow: Bit(u16, 6),
|
tim3: Bit(u16, 6),
|
||||||
serial: Bit(u16, 7),
|
serial: Bit(u16, 7),
|
||||||
dma0: Bit(u16, 8),
|
dma0: Bit(u16, 8),
|
||||||
dma1: Bit(u16, 9),
|
dma1: Bit(u16, 9),
|
||||||
|
|
|
@ -144,10 +144,10 @@ fn Timer(comptime id: u2) type {
|
||||||
|
|
||||||
if (self.cnt.irq.read()) {
|
if (self.cnt.irq.read()) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
0 => io.irq.tim0_overflow.set(),
|
0 => io.irq.tim0.set(),
|
||||||
1 => io.irq.tim1_overflow.set(),
|
1 => io.irq.tim1.set(),
|
||||||
2 => io.irq.tim2_overflow.set(),
|
2 => io.irq.tim2.set(),
|
||||||
3 => io.irq.tim3_overflow.set(),
|
3 => io.irq.tim3.set(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.handleInterrupt();
|
cpu.handleInterrupt();
|
||||||
|
|
|
@ -277,22 +277,22 @@ pub const Arm7tdmi = struct {
|
||||||
const dma2 = &self.bus.dma[2];
|
const dma2 = &self.bus.dma[2];
|
||||||
const dma3 = &self.bus.dma[3];
|
const dma3 = &self.bus.dma[3];
|
||||||
|
|
||||||
if (dma0.active) {
|
if (dma0.in_progress) {
|
||||||
dma0.step(self);
|
dma0.step(self);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dma1.active) {
|
if (dma1.in_progress) {
|
||||||
dma1.step(self);
|
dma1.step(self);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dma2.active) {
|
if (dma2.in_progress) {
|
||||||
dma2.step(self);
|
dma2.step(self);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dma3.active) {
|
if (dma3.in_progress) {
|
||||||
dma3.step(self);
|
dma3.step(self);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue