Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
# Nes Emulator
# NEXie(A basic NES Emulator!)
This project was made in Rust and emulates the Nintendo Entertainment System including the CPU(MOS Technology 6502) and PPU.

At the moment, the MOS 6502 CPU and most of the PPU is complete. Background and Sprite data can be loaded and displayed, however no input nor scrolling is implemented(yet).
The CPU has been tested with `nestest` by kevtris and compared against the Nintendulator log(Look in trace.rs to see how the trace is created).

At the moment, the Ricoh 2A03 CPU(based off the MOS 6502 CPU) and most of the PPU is complete. Input and vertical scrolling is complete in this version(tested with Pac-Man and Ice Climbers). Horizontal scrolling and APU(Audio Processing Unit) still needs to be implemented.
## Running the Emulator
To run, look inside `main.rs` and enter the path of the `.nes` file to run. Then, run `cargo run` to run the application.
## Pictures
To run, look inside `main.rs` and enter the path of the `.nes` file to run. Then, run `cargo run` to run the emulator!

## Input
| Controller Input | Keyboard Input |
|------------------|-----------------|
| Left | Left Arrow Key |
| Right | Right Arrow Key |
| Up | Up Arrow Key |
| Down | Down Arrow Key |
| A | z |
| B | x |
| Start | Enter |
| Select | Space |
## Pictures/Demos(Tested with Ubuntu 24.04 in WSL)
<p align="center">
<img src="pictures/pacman.gif" alt="pacman" width="45%" />
<img src="pictures/ic.gif" alt="ice climbers" width="45%" />
</p>
Binary file added pictures/ic.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added pictures/pacman.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 11 additions & 13 deletions src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ pub struct Bus<'call> {
pub ppu: PPU,
pub cycles: usize, // Contains total amount of cpu cycles
gameloop_callback: Box<dyn FnMut(&PPU, &mut Controller) + 'call>, // Box, pointer to heap ddata is managed by the box
controller1: Controller
controller1: Controller,
}

impl <'a>Bus<'a> {
pub fn new<'call, F>(rom: Rom, gameloop_callback: F) -> Bus<'call> where F: FnMut(&PPU, &mut Controller) + 'call,{
impl<'a> Bus<'a> {
pub fn new<'call, F>(rom: Rom, gameloop_callback: F) -> Bus<'call>
where
F: FnMut(&PPU, &mut Controller) + 'call,
{
let ppu = PPU::new(rom.chr_rom, rom.screen_mirroring);
Bus {
cpu_vram: [0; 2048],
prg_rom: rom.prg_rom,
ppu: ppu,
cycles: 7, // Starting with 7 clock cycles
gameloop_callback: Box::from(gameloop_callback),
controller1: Controller::new()
controller1: Controller::new(),
}
}
fn read_prg_rom(&self, mut addr: u16) -> u8 {
Expand All @@ -49,7 +52,6 @@ impl <'a>Bus<'a> {
pub fn poll_nmi_status(&mut self) -> Option<u8> {
self.ppu.nmi_interrupt.take()
}

}

const RAM: u16 = 0x0000;
Expand All @@ -74,9 +76,7 @@ impl Mem for Bus<'_> {
}
0x2002 => self.ppu.read_status(),
0x2004 => self.ppu.read_oam_data(),
0x2007 => {
self.ppu.read_data()
},
0x2007 => self.ppu.read_data(),
0x2008..=PPU_REGISTERS_MIRRORS_END => {
// Mirroring down to 0x2000 to 0x2007
let mirror_down_addr = addr & 0x2007;
Expand All @@ -87,11 +87,9 @@ impl Mem for Bus<'_> {
0
}

0x4016 =>{
self.controller1.read()
}
0x4016 => self.controller1.read(),

0x4017 =>{
0x4017 => {
// Controller 2
0
}
Expand Down Expand Up @@ -144,7 +142,7 @@ impl Mem for Bus<'_> {
}

0x4000..=0x4013 | 0x4015 => {
//ignore APU
//ignore APU
}

0x4016 => {
Expand Down
26 changes: 11 additions & 15 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use bitflags::bitflags;

bitflags!{
bitflags! {
#[derive(Copy, Clone, Debug)]
pub struct ControllerButton: u8{
const RIGHT = 0b1000_0000;
Expand All @@ -17,45 +17,41 @@ bitflags!{
}
}

pub struct Controller{
pub struct Controller {
strobe: bool, // Determines if we are writing input or leaving the read
button_index: u8,
button_status: ControllerButton
button_status: ControllerButton,
}

impl Controller{
impl Controller {
pub fn new() -> Self {
Controller{
Controller {
strobe: false,
button_index: 0,
button_status: ControllerButton::from_bits_truncate(0b0000_0000),
}
}

pub fn write(&mut self, data: u8){
println!("writing controller!");
pub fn write(&mut self, data: u8) {
self.strobe = data & 1 == 1;
if self.strobe{
println!("resetting strobe!");
if self.strobe {
// Starts at the first index
self.button_index = 0
}
}

pub fn read(&mut self) -> u8{
println!("reading controller!");
if self.button_index > 7{
pub fn read(&mut self) -> u8 {
if self.button_index > 7 {
return 1; // Indicates that there isn't anything left ot read
}
let response = (self.button_status.bits() & (1 << self.button_index)) >> self.button_index;
if !self.strobe && self.button_index <= 7{
if !self.strobe && self.button_index <= 7 {
self.button_index += 1; // Increments the index for the next read
}
println!("Response is {:x}", response);
response
}

pub fn set_button_pressed_status(&mut self, button: ControllerButton, input: bool){
pub fn set_button_pressed_status(&mut self, button: ControllerButton, input: bool) {
println!("Set status to {:?}", button);
self.button_status.set(button, input);
}
Expand Down
47 changes: 20 additions & 27 deletions src/cpu.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{bus::Bus};
use crate::bus::Bus;

use log::trace;

use bitflags::bitflags;
use core::panic;
use log::{debug};
use std::{fmt};
use log::debug;
use std::fmt;

type Byte = u8;

Expand Down Expand Up @@ -198,10 +198,9 @@ impl<'a> CPU<'a> {
}
}


// If nmi interrupt is encountered
fn interrupt_nmi(&mut self){

fn interrupt_nmi(&mut self) {
self.stack_push_u16(self.pc); // Push PC and Status flag on stack
let mut flag = self.flags.clone();
flag.set(CpuFlags::BREAK, false);
Expand All @@ -223,12 +222,12 @@ impl<'a> CPU<'a> {
F: FnMut(&mut CPU),
{
loop {
if let Some(_nmi) = self.bus.poll_nmi_status(){
if let Some(_nmi) = self.bus.poll_nmi_status() {
println!("nmi triggered!");
self.interrupt_nmi();
}

if self.halted{
if self.halted {
println!("Got EOF signal! Exiting program...");
break;
}
Expand Down Expand Up @@ -1098,23 +1097,15 @@ impl<'a> CPU<'a> {
let addr = self.get_operand_address(&mode); // Memory location of the value to extract
self.g1_cycles(&mode, addr, aaa == 4); // Adds cycles based on addressing mode, if aaa is 4, we're dealing with STA
match aaa {
0 =>
self.ora(addr),
1 =>
self.and(addr),
2 =>
self.eor(addr),
3 =>
self.adc(addr),
4 =>
self.sta(addr),

5 =>
self.lda(addr),
6 =>
self.cmp(addr),
7 =>
self.sbc(addr),
0 => self.ora(addr),
1 => self.and(addr),
2 => self.eor(addr),
3 => self.adc(addr),
4 => self.sta(addr),

5 => self.lda(addr),
6 => self.cmp(addr),
7 => self.sbc(addr),
_ => unimplemented!("aaa"),
};
debug!("g1 the flags are {:#X}", self.flags.bits());
Expand Down Expand Up @@ -1376,12 +1367,14 @@ impl<'a> CPU<'a> {
self.pc = self.pc.wrapping_add(jump as u16);
// NOTE For cycles, we add an additional 1 to emulate for last pc at the end of run(this does not edit the current pc value)
let new_page = self.pc.wrapping_add(1) >> 8;
debug!("branch: old_page is {:2X}, new_page is {:2X}", old_page, new_page);
debug!(
"branch: old_page is {:2X}, new_page is {:2X}",
old_page, new_page
);
if old_page != new_page {
self.add_cycles(1);
}


//println!("Finished branch, pc is now on {:#x}", self.pc);
}

Expand Down
32 changes: 16 additions & 16 deletions src/frame.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
pub struct Frame {
pub data: Vec<u8>,
pub data: Vec<u8>,
}

impl Frame {
const WIDTH: usize = 256;
const HIGHT: usize = 240;
const WIDTH: usize = 256;
const HIGHT: usize = 240;

pub fn new() -> Self {
Frame {
data: vec![0; (Frame::WIDTH) * (Frame::HIGHT) * 3],
}
}
pub fn new() -> Self {
Frame {
data: vec![0; (Frame::WIDTH) * (Frame::HIGHT) * 3],
}
}

pub fn set_pixel(&mut self, x: usize, y: usize, rgb: (u8, u8, u8)) {
let base = y * 3 * Frame::WIDTH + x * 3;
if base + 2 < self.data.len() {
self.data[base] = rgb.0;
self.data[base + 1] = rgb.1;
self.data[base + 2] = rgb.2;
}
}
pub fn set_pixel(&mut self, x: usize, y: usize, rgb: (u8, u8, u8)) {
let base = y * 3 * Frame::WIDTH + x * 3;
if base + 2 < self.data.len() {
self.data[base] = rgb.0;
self.data[base + 1] = rgb.1;
self.data[base + 2] = rgb.2;
}
}
}
13 changes: 6 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
pub mod bus;
pub mod controller;
pub mod cpu;
pub mod frame;
pub mod op;
pub mod palette;
pub mod ppu;
pub mod ppu_reg;
pub mod render;
pub mod rom;
pub mod trace;
pub mod frame;
pub mod palette;
pub mod render;
pub mod controller;

use cpu::*;

use frame::Frame;
use ppu::PPU;
use rom::Rom;
use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum};
use trace::trace;
use frame::Frame;
use ppu::PPU;

use crate::bus::Bus;
#[macro_export]
Expand All @@ -29,7 +29,6 @@ macro_rules! print_title {
};
}


// Used with NES test, removing for now due to polishing state of ppu and controllers
// pub fn run_nestest_and_capture() -> Vec<String> {
// let sdl_context = sdl2::init().unwrap();
Expand Down
Loading
Loading