7.9 KiB
The Apple 1 Family — Memory Maps, ROMs, and Machine Components
A working document for making the one-family machines fully
component-described: CPU, RAM banks, and ROM images specified in the
config document, with the ROM images embedded in the binary and
selectable as builtin:<name>. Plan:
plans/20260719-03-one-machine-components.md.
Reference: Apple I Replica Creation chapter 7 (https://www.applefritter.com/replica/chapter7).
The real memory maps
Apple 1 (1976):
| Range | What |
|---|---|
$0000-$0FFF |
4KB system/user RAM (expandable on board) |
$D010-$D013 |
6820 PIA — keyboard in, display out |
$E000-$EFFF |
4KB RAM bank for Integer BASIC |
$FF00-$FFFF |
256B Woz Monitor ROM (holds the reset vector) |
BASIC was not in ROM on the real machine: every power-on it was
loaded from cassette (or typed in) into the $E000 RAM bank. CPU:
6502.
Replica 1 (Briel Computers):
| Range | What |
|---|---|
$0000-$7FFF |
32KB RAM |
$D010-$D013 |
6821 PIA — same fixed I/O |
$E000-$EFFF |
Integer BASIC, now in ROM |
$F000-$FFFF |
ROM: Krusader assembler + monitor |
CPU: the boards ship with a 65C02 (the book's text says "6502" generically; Krusader 1.3 exists in a 6502 and a 65C02 build for exactly this reason).
Do Krusader and Apple 1 BASIC overlap? (No.)
They coexist, side by side, in the Replica 1's single 8KB ROM. Verified byte-by-byte against the images in this repo:
Krusader-1.3-{6502,65C02}distributions are 8KB images mapped$E000-$FFFF, laid out as:$E000-$EFFF— Woz's Integer BASIC (4KB; identical in both builds, and identical toapple1-basic.rom);$F000-$FEFF— Krusader 1.3 itself (the builds diverge from$F00Don — the 65C02 build uses 65C02 opcodes);$FF00-$FFFF— a lightly modified Woz Monitor (per build — the two builds' monitor pages differ from each other and from the pristine WozMon, which diverges at offset$0F).
- The repo's historical
rom/krusader.romis byte-identical to the Krusader 1.3 6502 build;rom/apple1.rom(256B) is the pristine WozMon. - Finding from the comparison: today's emulated Replica 1 pairs a 65C02 CPU with the 6502 Krusader build — harmless (6502 code runs on a 65C02) but not what the real board ships; the component-described profile fixes it.
Consequence for profiles: a byte-faithful Replica 1 mounts BASIC at
$E000 and the Krusader $F000-$FFFF slice — including Krusader's
own monitor page — rather than Krusader plus a separate pristine
WozMon (which would change the $FF00 bytes away from the real ROM).
A pristine-WozMon Replica 1 stays one overlay away for whoever wants
it.
The embedded ROM set (roms/)
Name = file stem = builtin: token, mirroring the configs convention:
| File | Size | Mounts at | Contents |
|---|---|---|---|
WozMon.rom |
256B | $FF00 |
pristine Woz Monitor (today's apple1.rom) |
apple1-basic.rom |
4KB | $E000 |
Woz Integer BASIC (identical first 4K of every Krusader image) |
Krusader-1.3-6502.rom |
4KB | $F000 |
Krusader 1.3 (6502 build), its monitor page included |
Krusader-1.3-65C02.rom |
4KB | $F000 |
Krusader 1.3 (65C02 build), its monitor page included |
Provenance: Krusader is Ken Wessen's, distributed freely for the
Replica 1; Integer BASIC and the Woz Monitor are the same Apple
images the emulator has always shipped. A concatenation test pins the
decomposition: apple1-basic.rom + Krusader-1.3-6502.rom must equal
the historical 8KB krusader.rom byte-for-byte.
The only fixed hardware in the one family is the PIA at
$D010-$D013; everything else — CPU, RAM banks, ROM images — is
config. (The Apple 1's 7-bit display masking stays a machine.model
behavior: it is a property of the terminal section, not of the memory
map.)
As built (plans/20260719-03, R1–R3)
rom/renamed toroms/; the four mountable images landed as committed files (name = stem =builtin:token), the historicalapple1.rom/krusader.romretired, and the decomposition is pinned by SHA-1 provenance tests (config.rs) and machine-level byte identity (one.rs): the composedbuiltin:replica1$E000-$FFFFequals the Krusader 65C02 distribution, and the 6502-slice spelling reproduces the historicalkrusader.rommachine.config::rom_builtin/read_memory_image: a memory region'spathofbuiltin:<name>resolves against the embedded registry, never the filesystem;referenced_filesskips it, so built-in configs carry ROM references and stay self-contained.machine.cpu("6502"/"65C02") and RAM-bank regions (size, RAM only, exactly-one-of path/size) are apple1-family keys; layouts are validated — no overlaps with each other or the PIA, fit in 64K, and something must cover the reset vector ($FFFC-$FFFD). File images have unknown extents and get start-address checks plus benefit of the doubt on the vector.One::from_components(model, cpu, regions)is the single construction path (no base RAM — the board is entirely regions);One::new(model)builds from the model's built-in config, so the board is described exactly once, inconfigs/. One-familymachine.memorydescribes the whole board; absent means the model's builtin (normalizefills the options, so--print-configalways shows the full board).- The profiles are the maps above:
builtin:apple1= 6502 + 4KB@$0000+ BASIC preloaded as writable RAM in the$E000bank (cassette-faithful, minus the cassette;E000Rstarts it — pinned by a boot-to-BASIC test) + pristine WozMon;builtin:replica1= 65C02 + 32KB + ROM BASIC + the 65C02 Krusader slice, fixing the historical 6502-build-on-65C02 mismatch.
The tty / telnet frontend (plans/20260719-04, as built)
The Apple 1 is a terminal machine, so ewm one --tty runs it headless
with stdin/stdout as the keyboard and display — the local terminal,
nc, or (via scripts/systemd/, inetd-style Accept=yes socket
activation with StandardInput=socket) one fresh machine per telnet
connection on port 6502. The emulator does no networking. The build
(sdl-static-headless: static SDL, Unix console build) and server
setup are documented in scripts/systemd/README.md.
- Pacing: 20 ms ticks throttled to 1.023 MHz wall-clock (unused
tick budget is slept off, so a paste cannot sprint the machine);
keys feed one at a time gated on the PIA's one-byte latch
(
Pia::key_pending— IRQA1, cleared when the ROM reads$D010). - Mapping: a–z uppercase in; LF/CRLF → CR in; CR → CRLF out; 7-bit printables only. Ctrl bytes and a bare ESC belong to the machine (real CTRL key; ESC is the monitor's cancel-line key).
- Reset is Meta-R — the keyboard's free modifier: a lone ESC is
held ~50 ms for the
rofESC r; matched → warm reset, otherwise the ESC is forwarded. TelnetBRK/IP(send brk) also resets — immediately, flushing typed-ahead, like the real button. - Banner:
--tty-banner <path>prints a text file (CRLF- normalized) to the session before the machine says anything — visitor instructions;scripts/systemd/banner.txtis the shipped example, wired into the service unit. - Telnet (hand-rolled RFC 854 subset):
WILL ECHO+WILL SGA(character-at-a-time, server echo — so a telnet client stops echoing locally and lines appear once), other negotiations refused, subnegotiations swallowed,BRK/IP→ reset.--tty-telnet(what the systemd unit uses) announces the two WILLs proactively on connect — a passive telnet client waits for the server, so a reactive-only offer never fired and every line double-echoed. Bare--ttystays byte-clean for local terminals andnc, with a reactive fallback that still negotiates if such a client sends anIACfirst. Input EOF gets a two-emulated-second grace so the last command finishes printing.