
Implementing a NES Emulator in Rust - MichaelBurge
http://www.michaelburge.us/2019/03/18/nes-design.html
======
kouteiheika
Shameless plug - for anyone that's interested, I also wrote a NES emulator in
Rust a few years back:
[https://github.com/koute/pinky](https://github.com/koute/pinky)

I even compiled it to WebAssembly: [https://koute.github.io/pinky-
web/](https://koute.github.io/pinky-web/)

> The CPU address space has several PPU registers mapped. So the CPU maintains
> a permanent mutable reference to the PPU. But the top-level Nes object also
> owns the PPU. I worked around this using Box to assign fixed memory
> addresses to values, and then “unsafe” pointer dereferences when needed.

There's actually a neat little trick which I used in my emulator which allows
you to fulfill Rust's single ownership rule while simultaneously keeping the
subcomponents of the emulator independent, making it possible to interconnect
them without any unsafe and letting the compiler to optimize the whole thing
as everything is statically dispatched.

Basically simplifying a little bit for each component (CPU, PPU, APU, etc.):

1\. You put the whole state of the component into a separate `State`
structure.

2\. You create a `Context` trait which has a `get_state`/`get_state_mut`
method as well as various callbacks needed by the component itself (e.g. PPU's
`Context` has a `peek_video_memory` so that it can read video memory which is
stored externally).

3\. You create an `Interface` trait (with `Context` as supertrait) which
contains the public interface of a given component (e.g. PPU has `execute`,
`peek_ppustatus`, etc.).

4\. You put the whole implementation of the component inside of a `Private`
trait (with `Context` as supertrait).

5\. You do a blanket impl of `Interface` and `Private` for any `T` which
implements `Context`.

So then you create a single `Nes` structure which has `cpu::State`,
`ppu::State`, etc. inside of it as fields, and implements the `cpu::Context`,
`ppu::Context`, etc. traits. Inside of those impls you just wire the various
subcomponents together, and voila, every component is encapsulated from each
other and yet they can talk with each other, and there is no indirection
anywhere (no `Box`es, no `RefCell`s, etc.).

~~~
hathawsh
I'm really enjoying your 6502 emulation code. It feels clean and easy to read.

[https://github.com/koute/pinky/blob/master/mos6502/src/virtu...](https://github.com/koute/pinky/blob/master/mos6502/src/virtual_mos6502.rs)

(I imagine the rest is good too, but 6502 is what I know. :-) )

------
TheServer201
In the Iterators section if you use

    
    
      for (idx, x) in xs.iter().enumerate()
    

instead of

    
    
      for (x, idx) in xs.iter().zip(0..xs.len())
    

you get the 4x unroll
([https://godbolt.org/z/XDyx0w](https://godbolt.org/z/XDyx0w)).

------
devit
Why not use serde instead of rolling your own "Savable"?

Also you should use Rc/Arc and Weak (along with RefCell/Mutex if needed)
instead of the unsafe pointers. Even better, if possible, move the functions
that access multiple components to the object that holds them all, and don't
use any "smart pointers". Another possible design is to pass borrowed
references explicitly to the methods, and add them to the trait signatures
where needed.

Regarding memory access, it's probably fastest to have an hardcoded fast path
for RAM and ROM, and then process the rest like now. Lookup tables might help
as well (as long as all the data including them fits in cache, of course).

~~~
FullyFunctional
This is very interesting. Do you have a good example to study how this should
be done?

------
toprerules
So what are your general thoughts on Rust? Would you use it again? How does it
compare to using C or C++? Did you enjoy working in it? What was
disappointing/motivating about the language?

------
maeln
If you like the subject of emulator and Rust, I suggest you take a look at
Ferris youtube :
[https://www.youtube.com/channel/UC4mpLlHn0FOekNg05yCnkzQ/vid...](https://www.youtube.com/channel/UC4mpLlHn0FOekNg05yCnkzQ/videos)

He stream is development of a Nintendo 64 and Virtual Boy emulator in Rust.

~~~
codetrotter
And here is a video [1] and free to read online book [2] that someone else
made about implementing a Game Boy emulator in Rust.

[1]: RustFest Rome 2018 - Ryan Levick: Oh Boy! Creating a Game Boy Emulator in
Rust - [https://youtu.be/B7seNuQncvU](https://youtu.be/B7seNuQncvU)

[2]: DMG-01: How to Emulate a Game Boy -
[http://blog.ryanlevick.com/DMG-01/](http://blog.ryanlevick.com/DMG-01/)

------
tbrock
If I recall correctly, P.C. Walton, one of the creators of rust, used an NES
emulator during rust’s initial programming to measure performance of the
language’s output binaries.

~~~
eloisius
Indeed. I peeped at his code[1] frequently while I worked on a GameBoy
Original emulator[2] that I've as of yes failed to finish.

[1]:
[https://github.com/pcwalton/sprocketnes](https://github.com/pcwalton/sprocketnes)

[2]: [https://github.com/zacstewart/gbrs](https://github.com/zacstewart/gbrs)

------
sidcool
Question, what made it easier to build this in Rust than other languages? I
know the advantages of Rust, but what exactly was the experience in this
context?

~~~
FartyMcFarter
It seems that unsafe references were used, so the advantages of Rust appear to
have been somewhat relinquished in this case.

~~~
bionicbits
I am not sure you can _not_ use `unsafe` in an embedded type of project.
`unsafe` isn't necessarily bad, but when used at least makes it very clear
where extra attention should be paid.

~~~
hopler
Emulating a system of a few 2Mhz with a 4x3Ghz chipset ought to be doable
using managed memory.

~~~
flohofwoe
Nitpicking a bit, but there's not a lot of useful things that could be
multithreaded in an 8-bit emulator, so it would be more like 1x3Ghz ;)

In those old 8- and 16-bit machines all components usually ran completely
synchronous for each clock tick, for instance if the video emulation runs out
of sync with the CPU emulation for a tick or two, a write from the CPU to a
video hardware register might not make it in time and you get garbage video
output.

Modern asynchronous systems may need less (relative) host system performance
for emulation, because the timing requirements between the different hardware
components are much more relaxed.

~~~
jrockway
The original hardware was multithreaded... in the sense that there were
several separate processing units.

~~~
flohofwoe
Yes, but as far as I can glance from the NES spec, those still run from the
same clock (with different dividers, but they're still locked to each other).

I haven't written an emulator for the NES yet, but on machines like the
Amstrad CPC or C64, the CPU can reprogram video hardware registers (like color
palette entries) at any time, for instance in the middle of a scanline. When
this is off by a tick, you get the color palette change a couple of pixels
late or early.

If CPU and video chip emulation would run on different threads, they would
need to sync with each other a few million times per second, that doesn't
sound like a good idea. If synchronization is only needed once per scanline,
or even per frame that's an option of course. Often this is good enough to run
some games which didn't go too close to the metal (again this is only from my
experience with home computer emulators, haven't done NES stuff yet).

Parallelizing through SIMD might be an option though (one can pack a lot of 4-
or 8-bit counters into a 512-bit register).

------
AntiRush
There's also sprocketnes, which was originally written ~6 years ago by
pcwalton, who works on rust/servo at Mozilla.

It's been kept up to date and it's interesting to see how the project evolved
along with the language:

[https://github.com/pcwalton/sprocketnes](https://github.com/pcwalton/sprocketnes)

~~~
prudhvis
While the project itself is a great resource, the dependencies marked as "*"
are unfortunate for a project that is 6 years in the making.

------
Bulbasaur2015
What was the most challenging part for you in building this emulator?

~~~
cabaalis
I want to build one myself as well, but I don't want to use existing code as a
guide, rather challenge myself to implement from raw specs.

The problem is I haven't really found any yet that helped me (admittedly, I
have so far devoted just one Saturday morning to it.) Are there any specs
about the hardware that you used, or that anyone else can share?

~~~
kouteiheika
> Are there any specs about the hardware that you used, or that anyone else
> can share?

Everything you need is either on
[http://wiki.nesdev.com](http://wiki.nesdev.com) or on their forums. I know
because that's what I did with my NES emulator - I explicitly didn't want to
look at any source code and instead wanted to implement everything only based
on the docs.

Granted, what's on the NESdev isn't always easy to grok, up to the point of
being _really_ confusing sometimes. What I've found really helped is gradually
setting up a test suite based on various test ROMs I could find, which even
allows you to implement some parts of the emulator TDD-style once you get the
basics up and running.

------
robert-boehnke
Nice, I wrote an NES emulator in Swift once:
[https://github.com/robb/NES](https://github.com/robb/NES)

------
souprock
This is the second emulator I'm seen in rust, the other being an ARM emulator
done as a personal project by somebody who was an intern here. I do emulators,
but in C. (BTW, hiring)

For an expert performance-sensitive C programmer who pulls out all the
platform-specific tricks to make stuff go fast, how do you think rust would
be? The default safety is appealing, but I have lots of concerns.

Bitfield access looks painful. In C, I can set things up to make read/write
named access to bitfields easy. Granted, the header to set this up is
complicated: make an union of anonymous structs, each with a bitfield and the
needed padding. With that though, I can use bitfields just like ordinary
struct members. I can pluck fields out of opcodes for emulation, or I can fill
them in for a JIT.

Sometimes in C, one might use gcc's computed goto extension. (getting a void
pointer from a unary && operator applied to a label, then derefing it at the
goto) It doesn't seem like rust has this, even in unsafe code. Actually, there
isn't even a "goto" keyword... which I find to be a worrisome sign that might
indicate stubbornly academic language design.

The bounds checking on arrays is kind of the whole point of rust, but if that
can't get out of the way for speed then I'd have to make everything unsafe. If
I do that, then rust is pointless. Has anybody checked the assembly to see if
the compiler is good at eliminating the checks? For example, if I use a 5-bit
bitfield to index into a 32-entry table, do the checks get optimized out?

What if I want aliasing? With gcc I can mark things __may_alias__, and with
Visual Studio I don't even need that. I had a case where I needed to lay
structs over each other like shingles, in groups of 4 with internal padding,
so that the same struct member of each of the 4 structs would be adjacent in
memory. This was needed so that vector intrinsics could be used.

Speaking of that, are there vector intrinsics? What if I specifically want MMX
opcodes in one place (for MMX emulation) and SSE opcodes in another place?

Can I get a switch without a default, such that the compiler doesn't try to
generate code for a case that isn't listed? I know this will seem like a
horrible idea to many programmers, but sometimes performance matters. When the
language can't keep up, I have to drop down into assembly, and that sucks
more.

~~~
sanxiyn
Rust has no strict aliasing, so all Rust types are may_alias.

~~~
souprock
I thought it was the other way, all being effectively restrict, with the
hazard removed by the borrow checker.

In any case, I want to be able to have both behaviors.

------
RealityVoid
Tiny off-topic, I was at hackaday Belgrade last year and a very good friend of
mine ported a NES emulator on the badge. It was amazing for me seeing him how
he solved the issues so quickly and how productive he was. And it was fun
fiddling with an emulator on a uncommon embedded target.

------
tyingq
You mentioned fighting the borrow checker a bit around the emulated CPU
address space. Did this come up anywhere else, like frame buffers?

Edit: Ugh. Never mind...not being familiar with NES I didn't recognize what a
PPU was :)

~~~
MichaelBurge
The borrow checker is mainly a problem when the same data needs to be modified
in two different places. Only the PPU writes to a frame buffer(that it owns),
so it wasn't a problem there.

The game controllers were another case: They are mapped in CPU address space,
so the CPU needs a permanent mutable reference to them. But the top-level also
needs to update them with inputs read from my Xbox 360 controller.

------
muterad_murilax
[http://www.michaelburge.us/assets/articles/20190318-nes-
emul...](http://www.michaelburge.us/assets/articles/20190318-nes-
emulator/bankswitch.gif)

Off-topic: Were the "bumping wheel" tiles ever used in the actual game? If so,
in which level?

~~~
muterad_murilax
[https://tcrf.net/Super_Mario_Bros._3/Unused_graphics#World_8](https://tcrf.net/Super_Mario_Bros._3/Unused_graphics#World_8)

Apparently not.

------
domlebo70
Awesome. Love the blog post. I learnt Haskell using the NES
([http://github.com/dbousamra/hnes](http://github.com/dbousamra/hnes)).
Definitely recommend doing your own.

------
crooked-v
This reminds me that I want to make an NES emulator in JS using Redux, in part
to play around with the entire machine state being serializable/deserializable
and time-travelable.

------
juliangoldsmith
Does anyone know if OP would be able to work around the single-ownership issue
using Cell/RefCell?

I still haven't fully grasped how to use those, but they seem like they'd fit
there.

~~~
bfrydl
OP mentions that the use of unsafe code means it's single-threaded, so yes
it's likely that RefCell could be used instead of unsafe code.

~~~
steveklabnik
I'm frankly a bit worried about that statement; I believe it means that
they're producing UB. Even with unsafe, Rust code is expected to uphold the
safety rules.

I haven't read the code though.

~~~
bfrydl
OP is using unsafe to store a mut pointer to a boxed value so that it can be
mutated in two places. So the statement is just referring to data races.

~~~
steveklabnik
Sounds like UB to me! I’ll have to take a look. It really depends on the
details.

(I tried to run miri on it, but I'm on Windows, and this is unix-only.)

EDIT: even with porting some of the code, I can’t get the current repository
to build, there’s a borrow checker error...)

~~~
MichaelBurge
Sorry about that. If it helps, I'm using Rust 1.32 to build:

    
    
        $ rustc --version
        rustc 1.32.0 (9fda7c223 2019-01-16)
        $ cargo --version
        cargo 1.32.0 (8610973aa 2019-01-02)
    
        $ cargo build --release
        $ cargo run --release --bin nes-emulator
    `

~~~
steveklabnik
It’s chill! So, two things:

1\. I can send you a patch later, but if you use std::io::stdin() and stdout,
instead of opening the file descriptors, it should compile on Windows.

2\. The borrow error was coming from miri; it detected some UB! Compiling with
cargo does work. It’s not the bit I expected; I can file a bug with the error
once I’m back at my laptop.

Anyway, this is a cool project, and I’m glad you’re doing it; don’t let my
worries bug you. I’ve been trying to learn more unsafe stuff and so the UB
bits are just on my mind more lately.

EDIT: actually it's just from building on nightly rust, not stable. No miri
needed. I wonder why stable is okay with this... anyway I filed some github
issues, please feel free to ignore me or close them, but if you wanna keep
talking about this, let's do that there :)

------
sidcool
What is something that I can do to learn Rust better? I have been through the
book, but that's it. What would help me really learn Rust?

~~~
wyldfire
"How do you get to Carnegie Hall?"

Seriously, the best thing to do for virtually all professional or hobby work
is to get more experience with it. Choose a project with small enough scope to
give you encouraging results to keep that feedback loop going.

~~~
sidcool
That is a great analogy thanks.

~~~
asdkhadsj
From personal experience, invest in good _(for you)_ resources to learn the
language. I had two attempts, and it wasn't until I took "learning" seriously
and purchased a book did Rust stick. My first attempt that has worked for all
other languages, mostly by just reading docs and programming, was a massive
headache.

That was years ago though, perhaps it's better now. All I know is after
reading Programming Rust, I picked Rust up and it's been amazing. I love the
language.

------
dfee
How cool would it be if computers shipped with a small FPGA to get better
performance for stuff like this?

------
leonardmh
I’ll upvote anything using rust in a cool way

