add nes cartridge subsystem and 41 ines mapper variants

This commit is contained in:
2026-08-14 06:48:16 +02:00
parent 42f20478bd
commit 04fb7bcc1f
41 changed files with 5042 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
const types = @import("types.zig");
const Mirroring = types.Mirroring;
const Mmc5 = @This();
prg_mode: u2 = 3,
chr_mode: u2 = 0,
prg_banks: [4]u8 = [_]u8{ 0, 0, 1, 2 }, // $6000, $8000, $A000, $C000
chr_banks: [12]u16 = [_]u16{0} ** 12,
mult_a: u8 = 0,
mult_b: u8 = 0,
irq_scanline: u8 = 0,
irq_enabled: bool = false,
irq_assert: bool = false,
irq_in_frame: bool = false,
current_scanline: u8 = 0,
mirroring_mode: Mirroring = .vertical,
ex_ram: [1024]u8 = [_]u8{0} ** 1024,
prg_bank_count: usize,
chr_bank_count: usize,
pub fn init(
prg_size: usize,
chr_size: usize,
) Mmc5 {
return .{
.prg_bank_count = prg_size / 0x2000,
.chr_bank_count = if (chr_size > 0) chr_size / 0x0400 else 1,
};
}
pub fn cpuMapRead(
self: *const Mmc5,
address: u16,
) ?usize {
if (address < 0x5c00) return null;
if (address >= 0x5c00 and address <= 0x5fff) {
return 0x80000000 | @as(usize, address - 0x5c00);
}
if (address < 0x8000) {
const bank = @as(usize, self.prg_banks[0] & 0x7f) % self.prg_bank_count;
return bank * 0x2000 + (address & 0x1fff);
}
const last_bank = self.prg_bank_count - 1;
const offset = @as(usize, address & 0x1fff);
switch ((address - 0x8000) / 0x2000) {
0 => return (@as(usize, self.prg_banks[1] & 0x7f) % self.prg_bank_count) * 0x2000 + offset,
1 => return (@as(usize, self.prg_banks[2] & 0x7f) % self.prg_bank_count) * 0x2000 + offset,
2 => return (@as(usize, self.prg_banks[3] & 0x7f) % self.prg_bank_count) * 0x2000 + offset,
3 => return last_bank * 0x2000 + offset,
else => unreachable,
}
}
pub fn cpuWrite(
self: *Mmc5,
address: u16,
value: u8,
) void {
if (address >= 0x5c00 and address <= 0x5fff) {
self.ex_ram[address - 0x5c00] = value;
return;
}
switch (address) {
0x5100 => self.prg_mode = @truncate(value & 3),
0x5101 => self.chr_mode = @truncate(value & 3),
0x5105 => {
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,
}
},
0x5114 => self.prg_banks[0] = value,
0x5115 => self.prg_banks[1] = value,
0x5116 => self.prg_banks[2] = value,
0x5117 => self.prg_banks[3] = value,
0x5120...0x512b => |addr| {
const idx = addr - 0x5120;
self.chr_banks[idx] = value;
},
0x5203 => self.irq_scanline = value,
0x5204 => {
self.irq_enabled = (value & 0x80) != 0;
self.irq_assert = false;
},
0x5205 => self.mult_a = value,
0x5206 => self.mult_b = value,
else => {},
}
}
pub fn readRegister(
self: *Mmc5,
address: u16,
) ?u8 {
switch (address) {
0x5204 => {
const res: u8 = (if (self.irq_assert) @as(u8, 0x80) else 0) | (if (self.irq_in_frame) @as(u8, 0x40) else 0);
self.irq_assert = false;
return res;
},
0x5205 => {
const prod = @as(u16, self.mult_a) * @as(u16, self.mult_b);
return @truncate(prod);
},
0x5206 => {
const prod = @as(u16, self.mult_a) * @as(u16, self.mult_b);
return @truncate(prod >> 8);
},
else => return null,
}
}
pub fn ppuMapRead(
self: *const Mmc5,
address: u16,
) ?usize {
if (address >= 0x2000) return null;
const slot = address / 0x0400;
const offset = @as(usize, address & 0x03ff);
const bank = @as(usize, self.chr_banks[slot]) % self.chr_bank_count;
return bank * 0x0400 + offset;
}
pub fn ppuMapWrite(
self: *const Mmc5,
address: u16,
) ?usize {
return self.ppuMapRead(address);
}
pub fn handleScanline(
self: *Mmc5,
) void {
self.current_scanline += 1;
if (self.current_scanline == self.irq_scanline) {
if (self.irq_enabled) {
self.irq_assert = true;
}
}
if (self.current_scanline >= 240) {
self.current_scanline = 0;
self.irq_in_frame = false;
}
}
pub fn mirroring(
self: *const Mmc5,
) Mirroring {
return self.mirroring_mode;
}
pub fn irqAsserted(
self: *const Mmc5,
) bool {
return self.irq_assert;
}