Skip to content

Commit c07d780

Browse files
committed
snake game running from ROM. tests broken
1 parent cda14b6 commit c07d780

File tree

4 files changed

+113
-73
lines changed

4 files changed

+113
-73
lines changed

src/bus.rs

+40-33
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,42 @@
1-
use crate::core::Mem;
1+
use crate::{core::Mem, rom::Rom};
22

33
const RAM: u16 = 0x0000;
44
const RAM_MIRROR_END: u16 = 0x2000;
55

66
const PPU: u16 = 0x2000;
77
const PPU_MIRROR_END: u16 = 0x4000;
88

9+
pub const PRG_ROM_START: u16 = 0x8000;
10+
const PRG_ROM_END: u16 = 0xFFFF;
11+
912
pub struct Bus {
1013
cpu_vram: [u8; 0x800], // 2048
11-
12-
// TODO: This was needed to get my tests working again after Ch4
13-
// https://bugzmanov.github.io/nes_ebook/chapter_4.html
14-
// Did the author have another approach? I couldn't find it in repo.
15-
// fallback: [u8; 0xffff],
16-
program_start1: u8,
17-
program_start2: u8,
14+
rom: Rom,
15+
program_start_1: u8,
16+
program_start_2: u8,
1817
}
1918

2019
impl Bus {
21-
pub fn new() -> Self {
20+
pub fn new(rom: Rom) -> Self {
2221
Bus {
2322
cpu_vram: [0; 0x800],
24-
// fallback: [0; 0xffff],
25-
program_start1: 0,
26-
program_start2: 0,
23+
rom,
24+
program_start_1: 0,
25+
program_start_2: 0,
2726
}
2827
}
29-
}
30-
31-
// TODO: More mappings..
32-
33-
// That RAM is accessible via [0x0000 … 0x2000] address space.
3428

35-
// Access to [0x2000 … 0x4020] is redirected to other available NES hardware modules: PPU, APU, GamePads, etc. (more on this later)
29+
fn read_prg_rom(&self, addr: u16) -> u8 {
30+
let mut idx = addr - PRG_ROM_START;
3631

37-
// Access to [0x4020 .. 0x6000] is a special space that different generations of cartridges used differently. It might be mapped to RAM, ROM, or nothing at all. The space is controlled by so-called mappers - special circuitry on a cartridge. We will ignore this space.
38-
39-
// Access to [0x6000 .. 0x8000] is reserved to a RAM space on a cartridge if a cartridge has one. It was used in games like Zelda for storing and retrieving the game state. We will ignore this space as well.
32+
// If the prg rom is 16KiB (not 32KiB), then we should mirror it
33+
if self.rom.prg_rom.len() == 0x4000 {
34+
idx %= 0x4000;
35+
}
4036

41-
// Access to [0x8000 … 0xFFFF] is mapped to Program ROM (PRG ROM) space on a cartridge.
37+
self.rom.prg_rom[idx as usize]
38+
}
39+
}
4240

4341
impl Mem for Bus {
4442
fn mem_read(&self, addr: u16) -> u8 {
@@ -47,12 +45,15 @@ impl Mem for Bus {
4745
self.cpu_vram[a as usize]
4846
} else if (PPU..PPU_MIRROR_END).contains(&addr) {
4947
todo!("PPU NYI")
50-
} else if addr == 0xFFFC {
51-
self.program_start1
52-
} else if addr == 0xFFFD {
53-
self.program_start2
48+
} else if (PRG_ROM_START..=PRG_ROM_END).contains(&addr) {
49+
// if addr == 0xFFFC {
50+
// self.program_start_1
51+
// } else if addr == 0xFFFD {
52+
// self.program_start_2
53+
// } else {
54+
self.read_prg_rom(addr)
55+
// }
5456
} else {
55-
// self.fallback[addr as usize]
5657
0
5758
}
5859
}
@@ -63,10 +64,14 @@ impl Mem for Bus {
6364
self.cpu_vram[a as usize] = data
6465
} else if (PPU..PPU_MIRROR_END).contains(&addr) {
6566
todo!("PPU NYI")
66-
} else if addr == 0xFFFC {
67-
self.program_start1 = data
68-
} else if addr == 0xFFFD {
69-
self.program_start2 = data
67+
} else if (PRG_ROM_START..=PRG_ROM_END).contains(&addr) {
68+
// if addr == 0xFFFC {
69+
// self.program_start_1 = data
70+
// } else if addr == 0xFFFD {
71+
// self.program_start_2 = data
72+
// } else {
73+
panic!("attempt to write to ROM cartridge")
74+
// }
7075
} else {
7176
// self.fallback[addr as usize] = data
7277
};
@@ -79,7 +84,8 @@ mod tests {
7984

8085
#[test]
8186
fn test_read_mirroring() {
82-
let mut bus = Bus::new();
87+
let rom = Rom::new_test();
88+
let mut bus = Bus::new(rom);
8389
bus.cpu_vram[0] = 123;
8490

8591
assert_eq!(bus.mem_read(0), 123);
@@ -90,7 +96,8 @@ mod tests {
9096

9197
#[test]
9298
fn test_write_mirroring() {
93-
let mut bus = Bus::new();
99+
let rom = Rom::new_test();
100+
let mut bus = Bus::new(rom);
94101

95102
bus.mem_write(0, 1);
96103
assert_eq!(bus.cpu_vram[0], 1);

src/core.rs

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::bus::Bus;
1+
use crate::{
2+
bus::{Bus, PRG_ROM_START},
3+
rom::Rom,
4+
};
25

36
/// CPU (Central Processing Unit)
47
/// The NES uses 2A03, which is a modified version of the 6502 chip.
@@ -92,17 +95,22 @@ impl Mem for Cpu {
9295

9396
impl Cpu {
9497
pub fn new() -> Self {
98+
let rom = Rom::new_test();
9599
Cpu {
96100
pc: 0,
97101
sp: 0xff,
98102
a: 0,
99103
x: 0,
100104
y: 0,
101105
status: 0,
102-
bus: Bus::new(),
106+
bus: Bus::new(rom),
103107
}
104108
}
105109

110+
pub fn set_bus(&mut self, bus: Bus) {
111+
self.bus = bus
112+
}
113+
106114
//
107115
// STACK
108116
//
@@ -181,12 +189,22 @@ impl Cpu {
181189

182190
// load method should load a program into PRG ROM space and save the reference to the code into 0xFFFC memory cell
183191
pub fn load(&mut self, program: Vec<u8>) {
184-
for (i, val) in program.iter().enumerate() {
185-
self.mem_write((CPU_START + i) as u16, *val);
186-
}
187-
self.mem_write_u16(0xFFFC, CPU_START as u16);
192+
// for (i, val) in program.iter().enumerate() {
193+
// self.mem_write((CPU_START + i) as u16, *val);
194+
// }
195+
let rom = Rom::new(&program);
196+
self.set_bus(Bus::new(rom));
197+
198+
// TODO: update this to get unit tests working
199+
// self.mem_write_u16(0xFFFC, CPU_START as u16);
188200
}
189201

202+
// pub fn load_rom(&mut self, rom: Rom) {
203+
// // Load turns program into ROM, then sets the bus
204+
// self.set_bus(Bus::new(rom));
205+
// // self.mem_write_u16(0xFFFC, CPU_START as u16);
206+
// }
207+
190208
fn load_and_run(&mut self, program: Vec<u8>) {
191209
self.load(program);
192210
self.reset();
@@ -222,10 +240,10 @@ impl Cpu {
222240
0,
223241
op
224242
);
225-
// println!(
226-
// "\ta={:#06x} x={:#06x} y={:#06x} flags={:#010b}",
227-
// self.a, self.x, self.y, self.status,
228-
// );
243+
println!(
244+
"\ta={:#06x} x={:#06x} y={:#06x} flags={:#010b}",
245+
self.a, self.x, self.y, self.status,
246+
);
229247
self.pc += 1;
230248
match op {
231249
// LDA
@@ -1280,7 +1298,9 @@ mod tests {
12801298
#[test]
12811299
fn test_0xa0_lda_immediate_nonzero() {
12821300
let mut cpu = Cpu::new();
1283-
cpu.load_and_run(vec![0xa9, 0x55, 0x00]);
1301+
cpu.load_rom(Rom::new_test_rom(vec![0xa9, 0x55, 0x00]));
1302+
cpu.reset();
1303+
cpu.run();
12841304
assert_eq!(cpu.a, 0x55);
12851305
assert_eq!(cpu.status, 0b0000_0000);
12861306
}

src/main.rs

+6-25
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,24 @@
11
use core::Cpu;
22
use core::Mem;
33
use std::error::Error;
4+
use std::fs;
45
use std::thread::sleep;
56
use std::time::Duration;
67

78
mod bus;
89
mod core;
910
mod rom;
1011

12+
use bus::Bus;
1113
use rand::random;
14+
use rom::Rom;
1215
use sdl2::event::Event;
1316
use sdl2::keyboard::Keycode;
1417
use sdl2::pixels::Color;
1518
use sdl2::pixels::PixelFormatEnum;
1619
use sdl2::EventPump;
1720

1821
fn main() -> Result<(), Box<dyn Error>> {
19-
// snake game, for basic testing
20-
let program = vec![
21-
0x20, 0x06, 0x06, 0x20, 0x38, 0x06, 0x20, 0x0d, 0x06, 0x20, 0x2a, 0x06, 0x60, 0xa9, 0x02,
22-
0x85, 0x02, 0xa9, 0x04, 0x85, 0x03, 0xa9, 0x11, 0x85, 0x10, 0xa9, 0x10, 0x85, 0x12, 0xa9,
23-
0x0f, 0x85, 0x14, 0xa9, 0x04, 0x85, 0x11, 0x85, 0x13, 0x85, 0x15, 0x60, 0xa5, 0xfe, 0x85,
24-
0x00, 0xa5, 0xfe, 0x29, 0x03, 0x18, 0x69, 0x02, 0x85, 0x01, 0x60, 0x20, 0x4d, 0x06, 0x20,
25-
0x8d, 0x06, 0x20, 0xc3, 0x06, 0x20, 0x19, 0x07, 0x20, 0x20, 0x07, 0x20, 0x2d, 0x07, 0x4c,
26-
0x38, 0x06, 0xa5, 0xff, 0xc9, 0x77, 0xf0, 0x0d, 0xc9, 0x64, 0xf0, 0x14, 0xc9, 0x73, 0xf0,
27-
0x1b, 0xc9, 0x61, 0xf0, 0x22, 0x60, 0xa9, 0x04, 0x24, 0x02, 0xd0, 0x26, 0xa9, 0x01, 0x85,
28-
0x02, 0x60, 0xa9, 0x08, 0x24, 0x02, 0xd0, 0x1b, 0xa9, 0x02, 0x85, 0x02, 0x60, 0xa9, 0x01,
29-
0x24, 0x02, 0xd0, 0x10, 0xa9, 0x04, 0x85, 0x02, 0x60, 0xa9, 0x02, 0x24, 0x02, 0xd0, 0x05,
30-
0xa9, 0x08, 0x85, 0x02, 0x60, 0x60, 0x20, 0x94, 0x06, 0x20, 0xa8, 0x06, 0x60, 0xa5, 0x00,
31-
0xc5, 0x10, 0xd0, 0x0d, 0xa5, 0x01, 0xc5, 0x11, 0xd0, 0x07, 0xe6, 0x03, 0xe6, 0x03, 0x20,
32-
0x2a, 0x06, 0x60, 0xa2, 0x02, 0xb5, 0x10, 0xc5, 0x10, 0xd0, 0x06, 0xb5, 0x11, 0xc5, 0x11,
33-
0xf0, 0x09, 0xe8, 0xe8, 0xe4, 0x03, 0xf0, 0x06, 0x4c, 0xaa, 0x06, 0x4c, 0x35, 0x07, 0x60,
34-
0xa6, 0x03, 0xca, 0x8a, 0xb5, 0x10, 0x95, 0x12, 0xca, 0x10, 0xf9, 0xa5, 0x02, 0x4a, 0xb0,
35-
0x09, 0x4a, 0xb0, 0x19, 0x4a, 0xb0, 0x1f, 0x4a, 0xb0, 0x2f, 0xa5, 0x10, 0x38, 0xe9, 0x20,
36-
0x85, 0x10, 0x90, 0x01, 0x60, 0xc6, 0x11, 0xa9, 0x01, 0xc5, 0x11, 0xf0, 0x28, 0x60, 0xe6,
37-
0x10, 0xa9, 0x1f, 0x24, 0x10, 0xf0, 0x1f, 0x60, 0xa5, 0x10, 0x18, 0x69, 0x20, 0x85, 0x10,
38-
0xb0, 0x01, 0x60, 0xe6, 0x11, 0xa9, 0x06, 0xc5, 0x11, 0xf0, 0x0c, 0x60, 0xc6, 0x10, 0xa5,
39-
0x10, 0x29, 0x1f, 0xc9, 0x1f, 0xf0, 0x01, 0x60, 0x4c, 0x35, 0x07, 0xa0, 0x00, 0xa5, 0xfe,
40-
0x91, 0x00, 0x60, 0xa6, 0x03, 0xa9, 0x00, 0x81, 0x10, 0xa2, 0x00, 0xa9, 0x01, 0x81, 0x10,
41-
0x60, 0xa2, 0x00, 0xea, 0xea, 0xca, 0xd0, 0xfb, 0x60,
42-
];
43-
4422
let sdl_context = sdl2::init()?;
4523
let video_subsystem = sdl_context.video()?;
4624
let window = video_subsystem
@@ -57,6 +35,9 @@ fn main() -> Result<(), Box<dyn Error>> {
5735

5836
let mut screen_state = [0_u8; 32 * 3 * 32];
5937

38+
// snake game, for basic testing
39+
let program = fs::read("roms/snake.nes").unwrap();
40+
6041
let mut cpu = Cpu::new();
6142
cpu.load(program);
6243
cpu.reset();

src/rom.rs

+36-4
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ enum Mapper {
1111
}
1212

1313
pub struct Rom {
14-
prg_rom: Vec<u8>,
14+
pub prg_rom: Vec<u8>,
1515
chr_rom: Vec<u8>,
1616
mapper: Mapper,
1717
mirroring: Mirroring,
1818
}
1919

20-
const CHR_ROM_PAGE_SIZE: usize = 256;
21-
const PRG_ROM_PAGE_SIZE: usize = 256;
20+
const PRG_ROM_PAGE_SIZE: usize = 16384;
21+
const CHR_ROM_PAGE_SIZE: usize = 8192;
2222

2323
impl Rom {
2424
pub fn new(bytes: &[u8]) -> Self {
@@ -39,7 +39,7 @@ impl Rom {
3939
let control_byte_2 = header[7];
4040
// let size_of_prg_ram_times_8kb = header[8];
4141

42-
let has_trainer_bytes = control_byte_1 ^ (1 << 2) > 0;
42+
let has_trainer_bytes = control_byte_1 ^ (1 << 2) == 0;
4343
let is_four_screen = control_byte_1 & (1 << 3) > 0;
4444
let is_vertical_screen = control_byte_1 & 1 > 0;
4545

@@ -76,6 +76,38 @@ impl Rom {
7676
mirroring,
7777
}
7878
}
79+
80+
pub fn new_test() -> Self {
81+
let mut prg_rom = [0; 0x8000].to_vec();
82+
// prg_rom[0xFFFC - 0x8000] = CPU_START
83+
// prg_rom[0xFFFC - 0x8000] = CPU_START
84+
Self {
85+
prg_rom: vec![],
86+
chr_rom: vec![],
87+
mapper: Mapper::Zero,
88+
mirroring: Mirroring::Vertical,
89+
}
90+
}
91+
92+
pub(crate) fn new_test_rom(vec: Vec<u8>) -> Rom {
93+
let mut prg_rom: Vec<u8> = [0; 0x8000].to_vec();
94+
let program_start = 0x600;
95+
for (idx, b) in vec.iter().enumerate() {
96+
prg_rom[program_start + idx] = *b;
97+
}
98+
99+
prg_rom[0xFFFC - 0x8000] = 0x00;
100+
prg_rom[0xFFFD - 0x8000] = 0x86;
101+
102+
println!("{:?}", &prg_rom[0x600..0x640]);
103+
104+
Self {
105+
prg_rom,
106+
chr_rom: vec![],
107+
mapper: Mapper::Zero,
108+
mirroring: Mirroring::Vertical,
109+
}
110+
}
79111
}
80112

81113
#[cfg(test)]

0 commit comments

Comments
 (0)