some basic ROMs already boot (ARM946E-S only), working on Armwrestler and Rockwrestler next so I can ensure CPU compatability for but ARMv5TE and ARMv4T
218 lines
6.9 KiB
Zig
218 lines
6.9 KiB
Zig
const std = @import("std");
|
|
|
|
const Bitfield = @import("bitfield").Bitfield;
|
|
const Bit = @import("bitfield").Bit;
|
|
|
|
const Bus = @import("Bus.zig");
|
|
const SharedIo = @import("../io.zig").Io;
|
|
const writeToAddressOffset = @import("../io.zig").writeToAddressOffset;
|
|
const valueAtAddressOffset = @import("../io.zig").valueAtAddressOffset;
|
|
|
|
const log = std.log.scoped(.nds9_io);
|
|
|
|
pub const Io = struct {
|
|
shared: *SharedIo,
|
|
|
|
/// POWCNT1 - Graphics Power Control
|
|
/// Read / Write
|
|
powcnt: PowCnt = .{ .raw = 0x0000_0000 },
|
|
|
|
// Read Only
|
|
keyinput: AtomicKeyInput = .{},
|
|
|
|
pub fn init(io: *SharedIo) @This() {
|
|
return .{ .shared = io };
|
|
}
|
|
};
|
|
|
|
pub fn read(bus: *const Bus, comptime T: type, address: u32) T {
|
|
return switch (T) {
|
|
// zig fmt: off
|
|
u32 =>
|
|
@as(T, read(bus, u8, address + 3)) << 24
|
|
| @as(T, read(bus, u8, address + 2)) << 16
|
|
| @as(T, read(bus, u8, address + 1)) << 8
|
|
| read(bus, u8, address + 0) << 0,
|
|
// zig fmt: on
|
|
u16 => @as(T, read(bus, u8, address + 1)) << 8 | read(bus, u8, address),
|
|
u8 => switch (address) {
|
|
0x0400_0000...0x0400_0003 => valueAtAddressOffset(u32, address, bus.ppu.io.dispcnt_a.raw),
|
|
0x0400_0004...0x0400_0005 => valueAtAddressOffset(u16, address, bus.ppu.io.dispstat.raw),
|
|
|
|
0x0400_0130...0x0400_0131 => valueAtAddressOffset(u16, address, bus.io.keyinput.load(.Monotonic)),
|
|
0x0400_0180...0x0400_0183 => valueAtAddressOffset(u32, address, bus.io.shared.ipc_sync.raw),
|
|
0x0400_0184...0x0400_0187 => valueAtAddressOffset(u32, address, bus.io.shared.ipc_fifo_cnt.raw),
|
|
|
|
0x0400_0208...0x0400_020B => valueAtAddressOffset(u32, address, @intFromBool(bus.io.shared.ime)),
|
|
|
|
0x0400_0304...0x0400_0307 => valueAtAddressOffset(u32, address, bus.io.powcnt.raw),
|
|
else => warn("unexpected read: 0x{X:0>8}", .{address}),
|
|
},
|
|
else => @compileError(T ++ " is an unsupported bus read type"),
|
|
};
|
|
}
|
|
|
|
pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
|
switch (T) {
|
|
u32 => {
|
|
write(bus, u8, address + 3, @as(u8, @truncate(value >> 24)));
|
|
write(bus, u8, address + 2, @as(u8, @truncate(value >> 16)));
|
|
write(bus, u8, address + 1, @as(u8, @truncate(value >> 8)));
|
|
write(bus, u8, address + 0, @as(u8, @truncate(value >> 0)));
|
|
},
|
|
u16 => {
|
|
write(bus, u8, address + 1, @as(u8, @truncate(value >> 8)));
|
|
write(bus, u8, address + 0, @as(u8, @truncate(value >> 0)));
|
|
},
|
|
u8 => switch (address) {
|
|
0x0400_0000...0x0400_0003 => writeToAddressOffset(&bus.ppu.io.dispcnt_a.raw, address, value),
|
|
|
|
0x0400_0180...0x0400_0183 => writeToAddressOffset(&bus.io.shared.ipc_sync.raw, address, value),
|
|
0x0400_0184...0x0400_0187 => writeToAddressOffset(&bus.io.shared.ipc_fifo_cnt.raw, address, value),
|
|
|
|
0x0400_0208 => bus.io.shared.ime = value & 1 == 1,
|
|
0x0400_0209...0x0400_020B => {}, // unused bytes from IME
|
|
|
|
0x0400_0240 => bus.ppu.io.vramcnt_a.raw = value,
|
|
0x0400_0241 => bus.ppu.io.vramcnt_b.raw = value,
|
|
0x0400_0242 => bus.ppu.io.vramcnt_c.raw = value,
|
|
0x0400_0243 => bus.ppu.io.vramcnt_d.raw = value,
|
|
|
|
0x0400_0304...0x0400_0307 => writeToAddressOffset(&bus.io.powcnt.raw, address, value),
|
|
else => log.warn("unexpected write: 0x{X:}u8 -> 0x{X:0>8}", .{ value, address }),
|
|
},
|
|
else => @compileError(T ++ " is an unsupported bus write type"),
|
|
}
|
|
}
|
|
|
|
fn warn(comptime format: []const u8, args: anytype) u0 {
|
|
log.warn(format, args);
|
|
return 0;
|
|
}
|
|
|
|
const PowCnt = extern union {
|
|
// Enable flag for both LCDs
|
|
lcd: Bit(u32, 0),
|
|
gfx_2da: Bit(u32, 1),
|
|
render_3d: Bit(u32, 2),
|
|
geometry_3d: Bit(u32, 3),
|
|
gfx_2db: Bit(u32, 9),
|
|
display_swap: Bit(u32, 15),
|
|
raw: u32,
|
|
};
|
|
|
|
pub const DispcntA = extern union {
|
|
bg_mode: Bitfield(u32, 0, 2),
|
|
|
|
/// toggle between 2D and 3D for BG0
|
|
bg0_dimension: Bit(u32, 3),
|
|
tile_obj_mapping: Bit(u32, 4),
|
|
bitmap_obj_2d_dimension: Bit(u32, 5),
|
|
bitmap_obj_mapping: Bit(u32, 6),
|
|
forced_blank: Bit(u32, 7),
|
|
bg_enable: Bitfield(u32, 8, 4),
|
|
obj_enable: Bit(u32, 12),
|
|
win_enable: Bitfield(u32, 13, 2),
|
|
obj_win_enable: Bit(u32, 15),
|
|
display_mode: Bitfield(u32, 16, 2),
|
|
vram_block: Bitfield(u32, 18, 2),
|
|
tile_obj_1d_boundary: Bitfield(u32, 20, 2),
|
|
bitmap_obj_1d_boundary: Bit(u32, 22),
|
|
obj_during_hblank: Bit(u32, 23),
|
|
character_base: Bitfield(u32, 24, 3),
|
|
screen_base: Bitfield(u32, 27, 2),
|
|
bg_ext_pal_enable: Bit(u32, 30),
|
|
obj_ext_pal_enable: Bit(u32, 31),
|
|
raw: u32,
|
|
};
|
|
|
|
pub const Vramcnt = struct {
|
|
/// Can be used by VRAM-A and VRAM-B
|
|
pub const A = extern union {
|
|
mst: Bitfield(u8, 0, 2),
|
|
offset: Bitfield(u8, 3, 2),
|
|
enable: Bit(u8, 7),
|
|
raw: u8,
|
|
};
|
|
|
|
/// Can be used by VRAM-C, VRAM-D, VRAM-F, VRAM-G
|
|
pub const C = extern union {
|
|
mst: Bitfield(u8, 0, 3),
|
|
offset: Bitfield(u8, 3, 2),
|
|
enable: Bit(u8, 7),
|
|
raw: u8,
|
|
};
|
|
|
|
/// Can be used by VRAM-E
|
|
pub const E = extern union {
|
|
mst: Bitfield(u8, 0, 3),
|
|
enable: Bit(u8, 7),
|
|
raw: u8,
|
|
};
|
|
|
|
/// can be used by VRAM-H and VRAM-I
|
|
pub const H = extern union {
|
|
mst: Bitfield(u8, 0, 2),
|
|
enable: Bit(u8, 7),
|
|
raw: u8,
|
|
};
|
|
};
|
|
|
|
// Compared to the GBA:
|
|
// - LY/LYC values are now 9-bits
|
|
pub const Vcount = extern union {
|
|
scanline: Bitfield(u16, 0, 9),
|
|
raw: u16,
|
|
};
|
|
|
|
pub const Dispstat = extern union {
|
|
vblank: Bit(u16, 0),
|
|
hblank: Bit(u16, 1),
|
|
coincidence: Bit(u16, 2),
|
|
vblank_irq: Bit(u16, 3),
|
|
hblank_irq: Bit(u16, 4),
|
|
vcount_irq: Bit(u16, 5),
|
|
|
|
/// FIXME: confirm that I'm reading DISPSTAT.7 correctly into LYC
|
|
lyc: Bitfield(u16, 7, 9),
|
|
raw: u16,
|
|
};
|
|
|
|
/// Read Only
|
|
/// 0 = Pressed, 1 = Released
|
|
pub const KeyInput = extern union {
|
|
a: Bit(u16, 0),
|
|
b: Bit(u16, 1),
|
|
select: Bit(u16, 2),
|
|
start: Bit(u16, 3),
|
|
right: Bit(u16, 4),
|
|
left: Bit(u16, 5),
|
|
up: Bit(u16, 6),
|
|
down: Bit(u16, 7),
|
|
shoulder_r: Bit(u16, 8),
|
|
shoulder_l: Bit(u16, 9),
|
|
raw: u16,
|
|
};
|
|
|
|
const AtomicKeyInput = struct {
|
|
const Self = @This();
|
|
const Ordering = std.atomic.Ordering;
|
|
|
|
inner: KeyInput = .{ .raw = 0x03FF },
|
|
|
|
pub inline fn load(self: *const Self, comptime ordering: Ordering) u16 {
|
|
return switch (ordering) {
|
|
.AcqRel, .Release => @compileError("not supported for atomic loads"),
|
|
else => @atomicLoad(u16, &self.inner.raw, ordering),
|
|
};
|
|
}
|
|
|
|
pub inline fn fetchOr(self: *Self, value: u16, comptime ordering: Ordering) void {
|
|
_ = @atomicRmw(u16, &self.inner.raw, .Or, value, ordering);
|
|
}
|
|
|
|
pub inline fn fetchAnd(self: *Self, value: u16, comptime ordering: Ordering) void {
|
|
_ = @atomicRmw(u16, &self.inner.raw, .And, value, ordering);
|
|
}
|
|
};
|