build-a-game-boy-emulator / lesson-35.md
Lesson 35 · Bit ops & the cartridge

The memory map

Today you split the flat memory array into proper address regions - ROM, VRAM, work RAM, echo RAM, OAM, I/O, and high RAM - so reads and writes dispatch to the right backing store. This region-aware routing is what lets video RAM and I/O registers behave differently from plain memory later on.

The goal

Route memory reads and writes by address region so echo RAM correctly mirrors work RAM.

Start here - the target
TO DO
Scenario: Echo RAM mirrors work RAM
Giventhe memory routes by region
When0x55 is written to work RAM at 0xC000
Thenreading echo RAM at 0xE000 also returns 0x55
Anda write to 0xE001 is visible when reading 0xC001
Background

The flat array from lesson 3 got you this far, but the real address space is divided into regions, each with its own behavior: cartridge ROM at 0x00000x7FFF, video RAM at 0x8000, work RAM at 0xC000, sprite memory (OAM) at 0xFE00, I/O registers at 0xFF00, and high RAM at 0xFF80. Reads and writes should dispatch by address to the right backing store. (Keep the ROM region plain and writable for now - it stays a normal store until bank switching on lesson 37 turns writes there into control signals. A single flat backing array with an address-translation switch is a fine minimal implementation.)

The quirky one to prove today is echo RAM: addresses 0xE0000xFDFF transparently mirror work RAM at 0xC000, a hardware accident that games occasionally rely on. Routing it correctly forces your read/write functions into a region switch, which is exactly the structure you need so that, next, video RAM and I/O registers can behave differently from plain memory.

Make it work
// Keep the flat backing array - just translate the address first. Echo RAM
// (0xE000-0xFDFF) mirrors work RAM, so fold it down before indexing:
func (m *Memory) resolve(addr uint16) uint16 {
if addr >= 0xE000 && addr < 0xFE00 {
return addr - 0x2000 // echo RAM -> work RAM
}
return addr
}
// Read/Write then index m.data[m.resolve(addr)]; every region keeps working.
Further Reading

The Game Boy memory map - ROM, VRAM, WRAM, echo, OAM, I/O, and HRAM regions.

CheckpointDONE
Reads and writes now dispatch to the right region. Commit and stop here.