Hey there! I've a got a bit of time-off recently and wrote a mini ebook on how to create NES emulator in Rust.
Check it out:
A mini book on writing NES emulator using rust lang
Home Page: https://bugzmanov.github.io/nes_ebook/index.html
ld: library not found for -lSDL2
Open terminal and install:
Ubuntu: sudo apt-get install libsdl2-dev
or Fedora: sudo dnf install SDL2-devel
or Arch: sudo pacman -S sdl2
For more info: https://github.com/Rust-SDL2/rust-sdl2
At the end of 3.1, maybe you could give a hint to use wrapping_add()
. Because rust doesn't allow overflow in debug mode:
fn inx(&mut self) {
self.rx = self.rx.wrapping_add(1) as u8;
}
instead of:
fn inx(&mut self) {
self.rx += 1;
}
to solve:
#[test]
fn test_inx_overflow() {
let mut cpu = CPU::new();
cpu.register_x = 0xff;
cpu.interpret(vec![0xe8, 0xe8, 0x00]);
assert_eq!(cpu.register_x, 1)
}
Hi, first of all thank you for this tutorial! I've really been struggling trying to wrap my head around writing an NES emulator, and this has been a great resource.
I'm thinking this might be a bug or just me misunderstanding something about rust, but this line looks like it is doing a wrapping_add
of the u8
pointer, which is not how the emulator from https://skilldrick.github.io/easy6502/ is working. Given this code:
LDX #$00
LDA #$05
STA $ff
LDA #$07
STA $0100
LDA #$08
STA $00
LDY #$0d
STY $0705
LDY #$0b
STY $0805
LDA ($ff,X)
(you can plug it in here)
If the u8
pointer were wrapped, I'd expect it to load A from $0805
, but it is loading from $0705
, since we end up with A having a value of $0b
.
I think this code should instead be:
let lo = self.mem_read(ptr as u16);
let hi = self.mem_read((ptr as u16).wrapping_add(1));
Hi, I was just reading through your NES EMU book, but I was confused pretty quickly.
In Chapter 3.1, you start by implementing LDA as the very first instruction.
At the end of the instruction implementation, you adjust the status flags:
if result == 0 {
self.status = self.status | 0b0000_0001;
} else {
self.status = self.status & 0b1111_1110;
}
This seems to be modifying the Carry flag. However, the 6502 instruction reference you linked clearly indicates that it is the Zero and Negative flag that should be updated.
. | flag | effect |
---|---|---|
C | Carry Flag | Not affected |
Z | Zero Flag | Set if A = 0 |
I | Interrupt Disable | Not affected |
D | Decimal Mode Flag | Not affected |
B | Break Command | Not affected |
V | Overflow Flag | Not affected |
N | Negative Flag | Set if bit 7 of A is set |
Considering that the status register layout is
NVxx DIZC
should the code not be as follows?
if result == 0 {
self.status = self.status | 0b0000_0010;
} else {
self.status = self.status & 0b1111_1101;
}
The link on this page doesn't point to reference manual.
To compile SDL2, seems like we need more configutration on windows. for me , I use windows 10,
at command line I will type
it means I need download msvc version of sdl2 at here http://www.libsdl.org/release/SDL2-2.0.14-win32-x64.zip
unzip the file
copy ./SDL2-2.014/lib/x64/SDL2.lib .. SDL2main.lib .. SDL2test.lib to
C:\Users\Your_user_name\ .rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\
copy ./SDL2-2.014/lib/x64/SDL2.dll to your project folder next to your cargo.toml.
as ref pls read https://github.com/Rust-SDL2/rust-sdl2 readme
seems like we need comment 3 line to pass the test.
pub fn reset(&mut self){
//self.register_a = 0;
//self.register_x = 0;
self.register_y = 0;
self.stack_pointer = STACK_RESET;
self.status = CpuFlags::from_bits_truncate(0b100100);
//self.memory = [0; 0xFFFF];
self.program_counter = self.mem_read_u16(0xFFFC);
}
In cpu.rs
fn branch(&mut self, condition: bool) {
if condition {
let jump: i8 = self.mem_read(self.program_counter) as i8;
let jump_addr = self
.program_counter
.wrapping_add(1)
.wrapping_add(jump as u16);
If I change jump to u16 I get an extremely incorrect result immediately resulting in a BRK command run. The negative flag isn't set and it is immediately changed back to u16 later in the wrapping add. Any idea about this? Is there something in the documentation I am missing where this should a signed and then unsigned add?
Hi... looking at the code https://github.com/bugzmanov/nes_ebook/blob/master/code/ch8/src/cpu.rs#L31 I wonder why the stack is reset at 0x10FD, shouldn't it start at 0x10FF according to the spec?
I just got out of a 2 hour debugging session. After days of implementing processor instructions, I just wanted to see something run, but to my dismay, the SDL window opened and quickly closed, without showing anything. To discard any possible problems in my OS or libraries or whatever, I ran the version in this repo, and it worked.
After scratching my head for a while, I realized that the load function quietly changed to load the contents of the program to another memory location. As you can see, in the previous chapter it's different.
Please, state this clearly in the book. This program expects its code to be in a different memory location.
Emulating PPU memory access: Address and Data registers
Let's try to emulate two the most complex registers: Address (0x2006) and Data(0x2007)
I believe it should be:
"Let's try to emulate two of the most complex registers: Address (0x2006) and Data(0x2007)
Why use wrapping_ add() method?
For example:
pos is 200,self.register_x is 111
The result of adding them is 311,but using wrapping_add(),the result of the add method is 000000010011011, i.e. 55
The addressing range of NES is 0-65535, so why? Isn't that an error?
thx
Isn't upper and lower flipped in the render function?
pub fn render(ppu: &NesPPU, frame: &mut Frame) {
let bank = ppu.ctrl.bknd_pattern_addr();
for i in 0..0x03c0 { // just for now, lets use the first nametable
let tile = ppu.vram[i] as u16;
let tile_x = i % 32;
let tile_y = i / 32;
let tile = &ppu.chr_rom[(bank + tile * 16) as usize..=(bank + tile * 16 + 15) as usize];
for y in 0..=7 {
let mut upper = tile[y]; //<--- Shouldn't this be lower?
let mut lower = tile[y + 8]; //<--- Shouldn't this be upper?
I did the same in my emulator, and had wrong colors because bits from upper and lower end up in the wrong positions.
Hello, your tutorial is amazing first of all. I was just wondering why we still mirror before accessing nametables as in here:
pub fn mirror_vram_addr(&self, addr: u16) -> u16 { let mirrored_vram = addr & 0b10111111111111; // mirror down 0x3000-0x3eff to 0x2000 - 0x2eff
when we never reach that range as in:
0x2000..=0x2fff => { let result = self.internal_data_buf; self.internal_data_buf = self.vram[self.mirror_vram_addr(addr) as usize]; result } 0x3000..=0x3eff => unimplemented!("addr {} shouldn't be used in reallity", addr),
or is this an error where we need to change the range from 0x2fff
to 0x3eff
and get rid of the second match
? Thank you
in the function reset()
before register_a
successfully transfer the data to register_x
, the reset function overwrites the register_a
with 0
causing the register_x
to be 0
also instead of the intended data.
Hello thanks for your work by the way I have learned a lot in learning rust language because of this project, I really appreciate your work :).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.