VDU driver internals — variables and primitive entry points

The MOS VDU driver maintains ~120 bytes of state and exposes a handful of internal entry points that user code can call from the unknown PLOT codes vector (VDUV at &226/&227). These entry points let custom graphics code reuse the MOS’s shadow-aware addressing, masking, and clipping primitives instead of re-implementing them. They are not documented in the master-arm but get a very detailed treatment in master-rm Ch E.4.

For the user-facing VDU command reference see vdu. For the full PLOT k reference see plot-codes.

VDU variable storage map

The VDU driver scatters its state across three regions of the I/O processor:

RegionWherePurpose
Page 0&D0-&E1Hot-loop pixel/text masks + screen pointers
Page 3&300-&37FMain VDU variable block (accessed by OSBYTE &A0)
Second-32K&8800-&88FFECF patterns + soft chars (Master)
Second-32K&8400-&87FFFlood-fill workspace (Master)

Master sideways-RAM also holds default character ROM at &B900-&BFFF (used when soft chars are reset).

Reading variables — OSBYTE &A0

LDA #&A0
LDX #variable_number    ; e.g. &10 for graphics cursor X
JSR &FFF4
; X = low byte; Y = high byte (or next byte for 1-byte vars)

Faster alternative if you know code is on the I/O processor (not parasite): read directly from &300+X (low) / &301+X (high). The unknown-PLOT-codes vector is guaranteed to run on the host, so direct reads are safe there.

⚠ Neither path is reliable from an interrupt service routine — the variables may be mid-update.

VDU variable list (&300-&37F)

(p) = internal pixel coordinates; (e) = external (user) coordinates.

AddrBytesVariable
&002Graphics window left column (p)
&022Graphics window bottom row (p)
&042Graphics window right column (p)
&062Graphics window top row (p)
&081Text window left column
&091Text window bottom row
&0A1Text window right column
&0B1Text window top row
&0C2Graphics origin X (e)
&0E2Graphics origin Y (e)
&102Graphics cursor X (e) — externally-visible position
&122Graphics cursor Y (e)
&142Previous graphics cursor X (p)
&162Previous graphics cursor Y (p)
&181Text cursor X (chars from left); during cursor-edit / unknown-PLOT: see notes
&191Text cursor Y (chars from top); similar dual-use semantics
&1A1Y-offset for GADDR so that (ZMEMG),Y addresses correct byte
&1B-&239VDU queue&23 is the most-recent byte; earlier bytes precede
&242Graphics cursor X (p)
&262Graphics cursor Y (p)
&28-&4934Workspace — but &38 must not be used in MODE 7/135
&4A2Address 6845 uses to display text cursor
&4C2Bytes per character row of text window
&4E1MSB of first byte of screen RAM
&4F1Bytes per character
&502Address of byte in top-left corner (= R12/R13 mirror, hardware-scrolling)
&522Bytes per character row of whole screen
&541MSB of screen memory size
&551Current screen mode (0-7, no shadow bit)
&561Memory mode code: 0=20K, 1=16K, 2=10K, 3=8K, 4=1K
&571Text foreground colour mask
&581Text background colour mask
&5910 if plotting foreground; 8 if background
&5A1Current graphics plot mode (usually set from one of next two, reduced MOD 16)
&5B1Foreground plot mode (last VDU 18 with colour < 128)
&5C1Background plot mode (last VDU 18 with colour ≥ 128)
&5D2Address of routine processing current VDU sequence
&5F1R10 value to revert to when leaving cursor edit
&601(num colours) − 1, or 0 in MODE 7
&611(pixels/byte) − 1, or 0 if not graphics — non-zero ⇒ graphics mode (test for “am I in graphics?“)
&621Mask for leftmost pixel in a byte
&631Mask for rightmost pixel in a byte
&641Cursor-edit output X (or input X inside unknown-PLOT vector)
&651Cursor-edit output Y
&661Cursor control flags (set by VDU 23,16)
&671Dot pattern (set by VDU 23,6)
&681Current state of dot pattern
&6910 if currently-plotting colour is solid; non-zero if ECF
&6A10 if foreground solid; non-zero if foreground ECF
&6B10 if background solid; non-zero if background ECF
&6C1Top bit set when cursor is in “column 81” pending state
&6D1Foreground graphics colour (last VDU 18)
&6E1Background graphics colour (last VDU 18)
&6F-&7E16Software copy of current palette
&7F1Reserved

Page-0 VDU workspace

AddrNamePurpose
&D0STATEVDU status byte — don’t modify directly
&D1ZMASKPixel mask
&D2ZORAText OR mask
&D3ZEORText EOR mask
&D4ZGORAGraphics OR mask (usable as workspace when not plotting)
&D5ZGEORGraphics EOR mask
&D6-&D7ZMEMGGraphics pointer
&D8-&D9ZMEMTText pointer
&DA-&DBZTEMPTemporary
&DC-&DDZTEMPBTemporary
&DE-&DFZTEMPCTemporary
&E0-&E1ZTEMPDTemporary — used otherwise in pre-Master BBC micros

Per master-rm Ch E.4: when the unknown-PLOT-codes vector is entered on the I/O processor, these locations are guaranteed to belong to the VDU driver. Routines indirecting the vector into a sideways ROM (via extended vector to &FF39) face a different memory map — see “Sideways ROM intercept” below.

Second-32K layout (Master only, visible when unknown-PLOT-codes vector is active)

The MOS sideways-RAM is paged in during unknown-PLOT-codes handling:

RangeRAM/ROMPurpose
&8400-&87FFRAMVDU workspace (flood-fill scratch)
&8800-&8807RAMECF pattern 1 definition (8 bytes)
&8808-&880FRAMECF pattern 2
&8810-&8817RAMECF pattern 3
&8818-&881FRAMECF pattern 4
&8820-&8827RAMCurrent foreground ECF or solid colour
&8828-&882FRAMCurrent background ECF or solid colour
&8830-&88BFRAMVDU workspace
&88C0-&88FFRAMReserved
&8900-&89FFRAMCurrent definitions of chars &20-&3F
&8A00-&8AFFRAMChars &40-&5F
&8B00-&8BFFRAMChars &60-&7F
&8C00-&8CFFRAMChars &80-&9F
&8D00-&8DFFRAMChars &A0-&BF
&8E00-&8EFFRAMChars &C0-&DF
&8F00-&8FFFRAMChars &E0-&FF
&B900-&BFFFROMDefault character definitions (mirror of soft-char RAM layout)

The 8 VDU primitive entry points (&C000-&C015)

These live in MOS ROM and are only callable when the memory map is in the correct state — which is guaranteed on entry to the unknown-PLOT-codes vector and not generally true elsewhere. Call them via JSR &C0xx.

&C000 — load screen byte (shadow-aware)

JSR &C000   ; LDA (ZMEMG),Y but routed correctly for shadow mode

17 cycles; +1 if page boundary crossed. The shadow-aware equivalent of an indirect-indexed load — handles ACCCON D/E/X automatically.

&C003 — store screen byte (shadow-aware)

JSR &C003   ; STA (ZMEMG),Y, shadow-aware

18 cycles. Same idea for writes.

&C006PLBYTE

JSR &C006   ; plot ZMASK into byte at (ZMEMG),Y using ZGORA/ZGEOR colour masks

Uses ZTEMP as workspace; preserves X, Y, V, C. This is the single-pixel plot primitive used throughout the VDU driver.

&C009HPLOT (horizontal line fast plot)

JSR &C009   ; horizontal line in current colour/ECF + plot mode

Pre-load two 4-byte areas at &300+X and &300+Y with endpoint coordinates (lowX, highX, lowY, highY format). If Y coords differ, the leftmost endpoint’s Y is used. Only the portion inside the graphics window is plotted (both endpoints included).

The low-level primitive used by ALL MOS area fills. Uses ZGORA, ZGEOR, ZMASK, ZMEMG, ZTEMP, ZTEMPB, ZTEMPC as workspace. No registers preserved.

&C00CEIGABS (external → internal coords)

JSR &C00C   ; convert 4-byte external coord pair at &300+X to internal pixel coords

Subtracts graphics origin, then divides by appropriate power of 2 (mode-dependent). Uses ZTEMP; corrupts all registers + flags.

&C00FWIND (window clipping check)

JSR &C00F   ; classify 4-byte pixel-coord pair at &300+X relative to graphics window
; A returns position code (0 = inside; 1-10 outside in 9 directions)

Position codes:

 9 | 8 | 10
---+---+---
 1 | 0 | 2
---+---+---
 5 | 4 | 6

A = 0 ⇒ inside the window. N and Z flags set per A. Preserves X.

&C012GADDR (pixel address)

JSR &C012   ; given pixel coords at &300+X, set up ZMEMG/Y/ZMASK/etc to address that pixel

Don’t call without first verifying the point is on-screen (e.g. via WIND). On exit:

  • ZMEMG = start of page containing the pixel
  • Y (and VDU variable &1A = &31A) = offset of the byte within that page
  • ZMASK = bitmask of the pixel within its byte
  • ZGORA / ZGEOR = colour masks for current graphics plot mode + colour/ECF
  • X = Y MOD 7 = scan line within character row

Returns A=0, Z=1. Uses ZTEMP.

&C015IEG (internal → external graphics cursor)

JSR &C015   ; convert internal pixel cursor (&324-&327) back to external (&310-&313)

Call after your code generates a new graphics cursor position so that the two cursor copies (internal pixel + external user) stay in sync — otherwise relative plots will drift. Used internally e.g. after a character is printed in VDU 5 mode.

Uses no page-0 locations; corrupts all registers + flags.

Worked example: re-implementing PLOT 64-71 (single point) from VDUV

Per master-rm Ch E.4:

.POINT
  LDX #&20      ; address new point within VDU queue
                ; (left there on entry to unknown PLOT codes vector)
  JSR WIND      ; is point inside the graphics window?
  BNE END       ; return if not
  JSR GADDR     ; address the point now we know it's on-screen
  JSR PLBYTE    ; and plot it
.END
  RTS

This is the cleanest possible custom-PLOT implementation: it inherits the MOS’s clipping, shadow-mode awareness, colour-mode handling, and ECF support for free. Substituting your own geometry computation for the WIND/GADDR/PLBYTE trio lets you add new primitives (e.g. anti-aliased lines, hardware-textured polygons) that integrate cleanly with the rest of the VDU system.

Sideways-ROM intercept caveat

If the unknown-PLOT-codes vector is indirected into a sideways ROM via an extended vector (&FF39 + extended-vector triple — see os-workspace “Master vector additions”), MOS swaps the memory map before entering the ROM:

  • Filing-system RAM occupies &C000-&DFFF (so the &C000-&C015 entry points are not accessible).
  • Sideways ROM occupies all of &8000-&BFFF (so the second-32K data areas &8400-&8FFF are not accessible).

You can work around this in two parts:

  1. To use the entry points, first JSR into a small stub in main RAM (under &8000) that calls the MOS routines, then RTS back to the sideways ROM.
  2. To access ECF pattern data / soft-char definitions, use OSWORD calls (&05/&06 for sideways-RAM access — see paged-rom ANDY section for the address-encoding convention).

This is awkward enough that direct unknown-PLOT-codes vector intercept (without sideways ROM) is the preferred pattern for performance-critical custom primitives.

See also

  • vdu — User-facing VDU command reference.
  • plot-codes — PLOT k user reference.
  • os-workspace — Page 3 layout, Master second-32K full map.
  • shadow-ram — shadow-mode addressing the C000 entry points handle for you.
  • custom-modes — when to leave the VDU driver entirely vs intercept its plot vector.
  • master-rm Ch E.4 — primary source.

This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.