Compare commits
7 Commits
06ee31980e
...
f715585867
Author | SHA1 | Date |
---|---|---|
Rekai Nyangadzayi Musuka | f715585867 | |
Rekai Nyangadzayi Musuka | cab40efc59 | |
Rekai Nyangadzayi Musuka | a17300a8e0 | |
Rekai Nyangadzayi Musuka | 2ebe1c0b0e | |
Rekai Nyangadzayi Musuka | 6db70638fe | |
Rekai Nyangadzayi Musuka | bc5ab5810a | |
Rekai Nyangadzayi Musuka | decf2a01c9 |
|
@ -1,53 +0,0 @@
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
||||||
runs-on: ${{matrix.os}}
|
|
||||||
steps:
|
|
||||||
- uses: goto-bus-stop/setup-zig@v1
|
|
||||||
with:
|
|
||||||
version: master
|
|
||||||
- name: prepare-linux
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install libsdl2-dev
|
|
||||||
- name: prepare-windows
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
vcpkg integrate install
|
|
||||||
vcpkg install sdl2:x64-windows
|
|
||||||
git config --global core.autocrlf false
|
|
||||||
- name: prepare-macos
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
run: |
|
|
||||||
brew install sdl2
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: build
|
|
||||||
run: zig build -Drelease-safe
|
|
||||||
- name: upload
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: zba-${{matrix.os}}
|
|
||||||
path: zig-out/bin
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- uses: goto-bus-stop/setup-zig@v1
|
|
||||||
with:
|
|
||||||
version: master
|
|
||||||
- run: zig fmt src/**/*.zig
|
|
||||||
|
|
104
README.md
104
README.md
|
@ -1,96 +1,80 @@
|
||||||
# ZBA (working title)
|
# ZBA (working title)
|
||||||
|
|
||||||
A Game Boy Advance Emulator written in Zig ⚡!
|
A Game Boy Advance Emulator written in Zig ⚡!
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
I'm hardly the first to write a Game Boy Advance Emulator nor will I be the last. This project isn't going to compete with the GOATs like
|
||||||
|
[mGBA](https://github.com/mgba-emu) or [NanoBoyAdvance](https://github.com/nba-emu/NanoBoyAdvance). There aren't any interesting
|
||||||
|
ideas either like in [DSHBA](https://github.com/DenSinH/DSHBA).
|
||||||
|
|
||||||
I'm hardly the first to write a Game Boy Advance Emulator nor will I be the last. This project isn't going to compete with the GOATs like [mGBA](https://github.com/mgba-emu) or [NanoBoyAdvance](https://github.com/nba-emu/NanoBoyAdvance). There aren't any interesting ideas either like in [DSHBA](https://github.com/DenSinH/DSHBA).
|
This is a simple (read: incomplete) for-fun long-term project. I hope to get "mostly there", which to me means that I'm not missing any major hardware
|
||||||
|
features and the set of possible improvements would be in memory timing or in UI/UX. With respect to that goal, here's what's outstanding:
|
||||||
This is a simple (read: incomplete) for-fun long-term project. I hope to get "mostly there", which to me means that I'm not missing any major hardware features and the set of possible improvements would be in memory timing or in UI/UX. With respect to that goal, here's what's outstanding:
|
|
||||||
|
|
||||||
### TODO
|
|
||||||
|
|
||||||
|
### TODO
|
||||||
- [ ] Affine Sprites
|
- [ ] Affine Sprites
|
||||||
- [ ] Windowing (see [this branch](https://git.musuka.dev/paoda/zba/src/branch/window))
|
- [ ] Windowing (see [this branch](https://git.musuka.dev/paoda/zba/src/branch/window))
|
||||||
- [ ] Audio Resampler (Having issues with SDL2's)
|
- [ ] Audio Resampler (Having issues with SDL2's)
|
||||||
- [ ] Immediate Mode GUI
|
- [ ] Immediate Mode GUI
|
||||||
- [ ] Refactoring for easy-ish perf boosts
|
- [ ] Refactoring for easy-ish perf boosts
|
||||||
|
|
||||||
## Usage
|
## Tests
|
||||||
|
|
||||||
As it currently exists, ZBA is run from the terminal. In your console of choice, type `./zba --help` to see what you can do.
|
|
||||||
|
|
||||||
I typically find myself typing `./zba -b ./bin/bios.bin ./bin/test/suite.gba` to see how badly my "cool new feature" broke everything else.
|
|
||||||
|
|
||||||
Need a BIOS? Why not try using the open-source [Cult-Of-GBA BIOS](https://github.com/Cult-of-GBA/BIOS) written by [fleroviux](https://github.com/fleroviux) and [DenSinH](https://github.com/DenSinH)?
|
|
||||||
|
|
||||||
Finally it's worth noting that ZBA uses a TOML config file it'll store in your OS's data directory. See `example.toml` to learn about the defaults and what exactly you can mess around with.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
- [x] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests)
|
- [x] [jsmolka's GBA Test Collection](https://github.com/jsmolka/gba-tests)
|
||||||
- [x] `arm.gba` and `thumb.gba`
|
- [x] `arm.gba` and `thumb.gba`
|
||||||
- [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba`
|
- [x] `flash64.gba`, `flash128.gba`, `none.gba`, and `sram.gba`
|
||||||
- [x] `hello.gba`, `shades.gba`, and `stripes.gba`
|
- [x] `hello.gba`, `shades.gba`, and `stripes.gba`
|
||||||
- [x] `memory.gba`
|
- [x] `memory.gba`
|
||||||
- [x] `bios.gba`
|
- [x] `bios.gba`
|
||||||
- [x] `nes.gba`
|
- [x] `nes.gba`
|
||||||
- [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms)
|
- [ ] [DenSinH's GBA ROMs](https://github.com/DenSinH/GBARoms)
|
||||||
- [x] `eeprom-test` and `flash-test`
|
- [x] `eeprom-test` and `flash-test`
|
||||||
- [x] `midikey2freq`
|
- [x] `midikey2freq`
|
||||||
- [ ] `swi-tests-random`
|
- [ ] `swi-tests-random`
|
||||||
- [ ] [destoer's GBA Tests](https://github.com/destoer/gba_tests)
|
- [ ] [destoer's GBA Tests](https://github.com/destoer/gba_tests)
|
||||||
- [x] `cond_invalid.gba`
|
- [x] `cond_invalid.gba`
|
||||||
- [x] `dma_priority.gba`
|
- [x] `dma_priority.gba`
|
||||||
- [x] `hello_world.gba`
|
- [x] `hello_world.gba`
|
||||||
- [x] `if_ack.gba`
|
- [x] `if_ack.gba`
|
||||||
- [ ] `line_timing.gba`
|
- [ ] `line_timing.gba`
|
||||||
- [ ] `lyc_midline.gba`
|
- [ ] `lyc_midline.gba`
|
||||||
- [ ] `window_midframe.gba`
|
- [ ] `window_midframe.gba`
|
||||||
- [x] [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`
|
||||||
- [x] `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)
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
* [GBATEK](https://problemkaputt.de/gbatek.htm)
|
||||||
- [GBATEK](https://problemkaputt.de/gbatek.htm)
|
* [TONC](https://coranac.com/tonc/text/toc.htm)
|
||||||
- [TONC](https://coranac.com/tonc/text/toc.htm)
|
* [ARM Architecture Reference Manual](https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/third-party/ddi0100e_arm_arm.pdf)
|
||||||
- [ARM Architecture Reference Manual](https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/third-party/ddi0100e_arm_arm.pdf)
|
* [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.4474+b41b35f57](https://github.com/ziglang/zig/tree/b41b35f57)
|
||||||
Most recently built on Zig [v0.10.0](https://github.com/ziglang/zig/tree/0.10.0)
|
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
* [SDL.zig](https://github.com/MasterQ32/SDL.zig)
|
||||||
- [SDL.zig](https://github.com/MasterQ32/SDL.zig)
|
* [SDL2](https://www.libsdl.org/download-2.0.php)
|
||||||
- [SDL2](https://www.libsdl.org/download-2.0.php)
|
* [zig-clap](https://github.com/Hejsil/zig-clap)
|
||||||
- [zig-clap](https://github.com/Hejsil/zig-clap)
|
* [known-folders](https://github.com/ziglibs/known-folders)
|
||||||
- [known-folders](https://github.com/ziglibs/known-folders)
|
* [zig-toml](https://github.com/aeronavery/zig-toml)
|
||||||
- [zig-toml](https://github.com/aeronavery/zig-toml)
|
* [zig-datetime](https://github.com/frmdstryr/zig-datetime)
|
||||||
- [zig-datetime](https://github.com/frmdstryr/zig-datetime)
|
* [`bitfields.zig`](https://github.com/FlorenceOS/Florence/blob/aaa5a9e568/lib/util/bitfields.zig)
|
||||||
- [`bitfields.zig`](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`.
|
`bitfields.zig` from [FlorenceOS](https://github.com/FlorenceOS) is included under `lib/util/bitfield.zig`.
|
||||||
|
|
||||||
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`
|
||||||
|
|
||||||
Be sure to provide SDL2 using:
|
Be sure to provide SDL2 using:
|
||||||
|
* Linux: Your distro's package manager
|
||||||
|
* MacOS: ¯\\\_(ツ)_/¯
|
||||||
|
* Windows: [`vcpkg`](https://github.com/Microsoft/vcpkg) (install `sdl2:x64-windows`)
|
||||||
|
|
||||||
- Linux: Your distro's package manager
|
`SDL.zig` will provide a helpful compile error if the zig compiler is unable to find SDL2.
|
||||||
- MacOS: ¯\\\_(ツ)_/¯
|
|
||||||
- Windows: [`vcpkg`](https://github.com/Microsoft/vcpkg) (install `sdl2:x64-windows`)
|
|
||||||
|
|
||||||
`SDL.zig` will provide a helpful compile error if the zig compiler is unable to find SDL2.
|
Once you've got all the dependencies, execute `zig build -Drelease-fast`. The executable is located at `zig-out/bin/`.
|
||||||
|
|
||||||
Once you've got all the dependencies, execute `zig build -Drelease-fast`. The executable is located at `zig-out/bin/`.
|
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
Key | Button
|
Key | Button
|
||||||
--- | ---
|
--- | ---
|
||||||
<kbd>X</kbd> | A
|
<kbd>X</kbd> | A
|
||||||
|
|
286
src/core/Bus.zig
286
src/core/Bus.zig
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const AudioDeviceId = @import("sdl2").SDL_AudioDeviceID;
|
||||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||||
const Bios = @import("bus/Bios.zig");
|
const Bios = @import("bus/Bios.zig");
|
||||||
const Ewram = @import("bus/Ewram.zig");
|
const Ewram = @import("bus/Ewram.zig");
|
||||||
|
@ -33,11 +34,6 @@ pub const fetch_timings: [2][0x10]u8 = [_][0x10]u8{
|
||||||
[_]u8{ 1, 1, 6, 1, 1, 2, 2, 1, 4, 4, 4, 4, 4, 4, 8, 8 }, // 32-bit
|
[_]u8{ 1, 1, 6, 1, 1, 2, 2, 1, 4, 4, 4, 4, 4, 4, 8, 8 }, // 32-bit
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fastmem Related
|
|
||||||
const page_size = 1 * 0x400; // 1KiB
|
|
||||||
const address_space_size = 0x1000_0000;
|
|
||||||
const table_len = address_space_size / page_size;
|
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pak: GamePak,
|
pak: GamePak,
|
||||||
|
@ -53,17 +49,7 @@ io: Io,
|
||||||
cpu: *Arm7tdmi,
|
cpu: *Arm7tdmi,
|
||||||
sched: *Scheduler,
|
sched: *Scheduler,
|
||||||
|
|
||||||
read_table: *const [table_len]?*const anyopaque,
|
|
||||||
write_tables: [2]*const [table_len]?*anyopaque,
|
|
||||||
allocator: Allocator,
|
|
||||||
|
|
||||||
pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi, paths: FilePaths) !void {
|
pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi, paths: FilePaths) !void {
|
||||||
const tables = try allocator.alloc(?*anyopaque, 3 * table_len); // Allocate all tables
|
|
||||||
|
|
||||||
const read_table: *[table_len]?*const anyopaque = tables[0..table_len];
|
|
||||||
const left_write: *[table_len]?*anyopaque = tables[table_len .. 2 * table_len];
|
|
||||||
const right_write: *[table_len]?*anyopaque = tables[2 * table_len .. 3 * table_len];
|
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.pak = try GamePak.init(allocator, cpu, paths.rom, paths.save),
|
.pak = try GamePak.init(allocator, cpu, paths.rom, paths.save),
|
||||||
.bios = try Bios.init(allocator, paths.bios),
|
.bios = try Bios.init(allocator, paths.bios),
|
||||||
|
@ -76,20 +62,7 @@ pub fn init(self: *Self, allocator: Allocator, sched: *Scheduler, cpu: *Arm7tdmi
|
||||||
.io = Io.init(),
|
.io = Io.init(),
|
||||||
.cpu = cpu,
|
.cpu = cpu,
|
||||||
.sched = sched,
|
.sched = sched,
|
||||||
|
|
||||||
.read_table = read_table,
|
|
||||||
.write_tables = .{ left_write, right_write },
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// read_table, write_tables, and *Self are not restricted to the lifetime
|
|
||||||
// of this init function so we can initialize our tables here
|
|
||||||
fillReadTable(self, read_table);
|
|
||||||
|
|
||||||
// Internal Display Memory behavious unusually on 8-bit reads
|
|
||||||
// so we have two different tables depending on whether there's an 8-bit read or not
|
|
||||||
fillWriteTable(u32, self, left_write);
|
|
||||||
fillWriteTable(u8, self, right_write);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
|
@ -98,131 +71,34 @@ pub fn deinit(self: *Self) void {
|
||||||
self.pak.deinit();
|
self.pak.deinit();
|
||||||
self.bios.deinit();
|
self.bios.deinit();
|
||||||
self.ppu.deinit();
|
self.ppu.deinit();
|
||||||
|
|
||||||
// This is so I can deallocate the original `allocator.alloc`. I have to re-make the type
|
|
||||||
// since I'm not keeping it around, This is very jank and bad though
|
|
||||||
// FIXME: please figure out another way
|
|
||||||
self.allocator.free(@ptrCast([*]const ?*anyopaque, self.write_tables[0][0..])[0 .. 3 * table_len]);
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fillReadTable(bus: *Self, table: *[table_len]?*const anyopaque) void {
|
pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T {
|
||||||
const vramMirror = @import("ppu/Vram.zig").mirror;
|
const page = @truncate(u8, address >> 24);
|
||||||
|
const aligned_addr = forceAlign(T, address);
|
||||||
for (table) |*ptr, i| {
|
|
||||||
const addr = page_size * i;
|
|
||||||
|
|
||||||
ptr.* = switch (addr) {
|
|
||||||
// General Internal Memory
|
|
||||||
0x0000_0000...0x0000_3FFF => null, // BIOS has it's own checks
|
|
||||||
0x0200_0000...0x02FF_FFFF => &bus.ewram.buf[addr & 0x3FFFF],
|
|
||||||
0x0300_0000...0x03FF_FFFF => &bus.iwram.buf[addr & 0x7FFF],
|
|
||||||
0x0400_0000...0x0400_03FF => null, // I/O
|
|
||||||
|
|
||||||
// Internal Display Memory
|
|
||||||
0x0500_0000...0x05FF_FFFF => &bus.ppu.palette.buf[addr & 0x3FF],
|
|
||||||
0x0600_0000...0x06FF_FFFF => &bus.ppu.vram.buf[vramMirror(addr)],
|
|
||||||
0x0700_0000...0x07FF_FFFF => &bus.ppu.oam.buf[addr & 0x3FF],
|
|
||||||
|
|
||||||
// External Memory (Game Pak)
|
|
||||||
0x0800_0000...0x0DFF_FFFF => fillTableExternalMemory(bus, addr),
|
|
||||||
0x0E00_0000...0x0FFF_FFFF => null, // SRAM
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fillWriteTable(comptime T: type, bus: *Self, table: *[table_len]?*const anyopaque) void {
|
|
||||||
comptime std.debug.assert(T == u32 or T == u16 or T == u8);
|
|
||||||
const vramMirror = @import("ppu/Vram.zig").mirror;
|
|
||||||
|
|
||||||
for (table) |*ptr, i| {
|
|
||||||
const addr = page_size * i;
|
|
||||||
|
|
||||||
ptr.* = switch (addr) {
|
|
||||||
// General Internal Memory
|
|
||||||
0x0000_0000...0x0000_3FFF => null, // BIOS has it's own checks
|
|
||||||
0x0200_0000...0x02FF_FFFF => &bus.ewram.buf[addr & 0x3FFFF],
|
|
||||||
0x0300_0000...0x03FF_FFFF => &bus.iwram.buf[addr & 0x7FFF],
|
|
||||||
0x0400_0000...0x0400_03FF => null, // I/O
|
|
||||||
|
|
||||||
// Internal Display Memory
|
|
||||||
0x0500_0000...0x05FF_FFFF => if (T != u8) &bus.ppu.palette.buf[addr & 0x3FF] else null,
|
|
||||||
0x0600_0000...0x06FF_FFFF => if (T != u8) &bus.ppu.vram.buf[vramMirror(addr)] else null,
|
|
||||||
0x0700_0000...0x07FF_FFFF => if (T != u8) &bus.ppu.oam.buf[addr & 0x3FF] else null,
|
|
||||||
|
|
||||||
// External Memory (Game Pak)
|
|
||||||
0x0800_0000...0x0DFF_FFFF => null, // ROM
|
|
||||||
0x0E00_0000...0x0FFF_FFFF => null, // SRAM
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fillTableExternalMemory(bus: *Self, addr: usize) ?*anyopaque {
|
|
||||||
// see `GamePak.zig` for more information about what conditions need to be true
|
|
||||||
// so that a simple pointer dereference isn't possible
|
|
||||||
|
|
||||||
const start_addr = addr;
|
|
||||||
const end_addr = addr + page_size;
|
|
||||||
|
|
||||||
const gpio_data = start_addr <= 0x0800_00C4 and 0x0800_00C4 < end_addr;
|
|
||||||
const gpio_direction = start_addr <= 0x0800_00C6 and 0x0800_00C6 < end_addr;
|
|
||||||
const gpio_control = start_addr <= 0x0800_00C8 and 0x0800_00C8 < end_addr;
|
|
||||||
|
|
||||||
if (bus.pak.gpio.device.kind != .None and (gpio_data or gpio_direction or gpio_control)) {
|
|
||||||
// We found a GPIO device, and this page a GPIO register. We want to handle this in slowmem
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bus.pak.backup.kind == .Eeprom) {
|
|
||||||
if (bus.pak.buf.len > 0x100_000) {
|
|
||||||
// We are using a "large" EEPROM which means that if the below check is true
|
|
||||||
// this page has an address that's reserved for the EEPROM and therefore must
|
|
||||||
// be handled in slowmem
|
|
||||||
if (addr & 0x1FF_FFFF > 0x1FF_FEFF) return null;
|
|
||||||
} else {
|
|
||||||
// We are using a "small" EEPROM which means that if the below check is true
|
|
||||||
// (that is, we're in the 0xD address page) then we must handle at least one
|
|
||||||
// address in this page in slowmem
|
|
||||||
if (@truncate(u4, addr >> 24) == 0xD) return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, the GamePak has some unique behaviour for reads past the end of the ROM,
|
|
||||||
// so those will be handled by slowmem as well
|
|
||||||
const masked_addr = addr & 0x1FF_FFFF;
|
|
||||||
if (masked_addr >= bus.pak.buf.len) return null;
|
|
||||||
|
|
||||||
return &bus.pak.buf[masked_addr];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Take advantage of fastmem here too?
|
|
||||||
pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
|
||||||
const page = @truncate(u8, unaligned_address >> 24);
|
|
||||||
const address = forceAlign(T, unaligned_address);
|
|
||||||
|
|
||||||
return switch (page) {
|
return switch (page) {
|
||||||
// General Internal Memory
|
// General Internal Memory
|
||||||
0x00 => blk: {
|
0x00 => blk: {
|
||||||
if (address < Bios.size)
|
if (address < Bios.size)
|
||||||
break :blk self.bios.dbgRead(T, self.cpu.r[15], address);
|
break :blk self.bios.dbgRead(T, self.cpu.r[15], aligned_addr);
|
||||||
|
|
||||||
break :blk self.openBus(T, address);
|
break :blk self.openBus(T, address);
|
||||||
},
|
},
|
||||||
0x02 => self.ewram.read(T, address),
|
0x02 => self.ewram.read(T, aligned_addr),
|
||||||
0x03 => self.iwram.read(T, address),
|
0x03 => self.iwram.read(T, aligned_addr),
|
||||||
0x04 => self.readIo(T, address),
|
0x04 => self.readIo(T, address),
|
||||||
|
|
||||||
// Internal Display Memory
|
// Internal Display Memory
|
||||||
0x05 => self.ppu.palette.read(T, address),
|
0x05 => self.ppu.palette.read(T, aligned_addr),
|
||||||
0x06 => self.ppu.vram.read(T, address),
|
0x06 => self.ppu.vram.read(T, aligned_addr),
|
||||||
0x07 => self.ppu.oam.read(T, address),
|
0x07 => self.ppu.oam.read(T, aligned_addr),
|
||||||
|
|
||||||
// External Memory (Game Pak)
|
// External Memory (Game Pak)
|
||||||
0x08...0x0D => self.pak.dbgRead(T, address),
|
0x08...0x0D => self.pak.dbgRead(T, aligned_addr),
|
||||||
0x0E...0x0F => blk: {
|
0x0E...0x0F => blk: {
|
||||||
const value = self.pak.backup.read(unaligned_address);
|
const value = self.pak.backup.read(address);
|
||||||
|
|
||||||
const multiplier = switch (T) {
|
const multiplier = switch (T) {
|
||||||
u32 => 0x01010101,
|
u32 => 0x01010101,
|
||||||
|
@ -237,22 +113,16 @@ pub fn dbgRead(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readIo(self: *const Self, comptime T: type, address: u32) T {
|
fn readIo(self: *const Self, comptime T: type, unaligned_address: u32) T {
|
||||||
return io.read(self, T, address) orelse self.openBus(T, address);
|
const maybe_value = io.read(self, T, forceAlign(T, unaligned_address));
|
||||||
|
return if (maybe_value) |value| value else self.openBus(T, unaligned_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn openBus(self: *const Self, comptime T: type, address: u32) T {
|
fn openBus(self: *const Self, comptime T: type, address: u32) T {
|
||||||
@setCold(true);
|
|
||||||
const r15 = self.cpu.r[15];
|
const r15 = self.cpu.r[15];
|
||||||
|
|
||||||
const word = blk: {
|
const word = blk: {
|
||||||
// If Arm, get the most recently fetched instruction (PC + 8)
|
// If Arm, get the most recently fetched instruction (PC + 8)
|
||||||
//
|
|
||||||
// FIXME: This is most likely a faulty assumption.
|
|
||||||
// I think what *actually* happens is that the Bus has a latch for the most
|
|
||||||
// recently fetched piece of data, which is then returned during Open Bus (also DMA open bus?)
|
|
||||||
// I can "get away" with this because it's very statistically likely that the most recently latched value is
|
|
||||||
// the most recently fetched instruction by the pipeline
|
|
||||||
if (!self.cpu.cpsr.t.read()) break :blk self.cpu.pipe.stage[1].?;
|
if (!self.cpu.cpsr.t.read()) break :blk self.cpu.pipe.stage[1].?;
|
||||||
|
|
||||||
const page = @truncate(u8, r15 >> 24);
|
const page = @truncate(u8, r15 >> 24);
|
||||||
|
@ -302,58 +172,33 @@ fn openBus(self: *const Self, comptime T: type, address: u32) T {
|
||||||
return @truncate(T, word);
|
return @truncate(T, word);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(self: *Self, comptime T: type, unaligned_address: u32) T {
|
pub fn read(self: *Self, comptime T: type, address: u32) T {
|
||||||
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
|
const page = @truncate(u8, address >> 24);
|
||||||
const page = unaligned_address >> bits;
|
const aligned_addr = forceAlign(T, address);
|
||||||
const offset = unaligned_address & (page_size - 1);
|
|
||||||
|
|
||||||
// whether or not we do this in slowmem or fastmem, we should advance the scheduler
|
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, page)];
|
||||||
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, unaligned_address >> 24)];
|
|
||||||
|
|
||||||
// We're doing some serious out-of-bounds open-bus reads
|
|
||||||
if (page > table_len) return self.openBus(T, unaligned_address);
|
|
||||||
|
|
||||||
if (self.read_table[page]) |some_ptr| {
|
|
||||||
// We have a pointer to a page, cast the pointer to it's underlying type
|
|
||||||
const Ptr = [*]const T;
|
|
||||||
const alignment = @alignOf(std.meta.Child(Ptr));
|
|
||||||
const ptr = @ptrCast(Ptr, @alignCast(alignment, some_ptr));
|
|
||||||
|
|
||||||
// Note: We don't check array length, since we force align the
|
|
||||||
// lower bits of the address as the GBA would
|
|
||||||
return ptr[forceAlign(T, offset) / @sizeOf(T)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.slowRead(T, unaligned_address);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slowRead(self: *Self, comptime T: type, unaligned_address: u32) T {
|
|
||||||
@setCold(true);
|
|
||||||
|
|
||||||
const page = @truncate(u8, unaligned_address >> 24);
|
|
||||||
const address = forceAlign(T, unaligned_address);
|
|
||||||
|
|
||||||
return switch (page) {
|
return switch (page) {
|
||||||
// General Internal Memory
|
// General Internal Memory
|
||||||
0x00 => blk: {
|
0x00 => blk: {
|
||||||
if (address < Bios.size)
|
if (address < Bios.size)
|
||||||
break :blk self.bios.read(T, self.cpu.r[15], address);
|
break :blk self.bios.read(T, self.cpu.r[15], aligned_addr);
|
||||||
|
|
||||||
break :blk self.openBus(T, address);
|
break :blk self.openBus(T, address);
|
||||||
},
|
},
|
||||||
0x02 => unreachable, // completely handled by fastmeme
|
0x02 => self.ewram.read(T, aligned_addr),
|
||||||
0x03 => unreachable, // completely handled by fastmeme
|
0x03 => self.iwram.read(T, aligned_addr),
|
||||||
0x04 => self.readIo(T, address),
|
0x04 => self.readIo(T, address),
|
||||||
|
|
||||||
// Internal Display Memory
|
// Internal Display Memory
|
||||||
0x05 => unreachable, // completely handled by fastmeme
|
0x05 => self.ppu.palette.read(T, aligned_addr),
|
||||||
0x06 => unreachable, // completely handled by fastmeme
|
0x06 => self.ppu.vram.read(T, aligned_addr),
|
||||||
0x07 => unreachable, // completely handled by fastmeme
|
0x07 => self.ppu.oam.read(T, aligned_addr),
|
||||||
|
|
||||||
// External Memory (Game Pak)
|
// External Memory (Game Pak)
|
||||||
0x08...0x0D => self.pak.read(T, address),
|
0x08...0x0D => self.pak.read(T, aligned_addr),
|
||||||
0x0E...0x0F => blk: {
|
0x0E...0x0F => blk: {
|
||||||
const value = self.pak.backup.read(unaligned_address);
|
const value = self.pak.backup.read(address);
|
||||||
|
|
||||||
const multiplier = switch (T) {
|
const multiplier = switch (T) {
|
||||||
u32 => 0x01010101,
|
u32 => 0x01010101,
|
||||||
|
@ -368,71 +213,44 @@ fn slowRead(self: *Self, comptime T: type, unaligned_address: u32) T {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
pub fn write(self: *Self, comptime T: type, address: u32, value: T) void {
|
||||||
const bits = @typeInfo(std.math.IntFittingRange(0, page_size - 1)).Int.bits;
|
const page = @truncate(u8, address >> 24);
|
||||||
const page = unaligned_address >> bits;
|
const aligned_addr = forceAlign(T, address);
|
||||||
const offset = unaligned_address & (page_size - 1);
|
|
||||||
|
|
||||||
// whether or not we do this in slowmem or fastmem, we should advance the scheduler
|
self.sched.tick += timings[@boolToInt(T == u32)][@truncate(u4, page)];
|
||||||
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
|
|
||||||
if (page > table_len) return;
|
|
||||||
|
|
||||||
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
|
|
||||||
const Ptr = [*]T;
|
|
||||||
const alignment = @alignOf(std.meta.Child(Ptr));
|
|
||||||
const ptr = @ptrCast(Ptr, @alignCast(alignment, some_ptr));
|
|
||||||
|
|
||||||
// Note: We don't check array length, since we force align the
|
|
||||||
// lower bits of the address as the GBA would
|
|
||||||
ptr[forceAlign(T, offset) / @sizeOf(T)] = value;
|
|
||||||
} else {
|
|
||||||
// we can return early if this is an 8-bit OAM write
|
|
||||||
if (T == u8 and @truncate(u8, unaligned_address >> 24) == 0x07) return;
|
|
||||||
|
|
||||||
self.slowWrite(T, unaligned_address, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slowWrite(self: *Self, comptime T: type, unaligned_address: u32, value: T) void {
|
|
||||||
// @setCold(true);
|
|
||||||
const page = @truncate(u8, unaligned_address >> 24);
|
|
||||||
const address = forceAlign(T, unaligned_address);
|
|
||||||
|
|
||||||
switch (page) {
|
switch (page) {
|
||||||
// General Internal Memory
|
// General Internal Memory
|
||||||
0x00 => self.bios.write(T, address, value),
|
0x00 => self.bios.write(T, aligned_addr, value),
|
||||||
0x02 => unreachable, // completely handled by fastmem
|
0x02 => self.ewram.write(T, aligned_addr, value),
|
||||||
0x03 => unreachable, // completely handled by fastmem
|
0x03 => self.iwram.write(T, aligned_addr, value),
|
||||||
0x04 => io.write(self, T, address, value),
|
0x04 => io.write(self, T, aligned_addr, value),
|
||||||
|
|
||||||
// Internal Display Memory
|
// Internal Display Memory
|
||||||
0x05 => self.ppu.palette.write(T, address, value),
|
0x05 => self.ppu.palette.write(T, aligned_addr, value),
|
||||||
0x06 => self.ppu.vram.write(T, self.ppu.dispcnt, address, value),
|
0x06 => self.ppu.vram.write(T, self.ppu.dispcnt, aligned_addr, value),
|
||||||
0x07 => unreachable, // completely handled by fastmem
|
0x07 => self.ppu.oam.write(T, aligned_addr, value),
|
||||||
|
|
||||||
// External Memory (Game Pak)
|
// External Memory (Game Pak)
|
||||||
0x08...0x0D => self.pak.write(T, self.dma[3].word_count, address, value),
|
0x08...0x0D => self.pak.write(T, self.dma[3].word_count, aligned_addr, value),
|
||||||
0x0E...0x0F => self.pak.backup.write(unaligned_address, @truncate(u8, rotr(T, value, 8 * rotateBy(T, unaligned_address)))),
|
0x0E...0x0F => {
|
||||||
|
const rotate_by = switch (T) {
|
||||||
|
u32 => address & 3,
|
||||||
|
u16 => address & 1,
|
||||||
|
u8 => 0,
|
||||||
|
else => @compileError("Backup: Unsupported write width"),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pak.backup.write(address, @truncate(u8, rotr(T, value, 8 * rotate_by)));
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn rotateBy(comptime T: type, address: u32) u32 {
|
fn forceAlign(comptime T: type, address: u32) u32 {
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => address & 3,
|
u32 => address & 0xFFFF_FFFC,
|
||||||
u16 => address & 1,
|
u16 => address & 0xFFFF_FFFE,
|
||||||
u8 => 0,
|
|
||||||
else => @compileError("Backup: Unsupported write width"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fn forceAlign(comptime T: type, address: u32) u32 {
|
|
||||||
return switch (T) {
|
|
||||||
u32 => address & ~@as(u32, 3),
|
|
||||||
u16 => address & ~@as(u32, 1),
|
|
||||||
u8 => address,
|
u8 => address,
|
||||||
else => @compileError("Bus: Invalid read/write type"),
|
else => @compileError("Bus: Invalid read/write type"),
|
||||||
};
|
};
|
||||||
|
|
210
src/core/apu.zig
210
src/core/apu.zig
|
@ -3,6 +3,8 @@ const SDL = @import("sdl2");
|
||||||
const io = @import("bus/io.zig");
|
const io = @import("bus/io.zig");
|
||||||
const util = @import("../util.zig");
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
|
const AudioDeviceId = SDL.SDL_AudioDeviceID;
|
||||||
|
|
||||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
const ToneSweep = @import("apu/ToneSweep.zig");
|
const ToneSweep = @import("apu/ToneSweep.zig");
|
||||||
|
@ -12,201 +14,132 @@ const Noise = @import("apu/Noise.zig");
|
||||||
|
|
||||||
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
||||||
|
|
||||||
const getHalf = util.getHalf;
|
const intToBytes = @import("../util.zig").intToBytes;
|
||||||
const setHalf = util.setHalf;
|
const setHi = @import("../util.zig").setHi;
|
||||||
const intToBytes = util.intToBytes;
|
const setLo = @import("../util.zig").setLo;
|
||||||
|
|
||||||
const log = std.log.scoped(.APU);
|
const log = std.log.scoped(.APU);
|
||||||
|
|
||||||
pub const host_rate = @import("../platform.zig").sample_rate;
|
pub const host_sample_rate = 1 << 15;
|
||||||
pub const host_format = @import("../platform.zig").sample_format;
|
|
||||||
|
|
||||||
pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
||||||
const byte_addr = @truncate(u8, addr);
|
const byte = @truncate(u8, addr);
|
||||||
|
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (byte_addr) {
|
u16 => switch (byte) {
|
||||||
0x60 => @as(T, apu.ch1.sound1CntH()) << 16 | apu.ch1.sound1CntL(),
|
|
||||||
0x64 => apu.ch1.sound1CntX(),
|
|
||||||
0x68 => apu.ch2.sound2CntL(),
|
|
||||||
0x6C => apu.ch2.sound2CntH(),
|
|
||||||
0x70 => @as(T, apu.ch3.sound3CntH()) << 16 | apu.ch3.sound3CntL(),
|
|
||||||
0x74 => apu.ch3.sound3CntX(),
|
|
||||||
0x78 => apu.ch4.sound4CntL(),
|
|
||||||
0x7C => apu.ch4.sound4CntH(),
|
|
||||||
0x80 => @as(T, apu.dma_cnt.raw) << 16 | apu.psg_cnt.raw, // SOUNDCNT_H, SOUNDCNT_L
|
|
||||||
0x84 => apu.soundCntX(),
|
|
||||||
0x88 => apu.bias.raw, // SOUNDBIAS, high is unused
|
|
||||||
0x8C => null,
|
|
||||||
0x90, 0x94, 0x98, 0x9C => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
|
||||||
0xA0 => null, // FIFO_A
|
|
||||||
0xA4 => null, // FIFO_B
|
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
|
||||||
u16 => switch (byte_addr) {
|
|
||||||
0x60 => apu.ch1.sound1CntL(),
|
0x60 => apu.ch1.sound1CntL(),
|
||||||
0x62 => apu.ch1.sound1CntH(),
|
0x62 => apu.ch1.sound1CntH(),
|
||||||
0x64 => apu.ch1.sound1CntX(),
|
0x64 => apu.ch1.sound1CntX(),
|
||||||
0x66 => 0x0000, // suite.gba expects 0x0000, not 0xDEAD
|
|
||||||
0x68 => apu.ch2.sound2CntL(),
|
0x68 => apu.ch2.sound2CntL(),
|
||||||
0x6A => 0x0000,
|
|
||||||
0x6C => apu.ch2.sound2CntH(),
|
0x6C => apu.ch2.sound2CntH(),
|
||||||
0x6E => 0x0000,
|
|
||||||
0x70 => apu.ch3.sound3CntL(),
|
0x70 => apu.ch3.select.raw & 0xE0, // SOUND3CNT_L
|
||||||
0x72 => apu.ch3.sound3CntH(),
|
0x72 => apu.ch3.sound3CntH(),
|
||||||
0x74 => apu.ch3.sound3CntX(),
|
0x74 => apu.ch3.freq.raw & 0x4000, // SOUND3CNT_X
|
||||||
0x76 => 0x0000,
|
|
||||||
0x78 => apu.ch4.sound4CntL(),
|
0x78 => apu.ch4.sound4CntL(),
|
||||||
0x7A => 0x0000,
|
|
||||||
0x7C => apu.ch4.sound4CntH(),
|
0x7C => apu.ch4.sound4CntH(),
|
||||||
0x7E => 0x0000,
|
|
||||||
0x80 => apu.soundCntL(),
|
0x80 => apu.psg_cnt.raw & 0xFF77, // SOUNDCNT_L
|
||||||
0x82 => apu.soundCntH(),
|
0x82 => apu.dma_cnt.raw & 0x770F, // SOUNDCNT_H
|
||||||
0x84 => apu.soundCntX(),
|
0x84 => apu.soundCntX(),
|
||||||
0x86 => 0x0000,
|
|
||||||
0x88 => apu.bias.raw, // SOUNDBIAS
|
0x88 => apu.bias.raw, // SOUNDBIAS
|
||||||
0x8A => 0x0000,
|
|
||||||
0x8C, 0x8E => null,
|
|
||||||
0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
|
||||||
0xA0, 0xA2 => null, // FIFO_A
|
|
||||||
0xA4, 0xA6 => null, // FIFO_B
|
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
|
||||||
u8 => switch (byte_addr) {
|
|
||||||
0x60, 0x61 => @truncate(T, @as(u16, apu.ch1.sound1CntL()) >> getHalf(byte_addr)),
|
|
||||||
0x62, 0x63 => @truncate(T, apu.ch1.sound1CntH() >> getHalf(byte_addr)),
|
|
||||||
0x64, 0x65 => @truncate(T, apu.ch1.sound1CntX() >> getHalf(byte_addr)),
|
|
||||||
0x66, 0x67 => 0x00, // assuming behaviour is identical to that of 16-bit reads
|
|
||||||
0x68, 0x69 => @truncate(T, apu.ch2.sound2CntL() >> getHalf(byte_addr)),
|
|
||||||
0x6A, 0x6B => 0x00,
|
|
||||||
0x6C, 0x6D => @truncate(T, apu.ch2.sound2CntH() >> getHalf(byte_addr)),
|
|
||||||
0x6E, 0x6F => 0x00,
|
|
||||||
0x70, 0x71 => @truncate(T, @as(u16, apu.ch3.sound3CntL()) >> getHalf(byte_addr)), // SOUND3CNT_L
|
|
||||||
0x72, 0x73 => @truncate(T, apu.ch3.sound3CntH() >> getHalf(byte_addr)),
|
|
||||||
0x74, 0x75 => @truncate(T, apu.ch3.sound3CntX() >> getHalf(byte_addr)), // SOUND3CNT_L
|
|
||||||
0x76, 0x77 => 0x00,
|
|
||||||
0x78, 0x79 => @truncate(T, apu.ch4.sound4CntL() >> getHalf(byte_addr)),
|
|
||||||
0x7A, 0x7B => 0x00,
|
|
||||||
0x7C, 0x7D => @truncate(T, apu.ch4.sound4CntH() >> getHalf(byte_addr)),
|
|
||||||
0x7E, 0x7F => 0x00,
|
|
||||||
0x80, 0x81 => @truncate(T, apu.soundCntL() >> getHalf(byte_addr)), // SOUNDCNT_L
|
|
||||||
0x82, 0x83 => @truncate(T, apu.soundCntH() >> getHalf(byte_addr)), // SOUNDCNT_H
|
|
||||||
0x84, 0x85 => @truncate(T, @as(u16, apu.soundCntX()) >> getHalf(byte_addr)),
|
|
||||||
0x86, 0x87 => 0x00,
|
|
||||||
0x88, 0x89 => @truncate(T, apu.bias.raw >> getHalf(byte_addr)), // SOUNDBIAS
|
|
||||||
0x8A, 0x8B => 0x00,
|
|
||||||
0x8C...0x8F => null,
|
|
||||||
0x90...0x9F => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
0x90...0x9F => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
||||||
0xA0, 0xA1, 0xA2, 0xA3 => null, // FIFO_A
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
0xA4, 0xA5, 0xA6, 0xA7 => null, // FIFO_B
|
|
||||||
else => util.io.read.err(T, log, "unexpected {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
},
|
||||||
|
u8 => switch (byte) {
|
||||||
|
0x60 => apu.ch1.sound1CntL(), // NR10
|
||||||
|
0x62 => apu.ch1.duty.raw, // NR11
|
||||||
|
0x63 => apu.ch1.envelope.raw, // NR12
|
||||||
|
0x68 => apu.ch2.duty.raw, // NR21
|
||||||
|
0x69 => apu.ch2.envelope.raw, // NR22
|
||||||
|
0x73 => apu.ch3.vol.raw, // NR32
|
||||||
|
0x79 => apu.ch4.envelope.raw, // NR42
|
||||||
|
0x7C => apu.ch4.poly.raw, // NR43
|
||||||
|
0x81 => @truncate(u8, apu.psg_cnt.raw >> 8), // NR51
|
||||||
|
0x84 => apu.soundCntX(),
|
||||||
|
0x89 => @truncate(u8, apu.bias.raw >> 8), // SOUNDBIAS_H
|
||||||
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
|
},
|
||||||
|
u32 => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
else => @compileError("APU: Unsupported read width"),
|
else => @compileError("APU: Unsupported read width"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = @truncate(u8, addr);
|
||||||
|
|
||||||
switch (T) {
|
switch (T) {
|
||||||
u32 => switch (byte_addr) {
|
u32 => switch (byte) {
|
||||||
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)),
|
||||||
|
|
||||||
0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
|
0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
|
||||||
0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
|
0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x70 => apu.ch3.setSound3Cnt(value),
|
0x70 => apu.ch3.setSound3Cnt(value),
|
||||||
0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
|
0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
|
0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
|
||||||
0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
|
0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x80 => apu.setSoundCnt(value),
|
0x80 => apu.setSoundCnt(value),
|
||||||
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
// WAVE_RAM
|
||||||
0x88 => apu.bias.raw = @truncate(u16, value),
|
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
||||||
0x8C => {},
|
|
||||||
|
|
||||||
0x90, 0x94, 0x98, 0x9C => 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
|
||||||
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 => switch (byte) {
|
||||||
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),
|
||||||
0x66 => {},
|
|
||||||
|
|
||||||
0x68 => apu.ch2.setSound2CntL(value),
|
0x68 => apu.ch2.setSound2CntL(value),
|
||||||
0x6A => {},
|
|
||||||
0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
|
0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
|
||||||
0x6E => {},
|
|
||||||
|
|
||||||
0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
|
0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
|
||||||
0x72 => apu.ch3.setSound3CntH(value),
|
0x72 => apu.ch3.setSound3CntH(value),
|
||||||
0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
|
0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
|
||||||
0x76 => {},
|
|
||||||
|
|
||||||
0x78 => apu.ch4.setSound4CntL(value),
|
0x78 => apu.ch4.setSound4CntL(value),
|
||||||
0x7A => {},
|
|
||||||
0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
|
0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
|
||||||
0x7E => {},
|
|
||||||
|
|
||||||
0x80 => apu.setSoundCntL(value),
|
0x80 => apu.psg_cnt.raw = value, // SOUNDCNT_L
|
||||||
0x82 => apu.setSoundCntH(value),
|
0x82 => apu.setSoundCntH(value),
|
||||||
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
||||||
0x86 => {},
|
|
||||||
0x88 => apu.bias.raw = value, // SOUNDBIAS
|
0x88 => apu.bias.raw = value, // SOUNDBIAS
|
||||||
0x8A, 0x8C, 0x8E => {},
|
// WAVE_RAM
|
||||||
|
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
||||||
0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
|
||||||
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 }),
|
|
||||||
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 => switch (byte) {
|
||||||
0x60 => apu.ch1.setSound1CntL(value),
|
0x60 => apu.ch1.setSound1CntL(value),
|
||||||
0x61 => {},
|
|
||||||
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),
|
||||||
0x65 => apu.ch1.setNr14(&apu.fs, value),
|
0x65 => apu.ch1.setNr14(&apu.fs, value),
|
||||||
0x66, 0x67 => {},
|
|
||||||
|
|
||||||
0x68 => apu.ch2.setNr21(value),
|
0x68 => apu.ch2.setNr21(value),
|
||||||
0x69 => apu.ch2.setNr22(value),
|
0x69 => apu.ch2.setNr22(value),
|
||||||
0x6A, 0x6B => {},
|
|
||||||
0x6C => apu.ch2.setNr23(value),
|
0x6C => apu.ch2.setNr23(value),
|
||||||
0x6D => apu.ch2.setNr24(&apu.fs, value),
|
0x6D => apu.ch2.setNr24(&apu.fs, value),
|
||||||
0x6E, 0x6F => {},
|
|
||||||
|
|
||||||
0x70 => apu.ch3.setSound3CntL(value), // NR30
|
0x70 => apu.ch3.setSound3CntL(value), // NR30
|
||||||
0x71 => {},
|
|
||||||
0x72 => apu.ch3.setNr31(value),
|
0x72 => apu.ch3.setNr31(value),
|
||||||
0x73 => apu.ch3.vol.raw = value, // NR32
|
0x73 => apu.ch3.vol.raw = value, // NR32
|
||||||
0x74 => apu.ch3.setNr33(value),
|
0x74 => apu.ch3.setNr33(value),
|
||||||
0x75 => apu.ch3.setNr34(&apu.fs, value),
|
0x75 => apu.ch3.setNr34(&apu.fs, value),
|
||||||
0x76, 0x77 => {},
|
|
||||||
|
|
||||||
0x78 => apu.ch4.setNr41(value),
|
0x78 => apu.ch4.setNr41(value),
|
||||||
0x79 => apu.ch4.setNr42(value),
|
0x79 => apu.ch4.setNr42(value),
|
||||||
0x7A, 0x7B => {},
|
|
||||||
0x7C => apu.ch4.poly.raw = value, // NR 43
|
0x7C => apu.ch4.poly.raw = value, // NR 43
|
||||||
0x7D => apu.ch4.setNr44(&apu.fs, value),
|
0x7D => apu.ch4.setNr44(&apu.fs, value),
|
||||||
0x7E, 0x7F => {},
|
|
||||||
|
|
||||||
0x80, 0x81 => apu.setSoundCntL(setHalf(u16, apu.psg_cnt.raw, byte_addr, value)),
|
|
||||||
0x82, 0x83 => apu.setSoundCntH(setHalf(u16, apu.dma_cnt.raw, byte_addr, value)),
|
|
||||||
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
|
||||||
0x85 => {},
|
|
||||||
0x86, 0x87 => {},
|
|
||||||
0x88, 0x89 => apu.bias.raw = setHalf(u16, apu.bias.raw, byte_addr, value), // SOUNDBIAS
|
|
||||||
0x8A...0x8F => {},
|
|
||||||
|
|
||||||
|
0x80 => apu.setNr50(value),
|
||||||
|
0x81 => apu.setNr51(value),
|
||||||
|
0x82 => apu.setSoundCntH(setLo(u16, apu.dma_cnt.raw, value)),
|
||||||
|
0x83 => apu.setSoundCntH(setHi(u16, apu.dma_cnt.raw, value)),
|
||||||
|
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), // NR52
|
||||||
|
0x89 => apu.setSoundBiasH(value),
|
||||||
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...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 }),
|
|
||||||
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"),
|
||||||
|
@ -256,7 +189,7 @@ pub const Apu = struct {
|
||||||
.bias = .{ .raw = 0x0200 },
|
.bias = .{ .raw = 0x0200 },
|
||||||
|
|
||||||
.sampling_cycle = 0b00,
|
.sampling_cycle = 0b00,
|
||||||
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, host_format, 2, host_rate).?,
|
.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, 1 << 15, SDL.AUDIO_U16, 2, host_sample_rate).?,
|
||||||
.sched = sched,
|
.sched = sched,
|
||||||
|
|
||||||
.capacitor = 0,
|
.capacitor = 0,
|
||||||
|
@ -283,20 +216,10 @@ pub const Apu = struct {
|
||||||
|
|
||||||
/// SOUNDCNT
|
/// SOUNDCNT
|
||||||
fn setSoundCnt(self: *Self, value: u32) void {
|
fn setSoundCnt(self: *Self, value: u32) void {
|
||||||
self.setSoundCntL(@truncate(u16, value));
|
self.psg_cnt.raw = @truncate(u16, value);
|
||||||
self.setSoundCntH(@truncate(u16, value >> 16));
|
self.setSoundCntH(@truncate(u16, value >> 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SOUNDCNT_L
|
|
||||||
pub fn soundCntL(self: *const Self) u16 {
|
|
||||||
return self.psg_cnt.raw & 0xFF77;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SOUNDCNT_L
|
|
||||||
pub fn setSoundCntL(self: *Self, value: u16) void {
|
|
||||||
self.psg_cnt.raw = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SOUNDCNT_H
|
/// 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 };
|
||||||
|
@ -309,11 +232,6 @@ pub const Apu = struct {
|
||||||
self.dma_cnt = new;
|
self.dma_cnt = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SOUNDCNT_H
|
|
||||||
pub fn soundCntH(self: *const Self) u16 {
|
|
||||||
return self.dma_cnt.raw & 0x770F;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR52
|
/// NR52
|
||||||
pub fn setSoundCntX(self: *Self, value: bool) void {
|
pub fn setSoundCntX(self: *Self, value: bool) void {
|
||||||
self.cnt.apu_enable.write(value);
|
self.cnt.apu_enable.write(value);
|
||||||
|
@ -344,6 +262,20 @@ pub const Apu = struct {
|
||||||
return apu_enable << 7 | ch4_enable << 3 | ch3_enable << 2 | ch2_enable << 1 | ch1_enable;
|
return apu_enable << 7 | ch4_enable << 3 | ch3_enable << 2 | ch2_enable << 1 | ch1_enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NR50
|
||||||
|
pub fn setNr50(self: *Self, byte: u8) void {
|
||||||
|
self.psg_cnt.raw = (self.psg_cnt.raw & 0xFF00) | byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NR51
|
||||||
|
pub fn setNr51(self: *Self, byte: u8) void {
|
||||||
|
self.psg_cnt.raw = @as(u16, byte) << 8 | (self.psg_cnt.raw & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setSoundBiasH(self: *Self, byte: u8) void {
|
||||||
|
self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sampleAudio(self: *Self, late: u64) void {
|
pub fn sampleAudio(self: *Self, late: u64) void {
|
||||||
self.sched.push(.SampleAudio, self.interval() -| late);
|
self.sched.push(.SampleAudio, self.interval() -| late);
|
||||||
|
|
||||||
|
@ -407,6 +339,7 @@ pub const Apu = struct {
|
||||||
const ext_left = (clamped_left << 5) | (clamped_left >> 6);
|
const ext_left = (clamped_left << 5) | (clamped_left >> 6);
|
||||||
const ext_right = (clamped_right << 5) | (clamped_right >> 6);
|
const ext_right = (clamped_right << 5) | (clamped_right >> 6);
|
||||||
|
|
||||||
|
// FIXME: This rarely happens
|
||||||
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
|
if (self.sampling_cycle != self.bias.sampling_cycle.read()) self.replaceSDLResampler();
|
||||||
|
|
||||||
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
|
_ = SDL.SDL_AudioStreamPut(self.stream, &[2]u16{ ext_left, ext_right }, 2 * @sizeOf(u16));
|
||||||
|
@ -423,7 +356,7 @@ pub const Apu = struct {
|
||||||
defer SDL.SDL_FreeAudioStream(old_stream);
|
defer SDL.SDL_FreeAudioStream(old_stream);
|
||||||
|
|
||||||
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, sample_rate), host_format, 2, host_rate).?;
|
self.stream = SDL.SDL_NewAudioStream(SDL.AUDIO_U16, 2, @intCast(c_int, sample_rate), SDL.AUDIO_U16, 2, host_sample_rate).?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interval(self: *const Self) u64 {
|
fn interval(self: *const Self) u64 {
|
||||||
|
@ -472,15 +405,11 @@ pub const Apu = struct {
|
||||||
if (!self.cnt.apu_enable.read()) return;
|
if (!self.cnt.apu_enable.read()) return;
|
||||||
|
|
||||||
if (@boolToInt(self.dma_cnt.chA_timer.read()) == tim_id) {
|
if (@boolToInt(self.dma_cnt.chA_timer.read()) == tim_id) {
|
||||||
if (!self.chA.enabled) return;
|
|
||||||
|
|
||||||
self.chA.updateSample();
|
self.chA.updateSample();
|
||||||
if (self.chA.len() <= 15) cpu.bus.dma[1].requestAudio(0x0400_00A0);
|
if (self.chA.len() <= 15) cpu.bus.dma[1].requestAudio(0x0400_00A0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (@boolToInt(self.dma_cnt.chB_timer.read()) == tim_id) {
|
if (@boolToInt(self.dma_cnt.chB_timer.read()) == tim_id) {
|
||||||
if (!self.chB.enabled) return;
|
|
||||||
|
|
||||||
self.chB.updateSample();
|
self.chB.updateSample();
|
||||||
if (self.chB.len() <= 15) cpu.bus.dma[2].requestAudio(0x0400_00A4);
|
if (self.chB.len() <= 15) cpu.bus.dma[2].requestAudio(0x0400_00A4);
|
||||||
}
|
}
|
||||||
|
@ -494,28 +423,19 @@ pub fn DmaSound(comptime kind: DmaSoundKind) type {
|
||||||
fifo: SoundFifo,
|
fifo: SoundFifo,
|
||||||
kind: DmaSoundKind,
|
kind: DmaSoundKind,
|
||||||
sample: i8,
|
sample: i8,
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
fn init() Self {
|
fn init() Self {
|
||||||
return .{
|
return .{
|
||||||
.fifo = SoundFifo.init(),
|
.fifo = SoundFifo.init(),
|
||||||
.kind = kind,
|
.kind = kind,
|
||||||
.sample = 0,
|
.sample = 0,
|
||||||
.enabled = false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(self: *Self, value: u32) void {
|
pub fn push(self: *Self, value: u32) void {
|
||||||
if (!self.enabled) self.enable();
|
|
||||||
|
|
||||||
self.fifo.write(&intToBytes(u32, value)) catch |e| log.err("{} Error: {}", .{ kind, e });
|
self.fifo.write(&intToBytes(u32, value)) catch |e| log.err("{} Error: {}", .{ kind, e });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable(self: *Self) void {
|
|
||||||
@setCold(true);
|
|
||||||
self.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(self: *const Self) usize {
|
pub fn len(self: *const Self) usize {
|
||||||
return self.fifo.readableLength();
|
return self.fifo.readableLength();
|
||||||
}
|
}
|
||||||
|
@ -536,8 +456,8 @@ const DmaSoundKind = enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FrameSequencer = struct {
|
pub const FrameSequencer = struct {
|
||||||
|
const interval = (1 << 24) / 512;
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
pub const interval = (1 << 24) / 512;
|
|
||||||
|
|
||||||
step: u3,
|
step: u3,
|
||||||
|
|
||||||
|
|
|
@ -71,11 +71,6 @@ pub fn setSound3CntL(self: *Self, value: u8) void {
|
||||||
if (!self.select.enabled.read()) self.enabled = false;
|
if (!self.select.enabled.read()) self.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR30
|
|
||||||
pub fn sound3CntL(self: *const Self) u8 {
|
|
||||||
return self.select.raw & 0xE0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR31, NR32
|
/// NR31, NR32
|
||||||
pub fn sound3CntH(self: *const Self) u16 {
|
pub fn sound3CntH(self: *const Self) u16 {
|
||||||
return @as(u16, self.length & 0xE0) << 8;
|
return @as(u16, self.length & 0xE0) << 8;
|
||||||
|
@ -99,11 +94,6 @@ pub fn setSound3CntX(self: *Self, fs: *const FrameSequencer, value: u16) void {
|
||||||
self.setNr34(fs, @truncate(u8, value >> 8));
|
self.setNr34(fs, @truncate(u8, value >> 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR33, NR34
|
|
||||||
pub fn sound3CntX(self: *const Self) u16 {
|
|
||||||
return self.freq.raw & 0x4000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR33
|
/// NR33
|
||||||
pub fn setNr33(self: *Self, byte: u8) void {
|
pub fn setNr33(self: *Self, byte: u8) void {
|
||||||
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
self.freq.raw = (self.freq.raw & 0xFF00) | byte;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
//! Linear Feedback Shift Register
|
|
||||||
const io = @import("../../bus/io.zig");
|
const io = @import("../../bus/io.zig");
|
||||||
|
|
||||||
|
/// Linear Feedback Shift Register
|
||||||
const Scheduler = @import("../../scheduler.zig").Scheduler;
|
const Scheduler = @import("../../scheduler.zig").Scheduler;
|
||||||
|
const FrameSequencer = @import("../../apu.zig").FrameSequencer;
|
||||||
|
const Noise = @import("../Noise.zig");
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
pub const interval: u64 = (1 << 24) / (1 << 22);
|
pub const interval: u64 = (1 << 24) / (1 << 22);
|
||||||
|
@ -33,7 +35,7 @@ pub fn reload(self: *Self, poly: io.PolyCounter) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scheduler Event Handler for LFSR Timer Expire
|
/// Scheduler Event Handler for LFSR Timer Expire
|
||||||
/// FIXME: This gets called a lot, slowing down the scheduler
|
/// FIXME: This gets called a lot, clogging up the Scheduler
|
||||||
pub fn onLfsrTimerExpire(self: *Self, poly: io.PolyCounter, late: u64) void {
|
pub fn onLfsrTimerExpire(self: *Self, poly: io.PolyCounter, late: u64) void {
|
||||||
// Obscure: "Using a noise channel clock shift of 14 or 15
|
// Obscure: "Using a noise channel clock shift of 14 or 15
|
||||||
// results in the LFSR receiving no clocks."
|
// results in the LFSR receiving no clocks."
|
||||||
|
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const io = @import("../../bus/io.zig");
|
const io = @import("../../bus/io.zig");
|
||||||
|
|
||||||
const Scheduler = @import("../../scheduler.zig").Scheduler;
|
const Scheduler = @import("../../scheduler.zig").Scheduler;
|
||||||
|
const FrameSequencer = @import("../../apu.zig").FrameSequencer;
|
||||||
const ToneSweep = @import("../ToneSweep.zig");
|
const ToneSweep = @import("../ToneSweep.zig");
|
||||||
const Tone = @import("../Tone.zig");
|
const Tone = @import("../Tone.zig");
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ const std = @import("std");
|
||||||
const io = @import("../../bus/io.zig");
|
const io = @import("../../bus/io.zig");
|
||||||
|
|
||||||
const Scheduler = @import("../../scheduler.zig").Scheduler;
|
const Scheduler = @import("../../scheduler.zig").Scheduler;
|
||||||
|
const FrameSequencer = @import("../../apu.zig").FrameSequencer;
|
||||||
|
const Wave = @import("../Wave.zig");
|
||||||
|
|
||||||
const buf_len = 0x20;
|
const buf_len = 0x20;
|
||||||
pub const interval: u64 = (1 << 24) / (1 << 22);
|
pub const interval: u64 = (1 << 24) / (1 << 22);
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const config = @import("../../config.zig");
|
const config = @import("../../config.zig");
|
||||||
|
|
||||||
|
const Bit = @import("bitfield").Bit;
|
||||||
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
|
const DateTime = @import("datetime").datetime.Datetime;
|
||||||
|
|
||||||
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
||||||
const Backup = @import("backup.zig").Backup;
|
const Backup = @import("backup.zig").Backup;
|
||||||
const Gpio = @import("gpio.zig").Gpio;
|
const Gpio = @import("gpio.zig").Gpio;
|
||||||
|
@ -105,13 +109,14 @@ pub fn dbgRead(self: *const Self, comptime T: type, address: u32) T {
|
||||||
|
|
||||||
switch (T) {
|
switch (T) {
|
||||||
u32 => switch (address) {
|
u32 => switch (address) {
|
||||||
// FIXME: Do I even need to implement these?
|
// TODO: Do I even need to implement these?
|
||||||
0x0800_00C4 => std.debug.panic("Handle 32-bit GPIO Data/Direction Reads", .{}),
|
0x0800_00C4 => std.debug.panic("Handle 32-bit GPIO Data/Direction Reads", .{}),
|
||||||
0x0800_00C6 => std.debug.panic("Handle 32-bit GPIO Direction/Control Reads", .{}),
|
0x0800_00C6 => std.debug.panic("Handle 32-bit GPIO Direction/Control Reads", .{}),
|
||||||
0x0800_00C8 => std.debug.panic("Handle 32-bit GPIO Control Reads", .{}),
|
0x0800_00C8 => std.debug.panic("Handle 32-bit GPIO Control Reads", .{}),
|
||||||
else => {},
|
else => {},
|
||||||
},
|
},
|
||||||
u16 => switch (address) {
|
u16 => switch (address) {
|
||||||
|
// FIXME: What do 16-bit GPIO Reads look like?
|
||||||
0x0800_00C4 => return self.gpio.read(.Data),
|
0x0800_00C4 => return self.gpio.read(.Data),
|
||||||
0x0800_00C6 => return self.gpio.read(.Direction),
|
0x0800_00C6 => return self.gpio.read(.Direction),
|
||||||
0x0800_00C8 => return self.gpio.read(.Control),
|
0x0800_00C8 => return self.gpio.read(.Control),
|
||||||
|
|
|
@ -151,8 +151,8 @@ pub const Backup = struct {
|
||||||
const file_path = try self.savePath(allocator, path);
|
const file_path = try self.savePath(allocator, path);
|
||||||
defer allocator.free(file_path);
|
defer allocator.free(file_path);
|
||||||
|
|
||||||
const expected = "untitled.sav";
|
// FIXME: Don't rely on this lol
|
||||||
if (std.mem.eql(u8, file_path[file_path.len - expected.len .. file_path.len], expected)) {
|
if (std.mem.eql(u8, file_path[file_path.len - 12 .. file_path.len], "untitled.sav")) {
|
||||||
return log.err("ROM header lacks title, no save loaded", .{});
|
return log.err("ROM header lacks title, no save loaded", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ pub const Eeprom = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.state == .RequestEnd) {
|
if (self.state == .RequestEnd) {
|
||||||
// if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{});
|
if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{});
|
||||||
self.state = .Ready;
|
self.state = .Ready;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,8 @@ const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
||||||
pub const DmaTuple = std.meta.Tuple(&[_]type{ DmaController(0), DmaController(1), DmaController(2), DmaController(3) });
|
pub const DmaTuple = std.meta.Tuple(&[_]type{ 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 setHi = util.setHi;
|
||||||
const setHalf = util.setHalf;
|
const setLo = util.setLo;
|
||||||
const setQuart = util.setQuart;
|
|
||||||
|
|
||||||
const rotr = @import("../../util.zig").rotr;
|
const rotr = @import("../../util.zig").rotr;
|
||||||
|
|
||||||
|
@ -19,126 +18,78 @@ pub fn create() DmaTuple {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(comptime T: type, dma: *const DmaTuple, addr: u32) ?T {
|
pub fn read(comptime T: type, dma: *const DmaTuple, addr: u32) ?T {
|
||||||
const byte_addr = @truncate(u8, addr);
|
const byte = @truncate(u8, addr);
|
||||||
|
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (byte_addr) {
|
u32 => switch (byte) {
|
||||||
0xB0, 0xB4 => null, // DMA0SAD, DMA0DAD,
|
0xB8 => @as(T, dma.*[0].cnt.raw) << 16,
|
||||||
0xB8 => @as(T, dma.*[0].dmacntH()) << 16, // DMA0CNT_L is write-only
|
0xC4 => @as(T, dma.*[1].cnt.raw) << 16,
|
||||||
0xBC, 0xC0 => null, // DMA1SAD, DMA1DAD
|
0xD0 => @as(T, dma.*[2].cnt.raw) << 16,
|
||||||
0xC4 => @as(T, dma.*[1].dmacntH()) << 16, // DMA1CNT_L is write-only
|
0xDC => @as(T, dma.*[3].cnt.raw) << 16,
|
||||||
0xC8, 0xCC => null, // DMA2SAD, DMA2DAD
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
0xD0 => @as(T, dma.*[2].dmacntH()) << 16, // DMA2CNT_L is write-only
|
|
||||||
0xD4, 0xD8 => null, // DMA3SAD, DMA3DAD
|
|
||||||
0xDC => @as(T, dma.*[3].dmacntH()) << 16, // DMA3CNT_L is write-only
|
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
},
|
||||||
u16 => switch (byte_addr) {
|
u16 => switch (byte) {
|
||||||
0xB0, 0xB2, 0xB4, 0xB6 => null, // DMA0SAD, DMA0DAD
|
0xBA => dma.*[0].cnt.raw,
|
||||||
0xB8 => 0x0000, // DMA0CNT_L, suite.gba expects 0x0000 instead of 0xDEAD
|
0xC6 => dma.*[1].cnt.raw,
|
||||||
0xBA => dma.*[0].dmacntH(),
|
0xD2 => dma.*[2].cnt.raw,
|
||||||
|
0xDE => dma.*[3].cnt.raw,
|
||||||
0xBC, 0xBE, 0xC0, 0xC2 => null, // DMA1SAD, DMA1DAD
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
0xC4 => 0x0000, // DMA1CNT_L
|
|
||||||
0xC6 => dma.*[1].dmacntH(),
|
|
||||||
|
|
||||||
0xC8, 0xCA, 0xCC, 0xCE => null, // DMA2SAD, DMA2DAD
|
|
||||||
0xD0 => 0x0000, // DMA2CNT_L
|
|
||||||
0xD2 => dma.*[2].dmacntH(),
|
|
||||||
|
|
||||||
0xD4, 0xD6, 0xD8, 0xDA => null, // DMA3SAD, DMA3DAD
|
|
||||||
0xDC => 0x0000, // DMA3CNT_L
|
|
||||||
0xDE => dma.*[3].dmacntH(),
|
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
|
||||||
u8 => switch (byte_addr) {
|
|
||||||
0xB0...0xB7 => null, // DMA0SAD, DMA0DAD
|
|
||||||
0xB8, 0xB9 => 0x00, // DMA0CNT_L
|
|
||||||
0xBA, 0xBB => @truncate(T, dma.*[0].dmacntH() >> getHalf(byte_addr)),
|
|
||||||
|
|
||||||
0xBC...0xC3 => null, // DMA1SAD, DMA1DAD
|
|
||||||
0xC4, 0xC5 => 0x00, // DMA1CNT_L
|
|
||||||
0xC6, 0xC7 => @truncate(T, dma.*[1].dmacntH() >> getHalf(byte_addr)),
|
|
||||||
|
|
||||||
0xC8...0xCF => null, // DMA2SAD, DMA2DAD
|
|
||||||
0xD0, 0xD1 => 0x00, // DMA2CNT_L
|
|
||||||
0xD2, 0xD3 => @truncate(T, dma.*[2].dmacntH() >> getHalf(byte_addr)),
|
|
||||||
|
|
||||||
0xD4...0xDB => null, // DMA3SAD, DMA3DAD
|
|
||||||
0xDC, 0xDD => 0x00, // DMA3CNT_L
|
|
||||||
0xDE, 0xDF => @truncate(T, dma.*[3].dmacntH() >> getHalf(byte_addr)),
|
|
||||||
else => util.io.read.err(T, log, "unexpected {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
},
|
||||||
|
u8 => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
else => @compileError("DMA: Unsupported read width"),
|
else => @compileError("DMA: Unsupported read width"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(comptime T: type, dma: *DmaTuple, addr: u32, value: T) void {
|
pub fn write(comptime T: type, dma: *DmaTuple, addr: u32, value: T) void {
|
||||||
const byte_addr = @truncate(u8, addr);
|
const byte = @truncate(u8, addr);
|
||||||
|
|
||||||
switch (T) {
|
switch (T) {
|
||||||
u32 => switch (byte_addr) {
|
u32 => switch (byte) {
|
||||||
0xB0 => dma.*[0].setDmasad(value),
|
0xB0 => dma.*[0].setDmasad(value),
|
||||||
0xB4 => dma.*[0].setDmadad(value),
|
0xB4 => dma.*[0].setDmadad(value),
|
||||||
0xB8 => dma.*[0].setDmacnt(value),
|
0xB8 => dma.*[0].setDmacnt(value),
|
||||||
|
|
||||||
0xBC => dma.*[1].setDmasad(value),
|
0xBC => dma.*[1].setDmasad(value),
|
||||||
0xC0 => dma.*[1].setDmadad(value),
|
0xC0 => dma.*[1].setDmadad(value),
|
||||||
0xC4 => dma.*[1].setDmacnt(value),
|
0xC4 => dma.*[1].setDmacnt(value),
|
||||||
|
|
||||||
0xC8 => dma.*[2].setDmasad(value),
|
0xC8 => dma.*[2].setDmasad(value),
|
||||||
0xCC => dma.*[2].setDmadad(value),
|
0xCC => dma.*[2].setDmadad(value),
|
||||||
0xD0 => dma.*[2].setDmacnt(value),
|
0xD0 => dma.*[2].setDmacnt(value),
|
||||||
|
|
||||||
0xD4 => dma.*[3].setDmasad(value),
|
0xD4 => dma.*[3].setDmasad(value),
|
||||||
0xD8 => dma.*[3].setDmadad(value),
|
0xD8 => dma.*[3].setDmadad(value),
|
||||||
0xDC => dma.*[3].setDmacnt(value),
|
0xDC => dma.*[3].setDmacnt(value),
|
||||||
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 => switch (byte) {
|
||||||
0xB0, 0xB2 => dma.*[0].setDmasad(setHalf(u32, dma.*[0].sad, byte_addr, value)),
|
0xB0 => dma.*[0].setDmasad(setLo(u32, dma.*[0].sad, value)),
|
||||||
0xB4, 0xB6 => dma.*[0].setDmadad(setHalf(u32, dma.*[0].dad, byte_addr, value)),
|
0xB2 => dma.*[0].setDmasad(setHi(u32, dma.*[0].sad, value)),
|
||||||
|
0xB4 => dma.*[0].setDmadad(setLo(u32, dma.*[0].dad, value)),
|
||||||
|
0xB6 => dma.*[0].setDmadad(setHi(u32, dma.*[0].dad, value)),
|
||||||
0xB8 => dma.*[0].setDmacntL(value),
|
0xB8 => dma.*[0].setDmacntL(value),
|
||||||
0xBA => dma.*[0].setDmacntH(value),
|
0xBA => dma.*[0].setDmacntH(value),
|
||||||
|
|
||||||
0xBC, 0xBE => dma.*[1].setDmasad(setHalf(u32, dma.*[1].sad, byte_addr, value)),
|
0xBC => dma.*[1].setDmasad(setLo(u32, dma.*[1].sad, value)),
|
||||||
0xC0, 0xC2 => dma.*[1].setDmadad(setHalf(u32, dma.*[1].dad, byte_addr, value)),
|
0xBE => dma.*[1].setDmasad(setHi(u32, dma.*[1].sad, value)),
|
||||||
|
0xC0 => dma.*[1].setDmadad(setLo(u32, dma.*[1].dad, value)),
|
||||||
|
0xC2 => dma.*[1].setDmadad(setHi(u32, dma.*[1].dad, value)),
|
||||||
0xC4 => dma.*[1].setDmacntL(value),
|
0xC4 => dma.*[1].setDmacntL(value),
|
||||||
0xC6 => dma.*[1].setDmacntH(value),
|
0xC6 => dma.*[1].setDmacntH(value),
|
||||||
|
|
||||||
0xC8, 0xCA => dma.*[2].setDmasad(setHalf(u32, dma.*[2].sad, byte_addr, value)),
|
0xC8 => dma.*[2].setDmasad(setLo(u32, dma.*[2].sad, value)),
|
||||||
0xCC, 0xCE => dma.*[2].setDmadad(setHalf(u32, dma.*[2].dad, byte_addr, value)),
|
0xCA => dma.*[2].setDmasad(setHi(u32, dma.*[2].sad, value)),
|
||||||
|
0xCC => dma.*[2].setDmadad(setLo(u32, dma.*[2].dad, value)),
|
||||||
|
0xCE => dma.*[2].setDmadad(setHi(u32, dma.*[2].dad, value)),
|
||||||
0xD0 => dma.*[2].setDmacntL(value),
|
0xD0 => dma.*[2].setDmacntL(value),
|
||||||
0xD2 => dma.*[2].setDmacntH(value),
|
0xD2 => dma.*[2].setDmacntH(value),
|
||||||
|
|
||||||
0xD4, 0xD6 => dma.*[3].setDmasad(setHalf(u32, dma.*[3].sad, byte_addr, value)),
|
0xD4 => dma.*[3].setDmasad(setLo(u32, dma.*[3].sad, value)),
|
||||||
0xD8, 0xDA => dma.*[3].setDmadad(setHalf(u32, dma.*[3].dad, byte_addr, value)),
|
0xD6 => dma.*[3].setDmasad(setHi(u32, dma.*[3].sad, value)),
|
||||||
|
0xD8 => dma.*[3].setDmadad(setLo(u32, dma.*[3].dad, value)),
|
||||||
|
0xDA => dma.*[3].setDmadad(setHi(u32, dma.*[3].dad, value)),
|
||||||
0xDC => dma.*[3].setDmacntL(value),
|
0xDC => dma.*[3].setDmacntL(value),
|
||||||
0xDE => dma.*[3].setDmacntH(value),
|
0xDE => dma.*[3].setDmacntH(value),
|
||||||
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 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
0xB0, 0xB1, 0xB2, 0xB3 => dma.*[0].setDmasad(setQuart(dma.*[0].sad, byte_addr, value)),
|
|
||||||
0xB4, 0xB5, 0xB6, 0xB7 => dma.*[0].setDmadad(setQuart(dma.*[0].dad, byte_addr, value)),
|
|
||||||
0xB8, 0xB9 => dma.*[0].setDmacntL(setHalf(u16, dma.*[0].word_count, byte_addr, value)),
|
|
||||||
0xBA, 0xBB => dma.*[0].setDmacntH(setHalf(u16, dma.*[0].cnt.raw, byte_addr, value)),
|
|
||||||
|
|
||||||
0xBC, 0xBD, 0xBE, 0xBF => dma.*[1].setDmasad(setQuart(dma.*[1].sad, byte_addr, value)),
|
|
||||||
0xC0, 0xC1, 0xC2, 0xC3 => dma.*[1].setDmadad(setQuart(dma.*[1].dad, byte_addr, value)),
|
|
||||||
0xC4, 0xC5 => dma.*[1].setDmacntL(setHalf(u16, dma.*[1].word_count, byte_addr, value)),
|
|
||||||
0xC6, 0xC7 => dma.*[1].setDmacntH(setHalf(u16, dma.*[1].cnt.raw, byte_addr, value)),
|
|
||||||
|
|
||||||
0xC8, 0xC9, 0xCA, 0xCB => dma.*[2].setDmasad(setQuart(dma.*[2].sad, byte_addr, value)),
|
|
||||||
0xCC, 0xCD, 0xCE, 0xCF => dma.*[2].setDmadad(setQuart(dma.*[2].dad, byte_addr, value)),
|
|
||||||
0xD0, 0xD1 => dma.*[2].setDmacntL(setHalf(u16, dma.*[2].word_count, byte_addr, value)),
|
|
||||||
0xD2, 0xD3 => dma.*[2].setDmacntH(setHalf(u16, dma.*[2].cnt.raw, byte_addr, value)),
|
|
||||||
|
|
||||||
0xD4, 0xD5, 0xD6, 0xD7 => dma.*[3].setDmasad(setQuart(dma.*[3].sad, byte_addr, value)),
|
|
||||||
0xD8, 0xD9, 0xDA, 0xDB => dma.*[3].setDmadad(setQuart(dma.*[3].dad, byte_addr, value)),
|
|
||||||
0xDC, 0xDD => dma.*[3].setDmacntL(setHalf(u16, dma.*[3].word_count, byte_addr, value)),
|
|
||||||
0xDE, 0xDF => dma.*[3].setDmacntH(setHalf(u16, dma.*[3].cnt.raw, byte_addr, value)),
|
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
|
|
||||||
},
|
|
||||||
else => @compileError("DMA: Unsupported write width"),
|
else => @compileError("DMA: Unsupported write width"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,10 +158,6 @@ fn DmaController(comptime id: u2) type {
|
||||||
self.word_count = @truncate(@TypeOf(self.word_count), halfword);
|
self.word_count = @truncate(@TypeOf(self.word_count), halfword);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dmacntH(self: *const Self) u16 {
|
|
||||||
return self.cnt.raw & if (id == 3) 0xFFE0 else 0xF7E0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setDmacntH(self: *Self, halfword: u16) void {
|
pub fn setDmacntH(self: *Self, halfword: u16) void {
|
||||||
const new = DmaControl{ .raw = halfword };
|
const new = DmaControl{ .raw = halfword };
|
||||||
|
|
||||||
|
@ -259,6 +206,7 @@ fn DmaController(comptime id: u2) type {
|
||||||
switch (sad_adj) {
|
switch (sad_adj) {
|
||||||
.Increment => self.sad_latch +%= offset,
|
.Increment => self.sad_latch +%= offset,
|
||||||
.Decrement => self.sad_latch -%= offset,
|
.Decrement => self.sad_latch -%= offset,
|
||||||
|
// FIXME: Is just ignoring this ok?
|
||||||
.IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}),
|
.IncrementReload => log.err("{} is a prohibited adjustment on SAD", .{sad_adj}),
|
||||||
.Fixed => {},
|
.Fixed => {},
|
||||||
}
|
}
|
||||||
|
@ -334,10 +282,10 @@ fn DmaController(comptime id: u2) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void {
|
pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void {
|
||||||
comptime var i: usize = 0;
|
bus.dma[0].poll(kind);
|
||||||
inline while (i < 4) : (i += 1) {
|
bus.dma[1].poll(kind);
|
||||||
bus.dma[i].poll(kind);
|
bus.dma[2].poll(kind);
|
||||||
}
|
bus.dma[3].poll(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Adjustment = enum(u2) {
|
const Adjustment = enum(u2) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitfield").Bit;
|
||||||
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
const DateTime = @import("datetime").datetime.Datetime;
|
const DateTime = @import("datetime").datetime.Datetime;
|
||||||
|
|
||||||
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
||||||
|
@ -71,7 +72,7 @@ pub const Gpio = struct {
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.data = 0b0000,
|
.data = 0b0000,
|
||||||
.direction = 0b1111, // TODO: What is GPIO Direction set to by default?
|
.direction = 0b1111, // TODO: What is GPIO DIrection set to by default?
|
||||||
.cnt = 0b0,
|
.cnt = 0b0,
|
||||||
|
|
||||||
.device = switch (kind) {
|
.device = switch (kind) {
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
const timer = @import("timer.zig");
|
const timer = @import("timer.zig");
|
||||||
const dma = @import("dma.zig");
|
const dma = @import("dma.zig");
|
||||||
const apu = @import("../apu.zig");
|
const apu = @import("../apu.zig");
|
||||||
const ppu = @import("../ppu.zig");
|
|
||||||
const util = @import("../../util.zig");
|
const util = @import("../../util.zig");
|
||||||
|
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitfield").Bit;
|
||||||
const Bitfield = @import("bitfield").Bitfield;
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
const Bus = @import("../Bus.zig");
|
const Bus = @import("../Bus.zig");
|
||||||
|
const DmaController = @import("dma.zig").DmaController;
|
||||||
|
const Scheduler = @import("../scheduler.zig").Scheduler;
|
||||||
|
|
||||||
const getHalf = util.getHalf;
|
const setHi = util.setHi;
|
||||||
const setHalf = util.setHalf;
|
const setLo = util.setLo;
|
||||||
|
|
||||||
const log = std.log.scoped(.@"I/O");
|
const log = std.log.scoped(.@"I/O");
|
||||||
|
|
||||||
|
@ -22,7 +24,6 @@ pub const Io = struct {
|
||||||
ie: InterruptEnable,
|
ie: InterruptEnable,
|
||||||
irq: InterruptRequest,
|
irq: InterruptRequest,
|
||||||
postflg: PostFlag,
|
postflg: PostFlag,
|
||||||
waitcnt: WaitControl,
|
|
||||||
haltcnt: HaltControl,
|
haltcnt: HaltControl,
|
||||||
keyinput: KeyInput,
|
keyinput: KeyInput,
|
||||||
|
|
||||||
|
@ -32,7 +33,6 @@ pub const Io = struct {
|
||||||
.ie = .{ .raw = 0x0000 },
|
.ie = .{ .raw = 0x0000 },
|
||||||
.irq = .{ .raw = 0x0000 },
|
.irq = .{ .raw = 0x0000 },
|
||||||
.keyinput = .{ .raw = 0x03FF },
|
.keyinput = .{ .raw = 0x03FF },
|
||||||
.waitcnt = .{ .raw = 0x0000_0000 }, // Bit 15 == 0 for GBA
|
|
||||||
.postflg = .FirstBoot,
|
.postflg = .FirstBoot,
|
||||||
.haltcnt = .Execute,
|
.haltcnt = .Execute,
|
||||||
};
|
};
|
||||||
|
@ -48,10 +48,9 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (address) {
|
u32 => switch (address) {
|
||||||
// Display
|
// Display
|
||||||
0x0400_0000...0x0400_0054 => ppu.read(T, &bus.ppu, address),
|
0x0400_0000 => bus.ppu.dispcnt.raw,
|
||||||
|
0x0400_0004 => @as(T, bus.ppu.vcount.raw) << 16 | bus.ppu.dispstat.raw,
|
||||||
// Sound
|
0x0400_0006 => @as(T, bus.ppu.bg[0].cnt.raw) << 16 | bus.ppu.vcount.raw,
|
||||||
0x0400_0060...0x0400_00A4 => apu.read(T, &bus.apu, address),
|
|
||||||
|
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address),
|
0x0400_00B0...0x0400_00DC => dma.read(T, &bus.dma, address),
|
||||||
|
@ -69,18 +68,26 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
|
||||||
0x0400_0150 => util.io.read.todo(log, "Read {} from JOY_RECV", .{T}),
|
0x0400_0150 => util.io.read.todo(log, "Read {} from JOY_RECV", .{T}),
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200 => @as(u32, bus.io.irq.raw) << 16 | bus.io.ie.raw,
|
0x0400_0200 => @as(T, bus.io.irq.raw) << 16 | bus.io.ie.raw,
|
||||||
0x0400_0204 => bus.io.waitcnt.raw,
|
|
||||||
0x0400_0208 => @boolToInt(bus.io.ime),
|
0x0400_0208 => @boolToInt(bus.io.ime),
|
||||||
0x0400_0300 => @enumToInt(bus.io.postflg),
|
|
||||||
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
|
||||||
},
|
},
|
||||||
u16 => switch (address) {
|
u16 => switch (address) {
|
||||||
// Display
|
// Display
|
||||||
0x0400_0000...0x0400_0054 => ppu.read(T, &bus.ppu, address),
|
0x0400_0000 => bus.ppu.dispcnt.raw,
|
||||||
|
0x0400_0004 => bus.ppu.dispstat.raw,
|
||||||
|
0x0400_0006 => bus.ppu.vcount.raw,
|
||||||
|
0x0400_0008 => bus.ppu.bg[0].cnt.raw,
|
||||||
|
0x0400_000A => bus.ppu.bg[1].cnt.raw,
|
||||||
|
0x0400_000C => bus.ppu.bg[2].cnt.raw,
|
||||||
|
0x0400_000E => bus.ppu.bg[3].cnt.raw,
|
||||||
|
0x0400_004C => util.io.read.todo(log, "Read {} from MOSAIC", .{T}),
|
||||||
|
0x0400_0050 => bus.ppu.bldcnt.raw,
|
||||||
|
0x0400_0052 => bus.ppu.bldalpha.raw,
|
||||||
|
0x0400_0054 => bus.ppu.bldy.raw,
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_00A6 => apu.read(T, &bus.apu, address),
|
0x0400_0060...0x0400_009E => apu.read(T, &bus.apu, address),
|
||||||
|
|
||||||
// DMA Transfers
|
// DMA Transfers
|
||||||
0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address),
|
0x0400_00B0...0x0400_00DE => dma.read(T, &bus.dma, address),
|
||||||
|
@ -96,34 +103,28 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
|
||||||
|
|
||||||
// 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}),
|
||||||
0x0400_0136 => 0x0000,
|
|
||||||
0x0400_0142 => 0x0000,
|
|
||||||
0x0400_015A => 0x0000,
|
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200 => bus.io.ie.raw,
|
0x0400_0200 => bus.io.ie.raw,
|
||||||
0x0400_0202 => bus.io.irq.raw,
|
0x0400_0202 => bus.io.irq.raw,
|
||||||
0x0400_0204 => bus.io.waitcnt.raw,
|
0x0400_0204 => util.io.read.todo(log, "Read {} from WAITCNT", .{T}),
|
||||||
0x0400_0206 => 0x0000,
|
|
||||||
0x0400_0208 => @boolToInt(bus.io.ime),
|
0x0400_0208 => @boolToInt(bus.io.ime),
|
||||||
0x0400_020A => 0x0000,
|
|
||||||
0x0400_0300 => @enumToInt(bus.io.postflg),
|
|
||||||
0x0400_0302 => 0x0000,
|
|
||||||
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
|
||||||
},
|
},
|
||||||
u8 => return switch (address) {
|
u8 => return switch (address) {
|
||||||
// Display
|
// Display
|
||||||
0x0400_0000...0x0400_0055 => ppu.read(T, &bus.ppu, address),
|
0x0400_0000 => @truncate(T, bus.ppu.dispcnt.raw),
|
||||||
|
0x0400_0004 => @truncate(T, bus.ppu.dispstat.raw),
|
||||||
|
0x0400_0005 => @truncate(T, bus.ppu.dispcnt.raw >> 8),
|
||||||
|
0x0400_0006 => @truncate(T, bus.ppu.vcount.raw),
|
||||||
|
0x0400_0008 => @truncate(T, bus.ppu.bg[0].cnt.raw),
|
||||||
|
0x0400_0009 => @truncate(T, bus.ppu.bg[0].cnt.raw >> 8),
|
||||||
|
0x0400_000A => @truncate(T, bus.ppu.bg[1].cnt.raw),
|
||||||
|
0x0400_000B => @truncate(T, bus.ppu.bg[1].cnt.raw >> 8),
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_00A7 => apu.read(T, &bus.apu, address),
|
0x0400_0060...0x0400_00A7 => apu.read(T, &bus.apu, address),
|
||||||
|
|
||||||
// DMA Transfers
|
|
||||||
0x0400_00B0...0x0400_00DF => dma.read(T, &bus.dma, address),
|
|
||||||
|
|
||||||
// Timers
|
|
||||||
0x0400_0100...0x0400_010F => timer.read(T, &bus.tim, address),
|
|
||||||
|
|
||||||
// Serial Communication 1
|
// Serial Communication 1
|
||||||
0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT_L", .{T}),
|
0x0400_0128 => util.io.read.todo(log, "Read {} from SIOCNT_L", .{T}),
|
||||||
|
|
||||||
|
@ -132,20 +133,10 @@ pub fn read(bus: *const Bus, comptime T: type, address: u32) ?T {
|
||||||
|
|
||||||
// Serial Communication 2
|
// Serial Communication 2
|
||||||
0x0400_0135 => util.io.read.todo(log, "Read {} from RCNT_H", .{T}),
|
0x0400_0135 => util.io.read.todo(log, "Read {} from RCNT_H", .{T}),
|
||||||
0x0400_0136, 0x0400_0137 => 0x00,
|
|
||||||
0x0400_0142, 0x0400_0143 => 0x00,
|
|
||||||
0x0400_015A, 0x0400_015B => 0x00,
|
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200, 0x0400_0201 => @truncate(T, bus.io.ie.raw >> getHalf(@truncate(u8, address))),
|
0x0400_0200 => @truncate(T, bus.io.ie.raw),
|
||||||
0x0400_0202, 0x0400_0203 => @truncate(T, bus.io.irq.raw >> getHalf(@truncate(u8, address))),
|
|
||||||
0x0400_0204, 0x0400_0205 => @truncate(T, bus.io.waitcnt.raw >> getHalf(@truncate(u8, address))),
|
|
||||||
0x0400_0206, 0x0400_0207 => 0x00,
|
|
||||||
0x0400_0208, 0x0400_0209 => @truncate(T, @as(u16, @boolToInt(bus.io.ime)) >> getHalf(@truncate(u8, address))),
|
|
||||||
0x0400_020A, 0x0400_020B => 0x00,
|
|
||||||
0x0400_0300 => @enumToInt(bus.io.postflg),
|
0x0400_0300 => @enumToInt(bus.io.postflg),
|
||||||
0x0400_0301 => null,
|
|
||||||
0x0400_0302, 0x0400_0303 => 0x00,
|
|
||||||
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, address }),
|
||||||
},
|
},
|
||||||
else => @compileError("I/O: Unsupported read width"),
|
else => @compileError("I/O: Unsupported read width"),
|
||||||
|
@ -156,7 +147,34 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (address) {
|
u32 => switch (address) {
|
||||||
// Display
|
// Display
|
||||||
0x0400_0000...0x0400_0054 => ppu.write(T, &bus.ppu, address, value),
|
0x0400_0000 => bus.ppu.dispcnt.raw = @truncate(u16, value),
|
||||||
|
0x0400_0004 => {
|
||||||
|
bus.ppu.dispstat.raw = @truncate(u16, value);
|
||||||
|
bus.ppu.vcount.raw = @truncate(u16, value >> 16);
|
||||||
|
},
|
||||||
|
0x0400_0008 => bus.ppu.setAdjCnts(0, value),
|
||||||
|
0x0400_000C => bus.ppu.setAdjCnts(2, value),
|
||||||
|
0x0400_0010 => bus.ppu.setBgOffsets(0, value),
|
||||||
|
0x0400_0014 => bus.ppu.setBgOffsets(1, value),
|
||||||
|
0x0400_0018 => bus.ppu.setBgOffsets(2, value),
|
||||||
|
0x0400_001C => bus.ppu.setBgOffsets(3, value),
|
||||||
|
0x0400_0020 => bus.ppu.aff_bg[0].writePaPb(value),
|
||||||
|
0x0400_0024 => bus.ppu.aff_bg[0].writePcPd(value),
|
||||||
|
0x0400_0028 => bus.ppu.aff_bg[0].setX(bus.ppu.dispstat.vblank.read(), value),
|
||||||
|
0x0400_002C => bus.ppu.aff_bg[0].setY(bus.ppu.dispstat.vblank.read(), value),
|
||||||
|
0x0400_0030 => bus.ppu.aff_bg[1].writePaPb(value),
|
||||||
|
0x0400_0034 => bus.ppu.aff_bg[1].writePcPd(value),
|
||||||
|
0x0400_0038 => bus.ppu.aff_bg[1].setX(bus.ppu.dispstat.vblank.read(), value),
|
||||||
|
0x0400_003C => bus.ppu.aff_bg[1].setY(bus.ppu.dispstat.vblank.read(), value),
|
||||||
|
0x0400_0040 => bus.ppu.win.setH(value),
|
||||||
|
0x0400_0044 => bus.ppu.win.setV(value),
|
||||||
|
0x0400_0048 => bus.ppu.win.setIo(value),
|
||||||
|
0x0400_004C => log.debug("Wrote 0x{X:0>8} to MOSAIC", .{value}),
|
||||||
|
0x0400_0050 => {
|
||||||
|
bus.ppu.bldcnt.raw = @truncate(u16, value);
|
||||||
|
bus.ppu.bldalpha.raw = @truncate(u16, value >> 16);
|
||||||
|
},
|
||||||
|
0x0400_0054 => bus.ppu.bldy.raw = @truncate(u16, value),
|
||||||
0x0400_0058...0x0400_005C => {}, // Unused
|
0x0400_0058...0x0400_005C => {}, // Unused
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
|
@ -192,28 +210,65 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200 => bus.io.setIrqs(value),
|
0x0400_0200 => bus.io.setIrqs(value),
|
||||||
0x0400_0204 => bus.io.waitcnt.set(@truncate(u16, value)),
|
0x0400_0204 => log.debug("Wrote 0x{X:0>8} to WAITCNT", .{value}),
|
||||||
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
||||||
0x0400_0300 => {
|
0x0400_020C...0x0400_021C => {}, // Unused
|
||||||
bus.io.postflg = @intToEnum(PostFlag, value & 1);
|
|
||||||
bus.io.haltcnt = if (value >> 15 & 1 == 0) .Halt else @panic("TODO: Implement STOP");
|
|
||||||
},
|
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, address }),
|
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, address }),
|
||||||
},
|
},
|
||||||
u16 => switch (address) {
|
u16 => switch (address) {
|
||||||
// Display
|
// Display
|
||||||
0x0400_0000...0x0400_0054 => ppu.write(T, &bus.ppu, address, value),
|
0x0400_0000 => bus.ppu.dispcnt.raw = value,
|
||||||
0x0400_0056 => {}, // Not used
|
0x0400_0004 => bus.ppu.dispstat.raw = value,
|
||||||
|
0x0400_0006 => {}, // vcount is read-only
|
||||||
|
0x0400_0008 => bus.ppu.bg[0].cnt.raw = value,
|
||||||
|
0x0400_000A => bus.ppu.bg[1].cnt.raw = value,
|
||||||
|
0x0400_000C => bus.ppu.bg[2].cnt.raw = value,
|
||||||
|
0x0400_000E => bus.ppu.bg[3].cnt.raw = value,
|
||||||
|
0x0400_0010 => bus.ppu.bg[0].hofs.raw = value, // TODO: Don't write out every HOFS / VOFS?
|
||||||
|
0x0400_0012 => bus.ppu.bg[0].vofs.raw = value,
|
||||||
|
0x0400_0014 => bus.ppu.bg[1].hofs.raw = value,
|
||||||
|
0x0400_0016 => bus.ppu.bg[1].vofs.raw = value,
|
||||||
|
0x0400_0018 => bus.ppu.bg[2].hofs.raw = value,
|
||||||
|
0x0400_001A => bus.ppu.bg[2].vofs.raw = value,
|
||||||
|
0x0400_001C => bus.ppu.bg[3].hofs.raw = value,
|
||||||
|
0x0400_001E => bus.ppu.bg[3].vofs.raw = value,
|
||||||
|
0x0400_0020 => bus.ppu.aff_bg[0].pa = @bitCast(i16, value),
|
||||||
|
0x0400_0022 => bus.ppu.aff_bg[0].pb = @bitCast(i16, value),
|
||||||
|
0x0400_0024 => bus.ppu.aff_bg[0].pc = @bitCast(i16, value),
|
||||||
|
0x0400_0026 => bus.ppu.aff_bg[0].pd = @bitCast(i16, value),
|
||||||
|
0x0400_0028 => bus.ppu.aff_bg[0].x = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[0].x), value)),
|
||||||
|
0x0400_002A => bus.ppu.aff_bg[0].x = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[0].x), value)),
|
||||||
|
0x0400_002C => bus.ppu.aff_bg[0].y = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[0].y), value)),
|
||||||
|
0x0400_002E => bus.ppu.aff_bg[0].y = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[0].y), value)),
|
||||||
|
0x0400_0030 => bus.ppu.aff_bg[1].pa = @bitCast(i16, value),
|
||||||
|
0x0400_0032 => bus.ppu.aff_bg[1].pb = @bitCast(i16, value),
|
||||||
|
0x0400_0034 => bus.ppu.aff_bg[1].pc = @bitCast(i16, value),
|
||||||
|
0x0400_0036 => bus.ppu.aff_bg[1].pd = @bitCast(i16, value),
|
||||||
|
0x0400_0038 => bus.ppu.aff_bg[1].x = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[1].x), value)),
|
||||||
|
0x0400_003A => bus.ppu.aff_bg[1].x = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[1].x), value)),
|
||||||
|
0x0400_003C => bus.ppu.aff_bg[1].y = @bitCast(i32, setLo(u32, @bitCast(u32, bus.ppu.aff_bg[1].y), value)),
|
||||||
|
0x0400_003E => bus.ppu.aff_bg[1].y = @bitCast(i32, setHi(u32, @bitCast(u32, bus.ppu.aff_bg[1].y), value)),
|
||||||
|
0x0400_0040 => bus.ppu.win.h[0].raw = value,
|
||||||
|
0x0400_0042 => bus.ppu.win.h[1].raw = value,
|
||||||
|
0x0400_0044 => bus.ppu.win.v[0].raw = value,
|
||||||
|
0x0400_0046 => bus.ppu.win.v[1].raw = value,
|
||||||
|
0x0400_0048 => bus.ppu.win.in.raw = value,
|
||||||
|
0x0400_004A => bus.ppu.win.out.raw = value,
|
||||||
|
0x0400_004C => log.debug("Wrote 0x{X:0>4} to MOSAIC", .{value}),
|
||||||
|
0x0400_0050 => bus.ppu.bldcnt.raw = value,
|
||||||
|
0x0400_0052 => bus.ppu.bldalpha.raw = value,
|
||||||
|
0x0400_0054 => bus.ppu.bldy.raw = value,
|
||||||
|
0x0400_004E, 0x0400_0056 => {}, // Not used
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_00A6 => 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),
|
||||||
|
|
||||||
// Timers
|
// Timers
|
||||||
0x0400_0100...0x0400_010E => timer.write(T, &bus.tim, address, value),
|
0x0400_0100...0x0400_010E => timer.write(T, &bus.tim, address, value),
|
||||||
0x0400_0114 => {},
|
0x0400_0114 => {}, // TODO: Gyakuten Saiban writes 0x8000 to 0x0400_0114
|
||||||
0x0400_0110 => {}, // Not Used,
|
0x0400_0110 => {}, // Not Used,
|
||||||
|
|
||||||
// Serial Communication 1
|
// Serial Communication 1
|
||||||
|
@ -237,29 +292,35 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200 => bus.io.ie.raw = value,
|
0x0400_0200 => bus.io.ie.raw = value,
|
||||||
0x0400_0202 => bus.io.irq.raw &= ~value,
|
0x0400_0202 => bus.io.irq.raw &= ~value,
|
||||||
0x0400_0204 => bus.io.waitcnt.set(value),
|
0x0400_0204 => log.debug("Wrote 0x{X:0>4} to WAITCNT", .{value}),
|
||||||
0x0400_0206 => {},
|
|
||||||
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
||||||
0x0400_020A => {},
|
0x0400_0206, 0x0400_020A => {}, // Not Used
|
||||||
0x0400_0300 => {
|
|
||||||
bus.io.postflg = @intToEnum(PostFlag, value & 1);
|
|
||||||
bus.io.haltcnt = if (value >> 15 & 1 == 0) .Halt else @panic("TODO: Implement STOP");
|
|
||||||
},
|
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, address }),
|
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, address }),
|
||||||
},
|
},
|
||||||
u8 => switch (address) {
|
u8 => switch (address) {
|
||||||
// Display
|
// Display
|
||||||
0x0400_0000...0x0400_0055 => ppu.write(T, &bus.ppu, address, value),
|
0x0400_0004 => bus.ppu.dispstat.raw = setLo(u16, bus.ppu.dispstat.raw, value),
|
||||||
|
0x0400_0005 => bus.ppu.dispstat.raw = setHi(u16, bus.ppu.dispstat.raw, value),
|
||||||
|
0x0400_0008 => bus.ppu.bg[0].cnt.raw = setLo(u16, bus.ppu.bg[0].cnt.raw, value),
|
||||||
|
0x0400_0009 => bus.ppu.bg[0].cnt.raw = setHi(u16, bus.ppu.bg[0].cnt.raw, value),
|
||||||
|
0x0400_000A => bus.ppu.bg[1].cnt.raw = setLo(u16, bus.ppu.bg[1].cnt.raw, value),
|
||||||
|
0x0400_000B => bus.ppu.bg[1].cnt.raw = setHi(u16, bus.ppu.bg[1].cnt.raw, value),
|
||||||
|
0x0400_0040 => bus.ppu.win.h[0].raw = setLo(u16, bus.ppu.win.h[0].raw, value),
|
||||||
|
0x0400_0041 => bus.ppu.win.h[0].raw = setHi(u16, bus.ppu.win.h[0].raw, value),
|
||||||
|
0x0400_0042 => bus.ppu.win.h[1].raw = setLo(u16, bus.ppu.win.h[1].raw, value),
|
||||||
|
0x0400_0043 => bus.ppu.win.h[1].raw = setHi(u16, bus.ppu.win.h[1].raw, value),
|
||||||
|
0x0400_0044 => bus.ppu.win.v[0].raw = setLo(u16, bus.ppu.win.v[0].raw, value),
|
||||||
|
0x0400_0045 => bus.ppu.win.v[0].raw = setHi(u16, bus.ppu.win.v[0].raw, value),
|
||||||
|
0x0400_0046 => bus.ppu.win.v[1].raw = setLo(u16, bus.ppu.win.v[1].raw, value),
|
||||||
|
0x0400_0047 => bus.ppu.win.v[1].raw = setHi(u16, bus.ppu.win.v[1].raw, value),
|
||||||
|
0x0400_0048 => bus.ppu.win.in.raw = setLo(u16, bus.ppu.win.in.raw, value),
|
||||||
|
0x0400_0049 => bus.ppu.win.in.raw = setHi(u16, bus.ppu.win.in.raw, value),
|
||||||
|
0x0400_004A => bus.ppu.win.out.raw = setLo(u16, bus.ppu.win.out.raw, value),
|
||||||
|
0x0400_0054 => bus.ppu.bldy.raw = setLo(u16, bus.ppu.bldy.raw, value),
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_00A7 => apu.write(T, &bus.apu, address, value),
|
0x0400_0060...0x0400_00A7 => apu.write(T, &bus.apu, address, value),
|
||||||
|
|
||||||
// Dma Transfers
|
|
||||||
0x0400_00B0...0x0400_00DF => dma.write(T, &bus.dma, address, value),
|
|
||||||
|
|
||||||
// Timers
|
|
||||||
0x0400_0100...0x0400_010F => timer.write(T, &bus.tim, 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}),
|
||||||
0x0400_0128 => log.debug("Wrote 0x{X:0>2} to SIOCNT_L", .{value}),
|
0x0400_0128 => log.debug("Wrote 0x{X:0>2} to SIOCNT_L", .{value}),
|
||||||
|
@ -269,16 +330,9 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0140 => log.debug("Wrote 0x{X:0>2} to JOYCNT_L", .{value}),
|
0x0400_0140 => log.debug("Wrote 0x{X:0>2} to JOYCNT_L", .{value}),
|
||||||
|
|
||||||
// Interrupts
|
// Interrupts
|
||||||
0x0400_0200, 0x0400_0201 => bus.io.ie.raw = setHalf(u16, bus.io.ie.raw, @truncate(u8, address), value),
|
|
||||||
0x0400_0202 => bus.io.irq.raw &= ~@as(u16, value),
|
0x0400_0202 => bus.io.irq.raw &= ~@as(u16, value),
|
||||||
0x0400_0203 => bus.io.irq.raw &= ~@as(u16, value) << 8, // TODO: Is this good?
|
|
||||||
0x0400_0204, 0x0400_0205 => bus.io.waitcnt.set(setHalf(u16, @truncate(u16, bus.io.waitcnt.raw), @truncate(u8, address), value)),
|
|
||||||
0x0400_0206, 0x0400_0207 => {},
|
|
||||||
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
0x0400_0208 => bus.io.ime = value & 1 == 1,
|
||||||
0x0400_0209 => {},
|
0x0400_0300 => bus.io.postflg = std.meta.intToEnum(PostFlag, value & 1) catch unreachable,
|
||||||
0x0400_020A, 0x0400_020B => {},
|
|
||||||
|
|
||||||
0x0400_0300 => bus.io.postflg = @intToEnum(PostFlag, value & 1),
|
|
||||||
0x0400_0301 => bus.io.haltcnt = if (value >> 7 & 1 == 0) .Halt else std.debug.panic("TODO: Implement STOP", .{}),
|
0x0400_0301 => bus.io.haltcnt = if (value >> 7 & 1 == 0) .Halt else std.debug.panic("TODO: Implement STOP", .{}),
|
||||||
|
|
||||||
0x0400_0410 => log.debug("Wrote 0x{X:0>2} to the common yet undocumented 0x{X:0>8}", .{ value, address }),
|
0x0400_0410 => log.debug("Wrote 0x{X:0>2} to the common yet undocumented 0x{X:0>8}", .{ value, address }),
|
||||||
|
@ -317,22 +371,14 @@ pub const DisplayControl = extern union {
|
||||||
|
|
||||||
/// Read / Write
|
/// Read / Write
|
||||||
pub const DisplayStatus = extern union {
|
pub const DisplayStatus = extern union {
|
||||||
/// read-only
|
|
||||||
vblank: Bit(u16, 0),
|
vblank: Bit(u16, 0),
|
||||||
/// read-only
|
|
||||||
hblank: Bit(u16, 1),
|
hblank: Bit(u16, 1),
|
||||||
// read-only
|
|
||||||
coincidence: Bit(u16, 2),
|
coincidence: Bit(u16, 2),
|
||||||
vblank_irq: Bit(u16, 3),
|
vblank_irq: Bit(u16, 3),
|
||||||
hblank_irq: Bit(u16, 4),
|
hblank_irq: Bit(u16, 4),
|
||||||
vcount_irq: Bit(u16, 5),
|
vcount_irq: Bit(u16, 5),
|
||||||
vcount_trigger: Bitfield(u16, 8, 8),
|
vcount_trigger: Bitfield(u16, 8, 8),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
|
|
||||||
pub fn set(self: *DisplayStatus, value: u16) void {
|
|
||||||
const mask: u16 = 0x00C7; // set bits are read-only
|
|
||||||
self.raw = (self.raw & mask) | (value & ~mask);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Read Only
|
/// Read Only
|
||||||
|
@ -636,24 +682,3 @@ pub const SoundBias = extern union {
|
||||||
sampling_cycle: Bitfield(u16, 14, 2),
|
sampling_cycle: Bitfield(u16, 14, 2),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Read / Write
|
|
||||||
pub const WaitControl = extern union {
|
|
||||||
sram_cnt: Bitfield(u16, 0, 2),
|
|
||||||
s0_first: Bitfield(u16, 2, 2),
|
|
||||||
s0_second: Bit(u16, 4),
|
|
||||||
s1_first: Bitfield(u16, 5, 2),
|
|
||||||
s1_second: Bit(u16, 7),
|
|
||||||
s2_first: Bitfield(u16, 8, 2),
|
|
||||||
s2_second: Bit(u16, 10),
|
|
||||||
phi_out: Bitfield(u16, 11, 2),
|
|
||||||
|
|
||||||
prefetch_enable: Bit(u16, 14),
|
|
||||||
pak_kind: Bit(u16, 15),
|
|
||||||
raw: u16,
|
|
||||||
|
|
||||||
pub fn set(self: *WaitControl, value: u16) void {
|
|
||||||
const mask: u16 = 0x8000; // set bits are read-only
|
|
||||||
self.raw = (self.raw & mask) | (value & ~mask);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -2,99 +2,68 @@ const std = @import("std");
|
||||||
const util = @import("../../util.zig");
|
const util = @import("../../util.zig");
|
||||||
|
|
||||||
const TimerControl = @import("io.zig").TimerControl;
|
const TimerControl = @import("io.zig").TimerControl;
|
||||||
|
const Io = @import("io.zig").Io;
|
||||||
const Scheduler = @import("../scheduler.zig").Scheduler;
|
const Scheduler = @import("../scheduler.zig").Scheduler;
|
||||||
|
const Event = @import("../scheduler.zig").Event;
|
||||||
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 = std.meta.Tuple(&[_]type{ 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 setHalf = util.setHalf;
|
|
||||||
|
|
||||||
pub fn create(sched: *Scheduler) TimerTuple {
|
pub fn create(sched: *Scheduler) TimerTuple {
|
||||||
return .{ Timer(0).init(sched), Timer(1).init(sched), Timer(2).init(sched), Timer(3).init(sched) };
|
return .{ Timer(0).init(sched), Timer(1).init(sched), Timer(2).init(sched), Timer(3).init(sched) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(comptime T: type, tim: *const TimerTuple, addr: u32) ?T {
|
pub fn read(comptime T: type, tim: *const TimerTuple, addr: u32) ?T {
|
||||||
const nybble_addr = @truncate(u4, addr);
|
const nybble = @truncate(u4, addr);
|
||||||
|
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (nybble_addr) {
|
u32 => switch (nybble) {
|
||||||
0x0 => @as(T, tim.*[0].cnt.raw) << 16 | tim.*[0].timcntL(),
|
0x0 => @as(T, tim.*[0].cnt.raw) << 16 | tim.*[0].timcntL(),
|
||||||
0x4 => @as(T, tim.*[1].cnt.raw) << 16 | tim.*[1].timcntL(),
|
0x4 => @as(T, tim.*[1].cnt.raw) << 16 | tim.*[1].timcntL(),
|
||||||
0x8 => @as(T, tim.*[2].cnt.raw) << 16 | tim.*[2].timcntL(),
|
0x8 => @as(T, tim.*[2].cnt.raw) << 16 | tim.*[2].timcntL(),
|
||||||
0xC => @as(T, tim.*[3].cnt.raw) << 16 | tim.*[3].timcntL(),
|
0xC => @as(T, tim.*[3].cnt.raw) << 16 | tim.*[3].timcntL(),
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
},
|
},
|
||||||
u16 => switch (nybble_addr) {
|
u16 => switch (nybble) {
|
||||||
0x0 => tim.*[0].timcntL(),
|
0x0 => tim.*[0].timcntL(),
|
||||||
0x2 => tim.*[0].cnt.raw,
|
0x2 => tim.*[0].cnt.raw,
|
||||||
|
|
||||||
0x4 => tim.*[1].timcntL(),
|
0x4 => tim.*[1].timcntL(),
|
||||||
0x6 => tim.*[1].cnt.raw,
|
0x6 => tim.*[1].cnt.raw,
|
||||||
|
|
||||||
0x8 => tim.*[2].timcntL(),
|
0x8 => tim.*[2].timcntL(),
|
||||||
0xA => tim.*[2].cnt.raw,
|
0xA => tim.*[2].cnt.raw,
|
||||||
|
|
||||||
0xC => tim.*[3].timcntL(),
|
0xC => tim.*[3].timcntL(),
|
||||||
0xE => tim.*[3].cnt.raw,
|
0xE => tim.*[3].cnt.raw,
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
else => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
},
|
|
||||||
u8 => switch (nybble_addr) {
|
|
||||||
0x0, 0x1 => @truncate(T, tim.*[0].timcntL() >> getHalf(nybble_addr)),
|
|
||||||
0x2, 0x3 => @truncate(T, tim.*[0].cnt.raw >> getHalf(nybble_addr)),
|
|
||||||
|
|
||||||
0x4, 0x5 => @truncate(T, tim.*[1].timcntL() >> getHalf(nybble_addr)),
|
|
||||||
0x6, 0x7 => @truncate(T, tim.*[1].cnt.raw >> getHalf(nybble_addr)),
|
|
||||||
|
|
||||||
0x8, 0x9 => @truncate(T, tim.*[2].timcntL() >> getHalf(nybble_addr)),
|
|
||||||
0xA, 0xB => @truncate(T, tim.*[2].cnt.raw >> getHalf(nybble_addr)),
|
|
||||||
|
|
||||||
0xC, 0xD => @truncate(T, tim.*[3].timcntL() >> getHalf(nybble_addr)),
|
|
||||||
0xE, 0xF => @truncate(T, tim.*[3].cnt.raw >> getHalf(nybble_addr)),
|
|
||||||
},
|
},
|
||||||
|
u8 => util.io.read.undef(T, log, "Tried to perform a {} read to 0x{X:0>8}", .{ T, addr }),
|
||||||
else => @compileError("TIM: Unsupported read width"),
|
else => @compileError("TIM: Unsupported read width"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(comptime T: type, tim: *TimerTuple, addr: u32, value: T) void {
|
pub fn write(comptime T: type, tim: *TimerTuple, addr: u32, value: T) void {
|
||||||
const nybble_addr = @truncate(u4, addr);
|
const nybble = @truncate(u4, addr);
|
||||||
|
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (nybble_addr) {
|
u32 => switch (nybble) {
|
||||||
0x0 => tim.*[0].setTimcnt(value),
|
0x0 => tim.*[0].setTimcnt(value),
|
||||||
0x4 => tim.*[1].setTimcnt(value),
|
0x4 => tim.*[1].setTimcnt(value),
|
||||||
0x8 => tim.*[2].setTimcnt(value),
|
0x8 => tim.*[2].setTimcnt(value),
|
||||||
0xC => tim.*[3].setTimcnt(value),
|
0xC => tim.*[3].setTimcnt(value),
|
||||||
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 (nybble_addr) {
|
u16 => switch (nybble) {
|
||||||
0x0 => tim.*[0].setTimcntL(value),
|
0x0 => tim.*[0].setTimcntL(value),
|
||||||
0x2 => tim.*[0].setTimcntH(value),
|
0x2 => tim.*[0].setTimcntH(value),
|
||||||
|
|
||||||
0x4 => tim.*[1].setTimcntL(value),
|
0x4 => tim.*[1].setTimcntL(value),
|
||||||
0x6 => tim.*[1].setTimcntH(value),
|
0x6 => tim.*[1].setTimcntH(value),
|
||||||
|
|
||||||
0x8 => tim.*[2].setTimcntL(value),
|
0x8 => tim.*[2].setTimcntL(value),
|
||||||
0xA => tim.*[2].setTimcntH(value),
|
0xA => tim.*[2].setTimcntH(value),
|
||||||
|
|
||||||
0xC => tim.*[3].setTimcntL(value),
|
0xC => tim.*[3].setTimcntL(value),
|
||||||
0xE => tim.*[3].setTimcntH(value),
|
0xE => tim.*[3].setTimcntH(value),
|
||||||
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 (nybble_addr) {
|
u8 => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
0x0, 0x1 => tim.*[0].setTimcntL(setHalf(u16, tim.*[0]._reload, nybble_addr, value)),
|
|
||||||
0x2, 0x3 => tim.*[0].setTimcntH(setHalf(u16, tim.*[0].cnt.raw, nybble_addr, value)),
|
|
||||||
|
|
||||||
0x4, 0x5 => tim.*[1].setTimcntL(setHalf(u16, tim.*[1]._reload, nybble_addr, value)),
|
|
||||||
0x6, 0x7 => tim.*[1].setTimcntH(setHalf(u16, tim.*[1].cnt.raw, nybble_addr, value)),
|
|
||||||
|
|
||||||
0x8, 0x9 => tim.*[2].setTimcntL(setHalf(u16, tim.*[2]._reload, nybble_addr, value)),
|
|
||||||
0xA, 0xB => tim.*[2].setTimcntH(setHalf(u16, tim.*[2].cnt.raw, nybble_addr, value)),
|
|
||||||
|
|
||||||
0xC, 0xD => tim.*[3].setTimcntL(setHalf(u16, tim.*[3]._reload, nybble_addr, value)),
|
|
||||||
0xE, 0xF => tim.*[3].setTimcntH(setHalf(u16, tim.*[3].cnt.raw, nybble_addr, value)),
|
|
||||||
},
|
|
||||||
else => @compileError("TIM: Unsupported write width"),
|
else => @compileError("TIM: Unsupported write width"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -190,15 +159,19 @@ fn Timer(comptime id: u2) type {
|
||||||
|
|
||||||
// Perform Cascade Behaviour
|
// Perform Cascade Behaviour
|
||||||
switch (id) {
|
switch (id) {
|
||||||
inline 0, 1, 2 => |idx| {
|
0 => if (cpu.bus.tim[1].cnt.cascade.read()) {
|
||||||
const next = idx + 1;
|
cpu.bus.tim[1]._counter +%= 1;
|
||||||
|
if (cpu.bus.tim[1]._counter == 0) cpu.bus.tim[1].onTimerExpire(cpu, late);
|
||||||
if (cpu.bus.tim[next].cnt.cascade.read()) {
|
|
||||||
cpu.bus.tim[next]._counter +%= 1;
|
|
||||||
if (cpu.bus.tim[next]._counter == 0) cpu.bus.tim[next].onTimerExpire(cpu, late);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
3 => {}, // THere is no timer for TIM3 to cascade to
|
1 => if (cpu.bus.tim[2].cnt.cascade.read()) {
|
||||||
|
cpu.bus.tim[2]._counter +%= 1;
|
||||||
|
if (cpu.bus.tim[2]._counter == 0) cpu.bus.tim[2].onTimerExpire(cpu, late);
|
||||||
|
},
|
||||||
|
2 => if (cpu.bus.tim[3].cnt.cascade.read()) {
|
||||||
|
cpu.bus.tim[3]._counter +%= 1;
|
||||||
|
if (cpu.bus.tim[3]._counter == 0) cpu.bus.tim[3].onTimerExpire(cpu, late);
|
||||||
|
},
|
||||||
|
3 => {}, // There is no Timer for TIM3 to "cascade" to,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reschedule Timer if we're not cascading
|
// Reschedule Timer if we're not cascading
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
const Bus = @import("Bus.zig");
|
const Bus = @import("Bus.zig");
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitfield").Bit;
|
||||||
const Bitfield = @import("bitfield").Bitfield;
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
|
const FilePaths = @import("../util.zig").FilePaths;
|
||||||
const Logger = @import("../util.zig").Logger;
|
const Logger = @import("../util.zig").Logger;
|
||||||
|
|
||||||
const File = std.fs.File;
|
const File = std.fs.File;
|
||||||
|
@ -455,12 +457,29 @@ pub const Arm7tdmi = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stepDmaTransfer(self: *Self) bool {
|
pub fn stepDmaTransfer(self: *Self) bool {
|
||||||
comptime var i: usize = 0;
|
const dma0 = &self.bus.dma[0];
|
||||||
inline while (i < 4) : (i += 1) {
|
const dma1 = &self.bus.dma[1];
|
||||||
if (self.bus.dma[i].in_progress) {
|
const dma2 = &self.bus.dma[2];
|
||||||
self.bus.dma[i].step(self);
|
const dma3 = &self.bus.dma[3];
|
||||||
return true;
|
|
||||||
}
|
if (dma0.in_progress) {
|
||||||
|
dma0.step(self);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dma1.in_progress) {
|
||||||
|
dma1.step(self);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dma2.in_progress) {
|
||||||
|
dma2.step(self);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dma3.in_progress) {
|
||||||
|
dma3.step(self);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -608,29 +627,25 @@ pub const Arm7tdmi = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const condition_lut = [_]u16{
|
pub fn checkCond(cpsr: PSR, cond: u4) bool {
|
||||||
0xF0F0, // EQ - Equal
|
return switch (cond) {
|
||||||
0x0F0F, // NE - Not Equal
|
0x0 => cpsr.z.read(), // EQ - Equal
|
||||||
0xCCCC, // CS - Unsigned higher or same
|
0x1 => !cpsr.z.read(), // NE - Not equal
|
||||||
0x3333, // CC - Unsigned lower
|
0x2 => cpsr.c.read(), // CS - Unsigned higher or same
|
||||||
0xFF00, // MI - Negative
|
0x3 => !cpsr.c.read(), // CC - Unsigned lower
|
||||||
0x00FF, // PL - Positive or Zero
|
0x4 => cpsr.n.read(), // MI - Negative
|
||||||
0xAAAA, // VS - Overflow
|
0x5 => !cpsr.n.read(), // PL - Positive or zero
|
||||||
0x5555, // VC - No Overflow
|
0x6 => cpsr.v.read(), // VS - Overflow
|
||||||
0x0C0C, // HI - unsigned hierh
|
0x7 => !cpsr.v.read(), // VC - No overflow
|
||||||
0xF3F3, // LS - unsigned lower or same
|
0x8 => cpsr.c.read() and !cpsr.z.read(), // HI - unsigned higher
|
||||||
0xAA55, // GE - greater or equal
|
0x9 => !cpsr.c.read() or cpsr.z.read(), // LS - unsigned lower or same
|
||||||
0x55AA, // LT - less than
|
0xA => cpsr.n.read() == cpsr.v.read(), // GE - Greater or equal
|
||||||
0x0A05, // GT - greater than
|
0xB => cpsr.n.read() != cpsr.v.read(), // LT - Less than
|
||||||
0xF5FA, // LE - less than or equal
|
0xC => !cpsr.z.read() and (cpsr.n.read() == cpsr.v.read()), // GT - Greater than
|
||||||
0xFFFF, // AL - always
|
0xD => cpsr.z.read() or (cpsr.n.read() != cpsr.v.read()), // LE - Less than or equal
|
||||||
0x0000, // NV - never
|
0xE => true, // AL - Always
|
||||||
};
|
0xF => false, // NV - Never (reserved in ARMv3 and up, but seems to have not changed?)
|
||||||
|
};
|
||||||
pub inline fn checkCond(cpsr: PSR, cond: u4) bool {
|
|
||||||
const flags = @truncate(u4, cpsr.raw >> 28);
|
|
||||||
|
|
||||||
return condition_lut[cond] & (@as(u16, 1) << flags) != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Pipeline = struct {
|
const Pipeline = struct {
|
||||||
|
|
|
@ -57,6 +57,7 @@ pub fn blockDataTransfer(comptime P: bool, comptime U: bool, comptime S: bool, c
|
||||||
cpu.r[15] = bus.read(u32, und_addr);
|
cpu.r[15] = bus.read(u32, und_addr);
|
||||||
cpu.pipe.reload(cpu);
|
cpu.pipe.reload(cpu);
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME: Should r15 on write be +12 ahead?
|
||||||
bus.write(u32, und_addr, cpu.r[15] + 4);
|
bus.write(u32, und_addr, cpu.r[15] + 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const Bus = @import("../../Bus.zig");
|
const Bus = @import("../../Bus.zig");
|
||||||
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
||||||
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
|
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const Bus = @import("../../Bus.zig");
|
const Bus = @import("../../Bus.zig");
|
||||||
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
||||||
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
|
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const Bus = @import("../../Bus.zig");
|
const Bus = @import("../../Bus.zig");
|
||||||
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
||||||
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
|
const InstrFn = @import("../../cpu.zig").arm.InstrFn;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const util = @import("../../../util.zig");
|
||||||
|
|
||||||
const shifter = @import("../barrel_shifter.zig");
|
const shifter = @import("../barrel_shifter.zig");
|
||||||
const Bus = @import("../../Bus.zig");
|
const Bus = @import("../../Bus.zig");
|
||||||
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../cpu.zig").Arm7tdmi;
|
||||||
const CPSR = @import("../cpu.zig").PSR;
|
const CPSR = @import("../cpu.zig").PSR;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const Bus = @import("../../Bus.zig");
|
const Bus = @import("../../Bus.zig");
|
||||||
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
||||||
const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
|
const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const Bus = @import("../../Bus.zig");
|
const Bus = @import("../../Bus.zig");
|
||||||
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("../../cpu.zig").Arm7tdmi;
|
||||||
const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
|
const InstrFn = @import("../../cpu.zig").thumb.InstrFn;
|
||||||
|
|
|
@ -2,29 +2,29 @@ const std = @import("std");
|
||||||
const SDL = @import("sdl2");
|
const SDL = @import("sdl2");
|
||||||
const config = @import("../config.zig");
|
const config = @import("../config.zig");
|
||||||
|
|
||||||
|
const Bus = @import("Bus.zig");
|
||||||
const Scheduler = @import("scheduler.zig").Scheduler;
|
const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||||
const FpsTracker = @import("../util.zig").FpsTracker;
|
const FpsTracker = @import("../util.zig").FpsTracker;
|
||||||
|
const FilePaths = @import("../util.zig").FilePaths;
|
||||||
|
|
||||||
const Timer = std.time.Timer;
|
const Timer = std.time.Timer;
|
||||||
|
const Thread = std.Thread;
|
||||||
const Atomic = std.atomic.Atomic;
|
const Atomic = std.atomic.Atomic;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
/// 4 Cycles in 1 dot
|
// 228 Lines which consist of 308 dots (which are 4 cycles long)
|
||||||
const cycles_per_dot = 4;
|
const cycles_per_frame: u64 = 228 * (308 * 4); //280896
|
||||||
|
const clock_rate: u64 = 1 << 24; // 16.78MHz
|
||||||
|
|
||||||
/// The GBA draws 228 Horizontal which each consist 308 dots
|
// TODO: Don't truncate this, be more accurate w/ timing
|
||||||
/// (note: not all lines are visible)
|
// 59.6046447754ns (truncated to just 59ns)
|
||||||
const cycles_per_frame = 228 * (308 * cycles_per_dot); //280896
|
const clock_period: u64 = std.time.ns_per_s / clock_rate;
|
||||||
|
const frame_period = (clock_period * cycles_per_frame);
|
||||||
|
|
||||||
/// The GBA ARM7TDMI runs at 2^24 Hz
|
// 59.7275005696Hz
|
||||||
const clock_rate = 1 << 24; // 16.78MHz
|
pub const frame_rate = @intToFloat(f64, std.time.ns_per_s) /
|
||||||
|
((@intToFloat(f64, std.time.ns_per_s) / @intToFloat(f64, clock_rate)) * @intToFloat(f64, cycles_per_frame));
|
||||||
/// The # of nanoseconds a frame should take
|
|
||||||
const frame_period = (std.time.ns_per_s * cycles_per_frame) / clock_rate;
|
|
||||||
|
|
||||||
/// Exact Value: 59.7275005696Hz
|
|
||||||
/// The inverse of the frame period
|
|
||||||
pub const frame_rate: f64 = @intToFloat(f64, clock_rate) / cycles_per_frame;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.Emulation);
|
const log = std.log.scoped(.Emulation);
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ const RunKind = enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn run(quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: *FpsTracker) void {
|
pub fn run(quit: *Atomic(bool), scheduler: *Scheduler, cpu: *Arm7tdmi, tracker: *FpsTracker) void {
|
||||||
const audio_sync = config.config().guest.audio_sync and !config.config().host.mute;
|
const audio_sync = config.config().guest.audio_sync;
|
||||||
if (audio_sync) log.info("Audio sync enabled", .{});
|
if (audio_sync) log.info("Audio sync enabled", .{});
|
||||||
|
|
||||||
if (config.config().guest.video_sync) {
|
if (config.config().guest.video_sync) {
|
||||||
|
@ -105,7 +105,6 @@ pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
|
fn audioSync(audio_sync: bool, stream: *SDL.SDL_AudioStream, is_buffer_full: *bool) void {
|
||||||
comptime std.debug.assert(@import("../platform.zig").sample_format == SDL.AUDIO_U16);
|
|
||||||
const sample_size = 2 * @sizeOf(u16);
|
const sample_size = 2 * @sizeOf(u16);
|
||||||
const max_buf_size: c_int = 0x400;
|
const max_buf_size: c_int = 0x400;
|
||||||
|
|
||||||
|
@ -133,10 +132,11 @@ fn videoSync(timer: *Timer, wake_time: u64) u64 {
|
||||||
|
|
||||||
// TODO: Better sleep impl?
|
// TODO: Better sleep impl?
|
||||||
fn sleep(timer: *Timer, wake_time: u64) ?u64 {
|
fn sleep(timer: *Timer, wake_time: u64) ?u64 {
|
||||||
|
// const step = std.time.ns_per_ms * 10; // 10ms
|
||||||
const timestamp = timer.read();
|
const timestamp = timer.read();
|
||||||
|
|
||||||
// ns_late is non zero if we are late.
|
// ns_late is non zero if we are late.
|
||||||
var ns_late = timestamp -| wake_time;
|
const ns_late = timestamp -| wake_time;
|
||||||
|
|
||||||
// If we're more than a frame late, skip the rest of this loop
|
// If we're more than a frame late, skip the rest of this loop
|
||||||
// Recalculate what our new wake time should be so that we can
|
// Recalculate what our new wake time should be so that we can
|
||||||
|
@ -144,18 +144,15 @@ fn sleep(timer: *Timer, wake_time: u64) ?u64 {
|
||||||
if (ns_late > frame_period) return timestamp + frame_period;
|
if (ns_late > frame_period) return timestamp + frame_period;
|
||||||
const sleep_for = frame_period - ns_late;
|
const sleep_for = frame_period - ns_late;
|
||||||
|
|
||||||
const step = 2 * std.time.ns_per_ms; // Granularity of 2ms
|
// // Employ several sleep calls in periods of 10ms
|
||||||
const times = sleep_for / step;
|
// // By doing this the behaviour should average out to be
|
||||||
var i: usize = 0;
|
// // more consistent
|
||||||
|
// const loop_count = sleep_for / step; // How many groups of 10ms
|
||||||
|
|
||||||
while (i < times) : (i += 1) {
|
// var i: usize = 0;
|
||||||
std.time.sleep(step);
|
// while (i < loop_count) : (i += 1) std.time.sleep(step);
|
||||||
|
|
||||||
// Upon wakeup, check to see if this particular sleep was longer than expected
|
std.time.sleep(sleep_for);
|
||||||
// if so we should exit early, but probably not skip a whole frame period
|
|
||||||
ns_late = timer.read() -| wake_time;
|
|
||||||
if (ns_late > frame_period) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
297
src/core/ppu.zig
297
src/core/ppu.zig
|
@ -1,7 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const io = @import("bus/io.zig");
|
const io = @import("bus/io.zig");
|
||||||
const util = @import("../util.zig");
|
|
||||||
|
|
||||||
const Bit = @import("bitfield").Bit;
|
const Bit = @import("bitfield").Bit;
|
||||||
const Bitfield = @import("bitfield").Bitfield;
|
const Bitfield = @import("bitfield").Bitfield;
|
||||||
const dma = @import("bus/dma.zig");
|
const dma = @import("bus/dma.zig");
|
||||||
|
@ -15,230 +13,12 @@ const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||||
const FrameBuffer = @import("../util.zig").FrameBuffer;
|
const FrameBuffer = @import("../util.zig").FrameBuffer;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.PPU);
|
const log = std.log.scoped(.Ppu);
|
||||||
|
|
||||||
const getHalf = util.getHalf;
|
|
||||||
const setHalf = util.setHalf;
|
|
||||||
const setQuart = util.setQuart;
|
|
||||||
const pollDmaOnBlank = @import("bus/dma.zig").pollDmaOnBlank;
|
|
||||||
|
|
||||||
pub const width = 240;
|
pub const width = 240;
|
||||||
pub const height = 160;
|
pub const height = 160;
|
||||||
pub const framebuf_pitch = width * @sizeOf(u32);
|
pub const framebuf_pitch = width * @sizeOf(u32);
|
||||||
|
|
||||||
pub fn read(comptime T: type, ppu: *const Ppu, addr: u32) ?T {
|
|
||||||
const byte_addr = @truncate(u8, addr);
|
|
||||||
|
|
||||||
return switch (T) {
|
|
||||||
u32 => switch (byte_addr) {
|
|
||||||
0x00 => ppu.dispcnt.raw, // Green Swap is in high half-word
|
|
||||||
0x04 => @as(T, ppu.vcount.raw) << 16 | ppu.dispstat.raw,
|
|
||||||
0x08 => @as(T, ppu.bg[1].bg1Cnt()) << 16 | ppu.bg[0].bg0Cnt(),
|
|
||||||
0x0C => @as(T, ppu.bg[3].cnt.raw) << 16 | ppu.bg[2].cnt.raw,
|
|
||||||
0x10, 0x14, 0x18, 0x1C => null, // BGXHOFS/VOFS
|
|
||||||
0x20, 0x24, 0x28, 0x2C => null, // BG2 Rot/Scaling
|
|
||||||
0x30, 0x34, 0x38, 0x3C => null, // BG3 Rot/Scaling
|
|
||||||
0x40, 0x44 => null, // WINXH/V Registers
|
|
||||||
0x48 => @as(T, ppu.win.getOut()) << 16 | ppu.win.getIn(),
|
|
||||||
0x4C => null, // MOSAIC, undefined in high byte
|
|
||||||
0x50 => @as(T, ppu.bld.getAlpha()) << 16 | ppu.bld.getCnt(),
|
|
||||||
0x54 => null, // BLDY, undefined in high half-wrd
|
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
|
||||||
u16 => switch (byte_addr) {
|
|
||||||
0x00 => ppu.dispcnt.raw,
|
|
||||||
0x02 => null, // Green Swap
|
|
||||||
0x04 => ppu.dispstat.raw,
|
|
||||||
0x06 => ppu.vcount.raw,
|
|
||||||
0x08 => ppu.bg[0].bg0Cnt(),
|
|
||||||
0x0A => ppu.bg[1].bg1Cnt(),
|
|
||||||
0x0C => ppu.bg[2].cnt.raw,
|
|
||||||
0x0E => ppu.bg[3].cnt.raw,
|
|
||||||
0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E => null, // BGXHOFS/VOFS
|
|
||||||
0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E => null, // BG2 Rot/Scaling
|
|
||||||
0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E => null, // BG3 Rot/Scaling
|
|
||||||
0x40, 0x42, 0x44, 0x46 => null, // WINXH/V Registers
|
|
||||||
0x48 => ppu.win.getIn(),
|
|
||||||
0x4A => ppu.win.getOut(),
|
|
||||||
0x4C => null, // MOSAIC
|
|
||||||
0x4E => null,
|
|
||||||
0x50 => ppu.bld.getCnt(),
|
|
||||||
0x52 => ppu.bld.getAlpha(),
|
|
||||||
0x54 => null, // BLDY
|
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
|
||||||
u8 => switch (byte_addr) {
|
|
||||||
0x00, 0x01 => @truncate(T, ppu.dispcnt.raw >> getHalf(byte_addr)),
|
|
||||||
0x02, 0x03 => null,
|
|
||||||
0x04, 0x05 => @truncate(T, ppu.dispstat.raw >> getHalf(byte_addr)),
|
|
||||||
0x06, 0x07 => @truncate(T, ppu.vcount.raw >> getHalf(byte_addr)),
|
|
||||||
0x08, 0x09 => @truncate(T, ppu.bg[0].bg0Cnt() >> getHalf(byte_addr)),
|
|
||||||
0x0A, 0x0B => @truncate(T, ppu.bg[1].bg1Cnt() >> getHalf(byte_addr)),
|
|
||||||
0x0C, 0x0D => @truncate(T, ppu.bg[2].cnt.raw >> getHalf(byte_addr)),
|
|
||||||
0x0E, 0x0F => @truncate(T, ppu.bg[3].cnt.raw >> getHalf(byte_addr)),
|
|
||||||
0x10...0x1F => null, // BGXHOFS/VOFS
|
|
||||||
0x20...0x2F => null, // BG2 Rot/Scaling
|
|
||||||
0x30...0x3F => null, // BG3 Rot/Scaling
|
|
||||||
0x40...0x47 => null, // WINXH/V Registers
|
|
||||||
0x48, 0x49 => @truncate(T, ppu.win.getIn() >> getHalf(byte_addr)),
|
|
||||||
0x4A, 0x4B => @truncate(T, ppu.win.getOut() >> getHalf(byte_addr)),
|
|
||||||
0x4C, 0x4D => null, // MOSAIC
|
|
||||||
0x4E, 0x4F => null,
|
|
||||||
0x50, 0x51 => @truncate(T, ppu.bld.getCnt() >> getHalf(byte_addr)),
|
|
||||||
0x52, 0x53 => @truncate(T, ppu.bld.getAlpha() >> getHalf(byte_addr)),
|
|
||||||
0x54, 0x55 => null, // BLDY
|
|
||||||
else => util.io.read.err(T, log, "unexpected {} read from 0x{X:0>8}", .{ T, addr }),
|
|
||||||
},
|
|
||||||
else => @compileError("PPU: Unsupported read width"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(comptime T: type, ppu: *Ppu, addr: u32, value: T) void {
|
|
||||||
const byte_addr = @truncate(u8, addr); // prefixed with 0x0400_00
|
|
||||||
|
|
||||||
switch (T) {
|
|
||||||
u32 => switch (byte_addr) {
|
|
||||||
0x00 => ppu.dispcnt.raw = @truncate(u16, value),
|
|
||||||
0x04 => {
|
|
||||||
ppu.dispstat.set(@truncate(u16, value));
|
|
||||||
ppu.vcount.raw = @truncate(u16, value >> 16);
|
|
||||||
},
|
|
||||||
0x08 => ppu.setAdjCnts(0, value),
|
|
||||||
0x0C => ppu.setAdjCnts(2, value),
|
|
||||||
|
|
||||||
0x10 => ppu.setBgOffsets(0, value),
|
|
||||||
0x14 => ppu.setBgOffsets(1, value),
|
|
||||||
0x18 => ppu.setBgOffsets(2, value),
|
|
||||||
0x1C => ppu.setBgOffsets(3, value),
|
|
||||||
|
|
||||||
0x20 => ppu.aff_bg[0].writePaPb(value),
|
|
||||||
0x24 => ppu.aff_bg[0].writePcPd(value),
|
|
||||||
0x28 => ppu.aff_bg[0].setX(ppu.dispstat.vblank.read(), value),
|
|
||||||
0x2C => ppu.aff_bg[0].setY(ppu.dispstat.vblank.read(), value),
|
|
||||||
|
|
||||||
0x30 => ppu.aff_bg[1].writePaPb(value),
|
|
||||||
0x34 => ppu.aff_bg[1].writePcPd(value),
|
|
||||||
0x38 => ppu.aff_bg[1].setX(ppu.dispstat.vblank.read(), value),
|
|
||||||
0x3C => ppu.aff_bg[1].setY(ppu.dispstat.vblank.read(), value),
|
|
||||||
|
|
||||||
0x40 => ppu.win.setH(value),
|
|
||||||
0x44 => ppu.win.setV(value),
|
|
||||||
0x48 => ppu.win.setIo(value),
|
|
||||||
0x4C => log.debug("Wrote 0x{X:0>8} to MOSAIC", .{value}),
|
|
||||||
|
|
||||||
0x50 => {
|
|
||||||
ppu.bld.cnt.raw = @truncate(u16, value);
|
|
||||||
ppu.bld.alpha.raw = @truncate(u16, value >> 16);
|
|
||||||
},
|
|
||||||
0x54 => ppu.bld.y.raw = @truncate(u16, value),
|
|
||||||
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) {
|
|
||||||
0x00 => ppu.dispcnt.raw = value,
|
|
||||||
0x02 => {}, // Green Swap
|
|
||||||
0x04 => ppu.dispstat.set(value),
|
|
||||||
0x06 => {}, // VCOUNT
|
|
||||||
|
|
||||||
0x08 => ppu.bg[0].cnt.raw = value,
|
|
||||||
0x0A => ppu.bg[1].cnt.raw = value,
|
|
||||||
0x0C => ppu.bg[2].cnt.raw = value,
|
|
||||||
0x0E => ppu.bg[3].cnt.raw = value,
|
|
||||||
|
|
||||||
0x10 => ppu.bg[0].hofs.raw = value, // TODO: Don't write out every HOFS / VOFS?
|
|
||||||
0x12 => ppu.bg[0].vofs.raw = value,
|
|
||||||
0x14 => ppu.bg[1].hofs.raw = value,
|
|
||||||
0x16 => ppu.bg[1].vofs.raw = value,
|
|
||||||
0x18 => ppu.bg[2].hofs.raw = value,
|
|
||||||
0x1A => ppu.bg[2].vofs.raw = value,
|
|
||||||
0x1C => ppu.bg[3].hofs.raw = value,
|
|
||||||
0x1E => ppu.bg[3].vofs.raw = value,
|
|
||||||
|
|
||||||
0x20 => ppu.aff_bg[0].pa = @bitCast(i16, value),
|
|
||||||
0x22 => ppu.aff_bg[0].pb = @bitCast(i16, value),
|
|
||||||
0x24 => ppu.aff_bg[0].pc = @bitCast(i16, value),
|
|
||||||
0x26 => ppu.aff_bg[0].pd = @bitCast(i16, value),
|
|
||||||
0x28, 0x2A => ppu.aff_bg[0].x = @bitCast(i32, setHalf(u32, @bitCast(u32, ppu.aff_bg[0].x), byte_addr, value)),
|
|
||||||
0x2C, 0x2E => ppu.aff_bg[0].y = @bitCast(i32, setHalf(u32, @bitCast(u32, ppu.aff_bg[0].y), byte_addr, value)),
|
|
||||||
|
|
||||||
0x30 => ppu.aff_bg[1].pa = @bitCast(i16, value),
|
|
||||||
0x32 => ppu.aff_bg[1].pb = @bitCast(i16, value),
|
|
||||||
0x34 => ppu.aff_bg[1].pc = @bitCast(i16, value),
|
|
||||||
0x36 => ppu.aff_bg[1].pd = @bitCast(i16, value),
|
|
||||||
0x38, 0x3A => ppu.aff_bg[1].x = @bitCast(i32, setHalf(u32, @bitCast(u32, ppu.aff_bg[1].x), byte_addr, value)),
|
|
||||||
0x3C, 0x3E => ppu.aff_bg[1].y = @bitCast(i32, setHalf(u32, @bitCast(u32, ppu.aff_bg[1].y), byte_addr, value)),
|
|
||||||
|
|
||||||
0x40 => ppu.win.h[0].raw = value,
|
|
||||||
0x42 => ppu.win.h[1].raw = value,
|
|
||||||
0x44 => ppu.win.v[0].raw = value,
|
|
||||||
0x46 => ppu.win.v[1].raw = value,
|
|
||||||
0x48 => ppu.win.in.raw = value,
|
|
||||||
0x4A => ppu.win.out.raw = value,
|
|
||||||
0x4C => log.debug("Wrote 0x{X:0>4} to MOSAIC", .{value}),
|
|
||||||
0x4E => {},
|
|
||||||
|
|
||||||
0x50 => ppu.bld.cnt.raw = value,
|
|
||||||
0x52 => ppu.bld.alpha.raw = value,
|
|
||||||
0x54 => ppu.bld.y.raw = value,
|
|
||||||
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) {
|
|
||||||
0x00, 0x01 => ppu.dispcnt.raw = setHalf(u16, ppu.dispcnt.raw, byte_addr, value),
|
|
||||||
0x02, 0x03 => {}, // Green Swap
|
|
||||||
0x04, 0x05 => ppu.dispstat.set(setHalf(u16, ppu.dispstat.raw, byte_addr, value)),
|
|
||||||
0x06, 0x07 => {}, // VCOUNT
|
|
||||||
|
|
||||||
// BGXCNT
|
|
||||||
0x08, 0x09 => ppu.bg[0].cnt.raw = setHalf(u16, ppu.bg[0].cnt.raw, byte_addr, value),
|
|
||||||
0x0A, 0x0B => ppu.bg[1].cnt.raw = setHalf(u16, ppu.bg[1].cnt.raw, byte_addr, value),
|
|
||||||
0x0C, 0x0D => ppu.bg[2].cnt.raw = setHalf(u16, ppu.bg[2].cnt.raw, byte_addr, value),
|
|
||||||
0x0E, 0x0F => ppu.bg[3].cnt.raw = setHalf(u16, ppu.bg[3].cnt.raw, byte_addr, value),
|
|
||||||
|
|
||||||
// BGX HOFS/VOFS
|
|
||||||
0x10, 0x11 => ppu.bg[0].hofs.raw = setHalf(u16, ppu.bg[0].hofs.raw, byte_addr, value),
|
|
||||||
0x12, 0x13 => ppu.bg[0].vofs.raw = setHalf(u16, ppu.bg[0].vofs.raw, byte_addr, value),
|
|
||||||
0x14, 0x15 => ppu.bg[1].hofs.raw = setHalf(u16, ppu.bg[1].hofs.raw, byte_addr, value),
|
|
||||||
0x16, 0x17 => ppu.bg[1].vofs.raw = setHalf(u16, ppu.bg[1].vofs.raw, byte_addr, value),
|
|
||||||
0x18, 0x19 => ppu.bg[2].hofs.raw = setHalf(u16, ppu.bg[2].hofs.raw, byte_addr, value),
|
|
||||||
0x1A, 0x1B => ppu.bg[2].vofs.raw = setHalf(u16, ppu.bg[2].vofs.raw, byte_addr, value),
|
|
||||||
0x1C, 0x1D => ppu.bg[3].hofs.raw = setHalf(u16, ppu.bg[3].hofs.raw, byte_addr, value),
|
|
||||||
0x1E, 0x1F => ppu.bg[3].vofs.raw = setHalf(u16, ppu.bg[3].vofs.raw, byte_addr, value),
|
|
||||||
|
|
||||||
// BG2 Rot/Scaling
|
|
||||||
0x20, 0x21 => ppu.aff_bg[0].pa = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[0].pa), byte_addr, value)),
|
|
||||||
0x22, 0x23 => ppu.aff_bg[0].pb = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[0].pb), byte_addr, value)),
|
|
||||||
0x24, 0x25 => ppu.aff_bg[0].pc = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[0].pc), byte_addr, value)),
|
|
||||||
0x26, 0x27 => ppu.aff_bg[0].pd = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[0].pd), byte_addr, value)),
|
|
||||||
0x28, 0x29, 0x2A, 0x2B => ppu.aff_bg[0].x = @bitCast(i32, setQuart(@bitCast(u32, ppu.aff_bg[0].x), byte_addr, value)),
|
|
||||||
0x2C, 0x2D, 0x2E, 0x2F => ppu.aff_bg[0].y = @bitCast(i32, setQuart(@bitCast(u32, ppu.aff_bg[0].y), byte_addr, value)),
|
|
||||||
|
|
||||||
// BG3 Rot/Scaling
|
|
||||||
0x30, 0x31 => ppu.aff_bg[1].pa = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[1].pa), byte_addr, value)),
|
|
||||||
0x32, 0x33 => ppu.aff_bg[1].pb = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[1].pb), byte_addr, value)),
|
|
||||||
0x34, 0x35 => ppu.aff_bg[1].pc = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[1].pc), byte_addr, value)),
|
|
||||||
0x36, 0x37 => ppu.aff_bg[1].pd = @bitCast(i16, setHalf(u16, @bitCast(u16, ppu.aff_bg[1].pd), byte_addr, value)),
|
|
||||||
0x38, 0x39, 0x3A, 0x3B => ppu.aff_bg[1].x = @bitCast(i32, setQuart(@bitCast(u32, ppu.aff_bg[1].x), byte_addr, value)),
|
|
||||||
0x3C, 0x3D, 0x3E, 0x3F => ppu.aff_bg[1].y = @bitCast(i32, setQuart(@bitCast(u32, ppu.aff_bg[1].y), byte_addr, value)),
|
|
||||||
|
|
||||||
// Window
|
|
||||||
0x40, 0x41 => ppu.win.h[0].raw = setHalf(u16, ppu.win.h[0].raw, byte_addr, value),
|
|
||||||
0x42, 0x43 => ppu.win.h[1].raw = setHalf(u16, ppu.win.h[1].raw, byte_addr, value),
|
|
||||||
0x44, 0x45 => ppu.win.v[0].raw = setHalf(u16, ppu.win.v[0].raw, byte_addr, value),
|
|
||||||
0x46, 0x47 => ppu.win.v[1].raw = setHalf(u16, ppu.win.v[1].raw, byte_addr, value),
|
|
||||||
0x48, 0x49 => ppu.win.in.raw = setHalf(u16, ppu.win.in.raw, byte_addr, value),
|
|
||||||
0x4A, 0x4B => ppu.win.out.raw = setHalf(u16, ppu.win.out.raw, byte_addr, value),
|
|
||||||
0x4C, 0x4D => log.debug("Wrote 0x{X:0>2} to MOSAIC", .{value}),
|
|
||||||
0x4E, 0x4F => {},
|
|
||||||
|
|
||||||
// Blending
|
|
||||||
0x50, 0x51 => ppu.bld.cnt.raw = setHalf(u16, ppu.bld.cnt.raw, byte_addr, value),
|
|
||||||
0x52, 0x53 => ppu.bld.alpha.raw = setHalf(u16, ppu.bld.alpha.raw, byte_addr, value),
|
|
||||||
0x54, 0x55 => ppu.bld.y.raw = setHalf(u16, ppu.bld.y.raw, byte_addr, value),
|
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
|
|
||||||
},
|
|
||||||
else => @compileError("PPU: Unsupported write width"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Ppu = struct {
|
pub const Ppu = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -252,7 +32,9 @@ pub const Ppu = struct {
|
||||||
dispstat: io.DisplayStatus,
|
dispstat: io.DisplayStatus,
|
||||||
vcount: io.VCount,
|
vcount: io.VCount,
|
||||||
|
|
||||||
bld: Blend,
|
bldcnt: io.BldCnt,
|
||||||
|
bldalpha: io.BldAlpha,
|
||||||
|
bldy: io.BldY,
|
||||||
|
|
||||||
vram: Vram,
|
vram: Vram,
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
|
@ -283,10 +65,12 @@ pub const Ppu = struct {
|
||||||
.win = Window.init(),
|
.win = Window.init(),
|
||||||
.bg = [_]Background{Background.init()} ** 4,
|
.bg = [_]Background{Background.init()} ** 4,
|
||||||
.aff_bg = [_]AffineBackground{AffineBackground.init()} ** 2,
|
.aff_bg = [_]AffineBackground{AffineBackground.init()} ** 2,
|
||||||
.bld = Blend.create(),
|
|
||||||
.dispcnt = .{ .raw = 0x0000 },
|
.dispcnt = .{ .raw = 0x0000 },
|
||||||
.dispstat = .{ .raw = 0x0000 },
|
.dispstat = .{ .raw = 0x0000 },
|
||||||
.vcount = .{ .raw = 0x0000 },
|
.vcount = .{ .raw = 0x0000 },
|
||||||
|
.bldcnt = .{ .raw = 0x0000 },
|
||||||
|
.bldalpha = .{ .raw = 0x0000 },
|
||||||
|
.bldy = .{ .raw = 0x0000 },
|
||||||
|
|
||||||
.scanline = try Scanline.init(allocator),
|
.scanline = try Scanline.init(allocator),
|
||||||
.scanline_sprites = sprites,
|
.scanline_sprites = sprites,
|
||||||
|
@ -381,7 +165,7 @@ pub const Ppu = struct {
|
||||||
const x = (sprite.x() +% i) % width;
|
const x = (sprite.x() +% i) % width;
|
||||||
const ix = @bitCast(i9, x);
|
const ix = @bitCast(i9, x);
|
||||||
|
|
||||||
if (!shouldDrawSprite(self.bld.cnt, &self.scanline, x)) continue;
|
if (!shouldDrawSprite(self.bldcnt, &self.scanline, x)) continue;
|
||||||
|
|
||||||
const sprite_start = sprite.x();
|
const sprite_start = sprite.x();
|
||||||
const isprite_start = @bitCast(i9, sprite_start);
|
const isprite_start = @bitCast(i9, sprite_start);
|
||||||
|
@ -410,7 +194,7 @@ pub const Ppu = struct {
|
||||||
// Sprite Palette starts at 0x0500_0200
|
// Sprite Palette starts at 0x0500_0200
|
||||||
if (pal_id != 0) {
|
if (pal_id != 0) {
|
||||||
const bgr555 = self.palette.read(u16, 0x200 + pal_id * 2);
|
const bgr555 = self.palette.read(u16, 0x200 + pal_id * 2);
|
||||||
copyToSpriteBuffer(self.bld.cnt, &self.scanline, x, bgr555);
|
copyToSpriteBuffer(self.bldcnt, &self.scanline, x, bgr555);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,7 +215,7 @@ pub const Ppu = struct {
|
||||||
const x = (sprite.x() +% i) % width;
|
const x = (sprite.x() +% i) % width;
|
||||||
const ix = @bitCast(i9, x);
|
const ix = @bitCast(i9, x);
|
||||||
|
|
||||||
if (!shouldDrawSprite(self.bld.cnt, &self.scanline, x)) continue;
|
if (!shouldDrawSprite(self.bldcnt, &self.scanline, x)) continue;
|
||||||
|
|
||||||
const sprite_start = sprite.x();
|
const sprite_start = sprite.x();
|
||||||
const isprite_start = @bitCast(i9, sprite_start);
|
const isprite_start = @bitCast(i9, sprite_start);
|
||||||
|
@ -466,7 +250,7 @@ pub const Ppu = struct {
|
||||||
// Sprite Palette starts at 0x0500_0200
|
// Sprite Palette starts at 0x0500_0200
|
||||||
if (pal_id != 0) {
|
if (pal_id != 0) {
|
||||||
const bgr555 = self.palette.read(u16, 0x200 + pal_id * 2);
|
const bgr555 = self.palette.read(u16, 0x200 + pal_id * 2);
|
||||||
copyToSpriteBuffer(self.bld.cnt, &self.scanline, x, bgr555);
|
copyToSpriteBuffer(self.bldcnt, &self.scanline, x, bgr555);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -715,11 +499,11 @@ pub const Ppu = struct {
|
||||||
|
|
||||||
fn getBgr555(self: *Self, maybe_top: ?u16, maybe_btm: ?u16) u16 {
|
fn getBgr555(self: *Self, maybe_top: ?u16, maybe_btm: ?u16) u16 {
|
||||||
if (maybe_btm) |btm| {
|
if (maybe_btm) |btm| {
|
||||||
return switch (self.bld.cnt.mode.read()) {
|
return switch (self.bldcnt.mode.read()) {
|
||||||
0b00 => if (maybe_top) |top| top else btm,
|
0b00 => if (maybe_top) |top| top else btm,
|
||||||
0b01 => if (maybe_top) |top| alphaBlend(btm, top, self.bld.alpha) else btm,
|
0b01 => if (maybe_top) |top| alphaBlend(btm, top, self.bldalpha) else btm,
|
||||||
0b10 => blk: {
|
0b10 => blk: {
|
||||||
const evy: u16 = self.bld.y.evy.read();
|
const evy: u16 = self.bldy.evy.read();
|
||||||
|
|
||||||
const r = btm & 0x1F;
|
const r = btm & 0x1F;
|
||||||
const g = (btm >> 5) & 0x1F;
|
const g = (btm >> 5) & 0x1F;
|
||||||
|
@ -732,7 +516,7 @@ pub const Ppu = struct {
|
||||||
break :blk (bld_b << 10) | (bld_g << 5) | bld_r;
|
break :blk (bld_b << 10) | (bld_g << 5) | bld_r;
|
||||||
},
|
},
|
||||||
0b11 => blk: {
|
0b11 => blk: {
|
||||||
const evy: u16 = self.bld.y.evy.read();
|
const evy: u16 = self.bldy.evy.read();
|
||||||
|
|
||||||
const btm_r = btm & 0x1F;
|
const btm_r = btm & 0x1F;
|
||||||
const btm_g = (btm >> 5) & 0x1F;
|
const btm_g = (btm >> 5) & 0x1F;
|
||||||
|
@ -752,9 +536,9 @@ pub const Ppu = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copyToBackgroundBuffer(self: *Self, comptime n: u2, bounds: ?WindowBounds, i: usize, bgr555: u16) void {
|
fn copyToBackgroundBuffer(self: *Self, comptime n: u2, bounds: ?WindowBounds, i: usize, bgr555: u16) void {
|
||||||
if (self.bld.cnt.mode.read() != 0b00) {
|
if (self.bldcnt.mode.read() != 0b00) {
|
||||||
// Standard Alpha Blending
|
// Standard Alpha Blending
|
||||||
const a_layers = self.bld.cnt.layer_a.read();
|
const a_layers = self.bldcnt.layer_a.read();
|
||||||
const is_blend_enabled = (a_layers >> n) & 1 == 1;
|
const is_blend_enabled = (a_layers >> n) & 1 == 1;
|
||||||
|
|
||||||
// If Alpha Blending is enabled and we've found an eligible layer for
|
// If Alpha Blending is enabled and we've found an eligible layer for
|
||||||
|
@ -815,9 +599,9 @@ pub const Ppu = struct {
|
||||||
|
|
||||||
// If Alpha Blending isn't enabled, then we've already found a higher prio
|
// If Alpha Blending isn't enabled, then we've already found a higher prio
|
||||||
// pixel, we can return early
|
// pixel, we can return early
|
||||||
if (self.bld.cnt.mode.read() != 0b01) return false;
|
if (self.bldcnt.mode.read() != 0b01) return false;
|
||||||
|
|
||||||
const b_layers = self.bld.cnt.layer_b.read();
|
const b_layers = self.bldcnt.layer_b.read();
|
||||||
|
|
||||||
const win_part = if (bounds) |win| blk: {
|
const win_part = if (bounds) |win| blk: {
|
||||||
// Window Enabled
|
// Window Enabled
|
||||||
|
@ -928,30 +712,6 @@ pub const Ppu = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Blend = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
cnt: io.BldCnt,
|
|
||||||
alpha: io.BldAlpha,
|
|
||||||
y: io.BldY,
|
|
||||||
|
|
||||||
pub fn create() Self {
|
|
||||||
return .{
|
|
||||||
.cnt = .{ .raw = 0x000 },
|
|
||||||
.alpha = .{ .raw = 0x000 },
|
|
||||||
.y = .{ .raw = 0x000 },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getCnt(self: *const Self) u16 {
|
|
||||||
return self.cnt.raw & 0x3FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getAlpha(self: *const Self) u16 {
|
|
||||||
return self.alpha.raw & 0x1F1F;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Window = struct {
|
const Window = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -971,14 +731,6 @@ const Window = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getIn(self: *const Self) u16 {
|
|
||||||
return self.in.raw & 0x3F3F;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getOut(self: *const Self) u16 {
|
|
||||||
return self.out.raw & 0x3F3F;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inRange(self: *const Self, comptime id: u1, x: u9, y: u8) bool {
|
fn inRange(self: *const Self, comptime id: u1, x: u9, y: u8) bool {
|
||||||
const h = self.h[id];
|
const h = self.h[id];
|
||||||
const v = self.v[id];
|
const v = self.v[id];
|
||||||
|
@ -1031,17 +783,6 @@ const Background = struct {
|
||||||
.vofs = .{ .raw = 0x0000 },
|
.vofs = .{ .raw = 0x0000 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For whatever reason, some higher bits of BG0CNT
|
|
||||||
/// are masked out
|
|
||||||
pub inline fn bg0Cnt(self: *const Self) u16 {
|
|
||||||
return self.cnt.raw & 0xDFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// BG1CNT inherits the same mask as BG0CNTs
|
|
||||||
pub inline fn bg1Cnt(self: *const Self) u16 {
|
|
||||||
return self.bg0Cnt();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const AffineBackground = struct {
|
const AffineBackground = struct {
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub fn deinit(self: *Self) void {
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mirror(address: usize) usize {
|
fn mirror(address: usize) usize {
|
||||||
// Mirrored in steps of 128K (64K + 32K + 32K) (abcc)
|
// Mirrored in steps of 128K (64K + 32K + 32K) (abcc)
|
||||||
const addr = address & 0x1FFFF;
|
const addr = address & 0x1FFFF;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Bus = @import("Bus.zig");
|
||||||
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("cpu.zig").Arm7tdmi;
|
||||||
const Clock = @import("bus/gpio.zig").Clock;
|
const Clock = @import("bus/gpio.zig").Clock;
|
||||||
|
|
||||||
|
@ -46,7 +47,10 @@ pub const Scheduler = struct {
|
||||||
},
|
},
|
||||||
.TimerOverflow => |id| {
|
.TimerOverflow => |id| {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
inline 0...3 => |idx| cpu.bus.tim[idx].onTimerExpire(cpu, late),
|
0 => cpu.bus.tim[0].onTimerExpire(cpu, late),
|
||||||
|
1 => cpu.bus.tim[1].onTimerExpire(cpu, late),
|
||||||
|
2 => cpu.bus.tim[2].onTimerExpire(cpu, late),
|
||||||
|
3 => cpu.bus.tim[3].onTimerExpire(cpu, late),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.ApuChannel => |id| {
|
.ApuChannel => |id| {
|
||||||
|
|
|
@ -126,7 +126,7 @@ fn ensureDirectoriesExist(data_path: []const u8) !void {
|
||||||
// (~/.local/share/zba/save for linux, ??? for macOS)
|
// (~/.local/share/zba/save for linux, ??? for macOS)
|
||||||
|
|
||||||
// Will recursively create directories
|
// Will recursively create directories
|
||||||
try dir.makePath("zba" ++ std.fs.path.sep_str ++ "save");
|
try dir.makePath("zba" ++ [_]u8{std.fs.path.sep} ++ "save");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) []const u8 {
|
fn romPath(result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) []const u8 {
|
||||||
|
|
|
@ -11,12 +11,10 @@ const FpsTracker = @import("util.zig").FpsTracker;
|
||||||
|
|
||||||
const span = @import("util.zig").span;
|
const span = @import("util.zig").span;
|
||||||
|
|
||||||
|
const pitch = @import("core/ppu.zig").framebuf_pitch;
|
||||||
const gba_width = @import("core/ppu.zig").width;
|
const gba_width = @import("core/ppu.zig").width;
|
||||||
const gba_height = @import("core/ppu.zig").height;
|
const gba_height = @import("core/ppu.zig").height;
|
||||||
|
|
||||||
pub const sample_rate = 1 << 15;
|
|
||||||
pub const sample_format = SDL.AUDIO_U16;
|
|
||||||
|
|
||||||
const default_title: []const u8 = "ZBA";
|
const default_title: []const u8 = "ZBA";
|
||||||
|
|
||||||
pub const Gui = struct {
|
pub const Gui = struct {
|
||||||
|
@ -202,10 +200,7 @@ pub const Gui = struct {
|
||||||
SDL.SDLK_s => io.keyinput.shoulder_r.set(),
|
SDL.SDLK_s => io.keyinput.shoulder_r.set(),
|
||||||
SDL.SDLK_RETURN => io.keyinput.start.set(),
|
SDL.SDLK_RETURN => io.keyinput.start.set(),
|
||||||
SDL.SDLK_RSHIFT => io.keyinput.select.set(),
|
SDL.SDLK_RSHIFT => io.keyinput.select.set(),
|
||||||
SDL.SDLK_i => {
|
SDL.SDLK_i => log.err("Sample Count: {}", .{@intCast(u32, SDL.SDL_AudioStreamAvailable(cpu.bus.apu.stream)) / (2 * @sizeOf(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))});
|
|
||||||
},
|
|
||||||
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
|
// Dump IWRAM to file
|
||||||
|
@ -258,6 +253,7 @@ pub const Gui = struct {
|
||||||
const Audio = struct {
|
const Audio = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
const log = std.log.scoped(.PlatformAudio);
|
const log = std.log.scoped(.PlatformAudio);
|
||||||
|
const sample_rate = @import("core/apu.zig").host_sample_rate;
|
||||||
|
|
||||||
device: SDL.SDL_AudioDeviceID,
|
device: SDL.SDL_AudioDeviceID,
|
||||||
|
|
||||||
|
@ -265,22 +261,16 @@ const Audio = struct {
|
||||||
var have: SDL.SDL_AudioSpec = undefined;
|
var have: SDL.SDL_AudioSpec = undefined;
|
||||||
var want: SDL.SDL_AudioSpec = std.mem.zeroes(SDL.SDL_AudioSpec);
|
var want: SDL.SDL_AudioSpec = std.mem.zeroes(SDL.SDL_AudioSpec);
|
||||||
want.freq = sample_rate;
|
want.freq = sample_rate;
|
||||||
want.format = sample_format;
|
want.format = SDL.AUDIO_U16;
|
||||||
want.channels = 2;
|
want.channels = 2;
|
||||||
want.samples = 0x100;
|
want.samples = 0x100;
|
||||||
want.callback = Self.callback;
|
want.callback = Self.callback;
|
||||||
want.userdata = apu;
|
want.userdata = apu;
|
||||||
|
|
||||||
std.debug.assert(sample_format == SDL.AUDIO_U16);
|
|
||||||
log.info("Host Sample Rate: {}Hz, Host Format: SDL.AUDIO_U16", .{sample_rate});
|
|
||||||
|
|
||||||
const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
|
const device = SDL.SDL_OpenAudioDevice(null, 0, &want, &have, 0);
|
||||||
if (device == 0) panic();
|
if (device == 0) panic();
|
||||||
|
|
||||||
if (!config.config().host.mute) {
|
SDL.SDL_PauseAudioDevice(device, 0); // Unpause Audio
|
||||||
SDL.SDL_PauseAudioDevice(device, 0); // Unpause Audio
|
|
||||||
log.info("Unpaused Device", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .device = device };
|
return .{ .device = device };
|
||||||
}
|
}
|
||||||
|
@ -291,10 +281,18 @@ const Audio = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void {
|
export fn callback(userdata: ?*anyopaque, stream: [*c]u8, len: c_int) void {
|
||||||
const T = *Apu;
|
const apu = @ptrCast(*Apu, @alignCast(@alignOf(*Apu), userdata));
|
||||||
const apu = @ptrCast(T, @alignCast(@alignOf(T), userdata));
|
|
||||||
|
|
||||||
_ = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
|
// TODO: Find a better way to mute this
|
||||||
|
if (!config.config().host.mute) {
|
||||||
|
_ = SDL.SDL_AudioStreamGet(apu.stream, stream, len);
|
||||||
|
} else {
|
||||||
|
// FIXME: I don't think this hack to remove DC Offset is acceptable :thinking:
|
||||||
|
std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't write anything, play silence otherwise garbage will be played
|
||||||
|
// if (written == 0) std.mem.set(u8, stream[0..@intCast(usize, len)], 0x40);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
54
src/util.zig
54
src/util.zig
|
@ -14,9 +14,9 @@ pub fn sext(comptime T: type, comptime U: type, value: T) T {
|
||||||
|
|
||||||
const iT = std.meta.Int(.signed, @typeInfo(T).Int.bits);
|
const iT = std.meta.Int(.signed, @typeInfo(T).Int.bits);
|
||||||
const ExtU = if (@typeInfo(U).Int.signedness == .unsigned) T else iT;
|
const ExtU = if (@typeInfo(U).Int.signedness == .unsigned) T else iT;
|
||||||
const shift_amt = @intCast(Log2Int(T), @typeInfo(T).Int.bits - @typeInfo(U).Int.bits);
|
const shift = @intCast(Log2Int(T), @typeInfo(T).Int.bits - @typeInfo(U).Int.bits);
|
||||||
|
|
||||||
return @bitCast(T, @bitCast(iT, @as(ExtU, @truncate(U, value)) << shift_amt) >> shift_amt);
|
return @bitCast(T, @bitCast(iT, @as(ExtU, @truncate(U, value)) << shift) >> shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://godbolt.org/z/W3en9Eche
|
/// See https://godbolt.org/z/W3en9Eche
|
||||||
|
@ -145,9 +145,7 @@ pub const io = struct {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undef(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
pub fn undef(comptime T: type, log: anytype, comptime format: []const u8, args: anytype) ?T {
|
||||||
@setCold(true);
|
|
||||||
|
|
||||||
const unhandled_io = config.config().debug.unhandled_io;
|
const unhandled_io = config.config().debug.unhandled_io;
|
||||||
|
|
||||||
log.warn(format, args);
|
log.warn(format, args);
|
||||||
|
@ -155,13 +153,6 @@ pub const io = struct {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn err(comptime T: type, comptime log: anytype, comptime format: []const u8, args: anytype) ?T {
|
|
||||||
@setCold(true);
|
|
||||||
|
|
||||||
log.err(format, args);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const write = struct {
|
pub const write = struct {
|
||||||
|
@ -284,37 +275,22 @@ pub const audio = struct {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Sets a quarter (8) of the bits of the u32 `left` to the value of u8 `right`
|
/// Sets the high bits of an integer to a value
|
||||||
pub inline fn setQuart(left: u32, addr: u8, right: u8) u32 {
|
pub inline fn setLo(comptime T: type, left: T, right: HalfInt(T)) T {
|
||||||
const offset = @truncate(u2, addr);
|
return switch (T) {
|
||||||
|
u32 => (left & 0xFFFF_0000) | right,
|
||||||
return switch (offset) {
|
u16 => (left & 0xFF00) | right,
|
||||||
0b00 => (left & 0xFFFF_FF00) | right,
|
u8 => (left & 0xF0) | right,
|
||||||
0b01 => (left & 0xFFFF_00FF) | @as(u32, right) << 8,
|
else => @compileError("unsupported type"),
|
||||||
0b10 => (left & 0xFF00_FFFF) | @as(u32, right) << 16,
|
|
||||||
0b11 => (left & 0x00FF_FFFF) | @as(u32, right) << 24,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the correct shift offset for an aligned/unaligned u8 read
|
/// sets the low bits of an integer to a value
|
||||||
///
|
pub inline fn setHi(comptime T: type, left: T, right: HalfInt(T)) T {
|
||||||
/// TODO: Support u16 reads of u32 values?
|
|
||||||
pub inline fn getHalf(byte: u8) u4 {
|
|
||||||
return @truncate(u4, byte & 1) << 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn setHalf(comptime T: type, left: T, addr: u8, right: HalfInt(T)) T {
|
|
||||||
const offset = @truncate(u1, addr >> if (T == u32) 1 else 0);
|
|
||||||
|
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (offset) {
|
u32 => (left & 0x0000_FFFF) | @as(u32, right) << 16,
|
||||||
0b0 => (left & 0xFFFF_0000) | right,
|
u16 => (left & 0x00FF) | @as(u16, right) << 8,
|
||||||
0b1 => (left & 0x0000_FFFF) | @as(u32, right) << 16,
|
u8 => (left & 0x0F) | @as(u8, right) << 4,
|
||||||
},
|
|
||||||
u16 => switch (offset) {
|
|
||||||
0b0 => (left & 0xFF00) | right,
|
|
||||||
0b1 => (left & 0x00FF) | @as(u16, right) << 8,
|
|
||||||
},
|
|
||||||
else => @compileError("unsupported type"),
|
else => @compileError("unsupported type"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue