Close

Simple assembly language program with cc65

A project log for GLXgears on a commodore 64

GLXgears on a commodore 64

lion-mclionheadlion mclionhead 02/13/2023 at 23:260 Comments

Lions remember very little about C64 development.  load "$",8 loads a directory listing into the program space.  PRG files are programs.  SEQ files are data.  Assembly language programs tended to be stored in SEQ files.  There was 1 PRG file for starting.  load with ,8,1 was required for assembly language.  The mane wrinkle was the many addressing modes enabled by the X Y index registers.  You'd load 8 bit offsets into those & use LDA STA variants which add those to the address.  There are no 16 bit registers, which lions tend to confuse with the 68HC11 which had a 16 bit X Y & accumulator.  

Assembly language arguments in ca65 are:

$01 dereference a value in a hex address

1234 dereference a value in a decimal address

#$34 a literal in hex format

#%01010101 a literal in binary 

#123 a literal in decimal format

< provides the low byte of a literal

> provides the high byte of a literal

Enclosing the agrument in various parenthesis  POINTER,X  (POINTER) (POINTER),Y (POINTER,X) invokes different addressing modes described in http://www.emulator101.com/6502-addressing-modes.html.

POINTER,X  Add X to pointer to get POINTER2.  Dereference the value in POINTER2.  POINTER is a 16 bit  or 8 bit address.  There are different opcodes for the 16 bit & 8 bit variant.  The 8 bit variant wraps at 256 (zero page memory).  This works with X or Y.

(POINTER) Read the address stored in POINTER to get POINTER2.  Dereference the value in POINTER2.  POINTER is a 16 bit address. This only works with GOTO.

(POINTER,X) add X to POINTER to get POINTER2.  Dereference the address in POINTER2.  POINTER is limited to zero page memory.  This works with X only.

(POINTER),Y Dereference the address stored in POINTER to get POINTER2.  Add Y to POINTER2 to get POINTER3.  Dereference the address in POINTER3.  POINTER is limited to zero page memory.  This works with Y only.

Double & triple pointers were the key to accessing large amounts of memory with 8 bit registers.   They had special instructions for accessing the 1st 256 bytes of memory (zero page).  You'd ideally have all your variables in that space.  16 bit POINTER,X was the only indexing mode lions could understand 40 years ago.

A starting point is to call into the C library to print something.

.autoimport    on              ; imports _cprintf, pushax
.forceimport    __STARTUP__ ; imports STARTUP, INIT, ONCE
.export        _main           ; expose mane to the C library
.segment    "RODATA"
_Text:                      ; PETSCII text to print
    .byte    $C8,$45,$4C,$4C,$4F,$20,$57,$4F,$52,$4C,$44,$21,$00


.segment    "CODE"
.proc    _main: near
    lda     #<(_Text) ; low byte function argument
    ldx     #>(_Text) ; high byte function argument
    jsr     pushax ; put function arguments on stack
    ldy     #$02   ; size of function arguments (2 bytes)
    jsr     _cprintf ; C library function
.endproc

The trick with this is it uses PETSCII instead of ASCII.  

The pushax function is a beast in libsrc/runtime/pushax.s

There's a command to assemble it.

ca65 -t c64 hello.s

 Then link it with the standard C library.

ld65 -t c64 hello.o -o hello c64.lib

The executable doesn't end in .prg but is a PRG file anyway.  It's an utterly gigantic 2108 bytes for what it does, probably because cprintf has to parse formatting codes.  There is a simpler _puts function.  

	lda     #<(string)
	ldx     #>(string)
	jsr     _puts

The trick with the C library is compiling C programs to figure out the function arguments.  Cc65 generates a .s file with the assembly language function calls.  The mane C function of note is cprintf.

For fast development on the emulator, the journey begins by creating a disk image

c1541 -format "disk,00" d64 disk.d64

 Store the program in the disk image.

c1541 -attach disk.d64 -write hello hello,p

Then the emulator can automatically run the 1st program on the disk image.

x64 -VICIIdsize -ntsc -warp -autostart disk.d64  

The C library puts it into lowercase mode.  Left CTRL + SHIFT toggles the case.  Left CTRL is the commodore key.

There's a command for listing the directory so you don't have to fight the emulator.

c1541 -attach disk.d64 -list

The only way to get it to load a file other than the 1st one is to make a script.  

The emulator has always randomly dropped keypresses in addition to crashing some games.  It may be that these games always crashed.

The trick with the emulator is even without the bugs, audio can never be synchronized with video as it was.  Modern sound drivers are all based on buffers while the SID was a direct analog synthesizer tied to registers.

Discussions