return to title

C64 Assembly

Date: 2024-11-17
Tags: 6502, 6510, 65xx, art, assembly, coding, procedural generation, retro

Starting on Commodore-64, generally called C64 (MOS 6510 that is a modified version of 6502, will use 65xx here) assembly on Linux. This 8-bit microprocessor is relatively limited and simple, as Z80 microprocessor, and used on several popular platforms, including Commodore PET/VIC20, Apple][, BBC, Oric 1/Atmos/Telestrat, Atari 400/800/5200/XL/Lynx, NES and SNES or PC Engine/TurboGraphix. The power of the most interesing of these computers are in association with their audio, graphics and video co-processors.

This document, is based on the usage of generaly available and FLOSS, xa assembler, xda65 debugger and VICE emulator, on GNU/Linux platforms.

Hello World!

Tools

Assemblers

Multi-architecture assemblers

Emulators

Examples, doc & others

Music trackers

Graphics tools

Installing

ArchLinux (and derivatives)

sudo pacman -S vice xa
yay -S xda65 cc65

Debian (and derivatives)

apt install vice xa65 cc65
# need to compile yourself dxa65 debugger:
curl -O http://www.floodgap.com/retrotech/xa/dists/dxa65-0.1.5.tar.gz
tar xf dxa65-0.1.5.tar.gz
cd dxa65-0.1.5/
make
sudo cp -a dxa /usr/local/bin/
sudo cp -a dxa.1 /usr/local/man/man1/

I personnaly use [vim](https://www.vim.org) (pacman -S vim, some prefers more heavy [neovim](https://neovim.io/), [kakoune](http://kakoune.org), [helix-editor](https://helix-editor.com), or X/Wayland frontend of vim called gvim, or more simple, limited, matching more old-faschionned "MS-Windows" things [micro](https://micro-editor.github.io/) or [nano](https://www.nano-editor.org/)), as editor. Sometime for more complex, multi-files code, I like to use the very light and graphic editor Geany (pacman -S geany-plugins to install both geany and its plugins).

Reference documentations

Most was found on https://commodore.bombjack.org/

C64 Programmer's Reference Guide, contains BASIC language, graphics, audio, and 65xx assembly programming courses chapters and various mandatory references. These are books published by no more existing company, CBM (Commodore Business Machines) in 1980s, and given with their computers, considered as abandonware.

Very useful, everydays references resources on this book. Text edition has now roman digits introduction part and no pictures, so pages search by number in PDF use not the same index.

Other documentations:

Assembly code

Following C64 Programmers Guide, and digging around the net for usage of x64, I sadely found only some buggy parts of things that work. Few months ago I made something that worked from available references, need to found them again.

tip for conversion from decimal from hexadecimal

Type for converting from decimal to hexadecimal, I launch an Lua interpreter console and type:

string.format("%x",65) -- display hexadecimal value of 65 decimal
0x50 -- display decimal value of 50 hexadecimal

On lua < 5.3 (works also with >= 5.3 and needed in loops

print(string.format("%x",65))
print(0x50)

C64 specific knowledge

65xx assembly considerations

Strings can be defined by there decimal, hexadecimal or ascii values in various assemblers, in our case.

.byte 72,69,76,76,79, 32, 87,79,82,76,68,33, 0               ; decimal
.byte $48,$45,$4c,$4c,$4f, $20, $57,$4f,$52,$4c,$44, $21, $0 ; hexadecimal
.aasc "HELLO WORLD!"        ; ascii*
.byte 0                     ; string ending 0 (as is C, the more convenient)

65xx, z80 or other 8 bits microprocessors of this era are quite limited, no floating point, no multiplication or division, only 3 specialized registers in case of 65xx:

Another essential part of all microporocessors is a register generally called CSR (control status register) and here, just STATUS REGISTER containing flags, set as result of various operations and used for other operations and conditionnals like conditionnal jumps. C64's MCS6510 contains flags N Z C I D V. As a start the more essentials for first programs are:

I didn't found detailed description of the Status register in the manual, but 64-wiki have a dedicated page, for short reference:

7 6 5 4 3 2 1 0
N V   B D I Z C

There are also two sessential ones il all processors architectures and a last one specific to 65xx but we don't care them at start:

The system has some other control register related to various microcontrollers on the board (audio, graphics, peripherals, etc)), they are descibed in their dedicated sections of the manual and are out of topic in this introduction.

The helloworld.s whole source code (C64 helloworld.s downloadable here), more long description below:

I commented instructions as much as necessary, refer to a 6502 assembly book (see above), c64 reference guide contains a 6502 assembly course, or the numerous guide on the net to better understanding it. 65xx world isn't RISC-V world, so not fully standardised by community, each assembler has his specify beside 65xx assembly code itself, you need to have separate tutorial for each one sadely.

CHROUT = $ffd2 ; Kernal function to print a character to OUTPUT channel, page 272,278

; usable memory for programs is between $800 and $9fff (2048-40959), after "C64 Promgrammer's Reference Guide", page 212
; C64 BASIC header, mandatory
.byte $01, $08; Load header into $0801
*=$0801
.byte $0c, $08, $0a, $00, $9e, $20
.byte $34, $30, $39, $36, $00, $00
.byte $00

; datas
hello: ; "hello world!" string preceded by clear screen (147/$93) and followed by 0
.byte 147            ; clear screen character, avoid to write more instructions
.aasc "HELLO WORLD!" ; ascii text
.byte 0              ; end of string, not really needed in this example as:

; aligned code
.dsb $1000 - * ; Pad with zeroes from current position to $1000
;*=$1000    ; this doesn't work, prog should be between $1000 and $9fff (2048-40959)
    ldx #0       ; set x to 0
loop:
    lda hello,x  ; load character hello + x
    beq end      ; if 0 then ends (lda set z flag if loaded value is zero)
    jsr CHROOT   ; print character KERNAL routine
    inx          ; increment x
    jmp loop     ; loop
end:
    rts ; return from subroutine, or here, end program

Detailed description

Definitions that are preprocessed by assembler don't take memory in final program, as they are used exclusively by the assembler. That's the case of defined CHROOT address $FFD2, that is the standard CHROOT Kernal routine. It is possible to use directly address values in code, but using this kind of constant labels, help to make the code more readable.

CHROUT = $ffd2 ; Kernal function to print a character to OUTPUT channel, page 272,278

!!Warning, This kind of definition is compatible with xa65 assembler, but could be different on some others. Look at your assembler documentation!!

As program start at $1000 and the little BASIC header at $800, we have some place to keep datas that would be nil else. There are far more place after, but this will make the program less compact, would be needed for bigger ones. So we place datas before the program instructions.

First we load value 0 to the X register (ldx = load X)

    ldx #0

Then we start the main loop and place a label to be able to jump to it, it will be replaced with it's equivalent address in jump instructions in machine code

loop:

Then we load in the accumulator (lda = load A) the content of address labeled by hello: + index register X (here hello + 0) in the accumulator.

    lda hello,x  ; load character hello + x

We prefixed the "HELLO WORD!" string by the code 147 ($97) here. As shown in page 380 (APPENDIX C) of the manual, this is the {clear} (screen) character when sent to the Kernal print function.

hello: ; "hello world!" string preceded by clear screen (147/$93) and followed by 0
.byte 147            ; clear screen character, avoid to write more instructions
.aasc "HELLO WORLD!" ; ascii text
.byte 0              ; end of string, not really needed in this example as:

As you see, we also suffixed the string by a 0. This is usual in C language strings and optimal in assembly; If the content of the value loaded in accumulator by lda is 0, then the flag Z (zero) will be set (ref: LDA, page 245, 263 in TXT ed.) to 1 else to 0 (details, page 226, 244 in TXT ed.), so, the next instruction:

We test if beq (be "equal"). This instruction is true if the flag Z is set to 1, as, if a difference between two value is not equal to zero, they are obviously not the same. If the value is zero, we already have finished to read the string, we can go to end: labeled instruction address, else we continue at next instruction.

    beq end      ; if 0 then ends (lda set z flag if zero)

Here we jsr (Jump at SubRoutine) at CHROUT, previously definied as hexadecimal address $FFD2, where Kernal subroutine CHROUT to print a char is (page 272, 290 in TXT ed, long description page 278(296)). This will actually print the current value in accumulator at cursor address on screen, and then return to next instruction in this program.

    jsr CHROUT   ; print character KERNAL routine

We now, inx (increment the X register of 1), to place it to the next char in the reference string

    inx          ; increment x

Then we inconditionnaly jmp (jump) to the address of instructions labeled by loop: to read the next char in string and continue

    jmp loop     ; loop

This part is only reached if the previous beq instruction condition is true, so we finish the program by rts (return froom subroutine) and comeback to instruction following the PC address before calling this program.

end:
    rts ; return from subroutine, or here, end program

Assembling and executing

Assembling for the C64:

xa -o helloworld.prg helloworld.s

You can download here the helloworld.prg

Launching:

vice hellowold.prg

Disassembling for analysis or debugging, here the optional -a dump, allow to see an hexidecimal representation of datas and opcodes to better understand how codemachine is related to source assembly

dxa65 -a dump helloworld.prg 

Exemple of the main program part disassembling (this is preceded by headers, our text datas and lots of zero):

1000          l1000:
1000 a2 00      ldx #$00
1002          l1002:
1002 bd 0e 08   lda l80e,x
1005 f0 07      beq l100e
1007          l1007:
1007 20 d2 ff   jsr $ffd2
100a e8         inx
100b 4c 02 10   jmp l1002
100e          l100e:
100e 60         rts

To have mode detailed option of this debugger, look at the man page (called dxa, not dxa65):

man dxa

Hope this detailed instructions are detailed enough and help you. You can reach me on Mastodon if needed.

Tags: 6502, 6510, 65xx, art, assembly, coding, procedural generation, retro