Compare commits

..

1 Commits
dev ... master

Author SHA1 Message Date
d4af38b50f Add LICENSE 2022-02-15 21:33:23 +00:00
17 changed files with 24 additions and 3960 deletions

View File

@ -1,5 +0,0 @@
[*.rs]
indent_style = tab
indent_size = 4
max_line_length = 100

14
.gitignore vendored
View File

@ -1,15 +1 @@
/target
/cgmath
*.log
*.bin
*.bin.*
*.bkp
*.perf
*.svg
*.lock
perf.*
tags

1859
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,8 @@
[package]
name = "nes_emulator"
version = "0.1.0"
edition = "2021"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.14"
chrono = "0.4.19"
fern = "0.6.0"
bitflags = "1.3.2"
rusttype = "0.9.2"
canvas = "0.1.0"
[patch.crates-io]
canvas = { git = "https://git.steins7.ovh/Steins7/canvas.git", branch = "dev" }
cgmath = { path = "cgmath" }

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Steins7
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1 +0,0 @@
<mxfile host="Electron" modified="2022-05-26T11:39:44.986Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/17.4.2 Chrome/96.0.4664.174 Electron/16.2.2 Safari/537.36" etag="qDAsjD8odGcVAzsI0HnM" version="17.4.2" type="device" pages="2"><diagram id="ump8jJu03wSbJk_0SnHp" name="Page-1">7Vtbc5s6EP41zLQP6XA3fvQl6ZlpfE4mTk9OHxUj2zQYuUJO7P76I0AyCGGCbTBkmqdYyyLE7rff7kpEMUar7VcM1ssJcqGv6Kq7VYyxouuaqTr0TyTZJRKnpyWCBfZcppQKpt5vyIQqk248F4aCIkHIJ95aFM5QEMAZEWQAY/Qqqs2RLz51DRZQEkxnwJelj55Llkyq2f30wl/QWyzZox29l1xYAa7M3iRcAhe9ZkTGtWKMMEIk+bXajqAfGY/bJbnv5sDV/cIwDEiVG/xH23nx/168wmA3/DkFt89DdBXdEC+O7PgbQ5cagA0RJku0QAHwr1PpEKNN4MJoWpWOUp1bhNZUqFHhT0jIjnkTbAiioiVZ+ewq3Hrkv+j2L7rFhj/YbNHv8TY72PFBQPAue1c0/pG9mN4Xj/iNIQGYDCIsUEGAAshlN57v73Uwet57mPpmKJuXWTxEGzyDJTblMAV4AUmZ7RO9yN6ZBzDnfYVoBelbUAUMfUC8FxGQgOF6sddjt9IXBbuMwhp5AQkzM99FAqrAQtRh8GTxaedAlNM2+2Xq9EfyeD7KvEcqinF5DEaND4y2hVHtPYLUUS8PUsNMHvkC/A0zAnBdXIjcW/BEs6SANuB7i4D+nlFPQkwFLxATj6ahAbuw8lw3ATYMvd/gKZ4vQgUzHZ3cGirWuBAVpWEVPQluBb+xZMqeIuQrwensLgp207QFk3OAHQeL1JNcBc3nIQWo6L56HPaR+VpjFesdkorety5OKprEKaO77xJsw1dv5YPYuQddJwX5wWA2RKtonEtf0+pX57JlpvDdm7PIP0LwHh2pkhUG5VaIIc5CkLJcZUY82lZmzlambCtNL7DVXli7rXSzXVY7idRO57SZD8LQm+VoTTuLxPSqpVEnSMzKYdApZzHNKFNvqH6328SklkVkmnXfAqUAyUza7UimrQzS2uv38+jJ+YBCW1BwOsFXdq7q0qxywsrrm0avVL+nl+o31PzZUonS9ebPOVDrnND8aVb3mz210Bet8M5ptHMU6xwui0TiuUyh1BHisUVi4E3MQeLJ6ZtW/wJE0pOIxAUEdJhIeGTVQSSqY4k9tt51XtEld9013qCb+a6zcoeuNtV18sO6jB2u6JDaYvRwf6votk+XMHyiCdFekNgKicT1XvKicA0CLmNzTAbTb/wyXV5WIyMumKva9NOHwcP3aYMPGA8eBg1OPxiP74+cPodPCjxSTCQ+nJPzaCTKimGSINP8M0I+wml1PKf5KSeqWsYcGzyaGDuGHDr9gshpbmtLk5zxZ27X1FmXWBXrkto3cM6CgiVR6OP9YEIln/Tnzw2nFN1+O6XsNzezgWE3FRiOZI1/L2YNqYXUTNkadoE1+o3lV/kgQMgRGVvYvzbRByXDiNOvGI3T7KPGTL6/WpZWAkAXHXF6eHrSqnVBgBDsPW0IjIO6QwtbA5/y7NHLqZx96yjjm82/h3YRDsaWWN8XpF+tqHK1GjsusT7y75nZlu8wv7N0a8j5tuOdNsfq+Z32lfrF4CmF5zil6512r9VIVU7YwdOUru/gVY7d2k+R6tnCY0FcdQsvt+XX0Bae/LnCLGw3ywifGmnVwHtOmql06HUh5Na++XxeIS9vGI6okbAXLe+yXU1Rj2cUfayi9xpra+RvJu8wWmCwosL7fybHWaTc4NV74VzzV/GTnuZ6P/lskUIGzAi30Q1rjJ1jG+NmDLbvjNvrluVDlOZ6QOL5UV+a1I6d6Er12DW6qlt2DIut81lJ/k/lD25U91FU405xIQ8YjaFa3hLLBr76Ca2JFxUI7bCAlj8urbiD2BgLcOdk7DUB6zWFaSskqb3NkkUnDyeYhw7TfypLqtr0X/OM6/8B</diagram><diagram id="rIACK3iZetm4nWkpG2b9" name="Page-2">7Vtdb9owFP01PDLhhPDxSGjXVqMaK5P28VK5iSFunThzTIH++tnBIaQJlHYQV6slJPDJdXx9z73OsYGGPQyXFwzGwTX1EWlYLX/ZsM8algXarZ54k8hqjfS6YA3MGPaVUQ5M8BNSYEuhc+yjpGDIKSUcx0XQo1GEPF7AIGN0UTSbUlIcNYYzVAImHiRl9Af2eaBQ0OnnFy4RngVq6J7VXV8IYWasZpIE0KeLLcg+b9hDRilffwqXQ0Rk8LK4rPt93nF14xhDET+kQ3QJXfjEJqM/36+a3gTcXE0HTTWLR0jmasLKWb7KIoB8ERDVpIwHdEYjSM5z1GV0HvlIDtMSrdxmRGksQCDAe8T5SrEL55wKKOAhUVfXY8qBds5NQQmdMw/tmVCWI5DNEN9j52wYEKmLaIg4W4l+DBHI8WPRD6hyaLaxy8MsPqhIvyLqoBT1YTwvBT5Z4JDASEZ4SiOecSBj7AWY+CO4onM5x4RD7yFruQFl+EnYwyy64jLjKvZWR94NEzKkhDIBRDQdIO80kTdTwzCUiG7jjAbwDLqGy4LhCCY8c5ASAuME36Uuy46hYARHLuWchspIRQExjpb7iS8TlXWwVXWp5QV0VXuxVaxthQVbdQpa7RORa1WUlCuXHozk2jOQ2bmKUYluEQSessXoA3pGTwVjkOBZJJoETWU3GUUslq2BgrksPTeJoYej2Si1OWvnyI2KhISo6Dsl6dIUYN9HUVrSHHJ4t0m4mOKIp5FyXPESAR22PjkNRzg+FG2Qt8VLmjM+pJGYC8QpkUikxgLJ9DiM9d1lU04FRb1I7YOYz+yOTrxtiNdCvGNpJr5tiNdCfLdXH/FfuccnvY6HV63J7Zef4/hxcdvs/m/qySmrp8qJWzWpp8rBgfNRww5a/xj3tOuAMbjaMlCVnt95LIFcYzmgqLEs29nm8EV7u+8843ztQZ4Bm6kclBRX4cL1+uyx/Xuw/HbRXNxH8a9m5wPkROXEdyyR7zslHOe4KbEvglsp4c6TUlaYXdbLuyzbeesuK9ueHV1zVdW70VzH0Fw7TkY07LKqn4GGeC3Ea99lVYltQ/zpia9zl1XpXs8Qr4X4zRGptrW+fJR6A0Oj4N6g4JyuTgVXSa45Lj1CWb/mdOa9KDhzXKqH+DoVXKV7Ved2hvjTE6/9nNxs1vUQr1/BZed+W9SPzU8djiLhbFu7hAPmMOZUhb3rq573IuJAeW9mqK+Feu0yDpidmybqtQs5YPZumqivVcrt/qK5QP3AKLmjKLl2v04lV0mueZofoa5f8/sMDTqu0j3zLNdDfJ0qrtI98yTXQ3ydGq7SPXMKq4f4Uyo40cz/SLb+/Vz+dzz7/C8=</diagram></mxfile>

Binary file not shown.

View File

@ -1,165 +0,0 @@
use std::{
fmt,
rc::Rc,
cell::RefCell,
};
use crate::{
utils::DisplayBuffer,
peripherals::{Peripheral, Ram, Mapper, Ppu},
};
pub struct Bus<M: Mapper> {
ram: Ram<0x800>,
ppu: Ppu<M>,
apu: Ram<0x1F>,//TODO
cartridge: Rc<RefCell<M>>,
}
impl<M: Mapper> fmt::Debug for Bus<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bus")
.field("ram", &self.ram)
.finish_non_exhaustive()
}
}
impl<M: Mapper> Peripheral for Bus<M> {
fn read_addr(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x1FFF => self.ram.read_addr(addr % 0x0800), //RAM is mirrored 3 times
0x2000..=0x3FFF => self.ppu.read_addr(addr % 0x8), //PPU is mirrored every 8 bytes
0x4000..=0x401F => self.apu.read_addr(addr),
0x4020..=0xFFFF => self.cartridge.borrow().read_addr(addr),
}
}
fn write_addr(&mut self, addr: u16, data: u8) {
match addr {
0x0000..=0x1FFF => self.ram.write_addr(addr % 0x0800, data),
0x2000..=0x3FFF => self.ppu.write_addr(addr % 0x8, data),
0x4000..=0x401F => self.apu.write_addr(addr, data),
0x4020..=0xFFFF => self.cartridge.borrow_mut().write_addr(addr, data),
};
}
}
impl<M: Mapper> Bus<M> {
pub fn new(mapper: Rc<RefCell<M>>,
screen: Rc<RefCell<DisplayBuffer>>,
pattern_table: Rc<RefCell<DisplayBuffer>>)
-> Self {
let mut ram = Ram::<0x800>::new();
//TODO temporary for instruction tests
ram.buffer[0x01] = 0xE9; //sbc
ram.buffer[0x02] = (-3 as i8) as u8;
ram.buffer[0x03] = 0x09;
ram.buffer[0x04] = 0x00;
ram.buffer[0x05] = 0xA9; //lda
ram.buffer[0x06] = 0x01;
ram.buffer[0x07] = 0x4C; //jmp
ram.buffer[0x08] = 0x00;
ram.buffer[0x09] = 0x00;
ram.buffer[0x0A] = 0x2A; //fn rol
ram.buffer[0x0B] = 0x90; //bcc
ram.buffer[0x0C] = (-3 as i8) as u8;
ram.buffer[0x0D] = 0x60; //rts
ram.buffer[0x0E] = 0xFD;
ram.buffer[0x0F] = 0x01;
ram.buffer[0x10] = 0x21;
ram.buffer[0x11] = 0x9F;
ram.buffer[0x12] = 0x31;
ram.buffer[0x13] = 0xB0;
// boundary
ram.buffer[0x14] = 0x3D;
ram.buffer[0x15] = 0xFF;
ram.buffer[0x16] = 0x02;
ram.buffer[0x17] = 0x39;
ram.buffer[0x18] = 0xFE;
ram.buffer[0x19] = 0x02;
ram.buffer[0x1A] = 0x31;
ram.buffer[0x1B] = 0xB2;
// ptr to val
ram.buffer[0xA0] = 0xFF;
ram.buffer[0xA1] = 0x01;
// ptr to val as table
ram.buffer[0xB0] = 0xFD;
ram.buffer[0xB1] = 0x01;
ram.buffer[0xB2] = 0xFE; //boundary
ram.buffer[0xB3] = 0x02;
// zero page val
ram.buffer[0xFF] = 0x01;
// val
ram.buffer[0x01FF] = 0x01;
ram.buffer[0x02FF] = 0xFF; //boundary
ram.buffer[0x0300] = 0x03;
// ptr to test
ram.buffer[0x030F] = 0xFF;
ram.buffer[0x0310] = 0x03;
// jmp test
ram.buffer[0x0400] = 0xE6;
Bus {
ram,
ppu: Ppu::new(mapper.clone(), screen, pattern_table),
apu: Ram::<0x1F>::new(),
cartridge: mapper,
}
}
pub fn tick(&mut self) {
self.ppu.tick();
}
}
/// special Bus only used for testing the emulator
pub struct FileBus {
mem: Ram<0x10000>,
}
impl fmt::Debug for FileBus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bus")
.field("mem", &self.mem)
.finish()
}
}
impl Peripheral for FileBus {
fn read_addr(&self, addr: u16) -> u8 {
self.mem.read_addr(addr) //RAM is mirrored 3 times
}
fn write_addr(&mut self, addr: u16, data: u8) {
self.mem.write_addr(addr % 0x0800, data)
}
}
impl FileBus {
#[allow(dead_code)]
pub fn new() -> Self {
FileBus {
mem: Ram::from_file("6502_functional_test.bin"),
}
}
}

1149
src/cpu.rs

File diff suppressed because it is too large Load Diff

View File

@ -1,261 +1,3 @@
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::{
rc::Rc,
cell::RefCell,
time::Instant,
fmt::Write,
};
mod cpu;
use cpu::Cpu;
mod bus;
mod peripherals;
use peripherals::mapper::Nrom128;
mod utils;
use canvas::{
Application,
Canvas,
sprite::{Sprite, TextureSprite, TextSprite, Center},
utils::{Size, Color, Position},
};
use utils::DisplayBuffer;
struct EmulatorState {
pub screen: Rc<RefCell<DisplayBuffer>>,
pub screen_sprite: TextureSprite,
#[allow(dead_code)]
pub pattern_table: Rc<RefCell<DisplayBuffer>>,
pub pattern_sprite: TextureSprite,
pub fps_text: TextSprite,
pub debug_text: TextSprite,
pub cpu: Cpu<Nrom128>,
pub start: Instant,
fn main() {
println!("Hello, world!");
}
struct NesEmulator {}
impl Application<EmulatorState> for NesEmulator {
fn init(canvas: &mut Canvas) -> Result<EmulatorState, &'static str> {
let start = Instant::now();
let screen_texture = canvas
.create_texture(Size {w: 256, h: 240}, Some(Color::RED.into()))
.unwrap();
let mut screen_sprite = canvas.create_texture_sprite(Size {w: 256 * 2, h: 240 * 2});
screen_sprite.set_texture(screen_texture.clone(), None, 0.5);
screen_sprite.set_center(Center::BotLeft);
screen_sprite.set_position(Position {x: 100, y: 200});
let screen = Rc::new(RefCell::new(DisplayBuffer::from_texture(screen_texture)));
let pattern_texture = canvas
.create_texture(Size {w: 256, h: 128}, Some(Color::BLUE.into()))
.unwrap();
let mut pattern_sprite = canvas.create_texture_sprite(Size {w: 256, h: 128});
pattern_sprite.set_texture(pattern_texture.clone(), None, 1.0);
pattern_sprite.set_center(Center::BotLeft);
pattern_sprite.set_position(Position {x: 100, y: 50});
let pattern_table = Rc::new(RefCell::new(DisplayBuffer::from_texture(pattern_texture)));
let mut fps_text = canvas.create_text_sprite("00", Size {w: 30, h: 20}, 20.0);
fps_text.set_center(Center::TopLeft);
fps_text.set_position(Position {x:0, y:720});
let mut debug_text = canvas.create_text_sprite("00", Size {w: 300, h: 600}, 20.0);
debug_text.set_center(Center::BotLeft);
debug_text.set_position(Position {x:700, y:50});
let mapper = Rc::new(RefCell::new(Nrom128::new(false)));
let cpu = Cpu::new(mapper, screen.clone(), pattern_table.clone());
canvas.clear();
canvas.update();
Ok(EmulatorState {
screen,
screen_sprite,
pattern_table,
pattern_sprite,
fps_text,
debug_text,
cpu,
start,
})
}
fn tick(state: &mut EmulatorState, canvas: &mut Canvas) -> Result<(), &'static str> {
let frame_time = state.start.elapsed().as_secs_f32();
state.start = Instant::now();
canvas.clear();
state.cpu.tick();
if state.screen.borrow_mut().is_ready {
state.screen.borrow_mut().is_ready = false;
}
canvas.draw(&mut state.screen_sprite);
canvas.draw(&mut state.pattern_sprite);
let mut fps_str = String::new();
write!(fps_str, "{} fps", 1.0/frame_time).unwrap();
state.fps_text.set_text(&fps_str);
canvas.draw(&mut state.fps_text);
let mut debug_str = String::new();
write!(debug_str, "{:#?}", state.cpu).unwrap();
state.debug_text.set_text(&debug_str);
canvas.draw(&mut state.debug_text);
canvas.update();
Ok(())
}
}
fn main() -> Result<(), &'static str> {
setup_logger()
.map_err(|_| "Failed to setup logger")?;
canvas::run_canvas("NES emulator", Size {w: 1280, h: 720}, NesEmulator {});
}
// use utils::{Pixel, PixelBuffer, FontRenderer, DisplayBuffer};
// use glutin::{
// dpi::LogicalSize,
// event_loop::EventLoop,
// };
// use mini_gl_fb::config;
// use std::{
// fmt::Write,
// rc::Rc,
// cell::RefCell,
// time::Instant,
// };
// use peripherals::mapper::Nrom128;
// use std::ops::Deref;
//
// // setup
// setup_logger().map_err(|_| "Failed to setup logger")?;
//
// let mut event_loop = EventLoop::new();
// let config = config! {
// window_title: "Test".to_string(),
// window_size: LogicalSize::new(1280.0, 720.0),
// invert_y: false,
// };
// let mut fb = mini_gl_fb::get_fancy(config, event_loop.deref());
// let mut buffer = PixelBuffer::new(1280, 720);
// let mut renderer = FontRenderer::new(20.0, [0u8, 0, 0]);
//
// // first image
// let mapper = Rc::new(RefCell::new(Nrom128::new(false)));
// let mut screen = Rc::new(RefCell::new(DisplayBuffer::new(256, 240)));
// let pattern_table = Rc::new(RefCell::new(DisplayBuffer::new(256, 128)));
// let mut cpu = Cpu::new(mapper, screen.clone(), pattern_table.clone());
// let mut debug_str = String::new();
// //write!(debug_str, "{:#?}", cpu).unwrap();
// buffer.fill([0, 0], [1280, 720], Pixel::rgba(255, 255, 255, 255));
// //renderer.draw(&mut buffer, &debug_str, [20, 20], [760, 560]);
//
// let vec: Vec<[u8; 4]> = (&buffer).into();
// fb.update_buffer(&vec);
// let mut update = false;
// let mut refresh = true;
// let mut start = Instant::now();
//
// // event loop
// fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
// use glutin::event::VirtualKeyCode;
//
// // wait for input before updating
//
// // close if escape is pressed
// if input.key_pressed(VirtualKeyCode::Escape) {
// return false
// }
//
// // execute next cpu instruction (step mode)
// if input.key_pressed(VirtualKeyCode::S) {
// cpu.tick();
// refresh = true;
// }
//
// if input.key_pressed(VirtualKeyCode::R) {
// update = true;
// }
//
// if input.key_pressed(VirtualKeyCode::C) {
// update = false;
// refresh = true;
// }
//
// if update {
// cpu.tick();
//
// if screen.borrow_mut().is_ready {
// screen.borrow_mut().is_ready = false;
// refresh = true;
// }
//
// input.wait = false;
// } else {
// input.wait = true;
// }
//
// if refresh {
// refresh = false;
//
// let frame_time = start.elapsed().as_secs_f32();
// start = Instant::now();
//
// let mut debug_str = String::new();
// buffer.fill([0, 0], [1280, 720], Pixel::rgba(255, 255, 255, 255));
// write!(debug_str, "{} fps", 1.0/frame_time);
// renderer.draw(&mut buffer, &debug_str, [1, 1], [20, 20]);
// debug_str = String::new();
// write!(debug_str, "{:#?}", cpu).unwrap();
// renderer.draw(&mut buffer, &debug_str, [600, 20], [760, 560]);
// buffer.embbed([20, 20], &screen.borrow().buffer, 2);
//
// let vec: Vec<[u8; 4]> = (&buffer).into();
// fb.update_buffer(&vec);
// }
//
// true
// });
//
// Ok(())
//}
fn setup_logger() -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%H:%M:%S]"),
record.target(),
//record.file(),
record.level(),
message
))
})
.level(log::LevelFilter::Error)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?;
Ok(())
}

View File

@ -1,17 +0,0 @@
mod ram;
mod ppu;
pub mod mapper;
pub use ram::Ram;
pub use ppu::Ppu;
pub use mapper::Mapper;
//--------------------------------------------------------------------------------------------------
pub trait Peripheral {
fn read_addr(&self, addr: u16) -> u8;
fn write_addr(&mut self, addr: u16, data: u8);
}

View File

@ -1,17 +0,0 @@
use super::Peripheral;
mod nrom;
pub use nrom::Nrom128;
//--Mapper struct-----------------------------------------------------------------------------------
///A trait used as API for the different implementations of mappers in cartridges
///
///A Mapper is a special `Peripheral` that can also be read by the `Ppu`. The exact memory layout
///is defined by the implementation
pub trait Mapper: Peripheral {
///read the specified 16 bits address using the ppu bus
fn ppu_read_addr(&self, addr: u16) -> u8;
}

View File

@ -1,71 +0,0 @@
use super::{
Mapper,
super::{
Peripheral,
Ram,
},
};
pub struct Nrom128 {
prg_rom: Ram<0x4000>, //16KiB
prg_ram: Ram<0x2000>, //8KiB
chr_rom: Ram<0x2000>, //8KiB
v_ram: Ram<0x0800>, //2KiB
vertical: bool,
}
impl Peripheral for Nrom128 {
fn read_addr(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF => self.prg_ram.read_addr(addr - 0x6000),
0x8000..=0xFFFF => self.prg_rom.read_addr((addr - 0x8000) % 0x4000),
//mirrored every 16KiB
_ => panic!("{}: Invalid address", addr),
}
}
fn write_addr(&mut self, addr: u16, data: u8) {
match addr {
0x6000..=0x7FFF => self.prg_ram.write_addr(addr - 0x6000, data),
_ => (), //writes are ignored for rom or invald addresses
}
}
}
impl Mapper for Nrom128 {
fn ppu_read_addr(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x1FFF => self.chr_rom.read_addr(addr),
0x2000..=0x3EFF => {
match self.vertical {
false => match (addr - 0x2000) % 0x0FFF {
0x0000..=0x07FF => self.v_ram.read_addr(addr % 0x0400),
0x0800..=0x0FFF => self.v_ram.read_addr((addr % 0x0400) + 0x0800),
_ => panic!("Unexpected behaviour"),
},
true => self.v_ram.read_addr(addr % 0x0800),
}},
_ => panic!("{}: Invalid ppu address", addr),
}
}
}
impl Nrom128 {
pub fn new(vertical: bool) -> Self {
Self {
prg_rom: Ram::<0x4000>::new(),
prg_ram: Ram::<0x2000>::new(),
chr_rom: Ram::<0x2000>::new(),
v_ram: Ram::<0x0800>::new(),
vertical,
}
}
}

View File

@ -1,174 +0,0 @@
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::{
rc::Rc,
cell::RefCell,
};
use bitflags::bitflags;
use crate::{
peripherals::{Peripheral, Ram, Mapper},
utils::DisplayBuffer,
};
//--------------------------------------------------------------------------------------------------
bitflags! {
struct PpuCtrl: u8 {
const Nl = 0x1 << 0;
const Nh = 0x1 << 1;
const I = 0x1 << 2;
const S = 0x1 << 3;
const B = 0x1 << 4;
const H = 0x1 << 5;
const P = 0x1 << 6;
const V = 0x1 << 7;
}
}
bitflags! {
struct PpuMask: u8 {
const Gr = 0x1 << 0;
const m = 0x1 << 1;
const M = 0x1 << 2;
const b = 0x1 << 3;
const s = 0x1 << 4;
const R = 0x1 << 5;
const G = 0x1 << 6;
const B = 0x1 << 7;
}
}
bitflags! {
struct PpuStatus: u8 {
const O = 0x1 << 5;
const S = 0x1 << 6;
const V = 0x1 << 7;
}
}
#[allow(dead_code)]
pub struct Ppu<M: Mapper> {
vbus: Rc<RefCell<M>>,
ppu_ctrl: PpuCtrl,
ppu_mask: PpuMask,
ppu_status: PpuStatus,
scroll: u8,
addr: u8,
data: u8,
oam_addr: u8,
oam_data: u8,
oam_dma: u8,
oam: Ram<0x100>,
blanking: bool,
line: u16,
dot: u16,
nmi: bool,
// outputs
screen: Rc<RefCell<DisplayBuffer>>,
pattern_table: Rc<RefCell<DisplayBuffer>>,
}
impl<M: Mapper> Peripheral for Ppu<M> {
fn read_addr(&self, _addr: u16) -> u8 {
unimplemented!();
}
fn write_addr(&mut self, _addr: u16, _data: u8) {
unimplemented!();
}
}
impl<M: Mapper> Ppu<M> {
pub fn new(mapper: Rc<RefCell<M>>,
screen: Rc<RefCell<DisplayBuffer>>,
pattern_table: Rc<RefCell<DisplayBuffer>>)
-> Self {
Self {
vbus: mapper,
ppu_ctrl: PpuCtrl::from_bits(0).unwrap(),
ppu_mask: PpuMask::from_bits(0).unwrap(),
ppu_status: PpuStatus::from_bits(0).unwrap(),
scroll: 0,
addr: 0,
data: 0,
oam_addr: 0,
oam_data: 0,
oam_dma: 0,
oam: Ram::<0x100>::new(),
blanking: false,
line: 0,
dot: 0,
nmi: false,
screen,
pattern_table,
}
}
pub fn poll_nmi(&mut self) -> bool {
let ret = self.nmi;
if self.nmi {
self.nmi = false;
}
ret
}
pub fn tick(&mut self) {
use crate::utils::Pixel;
self.screen.borrow_mut()
.set_pixel(
self.dot.into(),
self.line.into(),
Pixel::rgba(self.data.into(), 0, 0, 255));
self.execute();
}
fn execute(&mut self) {
match self.line {
0..=239 => self.render_frame(),
241 => {
if self.dot == 1 {
self.ppu_status.set(PpuStatus::V, true);
}
}
261 => {
if self.dot == 1 {
self.ppu_status.set(PpuStatus::V | PpuStatus::S | PpuStatus::O, false);
}
self.render_frame();
//last line, got back to first line
self.line = 0;
}
240..=260 => (), //vertical blank
_ => panic!("{}: Invalid line number", self.line),
}
self.dot = (self.dot + 1) % 256;
if self.dot == 0 {
self.line = (self.line + 1) % 240;
if self.line == 0 {
self.screen.borrow_mut().is_ready = true;
self.data = (self.data + 1) % 255;
}
}
}
fn render_frame(&mut self) {
match self.dot {
0 => (),
33..=40 => (),
_ => (),
}
}
}

View File

@ -1,59 +0,0 @@
use std::fmt;
use super::Peripheral;
//--------------------------------------------------------------------------------------------------
pub struct Ram<const SIZE: usize> {
pub buffer: [u8; SIZE],
}
impl<const SIZE: usize> fmt::Debug for Ram<SIZE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in 0x100..=0x1FF {
if i%0x10 == 0 {
f.write_fmt(format_args!("\n {:0>4X}", i))?;
}
f.write_fmt(format_args!(" {:0>2X}", self.buffer[i]))?
}
Ok(())
}
}
impl<const SIZE: usize> Peripheral for Ram<SIZE> {
fn read_addr(&self, addr: u16) -> u8 {
self.buffer[addr as usize]
}
fn write_addr(&mut self, addr: u16, data: u8) {
self.buffer[addr as usize] = data;
}
}
impl<const SIZE: usize> Ram<SIZE> {
pub fn new() -> Self {
Ram {
buffer: [0u8; SIZE],
}
}
pub fn from_file(file: &str) -> Self {
use std::{
fs::File,
io::Read,
};
let mut f = File::open(file).unwrap();
let mut buffer = [0u8; SIZE];
f.read_exact(&mut buffer).unwrap();
Ram {
buffer,
}
}
}

View File

@ -1,124 +0,0 @@
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
mod display_buffer;
pub use display_buffer::DisplayBuffer;
//--------------------------------------------------------------------------------------------------
/* Pixel struct */
#[derive(Clone, Copy)]
pub struct Pixel {
r: u8,
g: u8,
b: u8,
a: u8,
}
impl Pixel {
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Pixel {r, g, b, a, }
}
}
impl std::ops::Add for Pixel {
type Output = Self;
fn add(self, other: Self) -> Self {
let alpha = other.a as f32 / 255.0;
let inv_alpha = 1.0 - alpha;
Pixel {
r: ((self.r as f32)*inv_alpha + (other.r as f32)*alpha) as u8,
g: ((self.g as f32)*inv_alpha + (other.g as f32)*alpha) as u8,
b: ((self.b as f32)*inv_alpha + (other.b as f32)*alpha) as u8,
a: 255,
}
}
}
impl std::ops::AddAssign for Pixel {
fn add_assign(&mut self, other: Self) {
let alpha = other.a as f32 / 255.0;
let inv_alpha = 1.0 - alpha;
self.r = ((self.r as f32)*inv_alpha + (other.r as f32)*alpha) as u8;
self.g = ((self.g as f32)*inv_alpha + (other.g as f32)*alpha) as u8;
self.b = ((self.b as f32)*inv_alpha + (other.b as f32)*alpha) as u8;
}
}
impl From<&Pixel> for [u8; 4] {
fn from(pixel: &Pixel) -> Self {
[pixel.r, pixel.g, pixel.b, pixel.a]
}
}
impl From<[u8; 4]> for Pixel {
fn from(array: [u8; 4]) -> Self {
Pixel::rgba(array[0], array[1], array[2], array[3])
}
}
//--------------------------------------------------------------------------------------------------
/* PixelBuffer struct */
pub struct PixelBuffer {
buffer: Vec<Pixel>,
w: usize,
h: usize,
}
impl PixelBuffer {
pub fn new(w: usize, h: usize) -> Self {
PixelBuffer {
buffer: vec![Pixel::rgba(255u8, 255u8, 255u8, 255); h * w],
w,
h,
}
}
pub fn put_pixel(&mut self, x: usize, y: usize, pixel: Pixel) {
self.buffer[x + y*self.w] += pixel
}
pub fn get_pixel(&self, x: usize, y: usize) -> Pixel {
self.buffer[x + y*self.w]
}
pub fn fill(&mut self, origin: [usize; 2], size: [usize; 2], pixel: Pixel) {
for x in origin[0]..(origin[0] + size[0]) {
for y in origin[1]..(origin[1] + size[1]) {
self.buffer[x + y*self.w] = pixel;
}
}
}
pub fn embbed(&mut self, origin: [usize; 2], buffer: &Self, scale: usize) {
let [w, h] = buffer.get_dimensions();
for y in 0..(h*scale) {
for x in 0..(w*scale) {
self.put_pixel(x + origin[0], y + origin[1], buffer.get_pixel(x/scale, y/scale));
}
}
}
pub fn get_dimensions(&self) -> [usize; 2] {
[self.w, self.h]
}
}
impl From<&PixelBuffer> for Vec<[u8; 4]> {
fn from(buffer: &PixelBuffer) -> Self {
buffer.buffer.iter()
.map(|pixel| pixel.into())
.collect()
}
}

View File

@ -1,33 +0,0 @@
use super::Pixel;
use canvas::texture::TextureHandle;
//--------------------------------------------------------------------------------------------------
pub struct DisplayBuffer {
pub is_ready: bool,
texture: TextureHandle,
}
impl DisplayBuffer {
pub fn from_texture(texture: TextureHandle) -> Self {
Self {
texture,
is_ready: false,
}
}
pub fn set_pixel(&mut self, x: usize, y: usize, pixel: Pixel) {
self.texture.set_pixel(
canvas::utils::Position {x: x.try_into().unwrap(), y: y.try_into().unwrap()},
canvas::utils::Pixel {
r: pixel.r,
g: pixel.g,
b: pixel.b,
a: pixel.a,
});
}
}