I have written a short program in 6502 assembler for the Commodore 64 using the ca65 assembler and ld65 linker. The program should render a solid square sprite somewhere near the center of the display, but I don't see anything being rendered.
This is my assembly:
.segment "CODE"
; set sprite pointer index
; this, multiplied by $40, is the address
; in this case, the address is $2000
; $80 * $40 = $2000
lda #$80
sta $07f8
; enable sprite 0
lda #$01
sta $d015
; set x and y position
lda #$80
sta $d001
sta $d002
loop:
jmp loop
.segment "GFXDATA"
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
This is my linker script, adapted from ca65's recommended linker script for hand-written assembler on the c64. The only change I made was to add the "GFXDATA" segment, so that I could store my sprites at address $2000
.
FEATURES {
STARTADDRESS: default = $0801;
}
SYMBOLS {
__LOADADDR__: type = import;
}
MEMORY {
ZP: file = "", start = $0002, size = $00FE, define = yes;
LOADADDR: file = %O, start = %S - 2, size = $0002;
MAIN: file = %O, start = %S, size = $D000 - %S;
}
SEGMENTS {
ZEROPAGE: load = ZP, type = zp, optional = yes;
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = MAIN, type = ro, optional = yes;
CODE: load = MAIN, type = rw;
RODATA: load = MAIN, type = ro, optional = yes;
DATA: load = MAIN, type = rw, optional = yes;
GFXDATA: load = MAIN, type = ro, optional = yes, start = $2000;
BSS: load = MAIN, type = bss, optional = yes, define = yes;
}
This is the command I am using to compile and link:
cl65 -o graphics.prg --mapfile graphics.map -u __EXEHDR__ -t c64 -C linker.cfg graphics.asm
This is the contents of the mapfile after compiling:
Modules list:
-------------
graphics.o:
CODE Offs=000000 Size=000015 Align=00001 Fill=0000
GFXDATA Offs=000000 Size=000040 Align=00001 Fill=0000
/usr/share/cc65/lib/c64.lib(exehdr.o):
EXEHDR Offs=000000 Size=00000C Align=00001 Fill=0000
/usr/share/cc65/lib/c64.lib(loadaddr.o):
LOADADDR Offs=000000 Size=000002 Align=00001 Fill=0000
Segment list:
-------------
Name Start End Size Align
----------------------------------------------------
LOADADDR 0007FF 000800 000002 00001
EXEHDR 000801 00080C 00000C 00001
CODE 00080D 000821 000015 00001
GFXDATA 002000 00203F 000040 00001
Exports list by name:
---------------------
__EXEHDR__ 000001 REA __LOADADDR__ 000001 REA
Exports list by value:
----------------------
__EXEHDR__ 000001 REA __LOADADDR__ 000001 REA
Imports list:
-------------
__EXEHDR__ (exehdr.o):
[linker generated]
__LOADADDR__ (loadaddr.o):
[linker generated] linker.cfg(5)
And a hexdump of the final binary file:
0000000 0801 080b 0320 329e 3630 0031 0000 80a9
0000010 f88d a907 8d01 d015 80a9 018d 8dd0 d002
0000020 1f4c 0008 0000 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
*
0001800 ff00 ffff ffff ffff ffff ffff ffff ffff
0001810 ffff ffff ffff ffff ffff ffff ffff ffff
*
0001840 00ff
0001841
The "GFXDATA" segment is my sprite. The sprite is 64 bytes of $FF
, so it should look like a solid square. This sprite data is located at address $2000
.
The "CODE" segment is starts at the usual BASIC start location, and ca65 is inserting a BASIC loader for me so I can just type run
after loading the program.
I have not switched the VIC's bank, so the screen is still at its default address range ($0400-$07FF
), with the last 8 bytes of this range being my sprite pointers. I'm only using sprite pointer 0 ($07f8
) because I only have one sprite.
When I run the program, everything locks up -- which is to be expected, because the program ends in an infinite loop. But I don't see the sprite anywhere on screen:
What am I missing?
As @Jester pointed out in the comments, the X-position and Y-position memory addresses are wrong. The correct addresses are $d000
and $d001
:
; set x and y position
lda #$80
sta $d000
sta $d001
This is the corrected code:
.segment "CODE"
; set sprite pointer index
; this, multiplied by $40, is the address
; in this case, the address is $2000
; $80 * $40 = $2000
lda #$80
sta $07f8
; enable sprite 0
lda #$01
sta $d015
; set x and y position
lda #$80
sta $d000
sta $d001
loop:
jmp loop
.segment "GFXDATA"
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
And here's a picture of it in action: