Commodore 64 Development
Hello World
My brother gave me a refurbished C64 for christmas. I wanted to try out writing some assembly code for it. I have previously played around with C64 assembly, but only on an emulator.
The C64 has a MOS Technology 6510 processor that runs at 1 MHz. It is a modified version of the popular MOS 6502. The Ricoh 2A03 in the NES is also based on a 6502.
One of the simplest C64 programs flashes the background:
loop: inc $d020 ; increment BG color
jmp loop
A few lines can be added to check for the space key, to be able to stop the program:
loop: inc $d020 ; increment BG color
lda #$7f ; check for space pressed
sta $dc00
lda $dc01
and #$10
bne loop
This is all great, but I have one problem: I don't have an assembler for my C64, so how do I actually run this?
C64 Development on PC
An easy way to develop for C64 is writing the code in a your favorite editor, and using DASM to cross assemble. Add these lines at the top of the program:
processor 6502
org $1000 ; program base address
And assemble:
dasm tiny.asm -otiny.prg
And run it with the WinVICE emulator:
WinVICE\x64 tiny.prg
VICE loads the program automatically, and we only need to jump to the memory location $1000 from BASIC:
SYS 4096
It could not be easier. For fast development, this is clearly superior to working on the actual hardware. But of course, there's nothing like the real thing, and I '''want''' to run this on my real C64. Here is where it gets a bit tricky.
Embedding Machine Code in BASIC
Here is some great advice from the past, which I'm going to completely ignore:
''If you wish to write machine language programs, it is strongly suggested that you purchase an assembler of some sort. Without an assembler you will probably have to "POKE" the machine language program into memory, which is totally unadvisable.''
- Commodore 64 Programmer's Reference Guide, Copyright (C) 1982 by Commodore Business Machines, Inc. All rights reserved.
Idea: Poking Machine Code to Memory
If you want to transfer an assembled program to a physical C64, one option is to POKE it directly into memory and running it from there. For example, the following assembly code:
inc $d020
is assembled to machine code, listed in hexadecimal:
ee 20 d0
or in decimal:
238 32 208
We can POKE this directly into memory from BASIC:
poke 4096,238
poke 4097,32
poke 4098,208
and then jump directly to address 4096 to execute it:
sys 4096
Using this approach, we can now formulate a plan for running assembly code from BASIC:
- write assembly code program on PC
- assemble to machine code using DASM
- list machine code as a list of decimal integers
- write a BASIC program that POKEs the integers into memory
- run the machine code from memory
General BASIC Poking Program
Machine code can be embedded as data in a BASIC program that POKEs it into memory and calls SYS to run it. The following program has the machine code version of the above program embedded as data, and POKEs it into address 4096:
10 bs=4096
20 read b
30 if b=-1 then sys(4096)
40 poke bs,b
50 bs=bs+1
60 goto 20
100 data 238,32,208
9999 data -1
The program repeatedly copies the embedded data (in this case, our three numbers) to memory starting from address 4096. The data ends when the value -1 is reached, after which the program jumps directly to address 4096 to execute the machine code. The cool thing about it, though, is that it can be used with '''any''' machine code program.
Let's Try It Out!
Let's try converting this assembler program to BASIC:
loop: inc $d020 ; increment BG color
lda #$7f ; check for space pressed
sta $dc00
lda $dc01
and #$10
bne loop
If we save the above listed program as test.asm, we can assemble it with DASM:
dasm test.asm -otest.prg
The output is 17 bytes of machine code:
hexdump -C test.prg
00000000 00 10 ee 20 d0 a9 7f 8d 00 dc ad 01 dc 29 10 d0
00000010 f1
If we rearrange it, it's a bit more recognizable. Note that all addresses are Little Endian, meaning that the least significant byte appears before the most significant byte:
00 10 ; Location header ($1000, little endian)
ee 20 d0 ; inc $d020 - increment at address $d020 (flash background)
a9 7f ; lda #$7f - load accumulator (immediate addressing)
8d 00 dc ; sta $dc00 - store accumulator in address $dc00
ad 01 dc ; lda $dc01 - load accumulator (the opcode is different for absolute addressing)
29 10 ; and #$10 - and accumulator with $10
d0 f1 ; bne PC - 15 (8 bit representation of -15 is 0xF1)
This Ruby program outputs the bytes in decimal form:
File.binread("test.prg").bytes.each{ |b| print " %d" % b} ; puts
Output:
0 16 238 32 208 169 127 141 0 220 173 1 220 41 16 208 241
This can be embedded as data in BASIC. We skip the two bytes of location header, as the location is implied by the BASIC program:
100 data 238,32,208,169,127,141,0,220
110 data 173,1,220,41,16,208,241
So the full BASIC program with our embedded machine code becomes:
10 bs=4096
20 read b
30 if b=-1 then sys(4096)
40 poke bs,b
50 bs=bs+1
60 goto 20
100 data 238,32,208,169,127,141,0,220
110 data 173,1,220,41,16,208,241
9999 data -1
When the program is run, we get a sweet flashing background! Yay!
Auto Generated BASIC with Embedded Machine Code
I wrote a little Ruby script to automatically generate the BASIC program with an embedded machine code program:
ruby prg2bas.rb test.prg >test.bas
Here is the Ruby source:
d = File.binread(ARGV[0],10000,2).bytes
s = "10 bs=4096
20 read b
30 if b=-1 then sys(4096)
40 poke bs,b
50 bs=bs+1
60 goto 20"
x = 0
d.each { |b|
if (x % 8) == 0
s += "\n#{x/8*10+100} data "
else
s += ","
end
s += "%d" % b
x+=1
}
puts s+"\n9999 data -1"
References
- Project 64:Commodore 64 Documents - these are a must for any C64 owner.
- digitalerr0r: Commodore 64 Programming #1: A quick start guide to C-64 assembly programming on Windows
- The dasm macro assembler