add nes cartridge subsystem and 41 ines mapper variants
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user