6502 days of christmas

Introduction

I have been programming now for roughly 9 years and as with many developers, the longer I program, the lower I go in the stack. My personal journey went something like:

where especially the C# experienced helped me then a lot to find my first coding jobs. But still, I never really ventured into even lower level languages like C, or asm.

So time to change that: For this (last) years advent of code I decided to really challenge myself and do it in 6502 assembly.

Advent of code

My relationship with advent of code is so-so: I really like the idea, but I never had the time or interest to follow through with the full calendar. Also the timing kind of sucks, since december is one of the busiest months of the year for me. So looking at the overview, I managed to gather 11 🌟 stars 🌟 over 2 years I participated in (not counting 2025). So don't expect too much solved days out of this one, especially since I'm going in with exactly zero prior knowledge of assembly.

Why the 6502 assembly flavour?

There are quite a lot different assembly languages one could learn and choosing a first one as an entry point was kind of overwhelming in the beginning. A business-valid choice would have been x86-64, since that is what all the pcs at my job are running. I decided against that, since 1: it's boring to go for the most valuable move and 2: I read multiple times that this flavour of assembly is meant to be compiled to and not written by hand that much.

I looked into old chips that were actually programmed in assembly at their prime-time and the 6502 caught my attention. It was used in tons of systems, like f.e. the Acorn Archimedes, or the Commodore 64 and learning it's language could open up the skill to write Nintendo Entertainment System (nes) games, which just sounds great. Also the number of opcodes is low, which should translate into a short learning curve.

Resources for learning 6502 assembly

There is a lot of very valuable information online if one can find it, as always:

Setting up my dev environment

This was quite annoying. The chip is old and a lot of tech for it was written quite a while ago, so the nice modern toolchains I'm used to were rarely available. Since I did not want to mess around with setting up a toolchain to build 90s/2000s era C projects, I looked at hobbyist tooling developed more recently.

One project I found and used for day 1 of advent of code was the rs6502 crate, since that one has an assembler I could use out of the box. Sadly I had some errors running the built-in Cpu emulator, so for emulation I use the mos6502 crate. There probably also are language servers, but since all the assemblers have slightly different flavours of the constructs they support on top of the standard opcodes, I did not bother and just wrote the assembly by hand.

During coding the solution for day 1 I quickly realized the need for a debugger, but I could not find one that was easy to install and use, so I postponed it, just printing out register states of the emulator after every instruction. Having a proper debugger from the start would have definitely saved time.

The assembler I used initially was very basic, so I ditched it after a while for the customasm crate, which helpfully includes a sample 6502 syntax in it's examples!

The stack I ended up with is roughly:

My Debugger Here you can see the debugger stopped after calculating the result of day 3 part 1

Spoiler ahead

it's 0x4261 from memory addresses 0x0000 and 0x0001

The 6502

There are actually multiple versions of the chip, with the first one that saw wide adoption being the initial release of the MOS MCS 6502. Later versions apparently fixed some bugs and also added more opcodes, but I decided to stick to the OC.

All variants are 8-bit by default, and through and through. That means all registers are only a single byte wide, with no way to combine them into 16-bit values. This initially was very confusing to me, as all one can do with one byte is address 256 memory locations.

Registers

Oh boy, there are almost none and using them has quite the learning curve:

The biggest footguns are instructions that work with one of the registers, but not with another. So f.e. even though most arithmetic only works on the accumulator, there are opcodes for increasing the X and Y registers (INX and INY), but no INA. Another thing that was super annoying is even though X and Y are both kind of general purpose, they are used slightly different when indexing into memory, so f.e. there is LDA (indirect,X) and LDA (indirect),Y which both do something very different, so one must keep that in mind when deciding on which registers to use for what.

Memory Layout

The 6502 has a 16-bit adressable memory space, which equates to 65.536 (2^16) bytes of memory. The architecture does not discern between code and RAM, and everything can live everywhere in the whole address space. In practice you need to be careful though to never overlap, as general memory typically does not equate to valid opcodes.

There are a few special memory locations reserved for internal functionality of the chip:

In general, the memory is split into 256 pages, which each are 256 bytes wide. Some of the instructions take more cpu cycles when they have to cross a page boundary.

Zero Page

The very first memory page from 0x0000 to 0x00ff is special in many ways:

Opcodes

I'll focus on the opcodes I used most frequently here, for a complete overview, see the amazing resource at masswerk.at.

Also in the next post, I'll explain the opcodes that I'm using in details as I go along with the solution to day 1

Memory IO

Register manipulation

Control flow

GOTO all the way down

Yeah so control flow is just GOTO with a slight variant of using subroutines via JSR (JumpSubRoutine) and RTS(ReTurnfromSubroutine). Structuring programs and following the flow was very hard for me in the beginning with only this construct.

Day 1

I'll go over my solution to day 1 in the next post, to keep this one focused on the general setup and information about the technology itself.