[home]
[sandbox]
[resume]
[contact]
The Super Nintendo/Super Famicom is one of my all time favorite consoles. As a kid I spent countless
rainy day hours (the only time the family allowed video games) playing rented cartridges from the
local video rental store. However one particular game I permanently acquired: Super Mario World. Every
iteration of this game I get without hesitation be it the original, Japanese cartridge, and re-releases
on other consoles. Something about the design language, the user experience, the feeling of endless
secrets to discover, and always a nice challenge curve made it, for me, the perfect game.
A decade after I first played a SNES I entered university to study computer science. Like other
millenial compsci students the dream of making video games was a large inspiration to selecting the
major. But of course the 4 years had nothing to do with gaming, at least not directly. However my
coursework was always in the C programming language with the occasional bout in Scheme. One of
my professors proudly mentioned her former MIT classmates founded Naughty Dog and wrote games in Lisp
to make us more excited to use Scheme.
For years the idea of creating an emulator seemed fascinating but always out of reach. How cool to
imagine other hardware and languages running on my tiny netbook? As I grew older the idea stayed an
idea because honestly after a long day of software developer activities I wanted nothing more than
to avoid a computer altogether. Now suddenly I have a friend who shares a similar passion and one
weekend we thought "we write ultra-low latency, high performance trading systems in C so how hard
could an emulator be?"
Here's that journey.
what is an emulator?
At time of first writing this in 2025 I have about 12.5 years of industry experience working on a wide
breadth of products from custom microkernels on ARM architecture to Postgres database execution engine to
super high performance trading systems. Fortunately all in C with some exposure to assembly of both ARM
and Intel x86. While much of my coursework from degree is lost memories, refreshing isn't out of the
question. So I've in theory worked with a hypervisor and contributed to byte-code virtual machines. An
emulator can't be much different?
An emulator (in the context of this project) is simply software that enables your computer (host) to behave
like another computer (guest). For example an x86-64 machine running an emulation software to mimic a 16-bit
processor of yore. This could include a full operating system, or it could be tiny roms with single function
like make screen green.
WDC 65C816 and Ricoh 5A22
Nintendo chose as the CPU for the Super Nintendo a
[WDC 65C816] 16-bit extension of the
ever popular (used in the NES) 6502 processor. This CPU comes from Western Design Center and was
created at request of Apple in 1983 although they wouldn't formally use it for 3 more years with the
Apple IIGS. Nintendo had a relationship with Ricoh and at some point in time Ricoh reached out to work
with WDC for their [exclusive chip].
This 5A22 is a superset of the 65C816 containing some extra features.
writing an emulator
On a summer Sunday I sat with D and we wondered where to begin. Although we have worked together in a
business capacity implementing some, what I think, are very impressive technical pieces of software
this project starting from zero felt a bit daunting. I thought why not just get some super simple C
program up that reads an opcode and does a thing. It can be quick and dirty with a big ole switch statement/jump
table and we will handwrite a binary file that contains some opcodes. The design can be a struct of the
CPU registers for now and so our program will be:
- enter main and allocate a cpuregister struct
- open a binary file
- loop: read 1 byte, enter jump table for that opcode, update register
- close file when EOF
We are doing this in C on Linux 64-bit machines. Me on a laptop, D on his gaming PC. D with hints from
me wrote up that first pass and we implemented instruction INC: increment accumulator register. With a
silly binary file that contained a few 0x1A bytes we saw our little program open it and update that
register with the number of 0x1A bytes! This success was exciting and we came to the realization this
project idea isn't that scary. After some brief discussions about the first design we want to target
we had our idea: implement the full (or most necessary) opcodes in a way that is very easy to read but
modular so we can isolate the CPU functionality. This lets us design it almost like a library so that
each other hardware component we need to add isn't going into one giga-file of mess but instead we (in
theory) can swap in/out components we want like audio devices or graphics device. Because why stop at
the SNES when we could go and emulate an Apple IIGS? Or even make our own little microkernel for some
fun OS learning.
C is all I do, but..
After that first evening at D's place I went home and got hit with the thought of "what if this was also
an opportunity to try a language that isn't C for once?". I'm most comfortable in C. I could probably
whip this whole project up in a reasonable amount of time with my favorite language using all kinds of
optimization techniques as well. But my resume is deep C and not much else. The internet has me believing
memory safety is king of the industry nowadays so C is less common to find work (locally) so how about
that new kid everyone goes on about in silicon valley: r u s t.
Admittedly this wasn't my first crack at rust but it was my first time thinking about it in a couple years.
And before this my rust experience was ultra-limited to a partially implemented ELF reader utility. So I
stayed up very late wrestling the syntax, compiler errors, and "bad" C behaviors to get a really amateur
rust version of our initial creation completed. I texted D my frustration (rust isn't forgiving...) but
also that I was going to push it to our private repo for safekeeping and that I would probably maintain it
in parallel but no pressure to switch to rust. D tells me "let's do rust, I want to learn it too".
rust
Now we are committed to rust. After a long day of work we are excited to poke at this infamous language
and see if we can make something as clean and simple as we would in C. Many text messages, in-person chats,
and inline comments discussing the "why can't I do this in rust?!" or "there has to be a better way than
just pulling a random crate from the internet". But we're all in and not turning back. Each night a couple
new cpu instructions implemented. Those same nights, the code gets tighter and more concise as we become
more familiar with the syntax. I love C, it is so straight-forward. Rust is very difficult, but now I
can work more alongside it rather than against it.
However there are some things I won't get over. Like initializing a stack variable of any type to all zero
bytes. In C it is oh so easy:
struct foo {
int x;
char y;
char z;
};
int main (int argc, char *argv[])
{
struct foo myvar = {0};
/* ... */
}
In rust you ask? Oh there is no way. At least nothing as easy as just setting it equal to zero. You could
derive a default and define that to initialize to zero somehow but depending on the complexity of the
struct and in our case we had user-defined structs inside of structs (much like we would do in C) that
the compiler didn't like our attempts to short-cut zeroing out our stack allocated object. As mentioned
above our cpu registers were placed neatly in a struct but we also defined our memory system segmenting
the page, bank, and full memory concept into (what we thought) would be easy to access struct members. No.
You may be a seasoned rustacean who reads this and chuckles that it actually is easy. And you could be
totally right. I invite you to contact and show me how! I don't know if it is the onset of AI-slop or
what but internet searching was never providing consistent answers. Between the official rust book, the
rust for c programmers book, stack overflow, rust forums, and more there was always more than one way to
do it all claiming to be the "idiomatic way" along with warnings of potentially no longer being possible
due to rust version update. I guess this is the pains of working with a language in its infancy compared
to the more wise and veteran C. We press on.
changelog
20250815
- D: implemented other decrement opcodes
- D: added memory mode handling and cpu flag updates to DEC instructions
- M: bit twiddling upgrades for the cpu flags
- M: simplified the logging mechanism
- M: PHX/PHY and PLX/PLY push/pull register-to-stack instructions implemented
20250814
- D: fixed the build after M broke it. added memory/register defaults
- M: logger mostly working but broke memory
- M: logging now logs
- D: load the whole rom at once and use program-counter to index (we miss mmap())
- M: LDX load absolute index X register implemented
20250813
- D: ported more of the original C into rust
- M: updated rust code cpu register pretty print to not use mutable parameters
- M: added the memory, page, and banks structs in rust
- M: more work on memory and started a logger, it doesn't build. good luck
20250811
- M: a rust version for giggles
- M: read a whole file in rust 1 byte at a time
20250810
- D: initial commit of the C code
e-mail: [email protected]