# Nova128 Compiler Memory Map Story: E01-S05 Status: frozen for the compiler (D117); verified in VICE x128 3.10 Authoritative source: `cfg/compiler.cfg`. This document explains it. The generated-program memory map is **not** frozen here. It belongs to E04-S01 and E04-S02, which must reuse the test mailbox below. ## Mappings | State | CR | RCR (low bits) | P0 | P1 | IRQs | |---|---|---|---|---|---| | System | as BASIC left it (`$00`) | as BASIC left it | `$0000` | `$0001` | caller's | | Compiler bank 0 | `$0E` | `%0110` 8K bottom common | bank 0 page `$1A` | `$0001` | masked | | Compiler bank 1 | `$4E` | `%0110` 8K bottom common | bank 0 page `$1A` | `$0001` | masked | RCR bits 6–7 (VIC bank) are always carried over from the system value. `src/compiler/mmu.s` is the only module that writes MMU registers. ## Bank 0 | Range | Size | Name | Contents | |---|---|---|---| | `$0000-$12FF` | | system | BASIC 7, KERNAL, editor, screen. Off limits except `NMINV` (`$0318`), which the compiler saves and restores | | `$1300-$18FF` | 1536 | COMMON | Copied from the PRG by `mmu_init`: MMU switches, KERNAL wrapper, NMI guard, and any code that runs while bank 1 is mapped | | `$1900-$19FF` | 256 | DISKBUF | Disk transfer buffer, visible from both banks | | `$1A00-$1AFF` | 256 | private ZP | Physical page behind logical zero page in compiler states. Not a linker area: nothing else may live here | | `$1B00-$1BEF` | 240 | SCRATCH | Shared state needed from both banks | | `$1BF0-$1BFF` | 16 | MAILBOX | Fixed test mailbox (below) | | `$1C01-$1C0C` | 12 | header | BASIC 7 stub `10 SYS 7181` | | `$1C0D-$B7FF` | 39923 | MAIN | `STARTUP` (always first, entry at `$1C0D`), CODE, RODATA, DATA, COMMON load image, BSS | | `$B800-$BFFF` | 2048 | SWSTACK | Compiler software stack | | `$C000-$FFFF` | | system ROM | KERNAL/editor in both compiler states | `$1300-$1BFF` is the C128's free application area: after boot x128 shows only the power-on fill pattern there. All of `$0000-$1FFF` is bottom common RAM, so COMMON, DISKBUF, private ZP, SCRATCH, and MAILBOX are visible from bank 1. ## Bank 1 | Range | Size | Name | Contents | |---|---|---|---| | `$0000-$1FFF` | | hidden | Covered by bottom common RAM in compiler states | | `$2000-$BFFF` | 40960 | BANK1 / `TABLES` | Names, symbols, class/interface/type records, pass workspaces | | `$C000-$FFFF` | | unmapped | System ROM is visible here instead; BASIC string space at the top of bank 1 is untouched | Bank-1 data is reached only through `mmu.s` helpers (`mmu_peek1`/`mmu_poke1` with the zero-page pointer `mmu_ptr`, and later block-copy routines), or by code in COMMON. MAIN code never runs while bank 1 is mapped, because MAIN above `$1FFF` is not visible then. BASIC 7 keeps variables in bank 1 from `$0400`. The compiler is started with `RUN`, which clears variables, so overwriting `$2000-$BFFF` loses no BASIC state. ## Zero Page In compiler states P0 points at bank 0 page `$1A`, so the compiler owns the whole logical zero page `$02-$FF` (linker area `ZP`, 254 bytes). `$00/$01` are the 8502 port and are never allocated. Modules allocate zero page only through the `ZEROPAGE` segment. Current users: `mmu_ptr` (2 bytes). System zero page stays intact. KERNAL calls switch P0 back to `$0000` for the call. While P0 is relocated, the system zero page is still readable at `$1A00-$1AFF` (the MMU swaps the two pages), e.g. `ST` at `$1A90`. ## Interrupts - **IRQ:** masked in both compiler states. The ROM IRQ path (`$FF17`) jumps via `$0314` into BASIC's handler, which uses system zero page and would corrupt the private page. `kcall` enables IRQs only for the duration of a KERNAL call, in system state. - **NMI:** cannot be masked. The ROM entry `$FF05` uses no zero page: it pushes A/X/Y/CR, sets CR=0, and jumps via `$0318`. `mmu_init` points `$0318` at `mmu_nmi_guard` (in COMMON), which reads the CIA #2 ICR, counts the event, and exits through `$FF33` (pull CR/Y/X/A, RTI). RESTORE is therefore ignored while the compiler runs. `mmu_shutdown` restores `$0318`. ## Test Mailbox Defined in `src/common/mailbox.inc`. Used by platform tests, the compiler, and generated programs, and read by the harness after the program returns. | Address | Field | Meaning | |---|---|---| | `$1BF0` | magic 0 | `$4E` (`N`) once initialized | | `$1BF1` | magic 1 | `$38` (`8`) | | `$1BF2` | producer | `$01` platform test, `$02` compiler, `$03` generated program | | `$1BF3` | status | `$01` running, `$A5` pass, `$FA` fail | | `$1BF4-$1BF5` | code | Little-endian diagnostic, fault, or failed-check code; 0 on pass | | `$1BF6-$1BF7` | address | Little-endian fault or completion address | | `$1BF8-$1BFF` | data | Eight producer-defined bytes | Protocol: 1. The producer clears all 16 bytes, writes the magic and producer, then sets status `$01` before doing any work. 2. It writes code, address, and data, then writes the final status **last**. 3. The harness polls until the magic matches and status is `$A5` or `$FA`. A status still `$01` at the timeout means the producer hung or crashed. A missing magic means it never started. ## Evidence `scripts/verify-map-skeleton.ps1` links `src/compiler/mmu.s` with `tests/platform/map-skeleton.s` using `cfg/compiler.cfg`, then: 1. Builds five variants that overflow ZP, COMMON, MAIN, SWSTACK, and TABLES by one region's worth. ld65 rejects each in the expected memory area. 2. Checks fixed placements: STARTUP and `skel_entry` at `$1C0D`, MAILBOX at `$1BF0`, COMMON within `$1300-$18FF`, TABLES within `$2000-$BFFF`. 3. Runs the skeleton in x128 and reads the mailbox: `4E 38 01 A5 00 00 … 01 4E 1A C5`. All six checks pass: | Check | What it proves | |---|---| | ZP | A store to logical ZP lands at `$1Axx`; system ZP is unchanged | | KCALL | CHROUT via `kcall_inline` from both states; private ZP and CR/P0 intact afterwards | | BANK1 | First and last TABLES bytes hold bank-1 values; bank 0 at the same addresses unchanged | | COMMON | DISKBUF/SCRATCH written in bank 0 read back in bank 1; mailbox written from bank 1 | | NMI | A CIA #2 timer NMI taken in the bank-1 state returns with CR `$4E`, P0 `$1A`, sentinel `$C5`, and the same SP | | RESTORE | After `mmu_shutdown`: CR/RCR/P0/P1/`$0318` and SP equal their entry values | Negative control: forcing P0L to `$00` in `mmu_bank0/1` makes the skeleton report FAIL with code `$000F` (ZP, KCALL, BANK1, NMI).