What's fitted
Your ACE has everything. That is what the machine is: a complete computer on one board, video, sound, storage, serial, clock, sticks and banked RAM all present.
Your program will also run on a COB with three cards in it, or on an emulator someone started headless, or on a machine whose owner has pulled the sound card to fix something. One byte tells you which of those you are on, and checking it costs four instructions.
The byte
At power-on the Kernal walks the eight hardware slots, works out what answers, and writes the result to HW_PRESENT at $030D. One bit per slot, set means fitted.
| Bit | Mask | Slot | What's there |
|---|---|---|---|
| 0 | HW_RAM_L | 1 | AS6C4008 banked SRAM (low) |
| 1 | HW_RAM_H | 2 | AS6C4008 banked SRAM (high) |
| 2 | HW_RTC | 3 | DS1511Y |
| 3 | HW_CF | 4 | CompactFlash (8-bit True IDE) |
| 4 | HW_SC | 5 | R65C51 / W65C51 ACIA |
| 5 | HW_GPIO | 6 | W65C22 VIA |
| 6 | HW_SID | 7 | MOS 6581 SID / ARMSID |
| 7 | HW_VID | 8 | TMS9918A / Pico9918 |
lda HW_PRESENT
and #HW_SID
beq NoSound ; play it silent, thenThe same byte is what MEM prints as HW=$xx from BASIC, and what PEEK(781) reads.
The program that lists them
; What's fitted — the machine builds a list of its own cards at power-on and
; leaves it in one byte. This reads that byte and names every card it found.
;
; Guarding your own code the same way the Kernal guards its own is the whole
; point: a program that checks before it draws keeps running on a machine with
; no video card instead of hanging.
.setcpu "65C02"
.include "6502.inc"
.segment "CODE"
BasicStartup:
.byte $0A, $08, $0A, $00, $A5, $32, $30, $36, $30, $00, $00, $00
Start:
lda #<Header
ldy #>Header
jsr PrintStr
ldx #0
NextCard:
lda CardName_lo,x ; the name is padded, so the column lines up
ldy CardName_hi,x
jsr PrintStr ; PrintStr keeps X for us
lda Mask,x
and HW_PRESENT ; the byte the power-on probe wrote
beq NotFitted
lda #<Fitted
ldy #>Fitted
bra Report
NotFitted:
lda #<Missing
ldy #>Missing
Report:
jsr PrintStr
jsr PrintCRLF
inx
cpx #8
bne NextCard
rts
Mask:
.byte HW_RAM_L, HW_RAM_H, HW_RTC, HW_CF
.byte HW_SC, HW_GPIO, HW_SID, HW_VID
CardName_lo:
.lobytes Ram1, Ram2, Clock, Card, Serial, Input, Sound, Video
CardName_hi:
.hibytes Ram1, Ram2, Clock, Card, Serial, Input, Sound, Video
Header: .byte "WHAT THIS MACHINE HAS", CHAR_CR, CHAR_LF, $00
Fitted: .byte "YES", $00
Missing: .byte "NO", $00
Ram1: .byte "BANKED RAM, LOW ", $00
Ram2: .byte "BANKED RAM, HIGH ", $00
Clock: .byte "CLOCK ", $00
Card: .byte "MEMORY CARD ", $00
Serial: .byte "SERIAL PORT ", $00
Input: .byte "KEYBOARD AND STICKS ", $00
Sound: .byte "SOUND ", $00
Video: .byte "VIDEO ", $00On a complete ACE:
WHAT THIS MACHINE HAS
BANKED RAM, LOW YES
BANKED RAM, HIGH YES
CLOCK YES
MEMORY CARD YES
SERIAL PORT YES
KEYBOARD AND STICKS YES
SOUND YES
VIDEO YESA mask table and a name table indexed by the same counter is all it takes, and the padding in the names is what makes the column line up without any formatting code.
Degrade like the Kernal does
The ROM's own habit is worth copying, because it is what makes the same ROM work across the whole family:
- Sound — no card, no noise, no error. A game that beeps on a hit keeps playing.
- Video — no card, and the console goes to the serial port instead. The drawing routines return without doing anything.
- Storage — no card, and the load and save calls come back with the carry set. Nothing hangs.
- Console — no video and no serial is the one case the machine cannot survive, because there is nothing to talk to. It stops at power-on.
The pattern in your own code: check once at the start for what you must have, and check per-call for what you can do without.
Start:
lda HW_PRESENT
and #HW_VID
bne HaveScreen
lda #<NeedsAScreen ; say so, on whatever console exists
ldy #>NeedsAScreen
jsr PrintStr
rts
HaveScreen:Which ROM am I on?
KernalVersion gives the major version in A and the minor in X. If your program uses something a particular release added, ask:
jsr KernalVersion ; A = major, X = minor
cmp #1
bcc TooOld
bne NewEnough
cpx #5
bcc TooOld
NewEnough:There are three version numbers in the one ROM, and they move independently: the BIOS as a whole, BASIC, and the Monitor. KernalVersion reports the first.
The DIP switches turn cards off
The ACE has an eight-way switch bank that enables and disables each I/O section, meant for fault-finding. A switched-off card is invisible to the power-on probe, so it reads exactly like a card that is not there. That makes your detection code testable on real hardware without unplugging anything — see Your ACE.
Next: writing a cartridge.

