Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25338bc0ee | |||
| af35cfc712 |
12
src/bus.rs
12
src/bus.rs
@ -14,6 +14,7 @@ pub struct Bus<M: Mapper> {
|
|||||||
ppu: Ppu<M>,
|
ppu: Ppu<M>,
|
||||||
apu: Ram<0x1F>,//TODO
|
apu: Ram<0x1F>,//TODO
|
||||||
cartridge: Rc<RefCell<M>>,
|
cartridge: Rc<RefCell<M>>,
|
||||||
|
tick_counter: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Mapper> fmt::Debug for Bus<M> {
|
impl<M: Mapper> fmt::Debug for Bus<M> {
|
||||||
@ -119,11 +120,20 @@ impl<M: Mapper> Bus<M> {
|
|||||||
ppu: Ppu::new(mapper.clone(), screen, pattern_table),
|
ppu: Ppu::new(mapper.clone(), screen, pattern_table),
|
||||||
apu: Ram::<0x1F>::new(),
|
apu: Ram::<0x1F>::new(),
|
||||||
cartridge: mapper,
|
cartridge: mapper,
|
||||||
|
tick_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
self.ppu.tick();
|
|
||||||
|
if self.tick_counter >= 3 {
|
||||||
|
self.tick_counter = 0;
|
||||||
|
self.ppu.tick();
|
||||||
|
} else {
|
||||||
|
self.tick_counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
src/cpu.rs
18
src/cpu.rs
@ -151,6 +151,7 @@ pub struct Cpu<M: Mapper> {
|
|||||||
s: u8,
|
s: u8,
|
||||||
p: StatusReg,
|
p: StatusReg,
|
||||||
bus: Bus<M>,
|
bus: Bus<M>,
|
||||||
|
tick_counter: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Mapper> fmt::Debug for Cpu<M> {
|
impl<M: Mapper> fmt::Debug for Cpu<M> {
|
||||||
@ -189,16 +190,23 @@ impl<M: Mapper> Cpu<M> {
|
|||||||
s: 0xFD,
|
s: 0xFD,
|
||||||
p: StatusReg::from_bits(0x34).unwrap(),
|
p: StatusReg::from_bits(0x34).unwrap(),
|
||||||
bus,
|
bus,
|
||||||
|
tick_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
|
|
||||||
self.execute();
|
if self.tick_counter >= 11 {
|
||||||
self.bus.tick();
|
self.tick_counter = 0;
|
||||||
}
|
self.execute();
|
||||||
|
} else {
|
||||||
|
self.tick_counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
fn execute(&mut self) {
|
self.bus.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&mut self) {
|
||||||
|
|
||||||
let opcode = self.read_pc_addr();
|
let opcode = self.read_pc_addr();
|
||||||
// if self.pc-1 > 0x36D0 && self.pc-1 < 0x36D8 {
|
// if self.pc-1 > 0x36D0 && self.pc-1 < 0x36D8 {
|
||||||
|
|||||||
34
src/main.rs
34
src/main.rs
@ -25,6 +25,12 @@ use canvas::{
|
|||||||
};
|
};
|
||||||
use utils::DisplayBuffer;
|
use utils::DisplayBuffer;
|
||||||
|
|
||||||
|
///Nanoseconds for 12 periods of the master clock (21.478 MHz). The frequency is divided by 12 since
|
||||||
|
///this is the CPU clock. All faster peripherals (i.e. the PPU, will be run whithout timing
|
||||||
|
///considerations whithing these 12 periods. This shouldn't affect the system's behavior while
|
||||||
|
///improving the clock's precision
|
||||||
|
const MASTER_CLOCK_PERIOD_NS: u128 = 559;
|
||||||
|
|
||||||
struct EmulatorState {
|
struct EmulatorState {
|
||||||
pub screen: Rc<RefCell<DisplayBuffer>>,
|
pub screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
pub screen_sprite: TextureSprite,
|
pub screen_sprite: TextureSprite,
|
||||||
@ -95,15 +101,31 @@ impl Application<EmulatorState> for NesEmulator {
|
|||||||
|
|
||||||
fn tick(state: &mut EmulatorState, canvas: &mut Canvas) -> Result<(), &'static str> {
|
fn tick(state: &mut EmulatorState, canvas: &mut Canvas) -> Result<(), &'static str> {
|
||||||
|
|
||||||
let frame_time = state.start.elapsed().as_secs_f32();
|
//save frame start for frame time computation
|
||||||
state.start = Instant::now();
|
let frame_start = state.start;
|
||||||
canvas.clear();
|
|
||||||
state.cpu.tick();
|
|
||||||
|
|
||||||
if state.screen.borrow_mut().is_ready {
|
//tick the system for 1 frame
|
||||||
state.screen.borrow_mut().is_ready = false;
|
while state.screen.borrow_mut().is_ready == false {
|
||||||
|
|
||||||
|
let tick_period = state.start.elapsed().as_nanos();
|
||||||
|
//this should never fail as id frame_period doesn't fit in a u64, then the substraction
|
||||||
|
//saturated and the resulting 0 now fits
|
||||||
|
let delta = MASTER_CLOCK_PERIOD_NS.saturating_sub(tick_period) as u64;
|
||||||
|
std::thread::sleep(std::time::Duration::from_nanos(delta));
|
||||||
|
state.start = Instant::now();
|
||||||
|
|
||||||
|
for _ in 0..12 {
|
||||||
|
//TODO the cpu is ticked 12 times since MASTER_CLOCK_PERIOD_NS corresponds to 12
|
||||||
|
//clock periods. Further division is done in the function
|
||||||
|
state.cpu.tick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
state.screen.borrow_mut().is_ready = false;
|
||||||
|
|
||||||
|
//draw frame
|
||||||
|
let frame_time = frame_start.elapsed().as_secs_f32();
|
||||||
|
|
||||||
|
canvas.clear();
|
||||||
canvas.draw(&mut state.screen_sprite);
|
canvas.draw(&mut state.screen_sprite);
|
||||||
canvas.draw(&mut state.pattern_sprite);
|
canvas.draw(&mut state.pattern_sprite);
|
||||||
|
|
||||||
|
|||||||
@ -2,173 +2,259 @@
|
|||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
peripherals::{Peripheral, Ram, Mapper},
|
peripherals::{Peripheral, Ram, Mapper},
|
||||||
utils::DisplayBuffer,
|
utils::DisplayBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct PpuCtrl: u8 {
|
struct PpuCtrl: u8 {
|
||||||
const Nl = 0x1 << 0;
|
const NL = 0x1 << 0;
|
||||||
const Nh = 0x1 << 1;
|
const NH = 0x1 << 1;
|
||||||
const I = 0x1 << 2;
|
const I = 0x1 << 2;
|
||||||
const S = 0x1 << 3;
|
const S = 0x1 << 3;
|
||||||
const B = 0x1 << 4;
|
const B = 0x1 << 4;
|
||||||
const H = 0x1 << 5;
|
const H = 0x1 << 5;
|
||||||
const P = 0x1 << 6;
|
const P = 0x1 << 6;
|
||||||
const V = 0x1 << 7;
|
const V = 0x1 << 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct PpuMask: u8 {
|
struct PpuMask: u8 {
|
||||||
const Gr = 0x1 << 0;
|
const GR = 0x1 << 0;
|
||||||
const m = 0x1 << 1;
|
const M_ = 0x1 << 1;
|
||||||
const M = 0x1 << 2;
|
const M = 0x1 << 2;
|
||||||
const b = 0x1 << 3;
|
const B_ = 0x1 << 3;
|
||||||
const s = 0x1 << 4;
|
const S_ = 0x1 << 4;
|
||||||
const R = 0x1 << 5;
|
const R = 0x1 << 5;
|
||||||
const G = 0x1 << 6;
|
const G = 0x1 << 6;
|
||||||
const B = 0x1 << 7;
|
const B = 0x1 << 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct PpuStatus: u8 {
|
struct PpuStatus: u8 {
|
||||||
const O = 0x1 << 5;
|
const O = 0x1 << 5;
|
||||||
const S = 0x1 << 6;
|
const S = 0x1 << 6;
|
||||||
const V = 0x1 << 7;
|
const V = 0x1 << 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Ppu<M: Mapper> {
|
pub struct Ppu<M: Mapper> {
|
||||||
vbus: Rc<RefCell<M>>,
|
//peripherals
|
||||||
ppu_ctrl: PpuCtrl,
|
vbus: Rc<RefCell<M>>,
|
||||||
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
|
//public registers
|
||||||
screen: Rc<RefCell<DisplayBuffer>>,
|
ppu_ctrl: PpuCtrl,
|
||||||
pattern_table: Rc<RefCell<DisplayBuffer>>,
|
ppu_mask: PpuMask,
|
||||||
|
ppu_status: PpuStatus,
|
||||||
|
|
||||||
|
scroll: u8,
|
||||||
|
addr: u8,
|
||||||
|
data: u8,
|
||||||
|
|
||||||
|
//OAM
|
||||||
|
oam_addr: u8,
|
||||||
|
oam_data: u8,
|
||||||
|
oam_dma: u8,
|
||||||
|
oam: Ram<0x100>,
|
||||||
|
|
||||||
|
blanking: bool,
|
||||||
|
line: u16,
|
||||||
|
dot: u16,
|
||||||
|
nmi: bool,
|
||||||
|
odd: bool,
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
|
pattern_table: Rc<RefCell<DisplayBuffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Mapper> Peripheral for Ppu<M> {
|
impl<M: Mapper> Peripheral for Ppu<M> {
|
||||||
|
|
||||||
fn read_addr(&self, _addr: u16) -> u8 {
|
fn read_addr(&self, _addr: u16) -> u8 {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_addr(&mut self, _addr: u16, _data: u8) {
|
fn write_addr(&mut self, _addr: u16, _data: u8) {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Mapper> Ppu<M> {
|
impl<M: Mapper> Ppu<M> {
|
||||||
|
|
||||||
pub fn new(mapper: Rc<RefCell<M>>,
|
pub fn new(mapper: Rc<RefCell<M>>,
|
||||||
screen: Rc<RefCell<DisplayBuffer>>,
|
screen: Rc<RefCell<DisplayBuffer>>,
|
||||||
pattern_table: Rc<RefCell<DisplayBuffer>>)
|
pattern_table: Rc<RefCell<DisplayBuffer>>)
|
||||||
-> Self {
|
-> Self {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
vbus: mapper,
|
vbus: mapper,
|
||||||
ppu_ctrl: PpuCtrl::from_bits(0).unwrap(),
|
ppu_ctrl: PpuCtrl::from_bits(0).unwrap(),
|
||||||
ppu_mask: PpuMask::from_bits(0).unwrap(),
|
ppu_mask: PpuMask::from_bits(0).unwrap(),
|
||||||
ppu_status: PpuStatus::from_bits(0).unwrap(),
|
ppu_status: PpuStatus::from_bits(0).unwrap(),
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
addr: 0,
|
addr: 0,
|
||||||
data: 0,
|
data: 0,
|
||||||
oam_addr: 0,
|
oam_addr: 0,
|
||||||
oam_data: 0,
|
oam_data: 0,
|
||||||
oam_dma: 0,
|
oam_dma: 0,
|
||||||
oam: Ram::<0x100>::new(),
|
oam: Ram::<0x100>::new(),
|
||||||
blanking: false,
|
blanking: false,
|
||||||
line: 0,
|
line: 0,
|
||||||
dot: 0,
|
dot: 0,
|
||||||
nmi: false,
|
nmi: false,
|
||||||
|
odd: false,
|
||||||
|
|
||||||
screen,
|
screen,
|
||||||
pattern_table,
|
pattern_table,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_nmi(&mut self) -> bool {
|
pub fn poll_nmi(&mut self) -> bool {
|
||||||
let ret = self.nmi;
|
let ret = self.nmi;
|
||||||
if self.nmi {
|
if self.nmi {
|
||||||
self.nmi = false;
|
self.nmi = false;
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
use crate::utils::Pixel;
|
use crate::utils::Pixel;
|
||||||
|
|
||||||
self.screen.borrow_mut()
|
// self.screen.borrow_mut()
|
||||||
.set_pixel(
|
// .set_pixel(
|
||||||
self.dot.into(),
|
// self.dot.into(),
|
||||||
self.line.into(),
|
// self.line.into(),
|
||||||
Pixel::rgba(self.data.into(), 0, 0, 255));
|
// Pixel::rgba(self.data.into(), 0, 0, 255));
|
||||||
|
//
|
||||||
|
self.execute();
|
||||||
|
}
|
||||||
|
|
||||||
self.execute();
|
fn execute(&mut self) {
|
||||||
}
|
|
||||||
|
|
||||||
fn execute(&mut self) {
|
match self.line {
|
||||||
|
0..=239 => self.visible_line(),
|
||||||
|
241 => {
|
||||||
|
//start of vertical blank
|
||||||
|
self.post_render_line();
|
||||||
|
if self.dot == 1 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
261 => {
|
||||||
|
//end of vertical blank
|
||||||
|
self.pre_render_line();
|
||||||
|
}
|
||||||
|
240..=260 => (), //vertical blank
|
||||||
|
_ => panic!("{}: Invalid line number", self.line),
|
||||||
|
}
|
||||||
|
|
||||||
match self.line {
|
//increment current dot and line
|
||||||
0..=239 => self.render_frame(),
|
self.dot += 1;
|
||||||
241 => {
|
if self.dot > 340 {
|
||||||
if self.dot == 1 {
|
self.dot = 0;
|
||||||
self.ppu_status.set(PpuStatus::V, true);
|
self.line += 1;
|
||||||
}
|
if self.line > 261 {
|
||||||
}
|
self.line = 0;
|
||||||
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
|
fn visible_line(&mut self) {
|
||||||
self.line = 0;
|
match self.dot {
|
||||||
}
|
0 => (), //nothing done on first cycle
|
||||||
240..=260 => (), //vertical blank
|
|
||||||
_ => panic!("{}: Invalid line number", self.line),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dot = (self.dot + 1) % 256;
|
1..=256 | 321..=336 => {
|
||||||
if self.dot == 0 {
|
//background
|
||||||
self.line = (self.line + 1) % 240;
|
match self.dot % 8 {
|
||||||
if self.line == 0 {
|
0 => (), //render inc hori v, BG msbit
|
||||||
self.screen.borrow_mut().is_ready = true;
|
2 => (), //load NT
|
||||||
self.data = (self.data + 1) % 255;
|
4 => (), //load AT
|
||||||
}
|
6 => (), //BG lsbit
|
||||||
}
|
_ => (), //idle clock cycle
|
||||||
}
|
}
|
||||||
|
//OAM
|
||||||
|
match self.dot {
|
||||||
|
1..=64 => (), //secondary OAM clear
|
||||||
|
65..=256 => (), //sprite evalutation for next scanline
|
||||||
|
_ => (), //idle clock cycle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
257 => (), // hori(v) = hori(t)
|
||||||
|
|
||||||
fn render_frame(&mut self) {
|
//sprites
|
||||||
|
258..=320 => {
|
||||||
|
//sprites
|
||||||
|
match self.dot % 8 {
|
||||||
|
0 => (), //sprite msbit
|
||||||
|
2 => (), //garbage NT
|
||||||
|
4 => (), //garbageAT
|
||||||
|
6 => (), //sprite lsbit
|
||||||
|
_ => (), //idle clock cycle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => (), //idle or unused clock cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_render_line(&mut self) {
|
||||||
|
match self.dot {
|
||||||
|
0 => (),
|
||||||
|
|
||||||
|
//background
|
||||||
|
1 => {
|
||||||
|
//clear vblank, sprite 0, overflow
|
||||||
|
self.ppu_status.set(PpuStatus::V | PpuStatus::S | PpuStatus::O, false);
|
||||||
|
},
|
||||||
|
2..=248 | 321..=336 => match self.dot % 8 {
|
||||||
|
0 => (), //render inc hori v, BG msbit
|
||||||
|
2 => (), //load NT
|
||||||
|
4 => (), //load AT
|
||||||
|
6 => (), //BG lsbit
|
||||||
|
_ => (), //idle clock cycle
|
||||||
|
}
|
||||||
|
257 => (), // hori(v) = hori(t)
|
||||||
|
|
||||||
|
258..=320 => {
|
||||||
|
//sprites
|
||||||
|
match self.dot % 8 {
|
||||||
|
0 => (), //sprite msbit
|
||||||
|
2 => (), //garbage NT
|
||||||
|
4 => (), //garbageAT
|
||||||
|
6 => (), //sprite lsbit
|
||||||
|
_ => (), //idle clock cycle
|
||||||
|
}
|
||||||
|
//miscelaneous
|
||||||
|
match self.dot {
|
||||||
|
280..=304 => (), //vert(v) = vert(t)
|
||||||
|
_ => (), //idle clock cycle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
339 => if self.odd { self.dot = 340 }, //skip on clock cycle every 2 frames
|
||||||
|
|
||||||
|
_ => (), //idle or unused clock cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_render_line(&mut self) {
|
||||||
|
match self.dot {
|
||||||
|
1 => {
|
||||||
|
self.ppu_status.set(PpuStatus::V, true);
|
||||||
|
self.screen.borrow_mut().is_ready = true; //screen can be displayed
|
||||||
|
},
|
||||||
|
_ => (), //idle or unused clock cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self.dot {
|
|
||||||
0 => (),
|
|
||||||
33..=40 => (),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user