Let's Build a Compiler in Go for the 6502
compiler 6502Introduction
From 1988 to 1995, Jack W. Crenshaw put out a fifteen-part series entitiled “Let’s Build a Compiler”. It is a nice introduction to compiler construction. Being a bit dated, I thought it might be a fun project to create a series of similar series of articles using a more modern language. I use Go as my daily driver at work, so I’ll go ahead with that here. I won’t be using proper go idioms, but rather will try to more closely follow the structure of the Turbo Pascal in which the original series was written.
I’ll contrast the use of writing the compiler in a modern programming language by targeting an older CPU architecture. I had followed Ben Eater’s 6502 project and really wanted to write a compiler that gave me 6502 code. So these desires will blend here. As we build our compiler, it will translate to 6502 assembler that we’ll build with vasm.
The goal of this project is to learn a bit about how a compiler can take the text of a program and output something that a computer can execute. This isn’t how modern programming languages do it, but the concepts are there. This is just one way to take some source code and run it on some CPU, probably not a very good one.
We’re going to convert our source code directly to assembly for the 6502, a real compiler would build up a abstract syntax tree and then produce some IR. It would process and optimize the IR before finally producing whatever artifacts it produces. Those artifacts might be bytecode for a JIT compiler, or machine code for some CPU architecture.
Our translator skip all of that and will instead spit out 6502 mneumonics as it goes along, which will then process with an assembler to produce 6502 machine code. I don’t really know much about 6502 systems, so at least for awhile, we won’t make many assumptions about the system as a whole.
Running the code
We’ll need somewhere to run the code. Chances are you aren’t reading this on a computer that runs on a 6502 processor and you don’t have one handy. If you do, awesome! If not, thats ok, we can use an online emulator to run the code.
The code we’ll produce will look something like
LDA #1
STA $ff
LDA #2
CLC
ADC $ff
We won’t be expecting user input, not output anywhere but memory or leaving the result in the A register or something.
Though the online emulators below have some trade-offs, either should work to get started. Later posts might have more details and different suggestions on running the compiled programs.
Easy6502
Easy6502 is, as the name implies, an easy 6502 emulator to get started with. It even has a fancy display, though I doubt we’ll get to use it. You can paste the above code into the text field, asseble and run it, and you should see A=$03 showing that we successfully added 1 and 2 and the result is in the accumulator!
It looks like it only branches to labels, i.e. No hard-coded offsets. This might be fine.
mass:werk virtual 6502
mass:werk has another nice one, it seems a bit more fully-featured and general purpose. It is really very nice, but not quite as easy to jump into.
You have to assemble it yourself(we’ll use vasm, as mentioned above) and paste in the resulting binary as a hexdump. You can then load that into memory wherever you want(the default memory location is 0x0800). Annoyingly, the PC resets to 0 when resetting the CPU. The 6502 uses the first page of address (0x0000-0x00ff) for special zero page addressing.
For our short programs it will probably be fine to load the memory at 0000 and only use high zero-page addresses. Then when we reset the CPU it’ll start from our code.