Skip navigation
Published Tuesday, July 1, 2014 at 6:48 PM

Baby Steps

Before we get our ROM monitor off the ground, we'll need to sort out a few things first. The most important decision will be what assembler to use. I've decided to go with the CC65 tool chain, because I'm already familiar with it and I don't have a lot of time to come up to speed with a new assembler. Now, a word of caution: CC65 is no longer being developed, so its fate is uncertain. There are other assemblers out there, and if I were doing this outside of Retrochallenge and had more time, I would probably look into them. Chief among these seem to be Ophis, a 6502 cross assembler written in Python, and XA, a venerable cross assembler with a long history.

Now that I've picked my assembler, it's time to set up the project. I'm going to get things rolling with a very simple skeleton directory. If you want to follow along in real time, the project is hosted here on my Github.

seth$ ls -lF
total 40
-rw-r--r--  1 seth  staff   208 Jul  1 18:11 Makefile
-rw-r--r--  1 seth  staff   913 Jul  1 18:15 monitor.asm
-rw-r--r--  1 seth  staff   387 Jul  1 18:11 symon.config

I'll explain what each of these files is for.

Makefile

Let's start with the Makefile. This is a pretty bog-standard Makefile that knows how to assemble and link the source code in monitor.asm.

CA=ca65
LD=ld65

all: monitor

monitor: monitor.o
	$(LD) -C symon.config -vm -m monitor.map -o monitor.rom monitor.o

monitor.o:
	$(CA) --listing -o monitor.o monitor.asm

clean:
	rm -f *.o *.rom *.map *.lst

This is pretty self-explanatory. Typing make will use ca65 to assemble the monitor.asm file and output the object file monitor.o. Then, the ld65 linker will link the object file and produce the final ROM image, monitor.rom.

That symon.config file needs further explanation.

symon.config

MEMORY {
RAM1: start = $0000, size = $8000;
ROM1: start = $C000, size = $3B00, fill = yes;
MONITOR: start = $FB00, size = $4FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}

SEGMENTS {
CODE:     load = ROM1, type = ro;
DATA:     load = ROM1, type = ro;
MONITOR:  load = MONITOR, type = ro;
VECTORS:  load = ROMV, type = ro;
}

This file is basically a memory map of the RAM and ROM in the system I'm targeting. For me, this is my home-brew 6502 with 32KB of RAM and 16KB of ROM. The MEMORY section is the full memory map, and the SEGMENTS section spells out where the assembly language segments live in the memory map.

monitor.asm

Finally, there's the assembly source code itself, in monitor.asm. Right now, it does absolutely nothing:

;;
;; Retrochallenge Summer 2014
;; 6502 ROM Monitor
;;

;;************************************************************************
;; Non-monitor code, e.g. BASIC, utilities, etc., resides
;; in the bottom 14KB of ROM
;;************************************************************************
.segment        "CODE"
                .org    $C000

;;************************************************************************
;; ROM monitor code resides in the top 2KB of ROM
;;************************************************************************
.segment        "MONITOR"
                .org    $FB00

START:          LDA     #$00    ; Set zero flag
                BEQ     *       ; Loop forever

;;************************************************************************
;; Reset and Interrupt vectors
;;************************************************************************
.segment        "VECTORS"
                .org    $FFFA

                .word   START   ; NMI vector
                .word   START   ; Reset vector
                .word   START   ; IRQ vector

Notice how each .segment pseudo-op maps to one of the segments from the symon.config file? That tells the linker exactly where to put things in the final ROM image.

Right now, this code really doesn't do much. On reset, the 6502 will load the address located at $FFFC and $FFFD into the program counter and start running from that address. Here, the destination is the address of the START label, which works out to $FB00. The instruction at that address loads $00 into the accumulator, which sets the zero flag. The very next instruction tells the 6502 to branch back to itself if the zero flag is set — an infinite loop.

That's it for today. I just wanted to get a project off the ground and give me a framework to start developing in. More tomorrow.