180 lines
4.8 KiB
Zig
180 lines
4.8 KiB
Zig
const types = @import("types.zig");
|
|
const Mirroring = types.Mirroring;
|
|
|
|
const Vrc4 = @This();
|
|
|
|
prg_banks: [2]u8 = [_]u8{ 0, 1 },
|
|
chr_banks_lo: [8]u4 = [_]u4{0} ** 8,
|
|
chr_banks_hi: [8]u5 = [_]u5{0} ** 8,
|
|
|
|
prg_mode: u1 = 0,
|
|
mirroring_mode: Mirroring = .vertical,
|
|
|
|
irq_reload: u8 = 0,
|
|
irq_counter: u8 = 0,
|
|
irq_enabled: bool = false,
|
|
irq_enable_on_ack: bool = false,
|
|
irq_mode: u1 = 0, // 0 = cycle, 1 = scanline
|
|
irq_prescaler: i16 = 341,
|
|
irq_assert: bool = false,
|
|
|
|
shift_a: u3,
|
|
shift_b: u3,
|
|
|
|
prg_bank_count: usize,
|
|
chr_bank_count: usize,
|
|
|
|
pub fn init(
|
|
prg_size: usize,
|
|
chr_size: usize,
|
|
mapper_id: u8,
|
|
) Vrc4 {
|
|
const shift_a: u3 = if (mapper_id == 21) 1 else 0;
|
|
const shift_b: u3 = if (mapper_id == 21) 2 else 2;
|
|
|
|
return .{
|
|
.prg_bank_count = prg_size / 0x2000,
|
|
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
|
|
.shift_a = shift_a,
|
|
.shift_b = shift_b,
|
|
};
|
|
}
|
|
|
|
pub fn cpuMapRead(
|
|
self: *const Vrc4,
|
|
address: u16,
|
|
) ?usize {
|
|
if (address < 0x8000) return null;
|
|
|
|
const offset = @as(usize, address & 0x1fff);
|
|
const last_bank = self.prg_bank_count - 1;
|
|
const second_last_bank = self.prg_bank_count - 2;
|
|
|
|
const r0 = @as(usize, self.prg_banks[0] & 0x1f) % self.prg_bank_count;
|
|
const r1 = @as(usize, self.prg_banks[1] & 0x1f) % self.prg_bank_count;
|
|
|
|
const bank_idx = (address - 0x8000) / 0x2000;
|
|
var bank: usize = 0;
|
|
|
|
if (self.prg_mode == 0) {
|
|
switch (bank_idx) {
|
|
0 => bank = r0,
|
|
1 => bank = r1,
|
|
2 => bank = second_last_bank,
|
|
3 => bank = last_bank,
|
|
else => unreachable,
|
|
}
|
|
} else {
|
|
switch (bank_idx) {
|
|
0 => bank = second_last_bank,
|
|
1 => bank = r1,
|
|
2 => bank = r0,
|
|
3 => bank = last_bank,
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
return bank * 0x2000 + offset;
|
|
}
|
|
|
|
pub fn cpuWrite(
|
|
self: *Vrc4,
|
|
address: u16,
|
|
value: u8,
|
|
) void {
|
|
if (address < 0x8000) return;
|
|
|
|
const reg_addr = address & 0xf000;
|
|
const a = @as(u1, @truncate((address >> self.shift_a) & 1));
|
|
const b = @as(u1, @truncate((address >> self.shift_b) & 1));
|
|
const line = (@as(u2, b) << 1) | a;
|
|
|
|
switch (reg_addr) {
|
|
0x8000 => {
|
|
self.prg_banks[0] = value & 0x1f;
|
|
},
|
|
0x9000 => {
|
|
if (line == 0) {
|
|
switch (@as(u2, @truncate(value & 3))) {
|
|
0 => self.mirroring_mode = .vertical,
|
|
1 => self.mirroring_mode = .horizontal,
|
|
2 => self.mirroring_mode = .single_screen_lower,
|
|
3 => self.mirroring_mode = .single_screen_upper,
|
|
}
|
|
} else if (line == 1 or line == 2) {
|
|
self.prg_mode = @truncate((value >> 1) & 1);
|
|
}
|
|
},
|
|
0xa000 => {
|
|
self.prg_banks[1] = value & 0x1f;
|
|
},
|
|
0xb000, 0xc000, 0xd000, 0xe000 => {
|
|
const slot_base = ((reg_addr - 0xb000) / 0x1000) * 2;
|
|
const is_hi = (line & 2) != 0;
|
|
const is_odd = (line & 1) != 0;
|
|
const slot = slot_base + (if (is_odd) @as(usize, 1) else 0);
|
|
|
|
if (!is_hi) {
|
|
self.chr_banks_lo[slot] = @truncate(value & 0x0f);
|
|
} else {
|
|
self.chr_banks_hi[slot] = @truncate(value & 0x1f);
|
|
}
|
|
},
|
|
0xf000 => {
|
|
switch (line) {
|
|
0 => self.irq_reload = (self.irq_reload & 0xf0) | (value & 0x0f),
|
|
1 => self.irq_reload = (self.irq_reload & 0x0f) | ((value & 0x0f) << 4),
|
|
2 => {
|
|
self.irq_mode = @truncate((value >> 2) & 1);
|
|
self.irq_enabled = (value & 0x02) != 0;
|
|
self.irq_enable_on_ack = (value & 0x01) != 0;
|
|
if (self.irq_enabled) {
|
|
self.irq_counter = self.irq_reload;
|
|
self.irq_prescaler = 341;
|
|
}
|
|
self.irq_assert = false;
|
|
},
|
|
3 => {
|
|
self.irq_enabled = self.irq_enable_on_ack;
|
|
self.irq_assert = false;
|
|
},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
pub fn ppuMapRead(
|
|
self: *const Vrc4,
|
|
address: u16,
|
|
) ?usize {
|
|
if (address >= 0x2000) return null;
|
|
|
|
const slot = address / 0x0400;
|
|
const offset = @as(usize, address & 0x03ff);
|
|
|
|
const bank_val = (@as(usize, self.chr_banks_hi[slot]) << 4) | @as(usize, self.chr_banks_lo[slot]);
|
|
const bank = bank_val % self.chr_bank_count;
|
|
|
|
return bank * 0x0400 + offset;
|
|
}
|
|
|
|
pub fn ppuMapWrite(
|
|
self: *const Vrc4,
|
|
address: u16,
|
|
) ?usize {
|
|
return self.ppuMapRead(address);
|
|
}
|
|
|
|
pub fn mirroring(
|
|
self: *const Vrc4,
|
|
) Mirroring {
|
|
return self.mirroring_mode;
|
|
}
|
|
|
|
pub fn irqAsserted(
|
|
self: *const Vrc4,
|
|
) bool {
|
|
return self.irq_assert;
|
|
}
|