Compare commits

..

34 Commits

Author SHA1 Message Date
Rekai Nyangadzayi Musuka 23f5d676d4 chore: reimplement alpha blending 2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka 4ccacb0754 chore(ppu): use @ptrCast in drawTextMode 2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka e67dc9b7de chore(ppu): reimplement modes 3, 4, and 5 2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka f9ca005faf style(ppu): move text mode drawing to unique fn 2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka 11b905dc82 fix(window): proper inRange impl for window
window wrap now works (it's pretty slow though?)
2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka 5d3435757c chore: improve readability of sprite drawing code a bit 2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka d705137f24 style: remove unused imports 2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka 2c6fe879ad chore: dont allocate not-small ?Sprite array on stack
use memset like most other allocations in this emu
2022-12-04 18:35:20 -04:00
Rekai Nyangadzayi Musuka 24905e8096 chore: move FrameBuffer struct to util.zig 2022-12-04 18:35:19 -04:00
Rekai Nyangadzayi Musuka cab7816ce5 chore: move OAM, PALRAM and VRAM structs to separate files 2022-12-04 18:33:28 -04:00
Rekai Nyangadzayi Musuka 6b6614cfa7 fix: 8-bit writes to WIN PPU registers
Advance Wars depends on these registers similar to Mario Kart's 8-bit
writes to Affine Background registers:
2022-12-04 18:33:28 -04:00
Rekai Nyangadzayi Musuka 106a3f8b4d chore: refactor window 2022-12-04 18:33:28 -04:00
Rekai Nyangadzayi Musuka 918df2743e chore: crude background window impl (no affine) 2022-12-04 18:33:28 -04:00
Rekai Nyangadzayi Musuka 78c5160897 chore: rename function (misspelt until now somehow) 2022-12-04 18:33:28 -04:00
Rekai Nyangadzayi Musuka 240fbcb1df chore: update dependencies 2022-12-01 13:23:09 -04:00
Rekai Nyangadzayi Musuka 26db340077 fix(input): implement atomic for KeyInput 2022-11-30 00:42:20 -04:00
Rekai Nyangadzayi Musuka 20f611b7b5 chore: be more intentional in atomic ordering use 2022-11-30 00:21:02 -04:00
Rekai Nyangadzayi Musuka f9aefedf60 chore: cal glDeleteTextures on program exit 2022-11-29 23:35:13 -04:00
Rekai Nyangadzayi Musuka d7e3d34726 fix(platform): ensure that title char* is null terminated 2022-11-29 23:21:57 -04:00
Rekai Nyangadzayi Musuka 2294dc8832 chore: add minimum zig version 2022-11-29 23:10:29 -04:00
Rekai Nyangadzayi Musuka 4af86e1cb3 style: replace meta.Tuple calls with new tuple syntax 2022-11-29 23:01:06 -04:00
Rekai Nyangadzayi Musuka 9fcbbe7d57 chore: cleanup OpenGL vertex array + buffers 2022-11-29 22:53:37 -04:00
Rekai Nyangadzayi Musuka c3f67e38a1 chore: exit early on shader compile failure 2022-11-29 22:25:04 -04:00
Rekai Nyangadzayi Musuka 46e29245b7 fix(apu): disable APU writes when APU is disabled 2022-11-26 12:20:42 -04:00
Rekai Nyangadzayi Musuka 002e33b48b fix: properly render table in README 2022-11-24 08:22:58 -04:00
Rekai Nyangadzayi Musuka 5bb25fe214 chore: update dependencies 2022-11-23 21:57:53 -04:00
Rekai Nyangadzayi Musuka 66db2e6049 Revert "chore: refactor flash impl"
This reverts commit 96a9ae2ca5.
2022-11-20 21:46:40 -04:00
Rekai Nyangadzayi Musuka c5cf471912 fix(timer): removing cascade when TIM aleady enabled shouldn't reset counter 2022-11-20 19:13:49 -04:00
Rekai Nyangadzayi Musuka 4ed4f8e143 fix(dma): implement obscure behaviour for DMAs from ROM 2022-11-20 17:49:26 -04:00
Rekai Nyangadzayi Musuka f31699d921 fix(log): logged improper second opcode for THUMB BL 2022-11-20 15:36:40 -04:00
Rekai Nyangadzayi Musuka 96a9ae2ca5 chore: refactor flash impl 2022-11-17 10:47:19 -04:00
Rekai Nyangadzayi Musuka ee1c0bb313 chore: update README 2022-11-16 10:55:33 -04:00
Rekai Nyangadzayi Musuka 558c03b12b style: changes to cpu.zig 2022-11-16 10:21:40 -04:00
Rekai Nyangadzayi Musuka 7d8fbbb086 fix(bus): resolve off-by-one error 2022-11-14 01:59:43 -04:00
26 changed files with 493 additions and 358 deletions

View File

@ -28,31 +28,45 @@ Finally it's worth noting that ZBA uses a TOML config file it'll store in your O
## Tests ## Tests
- [x] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests) GBA Tests | [jsmolka](https://github.com/jsmolka/)
- [x] `arm.gba` and `thumb.gba` --- | ---
- [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba` `arm.gba`, `thumb.gba` | PASS
- [x] `hello.gba`, `shades.gba`, and `stripes.gba` `memory.gba`, `bios.gba` | PASS
- [x] `memory.gba` `flash64.gba`, `flash128.gba` | PASS
- [x] `bios.gba` `sram.gba` | PASS
- [x] `nes.gba` `none.gba` | PASS
- [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms) `hello.gba`, `shades.gba`, `stripes.gba` | PASS
- [x] `eeprom-test` and `flash-test` `nes.gba` | PASS
- [x] `midikey2freq`
- [ ] `swi-tests-random` GBARoms | [DenSinH](https://github.com/DenSinH/)
- [ ] [destoer's GBA Tests](https://github.com/destoer/gba_tests) --- | ---
- [x] `cond_invalid.gba` `eeprom-test`, `flash-test` | PASS
- [x] `dma_priority.gba` `midikey2freq` | PASS
- [x] `hello_world.gba` `swi-tests-random` | FAIL
- [x] `if_ack.gba`
- [ ] `line_timing.gba` gba_tests | [destoer](https://github.com/destoer/)
- [ ] `lyc_midline.gba` --- | ---
- [ ] `window_midframe.gba` `cond_invalid.gba` | PASS
- [x] [ladystarbreeze's GBA Test Collection](https://github.com/ladystarbreeze/GBA-Test-Collection) `dma_priority.gba` | PASS
- [x] `retAddr.gba` `hello_world.gba` | PASS
- [x] `helloWorld.gba` `if_ack.gba` | PASS
- [x] `helloAudio.gba` `line_timing.gba` | FAIL
- [x] [`armwrestler-gba-fixed.gba`](https://github.com/destoer/armwrestler-gba-fixed) `lyc_midline.gba` | FAIL
- [x] [FuzzARM](https://github.com/DenSinH/FuzzARM) `window_midframe.gba` | FAIL
GBA Test Collection | [ladystarbreeze](https://github.com/ladystarbreeze)
--- | ---
`retAddr.gba` | PASS
`helloWorld.gba` | PASS
`helloAudio.gba` | PASS
FuzzARM | [DenSinH](https://github.com/DenSinH/)
--- | ---
`main.gba` | PASS
arm7wrestler GBA Fixed | [destoer](https://github.com/destoer)
--- | ---
`armwrestler-gba-fixed.gba` | PASS
## Resources ## Resources
@ -63,19 +77,19 @@ Finally it's worth noting that ZBA uses a TOML config file it'll store in your O
## Compiling ## Compiling
Most recently built on Zig [v0.11.0-dev.144+892fb0fc8](https://github.com/ziglang/zig/tree/892fb0fc8) Most recently built on Zig [0.11.0-dev.368+1829b6eab](https://github.com/ziglang/zig/tree/1829b6eab)
### Dependencies ### Dependencies
- [SDL.zig](https://github.com/MasterQ32/SDL.zig) Dependency | Source
- [SDL2](https://www.libsdl.org/download-2.0.php) --- | ---
- [zig-clap](https://github.com/Hejsil/zig-clap) SDL.zig | <https://github.com/MasterQ32/SDL.zig>
- [known-folders](https://github.com/ziglibs/known-folders) zig-clap | <https://github.com/Hejsil/zig-clap>
- [zig-toml](https://github.com/aeronavery/zig-toml) known-folders | <https://github.com/ziglibs/known-folders>
- [zig-datetime](https://github.com/frmdstryr/zig-datetime) zig-toml | <https://github.com/aeronavery/zig-toml>
- [`bitfields.zig`](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig) zig-datetime | <https://github.com/frmdstryr/zig-datetime>
`bitfields.zig` | [https://github.com/FlorenceOS/Florence](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig)
`bitfields.zig` from [FlorenceOS](https://github.com/FlorenceOS) is included under `lib/util/bitfield.zig`. `gl.zig` | <https://github.com/MasterQ32/zig-opengl>
Use `git submodule update --init` from the project root to pull the git submodules `SDL.zig`, `zig-clap`, `known-folders`, `zig-toml` and `zig-datetime` Use `git submodule update --init` from the project root to pull the git submodules `SDL.zig`, `zig-clap`, `known-folders`, `zig-toml` and `zig-datetime`

View File

@ -1,7 +1,15 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const Sdk = @import("lib/SDL.zig/Sdk.zig"); const Sdk = @import("lib/SDL.zig/Sdk.zig");
pub fn build(b: *std.build.Builder) void { pub fn build(b: *std.build.Builder) void {
// Minimum Zig Version
const min_ver = std.SemanticVersion.parse("0.11.0-dev.323+30eb2a175") catch return; // https://github.com/ziglang/zig/commit/30eb2a175
if (builtin.zig_version.order(min_ver).compare(.lt)) {
std.log.err("{s}", .{b.fmt("Zig v{} does not meet the minimum version requirement. (Zig v{})", .{ builtin.zig_version, min_ver })});
std.os.exit(1);
}
// Standard target options allows the person running `zig build` to choose // Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which // what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options // means any target is allowed, and the default is native. Other options

@ -1 +1 @@
Subproject commit 6a9e37687a4b9ae3c14c9ea148ec51d14e01c7db Subproject commit 00b43568854f14e3bab340a4e062776ecb44a727

@ -1 +1 @@
Subproject commit 749c43f1f846adc950a5920ed61b40cbc3ec2c54 Subproject commit a1b01ffeab452790790034b8a0e97aa30bbeb800

@ -1 +1 @@
Subproject commit be26d500917b5b3af4708f62ce22b990032a0ad3 Subproject commit 016b8bcf98e50ae9408f6a9606bbec5a9bc6f677

View File

@ -58,7 +58,10 @@ pub fn load(allocator: Allocator, file_path: []const u8) !void {
const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos()); const contents = try config_file.readToEndAlloc(allocator, try config_file.getEndPos());
defer allocator.free(contents); defer allocator.free(contents);
const table = try toml.parseContents(allocator, contents, null); var parser = try toml.parseFile(allocator, file_path);
defer parser.deinit();
const table = try parser.parse();
defer table.deinit(); defer table.deinit();
// TODO: Report unknown config options // TODO: Report unknown config options

View File

@ -311,7 +311,7 @@ pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)]; self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)];
// We're doing some serious out-of-bounds open-bus reads // We're doing some serious out-of-bounds open-bus reads
if (page > table_len) return self.openBus(T, unaligned_address); if (page >= table_len) return self.openBus(T, unaligned_address);
if (self.read_table[page]) |some_ptr| { if (self.read_table[page]) |some_ptr| {
// We have a pointer to a page, cast the pointer to it's underlying type // We have a pointer to a page, cast the pointer to it's underlying type
@ -377,7 +377,7 @@ pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) vo
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)]; self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)];
// We're doing some serious out-of-bounds open-bus writes, they do nothing though // We're doing some serious out-of-bounds open-bus writes, they do nothing though
if (page > table_len) return; if (page >= table_len) return;
if (self.write_tables[@boolToInt(T == u8)][page]) |some_ptr| { if (self.write_tables[@boolToInt(T == u8)][page]) |some_ptr| {
// We have a pointer to a page, cast the pointer to it's underlying type // We have a pointer to a page, cast the pointer to it's underlying type

View File

@ -108,8 +108,14 @@ pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void { pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
const byte_addr = @truncate(u8, addr); const byte_addr = @truncate(u8, addr);
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (T) { switch (T) {
u32 => switch (byte_addr) { u32 => {
// 0x80 and 0x81 handled in setSoundCnt
if (byte_addr < 0x80 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
0x60 => apu.ch1.setSound1Cnt(value), 0x60 => apu.ch1.setSound1Cnt(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)), 0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
@ -131,8 +137,12 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
0xA0 => apu.chA.push(value), // FIFO_A 0xA0 => apu.chA.push(value), // FIFO_A
0xA4 => apu.chB.push(value), // FIFO_B 0xA4 => apu.chB.push(value), // FIFO_B
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }), else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
}
}, },
u16 => switch (byte_addr) { u16 => {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L 0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
0x62 => apu.ch1.setSound1CntH(value), 0x62 => apu.ch1.setSound1CntH(value),
0x64 => apu.ch1.setSound1CntX(&apu.fs, value), 0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
@ -164,8 +174,12 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }), 0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }),
0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }), 0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }),
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }), else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
}
}, },
u8 => switch (byte_addr) { u8 => {
if (byte_addr <= 0x81 and !apu.cnt.apu_enable.read()) return;
switch (byte_addr) {
0x60 => apu.ch1.setSound1CntL(value), 0x60 => apu.ch1.setSound1CntL(value),
0x61 => {}, 0x61 => {},
0x62 => apu.ch1.setNr11(value), 0x62 => apu.ch1.setNr11(value),
@ -208,6 +222,7 @@ pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }), 0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }),
0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }), 0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }),
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }), else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
}
}, },
else => @compileError("APU: Unsupported write width"), else => @compileError("APU: Unsupported write width"),
} }
@ -275,15 +290,20 @@ pub const Apu = struct {
} }
fn reset(self: *Self) void { fn reset(self: *Self) void {
// All PSG Registers between 0x0400_0060..0x0400_0081 are zeroed
// 0x0400_0082 and 0x0400_0088 retain their values
self.ch1.reset(); self.ch1.reset();
self.ch2.reset(); self.ch2.reset();
self.ch3.reset(); self.ch3.reset();
self.ch4.reset(); self.ch4.reset();
// GBATEK says 4000060h..4000081h I take this to mean inclusive
self.psg_cnt.raw = 0x0000;
} }
/// SOUNDCNT /// SOUNDCNT
fn setSoundCnt(self: *Self, value: u32) void { fn setSoundCnt(self: *Self, value: u32) void {
self.setSoundCntL(@truncate(u16, value)); if (self.cnt.apu_enable.read()) self.setSoundCntL(@truncate(u16, value));
self.setSoundCntH(@truncate(u16, value >> 16)); self.setSoundCntH(@truncate(u16, value >> 16));
} }
@ -322,11 +342,14 @@ pub const Apu = struct {
self.fs.step = 0; // Reset Frame Sequencer self.fs.step = 0; // Reset Frame Sequencer
// Reset Square Wave Offsets // Reset Square Wave Offsets
self.ch1.square.pos = 0; self.ch1.square.reset();
self.ch2.square.pos = 0; self.ch2.square.reset();
// Reset Wave Device Offsets // Reset Wave
self.ch3.wave_dev.offset = 0; self.ch3.wave_dev.reset();
// Rest Noise
self.ch4.lfsr.reset();
} else { } else {
self.reset(); self.reset();
} }
@ -395,8 +418,8 @@ pub const Apu = struct {
right += if (self.dma_cnt.chB_right.read()) chB_sample else 0; right += if (self.dma_cnt.chB_right.read()) chB_sample else 0;
// Add SOUNDBIAS // Add SOUNDBIAS
// FIXME: Is SOUNDBIAS 9-bit or 10-bit? // FIXME: SOUNDBIAS is 10-bit but The waveform is centered around 0 if I treat it as 11-bit
const bias = @as(i16, self.bias.level.read()) << 1; const bias = @as(i16, self.bias.level.read()) << 2;
left += bias; left += bias;
right += bias; right += bias;

View File

@ -49,10 +49,13 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.len = 0; self.len = 0; // NR41
self.envelope.raw = 0; self.envelope.raw = 0; // NR42
self.poly.raw = 0; self.poly.raw = 0; // NR43
self.cnt.raw = 0; self.cnt.raw = 0; // NR44
self.len_dev.reset();
self.env_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -43,9 +43,12 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.duty.raw = 0; self.duty.raw = 0; // NR21
self.envelope.raw = 0; self.envelope.raw = 0; // NR22
self.freq.raw = 0; self.freq.raw = 0; // NR32, NR24
self.len_dev.reset();
self.env_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -50,12 +50,14 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.sweep.raw = 0; self.sweep.raw = 0; // NR10
self.sweep_dev.calc_performed = false; self.duty.raw = 0; // NR11
self.envelope.raw = 0; // NR12
self.freq.raw = 0; // NR13, NR14
self.duty.raw = 0; self.len_dev.reset();
self.envelope.raw = 0; self.sweep_dev.reset();
self.freq.raw = 0; self.env_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -42,10 +42,13 @@ pub fn init(sched: *Scheduler) Self {
} }
pub fn reset(self: *Self) void { pub fn reset(self: *Self) void {
self.select.raw = 0; self.select.raw = 0; // NR30
self.length = 0; self.length = 0; // NR31
self.vol.raw = 0; self.vol.raw = 0; // NR32
self.freq.raw = 0; self.freq.raw = 0; // NR33, NR34
self.len_dev.reset();
self.wave_dev.reset();
self.sample = 0; self.sample = 0;
self.enabled = false; self.enabled = false;

View File

@ -11,6 +11,11 @@ pub fn create() Self {
return .{ .timer = 0, .vol = 0 }; return .{ .timer = 0, .vol = 0 };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.vol = 0;
}
pub fn tick(self: *Self, nrx2: io.Envelope) void { pub fn tick(self: *Self, nrx2: io.Envelope) void {
if (nrx2.period.read() != 0) { if (nrx2.period.read() != 0) {
if (self.timer != 0) self.timer -= 1; if (self.timer != 0) self.timer -= 1;

View File

@ -6,6 +6,10 @@ pub fn create() Self {
return .{ .timer = 0 }; return .{ .timer = 0 };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
}
pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void { pub fn tick(self: *Self, enabled: bool, ch_enable: *bool) void {
if (enabled) { if (enabled) {
if (self.timer == 0) return; if (self.timer == 0) return;

View File

@ -18,6 +18,13 @@ pub fn create() Self {
}; };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.enabled = false;
self.shadow = 0;
self.calc_performed = false;
}
pub fn tick(self: *Self, ch1: *ToneSweep) void { pub fn tick(self: *Self, ch1: *ToneSweep) void {
if (self.timer != 0) self.timer -= 1; if (self.timer != 0) self.timer -= 1;

View File

@ -19,6 +19,11 @@ pub fn create(sched: *Scheduler) Self {
}; };
} }
pub fn reset(self: *Self) void {
self.shift = 0;
self.timer = 0;
}
pub fn sample(self: *const Self) i8 { pub fn sample(self: *const Self) i8 {
return if ((~self.shift & 1) == 1) 1 else -1; return if ((~self.shift & 1) == 1) 1 else -1;
} }

View File

@ -20,6 +20,11 @@ pub fn init(sched: *Scheduler) Self {
}; };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.pos = 0;
}
/// Scheduler Event Handler for Square Synth Timer Expire /// Scheduler Event Handler for Square Synth Timer Expire
pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void { pub fn onSquareTimerExpire(self: *Self, comptime T: type, nrx34: io.Frequency, late: u64) void {
comptime std.debug.assert(T == ToneSweep or T == Tone); comptime std.debug.assert(T == ToneSweep or T == Tone);

View File

@ -38,6 +38,13 @@ pub fn init(sched: *Scheduler) Self {
}; };
} }
pub fn reset(self: *Self) void {
self.timer = 0;
self.offset = 0;
// sample buffer isn't reset because it's outside of the range of what NR52{7}'s effects
}
/// Reload internal Wave Timer /// Reload internal Wave Timer
pub fn reload(self: *Self, value: u11) void { pub fn reload(self: *Self, value: u11) void {
self.sched.removeScheduledEvent(.{ .ApuChannel = 2 }); self.sched.removeScheduledEvent(.{ .ApuChannel = 2 });

View File

@ -5,7 +5,7 @@ const DmaControl = @import("io.zig").DmaControl;
const Bus = @import("../Bus.zig"); const Bus = @import("../Bus.zig");
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) }); pub const DmaTuple = struct { DmaController(0), DmaController(1), DmaController(2), DmaController(3) };
const log = std.log.scoped(.DmaTransfer); const log = std.log.scoped(.DmaTransfer);
const getHalf = util.getHalf; const getHalf = util.getHalf;
@ -256,11 +256,16 @@ fn DmaController(comptime id: u2) type {
cpu.bus.write(u16, dad_addr, @truncate(u16, rotr(u32, self.data_latch, 8 * (dad_addr & 3)))); cpu.bus.write(u16, dad_addr, @truncate(u16, rotr(u32, self.data_latch, 8 * (dad_addr & 3))));
} }
switch (sad_adj) { switch (@truncate(u8, sad_addr >> 24)) {
// according to fleroviux, DMAs with a source address in ROM misbehave
// the resultant behaviour is that the source address will increment despite what DMAXCNT says
0x08...0x0D => self.sad_latch +%= offset, // obscure behaviour
else => switch (sad_adj) {
.Increment => self.sad_latch +%= offset, .Increment => self.sad_latch +%= offset,
.Decrement => self.sad_latch -%= offset, .Decrement => self.sad_latch -%= offset,
.IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}), .IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}),
.Fixed => {}, .Fixed => {},
},
} }
switch (dad_adj) { switch (dad_adj) {

View File

@ -24,14 +24,14 @@ pub const Io = struct {
postflg: PostFlag, postflg: PostFlag,
waitcnt: WaitControl, waitcnt: WaitControl,
haltcnt: HaltControl, haltcnt: HaltControl,
keyinput: KeyInput, keyinput: AtomicKeyInput,
pub fn init() Self { pub fn init() Self {
return .{ return .{
.ime = false, .ime = false,
.ie = .{ .raw = 0x0000 }, .ie = .{ .raw = 0x0000 },
.irq = .{ .raw = 0x0000 }, .irq = .{ .raw = 0x0000 },
.keyinput = .{ .raw = 0x03FF }, .keyinput = AtomicKeyInput.init(.{ .raw = 0x03FF }),
.waitcnt = .{ .raw = 0x0000_0000 }, // Bit 15 == 0 for GBA .waitcnt = .{ .raw = 0x0000_0000 }, // Bit 15 == 0 for GBA
.postflg = .FirstBoot, .postflg = .FirstBoot,
.haltcnt = .Execute, .haltcnt = .Execute,
@ -92,7 +92,7 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT", .{T}), 0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT", .{T}),
// Keypad Input // Keypad Input
0x0400_0130 => bus.io.keyinput.raw, 0x0400_0130 => bus.io.keyinput.load(.Monotonic).raw,
// Serial Communication 2 // Serial Communication 2
0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}), 0x0400_0134 => util.io.read.todo(log, "Read {} from RCNT", .{T}),
@ -376,6 +376,31 @@ const KeyInput = extern union {
raw: u16, raw: u16,
}; };
const AtomicKeyInput = struct {
const Self = @This();
const Ordering = std.atomic.Ordering;
inner: KeyInput,
pub fn init(value: KeyInput) Self {
return .{ .inner = value };
}
pub inline fn load(self: *const Self, comptime ordering: Ordering) KeyInput {
return .{ .raw = switch (ordering) {
.AcqRel, .Release => @compileError("not supported for atomic loads"),
else => @atomicLoad(u16, &self.inner.raw, ordering),
} };
}
pub inline fn store(self: *Self, value: u16, comptime ordering: Ordering) void {
switch (ordering) {
.AcqRel, .Acquire => @compileError("not supported for atomic stores"),
else => @atomicStore(u16, &self.inner.raw, value, ordering),
}
}
};
// Read / Write // Read / Write
pub const BackgroundControl = extern union { pub const BackgroundControl = extern union {
priority: Bitfield(u16, 0, 2), priority: Bitfield(u16, 0, 2),

View File

@ -5,7 +5,7 @@ const TimerControl = @import("io.zig").TimerControl;
const Scheduler = @import("../scheduler.zig").Scheduler; const Scheduler = @import("../scheduler.zig").Scheduler;
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi; const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
pub const TimerTuple = std.meta.Tuple(&[_]type{ Timer(0), Timer(1), Timer(2), Timer(3) }); pub const TimerTuple = struct { Timer(0), Timer(1), Timer(2), Timer(3) };
const log = std.log.scoped(.Timer); const log = std.log.scoped(.Timer);
const getHalf = util.getHalf; const getHalf = util.getHalf;
@ -150,20 +150,35 @@ fn Timer(comptime id: u2) type {
pub fn setTimcntH(self: *Self, halfword: u16) void { pub fn setTimcntH(self: *Self, halfword: u16) void {
const new = TimerControl{ .raw = halfword }; const new = TimerControl{ .raw = halfword };
// If Timer happens to be enabled, It will either be resheduled or disabled if (self.cnt.enabled.read()) {
// timer was already enabled
// If enabled falling edge or cascade falling edge, timer is paused
if (!new.enabled.read() or (!self.cnt.cascade.read() and new.cascade.read())) {
self.sched.removeScheduledEvent(.{ .TimerOverflow = id }); self.sched.removeScheduledEvent(.{ .TimerOverflow = id });
if (self.cnt.enabled.read() and (new.cascade.read() or !new.enabled.read())) { // Counter should hold the value it stopped at meaning we have to calculate it now
// Either through the cascade bit or the enable bit, the timer has effectively been disabled
// The Counter should hold whatever value it should have been at when it was disabled
self._counter +%= @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency()); self._counter +%= @truncate(u16, (self.sched.now() - self._start_timestamp) / self.frequency());
} }
// The counter is only reloaded on the rising edge of the enable bit // the timer has always been enabled, but the cascade bit which was blocking the timer has been unset
if (!self.cnt.enabled.read() and new.enabled.read()) self._counter = self._reload; if (new.enabled.read() and (self.cnt.cascade.read() and !new.cascade.read())) {
// we want to reschedule the timer event, however we won't reload the counter.
// the invariant here is that self._counter holds the already calculated paused value
// If Timer is enabled and we're not cascading, we need to schedule an overflow event self.rescheduleTimerExpire(0);
if (new.enabled.read() and !new.cascade.read()) self.rescheduleTimerExpire(0); }
} else {
// the timer was previously disabeld
if (new.enabled.read()) {
// timer should start counting (with a reloaded counter value)
self._counter = self._reload;
// if cascade happens to be set, the timer doesn't actually do anything though
if (!new.cascade.read()) self.rescheduleTimerExpire(0);
}
}
self.cnt.raw = halfword; self.cnt.raw = halfword;
} }
@ -202,7 +217,8 @@ fn Timer(comptime id: u2) type {
} }
// Reschedule Timer if we're not cascading // Reschedule Timer if we're not cascading
if (!self.cnt.cascade.read()) { // TIM0 cascade value is N/A
if (id == 0 or !self.cnt.cascade.read()) {
self._counter = self._reload; self._counter = self._reload;
self.rescheduleTimerExpire(late); self.rescheduleTimerExpire(late);
} }

View File

@ -7,6 +7,7 @@ const Scheduler = @import("scheduler.zig").Scheduler;
const Logger = @import("../util.zig").Logger; const Logger = @import("../util.zig").Logger;
const File = std.fs.File; const File = std.fs.File;
const log = std.log.scoped(.Arm7Tdmi);
// ARM Instructions // ARM Instructions
pub const arm = struct { pub const arm = struct {
@ -234,8 +235,6 @@ pub const thumb = struct {
} }
}; };
const log = std.log.scoped(.Arm7Tdmi);
pub const Arm7tdmi = struct { pub const Arm7tdmi = struct {
const Self = @This(); const Self = @This();
@ -246,34 +245,36 @@ pub const Arm7tdmi = struct {
cpsr: PSR, cpsr: PSR,
spsr: PSR, spsr: PSR,
/// Storage for R8_fiq -> R12_fiq and their normal counterparts bank: Bank,
/// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...]
banked_fiq: [2 * 5]u32,
/// Storage for r13_<mode>, r14_<mode>
/// e.g. [r13, r14, r13_svc, r14_svc]
banked_r: [2 * 6]u32,
banked_spsr: [5]PSR,
logger: ?Logger, logger: ?Logger,
pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self { /// Bank of Registers from other CPU Modes
return Self{ const Bank = struct {
.r = [_]u32{0x00} ** 16, /// Storage for r13_<mode>, r14_<mode>
.pipe = Pipeline.init(), /// e.g. [r13, r14, r13_svc, r14_svc]
.sched = sched, r: [2 * 6]u32,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F }, /// Storage for R8_fiq -> R12_fiq and their normal counterparts
.spsr = .{ .raw = 0x0000_0000 }, /// e.g [r[0 + 8], fiq_r[0 + 8], r[1 + 8], fiq_r[1 + 8]...]
.banked_fiq = [_]u32{0x00} ** 10, fiq: [2 * 5]u32,
.banked_r = [_]u32{0x00} ** 12,
.banked_spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5, spsr: [5]PSR,
.logger = if (log_file) |file| Logger.init(file) else null,
const Kind = enum(u1) {
R13 = 0,
R14,
};
pub fn create() Bank {
return .{
.r = [_]u32{0x00} ** 12,
.fiq = [_]u32{0x00} ** 10,
.spsr = [_]PSR{.{ .raw = 0x0000_0000 }} ** 5,
}; };
} }
inline fn bankedIdx(mode: Mode, kind: BankedKind) usize { inline fn regIdx(mode: Mode, kind: Kind) usize {
const idx: usize = switch (mode) { const idx: usize = switch (mode) {
.User, .System => 0, .User, .System => 0,
.Supervisor => 1, .Supervisor => 1,
@ -286,7 +287,7 @@ pub const Arm7tdmi = struct {
return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0; return (idx * 2) + if (kind == .R14) @as(usize, 1) else 0;
} }
inline fn bankedSpsrIndex(mode: Mode) usize { inline fn spsrIdx(mode: Mode) usize {
return switch (mode) { return switch (mode) {
.Supervisor => 0, .Supervisor => 0,
.Abort => 1, .Abort => 1,
@ -297,9 +298,23 @@ pub const Arm7tdmi = struct {
}; };
} }
inline fn bankedFiqIdx(i: usize, mode: Mode) usize { inline fn fiqIdx(i: usize, mode: Mode) usize {
return (i * 2) + if (mode == .Fiq) @as(usize, 1) else 0; return (i * 2) + if (mode == .Fiq) @as(usize, 1) else 0;
} }
};
pub fn init(sched: *Scheduler, bus: *Bus, log_file: ?std.fs.File) Self {
return Self{
.r = [_]u32{0x00} ** 16,
.pipe = Pipeline.init(),
.sched = sched,
.bus = bus,
.cpsr = .{ .raw = 0x0000_001F },
.spsr = .{ .raw = 0x0000_0000 },
.bank = Bank.create(),
.logger = if (log_file) |file| Logger.init(file) else null,
};
}
pub inline fn hasSPSR(self: *const Self) bool { pub inline fn hasSPSR(self: *const Self) bool {
const mode = getModeChecked(self, self.cpsr.mode.read()); const mode = getModeChecked(self, self.cpsr.mode.read());
@ -336,14 +351,14 @@ pub const Arm7tdmi = struct {
switch (idx) { switch (idx) {
8...12 => { 8...12 => {
if (current == .Fiq) { if (current == .Fiq) {
self.banked_fiq[bankedFiqIdx(idx - 8, .User)] = value; self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] = value;
} else self.r[idx] = value; } else self.r[idx] = value;
}, },
13, 14 => switch (current) { 13, 14 => switch (current) {
.User, .System => self.r[idx] = value, .User, .System => self.r[idx] = value,
else => { else => {
const kind = std.meta.intToEnum(BankedKind, idx - 13) catch unreachable; const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable;
self.banked_r[bankedIdx(.User, kind)] = value; self.bank.r[Bank.regIdx(.User, kind)] = value;
}, },
}, },
else => self.r[idx] = value, // R0 -> R7 and R15 else => self.r[idx] = value, // R0 -> R7 and R15
@ -354,12 +369,12 @@ pub const Arm7tdmi = struct {
const current = getModeChecked(self, self.cpsr.mode.read()); const current = getModeChecked(self, self.cpsr.mode.read());
return switch (idx) { return switch (idx) {
8...12 => if (current == .Fiq) self.banked_fiq[bankedFiqIdx(idx - 8, .User)] else self.r[idx], 8...12 => if (current == .Fiq) self.bank.fiq[Bank.fiqIdx(idx - 8, .User)] else self.r[idx],
13, 14 => switch (current) { 13, 14 => switch (current) {
.User, .System => self.r[idx], .User, .System => self.r[idx],
else => blk: { else => blk: {
const kind = std.meta.intToEnum(BankedKind, idx - 13) catch unreachable; const kind = std.meta.intToEnum(Bank.Kind, idx - 13) catch unreachable;
break :blk self.banked_r[bankedIdx(.User, kind)]; break :blk self.bank.r[Bank.regIdx(.User, kind)];
}, },
}, },
else => self.r[idx], // R0 -> R7 and R15 else => self.r[idx], // R0 -> R7 and R15
@ -372,38 +387,38 @@ pub const Arm7tdmi = struct {
// Bank R8 -> r12 // Bank R8 -> r12
var i: usize = 0; var i: usize = 0;
while (i < 5) : (i += 1) { while (i < 5) : (i += 1) {
self.banked_fiq[bankedFiqIdx(i, now)] = self.r[8 + i]; self.bank.fiq[Bank.fiqIdx(i, now)] = self.r[8 + i];
} }
// Bank r13, r14, SPSR // Bank r13, r14, SPSR
switch (now) { switch (now) {
.User, .System => { .User, .System => {
self.banked_r[bankedIdx(now, .R13)] = self.r[13]; self.bank.r[Bank.regIdx(now, .R13)] = self.r[13];
self.banked_r[bankedIdx(now, .R14)] = self.r[14]; self.bank.r[Bank.regIdx(now, .R14)] = self.r[14];
}, },
else => { else => {
self.banked_r[bankedIdx(now, .R13)] = self.r[13]; self.bank.r[Bank.regIdx(now, .R13)] = self.r[13];
self.banked_r[bankedIdx(now, .R14)] = self.r[14]; self.bank.r[Bank.regIdx(now, .R14)] = self.r[14];
self.banked_spsr[bankedSpsrIndex(now)] = self.spsr; self.bank.spsr[Bank.spsrIdx(now)] = self.spsr;
}, },
} }
// Grab R8 -> R12 // Grab R8 -> R12
i = 0; i = 0;
while (i < 5) : (i += 1) { while (i < 5) : (i += 1) {
self.r[8 + i] = self.banked_fiq[bankedFiqIdx(i, next)]; self.r[8 + i] = self.bank.fiq[Bank.fiqIdx(i, next)];
} }
// Grab r13, r14, SPSR // Grab r13, r14, SPSR
switch (next) { switch (next) {
.User, .System => { .User, .System => {
self.r[13] = self.banked_r[bankedIdx(next, .R13)]; self.r[13] = self.bank.r[Bank.regIdx(next, .R13)];
self.r[14] = self.banked_r[bankedIdx(next, .R14)]; self.r[14] = self.bank.r[Bank.regIdx(next, .R14)];
}, },
else => { else => {
self.r[13] = self.banked_r[bankedIdx(next, .R13)]; self.r[13] = self.bank.r[Bank.regIdx(next, .R13)];
self.r[14] = self.banked_r[bankedIdx(next, .R14)]; self.r[14] = self.bank.r[Bank.regIdx(next, .R14)];
self.spsr = self.banked_spsr[bankedSpsrIndex(next)]; self.spsr = self.bank.spsr[Bank.spsrIdx(next)];
}, },
} }
@ -424,8 +439,8 @@ pub const Arm7tdmi = struct {
self.r[13] = 0x0300_7F00; self.r[13] = 0x0300_7F00;
self.r[15] = 0x0800_0000; self.r[15] = 0x0800_0000;
self.banked_r[bankedIdx(.Irq, .R13)] = 0x0300_7FA0; self.bank.r[Bank.regIdx(.Irq, .R13)] = 0x0300_7FA0;
self.banked_r[bankedIdx(.Supervisor, .R13)] = 0x0300_7FE0; self.bank.r[Bank.regIdx(.Supervisor, .R13)] = 0x0300_7FE0;
// self.cpsr.raw = 0x6000001F; // self.cpsr.raw = 0x6000001F;
self.cpsr.raw = 0x0000_001F; self.cpsr.raw = 0x0000_001F;
@ -515,10 +530,10 @@ pub const Arm7tdmi = struct {
std.debug.print("R{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\n", .{ i, self.r[i], i_1, self.r[i_1], i_2, self.r[i_2], i_3, self.r[i_3] }); std.debug.print("R{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\tR{}: 0x{X:0>8}\n", .{ i, self.r[i], i_1, self.r[i_1], i_2, self.r[i_2], i_3, self.r[i_3] });
} }
std.debug.print("cpsr: 0x{X:0>8} ", .{self.cpsr.raw}); std.debug.print("cpsr: 0x{X:0>8} ", .{self.cpsr.raw});
prettyPrintPsr(&self.cpsr); self.cpsr.toString();
std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw}); std.debug.print("spsr: 0x{X:0>8} ", .{self.spsr.raw});
prettyPrintPsr(&self.spsr); self.spsr.toString();
std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage}); std.debug.print("pipeline: {??X:0>8}\n", .{self.pipe.stage});
@ -536,76 +551,6 @@ pub const Arm7tdmi = struct {
std.debug.panic(format, args); std.debug.panic(format, args);
} }
fn prettyPrintPsr(psr: *const PSR) void {
std.debug.print("[", .{});
if (psr.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{});
if (psr.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{});
if (psr.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{});
if (psr.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{});
if (psr.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{});
if (psr.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{});
if (psr.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{});
std.debug.print("|", .{});
if (getMode(psr.mode.read())) |mode| std.debug.print("{s}", .{modeString(mode)}) else std.debug.print("---", .{});
std.debug.print("]\n", .{});
}
fn modeString(mode: Mode) []const u8 {
return switch (mode) {
.User => "usr",
.Fiq => "fiq",
.Irq => "irq",
.Supervisor => "svc",
.Abort => "abt",
.Undefined => "und",
.System => "sys",
};
}
fn mgbaLog(self: *const Self, file: *const File, opcode: u32) !void {
const thumb_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>4}:\n";
const arm_fmt = "{X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} {X:0>8} cpsr: {X:0>8} | {X:0>8}:\n";
var buf: [0x100]u8 = [_]u8{0x00} ** 0x100; // this is larger than it needs to be
const r0 = self.r[0];
const r1 = self.r[1];
const r2 = self.r[2];
const r3 = self.r[3];
const r4 = self.r[4];
const r5 = self.r[5];
const r6 = self.r[6];
const r7 = self.r[7];
const r8 = self.r[8];
const r9 = self.r[9];
const r10 = self.r[10];
const r11 = self.r[11];
const r12 = self.r[12];
const r13 = self.r[13];
const r14 = self.r[14];
const r15 = self.r[15] -| if (self.cpsr.t.read()) 2 else @as(u32, 4);
const c_psr = self.cpsr.raw;
var log_str: []u8 = undefined;
if (self.cpsr.t.read()) {
if (opcode >> 11 == 0x1E) {
// Instruction 1 of a BL Opcode, print in ARM mode
const other_half = self.bus.debugRead(u16, self.r[15] - 2);
const bl_opcode = @as(u32, opcode) << 16 | other_half;
log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, bl_opcode });
} else {
log_str = try std.fmt.bufPrint(&buf, thumb_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, opcode });
}
} else {
log_str = try std.fmt.bufPrint(&buf, arm_fmt, .{ r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, c_psr, opcode });
}
_ = try file.writeAll(log_str);
}
}; };
const condition_lut = [_]u16{ const condition_lut = [_]u16{
@ -684,6 +629,22 @@ pub const PSR = extern union {
z: Bit(u32, 30), z: Bit(u32, 30),
n: Bit(u32, 31), n: Bit(u32, 31),
raw: u32, raw: u32,
fn toString(self: PSR) void {
std.debug.print("[", .{});
if (self.n.read()) std.debug.print("N", .{}) else std.debug.print("-", .{});
if (self.z.read()) std.debug.print("Z", .{}) else std.debug.print("-", .{});
if (self.c.read()) std.debug.print("C", .{}) else std.debug.print("-", .{});
if (self.v.read()) std.debug.print("V", .{}) else std.debug.print("-", .{});
if (self.i.read()) std.debug.print("I", .{}) else std.debug.print("-", .{});
if (self.f.read()) std.debug.print("F", .{}) else std.debug.print("-", .{});
if (self.t.read()) std.debug.print("T", .{}) else std.debug.print("-", .{});
std.debug.print("|", .{});
if (getMode(self.mode.read())) |m| std.debug.print("{s}", .{m.toString()}) else std.debug.print("---", .{});
std.debug.print("]\n", .{});
}
}; };
const Mode = enum(u5) { const Mode = enum(u5) {
@ -694,11 +655,18 @@ const Mode = enum(u5) {
Abort = 0b10111, Abort = 0b10111,
Undefined = 0b11011, Undefined = 0b11011,
System = 0b11111, System = 0b11111,
};
const BankedKind = enum(u1) { fn toString(self: Mode) []const u8 {
R13 = 0, return switch (self) {
R14, .User => "usr",
.Fiq => "fiq",
.Irq => "irq",
.Supervisor => "svc",
.Abort => "abt",
.Undefined => "und",
.System => "sys",
};
}
}; };
fn getMode(bits: u5) ?Mode { fn getMode(bits: u5) ?Mode {

View File

@ -56,7 +56,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
.Unlimited, .UnlimitedFPS => { .Unlimited, .UnlimitedFPS => {
log.info("Emulation w/out video sync", .{}); log.info("Emulation w/out video sync", .{});
while (!quit.load(.SeqCst)) { while (!quit.load(.Monotonic)) {
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full); audioSync(audio_sync, cpu.bus.apu.stream, &cpu.bus.apu.is_buffer_full);
@ -68,7 +68,7 @@ fn inner(comptime kind: RunKind, audio_sync: bool, quit: *Atomic(bool), schedule
var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer"); var timer = Timer.start() catch @panic("failed to initalize std.timer.Timer");
var wake_time: u64 = frame_period; var wake_time: u64 = frame_period;
while (!quit.load(.SeqCst)) { while (!quit.load(.Monotonic)) {
runFrame(scheduler, cpu); runFrame(scheduler, cpu);
const new_wake_time = videoSync(&timer, wake_time); const new_wake_time = videoSync(&timer, wake_time);

View File

@ -87,7 +87,7 @@ pub fn main() void {
cpu.fastBoot(); cpu.fastBoot();
} }
var gui = Gui.init(&bus.pak.title, &bus.apu, width, height); var gui = Gui.init(&bus.pak.title, &bus.apu, width, height) catch |e| exitln("failed to init gui: {}", .{e});
defer gui.deinit(); defer gui.deinit();
gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e}); gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e});

View File

@ -15,7 +15,7 @@ const gba_height = @import("core/ppu.zig").height;
pub const sample_rate = 1 << 15; pub const sample_rate = 1 << 15;
pub const sample_format = SDL.AUDIO_U16; pub const sample_format = SDL.AUDIO_U16;
const default_title: []const u8 = "ZBA"; const default_title = "ZBA";
pub const Gui = struct { pub const Gui = struct {
const Self = @This(); const Self = @This();
@ -44,7 +44,7 @@ pub const Gui = struct {
program_id: gl.GLuint, program_id: gl.GLuint,
pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) Self { pub fn init(title: *const [12]u8, apu: *Apu, width: i32, height: i32) !Self {
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic(); if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_EVENTS | SDL.SDL_INIT_AUDIO) < 0) panic();
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic(); if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GL_CONTEXT_PROFILE_CORE) < 0) panic();
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic(); if (SDL.SDL_GL_SetAttribute(SDL.SDL_GL_CONTEXT_MAJOR_VERSION, 3) < 0) panic();
@ -53,7 +53,7 @@ pub const Gui = struct {
const win_scale = @intCast(c_int, config.config().host.win_scale); const win_scale = @intCast(c_int, config.config().host.win_scale);
const window = SDL.SDL_CreateWindow( const window = SDL.SDL_CreateWindow(
default_title.ptr, default_title,
SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
@as(c_int, width * win_scale), @as(c_int, width * win_scale),
@ -64,10 +64,10 @@ pub const Gui = struct {
const ctx = SDL.SDL_GL_CreateContext(window) orelse panic(); const ctx = SDL.SDL_GL_CreateContext(window) orelse panic();
if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic(); if (SDL.SDL_GL_MakeCurrent(window, ctx) < 0) panic();
gl.load(ctx, Self.glGetProcAddress) catch @panic("gl.load failed"); try gl.load(ctx, Self.glGetProcAddress);
if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic(); if (SDL.SDL_GL_SetSwapInterval(@boolToInt(config.config().host.vsync)) < 0) panic();
const program_id = compileShaders(); const program_id = try compileShaders();
return Self{ return Self{
.window = window, .window = window,
@ -78,7 +78,7 @@ pub const Gui = struct {
}; };
} }
fn compileShaders() gl.GLuint { fn compileShaders() !gl.GLuint {
// TODO: Panic on Shader Compiler Failure + Error Message // TODO: Panic on Shader Compiler Failure + Error Message
const vert_shader = @embedFile("shader/pixelbuf.vert"); const vert_shader = @embedFile("shader/pixelbuf.vert");
const frag_shader = @embedFile("shader/pixelbuf.frag"); const frag_shader = @embedFile("shader/pixelbuf.frag");
@ -89,12 +89,16 @@ pub const Gui = struct {
gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0); gl.shaderSource(vs, 1, &[_][*c]const u8{vert_shader}, 0);
gl.compileShader(vs); gl.compileShader(vs);
if (!shader.didCompile(vs)) return error.VertexCompileError;
const fs = gl.createShader(gl.FRAGMENT_SHADER); const fs = gl.createShader(gl.FRAGMENT_SHADER);
defer gl.deleteShader(fs); defer gl.deleteShader(fs);
gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0); gl.shaderSource(fs, 1, &[_][*c]const u8{frag_shader}, 0);
gl.compileShader(fs); gl.compileShader(fs);
if (!shader.didCompile(fs)) return error.FragmentCompileError;
const program = gl.createProgram(); const program = gl.createProgram();
gl.attachShader(program, vs); gl.attachShader(program, vs);
gl.attachShader(program, fs); gl.attachShader(program, fs);
@ -104,7 +108,7 @@ pub const Gui = struct {
} }
// Returns the VAO ID since it's used in run() // Returns the VAO ID since it's used in run()
fn generateBuffers() [3]c_uint { fn generateBuffers() struct { c_uint, c_uint, c_uint } {
var vao_id: c_uint = undefined; var vao_id: c_uint = undefined;
var vbo_id: c_uint = undefined; var vbo_id: c_uint = undefined;
var ebo_id: c_uint = undefined; var ebo_id: c_uint = undefined;
@ -154,13 +158,21 @@ pub const Gui = struct {
var quit = std.atomic.Atomic(bool).init(false); var quit = std.atomic.Atomic(bool).init(false);
var tracker = FpsTracker.init(); var tracker = FpsTracker.init();
var buffer_ids = Self.generateBuffers();
defer {
gl.deleteBuffers(1, &buffer_ids[2]); // EBO
gl.deleteBuffers(1, &buffer_ids[1]); // VBO
gl.deleteVertexArrays(1, &buffer_ids[0]); // VAO
}
const vao_id = buffer_ids[0];
const tex_id = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
defer gl.deleteTextures(1, &tex_id);
const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker }); const thread = try std.Thread.spawn(.{}, emu.run, .{ &quit, scheduler, cpu, &tracker });
defer thread.join(); defer thread.join();
var title_buf: [0x100]u8 = [_]u8{0} ** 0x100; var title_buf: [0x100]u8 = undefined;
const vao_id = Self.generateBuffers()[0];
_ = Self.generateTexture(cpu.bus.ppu.framebuf.get(.Renderer));
emu_loop: while (true) { emu_loop: while (true) {
var event: SDL.SDL_Event = undefined; var event: SDL.SDL_Event = undefined;
@ -168,54 +180,50 @@ pub const Gui = struct {
switch (event.type) { switch (event.type) {
SDL.SDL_QUIT => break :emu_loop, SDL.SDL_QUIT => break :emu_loop,
SDL.SDL_KEYDOWN => { SDL.SDL_KEYDOWN => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym; const key_code = event.key.keysym.sym;
var keyinput = cpu.bus.io.keyinput.load(.Monotonic);
switch (key_code) { switch (key_code) {
SDL.SDLK_UP => io.keyinput.up.unset(), SDL.SDLK_UP => keyinput.up.unset(),
SDL.SDLK_DOWN => io.keyinput.down.unset(), SDL.SDLK_DOWN => keyinput.down.unset(),
SDL.SDLK_LEFT => io.keyinput.left.unset(), SDL.SDLK_LEFT => keyinput.left.unset(),
SDL.SDLK_RIGHT => io.keyinput.right.unset(), SDL.SDLK_RIGHT => keyinput.right.unset(),
SDL.SDLK_x => io.keyinput.a.unset(), SDL.SDLK_x => keyinput.a.unset(),
SDL.SDLK_z => io.keyinput.b.unset(), SDL.SDLK_z => keyinput.b.unset(),
SDL.SDLK_a => io.keyinput.shoulder_l.unset(), SDL.SDLK_a => keyinput.shoulder_l.unset(),
SDL.SDLK_s => io.keyinput.shoulder_r.unset(), SDL.SDLK_s => keyinput.shoulder_r.unset(),
SDL.SDLK_RETURN => io.keyinput.start.unset(), SDL.SDLK_RETURN => keyinput.start.unset(),
SDL.SDLK_RSHIFT => io.keyinput.select.unset(), SDL.SDLK_RSHIFT => keyinput.select.unset(),
else => {}, else => {},
} }
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
}, },
SDL.SDL_KEYUP => { SDL.SDL_KEYUP => {
const io = &cpu.bus.io;
const key_code = event.key.keysym.sym; const key_code = event.key.keysym.sym;
var keyinput = cpu.bus.io.keyinput.load(.Monotonic);
switch (key_code) { switch (key_code) {
SDL.SDLK_UP => io.keyinput.up.set(), SDL.SDLK_UP => keyinput.up.set(),
SDL.SDLK_DOWN => io.keyinput.down.set(), SDL.SDLK_DOWN => keyinput.down.set(),
SDL.SDLK_LEFT => io.keyinput.left.set(), SDL.SDLK_LEFT => keyinput.left.set(),
SDL.SDLK_RIGHT => io.keyinput.right.set(), SDL.SDLK_RIGHT => keyinput.right.set(),
SDL.SDLK_x => io.keyinput.a.set(), SDL.SDLK_x => keyinput.a.set(),
SDL.SDLK_z => io.keyinput.b.set(), SDL.SDLK_z => keyinput.b.set(),
SDL.SDLK_a => io.keyinput.shoulder_l.set(), SDL.SDLK_a => keyinput.shoulder_l.set(),
SDL.SDLK_s => io.keyinput.shoulder_r.set(), SDL.SDLK_s => keyinput.shoulder_r.set(),
SDL.SDLK_RETURN => io.keyinput.start.set(), SDL.SDLK_RETURN => keyinput.start.set(),
SDL.SDLK_RSHIFT => io.keyinput.select.set(), SDL.SDLK_RSHIFT => keyinput.select.set(),
SDL.SDLK_i => { SDL.SDLK_i => {
comptime std.debug.assert(sample_format == SDL.AUDIO_U16); comptime std.debug.assert(sample_format == SDL.AUDIO_U16);
log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(u16))}); log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(u16))});
}, },
SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }), // SDL.SDLK_j => log.err("Scheduler Capacity: {} | Scheduler Event Count: {}", .{ scheduler.queue.capacity(), scheduler.queue.count() }),
SDL.SDLK_k => { SDL.SDLK_k => {},
// Dump IWRAM to file
log.info("PC: 0x{X:0>8}", .{cpu.r[15]});
log.info("LR: 0x{X:0>8}", .{cpu.r[14]});
// const iwram_file = try std.fs.cwd().createFile("iwram.bin", .{});
// defer iwram_file.close();
// try iwram_file.writeAll(cpu.bus.iwram.buf);
},
else => {}, else => {},
} }
cpu.bus.io.keyinput.store(keyinput.raw, .Monotonic);
}, },
else => {}, else => {},
} }
@ -230,16 +238,15 @@ pub const Gui = struct {
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, null);
SDL.SDL_GL_SwapWindow(self.window); SDL.SDL_GL_SwapWindow(self.window);
const dyn_title = std.fmt.bufPrint(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable; const dyn_title = std.fmt.bufPrintZ(&title_buf, "ZBA | {s} [Emu: {}fps] ", .{ self.title, tracker.value() }) catch unreachable;
SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr); SDL.SDL_SetWindowTitle(self.window, dyn_title.ptr);
} }
quit.store(true, .SeqCst); // Terminate Emulator Thread quit.store(true, .Monotonic); // Terminate Emulator Thread
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.audio.deinit(); self.audio.deinit();
// TODO: Buffer deletions
gl.deleteProgram(self.program_id); gl.deleteProgram(self.program_id);
SDL.SDL_GL_DeleteContext(self.ctx); SDL.SDL_GL_DeleteContext(self.ctx);
SDL.SDL_DestroyWindow(self.window); SDL.SDL_DestroyWindow(self.window);
@ -296,6 +303,28 @@ const Audio = struct {
} }
}; };
const shader = struct {
const Kind = enum { vertex, fragment };
const log = std.log.scoped(.Shader);
fn didCompile(id: gl.GLuint) bool {
var success: gl.GLint = undefined;
gl.getShaderiv(id, gl.COMPILE_STATUS, &success);
if (success == 0) err(id);
return success == 1;
}
fn err(id: gl.GLuint) void {
const buf_len = 512;
var error_msg: [buf_len]u8 = undefined;
gl.getShaderInfoLog(id, buf_len, 0, &error_msg);
log.err("{s}", .{std.mem.sliceTo(&error_msg, 0)});
}
};
fn panic() noreturn { fn panic() noreturn {
const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error"; const str = @as(?[*:0]const u8, SDL.SDL_GetError()) orelse "unknown error";
@panic(std.mem.sliceTo(str, 0)); @panic(std.mem.sliceTo(str, 0));

View File

@ -49,7 +49,7 @@ pub const FpsTracker = struct {
pub fn value(self: *Self) u32 { pub fn value(self: *Self) u32 {
if (self.timer.read() >= std.time.ns_per_s) { if (self.timer.read() >= std.time.ns_per_s) {
self.fps = self.count.swap(0, .SeqCst); self.fps = self.count.swap(0, .Monotonic);
self.timer.reset(); self.timer.reset();
} }
@ -148,7 +148,7 @@ pub const Logger = struct {
if (cpu.cpsr.t.read()) { if (cpu.cpsr.t.read()) {
if (opcode >> 11 == 0x1E) { if (opcode >> 11 == 0x1E) {
// Instruction 1 of a BL Opcode, print in ARM mode // Instruction 1 of a BL Opcode, print in ARM mode
const low = cpu.bus.dbgRead(u16, cpu.r[15]); const low = cpu.bus.dbgRead(u16, cpu.r[15] - 2);
const bl_opcode = @as(u32, opcode) << 16 | low; const bl_opcode = @as(u32, opcode) << 16 | low;
self.print(arm_fmt, Self.fmtArgs(cpu, bl_opcode)) catch @panic("failed to write to log file"); self.print(arm_fmt, Self.fmtArgs(cpu, bl_opcode)) catch @panic("failed to write to log file");