//! Zesty - a pin-accurate, cycle-accurate NES/Famicom emulator in Zig pub const Cpu = @import("Cpu.zig"); pub const Ppu = @import("Ppu.zig"); pub const cartridge = @import("cartridge.zig"); pub const Cartridge = cartridge.Cartridge; pub const Options = struct { /// Number of main clock cycles per PPU clock cycle. ppu_clock_divider: u8, /// Number of main clock cycles per CPU clock cycle. cpu_clock_divider: u8, pub const famicom: Options = .{ .ppu_clock_divider = 4, .cpu_clock_divider = 12, }; pub const top_loader_ntsc: Options = .{ .ppu_clock_divider = 4, .cpu_clock_divider = 12, }; pub const top_loader_pal: Options = .{ .ppu_clock_divider = 5, .cpu_clock_divider = 16, }; }; pub const Zesty = struct { cpu: Cpu, ppu: Ppu, cart: ?Cartridge, ram: [0x800]u8, pins: Pins, last_pins: Pins, /// The main clock of the emulator. /// Resets every 24 cycles. main_clock: u8, opts: Options, pub fn init(opts: Options) Zesty { return .{ .cpu = .{}, .ppu = .{}, .cart = null, .ram = @splat(0xaa), .pins = .{}, .last_pins = .{}, .main_clock = 0, .opts = opts, }; } pub fn tick(self: *Zesty) void { defer { self.main_clock +%= 1; self.last_pins = self.pins; } self.pins.cpu_clk = self.main_clock % self.opts.cpu_clock_divider == 0; self.pins.ppu_clk = self.main_clock % self.opts.ppu_clock_divider == 0; if (self.pins.cpuRamChipSelect()) { const addr = self.pins.cpu_addr & 0x7ff; switch (self.pins.cpu_rw) { .read => self.pins.cpu_data = self.ram[addr], .write => self.ram[addr] = self.pins.cpu_data, } } if (self.cart) |*cart| cart.update(&self.pins); self.cpu.tick(&self.pins, self.last_pins); self.ppu.tick(&self.pins); } pub fn reset(self: *Zesty) void { self.pins.cpu_rst = true; } pub fn stepCpu(self: *Zesty) void { for (0..self.opts.cpu_clock_divider) |_| self.tick(); } }; /// The collection of all pins and electrical /// connections inside the system. pub const Pins = packed struct { //---------------------------------------------- // CPU /// APU audio out /// (2A03 p1-2) audio: u2 = 0, /// CPU clock cpu_clk: bool = false, /// CPU reset /// (2A03 p3 / 2C02 22) cpu_rst: bool = true, /// CPU address bus /// (2A03 p4-19 / 2C02 p10-12 / Cart p2-13,68-70) cpu_addr: u16 = 0x00ff, /// CPU data bus /// (2A03 p21-28 / 2C02 p2-9 / Cart p60-67) cpu_data: u8 = 0, /// CPU M2 /// (2A03 p31 / Cart p71) cpu_m2: bool = false, /// CPU IRQ /// (2A03 p32 / Cart p15) cpu_irq: bool = false, /// CPU NMI / PPU INT /// (2A03 p33 / 2C02 p19) cpu_nmi: bool = false, /// CPU R/W /// (2A03 p34 / 2C02 p1 / Cart p14) cpu_rw: Rw = .read, cpu_out0: bool = false, cpu_out1: bool = false, cpu_out2: bool = false, cpu_oe1: bool = false, cpu_oe2: bool = false, //---------------------------------------------- // PPU ppu_clk: bool = false, /// EXT pins (pins 14-17, inout) /// Usually just grounded. ppu_ext: u4 = 0, /// Shifted analog video output (pin 21, out) ppu_vout: bool = false, /// VRAM write pin (pin 23, out) ppu_wr: bool = false, /// VRAM read pin (pin 24, out) ppu_rd: bool = false, /// VRAM high address (pins 25-30, out) ppu_addr_hi: u6 = 0, /// VRAM address/data (pins 31-38, inout) ppu_addr_data: u8 = 0, /// Address latch enable (pin 39, out) ppu_ale: bool = false, //---------------------------------------------- // Cartridge /// EXP(?) (pins 16-20 & 54-58, inout) exp: u10 = 0, /// CIRAM address pin 10 (pin 22, out) ciram_a10: bool = false, /// CIRAM chip enable pin (pin 52, out) ciram_ce: bool = false, /// CIC out pin (pin 34, in) cic_out: bool = false, /// CIC in pin (pin 35, out) cic_in: bool = false, /// CIC clock pin (pin 38, out) cic_clk: bool = false, /// CIC reset pin (pin 39, out) cic_rst: bool = false, //---------------------------------------------- // Logic chip emulation /// Cartridge ROM select pub fn romSel(pins: Pins) bool { return !(pins.cpu_m2 and pins.cpu_addr >= 0x8000); } /// CPU RAM chip select pub fn cpuRamChipSelect(pins: Pins) bool { return pins.cpu_addr >> 13 == 0; } /// PPU chip select pub fn ppuChipSelect(pins: Pins) bool { return pins.cpu_addr >> 13 == 1; } }; pub const Rw = enum(u1) { write, read };