Skip to content

Register map

Canonical Atari registers + the rp-XT chiplet-extension allocation owned by xt6502. The chiplet-extension layout follows the README’s “Proposed map of register-space over and above ANTIC and C|GTIA” — all addresses cross-checked against AtariAge’s canonical hardware-register list (forums.atariage.com/topic/157241) to ensure no canonical register is shadowed.

For the wider $D0xx-$D7xx I/O space — including the third-party / expansion usage (PBI devices, U1MB, SIDE, MyIDE, VBXE, R-Time 8, …) that constrains where new XT registers can safely live — see the Appendix: ecosystem usage at the end of this page. New XT allocations are placed in ranges that table shows free; e.g. the $D5xx block below sits in the $D5C0-$D5DF gap between R-Time 8 ($D5B8-$D5BF) and SIDE/SDX ($D5E0-$D5FF).

XT register-unlock (the native decode is opt-in)

Section titled “XT register-unlock (the native decode is opt-in)”

Every XT register group below is gated by an 8-bit unlock register: the NATIVE (6502/ANTIC-side) decode only fires when the group’s bit is set, so a machine boots and behaves bone-stock until something deliberately unlocks it. PL reset → 0x00 (fully locked / stock); a 6502-only reset does NOT clear it. The A9/GP0-bridge path is never gated. Two write ports: the A9 (GP0 bridge offset 0x20, the authority) and the 6502 ($D1DF, self-unlock). Bits: 0 ANTIC_CHIPLET, 1 SPRITE, 2 BLITTER (+$D4CA turbo), 3 BANK, 4 GEM (reserved), 5 KBD (reserved — kbd-inject is bridge-only). When a group is locked the address falls through to the stock decode (ANTIC mirror in $D4xx, open bus / cart in $D5xx).

xt6502 owns the entire page. Real-silicon mirror behaviour is preserved on $D000-$D07F; the upper half ($D080-$D0FF) is the chiplet-extension window with mirroring broken.

AddrNamePurpose
$D000HPOSP0Player 0 horizontal position (color clocks).
$D001HPOSP1Player 1 horizontal position.
$D002HPOSP2Player 2 horizontal position.
$D003HPOSP3Player 3 horizontal position.
$D004HPOSM0Missile 0 horizontal position.
$D005HPOSM1Missile 1 horizontal position.
$D006HPOSM2Missile 2 horizontal position.
$D007HPOSM3Missile 3 horizontal position.
$D008SIZEP0Player 0 size: 00=1× / 01=2× / 10=1× / 11=4×.
$D009SIZEP1Player 1 size.
$D00ASIZEP2Player 2 size.
$D00BSIZEP3Player 3 size.
$D00CSIZEMAll four missile sizes (2 bits each).
$D00DGRAFP0Player 0 shape pattern (DMA-disabled writes).
$D00EGRAFP1Player 1 shape pattern.
$D00FGRAFP2Player 2 shape pattern.
$D010GRAFP3Player 3 shape pattern.
$D011GRAFMMissile shape pattern.
$D012COLPM0Player/missile 0 colour.
$D013COLPM1Player/missile 1 colour.
$D014COLPM2Player/missile 2 colour.
$D015COLPM3Player/missile 3 colour.
$D016COLPF0Playfield 0 colour.
$D017COLPF1Playfield 1 colour.
$D018COLPF2Playfield 2 colour.
$D019COLPF3Playfield 3 colour.
$D01ACOLBKBackground / border colour.
$D01BPRIORPriority + GTIA mode select (bits 6-7).
$D01CVDELAYPer-channel vertical delay (P/M).
$D01DGRACTLPlayer/missile DMA enable + latch control.
$D01EHITCLRWrite strobe — clears collision latches.
$D01FCONSOL_WConsole-key output side / speaker bit.
AddrNamePurpose
$D000M0PFMissile 0 → playfield collision latch.
$D001M1PFMissile 1 → playfield.
$D002M2PFMissile 2 → playfield.
$D003M3PFMissile 3 → playfield.
$D004P0PFPlayer 0 → playfield.
$D005P1PFPlayer 1 → playfield.
$D006P2PFPlayer 2 → playfield.
$D007P3PFPlayer 3 → playfield.
$D008M0PLMissile 0 → player.
$D009M1PLMissile 1 → player.
$D00AM2PLMissile 2 → player.
$D00BM3PLMissile 3 → player.
$D00CP0PLPlayer 0 → player.
$D00DP1PLPlayer 1 → player.
$D00EP2PLPlayer 2 → player.
$D00FP3PLPlayer 3 → player.
$D010TRIG0Joystick trigger 0 (serial-pushed by rp-POKEY/PIA).
$D011TRIG1Joystick trigger 1.
$D012TRIG2Joystick trigger 2.
$D013TRIG3Joystick trigger 3.
$D014PALbit 0 = PAL, bit 1 = NTSC sense (serial-pushed).
$D015reservedRead 0.
$D016reservedRead 0.
$D017reservedRead 0.
$D018reservedRead 0.
$D019reservedRead 0.
$D01AreservedRead 0.
$D01BreservedRead 0.
$D01CreservedRead 0.
$D01DreservedRead 0.
$D01EreservedRead 0.
$D01FCONSOL_RConsole-key state (serial-pushed by rp-syscontroller).

Real silicon mirrors $D000-$D01F on every 32-byte boundary up to $D07F. fpga-antic preserves this mirror.

Reserved. No assignments yet — mirror behaviour does NOT apply here. Reads return 0; writes are ignored. Future GTIA-side extensions (player palette indexing, full-colour P/M) will land here; see the README’s “Future work” section.

AddrNamePurpose
$D400DMACTLDMA control. Bits: 0-1 playfield width, 2 missile DMA, 3 player DMA, 4 PM resolution (1=line, 0=2line), 5 DL DMA enable.
$D401CHACTLCharset control. Bits: 0 vertical reflect, 1 inverse video, 2 inverse blank.
$D402DLISTLDisplay-list pointer low byte.
$D403DLISTHDisplay-list pointer high byte.
$D404HSCROLHorizontal scroll, 0..15 colour clocks.
$D405VSCROLVertical scroll, 0..15 scan lines.
$D406reserved(Real ANTIC: PMBASE high byte mirror — not used in rp-XT, snoop tag handles PM region.)
$D407PMBASEPlayer/missile data page base (×256).
$D408reservedReads $FF; writes ignored.
$D409CHBASECharset RAM page base (×256).
$D40AWSYNCWait for horizontal sync (write strobe).
$D40BVCOUNTVertical line counter (read-only; bit 0 ignored, granularity = 2 scan lines).
$D40CPENHLight pen horizontal. (rp-XT stub: no lightpen — reads $FF.)
$D40DPENVLight pen vertical. (rp-XT stub: no lightpen — reads $FF.)
$D40ENMIENNMI enable: bit 6 VBI, bit 7 DLI, bit 5 RNMI.
$D40FNMIST / NMIRESRead = NMI status. Write = clear status.

Per real ANTIC (Altirra §4.1), every unassigned or write-only address in the canonical $D400-$D40F range reads back $FF — only VCOUNT ($D40B) and NMIST ($D40F) return live data. The chiplet-extension window ($D480-$D4FF) below differs: its unassigned addresses read 0.

Placeholder for a future second ANTIC instance on the same slot. Do not reuse for any other purpose.

Mirror of $D400-$D40F on every 16-byte boundary up to $D47F.

Layout per the README’s “Proposed map of register-space over and above ANTIC and C|GTIA”. Mirror behaviour does NOT apply here.

AddrNameR/WPurpose
$D480CLOCK_MULTRBus clock multiplier vs the NTSC 1.79 MHz baseline. Pushed in by rp-syscontroller over the inter-chip serial link during boot configuration; readable once $D7FF has fired.
$D481MODER/Wbit 0 MODE_SNOOP — 1 = snoop (default at /G_RST), 0 = legacy DMA. bit 1 CPU_INTERNAL — 1 = the internal SALLY drives the bus (software sets this once OS-B is loaded and locked; 0 = external CPU at boot). bit 2 reserved. bit 3 AUTO_PHI2_ON_EXTIRQ (M-PBI) — 1 = a falling edge on /EXTIRQ forces the bus clock back to phi2 (CLOCK_MULT = 1) until the PBI device deasserts /EXTIRQ. bits 4-7 are read-only PBI sense lines on read-back (bit 4 /RD4, bit 5 /RD5, bit 6 /MPD, bit 7 /EXTIRQ); writes to those bits are ignored.
$D482OUTPUT_MODER/WCompositing-mode selector (8-bit, defaults $00). HDMI scan-out is always 1920×1080@60 — the SiI9022A is configured once at boot and never re-initialised. bit 1 OUT_FULLRES — 0 = legacy ANTIC-compat (the XL framebuffer integer-scaled and centred in the 1080p frame with pillarbox/letterbox bars), 1 = native full-1920×1080 desktop (the GEM desktop + blitter + sprite engine target the full raster). bit 0 and bits 2-7 reserved. The register latches and reads back, but the plane compositor takes its geometry from build-time parameters and does not yet act on it.
$D483PAL_RR/WRed value (0..255) for the palette entry at PAL_IDX.
$D484PAL_GR/WGreen value.
$D485PAL_BR/WBlue value.
$D486PAL_IDXR/WPalette index (0..255) the next R/G/B trio targets.
$D487reserved-Reserved for extension to palette index (e.g. second palette page). Reads 0; writes ignored.
$D488DRAW_OPR/WDRAW opcode (BUS_DRAW_OP_*). M17-2. Software stages this + up to 5 args at $D489-$D492 then strobes DRAW_GO. op[7] is the fill flag for paired closed-shape primitives (RECT, OVAL, ARC) — set high to render filled instead of outline. FILL ($03) is a separate flood-fill primitive, not “RECT with op[7]=1”.
$D489DRAW_ARG0_LOR/WArg 0, low byte. (Args are little-endian 16-bit; semantics depend on DRAW_OP — for LINE: x0/y0/x1/y1/colour; for RECT (outline or fill): x/y/w/h/(colour+mode); for FILL (flood): x/y/colour, args 3-4 unused. See wire-protocol.md DRAW table.)
$D48ADRAW_ARG0_HIR/WArg 0, high byte.
$D48BDRAW_ARG1_LOR/WArg 1, low byte.
$D48CDRAW_ARG1_HIR/WArg 1, high byte.
$D48DDRAW_ARG2_LOR/WArg 2, low byte.
$D48EDRAW_ARG2_HIR/WArg 2, high byte.
$D48FDRAW_ARG3_LOR/WArg 3, low byte.
$D490DRAW_ARG3_HIR/WArg 3, high byte.
$D491DRAW_ARG4_LOR/WArg 4, low byte.
$D492DRAW_ARG4_HIR/WArg 4, high byte.
$D493DRAW_GOR/WWrite any value to commit the staged DRAW for dispatch to rp_tx. Read returns {7'h00, pending} — software MUST poll DRAW_GO[0]==0 before staging the next command (back-to-back GO writes while pending=1 are lost).
$D494DRAW_ARG5_LOR/WArg 5, low byte. Used by 7-beat opcodes — ARC’s start_angle (M18-2) and BEZIER_TO’s mid control point (M18.1).
$D495DRAW_ARG5_HIR/WArg 5, high byte.
$D496DRAW_ARG6_LOR/WArg 6, low byte. ARC’s end_angle / BEZIER_TO’s colour.
$D497DRAW_ARG6_HIR/WArg 6, high byte.
$D498DRAW_ARG7_LOR/WArg 7, low byte. Used by BEZIER (9-beat opcode) for the y-coord of the 4th control point.
$D499DRAW_ARG7_HIR/WArg 7, high byte.
$D49ADRAW_ARG8_LOR/WArg 8, low byte. BEZIER’s colour.
$D49BDRAW_ARG8_HIR/WArg 8, high byte.
$D49COS_ROM_ADDR_LOR/WChiplet OS-ROM loader (SALLY-driven; distinct from the PS/AXI sally_rom_loader). Target write-address low byte.
$D49DOS_ROM_ADDR_HIR/WTarget write-address high byte.
$D49EOS_ROM_DATAR/WWrite a byte → committed to memory at OS_ROM_ADDR, then OS_ROM_ADDR auto-increments (unless WRITE_LOCK set). Read returns the last byte written.
$D49FOS_ROM_CTLR/Wbit 0 = WRITE_LOCK: once set, further OS_ROM_DATA writes are ignored (ROM-load disabled).
$D4A0-$D4FFsprite engine + 2D blitter-Fully allocated — see the section below.

$D4A0-$D4FF — sprite engine + 2D blitter (XT hardware)

Section titled “$D4A0-$D4FF — sprite engine + 2D blitter (XT hardware)”

This range is fully allocated — sprite engine, the SuperSally/A9 2D blitter, keyboard injection, and the SALLY turbo control all live here, and they share the same $D4xx decode space. New allocations MUST avoid the pages below. (History: putting the blitter’s DDR surface descriptors on $D4Dx silently collided with the sprite engine and corrupted the running 6502 — hence this section, and why the descriptors now live on $D4Ex.)

PageOwnerUse
$D4A0-$D4AFsprite enginePer-sprite control registers (fpga_xt_top sprite_reg_we snoops $D4Ax).
$D4B0-$D4BF2D blitter — page BDST geometry, pattern, CMD, STATUS, raster op (table below).
$D4C0-$D4CF2D blitter — page CSRC geometry, FLAGS, SEQ; overlaid with $D4CA SEQ_HI-read / turbo-write, and keyboard-inject $D4CB/$D4CD/$D4CF (table below).
$D4D0-$D4DFsprite engineIndexed sprite descriptor + collision + control (sprite_reg_we snoops $D4Dx). Blitter does NOT decode this page.
$D4E0-$D4EF2D blitter — page ESRC/DST DDR surface descriptors for SRC_BLIT (table below). $D4EC-$D4EF free.
$D4F0-$D4FFreservedFree. Reads 0; writes ignored.

2D blitter registers ($D4Bx / $D4Cx / $D4Ex)

Section titled “2D blitter registers ($D4Bx / $D4Cx / $D4Ex)”

The blitter shares its register bus between the native SALLY/ANTIC path and the A9 (via the GP0 AXI-Lite bridge — see below). Byte-wide registers.

AddrNameR/WPurpose
$D4B0-$D4B7DST_{X,Y,W,H}_{LO,HI}WDestination geometry. For LINE: W/H = signed DX/DY.
$D4B8 / $D4B9PAT_PHASE_{X,Y}WPattern phase (low 5 bits).
$D4BAPAT_LOG_WWlog2(pattern width); writing resets the PAT_DATA load pointer.
$D4BBPAT_DATAWPattern byte stream (R,G,B,A; auto-advances). For SRC_BLIT coverage, the 1×1 pattern = the text colour.
$D4BCCMDWFire: 01=RECT_FILL, 02=LINE_DRAW, 03=BLOCK_BLIT, 04=SCALED_BLIT, 05=FONT_RASTER (legacy coverage-BRAM, unused), 06=bilinear scaled, 07=SYNC, 08=SRC_BLIT (DDR→DDR coverage/RGBA blend).
$D4BDSTATUSRbit0 busy, bit1 queue-full, bit2 pat/font-load-blocked.
$D4BEPAT_LOG_HWlog2(pattern height).
$D4BFRASTER_OPWGEM raster op [3:0] for BLOCK_BLIT.
$D4C0-$D4C7SRC_{X,Y,W,H}_{LO,HI}WSource geometry (blit / scaled / SRC_BLIT src rect).
$D4C8FLAGSWbit0 BLEND, bit1 BILINEAR, bit2 SRC_DDR, bit3 SRC_COV, bit4 SRC_AOVER, bit5 DST_DDR (SRC_BLIT mode).
$D4C9SEQ_LORSYNC sequence counter, low byte.
$D4CASEQ_HI / CLOCK_MULTR / WRead = SYNC counter high byte. Write = SALLY turbo multiplier (clock_mult, decoded in fpga_xt_top).
$D4CB(kbd_break)WKeyboard injection — pulses the 6502 BREAK (decoded in fpga_xt_top, not a blitter reg).
$D4CD(kbd_release)WKeyboard injection — key release.
$D4CEFONT_DATAWLegacy coverage-BRAM byte stream (the FONT_RASTER path; superseded by SRC_BLIT).
$D4CF(kbd_inject)WKeyboard injection — pushes a KBCODE + IRQ to POKEY.
$D4E0-$D4E3SRC_BASEWSRC_BLIT source surface base address (32-bit, byte-stream LSB→MSB). Latched while !busy.
$D4E4-$D4E5SRC_STRIDEWSource surface row stride in bytes.
$D4E6-$D4E9DST_BASEWSRC_BLIT dest surface base address.
$D4EA-$D4EBDST_STRIDEWDest surface row stride in bytes.

GP0 AXI-Lite bridge (PS view, XT_BLITTER_BASE = 0x43C00000)

Section titled “GP0 AXI-Lite bridge (PS view, XT_BLITTER_BASE = 0x43C00000)”

The A9 reaches the blitter’s $D4xx registers through axi_blitter_bridge over the Zynq GP0 port. A 64-byte byte-offset window maps to four $D4 pages via a 2-bit page select (awaddr[5:4]):

AXI byte offset→ register page
0x00-0x0F$D4Bx
0x10-0x1F$D4Cx
0x20-0x2F$D4Dx (sprite engine — do NOT drive from the A9)
0x30-0x3F$D4Ex (SRC_BLIT descriptors)

The bridge intercepts a few offsets itself rather than forwarding them:

OffsetDirectionMeaning
0x1Cwritegp0_ctrl (NOT a blitter reg): bit0 = HDMI test-pattern/bars enable, bits[3:1] = XL scale.
0x1Creaddiag_word (PL debug; read/write share the offset).
0x20writext_unlock (NOT a blitter reg): the XT register-unlock mask (A9 = authority). Maps to $D4D0 on the native bus, which the blitter ignores and sprites don’t see over the bridge, so the offset is free. See the unlock section above.
0x20readxt_unlock effective value (incl. any 6502 self-unlock at $D1DF).
0x0DreadSTATUS (replicated across all 4 byte lanes).
0x19 / 0x1AreadSEQ_LO / SEQ_HI.
0x1Ereadclock_mult read-back (verify a speed write latched).
0x14 / 0x18readdiag3 (read-path counters) / diag2 (production-chain counters).
0x0C / 0x10readdiag5 (HP0 first-AR addr) / diag4 (HP3/XL first-AR addr).
0x04 / 0x08readdiag6 (HP2 read-probe status) / diag7 (last rdata).

The 256-entry full-RGB extended palette is exposed as the four-byte record at $D483-$D486 (R / G / B / IDX). A write to any of these four ports updates palette entry PAL_IDX with the latest R/G/B latched in the chip — order-independent. Per the README:

any change will update the palette index for all 4 parameters. This is not write-order dependent.

So programs can stream R G B IDX++ quartets in any internal order and the palette will be coherent after every write. The most common sequences are likely to be:

  • IDX, R, G, B (clear pattern; each colour fully written before IDX increments).
  • R, G, B, IDX (write the colour, then commit-by-IDX).

Both work identically.

The legacy hardware palette (COLBK / COLPF0-3 / COLPM0-3) is stored separately and indexes into the full 256-entry palette via its own small lookup; software written for canonical Atari hardware is unaffected.

$D5xx — XT extension (bank select + GEM service)

Section titled “$D5xx — XT extension (bank select + GEM service)”

Registers added by the XT extended architecture in the CCTL I/O gap. Mirror behaviour does NOT apply here. The XT owns only $D5C0-$D5DF — the free gap between R-Time 8 ($D5B8-$D5BF) and SIDE1/2 / SDX / U1MB ($D5E0-$D5FF); see the ecosystem Appendix at the end of this page. Do NOT extend XT registers into $D5E0+.

Allocating a new XT register — check these first, in order:

  1. Take it from $D5CF or $D5D5-$D5DF. Those are the only free bytes the XT owns. $D5CF is the last one inside the decoded $D5C0-$D5CF slot, so it costs no new decode — spend it last.
  2. Never $D0xx/$D2xx/$D3xx/$D4xx. Those pages are zeroed at warm- and cold-start (except $D301), so anything with a write side effect there — a doorbell, a FIFO port — gets strobed 256 times by the OS’s clear loop on every boot. They also owe mirror fidelity to the stock chips.
  3. Never $D6xx/$D7xx. It is PBI space, and both the PBI bridge (docs/OS/expansion-options.md — slots, /CARDSEL, the $D1FF device select, the /MPD $D800-$DFFF shadow) and VBXE compatibility ($D640-$D65F / $D740-$D75F install windows) need it clear.
  4. Prefer a port over a window. A byte-wide auto-incrementing data port moves an arbitrary payload through one address; an aperture over $4000-$5FFF costs 8 KB of the guest’s RAM and everything that follows from that. If the port has a read or write side effect it MUST fire exactly once per machine cycle — gate it like pk_re (fid_sub == 49 && fid_rdy), or a stalled/replayed fidelity-core cycle re-fires it.
  5. Cross-check the ecosystem Appendix at the end of this page before claiming anything outside $D5C0-$D5DF.
AddrNameR/WPurpose
$D5C0CODE_BANKR/WCode bank selector — selects the page mapped into the $6000-$9FFF code window (16 KB). 8-bit (256 banks); bank 0 = flat BRAM. Readable (the scheduler saves/restores it). Relocated off zero page (was BASIC VNTP) into the CCTL gap.
$D5C1DATA_BANKR/WData bank selector — selects the page mapped into the $A000-$CFFF data window (12 KB). 8-bit (256 banks); bank 0 = flat BRAM; bank $FF = the shared GEM arena (see doorbell below). Readable.
$D5C2reserved-Reserved. Reads 0; writes ignored.

$D5C3-$D5C8 — screen banking + math/mailbox aperture

Section titled “$D5C3-$D5C8 — screen banking + math/mailbox aperture”

Decoded in hdl/sally_mem.sv (is_scrn_* / is_math_*); read-back is served through the shared CCTL slot, so addr[3:0] selects within $D5C0-$D5CF. Details: docs/video/screen-banking.md, the math co-processor page.

AddrNameR/WPurpose
$D5C3SCRN_CPU_BANKR/WScreen bank for the CPU’s view of $4000-$5FFF. 0 = the flat 64 KB shadow.
$D5C4SCRN_ANTIC_BANKR/WScreen bank for ANTIC’s view of $4000-$5FFF (independent of the CPU’s).
$D5C5SCRN_STATR{7'b0, ready}.
$D5C6MATH_CTLR/Wbit 0 = MAP: overlay the math page / SIO mailbox on $4000-$5FFF (CPU view only — ANTIC never sees it). Wins over $D5C3. Hazard: while MAP is set, $4000-$5FFF is not the guest’s RAM, so an interrupt taken in that window runs with the aperture in place — see the project next-steps roll-up.
$D5C7MATH_EXEC / MATH_STATR/WWrite = doorbell to the A9 (any value). Read = {5'b0, chunk_ready, busy, done}.
$D5C8MATH_CHUNKR/WBacking chunk index; the SIO stub writes $FF (the mailbox is always resident).

Read-only LE u32: clk_sally cycles from the $D5C7 EXEC write to done rising. Raw 100 MHz fabric cycles (not step-gated), so it is turbo-independent — count/100 = µs. Latched, static between ops.

AddrNameR/WPurpose
$D5C9-$D5CCMATH_LATROp-latency counter, little-endian u32.
$D5CDSIO_IDXR/WSIO mailbox byte index. Write sets it (8 bits; the internal counter is 9, and auto-increment carries into the top bit); read returns the current low 8.
$D5CESIO_DATR/WThe mailbox byte at SIO_IDX. Every access — read OR write — post-increments the index, so one $D5CD write walks a whole payload. The read is side-effecting, so its strobe is generated at the top level gated like pk_re (exactly once per advancing cycle); a stalled fidelity-core presentation must not advance the index.
$D5CFfree-Free — the last unallocated byte in the decoded $D5C0-$D5CF slot.

The XL issues VDI/AES calls to the ARM-A9 GEM service through this block: stage the parameter block + arrays in bank $FF (the $A000-$CFFF data window), then drive these registers. Synchronous / blocking. Full protocol in the GEM service pages.

AddrNameR/WPurpose
$D5D0GEM_DISPATCHWNamespace select: 115 ($73) = VDI, 200 ($C8) = AES (ST TRAP #2 d0 convention).
$D5D1GEM_PBLK_LOWParameter-block address within the $A000-$CFFF window, low byte. Bank is implicitly $FF.
$D5D2GEM_PBLK_HIWParameter-block address, high byte.
$D5D3GEM_GO / GEM_STATUSR/WWrite (any value) rings the doorbell to the A9. Read returns status: bit 7 BUSY (1 while the A9 is servicing), bit 0 ERR, bits 6-1 result code. Poll BUSY=0 for completion.
$D5D4GEM_ABIVERRGEM service ABI version / magic for capability probe. $00 = no service present.
$D5D5-$D5DFreserved-Reserved for future GEM / service registers (XT window ends at $D5DF$D5E0+ is SIDE/SDX). Reads 0; writes ignored.

Appendix — Atari I/O-space ecosystem usage ($D0xx-$D7xx)

Section titled “Appendix — Atari I/O-space ecosystem usage ($D0xx-$D7xx)”

A reference catalogue of how the $D0xx-$D7xx hardware-register space is used across the Atari 8-bit ecosystem: stock chips plus third-party expansions (U1MB, SIDE, MyIDE, VBXE, PBI devices, the 1090 XL, …). Collected from community sources (AtariAge and similar) — treat as a best-effort guide, not an exhaustive spec. It exists to keep new XT allocations clear of established usage: e.g. the XT $D5xx block above sits in the $D5C0-$D5DF slot this table shows is free.

RangeUse
$D000-$D01FCTIA / GTIA (stock)
$D020-$D03Freserved — second GTIA
$D040-$D05Freserved — third GTIA
$D080-$D0FFVBXE soft-reset area
RangeUse
$D100-$D1FFPBI (general)
$D100-$D107MyIDE Internal
$D100-$D1BEU1MB RAM
$D1BFU1MB PBI bankswitching
$D100, $D104, $D108, $D110, $D1141400XL / 1450XLD modem, voice & disk interface
$D170-$D171, $D17C, $D1BC, $D1BE, $D1C0BlackBox
$D1C0-$D1C1SmartIDE LCD
$D1B0-$D1C7Atari speech / modem / disc registers
$D1B0, $D1B8unreleased 800XLD floppy controller
$D1C8-$D1CEAtari reserved
$D1CFread alternate interrupt register (1450 XLD only)
$D1D1-$D1DD1090 XL Amy boards 1-4
$D1DFXT register-unlock — claimed here (6502 self-unlock write port; R/W). In the documented-free gap between the Amy block ($D1D1-$D1DD) and the MIO ACIA ($D1E0+); nothing stock writes PBI space, so the location is the protection. See the unlock section near the top.
$D1E0-$D1E3MIO / 1090 XL serial-parallel ACIA0
$D1E4-$D1E71090 XL serial-parallel ACIA1
$D1E8-$D1EF1090 XL serial-parallel registers
$D1F0-$D1F71090 XL Z80 / alternate-CPU registers
$D1F8-$D1FD1090 XL 80-column video card
$D1FE1090 XL RAM bank-select
$D1FFPBI device enable (W) / IRQ mask (R)
RangeUse
$D200-$D20FPOKEY (stock)
$D210-$D21Fsecond POKEY (GUMBY)
$D280-$D283Covox (new location)
RangeUse
$D300-$D303PIA 6520 (stock)
$D310-$D313second PIA 6520
$D320-$D323VIA 6522
$D380-$D381U1MB configuration registers
$D383-$D384U1MB status registers
$D3E2U1MB SDX real-time clock (SPI)
RangeUse
$D400-$D40FANTIC (stock; $D406, $D408 unused)
$D410-$D41Freserved — second ANTIC
RangeUse
$D5004-bit audio samplers (e.g. ADC0804)
$D500-$D507MyIDE External
$D5B8-$D5BFR-Time 8
$D5C0-$D5DFXT extension — claimed here (bank select + GEM doorbell; see the $D5xx section above)
$D5E0SDX bankswitching
$D5E0-$D5E1U1MB SDX bankswitching enable / disable
$D5E0-$D5FFSIDE 1/2 registers (banking, DS1305 RTC, IDE, ID)
RangeUse
$D600-$D7FFPBI / 1400XL-1450XLD parallel-device RAM (Atari official)
$D600-$D603Covox
$D600-$D6FFMIO RAM / BlackBox RAM
$D640-$D65FVBXE D6 install
$D740-$D75FVBXE D7 install
  • Pages $D0, $D2, $D3, $D4 are zeroed at warm- and cold-start — except $D301. $D5 is not zeroed, which is why the XT bank-select and GEM registers there persist.
  • Free ranges should mirror the stock chips as much as possible.
  • Games that rely on specific mirror locations: Bounty Bob Strikes Back ($D47B).