My first, faltering steps in 6502 land
Friday, March 26, 2010 12:52:34 AM
Getting the tools
To get started I had to get a few things:
- An emulator
- A cross assembler
- A hello world
Since I'm running Linux, Vice seemed to be a logical choice. It is widely available and I just installed it through my distro, gentoo. I'll be using the x64 program here, which is the X-version of Vice.
When it comes to assembler, I was recommended DASM. It wasn't available in my distro of choice, so I downloaded and compiled it myself, which was straight forward.
As for a hello world, I found a nice intro on YouTube, but it wasn't a perfect fit for me since it showed a assembler running on the C64, which also has a slightly different syntax than DASM. So I did my best to adapt it and with a bit of help from a friend I got it to run!
There's also a problem of encoding the ASCII string into the code used by the character ROM. E.g., 'A' in the character ROM is 1, not 65 as in ASCII. I went full out and created a translation table from ASCII to ROM codes for all the characters on the C64. Another option would have been to just write the message string in ROM code directly, but it just looks so incomprehensible =)
6502 Assembly Basics
First of all, the 6502 has three registers we'll be using, the x, y and a registers. The a register is known as the accumulator, most arithmetic and logic operations use this both as one of the operands and to store the result. The x and y are general purpose registers that are slightly different to one another in how they can be used in some addressing modes.
The 6502 processor has a few different addressing modes and we use two of them here:
- Immediate - The operand is given as a hard coded number.
- Absolute indexed with x/y - A 16 bit address is given as a hard coded number and using x or y as an index.
Immediate operand is used to load an arbitrary number into a register and is done by preceding the operand with a hash sign.
; Load 0 into the a register. lda #0
Absolute addressing indexed with x or y uses a fixed address, given as a number (or in my case as labels or constants) and then x or y. The operand is the byte that is located at the fixed memory address plus what is in the
given register.
; Load the code for the 'A' character into screen memory plus 3. lda #1 ; Code for 'A' ldy #3 sta $0400,y
Another keyword I'm using quite a bit is "dc.b", which simply tells the assembler to declare bytes of memory in that location. This lets you put data into the program and you can use it to create global variables. After a declare statement you can write a comma separated list of strings, numbers etc.
; Declare an ASCII string and terminate it with a 0-byte. dc.b "HEJ", $00
Another version is "ds.b" which lets you specify how many bytes that you want and then what value should be stored in them.
; Declare 5 bytes all initialized to 0. ds.b $05, $00
That should really explain most parts of the code, so let's get to the listing!
; Set up target processor and address where we wish to be loaded.
; Apparently these _have_ to be indented at least one step, otherwise
; DASM throws a fit.
processor 6502
org $1000
; Address to start of screen memory.
SCREEN equ $0400
;
; Main code
;
; Use x as an index for how far into the string we are.
ldx #0
output_loop:
; Load the next ASCII character into y.
ldy hello,x
; If this is a NULL byte, quit looping.
beq end_output_loop
; Load the ROM code for this ASCII character into a.
lda ascii_to_rom,y
; Store the code into screen memory.
sta SCREEN,x
; Increase x by 1.
inx
; And always jump back.
jmp output_loop
end_output_loop:
; Exit the program.
rts
; Null terminated hello world string in ASCII.
hello:
dc.b "HELLO WORLD!", $00
; Translation table from ASCII to C64 ROM codes.
ascii_to_rom:
ds.b $20, $20
dc.b " !",'","#$%&'()*+,-./"
dc.b "0123456789:;<=>?"
dc.b $00
; Use DASM macros to insert the numbers 1-26.
X SET 1
REPEAT 26
dc.b X
X SET X + 1
REPEND
dc.b $1b,$20,$1d
Assembling and Running the Program
Now, assuming we've saved the source code in the file hello.s, let's go ahead and assemble it into a prg-file that can be understood by Vice.
$ dasm hello.s -ohello.prg
And finally, let's get it running. To load the prg-file you can either start x64 with the prg-file as a command line argument, or if you want to have Vice running and reload the prg-file, you can use "Smart-attach disk/tape..." under the File menu and choose your file.
$ x64 hello.prg &
Now it's loaded into the C64's memory at the address given by the org-keyword in the source. Now we just have to tell the C64 OS to jump to that address, which is done by the SYS-keyword. We need to give it as a decimal number and since we set the address to $1000 (i.e., 1000 in hexadecimal) we need to run the following in Vice:
sys 4096
That's it! You should see "HELLO WORLD!" at the top left of the screen. Next time I'll be looking a bit at more advanced addressing modes, so that we can loop over larger data sets than 256 bytes and make some subroutines etc.
Oh, and I made this silly little scroller as an extension to the hello world program, go check it out!
References
6502 Assembly
DASM documentation




















