What's fitted
An 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 | 6502-PICOVDP |
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-VDP.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, and the video card's own routines return with the carry set.
- 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 #6 ; the save slots arrived in 1.6
bcc TooOld
NewEnough:That is the BIOS's version, the one the header prints. The BASIC v2.0 on the line below it is a name rather than a second release number, and doesn't move.
Which video card?
HW_VID set means more than "there is a video card". The machine only counts a card that answers as a 6502-PICOVDP and carries its own character set, since the ROM has none; anything else — an empty slot, a TMS9918A, a card whose firmware is too old — leaves the bit clear and the console on the serial port.
VdpInfo says what the machine found in more detail: the firmware's version in A, as two decimal digits, and in X a byte of features, one bit each.
; Asking the video card what it is.
;
; VdpInfo reports what the machine found when it looked for a video card at
; power-on: which firmware the card runs, and what that firmware can do. A
; program that wants a feature asks here first, rather than finding out by
; drawing garbage.
.setcpu "65C02"
.include "6502-VDP.inc"
.segment "CODE"
BasicStartup:
.byte $0A, $08, $0A, $00, $A5, $32, $30, $36, $30, $00, $00, $00
Firmware := $40
Features := $41
Which := $42
Start:
jsr KernalVersion ; A = major version
cmp #2 ; 1.x has no VdpInfo: its slot just returns
bcs @ask
lda #<OldRom
ldy #>OldRom
jmp PrintStr
@ask:
jsr VdpInfo ; A = firmware, X = features, Y = $AC
bcc @found
cpy #VC_ID ; carry set, but the card answered:
beq @noFont ; a 6502-PICOVDP too old to have a font
lda #<NoCard
ldy #>NoCard
jmp PrintStr
@noFont:
lda #<NoFont
ldy #>NoFont
jmp PrintStr
@found:
sta Firmware
stx Features
lda #<Heading
ldy #>Heading
jsr PrintStr
lda Firmware ; two decimal digits: major, then minor
lsr a
lsr a
lsr a
lsr a
ora #'0'
jsr Chrout
lda #'.'
jsr Chrout
lda Firmware
and #$0F
ora #'0'
jsr Chrout
jsr PrintCRLF
jsr PrintCRLF
stz Which ; one line per feature bit
@feature:
lda Which
asl a
tax
lda Names+1,x
beq @next ; bit 6 means nothing yet
tay
lda Names,x
jsr PrintStr
lsr Features ; this bit into carry
lda #<Yes
ldy #>Yes
bcs @say
lda #<No
ldy #>No
@say:
jsr PrintStr
inc Which
lda Which
cmp #8
bne @feature
rts
@next:
lsr Features ; skip the bit
inc Which
bra @feature
Names:
.word TwoLayers, Deep, Flip, Scroll, Scanline, Memory, 0, Font
Heading: .byte "6502-PICOVDP, FIRMWARE ", $00
TwoLayers: .byte "TWO LAYERS ", $00
Deep: .byte "256-COLOR TILES ", $00
Flip: .byte "FLIPPED SPRITES ", $00
Scroll: .byte "HARDWARE SCROLL ", $00
Scanline: .byte "LINE INTERRUPT ", $00
Memory: .byte "64 KB OF MEMORY ", $00
Font: .byte "CHARACTER SET ", $00
Yes: .byte "YES", CHAR_CR, CHAR_LF, $00
No: .byte "NO", CHAR_CR, CHAR_LF, $00
OldRom: .byte "THIS ROM IS OLDER THAN BIOS 2", CHAR_CR, CHAR_LF, $00
NoCard: .byte "NO 6502-PICOVDP", CHAR_CR, CHAR_LF, $00
NoFont: .byte "A 6502-PICOVDP WITHOUT A CHARACTER SET", CHAR_CR, CHAR_LF, $006502-PICOVDP, FIRMWARE 0.5
TWO LAYERS YES
256-COLOR TILES YES
FLIPPED SPRITES YES
HARDWARE SCROLL YES
LINE INTERRUPT YES
64 KB OF MEMORY YES
CHARACTER SET YESThe carry is the part to test. Clear, and there is a 6502-PICOVDP with its character set. Set, and there isn't — unless Y came back $AC, the card's identifying byte, which means a 6502-PICOVDP answered but its firmware is older than the character set.
The version check comes first, and it matters. A ROM older than 2.0 has no VdpInfo: its slot in the jump table is a bare return, so the carry comes back as whatever it was, and a program that trusted it would drive a card that isn't there. So a program that needs the video card starts like this, and says so politely on any machine that doesn't qualify:
Start:
jsr KernalVersion ; A = major version
cmp #2
bcc NoCard ; older than 2.0
jsr VdpInfo
bcs NoCard ; no 6502-PICOVDP
; ... the card is there
NoCard:
lda #<NeedsCard
ldy #>NeedsCard
jmp PrintStr
NeedsCard: .byte "NEEDS BIOS 2 AND A 6502-PICOVDP", CHAR_CR, CHAR_LF, $00The status registers say the same things directly, for a program that talks to the card itself: status register 4 reads $AC on a 6502-PICOVDP, 5 is the firmware version, and 6 is the feature byte, with bit 7 the character set.
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 The ACE.
Next: writing a cartridge.

