NAUG Ch18 — Second Processors and the Tube

Holmes & Dickens, The New Advanced User Guide, pp.334-358. The Tube is the hardware + software interface between the BBC (host / “I/O processor”) and an external CPU board (“second processor” / “parasite”). The host owns all I/O; the parasite has only CPU + RAM + OS ROM + Tube ULA.

Architecture

+-----------------------------+      +-----------------------------+
|   BBC (I/O processor)       |      |   Second processor          |
|   - all I/O (screen, kbd,   |      |   - CPU (6502/Z80/32016/..) |
|     printer, disc, ADC..)   | +--+ |   - RAM (~64 KB to 4 MB)    |
|   - current language ROM    |<|T |>|   - OS ROM (small)          |
|   - paged ROMs              | |ub| |   - Tube ULA                |
|   - 16/32K user RAM         | |e | |   - **no I/O hardware**     |
|   - Tube ULA @ &FEE0-&FEFF  | +--+ |   - Tube ULA @ &FEF8-&FEFF  |
+-----------------------------+      +-----------------------------+

When a Tube is active:

  • The parasite runs the current language (BASIC etc.) — copied across at language-select time.
  • The host runs the Tube servicing code (in language workspace &400-&7FF).
  • Every I/O call on the parasite generates a Tube transaction → host handles it (OSWRCH, OSRDCH, OSFILE, etc.) → result returned.

32-bit addressing across the Tube

Logical 4 GB address space (filing systems and OSWORD &05/&06):

  • &FFFF0000-&FFFFFFFF (top 64 KB of 4 GB) = I/O processor memory.
  • Everything below = second processor memory (typically only the low 64 KB on a 6502 2P; more on Z80 / 32016).

When a filing system can’t detect a Tube, only the low 2 bytes are used (= 64 KB host space).

Tube ULA registers

The ULA implements four register pairs (status + data), each 8 bits wide. Addresses differ on each side:

RegHost addrParasite addrPurpose
R1 status&FEE0&FEF8bit 7 = data present, bit 6 = not filled, bits 5-0 = control
R1 data&FEE1&FEF9OSWRCH, events, ESCAPE. Writing IRQs parasite.
R2 status&FEE2&FEFAbit 7 = data, bit 6 = not filled
R2 data&FEE3&FEFBOther OS calls
R3 status&FEE4&FEFCas above
R3 data&FEE5&FEFDData transfer & I/O errors. Writing NMIs parasite.
R4 status&FEE6&FEFEas above
R4 data&FEE7&FEFFControl of data transfer. Writing IRQs parasite.

Register 1 status has additional control bits:

  • bit 5 P: parasite reset (active low)
  • bit 4 V: enable 2-byte FIFO on R3
  • bit 3 M: enable parasite NMI from R3
  • bit 2 J: enable parasite IRQ from R4
  • bit 1 I: enable parasite IRQ from R1
  • bit 0 Q: enable host IRQ from R4

R3 (and optionally R4) is FIFO-buffered — for queued data transfers. R1, R2, R4 are simple latches.

Don’t poke Tube registers directly except when implementing custom filing systems. Indiscriminate writes cause unserviceable interrupts → host crash.

OS call dispatch over the Tube (parasite → host)

NAUG §18.6 table (p333):

CallReason code (R2)→ host bytes← host bytes
OSRDCH&0002 (C flag bit 7, then char)
OSCLI&02var (cmd + &0D)1 (&7F)
OSBYTE A<&80&042 (X, A)1 (X)
OSBYTE A≥&80&063 (X, Y, A)3 (C bit 7, Y, X)
OSWORD A≠0&08varvar
OSWORD A=0&0A5 (max, min, len, addr)var (&FF ESC or &7F ack + line)
OSARGS&0C6 (Y, 4 bytes, A)5 (A, 4 bytes)
OSBGET&0E1 (Y)2 (C bit 7, A)
OSBPUT&102 (Y, A)1 (&7F)
OSFIND&12var1 (handle or &7F)
OSFILE&1417 (bytes 3-18 + A)17
OSGBPB&161415
OSWRCH(R1)1 char0

Calls go via the data register noted (R1 for OSWRCH; R2 for everything else).

Host → parasite events use R1 (BRK = R4 reason &FF then R2 message; events = 1 byte block; ESCAPE flag change = 1 byte; data transfer begin = R4 reason then params).

Tube software entry points on the I/O processor

Loaded at &400-&7FF (overlays language workspace when Tube active):

EntryFunction
&400Copy current language to second processor
&403Copy ESCAPE flag to second processor
&406Transfer data / claim / release / execute — the entry point user software can call

&406 — the user-facing entry

A = reason code. Two classes:

Claim / release

AFunction
&C0 + caller_idClaim the Tube. Returns C=1 on success, C=0 if busy (caller should retry).
&80 + caller_idRelease. Only the original claimer (matching caller_id) can release.

Caller IDs reserved:

  • &0 = CFS
  • &1 = DFS
  • &2 = NFS low-level
  • &3 = NFS high-level
  • &4-&3F = user-assignable
.claim
    PHA
.retry
    LDA #&C0 + my_id
    JSR &0406
    BCC retry          ; C=0 means busy, spin
    PLA
    RTS
 
.release
    PHA
    LDA #&80 + my_id
    JSR &0406
    PLA
    RTS

Data transfer / execute (after claiming)

X+Y point to a parameter block containing a 32-bit second-processor address (4 bytes, LSB first).

AFunctionInit delayPer-byte delay
0Multi-byte read (2P → I/O)24 µs24 µs
1Multi-byte write (I/O → 2P)024 µs
2Multi-byte-pair read (2P → I/O)26 µs26 µs/pair
3Multi-byte-pair write (I/O → 2P)026 µs/pair
4Execute at given address (no return)
5Reserved
6256-byte read (2P → I/O)19 µs10 µs/byte
7256-byte write (I/O → 2P)010 µs/byte

Once initiated, the host reads/writes bytes by accessing the R3 data register at &FEE5 directly (with the per-byte delay between accesses). The delay is mandatory — the chip needs time to refill the FIFO from the parasite side.

Reason 4 (execute) has implicit Tube release — no need to call release after.

For other reasons, the caller must explicitly release once done.

Tube OSBYTE / OSWORD

CallFunction
OSBYTE &EA (234)R/W Tube-present flag (0 = absent, &FF = present). Read first before any &406 call.
OSWORD &05 (5)Read byte of I/O processor memory (from parasite). 5-byte block: 4-byte address + byte.
OSWORD &06 (6)Write byte of I/O processor memory. Same block.
OSWORD &FF (Z80)Z80-specific bulk transfer. 13-byte block (i/o addr, z80 addr, length, R/W).

For single byte host-memory access from the parasite, OSWORD &05/&06 is the way. For bulk transfers, use the &406 data-transfer protocol.

Parasite memory map

&FFFF +--------------------------+
      | Tube hardware            |
      | + parasite OS ROM        | &F800-&FFFF (2 KB)
&F800 +--------------------------+
      | user memory              |
&C000 +--------------------------+
      | current language         | &8000-&BFFF (16 KB, not relocated)
&8000 +--------------------------+
      | main user memory         |
 OSHWM+--------------------------+
      | language workspace       | &0400-&07FF
&0400 +--------------------------+
      | OS workspace             |
&0000 +--------------------------+

OSHWM on a 6502 2P typically = &0800. Up to 44 KB user RAM available with BASIC relocated to &B800.

Zero page is usable up to &EE (the OS uses &EE-&FF). Page 2 indirection vectors not used by the parasite OS are also free.

Things that change when Tube is active

  • Host OSHWM bumps by &600 — fonts are auto-exploded on Tube (OSBYTE &14). E.g. Model B with DFS goes from &1900 to &1F00.
  • Tube code occupies &400-&7FF on the host (overlays language workspace).
  • Direct screen pokes don’t work from the parasite — screen RAM is in the host, not the parasite’s address space. Either use OSWRCH (slow) or use the &406 transfer protocol from the parasite to push pixel data to host screen RAM.
  • IRQ1V/IRQ2V on parasite are not implemented — IRQs are physically in the host. Interrupt-driven user code must live in the host (in a service ROM or installed via host-side initialisation). The “event handling” workaround: install event handlers on the host from the parasite via OSWORD &06 writes.
  • Tube events get 10 ms not 2 ms — event handlers across the Tube have a longer budget because of the round-trip cost.

Filed into

  • tube-ula — Tube ULA register reference (host + parasite addresses).
  • tube — Tube software protocol: claim/release/transfer, OSBYTE &EA, OSWORDs &05/&06, parasite OS dispatch table, what changes when Tube is active.
  • Updates: cross-links to 6502 (6502 2P at 3 MHz), memory-map (Tube at &FEE0), calls (Tube-safe vs non-vectored variants).

Open follow-ups

  • Custom filing system implementation over the Tube — chapter has worked example (§18.8 p346) for a 20 KB transfer ROM. Captured as pattern; full code lives in raw/manuals/.
  • Z80 2P OS jump table at &FFCE-&FFFE — captured in source page; if Z80 work matters, file as z80-2p.
  • 32016 2P PANOS — UNIX-like OS, separate documentation; out of scope.
  • 80186 2P (Master 512) — IBM PC emulation; out of scope.

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.