==================
== Ralph Landon ==
==================
My blog subtitle goes here?

6502 Go Compiler Hello World

compiler default

So what are we doing?

As mentioned in the introduction, we’re going to take a really simple approach to building a compiler and creating executable code following Jack Crenshaw’s “Let’s Build a Compiler” series. Ideally, you’ll follow along, writing this code, assembling the output, and executing on a 6502 emulator somewhere. It will probably get really annoying to keep reading “As in the original articles”, so take it a given that the rest of this series is shamelessly stolen from that wonderful work.

We’re going to build up a compiler following an incremental approach, starting with a small subset of the language and building it up incrementally as we proceed. This week, we’re just going to produce a “Hello, World!” of a compiler. It won’t do much, but we’ll be confident in our setup to proceed to the next steps.

The Cradle

The original articles call the necessary boilerplate “The Cradle”. We don’t quite need all of it, but we’ll copy over most of it just to make it easier to follow the style used.

Assuming you have Go installed, create and initialize a new directory.

mkdir 6502-go
cd 6502-go

go mod init
git init

touch main.go

As mentioned, we’re not really going to follow go idioms, we’ll probably have all the code in one file. So let’s create main.go right here with the following code in it.

package main

import (
	"fmt"
	"os"
)

var Look byte

func GetChar() {
	b := make([]byte, 1)
	n, err := os.Stdin.Read(b)
	if n == 0 || err != nil {
		Look = 0
	} else {
		Look = b[0]
	}
}

func Error(s string) {
	fmt.Println()
	fmt.Printf("Error: %s.\n", s)
}

func Abort(s string) {
	Error(s)
	os.Exit(1)
}

func Expected(s string) {
	Abort(s + " Expected")
}

func Match(x byte) {
	if Look == x {
		GetChar()
	} else {
		Expected("'" + string(x) + "'")
	}
}

func IsAlpha(c byte) bool {
	return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
}

func IsDigit(c byte) bool {
	return c >= '0' && c <= '9'
}

func GetName() byte {
	if !IsAlpha(Look) {
		Expected("Name")
	}
	n := Look
	GetChar()
	return n
}

func GetNum() byte {
	if !IsDigit(Look) {
		Expected("Integer")
	}
	n := Look
	GetChar()
	return n
}

func Emit(s string) {
	fmt.Print("\t" + s)
}

func EmitLn(s string) {
	Emit(s)
	fmt.Println()
}

func Init() {
	GetChar()
}

func main() {
	Init()
}

Ok, that’s kind of a lot, but there shouldn’t be anything too surprising. We have some utilities to get a character from the input, compare it to letters and numbers, and print strings and errors. If you run it right now, it should compile and expect at least 1 character in Stdin.

It isn’t really a “Hello, World!” yet, so lets have it parse an expression, the simplest expression we can start with it a single digit, let’s update the code:

func Init() {
	GetChar()
}

func Expression() {
	EmitLn("LDA #" + string(GetNum()))
}

func main() {
	Init()
	Expression()
}

Great! If you run it and supply an integer, we should get some valid 6502 assembly code!

This is where we’re really deviating from the original articles, they produced assembly for a 68000 CPU, which has eight data registers. Our 6502 only has 3. An Accumulator, the A register, where most of the magic happens, and an X and Y register, mostly for indexing memory.

LDA is the 6502 opcode for “Load A”, and the # means “Immediate”, so instead of reading memory address 0009, we actually was a constant, decimal 9 in the A register.

echo 9 | go run . | tee a.asm
        LDA #9

We can assemble that with vasm and get a binary that will run on our 6502.

> vavasm6502_oldstyle -Fbin a.asm
vasm 1.8h (c) in 2002-2019 Volker Barthelmann
vasm 6502 cpu backend 0.8 (c) 2002,2006,2008-2012,2014-2018 Frank Wille
vasm oldstyle syntax module 0.13f (c) 2002-2018 Frank Wille
vasm binary output module 1.8a (c) 2002-2009,2013,2015,2017 Volker Barthelmann

.text(acrwx1):             2 bytes

> xxd a.out
00000000: a909                                     ..

If everything went well, we should get a909, a9 is the opcode for LDA, and 09 is the number we supplied. While this is a very simple example, it is pretty cool that our compliler was able to go from source code 9 to 6502 assembly LDA #9 that can be assembled and run!

Parsing a constant integer expression is the first step towards being able to handle more complex expressions. Next, we’ll work on basic math expressions of the form (9+3)-5*7.

Executing the binary

If you’re using Easy6502, you can drop the assembly right into the editor without using vasm and upon running it you should see that the A register now has a value of 09.

If you’re using mass:werk, it takes a few steps. I use xxd -ps a.out to strip the extra info, giving just a909 as output. Copy that into the RAM editor and change the start address to 0000. Then click Load Memory. That should show the next instruction to be LDA #$09, A <= $09. If you click Single Step it should execute that step and AC will be 09.