GameBoy Emulator 1
Game Boy emulator core and tooling
Loading...
Searching...
No Matches
mmu.cpp
1#include "../../../include/mmu.hpp"
2#include "../../../include/cartridge.hpp"
3#include "../../../include/ppu.hpp"
4#include "../../../include/timer.hpp"
5#include "../../../include/interrupt_controller.hpp"
6#include <algorithm>
7#include <iterator>
8
9MMU::MMU() {
10 dummy_ic = std::make_unique<InterruptController>();
11 dummy_cartridge = std::make_unique<Cartridge>();
12 dummy_timer = std::make_unique<Timer>(*dummy_ic);
13 dummy_ppu = std::make_unique<PPU>(*dummy_ic);
14
15 cartridge = dummy_cartridge.get();
16 ic = dummy_ic.get();
17 timer = dummy_timer.get();
18 ppu = dummy_ppu.get();
19
20 io_regs.fill(0);
21 hram.fill(0);
22 wram.fill(0);
23
24 // Post-boot defaults
25 write(0xFF00, 0xCF); // Joypad
26 write(0xFF04, 0xAB); // DIV
27 write(0xFF05, 0x00); // TIMA
28 write(0xFF06, 0x00); // TMA
29 write(0xFF07, 0xF8); // TAC
30 write(0xFF0F, 0xE1); // IF
31 write(0xFF40, 0x91); // LCDC
32 write(0xFF41, 0x85); // STAT
33 write(0xFF42, 0x00); // SCY
34 write(0xFF43, 0x00); // SCX
35 write(0xFF44, 0x00); // LY
36 write(0xFF45, 0x00); // LYC
37 write(0xFF47, 0xFC); // BGP
38 write(0xFF48, 0xFF); // OBP0
39 write(0xFF49, 0xFF); // OBP1
40 write(0xFF4A, 0x00); // WY
41 write(0xFF4B, 0x00); // WX
42 write(0xFFFF, 0x00); // IE
43}
44
45MMU::MMU(Cartridge& cartRef, PPU& ppuRef, Timer& timerRef, InterruptController& icRef)
46 : cartridge(&cartRef)
47 , ppu(&ppuRef)
48 , timer(&timerRef)
49 , ic(&icRef)
50{
51 io_regs.fill(0);
52 hram.fill(0);
53 wram.fill(0);
54
55 // Post-boot defaults
56 write(0xFF00, 0xCF); // Joypad
57 write(0xFF04, 0xAB); // DIV
58 write(0xFF05, 0x00); // TIMA
59 write(0xFF06, 0x00); // TMA
60 write(0xFF07, 0xF8); // TAC
61 write(0xFF0F, 0xE1); // IF
62 write(0xFF40, 0x91); // LCDC
63 write(0xFF41, 0x85); // STAT
64 write(0xFF42, 0x00); // SCY
65 write(0xFF43, 0x00); // SCX
66 write(0xFF44, 0x00); // LY
67 write(0xFF45, 0x00); // LYC
68 write(0xFF47, 0xFC); // BGP
69 write(0xFF48, 0xFF); // OBP0
70 write(0xFF49, 0xFF); // OBP1
71 write(0xFF4A, 0x00); // WY
72 write(0xFF4B, 0x00); // WX
73 write(0xFFFF, 0x00); // IE
74}
75
76u8 MMU::read(const u16 address) const {
77 read_count++;
78
79 // ROM (0x0000 - 0x7FFF)
80 if (address <= 0x7FFF) {
81 return cartridge->read(address);
82 }
83 // VRAM (0x8000 - 0x9FFF)
84 if (address <= 0x9FFF) {
85 return ppu->read(address);
86 }
87 // External RAM (0xA000 - 0xBFFF)
88 if (address <= 0xBFFF) {
89 return cartridge->read(address);
90 }
91 // WRAM (0xC000 - 0xDFFF)
92 if (address <= 0xDFFF) {
93 return wram[address - 0xC000];
94 }
95 // Echo RAM (0xE000 - 0xFDFF) — mirrors WRAM
96 if (address <= 0xFDFF) {
97 return wram[address - 0xE000];
98 }
99 // OAM (0xFE00 - 0xFE9F)
100 if (address <= 0xFE9F) {
101 return ppu->read(address);
102 }
103 // Unusable (0xFEA0 - 0xFEFF)
104 if (address <= 0xFEFF) {
105 return 0x00;
106 }
107 // I/O Registers (0xFF00 - 0xFF7F)
108 if (address == 0xFF00) return get_joypad_state();
109 if (address >= 0xFF04 && address <= 0xFF07) return timer->read(address);
110 if (address == 0xFF0F) return ic->read(address);
111 if (address >= 0xFF40 && address <= 0xFF4B) return ppu->read(address);
112 if (address >= 0xFF00 && address <= 0xFF7F) return io_regs[address - 0xFF00];
113
114 // HRAM (0xFF80 - 0xFFFE)
115 if (address <= 0xFFFE) return hram[address - 0xFF80];
116
117 // IE (0xFFFF)
118 if (address == 0xFFFF) return ic->read(address);
119
120 return 0xFF;
121}
122
123void MMU::write(const u16 address, u8 value) {
124 write_count++;
125
126 // ROM / MBC (0x0000 - 0x7FFF)
127 if (address <= 0x7FFF) {
128 cartridge->write(address, value);
129 return;
130 }
131 // VRAM (0x8000 - 0x9FFF)
132 if (address <= 0x9FFF) {
133 ppu->write(address, value);
134 return;
135 }
136 // External RAM (0xA000 - 0xBFFF)
137 if (address <= 0xBFFF) {
138 cartridge->write(address, value);
139 return;
140 }
141 // WRAM (0xC000 - 0xDFFF)
142 if (address <= 0xDFFF) {
143 wram[address - 0xC000] = value;
144 return;
145 }
146 // Echo RAM (0xE000 - 0xFDFF) — mirrors WRAM
147 if (address <= 0xFDFF) {
148 wram[address - 0xE000] = value;
149 return;
150 }
151 // OAM (0xFE00 - 0xFE9F)
152 if (address <= 0xFE9F) {
153 ppu->write(address, value);
154 return;
155 }
156 // Unusable (0xFEA0 - 0xFEFF)
157 if (address <= 0xFEFF) {
158 return;
159 }
160 // I/O Registers
161 if (address == 0xFF00) { joypad_select = value & 0x30; return; }
162 if (address >= 0xFF04 && address <= 0xFF07) { timer->write(address, value); return; }
163 if (address == 0xFF0F) { ic->write(address, value); return; }
164 if (address == 0xFF46) { perform_dma(value); ppu->write(address, value); return; }
165 if (address >= 0xFF40 && address <= 0xFF4B) { ppu->write(address, value); return; }
166 if (address >= 0xFF00 && address <= 0xFF7F) { io_regs[address - 0xFF00] = value; return; }
167
168 // HRAM (0xFF80 - 0xFFFE)
169 if (address <= 0xFFFE) { hram[address - 0xFF80] = value; return; }
170
171 // IE (0xFFFF)
172 if (address == 0xFFFF) { ic->write(address, value); return; }
173}
174
175u8 MMU::get_current_rom_bank() const {
176 return cartridge->get_current_rom_bank();
177}
178
179void MMU::step_timer(int cycles) {
180 timer->step(cycles);
181}
182
183bool MMU::map_rom(const std::vector<u8>& rom_data) {
184 return cartridge->load_rom(rom_data);
185}
186
187void MMU::perform_dma(u8 value) {
188 u16 source_address = value << 8;
189 for (u16 i = 0; i < 0xA0; i++) {
190 write(0xFE00 + i, read(source_address + i));
191 }
192}
193
194u8 MMU::get_joypad_state() const {
195 u8 state = 0x0F;
196 if (!(joypad_select & 0x10)) state &= direction_buttons;
197 if (!(joypad_select & 0x20)) state &= action_buttons;
198 return 0xC0 | joypad_select | state;
199}
200
201void MMU::set_joypad_state(u8 action, u8 direction) {
202 u8 old_state = get_joypad_state();
203 action_buttons = action;
204 direction_buttons = direction;
205 u8 new_state = get_joypad_state();
206 // Interrupt requested if any button line goes from 1 (unpressed) to 0 (pressed)
207 if ((old_state & ~new_state) & 0x0F) {
208 ic->request_interrupt(InterruptType::Joypad);
209 }
210}
211
212std::vector<u8> MMU::dump_memory() const {
213 std::vector<u8> dump(0x10000, 0);
214
215 // Copy main arrays
216 std::copy(wram.begin(), wram.end(), dump.begin() + 0xC000);
217 std::copy(hram.begin(), hram.end(), dump.begin() + 0xFF80);
218 std::copy(io_regs.begin(), io_regs.end(), dump.begin() + 0xFF00);
219
220 // Copy cartridge & ppu data via read
221 for (u16 addr = 0x8000; addr <= 0x9FFF; ++addr) {
222 dump[addr] = read(addr);
223 }
224 for (u16 addr = 0xA000; addr <= 0xBFFF; ++addr) {
225 dump[addr] = read(addr);
226 }
227 for (u16 addr = 0xFE00; addr <= 0xFE9F; ++addr) {
228 dump[addr] = read(addr);
229 }
230 for (u16 addr = 0xFF40; addr <= 0xFF4B; ++addr) {
231 dump[addr] = read(addr);
232 }
233
234 // Save metadata
235 dump[0x00] = cartridge->get_current_rom_bank();
236 dump[0x01] = joypad_select;
237 dump[0x02] = action_buttons;
238 dump[0x03] = direction_buttons;
239 dump[0x04] = ic->get_if();
240 dump[0x05] = ic->get_ie();
241
242 return dump;
243}
244
245bool MMU::load_memory(const std::vector<u8>& dump) {
246 if (dump.size() != 0x10000) {
247 return false;
248 }
249
250 std::copy(dump.begin() + 0xC000, dump.begin() + 0xE000, wram.begin());
251 std::copy(dump.begin() + 0xFF80, dump.begin() + 0xFFFF, hram.begin());
252 std::copy(dump.begin() + 0xFF00, dump.begin() + 0xFF80, io_regs.begin());
253
254 cartridge->set_current_rom_bank(dump[0x00]);
255 joypad_select = dump[0x01];
256 action_buttons = dump[0x02];
257 direction_buttons = dump[0x03];
258 ic->set_if(dump[0x04]);
259 ic->set_ie(dump[0x05]);
260
261 // Restore state of VRAM, OAM, ERAM and registers
262 for (u16 addr = 0x8000; addr <= 0x9FFF; ++addr) {
263 write(addr, dump[addr]);
264 }
265 for (u16 addr = 0xA000; addr <= 0xBFFF; ++addr) {
266 write(addr, dump[addr]);
267 }
268 for (u16 addr = 0xFE00; addr <= 0xFE9F; ++addr) {
269 write(addr, dump[addr]);
270 }
271 for (u16 addr = 0xFF40; addr <= 0xFF4B; ++addr) {
272 write(addr, dump[addr]);
273 }
274
275 return true;
276}
277
278MMU::~MMU() = default;
279
280void MMU::reset() {
281 wram.fill(0);
282 hram.fill(0);
283 io_regs.fill(0);
284
285 joypad_select = 0x30;
286 action_buttons = 0x0F;
287 direction_buttons = 0x0F;
288 read_count = 0;
289 write_count = 0;
290
291 if (cartridge) cartridge->reset();
292 if (timer) timer->reset();
293 if (ic) ic->reset();
294 if (ppu) ppu->reset();
295
296 // Post-boot register defaults
297 write(0xFF00, 0xCF); // Joypad
298 write(0xFF04, 0xAB); // DIV
299 write(0xFF05, 0x00); // TIMA
300 write(0xFF06, 0x00); // TMA
301 write(0xFF07, 0xF8); // TAC
302 write(0xFF0F, 0xE1); // IF
303 write(0xFF40, 0x91); // LCDC
304 write(0xFF41, 0x85); // STAT
305 write(0xFF42, 0x00); // SCY
306 write(0xFF43, 0x00); // SCX
307 write(0xFF44, 0x00); // LY
308 write(0xFF45, 0x00); // LYC
309 write(0xFF47, 0xFC); // BGP
310 write(0xFF48, 0xFF); // OBP0
311 write(0xFF49, 0xFF); // OBP1
312 write(0xFF4A, 0x00); // WY
313 write(0xFF4B, 0x00); // WX
314 write(0xFFFF, 0x00); // IE
315}
Definition ppu.hpp:16
Definition timer.hpp:7