loading...

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

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:

  1. write assembly code program on PC
  2. assemble to machine code using DASM
  3. list machine code as a list of decimal integers
  4. write a BASIC program that POKEs the integers into memory
  5. 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