feat: implement ability to boot straigt to cartridge
This commit is contained in:
parent
1502cc3ec2
commit
2a234f4d14
151
src/bus.rs
151
src/bus.rs
|
@ -1,48 +1,161 @@
|
||||||
#[derive(Debug, Copy, Clone)]
|
use super::cartridge::Cartridge;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
boot: [u8; 256],
|
boot: Option<[u8; 256]>,
|
||||||
|
cartridge: Option<Cartridge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Bus {
|
impl Default for Bus {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
boot: include_bytes!("../bin/DMG_ROM.bin").to_owned(),
|
boot: Some(include_bytes!("../bin/DMG_ROM.bin").to_owned()),
|
||||||
|
cartridge: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Bus {
|
||||||
|
pub fn with_boot() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn without_boot() -> Self {
|
||||||
|
Self {
|
||||||
|
boot: None,
|
||||||
|
cartridge: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_cartridge(&mut self, path: &str) {
|
||||||
|
self.cartridge = Some(Cartridge::new(path).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x00FF => {
|
0x0000..=0x3FFF => {
|
||||||
// Restart and Interrupt Vectors
|
// 16KB ROM bank 00
|
||||||
self.boot[addr as usize]
|
if addr <= 0x00FF && self.boot.is_some() {
|
||||||
|
let boot = self.boot.unwrap();
|
||||||
|
boot[addr as usize]
|
||||||
|
} else {
|
||||||
|
match &self.cartridge {
|
||||||
|
Some(cart) => cart.read_byte(addr),
|
||||||
|
None => panic!("Tried to read from a non-existant cartridge"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x4000..=0x7FFF => match &self.cartridge {
|
||||||
|
// 16KB ROM Bank 01 -> NN (switchable via MB)
|
||||||
|
Some(cart) => cart.read_byte(addr),
|
||||||
|
None => panic!("Tried to read from a non-existant cartridge"),
|
||||||
|
},
|
||||||
|
0x8000..=0x9FFF => {
|
||||||
|
// 8KB Video RAM
|
||||||
|
unimplemented!("Unable to read {:#06X} in Video RAM", addr);
|
||||||
|
}
|
||||||
|
0xA000..=0xBFFF => {
|
||||||
|
// 8KB External RAM
|
||||||
|
unimplemented!("Unable to read {:#06X} in Extermal RAM", addr);
|
||||||
|
}
|
||||||
|
0xC000..=0xCFFF => {
|
||||||
|
// 4KB Work RAM Bank 0
|
||||||
|
unimplemented!("Unable to read {:#06X} in Work RAM Bank 0", addr);
|
||||||
|
}
|
||||||
|
0xD000..=0xDFFF => {
|
||||||
|
// 4KB Work RAM Bank 1 -> N
|
||||||
|
unimplemented!("Unable to read {:#06X} in Work RAM Bank N", addr);
|
||||||
|
}
|
||||||
|
0xE000..=0xFDFF => {
|
||||||
|
// Mirror of 0xC000 to 0xDDFF
|
||||||
|
unimplemented!("Unable to read {:#06X} in Restricted Mirror", addr);
|
||||||
|
}
|
||||||
|
0xFE00..=0xFE9F => {
|
||||||
|
// Sprite Attrbute Table
|
||||||
|
unimplemented!("Unable to read {:#06X} in the Sprite Attribute Table", addr);
|
||||||
|
}
|
||||||
|
0xFEA0..=0xFEFF => unimplemented!("{:#06X} is not allowed to be used", addr),
|
||||||
|
0xFF00..=0xFF7F => {
|
||||||
|
// IO Registers
|
||||||
|
unimplemented!("Unable to read {:#06X} in I/O Registers", addr);
|
||||||
|
}
|
||||||
|
0xFF80..=0xFFFE => {
|
||||||
|
// High RAM
|
||||||
|
unimplemented!("Unable to read {:#06X} in High RAM", addr);
|
||||||
|
}
|
||||||
|
0xFFFF => {
|
||||||
|
// Interupts Enable Register
|
||||||
|
unimplemented!("Unable to read Interrupt Enable Register {:#06X} ", addr);
|
||||||
}
|
}
|
||||||
_ => unimplemented!("Can't read byte from {:#06x}", addr),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_byte(&mut self, addr: u16, byte: u8) {
|
pub fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
match addr {
|
unimplemented!("Can't write {:#04x} to {:#06X}", byte, addr)
|
||||||
0x000..=0x0FF => {
|
|
||||||
// Restart and Itterupt Vectors
|
|
||||||
self.boot[addr as usize] = byte;
|
|
||||||
}
|
|
||||||
_ => unimplemented!("Can't write {:#04x} to {:#06x}", byte, addr),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_word(&self, addr: u16) -> u16 {
|
pub fn read_word(&self, addr: u16) -> u16 {
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x00FF => {
|
0x0000..=0x3FFF => {
|
||||||
// Restart and Interrupt Vectors
|
// 16KB ROM bank 00
|
||||||
(self.boot[(addr + 1) as usize] as u16) << 8 | self.boot[addr as usize] as u16
|
if addr <= 0x00FF && self.boot.is_some() {
|
||||||
|
let boot = self.boot.unwrap();
|
||||||
|
(boot[(addr + 1) as usize] as u16) << 8 | boot[addr as usize] as u16
|
||||||
|
} else {
|
||||||
|
match &self.cartridge {
|
||||||
|
Some(cart) => cart.read_word(addr),
|
||||||
|
None => panic!("Tried to read from a non-existant cartridge"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x4000..=0x7FFF => match &self.cartridge {
|
||||||
|
// 16KB ROM Bank 01 -> NN (switchable via MB)
|
||||||
|
Some(cart) => cart.read_word(addr),
|
||||||
|
None => panic!("Tried to read from a non-existant cartridge"),
|
||||||
|
},
|
||||||
|
0x8000..=0x9FFF => {
|
||||||
|
// 8KB Video RAM
|
||||||
|
unimplemented!("Unable to read {:#06X} in Video RAM", addr);
|
||||||
|
}
|
||||||
|
0xA000..=0xBFFF => {
|
||||||
|
// 8KB External RAM
|
||||||
|
unimplemented!("Unable to read {:#06X} in Extermal RAM", addr);
|
||||||
|
}
|
||||||
|
0xC000..=0xCFFF => {
|
||||||
|
// 4KB Work RAM Bank 0
|
||||||
|
unimplemented!("Unable to read {:#06X} in Work RAM Bank 0", addr);
|
||||||
|
}
|
||||||
|
0xD000..=0xDFFF => {
|
||||||
|
// 4KB Work RAM Bank 1 -> N
|
||||||
|
unimplemented!("Unable to read {:#06X} in Work RAM Bank N", addr);
|
||||||
|
}
|
||||||
|
0xE000..=0xFDFF => {
|
||||||
|
// Mirror of 0xC000 to 0xDDFF
|
||||||
|
unimplemented!("Unable to read {:#06X} in Restricted Mirror", addr);
|
||||||
|
}
|
||||||
|
0xFE00..=0xFE9F => {
|
||||||
|
// Sprite Attrbute Table
|
||||||
|
unimplemented!("Unable to read {:#06X} in the Sprite Attribute Table", addr);
|
||||||
|
}
|
||||||
|
0xFEA0..=0xFEFF => unimplemented!("{:#06X} is not allowed to be used", addr),
|
||||||
|
0xFF00..=0xFF7F => {
|
||||||
|
// IO Registers
|
||||||
|
unimplemented!("Unable to read {:#06X} in I/O Registers", addr);
|
||||||
|
}
|
||||||
|
0xFF80..=0xFFFE => {
|
||||||
|
// High RAM
|
||||||
|
unimplemented!("Unable to read {:#06X} in High RAM", addr);
|
||||||
|
}
|
||||||
|
0xFFFF => {
|
||||||
|
// Interupts Enable Register
|
||||||
|
unimplemented!("Unable to read Interrupt Enable Register {:#06X} ", addr);
|
||||||
}
|
}
|
||||||
_ => unimplemented!("Can't read word from {:#06x}", addr),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_word(&mut self, addr: u16, word: u16) {
|
pub fn write_word(&mut self, addr: u16, word: u16) {
|
||||||
unimplemented!("Can't write {:#06x} to {:#06x}", word, addr)
|
unimplemented!("Can't write {:#06X} to {:#06X}", word, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cartridge {
|
||||||
|
memory: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cartridge {
|
||||||
|
pub fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> {
|
||||||
|
let mut memory = vec![];
|
||||||
|
let mut rom = File::open(path)?;
|
||||||
|
rom.read_to_end(&mut memory)?;
|
||||||
|
|
||||||
|
Ok(Self { memory })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
||||||
|
self.memory[addr as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_word(&self, addr: u16) -> u16 {
|
||||||
|
(self.memory[(addr + 1) as usize] as u16) << 8 | self.memory[addr as usize] as u16
|
||||||
|
}
|
||||||
|
}
|
25
src/cpu.rs
25
src/cpu.rs
|
@ -1,7 +1,7 @@
|
||||||
use super::bus::Bus;
|
use super::bus::Bus;
|
||||||
use super::instruction::Instruction;
|
use super::instruction::Instruction;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Cpu {
|
pub struct Cpu {
|
||||||
bus: Bus,
|
bus: Bus,
|
||||||
reg: Registers,
|
reg: Registers,
|
||||||
|
@ -15,6 +15,25 @@ impl Cpu {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_without_boot() -> Self {
|
||||||
|
Self {
|
||||||
|
bus: Bus::without_boot(),
|
||||||
|
reg: Registers {
|
||||||
|
a: 0x01,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x13,
|
||||||
|
d: 0x00,
|
||||||
|
e: 0xD8,
|
||||||
|
h: 0x01,
|
||||||
|
l: 0x4D,
|
||||||
|
sp: 0xFFFE,
|
||||||
|
pc: 0x0100,
|
||||||
|
},
|
||||||
|
flags: 0xb0.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ime(&self) -> bool {
|
pub fn ime(&self) -> bool {
|
||||||
self.ime
|
self.ime
|
||||||
}
|
}
|
||||||
|
@ -26,6 +45,10 @@ impl Cpu {
|
||||||
pub fn inc_pc(&mut self) {
|
pub fn inc_pc(&mut self) {
|
||||||
self.reg.pc += 1;
|
self.reg.pc += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_cartridge(&mut self, path: &str) {
|
||||||
|
self.bus.load_cartridge(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub enum Instruction {
|
||||||
SET(u8, InstrRegister),
|
SET(u8, InstrRegister),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum JPTarget {
|
pub enum JPTarget {
|
||||||
RegisterPair(RegisterPair),
|
RegisterPair(RegisterPair),
|
||||||
ImmediateWord(u16),
|
ImmediateWord(u16),
|
||||||
|
@ -70,7 +70,7 @@ pub enum MATHTarget {
|
||||||
ImmediateByte(u8),
|
ImmediateByte(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum LDTarget {
|
pub enum LDTarget {
|
||||||
IndirectC,
|
IndirectC,
|
||||||
Register(InstrRegister),
|
Register(InstrRegister),
|
||||||
|
@ -1993,3 +1993,27 @@ impl Table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for JPTarget {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
JPTarget::RegisterPair(pair) => write!(f, "{:?}", pair),
|
||||||
|
JPTarget::ImmediateWord(word) => write!(f, "{:#06X}", word),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for LDTarget {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
LDTarget::IndirectC => f.write_str("IndirectC"),
|
||||||
|
LDTarget::Register(reg) => write!(f, "{:?}", reg),
|
||||||
|
LDTarget::IndirectRegister(pair) => write!(f, "{:?}", pair),
|
||||||
|
LDTarget::ByteAtAddress(addr) => write!(f, "{:#06X}", addr),
|
||||||
|
LDTarget::ImmediateWord(word) => write!(f, "{:#06X}", word),
|
||||||
|
LDTarget::ImmediateByte(byte) => write!(f, "{:04X}", byte),
|
||||||
|
LDTarget::RegisterPair(pair) => write!(f, "{:?}", pair),
|
||||||
|
LDTarget::ByteAtAddressWithOffset(byte) => write!(f, "{:#04X}", byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
mod bus;
|
mod bus;
|
||||||
|
mod cartridge;
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
mod instruction;
|
mod instruction;
|
||||||
|
|
|
@ -3,13 +3,15 @@ use gb::cpu::Cpu as LR35902;
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut game_boy = LR35902::new();
|
let mut game_boy = LR35902::new();
|
||||||
|
|
||||||
|
game_boy.load_cartridge("bin/cpu_instrs.gb");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pc = game_boy.register_pair(gb::cpu::RegisterPair::PC);
|
let pc = game_boy.register_pair(gb::cpu::RegisterPair::PC);
|
||||||
let opcode = game_boy.fetch();
|
let opcode = game_boy.fetch();
|
||||||
let instruction = game_boy.decode(opcode);
|
let instruction = game_boy.decode(opcode);
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Addr: {:#06x} | Opcode: {:#x} | Instr: {:x?}",
|
"Addr: {:#06X} | Opcode: {:#04X} | Instr: {:X?}",
|
||||||
pc, opcode, instruction
|
pc, opcode, instruction
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue