~matthilde.blog

Fun little blog about various things

Rolling my own 6502

September 30, 2023 — matthilde

Original publishing date: Sun Dec 11 09:16:17 PM UTC 2022

I always liked making emulators for some reason. A few years back I worked on the VICERA fantasy console. I also worked on a CHIP-8 emulator that I will eventually publish (whether I publish it or not, you're not missing much...). But I wanted to go further.


After watching Ben Eater's 6502 series, I thought it would be a good idea to work on a MOS 6502 emulator. I initially wanted to build a physical 6502 computer but it turns out I am completely broke and cannot afford anything to make it.

The MOS 6502 was a revolution back in the 80s, it was a really cheap microprocessor compared to the Motorola 6809 or the Intel 8080. It is an iconic chip that has been used in the NES, the BBC Micro, the Commodore 64, the Atari 2600, the Apple I and II and many other microcomputers of this era! It is even still manufactured today and has its place in the hobbyist market for nerds who like to build their own retro microcomputer or learn how computers works.

Other motivations I had to make this project is also making a spiritual successor of the VICERA out of it and learn 6502 assembly and how it works (Spoiler: It worked quite efficiently).

So now, let's talk about this big project that has been fun and painful at the same time to make.

Starting Point

To get started, I had to consider how and with what will I implement this emulator. If you have seen my previous projects, you might already know the answer: I'm gonna use the C programming language. While some people hate it, I -for some reason- love it.

The CPU will work around a few functions: run_opcode, irq, nmi, reset and init_cpu.

  • run_opcode will execute one opcode.
  • irq will trigger an IRQ.
  • reset will reset the CPU.
  • nmi will trigger a Non-Maskable Interrupt.
  • init_cpu will initialize our CPU.

The CPU will be a struct that contains register informations but also function hooks: A write and read function. On each read/write, it will run the respective function with the CPU struct, the address and (if writing) the value to write. I found this technique convenient and easy to implement to support memory-mapped I/O.

I also decided I would neglect BCD adc/sbc and accurate cycle counting.

Implementation

Now that we have designed our interface and choosed a programming language, we can now start implementing the emulator. Implementing all the opcodes was an horribly tedious task (even though there is a logic pattern in the opcodes, I only realised it later like a complete dumbass). I made an opcode struct and defined all opcodes with the number of cycles, the size and what do they do. Like this:

// fn(cpu)
typedef void (*CPUopcodefn)(struct CPU6502*, word);

typedef struct CPUopcode {
    byte argsize; // can be up to 2
    byte cycles;  // number of cycles required
    CPUopcodefn function;
} CPUopcode;

#define DEFOPCODE(name, argsize, cycles, op)                \
    static void opcode_fn_##name(CPU6502* cpu, word arg) {  \
        op;                                               \
    } \
    CPUopcode opcode_##name = \
        (CPUopcode){ argsize, cycles, opcode_fn_##name }

Along with a few convenient macros for addressing modes, read/write and stack operations, I ended up with lines and lines of definitions that looked like this:

DEFOPCODE(bpl,    1, 2, _NBRANCH(CPUFLAG_NEGATIVE));
DEFOPCODE(bmi,    1, 2, _BRANCH(CPUFLAG_NEGATIVE));
DEFOPCODE(bvc,    1, 2, _NBRANCH(CPUFLAG_OVERFLOW));
DEFOPCODE(bvs,    1, 2, _BRANCH(CPUFLAG_OVERFLOW));
DEFOPCODE(bcc,    1, 2, _NBRANCH(CPUFLAG_CARRY));
DEFOPCODE(bcs,    1, 2, _BRANCH(CPUFLAG_CARRY));
DEFOPCODE(bne,    1, 2, _NBRANCH(CPUFLAG_ZERO));
DEFOPCODE(beq,    1, 2, _BRANCH(CPUFLAG_ZERO));

Thanks to this reference, a very clear and well-made opcode reference that i have made very good use of.

Yes I know, this is quite inefficient. I then added all of this in a giant struct array. I then implemented interrupt and opcode execution functions, which were really easy to make. But once that I have "bulk-implemented" all those opcodes, it was obvious that the incoming step of the development was going to be very very challenging.

Debugging the emulator

I'll make it clear right now: It was painful. But it was the most interesting part of this project. It took me a while to figure out a good way to debug that emulator full of bugs, but then got inspired by that thing called Gameboy Doctor (thanks icefox). At this point I started looking for a good 6502 reference implementation that I could use to debug. After a long time searching online, I found fake6502. I honestly couldn't have found better than this. So I assembled and used a 6502 test suite that will help me find all the bugs inside it, along with the .lst file generated by the assembler and Cutter to find what's wrong more easily.

fake6502 had a very convenient and easy-to-use API, made in a minute a small program that logged registers on every opcode execution (which I did so for my emulator) to run the tests and ended up with a colossal .txt file (about 800 megabytes!), with that I generated a "bad dump" on each fix from my emulator then compare the results with a small python script I made. When the registers does not match, It would output something like this:

ERROR! Does not meet!
Good: PC=dead SP=be A=ef X=12 Y=24 S=ff
Bad:  PC=cafe SP=ba A=be X=12 Y=34 S=00

That was extremely useful. It took a few hours to completely debug all the opcodes. I think the hardest was the overflow flag, it was somehow extremely painful to figure out how to implement it properly.

But honestly, overall, it was a rather fun part of this project. But more fun comes afterwards...

Making an Apple I emulator

Ending the project on the MOS 6502 emulator itself would be give some disappointing results in my opinion. It's like putting a 6502 chip on a breadboard and not connect anything else like I/O, memory, a ROM, etc. I needed to make an application from it. This is why after this very time-consuming and tiresome debugging that I choosed to emulate the Apple I. But why the Apple I specifically? Well, a few good reasons:

  • It is a very simple machine, there isn't much complex hardware around the CPU. Basically just 2 8-bit ports, RAM and a ROM.
  • There are some applications written for it (duh).
  • There is no VRAM directly hooked up to the computer, the video interface is essentially through parallel. This allows to implement "video" directly in my terminal emulator and not having to use a graphics library such as SDL.

All I had to do is emulate the Motorola 6821 (in which uneccessary hardware behavior has been neglected), set the terminal to raw mode and set up some RAM and the Woz Monitor ROM like this:

+-----------+------------+
| 0000-7FFF | Lower RAM  |
+-----------+------------+
| D010-D012 | Serial I/O |
+-----------+------------+
| E000-EFFF | Higher RAM |
+-----------+------------+
| FF00-FFFF | WozMon     |
+-----------+------------+

The keyboard and parallel terminal was easy to do as well. Just reading from stdin and output from stdout. I just had to bind some keys to another code (like backspace, escape and return) for the keyboard input and AND-ing 0x7f to the terminal output before being sent to stdout with some bindings (like backspace again) as well.

I decided to hard-code the 256 bytes Woz Monitor ROM directly in the emulator because I can and added the possibility to transfer a 4KiB binary file in the higher RAM by specifying it to the command-line. I just had to load the Apple I BASIC ROM then type E000 R to enjoy it.

Here is a bunch of very useful resource I used to implement this emulator. Since the Apple I is not extremely popular among emulator developers and hobbyists, it took me a bit of research to find good resources for implementation.

To conclude...

I learned a lot from this project, how the 6502 and the Apple I worked. I had fun making it and I also took this occasion to finally write a blog alongside with the project. Something I haven't done for a while. I might consider toying around with the Apple I emulator to make something bigger and eventually end up working on my fantasy computer. I could as well consider making an NES emulator from it.

It's been a while I haven't posted any blogpost and I hope you enjoyed it. There are a lot of drafts of unfinished and unpublished blogs that I could finish. If I decide to finish them, you will probably see them pop on my site.

You can find the source code of this project here and I hope I inspired you to make something better than I did ^^.

Tags: repost, architecture, programming, c