Zesty - a pin-accurate, cycle-accurate NES emulator written in Zig
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 };