Compare commits
18 Commits
8e1a539e70
...
905c4448d0
|
@ -107,7 +107,7 @@ pub fn deinit(self: *Self) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fillReadTable(bus: *Self, table: *[table_len]?*const anyopaque) void {
|
fn fillReadTable(bus: *Self, table: *[table_len]?*const anyopaque) void {
|
||||||
const vramMirror = @import("ppu.zig").Vram.mirror;
|
const vramMirror = @import("ppu/Vram.zig").mirror;
|
||||||
|
|
||||||
for (table) |*ptr, i| {
|
for (table) |*ptr, i| {
|
||||||
const addr = page_size * i;
|
const addr = page_size * i;
|
||||||
|
@ -134,7 +134,7 @@ fn fillReadTable(bus: *Self, table: *[table_len]?*const anyopaque) void {
|
||||||
|
|
||||||
fn fillWriteTable(comptime T: type, bus: *Self, table: *[table_len]?*const anyopaque) void {
|
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);
|
comptime std.debug.assert(T == u32 or T == u16 or T == u8);
|
||||||
const vramMirror = @import("ppu.zig").Vram.mirror;
|
const vramMirror = @import("ppu/Vram.zig").mirror;
|
||||||
|
|
||||||
for (table) |*ptr, i| {
|
for (table) |*ptr, i| {
|
||||||
const addr = page_size * i;
|
const addr = page_size * i;
|
||||||
|
|
|
@ -338,7 +338,7 @@ fn DmaController(comptime id: u2) type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pollDmaOnBlank(bus: *Bus, comptime kind: DmaKind) void {
|
pub fn onBlanking(bus: *Bus, comptime kind: DmaKind) void {
|
||||||
comptime var i: usize = 0;
|
comptime var i: usize = 0;
|
||||||
inline while (i < 4) : (i += 1) {
|
inline while (i < 4) : (i += 1) {
|
||||||
bus.dma[i].poll(kind);
|
bus.dma[i].poll(kind);
|
||||||
|
|
|
@ -449,6 +449,8 @@ pub const BldY = extern union {
|
||||||
raw: u16,
|
raw: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const u8WriteKind = enum { Hi, Lo };
|
||||||
|
|
||||||
/// Write-only
|
/// Write-only
|
||||||
pub const WinH = extern union {
|
pub const WinH = extern union {
|
||||||
x2: Bitfield(u16, 0, 8),
|
x2: Bitfield(u16, 0, 8),
|
||||||
|
@ -458,6 +460,8 @@ pub const WinH = extern union {
|
||||||
|
|
||||||
/// Write-only
|
/// Write-only
|
||||||
pub const WinV = extern union {
|
pub const WinV = extern union {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
y2: Bitfield(u16, 0, 8),
|
y2: Bitfield(u16, 0, 8),
|
||||||
y1: Bitfield(u16, 8, 8),
|
y1: Bitfield(u16, 8, 8),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
|
@ -466,20 +470,20 @@ pub const WinV = extern union {
|
||||||
pub const WinIn = extern union {
|
pub const WinIn = extern union {
|
||||||
w0_bg: Bitfield(u16, 0, 4),
|
w0_bg: Bitfield(u16, 0, 4),
|
||||||
w0_obj: Bit(u16, 4),
|
w0_obj: Bit(u16, 4),
|
||||||
w0_colour: Bit(u16, 5),
|
w0_bld: Bit(u16, 5),
|
||||||
w1_bg: Bitfield(u16, 8, 4),
|
w1_bg: Bitfield(u16, 8, 4),
|
||||||
w1_obj: Bit(u16, 12),
|
w1_obj: Bit(u16, 12),
|
||||||
w1_colour: Bit(u16, 13),
|
w1_bld: Bit(u16, 13),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const WinOut = extern union {
|
pub const WinOut = extern union {
|
||||||
out_bg: Bitfield(u16, 0, 4),
|
out_bg: Bitfield(u16, 0, 4),
|
||||||
out_obj: Bit(u16, 4),
|
out_obj: Bit(u16, 4),
|
||||||
out_colour: Bit(u16, 5),
|
out_bld: Bit(u16, 5),
|
||||||
obj_bg: Bitfield(u16, 8, 4),
|
obj_bg: Bitfield(u16, 8, 4),
|
||||||
obj_obj: Bit(u16, 12),
|
obj_obj: Bit(u16, 12),
|
||||||
obj_colour: Bit(u16, 13),
|
obj_bld: Bit(u16, 13),
|
||||||
raw: u16,
|
raw: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
888
src/core/ppu.zig
888
src/core/ppu.zig
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,40 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const buf_len = 0x400;
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
buf: []u8,
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||||
|
const addr = address & 0x3FF;
|
||||||
|
|
||||||
|
return switch (T) {
|
||||||
|
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||||
|
else => @compileError("OAM: Unsupported read width"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
|
||||||
|
const addr = address & 0x3FF;
|
||||||
|
|
||||||
|
switch (T) {
|
||||||
|
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
|
||||||
|
u8 => return, // 8-bit writes are explicitly ignored
|
||||||
|
else => @compileError("OAM: Unsupported write width"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) !Self {
|
||||||
|
const buf = try allocator.alloc(u8, buf_len);
|
||||||
|
std.mem.set(u8, buf, 0);
|
||||||
|
|
||||||
|
return Self{ .buf = buf, .allocator = allocator };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.allocator.free(self.buf);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const buf_len = 0x400;
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
buf: []u8,
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||||
|
const addr = address & 0x3FF;
|
||||||
|
|
||||||
|
return switch (T) {
|
||||||
|
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||||
|
else => @compileError("PALRAM: Unsupported read width"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *Self, comptime T: type, address: usize, value: T) void {
|
||||||
|
const addr = address & 0x3FF;
|
||||||
|
|
||||||
|
switch (T) {
|
||||||
|
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)], value),
|
||||||
|
u8 => {
|
||||||
|
const align_addr = addr & ~@as(u32, 1); // Aligned to Halfword boundary
|
||||||
|
std.mem.writeIntSliceLittle(u16, self.buf[align_addr..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
|
||||||
|
},
|
||||||
|
else => @compileError("PALRAM: Unsupported write width"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) !Self {
|
||||||
|
const buf = try allocator.alloc(u8, buf_len);
|
||||||
|
std.mem.set(u8, buf, 0);
|
||||||
|
|
||||||
|
return Self{ .buf = buf, .allocator = allocator };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.allocator.free(self.buf);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn backdrop(self: *const Self) u16 {
|
||||||
|
return std.mem.readIntNative(u16, self.buf[0..2]);
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const io = @import("../bus/io.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const buf_len = 0x18000;
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
buf: []u8,
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
pub fn read(self: *const Self, comptime T: type, address: usize) T {
|
||||||
|
const addr = Self.mirror(address);
|
||||||
|
|
||||||
|
return switch (T) {
|
||||||
|
u32, u16, u8 => std.mem.readIntSliceLittle(T, self.buf[addr..][0..@sizeOf(T)]),
|
||||||
|
else => @compileError("VRAM: Unsupported read width"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *Self, comptime T: type, dispcnt: io.DisplayControl, address: usize, value: T) void {
|
||||||
|
const mode: u3 = dispcnt.bg_mode.read();
|
||||||
|
const idx = Self.mirror(address);
|
||||||
|
|
||||||
|
switch (T) {
|
||||||
|
u32, u16 => std.mem.writeIntSliceLittle(T, self.buf[idx..][0..@sizeOf(T)], value),
|
||||||
|
u8 => {
|
||||||
|
// Ignore write if it falls within the boundaries of OBJ VRAM
|
||||||
|
switch (mode) {
|
||||||
|
0, 1, 2 => if (0x0001_0000 <= idx) return,
|
||||||
|
else => if (0x0001_4000 <= idx) return,
|
||||||
|
}
|
||||||
|
|
||||||
|
const align_idx = idx & ~@as(u32, 1); // Aligned to a halfword boundary
|
||||||
|
std.mem.writeIntSliceLittle(u16, self.buf[align_idx..][0..@sizeOf(u16)], @as(u16, value) * 0x101);
|
||||||
|
},
|
||||||
|
else => @compileError("VRAM: Unsupported write width"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) !Self {
|
||||||
|
const buf = try allocator.alloc(u8, buf_len);
|
||||||
|
std.mem.set(u8, buf, 0);
|
||||||
|
|
||||||
|
return Self{ .buf = buf, .allocator = allocator };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.allocator.free(self.buf);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mirror(address: usize) usize {
|
||||||
|
// Mirrored in steps of 128K (64K + 32K + 32K) (abcc)
|
||||||
|
const addr = address & 0x1FFFF;
|
||||||
|
|
||||||
|
// If the address is within 96K we don't do anything,
|
||||||
|
// otherwise we want to mirror the last 32K (addresses between 64K and 96K)
|
||||||
|
return if (addr < buf_len) addr else 0x10000 + (addr & 0x7FFF);
|
||||||
|
}
|
46
src/util.zig
46
src/util.zig
|
@ -5,6 +5,8 @@ const config = @import("config.zig");
|
||||||
const Log2Int = std.math.Log2Int;
|
const Log2Int = std.math.Log2Int;
|
||||||
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
const Arm7tdmi = @import("core/cpu.zig").Arm7tdmi;
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
// Sign-Extend value of type `T` to type `U`
|
// Sign-Extend value of type `T` to type `U`
|
||||||
pub fn sext(comptime T: type, comptime U: type, value: T) T {
|
pub fn sext(comptime T: type, comptime U: type, value: T) T {
|
||||||
// U must have less bits than T
|
// U must have less bits than T
|
||||||
|
@ -123,6 +125,7 @@ pub const io = struct {
|
||||||
|
|
||||||
pub const Logger = struct {
|
pub const Logger = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
const FmtArgTuple = std.meta.Tuple(&.{ u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 });
|
||||||
|
|
||||||
buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer),
|
buf: std.io.BufferedWriter(4096 << 2, std.fs.File.Writer),
|
||||||
|
|
||||||
|
@ -181,8 +184,6 @@ pub const Logger = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FmtArgTuple = struct { u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32, u32 };
|
|
||||||
|
|
||||||
pub const audio = struct {
|
pub const audio = struct {
|
||||||
const _io = @import("core/bus/io.zig");
|
const _io = @import("core/bus/io.zig");
|
||||||
|
|
||||||
|
@ -275,3 +276,44 @@ fn HalfInt(comptime T: type) type {
|
||||||
|
|
||||||
return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
|
return std.meta.Int(type_info.Int.signedness, type_info.Int.bits >> 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Double Buffering Implementation
|
||||||
|
pub const FrameBuffer = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
layers: [2][]u8,
|
||||||
|
buf: []u8,
|
||||||
|
current: u1,
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
// TODO: Rename
|
||||||
|
const Device = enum { Emulator, Renderer };
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator, comptime len: comptime_int) !Self {
|
||||||
|
const buf = try allocator.alloc(u8, len * 2);
|
||||||
|
std.mem.set(u8, buf, 0);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
// Front and Back Framebuffers
|
||||||
|
.layers = [_][]u8{ buf[0..][0..len], buf[len..][0..len] },
|
||||||
|
.buf = buf,
|
||||||
|
.current = 0,
|
||||||
|
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.allocator.free(self.buf);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap(self: *Self) void {
|
||||||
|
self.current = ~self.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Self, comptime dev: Device) []u8 {
|
||||||
|
return self.layers[if (dev == .Emulator) self.current else ~self.current];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue