-
Notifications
You must be signed in to change notification settings - Fork 1
Memory System
This page describes the MMU (Memory Management Unit) implementation, covering the 128KB memory layout, bank switching, Language Card, soft switch map, expansion slot ROM routing, floating bus behavior, and memory visibility rules.
- Overview
- Memory Layout
- Bank Switching
- 80STORE Mode
- Language Card
- Soft Switch Map
- Slot ROM Space
- Floating Bus
- Keyboard and Input
- Paddle Timers
- Memory Access Tracking
The MMU is implemented in src/core/mmu/mmu.cpp and mmu.hpp. It manages the full 128KB address space of the Apple IIe Enhanced (64KB main + 64KB auxiliary RAM), plus 16KB of system ROM, 8KB of character ROM, and the Language Card RAM banks. All CPU memory accesses route through the MMU, which decodes addresses and dispatches to the appropriate memory bank, soft switch handler, or expansion card.
The MMU provides two read interfaces:
-
read()-- Normal read with full side effects (soft switch triggers, watchpoint callbacks, access tracking) -
peek()-- Non-side-effecting read for the debugger and memory viewer
| Memory | Size | Storage |
|---|---|---|
| Main RAM | 64 KB | mainRAM_[65536] |
| Auxiliary RAM | 64 KB | auxRAM_[65536] |
| Language Card Bank 1 (main) | 4 KB | lcBank1_[4096] |
| Language Card Bank 2 (main) | 4 KB | lcBank2_[4096] |
| Language Card High (main) | 8 KB | lcHighRAM_[8192] |
| Language Card Bank 1 (aux) | 4 KB | auxLcBank1_[4096] |
| Language Card Bank 2 (aux) | 4 KB | auxLcBank2_[4096] |
| Language Card High (aux) | 8 KB | auxLcHighRAM_[8192] |
| System ROM | 16 KB | systemROM_[16384] |
| Character ROM | 8 KB | charROM_[8192] |
| Address Range | Default Mapping | Notes |
|---|---|---|
$0000-$01FF
|
Main zero page and stack | ALTZP switches to aux |
$0200-$03FF
|
Main RAM | RAMRD/RAMWRT for aux |
$0400-$07FF
|
Text page 1 | 80STORE overrides RAMRD/RAMWRT |
$0800-$0BFF
|
Text page 2 | RAMRD/RAMWRT for aux |
$0C00-$1FFF
|
Main RAM | RAMRD/RAMWRT for aux |
$2000-$3FFF
|
HiRes page 1 | 80STORE+HIRES overrides RAMRD/RAMWRT |
$4000-$5FFF
|
HiRes page 2 | RAMRD/RAMWRT for aux |
$6000-$BFFF
|
Main RAM | RAMRD/RAMWRT for aux |
$C000-$C00F
|
Soft switches (write) / Keyboard (read) | Write-only switches; reads return keyboard latch |
$C010-$C01F
|
Status reads / Keyboard strobe | Bit 7 = status, bits 0-6 = floating bus |
$C020 |
Cassette output toggle | |
$C030 |
Speaker toggle | |
$C050-$C05F
|
Display and annunciator switches | Both read and write trigger |
$C060-$C063
|
Cassette input, buttons | |
$C064-$C067
|
Paddle inputs | Timer-based |
$C070 |
Paddle trigger | |
$C07F |
DHIRES status | |
$C080-$C08F
|
Language Card switches | See Language Card section |
$C090-$C0FF
|
Slot I/O (16 bytes per slot) | Routes to expansion cards |
$C100-$C7FF
|
Slot ROM (256 bytes per slot) | INTCXROM can override |
$C800-$CFFF
|
Expansion ROM / Internal ROM | Shared 2KB expansion space |
$D000-$DFFF
|
ROM or Language Card bank 1/2 | |
$E000-$FFFF
|
ROM or Language Card high RAM | Contains reset/IRQ vectors |
The Apple IIe has several soft switches that control which physical memory bank is visible at a given address range:
| Switch | Address | Effect |
|---|---|---|
| RAMRD off |
$C002 (write) |
Reads from $0200-$BFFF go to main RAM |
| RAMRD on |
$C003 (write) |
Reads from $0200-$BFFF go to aux RAM |
| RAMWRT off |
$C004 (write) |
Writes to $0200-$BFFF go to main RAM |
| RAMWRT on |
$C005 (write) |
Writes to $0200-$BFFF go to aux RAM |
| Switch | Address | Effect |
|---|---|---|
| ALTZP off |
$C008 (write) |
Zero page ($00-$FF) and stack ($100-$1FF) use main RAM; Language Card uses main banks |
| ALTZP on |
$C009 (write) |
Zero page and stack use aux RAM; Language Card uses aux banks |
| Switch | Address | Effect |
|---|---|---|
| INTCXROM off |
$C006 (write) |
$C100-$CFFF uses slot card ROMs |
| INTCXROM on |
$C007 (write) |
$C100-$CFFF uses internal system ROM |
| Switch | Address | Effect |
|---|---|---|
| SLOTC3ROM off |
$C00A (write) |
$C300-$C3FF uses internal 80-column ROM; activates $C800 internal ROM |
| SLOTC3ROM on |
$C00B (write) |
$C300-$C3FF uses slot 3 card ROM (if present) |
When 80STORE is enabled ($C001), the PAGE2 switch ($C054/$C055) controls main/aux bank selection for display memory, overriding RAMRD and RAMWRT:
-
Text page 1 (
$0400-$07FF): PAGE2 off = main RAM, PAGE2 on = aux RAM -
HiRes page 1 (
$2000-$3FFF): Only when both 80STORE and HIRES are on. PAGE2 off = main RAM, PAGE2 on = aux RAM
This is the mechanism used by 80-column text and Double Hi-Res modes to access auxiliary display memory. When 80STORE is off, RAMRD/RAMWRT control bank selection for these regions normally.
The Language Card provides 16KB of RAM that overlays the ROM at $D000-$FFFF. The $D000-$DFFF region has two switchable 4KB banks.
The Language Card is controlled by soft switches at $C080-$C08F. The switch behavior depends on read vs. write access and uses a "double read" mechanism for write-enable.
| Bits | Meaning |
|---|---|
| Bit 3 | Bank select: 0 = bank 2, 1 = bank 1 |
| Bits 0-1 | Mode (see table below) |
| Bits 0-1 | Read Source | Write Enable |
|---|---|---|
| 00 | RAM | Disabled |
| 01 | ROM | Enabled (after 2 consecutive reads) |
| 10 | ROM | Disabled |
| 11 | RAM | Enabled (after 2 consecutive reads) |
| Address | Read | Bank | Read Source | Write |
|---|---|---|---|---|
$C080 |
R | 2 | RAM | Off |
$C081 |
RR | 2 | ROM | On (double read) |
$C082 |
R | 2 | ROM | Off |
$C083 |
RR | 2 | RAM | On (double read) |
$C088 |
R | 1 | RAM | Off |
$C089 |
RR | 1 | ROM | On (double read) |
$C08A |
R | 1 | ROM | Off |
$C08B |
RR | 1 | RAM | On (double read) |
Writes to Language Card switches reset the "prewrite" counter without counting toward the double-read requirement. This means:
-
LDA $C083+LDA $C083enables writes (two reads) -
LDA $C083+STA $C083+LDA $C083does NOT enable writes (the STA resets the counter) -
INC $C083DOES enable writes (INC performs two reads before its write)
When ALTZP is on, the Language Card reads/writes go to the auxiliary banks (auxLcBank1_, auxLcBank2_, auxLcHighRAM_) instead of the main banks.
These switches are triggered by writes. Reads to this range return the keyboard latch value.
| Address | Switch | Description |
|---|---|---|
$C000 |
80STORE off | Disable 80STORE |
$C001 |
80STORE on | Enable 80STORE |
$C002 |
RAMRD off | Main RAM read |
$C003 |
RAMRD on | Aux RAM read |
$C004 |
RAMWRT off | Main RAM write |
$C005 |
RAMWRT on | Aux RAM write |
$C006 |
INTCXROM off | Slot card ROMs |
$C007 |
INTCXROM on | Internal ROM |
$C008 |
ALTZP off | Main zero page |
$C009 |
ALTZP on | Aux zero page |
$C00A |
SLOTC3ROM off | Internal slot 3 ROM |
$C00B |
SLOTC3ROM on | Slot 3 card ROM |
$C00C |
80COL off | 40-column mode |
$C00D |
80COL on | 80-column mode |
$C00E |
ALTCHAR off | Primary character set |
$C00F |
ALTCHAR on | Alternate character set (MouseText) |
Status switches return the switch state in bit 7 and floating bus data in bits 0-6.
| Address | Read | Description |
|---|---|---|
$C010 |
KBDSTRB | Clear keyboard strobe; bit 7 = any key down |
$C011 |
RDLCBNK2 | Language Card bank 2 selected |
$C012 |
RDLCRAM | Language Card RAM read enabled |
$C013 |
RDRAMRD | Aux RAM read enabled |
$C014 |
RDRAMWRT | Aux RAM write enabled |
$C015 |
RDCXROM | Internal CX ROM active |
$C016 |
RDALTZP | Aux zero page active |
$C017 |
RDC3ROM | Slot 3 ROM active |
$C018 |
RD80STORE | 80STORE active |
$C019 |
RDVBLBAR | Bit 7: 0 during VBL, 1 during active display |
$C01A |
RDTEXT | Text mode active |
$C01B |
RDMIXED | Mixed mode active |
$C01C |
RDPAGE2 | Page 2 active |
$C01D |
RDHIRES | HiRes mode active |
$C01E |
RDALTCHAR | Alternate character set active |
$C01F |
RD80COL | 80-column mode active |
These switches are activated by both reads and writes.
| Address | Switch | Description |
|---|---|---|
$C050 |
TXTCLR | Graphics mode |
$C051 |
TXTSET | Text mode |
$C052 |
MIXCLR | Full-screen mode |
$C053 |
MIXSET | Mixed mode (4 lines text) |
$C054 |
LOWSCR | Page 1 |
$C055 |
HISCR | Page 2 |
$C056 |
LORES | Low-resolution mode |
$C057 |
HIRES | High-resolution mode |
$C058/$C059
|
AN0 off/on | Annunciator 0 |
$C05A/$C05B
|
AN1 off/on | Annunciator 1 |
$C05C/$C05D
|
AN2 off/on | Annunciator 2 |
$C05E/$C05F
|
AN3 off/on | Annunciator 3 (controls DHIRES) |
| Address | Device | Description |
|---|---|---|
$C000 |
Keyboard | Read: key latch (bit 7 = key available) |
$C010 |
Keyboard Strobe | Read: clear strobe, return any-key-down in bit 7 |
$C020 |
Cassette Out | Toggle cassette output |
$C030 |
Speaker | Toggle speaker state |
$C060 |
Cassette In | Cassette input (always low, no cassette) |
$C061 |
Button 0 | Open Apple key (bit 7 = pressed) |
$C062 |
Button 1 | Closed Apple key (bit 7 = pressed) |
$C063 |
Button 2 | Shift key / button 2 |
$C064-$C067
|
Paddles 0-3 | Timer countdown (bit 7 = counting) |
$C070 |
PTRIG | Reset all paddle timers |
$C07F |
IOUDIS/DHIRES | Bit 7 = DHIRES status (AN3 inverted) |
Each expansion slot has 16 bytes of I/O space:
| Address Range | Slot |
|---|---|
$C080-$C08F
|
Language Card (special) |
$C090-$C09F
|
Slot 1 |
$C0A0-$C0AF
|
Slot 2 |
$C0B0-$C0BF
|
Slot 3 (80-column, built-in) |
$C0C0-$C0CF
|
Slot 4 |
$C0D0-$C0DF
|
Slot 5 |
$C0E0-$C0EF
|
Slot 6 |
$C0F0-$C0FF
|
Slot 7 |
Reads and writes to slot I/O are dispatched to the installed card's readIO() / writeIO() methods with a 4-bit offset (0-15).
Each slot has 256 bytes of ROM space at $CN00-$CNFF. Accessing a slot's ROM page activates that card's expansion ROM at $C800-$CFFF. The card's readROM() method is called with an 8-bit offset.
This 2KB region is shared among all expansion cards. Only one card's expansion ROM can be active at a time, determined by the last slot ROM page accessed. The activeExpansionSlot_ variable tracks which card currently owns this region.
A read from $CFFF deactivates the current expansion ROM after returning its data.
Slot 3 is normally the built-in 80-column firmware. When SLOTC3ROM is off (default), reads to $C300-$C3FF return internal system ROM and also activate the internal ROM for $C800-$CFFF (via intc8rom). When SLOTC3ROM is on, the region uses the slot 3 card ROM if one is installed.
When the CPU reads an unoccupied or unmapped I/O address, it gets the "floating bus" value -- whatever byte the video circuitry is currently fetching from memory. The MMU computes this from the current CPU cycle count:
- Calculate the frame cycle position:
cycles % CYCLES_PER_FRAME - Derive the scanline and horizontal position
- During horizontal blanking (cycles 0-24 of each scanline), return
$00 - During vertical blanking, wrap the scanline into the visible range
- Compute the address the video hardware would be reading based on the current video mode (text/lores or hires) and display page
- Return the byte at that address from the appropriate memory bank
This behavior is important for some copy protection schemes and timing-sensitive software that reads floating bus values to detect the beam position.
Reading $C000 returns the keyboard latch. Bit 7 is set when a key has been pressed and not yet read. Bits 0-6 contain the ASCII code of the key (or Apple II-specific code). The latch retains its value until explicitly cleared.
Reading $C010 clears the keyboard strobe (bit 7 of the latch) and returns the any-key-down status in bit 7 with the key code in bits 0-6. Writing to $C010 also clears the strobe.
Button state is returned in bit 7 (pressed = $80, released = $00). Bits 0-6 contain floating bus data. The buttons map to:
| Address | Button | Physical Key |
|---|---|---|
$C061 |
Button 0 | Open Apple (left Alt/Option) |
$C062 |
Button 1 | Closed Apple (right Alt/Option) |
$C063 |
Button 2 | Shift key state |
The Apple IIe uses a timer-based paddle reading mechanism:
- Software writes to
$C070(PTRIG) to start all four paddle timers - The MMU records the current CPU cycle count as
paddleTriggerCycle_ - Reading
$C064-$C067returns bit 7 = 1 while the timer is running, 0 when expired - Timer duration is
paddleValue * 11 cycles(approximately 11 cycles per unit) - Paddle values range from 0-255, with 128 as center
This means a centered joystick axis produces a timer of about 1,408 cycles (128 * 11).
The MMU includes optional access tracking for the debugger heat map visualization:
-
enableTracking(bool)-- Enable/disable tracking -
readCounts_[65536]-- Per-address read access counters (saturate at 255) -
writeCounts_[65536]-- Per-address write access counters -
decayTracking(amount)-- Reduce all counters by a fixed amount for real-time decay -
clearTracking()-- Reset all counters to zero
When tracking is enabled, every read() and write() call increments the corresponding counter for that address. The JavaScript heat map window periodically decays and reads these counters to produce a visual representation of memory access patterns.
- Architecture-Overview -- How the MMU fits into the emulator
- CPU-Emulation -- CPU that reads/writes through MMU callbacks
- Video-Rendering -- Video modes controlled by soft switches
- Expansion-Slots -- Card architecture using MMU slot system