aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs3
-rw-r--r--src/main.rs8
-rw-r--r--src/memory.rs39
-rw-r--r--src/mos6502.rs41
-rw-r--r--src/ppu.rs352
5 files changed, 400 insertions, 43 deletions
diff --git a/src/lib.rs b/src/lib.rs
index fa67f49..d38ba01 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1,4 @@
+extern crate core;
+mod memory;
mod mos6502;
+mod ppu;
diff --git a/src/main.rs b/src/main.rs
index 87684a3..e429932 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,12 @@
+extern crate core;
+mod memory;
+mod ppu;
mod mos6502;
-use mos6502::disasm;
fn main() {
/*
let code = [0xa9, 0x01, 0x8d, 0x00, 0x02, 0xa9, 0x05, 0x8d, 0x01, 0x02, 0xa9, 0x08, 0x8d, 0x02, 0x02 ];
let code2 = [0xa9, 0x03, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x02 ];
- let dasm = disasm::Disassembler::new(code2.iter());
+ let dasm = mos6502::disasm::Disassembler::new(code2.iter());
for l in dasm {
println!("{}", l);
}
@@ -14,6 +16,6 @@ fn main() {
let d = 0x06;
println!("{}", disasm::parse(code2[0], &[a, b, c, d]));
*/
- let mut mem = mos6502::CPUMemory::new();
+ let mut mem = memory::CPUMemory::new();
let cpu = mos6502::CPU::new(&mut mem);
}
diff --git a/src/memory.rs b/src/memory.rs
new file mode 100644
index 0000000..386e859
--- /dev/null
+++ b/src/memory.rs
@@ -0,0 +1,39 @@
+pub trait VMem {
+ fn read(&self, addr: u16) -> u8;
+ fn write(&mut self, addr: u16, data: u8);
+}
+
+pub struct CPUMemory {
+ internal: [u8; 2048]
+}
+
+impl CPUMemory {
+ pub fn new() -> Self {
+ CPUMemory{internal: [0; 2048]}
+ }
+}
+
+impl VMem for CPUMemory {
+ fn read(&self, addr: u16) -> u8 {
+ if addr < 0x2000 {
+ self.internal[(addr & 0x07ff) as usize]
+ } else if addr < 0x4000 {
+ match addr & 0x7 {
+ _ => 0
+ }
+ } else {
+ panic!("invalid memory read access at 0x{:04x}", addr)
+ }
+ }
+ fn write(&mut self, addr: u16, data: u8) {
+ if addr < 0x2000 {
+ self.internal[(addr & 0x07ff) as usize] = data;
+ } else if addr < 0x4000 {
+ match addr & 0x7 {
+ _ => ()
+ }
+ } else {
+ panic!("invalid memory write access at 0x{:04x}", addr)
+ }
+ }
+}
diff --git a/src/mos6502.rs b/src/mos6502.rs
index 3fd6eea..7af2d33 100644
--- a/src/mos6502.rs
+++ b/src/mos6502.rs
@@ -1,4 +1,5 @@
#![allow(dead_code)]
+use memory::VMem;
macro_rules! make_optable {
($x:ident, $t: ty) => (pub const $x: [$t; 0x100] = [
/* 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf */
@@ -676,46 +677,6 @@ mod addr {
fn nil(_cpu: &mut CPU) {}
}
-pub trait VMem {
- fn read(&self, addr: u16) -> u8;
- fn write(&mut self, addr: u16, data: u8);
-}
-
-pub struct CPUMemory {
- internal: [u8; 2048]
-}
-
-impl CPUMemory {
- pub fn new() -> Self {
- CPUMemory{internal: [0; 2048]}
- }
-}
-
-impl VMem for CPUMemory {
- fn read(&self, addr: u16) -> u8 {
- if addr < 0x2000 {
- self.internal[(addr & 0x07ff) as usize]
- } else if addr < 0x4000 {
- match addr & 0x7 {
- _ => 0
- }
- } else {
- panic!("invalid memory read access at 0x{:04x}", addr)
- }
- }
- fn write(&mut self, addr: u16, data: u8) {
- if addr < 0x2000 {
- self.internal[(addr & 0x07ff) as usize] = data;
- } else if addr < 0x4000 {
- match addr & 0x7 {
- _ => ()
- }
- } else {
- panic!("invalid memory write access at 0x{:04x}", addr)
- }
- }
-}
-
enum AddrMode {
Immediate,
Accumulator,
diff --git a/src/ppu.rs b/src/ppu.rs
new file mode 100644
index 0000000..2b22863
--- /dev/null
+++ b/src/ppu.rs
@@ -0,0 +1,352 @@
+use memory::VMem;
+use core::intrinsics::transmute;
+
+pub trait Screen {
+ fn put(&mut self, x: u8, y: u8, color: u8);
+ fn render(&self);
+}
+
+#[repr(C, packed)]
+#[derive(Copy, Clone)]
+struct Sprite {
+ y: u8, /* is the (actualy y) - 1 */
+ tile: u8,
+ attr: u8,
+ x: u8
+}
+
+struct PPU<'a, 'b> {
+ /* internal srams */
+ nametable_ram: [u8; 2048],
+ palette_ram: [u8; 32],
+ scanline: u16,
+ /* registers */
+ ppuctl: u8,
+ ppumask: u8,
+ ppustatus: u8,
+
+ x: u8, /* fine x scroll */
+ v: u16, /* current vram addr */
+ t: u16, /* temporary vram addr */
+ w: bool, /* first/second write toggle */
+ vblank: bool,
+ cycle: u16, /* cycle in the current scanline */
+ /* rendering regs & latches */
+ /* background registers */
+ bg_bitmap: [u16; 2],
+ bg_palette: [u8; 2],
+ /* background latches */
+ bg_nt: u8,
+ bg_attr: u8,
+ bg_bit_low: u8,
+ bg_bit_high: u8,
+ /* sprites */
+ oam: [Sprite; 64],
+ oam2: [Sprite; 8],
+ sp_bitmap: [[u8; 2]; 8],
+ sp_cnt: [u8; 8],
+ mem: &'a mut VMem,
+ scr: &'b mut Screen
+}
+
+impl<'a, 'b> PPU<'a, 'b> {
+ #[inline(always)] fn get_spritesize(&self) -> u8 {(self.ppuctl >> 5) & 1}
+
+ #[inline(always)]
+ fn fetch_nametable_byte(&mut self) {
+ self.bg_nt = self.mem.read(0x2000 | (self.v & 0x0fff));
+ }
+
+ #[inline(always)]
+ fn fetch_attrtable_byte(&mut self) {
+ let v = self.v;
+ /* the byte representing 4x4 tiles */
+ let b = self.mem.read(0x23c0 | (v & 0x0c00) |
+ ((v >> 4) & 0x38) | ((v >> 2) & 0x07));
+ self.bg_attr = (b >> ((v & 2) | ((v & 0x40) >> 4))) & 3;
+ }
+
+ #[inline(always)]
+ fn fetch_low_bgtile_byte(&mut self) {
+ /* 0x?000 */
+ self.bg_bit_low = self.mem.read(((self.ppuctl as u16 & 0x10) << 8) |
+ /* 0x-??0 */
+ ((self.bg_nt as u16) << 4) |
+ /* 0x---? (0 - 7) */
+ (self.v >> 12) | 0x0);
+ }
+
+ #[inline(always)]
+ fn fetch_high_bgtile_byte(&mut self) {
+ /* 0x?000 */
+ self.bg_bit_high = self.mem.read(((self.ppuctl as u16 & 0x10) << 8) |
+ /* 0x-??0 */
+ ((self.bg_nt as u16) << 4) |
+ /* 0x---? (8 - f) */
+ (self.v >> 12) | 0x8);
+ }
+
+ fn load_bgtile(&mut self) {
+ /* load the tile bitmap to high 8 bits of bitmap,
+ * assume the high 8 bits are zeros */
+ assert!(self.bg_bitmap[0] >> 8 == 0 &&
+ self.bg_bitmap[1] >> 8 == 0);
+ self.bg_bitmap[0] |= (self.bg_bit_low as u16) << 8;
+ self.bg_bitmap[1] |= (self.bg_bit_high as u16) << 8;
+ self.bg_palette[0] |= (self.bg_attr & 1) * 0xff;
+ self.bg_palette[1] |= ((self.bg_attr >> 1) & 1) * 0xff;
+ }
+
+ #[inline(always)]
+ fn shift_sprites(&mut self) {
+ for (i, c) in self.sp_cnt.iter_mut().enumerate() {
+ let c0 = *c;
+ match c0 {
+ 0 => {
+ self.sp_bitmap[i][0] >>= 1;
+ self.sp_bitmap[i][1] >>= 1;
+ },
+ _ => *c = c0 - 1
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn shift_bgtile(&mut self, d: u8) {
+ self.bg_bitmap[0] >>= d;
+ self.bg_bitmap[1] >>= d;
+ self.bg_palette[0] >>= d;
+ self.bg_palette[1] >>= d;
+ }
+
+ fn wrapping_inc_cx(&mut self) {
+ match self.v & 0x001f {
+ 31 => {
+ self.v &= !0x001fu16; /* reset coarse x */
+ self.v ^= 0x0400; /* switch horizontal nametable */
+ }
+ _ => self.v += 1
+ }
+ }
+
+ fn wrapping_inc_y(&mut self) {
+ match (self.v & 0x7000) == 0x7000 {
+ false => self.v += 0x1000, /* fine y < 7 */
+ true => {
+ self.v &= !0x7000u16; /* fine y <- 0 */
+ self.v = (self.v & !0x03e0u16) |
+ (match (self.v & 0x03e0) >> 5 {
+ 29 => {self.v ^= 0x0800; 0}, /* at bottom of scanline */
+ 31 => 0, /* do not switch nt */
+ y => y + 1
+ }) << 5;
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn reset_cx(&mut self) {
+ self.v = (self.v & !0x001fu16) | (self.t & 0x001f);
+ }
+
+ #[inline(always)]
+ fn reset_y(&mut self) {
+ self.v = (self.v & !0x73e0u16) | (self.t & 0x73e0);
+ }
+
+ fn clear_sprite(&mut self) {
+ self.oam2 = [Sprite{y: 0xff, tile: 0xff, attr: 0xff, x: 0xff}; 8];
+ }
+
+ fn eval_sprite(&mut self) {
+ /* we use scanline here because s.y is the (actual y) - 1 */
+ let mut nidx = 0;
+ let mut n = 0;
+ let h = match self.get_spritesize() {
+ 0 => 8,
+ _ => 16
+ };
+ for (i, s) in self.oam.iter().enumerate() {
+ let y = s.y as u16;
+ if y <= self.scanline && self.scanline < y + h {
+ self.oam2[nidx] = *s;
+ nidx += 1;
+ if nidx == 8 {
+ n = i + 1;
+ break;
+ }
+ }
+ }
+ let mut m = 0;
+ unsafe {
+ let oam_raw = transmute::<[Sprite; 64], [[u8; 4]; 64]>(self.oam);
+ while n < 64 {
+ let y = oam_raw[n][m] as u16;
+ if y <= self.scanline && self.scanline < y + h {
+ self.ppustatus |= 1 << 5; /* set overflow */
+ } else {
+ m = (m + 1) & 3; /* emulates hardware bug */
+ }
+ n += 1;
+ }
+ }
+ }
+
+ fn reverse_byte(mut x: u8) -> u8 {
+ x = ((x & 0xaa) >> 1) | ((x & 0x55) << 1);
+ x = ((x & 0xcc) >> 2) | ((x & 0x33) << 2);
+ x = ((x & 0xf0) >> 4) | ((x & 0x0f) << 4);
+ x
+ }
+
+ fn fetch_sprite(&mut self) {
+ /* we use scanline here because s.y is the (actual y) - 1 */
+ for (i, v) in self.oam2.iter().enumerate() {
+ let vflip = (v.attr & 0x80) == 0x80;
+ let y0 = self.scanline - v.y as u16;
+ let (ptable, tidx, y) = match self.get_spritesize() {
+ 0 => {
+ let y = if vflip {7 - y0 as u8} else {y0 as u8};
+ ((self.ppuctl as u16 & 0x08) << 9, v.tile, y)
+ },
+ _ => {
+ let y = if vflip {15 - y0 as u8} else {y0 as u8};
+ ((v.tile as u16 & 1) << 12,
+ (v.tile & !1u8) | (y >> 3),
+ y & 0x7)
+ }
+ };
+ self.sp_cnt[i] = v.x;
+ let mut low = self.mem.read(ptable | ((tidx as u16) << 4) | 0x0 | y as u16);
+ let mut high = self.mem.read(ptable | ((tidx as u16) << 4) | 0x8 | y as u16);
+ if (v.attr & 0x40) == 0x40 {
+ low = PPU::reverse_byte(low);
+ high = PPU::reverse_byte(high);
+ }
+ self.sp_bitmap[i][0] = low;
+ self.sp_bitmap[i][1] = high;
+ }
+ }
+
+ fn get_bgpixel(&self) -> u8 {
+ let bg_idx = ((self.bg_bitmap[1] & 1) << 1) | (self.bg_bitmap[0] & 1);
+ match bg_idx {
+ 0x0 => 0x0, /* transparent */
+ _ => {
+ let bg_pl = ((self.bg_palette[1] & 1) << 1) | (self.bg_palette[0] & 1);
+ self.mem.read(0x3f00 | (bg_pl << 2) as u16 | bg_idx as u16)
+ }
+ }
+ }
+
+ fn get_sppixel(&self, idx: usize) -> u8 {
+ let sp_idx = ((self.sp_bitmap[idx][1] & 1) << 1) | (self.sp_bitmap[idx][0] & 1);
+ match sp_idx {
+ 0x0 => 0x0,
+ _ => {
+ let attr = self.oam2[idx].attr;
+ self.mem.read(0x3f10 | ((attr & 3) << 2) as u16 | sp_idx as u16)
+ }
+ }
+ }
+
+ fn render_pixel(&mut self) {
+ let bg = self.get_bgpixel();
+ let mut sp = 0x0;
+ let mut pri = 0x1;
+ for i in 0..8 {
+ match self.get_sppixel(i as usize) {
+ 0x0 => (),
+ c => {
+ sp = c;
+ pri = (self.oam2[i].attr >> 5) & 1;
+ break;
+ }
+ }
+ }
+ assert!(0 < self.cycle && self.cycle < 257);
+ assert!(self.scanline < 240);
+ self.scr.put(self.cycle as u8 - 1,
+ self.scanline as u8,
+ if pri == 0 || bg == 0 { sp } else { bg });
+ }
+
+ pub fn tick(&mut self) -> bool {
+ let cycle = self.cycle;
+ if cycle == 0 {
+ self.cycle = cycle + 1;
+ return false;
+ }
+ let visible = self.scanline < 240;
+ let pre_render = self.scanline == 261;
+ let fill = pre_render || visible;
+ if pre_render {
+ if cycle == 1 {
+ self.vblank = false;
+ } else if 279 < cycle && cycle < 305 {
+ self.reset_y();
+ }
+ }
+ if fill {
+ let shifting = 0 < cycle && cycle < 257; /* 1..256 */
+ let fetch = shifting || (320 < cycle && cycle < 337);
+ if fetch { /* 1..256 and 321..336 */
+ match cycle & 0x7 {
+ 1 => {
+ self.load_bgtile();
+ self.fetch_nametable_byte();
+ },
+ 3 => self.fetch_attrtable_byte(),
+ 5 => self.fetch_low_bgtile_byte(),
+ 7 => self.fetch_high_bgtile_byte(),
+ 0 => self.wrapping_inc_cx(),
+ _ => ()
+ }
+ if !pre_render {
+ match cycle {
+ 1 => self.clear_sprite(), /* clear secondary OAM */
+ 65 => self.eval_sprite(), /* sprite evaluation */
+ _ => ()
+ }
+ }
+ match cycle {
+ 256 => self.wrapping_inc_y(),
+ 328 => self.shift_bgtile(8),
+ _ => ()
+ }
+ } else if cycle > 336 { /* 337..340 */
+ if cycle & 1 == 1 {
+ self.fetch_nametable_byte();
+ }
+ } else { /* 257..320 */
+ if cycle == 257 {
+ /* we don't emulate fetch to per cycle precision because all data are fetched
+ * from the secondary OAM which is not subject to any change during this
+ * scanline */
+ self.reset_cx();
+ self.fetch_sprite();
+ }
+ }
+ if shifting {
+ if visible {
+ self.render_pixel();
+ }
+ self.shift_bgtile(1);
+ self.shift_sprites();
+ }
+ } else if self.scanline == 241 && cycle == 1 {
+ self.vblank = true;
+ self.cycle += 1;
+ return true /* trigger cpu's NMI */
+ }
+ self.cycle += 1;
+ if self.cycle > 340 {
+ self.cycle = 0;
+ self.scanline += 1;
+ if self.scanline > 261 {
+ self.scanline = 0;
+ }
+ }
+ false
+ }
+}