Skip to content

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.

BitMaskSlotWhat's there
0HW_RAM_L1AS6C4008 banked SRAM (low)
1HW_RAM_H2AS6C4008 banked SRAM (high)
2HW_RTC3DS1511Y
3HW_CF4CompactFlash (8-bit True IDE)
4HW_SC5R65C51 / W65C51 ACIA
5HW_GPIO6W65C22 VIA
6HW_SID7MOS 6581 SID / ARMSID
7HW_VID86502-PICOVDP
asm
  lda HW_PRESENT
  and #HW_SID
  beq NoSound                   ; play it silent, then

The same byte is what MEM prints as HW=$xx from BASIC, and what PEEK(781) reads.

The program that lists them

asm
; 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               ", $00

On 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               YES
The same eight questions, asked of a machine with every card fitted. Open the full emulator

A 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.

asm
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:

asm
  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.

asm
; 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, $00
6502-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      YES
The video card, asked what it is and what it can do. Open the full emulator

The 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:

asm
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, $00

The 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.

Written for BIOS v2.0. Released under the MIT License.