Zesty - a pin-accurate, cycle-accurate NES emulator written in Zig
at main 198 lines 4.9 kB view raw
1//! Zesty - a pin-accurate, cycle-accurate NES/Famicom emulator in Zig 2 3pub const Cpu = @import("Cpu.zig"); 4pub const Ppu = @import("Ppu.zig"); 5pub const cartridge = @import("cartridge.zig"); 6pub const Cartridge = cartridge.Cartridge; 7 8pub const Options = struct { 9 /// Number of main clock cycles per PPU clock cycle. 10 ppu_clock_divider: u8, 11 12 /// Number of main clock cycles per CPU clock cycle. 13 cpu_clock_divider: u8, 14 15 pub const famicom: Options = .{ 16 .ppu_clock_divider = 4, 17 .cpu_clock_divider = 12, 18 }; 19 pub const top_loader_ntsc: Options = .{ 20 .ppu_clock_divider = 4, 21 .cpu_clock_divider = 12, 22 }; 23 pub const top_loader_pal: Options = .{ 24 .ppu_clock_divider = 5, 25 .cpu_clock_divider = 16, 26 }; 27}; 28 29pub const Zesty = struct { 30 cpu: Cpu, 31 ppu: Ppu, 32 cart: ?Cartridge, 33 ram: [0x800]u8, 34 35 pins: Pins, 36 last_pins: Pins, 37 38 /// The main clock of the emulator. 39 /// Resets every 24 cycles. 40 main_clock: u8, 41 42 opts: Options, 43 44 pub fn init(opts: Options) Zesty { 45 return .{ 46 .cpu = .{}, 47 .ppu = .{}, 48 .cart = null, 49 .ram = @splat(0xaa), 50 .pins = .{}, 51 .last_pins = .{}, 52 .main_clock = 0, 53 .opts = opts, 54 }; 55 } 56 57 pub fn tick(self: *Zesty) void { 58 defer { 59 self.main_clock +%= 1; 60 self.last_pins = self.pins; 61 } 62 63 self.pins.cpu_clk = self.main_clock % self.opts.cpu_clock_divider == 0; 64 self.pins.ppu_clk = self.main_clock % self.opts.ppu_clock_divider == 0; 65 66 if (self.pins.cpuRamChipSelect()) { 67 const addr = self.pins.cpu_addr & 0x7ff; 68 switch (self.pins.cpu_rw) { 69 .read => self.pins.cpu_data = self.ram[addr], 70 .write => self.ram[addr] = self.pins.cpu_data, 71 } 72 } 73 if (self.cart) |*cart| cart.update(&self.pins); 74 75 self.cpu.tick(&self.pins, self.last_pins); 76 self.ppu.tick(&self.pins); 77 } 78 79 pub fn reset(self: *Zesty) void { 80 self.pins.cpu_rst = true; 81 } 82 83 pub fn stepCpu(self: *Zesty) void { 84 for (0..self.opts.cpu_clock_divider) |_| self.tick(); 85 } 86}; 87 88/// The collection of all pins and electrical 89/// connections inside the system. 90pub const Pins = packed struct { 91 //---------------------------------------------- 92 // CPU 93 94 /// APU audio out 95 /// (2A03 p1-2) 96 audio: u2 = 0, 97 98 /// CPU clock 99 cpu_clk: bool = false, 100 101 /// CPU reset 102 /// (2A03 p3 / 2C02 22) 103 cpu_rst: bool = true, 104 105 /// CPU address bus 106 /// (2A03 p4-19 / 2C02 p10-12 / Cart p2-13,68-70) 107 cpu_addr: u16 = 0x00ff, 108 109 /// CPU data bus 110 /// (2A03 p21-28 / 2C02 p2-9 / Cart p60-67) 111 cpu_data: u8 = 0, 112 113 /// CPU M2 114 /// (2A03 p31 / Cart p71) 115 cpu_m2: bool = false, 116 117 /// CPU IRQ 118 /// (2A03 p32 / Cart p15) 119 cpu_irq: bool = false, 120 121 /// CPU NMI / PPU INT 122 /// (2A03 p33 / 2C02 p19) 123 cpu_nmi: bool = false, 124 125 /// CPU R/W 126 /// (2A03 p34 / 2C02 p1 / Cart p14) 127 cpu_rw: Rw = .read, 128 129 cpu_out0: bool = false, 130 cpu_out1: bool = false, 131 cpu_out2: bool = false, 132 cpu_oe1: bool = false, 133 cpu_oe2: bool = false, 134 135 //---------------------------------------------- 136 // PPU 137 ppu_clk: bool = false, 138 139 /// EXT pins (pins 14-17, inout) 140 /// Usually just grounded. 141 ppu_ext: u4 = 0, 142 143 /// Shifted analog video output (pin 21, out) 144 ppu_vout: bool = false, 145 146 /// VRAM write pin (pin 23, out) 147 ppu_wr: bool = false, 148 149 /// VRAM read pin (pin 24, out) 150 ppu_rd: bool = false, 151 152 /// VRAM high address (pins 25-30, out) 153 ppu_addr_hi: u6 = 0, 154 155 /// VRAM address/data (pins 31-38, inout) 156 ppu_addr_data: u8 = 0, 157 158 /// Address latch enable (pin 39, out) 159 ppu_ale: bool = false, 160 161 //---------------------------------------------- 162 // Cartridge 163 164 /// EXP(?) (pins 16-20 & 54-58, inout) 165 exp: u10 = 0, 166 167 /// CIRAM address pin 10 (pin 22, out) 168 ciram_a10: bool = false, 169 /// CIRAM chip enable pin (pin 52, out) 170 ciram_ce: bool = false, 171 172 /// CIC out pin (pin 34, in) 173 cic_out: bool = false, 174 /// CIC in pin (pin 35, out) 175 cic_in: bool = false, 176 /// CIC clock pin (pin 38, out) 177 cic_clk: bool = false, 178 /// CIC reset pin (pin 39, out) 179 cic_rst: bool = false, 180 181 //---------------------------------------------- 182 // Logic chip emulation 183 184 /// Cartridge ROM select 185 pub fn romSel(pins: Pins) bool { 186 return !(pins.cpu_m2 and pins.cpu_addr >= 0x8000); 187 } 188 /// CPU RAM chip select 189 pub fn cpuRamChipSelect(pins: Pins) bool { 190 return pins.cpu_addr >> 13 == 0; 191 } 192 /// PPU chip select 193 pub fn ppuChipSelect(pins: Pins) bool { 194 return pins.cpu_addr >> 13 == 1; 195 } 196}; 197 198pub const Rw = enum(u1) { write, read };