172 lines
4.3 KiB
Zig
172 lines
4.3 KiB
Zig
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;
|
|
}
|