commit acfbb8f1cf82c84aaf4072d26574e22687372e97 Author: John Date: Sat Jun 22 07:25:59 2024 -0500 boy: Initial public commit - SM83 implementation kinda works - Live disassembly, memory write tracing - Pretty snazzy debugger with custom memory editor - hexadecimal calculator with novel operator precedence rules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eadd635 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target + +# Test ROMs +/roms +**.gb + +# VSCode +.vscode diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a4ace1f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +workspace = { members = ["boy-debug", "boy-utils"] } +[package] +name = "boy" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitfield-struct = "0.7.0" diff --git a/boy-debug/Cargo.toml b/boy-debug/Cargo.toml new file mode 100644 index 0000000..86fdd6b --- /dev/null +++ b/boy-debug/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "boy-debug" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rustyline = "14.0.0" +boy = { path = ".." } +boy-utils = { path = "../boy-utils" } diff --git a/boy-debug/src/bus.rs b/boy-debug/src/bus.rs new file mode 100644 index 0000000..04ff8f1 --- /dev/null +++ b/boy-debug/src/bus.rs @@ -0,0 +1,119 @@ +//! A dummied out implementation of a Bus, which is filled entirely with RAM + +use std::{borrow::Cow, io::Result as IoResult, path::Path}; + +use boy::memory::io::BusIO; + +pub trait BusIOTools: BusIO { + /// Prints all successful reads and writes + fn trace(&mut self) -> TracingBus; + fn ascii(&mut self) -> AsciiSerial; + fn read_file(self, path: impl AsRef) -> IoResult + where + Self: Sized; +} + +impl BusIOTools for T { + fn trace(&mut self) -> TracingBus { + TracingBus { bus: self } + } + fn ascii(&mut self) -> AsciiSerial { + AsciiSerial { + data: 0, + buf: vec![], + bus: self, + } + } + fn read_file(mut self, path: impl AsRef) -> IoResult { + let data = std::fs::read(path)?; + eprintln!("Read {} bytes.", data.len()); + for (addr, data) in data.into_iter().enumerate() { + self.write(addr, data); + } + Ok(self) + } +} + +/// A [BusIO] wrapper which prints every read and write operation +#[repr(transparent)] +pub struct TracingBus<'t, T: BusIO + ?Sized> { + bus: &'t mut T, +} + +impl<'t, T: BusIO> BusIO for TracingBus<'t, T> { + fn read(&self, addr: usize) -> Option { + // print!("bus[{addr:04x}] -> "); + // match out { + // Some(v) => println!("{v:02x}"), + // None => println!("None"), + // } + self.bus.read(addr) + } + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + eprintln!("set [{addr:04x}], {data:02x}"); + self.bus.write(addr, data) + } +} + +impl<'t, T: BusIO> From<&'t mut T> for TracingBus<'t, T> { + fn from(value: &'t mut T) -> Self { + Self { bus: value } + } +} +impl<'t, T: BusIO + AsRef<[u8]>> AsRef<[u8]> for TracingBus<'t, T> { + fn as_ref(&self) -> &[u8] { + self.bus.as_ref() + } +} +impl<'t, T: BusIO + AsMut<[u8]>> AsMut<[u8]> for TracingBus<'t, T> { + fn as_mut(&mut self) -> &mut [u8] { + self.bus.as_mut() + } +} + +/// Implements a hacky serial port for extracting data +pub struct AsciiSerial<'t, T: BusIO + ?Sized> { + data: u8, + buf: Vec, + bus: &'t mut T, +} + +impl<'t, T: BusIO + ?Sized> AsciiSerial<'t, T> { + /// Gets the contents of the data buffer + pub fn string(&self) -> Cow { + String::from_utf8_lossy(&self.buf) + } +} + +impl<'t, T: BusIO + ?Sized> BusIO for AsciiSerial<'t, T> { + fn read(&self, addr: usize) -> Option { + match addr { + 0xff01 => Some(self.data), + 0xff02 => Some(0x7f), + _ => self.bus.read(addr), + } + } + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + match addr { + 0xff01 => { + // eprintln!("'{data:02x}'"); + self.buf.push(data); + } + 0xff02 => { + if data & 0x80 != 0 { + // eprintln!("SERIAL => {:02x}", self.data); + eprintln!("tx: {data:02x}, buf: {:02x?}", self.buf); + let interrupt = self.bus.read(0xff0f)? | (1 << 3); + self.bus.write(0xff0f, interrupt)?; + } + } + _ => {} + } + self.bus.write(addr, data) + } + fn diag(&mut self, param: usize) { + println!("debug: '{}'", self.string()); + self.buf.clear(); + self.bus.diag(param) + } +} diff --git a/boy-debug/src/disassembler.rs b/boy-debug/src/disassembler.rs new file mode 100644 index 0000000..622e302 --- /dev/null +++ b/boy-debug/src/disassembler.rs @@ -0,0 +1,86 @@ +//! Wraps the [boy] [Insn]-disassembler and extends it to disassemble entire instructions, rather than just single words + +use boy::cpu::disasm::{Insn, Prefixed}; +use std::iter::{Enumerate, Peekable}; + +pub trait Disassemble: Iterator + Sized { + fn disassemble(self) -> Disassembler; +} +impl + Sized> Disassemble for T { + fn disassemble(self) -> Disassembler { + Disassembler::new(self) + } +} + +/// An iterator over some bytes, which prints the disassembly to stdout +// TODO: parameterize this over a Writer +pub struct Disassembler> { + bytes: Peekable>, +} + +impl> Iterator for Disassembler { + type Item = (usize, String); + + fn next(&mut self) -> Option { + let (index, insn) = self.bytes.next()?; + Some((index, self.print(insn.into())?)) + } +} + +impl> Disassembler { + pub fn new(bytes: I) -> Self { + Disassembler { + bytes: bytes.enumerate().peekable(), + } + } + pub fn index(&mut self) -> Option { + self.bytes.peek().map(|v| v.0) + } + pub fn imm8(&mut self) -> Option { + self.bytes.next().map(|v| v.1) + } + pub fn smm8(&mut self) -> Option { + self.imm8().map(|b| b as i8) + } + pub fn imm16(&mut self) -> Option { + let low = self.imm8()? as u16; + let high = self.imm8()? as u16; + Some(high << 8 | low) + } + pub fn smm16(&mut self) -> Option { + self.imm16().map(|w| w as i16) + } + pub fn print(&mut self, insn: Insn) -> Option { + Some(match insn { + Insn::LdImm(reg) => format!("ld\t{reg}, {:02x}", self.imm8()?), + Insn::LdImm16(reg) => format!("ld\t{reg}, {:04x}", self.imm16()?), + Insn::LdAbs => format!("ld\ta, [{:04x}]", self.imm16()?), + Insn::StAbs => format!("ld\t[{:04x}], a", self.imm16()?), + Insn::StSpAbs => format!("ld\t[{:04x}], sp", self.imm16()?), + Insn::LdHlSpRel => format!("ld\thl, sp + {:02x}", self.imm8()?), + Insn::Ldh => format!("ldh\ta, [ff{:02x}]", self.imm8()?), + Insn::Sth => format!("ldh\t[ff{:02x}], a", self.imm8()?), + Insn::Add16SpI => format!("add\tsp, {:02x}", self.imm8()?), + Insn::AddI => format!("add\ta, {:02x}", self.imm8()?), + Insn::AdcI => format!("adc\ta, {:02x}", self.imm8()?), + Insn::SubI => format!("sub\ta, {:02x}", self.imm8()?), + Insn::SbcI => format!("sbc\ta, {:02x}", self.imm8()?), + Insn::AndI => format!("and\ta, {:02x}", self.imm8()?), + Insn::XorI => format!("xor\ta, {:02x}", self.imm8()?), + Insn::OrI => format!("or\ta, {:02x}", self.imm8()?), + Insn::CpI => format!("cp\ta, {:02x}", self.imm8()?), + Insn::Jr => format!("jr\t{0:02x} ({0:02})", self.smm8()?), + Insn::Jrc(cond) => format!("jr\t{cond}, {:02x} ({0:02})", self.smm8()?), + Insn::Jp => format!("jp\t{:04x}", self.imm16()?), + Insn::Jpc(cond) => format!("jp\t{cond}, {:04x}", self.imm16()?), + Insn::Call => format!("call\t{:04x}", self.imm16()?), + Insn::Callc(cond) => format!("call\t{cond}, {:04x}", self.imm16()?), + Insn::PrefixCB => self.print_prefix_cb()?, + _ => format!("{insn}"), + }) + } + pub fn print_prefix_cb(&mut self) -> Option { + let prefixed: Prefixed = self.imm8()?.into(); + Some(format!("{prefixed}")) + } +} diff --git a/boy-debug/src/lib.rs b/boy-debug/src/lib.rs new file mode 100644 index 0000000..777087a --- /dev/null +++ b/boy-debug/src/lib.rs @@ -0,0 +1,1127 @@ +//! Utilities for presenting a common debug interface + +pub mod bus; + +pub mod disassembler; + +pub mod cli { + //! Command line interface for boy-debug + + pub mod token { + //! Tokens for the [Lexer](super::lexer::Lexer) to emit + + pub use boy::cpu::disasm::{R16, R8}; + use std::fmt::Display; + + /// A unit of lexical information + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Token { + Op(Op), + Num(usize), + Bool(bool), + Reg(Reg), + Ident(String), + Other(char), + } + + impl Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Token::Op(op) => write!(f, "{op}"), + Token::Num(num) => num.fmt(f), + Token::Bool(b) => b.fmt(f), + Token::Reg(reg) => write!(f, "{reg}"), + Token::Ident(s) => s.fmt(f), + Token::Other(c) => write!(f, "'{c}' (Invalid)"), + } + } + } + + /// An arithmetic or bitwise operator + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Op { + /// `*`: Multiplication + Mul, + /// `/`: Division + Div, + /// `%`: Remainder + Rem, + /// `+`: Addition + Add, + /// `-`: Subtraction or Negation + Sub, + /// `>>`: Shift Right + Shr, + /// `<<`: Shift Left + Shl, + /// `&`: Bitwise And + And, + /// `|`: Bitwise Or + Or, + /// `^`: Bitwise Xor + Xor, + /// `!`: Bitwise Not + Bang, + /// `..`: Range + Range, + /// `(`: Open parenthesis + ParenOpen, + /// `)`: Close parenthesis + ParenClose, + /// `[`: Open brackets + BrackOpen, + /// `]`: Close brackets + BrackClose, + /// ',': Comma + Comma, + /// `=`: Equals Sign + Eq, + } + impl Display for Op { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Op::Mul => "*", + Op::Div => "/", + Op::Rem => "%", + Op::Add => "+", + Op::Sub => "-", + Op::Shr => ">>", + Op::Shl => "<<", + Op::And => "&", + Op::Or => "|", + Op::Xor => "^", + Op::Bang => "!", + Op::Range => "..", + Op::ParenOpen => "(", + Op::ParenClose => ")", + Op::BrackOpen => "[", + Op::BrackClose => "]", + Op::Comma => ",", + Op::Eq => "=", + } + .fmt(f) + } + } + + /// Describes an abstract CPU register + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Reg { + A, + F, + B, + C, + D, + E, + H, + L, + AF, + BC, + DE, + HL, + SP, + PC, + } + + impl Display for Reg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Reg::A => write!(f, "a"), + Reg::F => write!(f, "f"), + Reg::B => write!(f, "b"), + Reg::C => write!(f, "c"), + Reg::D => write!(f, "d"), + Reg::E => write!(f, "e"), + Reg::H => write!(f, "h"), + Reg::L => write!(f, "l"), + Reg::AF => write!(f, "af"), + Reg::BC => write!(f, "bc"), + Reg::DE => write!(f, "de"), + Reg::HL => write!(f, "hl"), + Reg::SP => write!(f, "sp"), + Reg::PC => write!(f, "pc"), + } + } + } + } + + pub mod lexer { + //! A [Lexer] lexically analyzes a stream of utf-8 characters, producing a stream of tokens + use super::token::{ + Op::*, + Reg, + Token::{self, *}, + }; + use std::iter::Peekable; + + pub trait Lexible: Iterator { + /// Constructs a [`Lexer`] from Self + fn lex(self) -> Lexer + where + Self: Sized; + } + + impl> Lexible for T { + fn lex(self) -> Lexer { + Lexer { + text: self.peekable(), + } + } + } + + #[derive(Clone, Debug)] + pub struct Lexer> { + text: Peekable, + } + + impl> Lexer { + fn sync(&mut self) -> &mut Self { + while self.peek().map(|c| c.is_whitespace()).unwrap_or(false) { + self.take(); + } + self + } + fn peek(&mut self) -> Option<&char> { + self.text.peek() + } + fn take(&mut self) -> &mut Self { + self.text.next(); + self + } + fn emit(&mut self, kind: Token) -> Option { + Some(kind) + } + fn digits(&mut self) -> Option { + let mut out = 0; + while let Some(Some(next)) = self.peek().map(|c| c.to_digit(B)) { + self.take(); + out = (out * B as usize) + next as usize; + } + Some(Num(out)) + } + fn base(&mut self) -> Option { + match self.peek() { + Some('b') => self.take().digits::<2>(), + Some('d') => self.take().digits::<10>(), + Some('o') => self.take().digits::<8>(), + Some('x') => self.take().digits::<16>(), + _ => self.digits::<16>(), + } + } + fn ident(&mut self) -> Option { + let mut out = String::new(); + while let Some(&c) = self.peek().filter(|&&c| c.is_alphanumeric() || c == '_') { + self.take(); + out.push(c) + } + // Match keyword + Some(match out.as_str() { + "false" | "off" => Token::Bool(false), + "true" | "on" => Token::Bool(true), + + "rA" | "a" => Token::Reg(Reg::A), + "rF" | "f" => Token::Reg(Reg::F), + "rB" | "b" => Token::Reg(Reg::B), + "rC" | "c" => Token::Reg(Reg::C), + "rD" | "d" => Token::Reg(Reg::D), + "rE" | "e" => Token::Reg(Reg::E), + "rH" | "h" => Token::Reg(Reg::H), + "rL" | "l" => Token::Reg(Reg::L), + + "rAF" | "af" => Token::Reg(Reg::AF), + "rBC" | "bc" => Token::Reg(Reg::BC), + "rDE" | "de" => Token::Reg(Reg::DE), + "rHL" | "hl" => Token::Reg(Reg::HL), + "rSP" | "sp" => Token::Reg(Reg::SP), + "rPC" | "pc" => Token::Reg(Reg::PC), + _ => match usize::from_str_radix(&out, 16) { + Ok(n) => Token::Num(n), + Err(_) => Token::Ident(out), + }, + }) + } + /// Checks the following character, producing the provided [Token] + /// if it matches the expected [char]. Otherwise, prodices [Token::Other]. + fn chain(&mut self, expect: char, becomes: Token) -> Option { + match self.peek() { + Some(c) if *c == expect => self.take().emit(becomes), + _ => self.emit(Token::Other(expect)), + } + } + + fn range(&mut self) -> Option { + match self.peek() { + Some('.') => self.take(), + _ => self, + } + .emit(Token::Op(Range)) + } + } + impl> Iterator for Lexer { + type Item = Token; + fn next(&mut self) -> Option { + match self.sync().peek()? { + // '{' => self.take().emit(Op(CurlyOpen)), + '[' => self.take().emit(Op(BrackOpen)), + '(' => self.take().emit(Op(ParenOpen)), + // '}' => self.take().emit(Op(CurlyClose)), + ']' => self.take().emit(Op(BrackClose)), + ')' => self.take().emit(Op(ParenClose)), + + '*' => self.take().emit(Op(Mul)), + '/' => self.take().emit(Op(Div)), + '%' => self.take().emit(Op(Rem)), + '+' => self.take().emit(Op(Add)), + '-' => self.take().emit(Op(Sub)), + '&' => self.take().emit(Op(And)), + '|' => self.take().emit(Op(Or)), + '^' => self.take().emit(Op(Xor)), + ',' => self.take().emit(Op(Comma)), + '=' => self.take().emit(Op(Eq)), + '>' => self.take().chain('>', Op(Shr)), + '<' => self.take().chain('<', Op(Shl)), + '.' => self.take().range(), // special handling allows for one or two dots + + '?' => self.take().emit(Token::Ident("regs".into())), + '$' | '#' => self.take().digits::<16>(), + '0' => self.take().base(), + '1'..='9' | 'A'..='F' => self.digits::<16>(), + c if c.is_alphabetic() => self.ident(), + + &other => self.take().emit(Other(other)), + } + } + } + } + + pub mod parser { + //! A [Parser] parses a sequence of Tokens and produces a sequence of [Request] + use super::token::Token; + use crate::ast::*; + use crate::message::Request; + use std::iter::Peekable; + + pub trait Parsible: Iterator + Sized { + fn parse(self) -> Parser; + } + impl + Sized> Parsible for T { + fn parse(self) -> Parser { + Parser::new(self) + } + } + + pub struct Parser> { + lexer: Peekable, + } + + impl> Parser { + pub fn new(t: T) -> Self { + Self { + lexer: t.peekable(), + } + } + #[must_use] + fn peek(&mut self) -> Option<&Token> { + self.lexer.peek() + } + fn take(&mut self) -> Option { + self.lexer.next() + } + #[must_use] + fn then(&mut self, tok: Token) -> Option<&mut Self> { + (*self.peek()? == tok).then(|| { + self.take(); + self + }) + } + } + + impl> Iterator for Parser { + type Item = Request; + fn next(&mut self) -> Option { + // match self.peek()? { + // Token::Op(_) => todo!(), + // Token::Num(_) => todo!(), + // Token::Bool(_) => todo!(), + // Token::Reg(_) => todo!(), + // Token::Ident(_) => todo!(), + // Token::Other(_) => todo!(), + // } + Some(Request::Do(Stmt::parse(self)?)) + } + } + + impl Stmt { + pub fn parse>(lexer: &mut Parser) -> Option { + let verb = Verb::parse(lexer)?; + let [dst, src] = match verb { + Verb::Set => [ + Expr::parse(lexer, 0)?, + Expr::parse(lexer.then(Token::Op(Op::Comma))?, 0)?, + ], + Verb::Get + | Verb::Run + | Verb::Break + | Verb::Unbreak + | Verb::Eval + | Verb::Disasm => [Expr::parse(lexer, 0)?, Expr::default()], + Verb::DumpRegs | Verb::DumpCart | Verb::Trace | Verb::Reset | Verb::Help => { + [Expr::default(), Expr::default()] + } + }; + Some(Stmt { verb, dst, src }) + } + } + + impl Verb { + pub fn parse>(lexer: &mut Parser) -> Option { + let Some(Token::Ident(id)) = (match lexer.peek()? { + Token::Ident(_) => lexer.take(), + Token::Op(Op::Eq) => { + lexer.take(); + return Some(Verb::Eval); + } + _ => return Some(Verb::Get), + }) else { + unreachable!() + }; + Some(match id.as_str() { + "set" | "write" => Verb::Set, + "get" | "read" | "dump" => Verb::Get, + "run" | "step" => Verb::Run, + "br" | "break" => Verb::Break, + "ub" | "unbreak" => Verb::Unbreak, + "di" | "dis" => Verb::Disasm, + "eval" => Verb::Eval, + "regs" => Verb::DumpRegs, + "cart" => Verb::DumpCart, + "trace" => Verb::Trace, + "reset" => Verb::Reset, + "help" => Verb::Help, + _ => None?, + }) + } + } + + impl Expr { + pub fn parse>(p: &mut Parser, level: u8) -> Option { + let mut head = match p.take()? { + Token::Reg(r) => Expr::Reg(r), + Token::Num(n) => Expr::Int(n as _), + Token::Bool(b) => Expr::Bool(b), + Token::Op(Op::ParenOpen) => { + let head = Expr::parse(p, 0)?; + if p.then(Token::Op(Op::ParenClose)).is_none() { + println!("Unterminated parentheses!") + } + head + } + Token::Op(Op::BrackOpen) => { + let mut exprs = vec![]; + while p.then(Token::Op(Op::BrackClose)).is_none() { + exprs.push(Expr::parse(p, 0)?); + if p.then(Token::Op(Op::Comma)).is_none() { + break; + } + } + if p.then(Token::Op(Op::BrackClose)).is_none() { + println!("Unterminated brackets in Array!"); + return None; + }; + Expr::Array(exprs) + } + Token::Op(Op::Mul) => { + let after = Op::Mul.prefix()?.after(); + Expr::Index(Box::new(Expr::parse(p, after)?), Box::new(Expr::Int(0))) + } + Token::Op(op) => { + let after = op.prefix()?.after(); + Expr::Prefix(op, Box::new(Expr::parse(p, after)?)) + } + tok => { + println!("Unexpected token: {tok}"); + None? + } + }; + + while let Some(&Token::Op(op)) = p.peek() { + if let Some(prec) = op.postfix() { + if prec.before() < level { + break; + } + p.take(); + head = match op { + Op::BrackOpen => { + let tail = Expr::parse(p, 0)?; + if p.then(Token::Op(Op::BrackClose)).is_none() { + println!("Unterminated brackets in Index!"); + return None; + }; + Expr::Index(Box::new(head), Box::new(tail)) + } + op => Expr::Postfix(op, Box::new(head)), + }; + continue; + } + if let Some(prec) = op.infix() { + if prec.before() < level { + break; + } + p.take(); + let tail = Expr::parse(p, prec.after())?; + head = match op { + Op::Range => Expr::Range(head.into(), tail.into()), + _ => Expr::Binary(op, Box::new(head), Box::new(tail)), + }; + continue; + } + break; + } + Some(head) + } + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub enum Level { + Postfix, + Range, + Array, + Bit, + Factor, + Shift, + Term, + Sign, + } + impl Level { + fn level(self) -> u8 { + (self as u8) << 1 + } + pub fn before(self) -> u8 { + self.level() + } + pub fn after(self) -> u8 { + self.level() + 1 + } + } + + pub trait Precedence { + fn prefix(&self) -> Option; + fn infix(&self) -> Option; + fn postfix(&self) -> Option; + } + + impl Precedence for Op { + fn prefix(&self) -> Option { + match self { + Op::Mul | Op::Add | Op::Sub | Op::Bang => Some(Level::Sign), + Op::Range => Some(Level::Range), + Op::BrackOpen => Some(Level::Array), + _ => None, + } + } + + fn infix(&self) -> Option { + match self { + Op::Mul | Op::Div | Op::Rem => Some(Level::Term), + Op::Add | Op::Sub => Some(Level::Factor), + Op::Shr | Op::Shl => Some(Level::Shift), + Op::And | Op::Or | Op::Xor => Some(Level::Bit), + Op::Bang => None, + Op::Range => Some(Level::Range), + _ => None, + } + } + + fn postfix(&self) -> Option { + match self { + Op::BrackOpen => Some(Level::Postfix), + _ => None, + } + } + } + } +} + +pub mod ast { + pub use crate::cli::token::{Op, Reg}; + use std::{fmt::Display, mem::take}; + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct Stmt { + pub verb: Verb, + pub dst: Expr, + pub src: Expr, + } + + impl Stmt { + pub fn visit(&mut self, f: &impl Fn(&mut Expr)) { + self.dst.visit(f); + self.dst.visit(f); + } + + /// Solves the statement arguments using the provided solver + pub fn solve(&mut self, solver: &impl Fn(&mut Expr) -> Option) -> &mut Self { + self.visit(&|expr| { + expr.solve(solver); + }); + self + } + } + impl Display for Stmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { verb, dst, src } = self; + match verb { + Verb::Set => write!(f, "{verb} {dst}, {src}"), + Verb::DumpRegs | Verb::DumpCart | Verb::Trace | Verb::Reset | Verb::Help => { + write!(f, "{verb}") + } + _ => write!(f, "{verb} {dst}"), + } + } + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Verb { + Set, + Get, + Run, + Break, + Unbreak, + Eval, + Disasm, + DumpRegs, + DumpCart, + Trace, + Reset, + Help, + } + + impl Display for Verb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Verb::Set => "set", + Verb::Get => "get", + Verb::Run => "run", + Verb::Break => "break", + Verb::Unbreak => "unbreak", + Verb::Eval => "eval", + Verb::Disasm => "dis", + Verb::DumpRegs => "regs", + Verb::DumpCart => "cart", + Verb::Trace => "trace", + Verb::Reset => "reset", + Verb::Help => "help", + } + .fmt(f) + } + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Expr { + Int(isize), + Bool(bool), + Reg(Reg), + Prefix(Op, Box), + Binary(Op, Box, Box), + Postfix(Op, Box), + Array(Vec), + Index(Box, Box), + Range(Box, Box), + } + + impl Expr { + /// Traverses the expression depth-first, calling function f() on every subexpression + pub fn visit(&mut self, f: &impl Fn(&mut Expr)) { + match self { + Expr::Int(_) | Expr::Bool(_) | Expr::Reg(_) => {} + Expr::Array(v) => v.iter_mut().for_each(|e| e.visit(f)), + Expr::Prefix(_, e) | Expr::Postfix(_, e) => e.visit(f), + Expr::Binary(_, a, b) | Expr::Range(a, b) | Expr::Index(a, b) => { + [a, b].into_iter().for_each(|e| e.visit(f)) + } + } + f(self) + } + + /// Replaces the expression with a solution produced by the solver. + pub fn solve(&mut self, solver: impl Fn(&mut Expr) -> Option) -> &mut Self { + if let Some(value) = solver(self) { + *self = value; + } + self + } + + /// Gets the value of an [integer expression](Expr::Int) + pub fn int(&self) -> Option { + match self { + &Expr::Int(i) => Some(i), + _ => None, + } + } + /// Gets the value of a [bool expression](Expr::Bool) + pub fn bool(&self) -> Option { + match self { + &Expr::Bool(b) => Some(b), + _ => None, + } + } + /// Gets the value of a [register expression](Expr::Reg) + pub fn reg(&self) -> Option { + match self { + &Expr::Reg(r) => Some(r), + _ => None, + } + } + + pub fn int_range(&self) -> Option> { + match self { + Expr::Range(start, end) => Some(start.int()?..end.int()?), + _ => None, + } + } + + /// Attempts to solve a top-level expression using basic arithmetic rules + pub fn eval_one(&mut self) -> Option { + match self { + Expr::Int(_) | Expr::Bool(_) | Expr::Reg(_) => None, + Expr::Array(_) | Expr::Range(_, _) => None, + Expr::Prefix(op, e) => e.eval_prefix(*op), + Expr::Binary(op, lhs, rhs) => lhs.eval_binary(*op, rhs), + Expr::Postfix(_, _) => todo!("Postfix expression: {self}"), + Expr::Index(a, idx) => a.eval_index(idx), + } + } + + fn eval_prefix(&self, op: Op) -> Option { + Some(match (op, self) { + (Op::Add, Expr::Int(v)) => Expr::Int(*v), + (Op::Sub, Expr::Int(v)) => Expr::Int(-v), + (Op::Bang, Expr::Int(v)) => Expr::Int(!v), + (Op::Bang, Expr::Bool(v)) => Expr::Bool(!v), + _ => return None, + }) + } + + fn eval_binary(&self, op: Op, rhs: &Self) -> Option { + use Expr::*; + Some(match (op, self, rhs) { + (Op::Mul, Int(lhs), Int(rhs)) => Int(lhs * rhs), + (Op::Div, Int(lhs), Int(rhs)) => Int(lhs / rhs), + (Op::Rem, Int(lhs), Int(rhs)) => Int(lhs % rhs), + (Op::Add, Int(lhs), Int(rhs)) => Int(lhs + rhs), + (Op::Sub, Int(lhs), Int(rhs)) => Int(lhs - rhs), + (Op::Shr, Int(lhs), Int(rhs)) => Int(lhs >> rhs), + (Op::Shl, Int(lhs), Int(rhs)) => Int(lhs << rhs), + (Op::And, Int(lhs), Int(rhs)) => Int(lhs & rhs), + (Op::And, Bool(lhs), Bool(rhs)) => Bool(lhs & rhs), + (Op::Or, Int(lhs), Int(rhs)) => Int(lhs | rhs), + (Op::Or, Bool(lhs), Bool(rhs)) => Bool(lhs | rhs), + (Op::Xor, Int(lhs), Int(rhs)) => Int(lhs ^ rhs), + (Op::Xor, Bool(lhs), Bool(rhs)) => Bool(lhs ^ rhs), + _ => return None, + }) + } + /// Evaluates an index expression: a[10] + fn eval_index(&mut self, idx: &Self) -> Option { + match (self, idx) { + (Expr::Array(a), &Expr::Int(idx)) if a.len() > idx as _ => { + Some(take(a).swap_remove(idx as _)) + } + _ => None, + } + } + } + + impl Default for Expr { + fn default() -> Self { + Expr::Bool(false) + } + } + + impl Display for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expr::Int(i) => write!(f, "{i:02x}"), + Expr::Bool(b) => b.fmt(f), + Expr::Reg(r) => r.fmt(f), + Expr::Prefix(op, e) => write!(f, "{op}{e}"), + Expr::Binary(op, a, b) => write!(f, "{a} {op} {b}"), + Expr::Postfix(op, e) => write!(f, "{e}{op}"), + Expr::Array(v) => { + write!(f, "[")?; + for (idx, e) in v.iter().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + e.fmt(f)? + } + write!(f, "]") + } + Expr::Range(a, b) => write!(f, "{a}..{b}"), + Expr::Index(a, i) => write!(f, "{a}[{i}]"), + } + } + } + + pub enum SubExprIter { + Once(std::iter::Once), + Many(std::vec::IntoIter), + } + + impl Iterator for SubExprIter { + type Item = Expr; + fn next(&mut self) -> Option { + match self { + SubExprIter::Once(iter) => iter.next(), + SubExprIter::Many(iter) => iter.next(), + } + } + } + impl IntoIterator for Expr { + type Item = Expr; + + type IntoIter = SubExprIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + Expr::Int(_) + | Expr::Bool(_) + | Expr::Reg(_) + | Expr::Prefix(_, _) + | Expr::Binary(_, _, _) + | Expr::Postfix(_, _) + | Expr::Range(_, _) + | Expr::Index(_, _) => SubExprIter::Once(std::iter::once(self)), + Expr::Array(a) => SubExprIter::Many(a.into_iter()), + } + } + } +} + +pub mod message { + //! Represents all possible debugger commands, and their return values + use crate::ast::Stmt; + use std::fmt::Display; + + /// A request sent to the emulated gameboy + #[derive(Clone, Debug, PartialEq, Eq, Hash)] + pub enum Request { + Do(Stmt), + } + + /// A response returned from the emulated Gameboy + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Response { + /// The request executed successfully + Success, + /// The request failed + Failure, + /// The data + Data(usize), + } + + impl Display for Request { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Request::Do(stmt) => write!(f, "Execute `{stmt}`"), + } + } + } +} + +pub mod gameboy { + use std::io::Write; + + use boy::cpu::CPU; + use boy::memory::io::*; + use boy_utils::*; + + use crate::{ + ast::{Expr, Reg, Stmt, Verb}, + bus::BusIOTools, + disassembler::Disassemble, + message::{Request, Response}, + }; + + #[derive(Clone, Debug)] + pub struct Gameboy { + trace: bool, + cpu: CPU, + bus: T, + } + + impl Gameboy { + pub fn new(bus: T) -> Self { + Self { + trace: false, + cpu: CPU::new(), + bus, + } + } + pub fn process(&mut self, message: Request) -> Response { + match message { + Request::Do(stmt) => self.interpret(stmt), + } + } + + fn hexdump(&self, start: isize, end: isize) -> Response { + let (start, end) = (start as usize, end as usize); + match bus_hexdump(&self.bus, start..end) { + Ok(_) => Response::Success, + Err(_) => Response::Failure, + } + } + + fn disasm(&self, start: isize, end: isize, lim: usize) -> Response { + let (start, end) = (start as _, end as _); + self.bus + .read_iter_from(start) + .disassemble() + .take(lim) + .filter(|(idx, _)| start + idx <= end) + .for_each(|(idx, insn)| println!("{:04x}: {}", start + idx, insn)); + Response::Success + } + + pub fn interpret(&mut self, mut stmt: Stmt) -> Response { + // Solve statement args + if let Verb::Set = stmt.verb { + stmt.src.solve(|expr| self.substitute(expr)); + } else { + stmt.solve(&|expr| self.substitute(expr)); + } + + // Intrepret basic statements + let Stmt { verb, dst, src } = stmt.solve(&Expr::eval_one); + match (verb, dst, src) { + (Verb::Set, Expr::Int(addr), Expr::Int(data)) => { + self.bus.write(*addr as _, *data as _); + } + (Verb::Set, Expr::Reg(reg), Expr::Int(data)) => { + set_register(&mut self.cpu, *reg, *data as _); + } + (Verb::Get, Expr::Int(addr), ..) => return self.hexdump(*addr, *addr + 0x10), + (Verb::Get, range @ Expr::Range(..), ..) => match range.int_range() { + Some(range) => return self.hexdump(range.start, range.end), + None => return Response::Failure, + }, + (Verb::Run, Expr::Int(step), ..) => return self.step(*step as _), + (Verb::Break, Expr::Int(addr), ..) => { + if self.cpu.set_break(*addr as _) { + println!("Set breakpoint {addr:04x}") + } + } + (Verb::Unbreak, Expr::Int(addr), ..) => { + if self.cpu.unset_break(*addr as _) { + println!("Unset breakpoint {addr:04x}") + } + } + (Verb::Disasm, Expr::Int(addr), ..) => { + return self.disasm(*addr, 0x10000, 8); + } + (Verb::Disasm, range @ Expr::Range(..), ..) => match range.int_range() { + Some(range) => return self.disasm(range.start, range.end, usize::MAX), + None => return Response::Failure, + }, + (Verb::Eval, expr, ..) => { + println!(" = {expr}"); + return Response::Success; + } + (Verb::DumpRegs, ..) => println!("{}", self.cpu), + (Verb::DumpCart, ..) => bus_info(&self.bus), + (Verb::Trace, ..) => self.trace ^= true, + (Verb::Reset, ..) => self.cpu = CPU::new(), + (Verb::Help, ..) => todo!("Help"), + _ => {} + } + + // Perform iteration and pairing + let Stmt { verb, dst, src } = stmt; + match (dst, src) { + // Array/range-to-array/range operations are done on zipped arguments + (Expr::Array(dst), Expr::Array(src)) => { + for (dst, src) in dst.into_iter().zip(src) { + self.interpret(Stmt { verb, dst, src }); + } + } + (Expr::Array(dst), src @ Expr::Range(..)) => { + let Some(src) = src.int_range() else { + return Response::Failure; + }; + for (dst, src) in dst.into_iter().zip(src) { + let src = Expr::Int(src); + self.interpret(Stmt { verb, dst, src }); + } + } + (dst @ Expr::Range(..), Expr::Array(src)) => { + let Some(dst) = dst.int_range() else { + return Response::Failure; + }; + for (dst, src) in dst.into_iter().zip(src) { + let dst = Expr::Int(dst); + self.interpret(Stmt { verb, dst, src }); + } + } + (dst @ Expr::Range(..), src @ Expr::Range(..)) => { + let (Some(dst), Some(src)) = (dst.int_range(), src.int_range()) else { + return Response::Failure; + }; + for (dst, src) in dst.into_iter().zip(src) { + let (dst, src) = (Expr::Int(dst), Expr::Int(src)); + self.interpret(Stmt { verb, dst, src }); + } + } + // Arrays get copied to an absolute address + (Expr::Int(addr), Expr::Array(src)) => { + for (idx, src) in src.into_iter().enumerate() { + let dst = Expr::Int(addr + idx as isize); + self.interpret(Stmt { verb, dst, src }); + } + } + // Ranges get evaluated to a contiguous position + (Expr::Int(addr), src @ Expr::Range(..)) => { + let Some(src) = src.int_range() else { + return Response::Failure; + }; + for (idx, src) in src.into_iter().enumerate() { + let (dst, src) = (Expr::Int(addr + idx as isize), Expr::Int(src)); + self.interpret(Stmt { verb, dst, src }); + } + } + // Arrays in dst position are iterated over + (Expr::Array(dst), src) => { + for dst in dst { + let src = src.clone(); + self.interpret(Stmt { verb, dst, src }); + } + } + // Ranges in dst position are iterated over + (dst @ Expr::Range(..), src) => { + let Some(dst) = dst.int_range() else { + return Response::Failure; + }; + for dst in dst { + let (dst, src) = (Expr::Int(dst), src.clone()); + self.interpret(Stmt { verb, dst, src }); + } + } + _ => {} + } + + Response::Success + } + + fn substitute(&self, expr: &Expr) -> Option { + Some(match expr { + Expr::Reg(r) => Expr::Int(get_register(&self.cpu, *r) as _), + Expr::Index(a, idx) => { + let (a, idx) = (a.int()?, idx.int()?); + Expr::Int(self.bus.read((a + idx) as _).unwrap_or(0xff) as _) + } + _ => None?, + }) + } + + fn step(&mut self, steps: usize) -> Response { + let Self { trace, cpu, bus } = self; + for step in 1..=steps { + let (loc, insn) = (cpu.pc(), cpu.ir()); + match match trace { + true => cpu.run(&mut bus.trace()), + false => cpu.run(bus), + } { + Ok(cycles) => { + if insn == boy::cpu::disasm::Insn::Nop { + continue; + } + if steps < 100 { + print!( + "{loc:04x}: [{insn:?}]\x1b[23GTook {cycles} cycle{}\x1b[50G", + if cycles == 1 { "" } else { "s" } + ); + } + if steps < 500 { + bus.read_iter_from(loc.wrapping_sub(1) as usize) + .disassemble() + .map(|(_, s)| println!("{:04x}: {s}", loc.wrapping_sub(1))) + .next(); + let _ = std::io::stdout().flush(); + } + } + Err(e) => { + println!( + "\n{loc:04x}: [{insn:?}]\x1b[23G{insn}\x1b[50G\x1b[31mStep {step:x}: {e}\x1b[0m" + ); + return Response::Failure; + } + } + } + println!(); + bus.diag(0); + Response::Success + } + } + + fn get_register(cpu: &CPU, reg: Reg) -> usize { + use boy::cpu::disasm::{R16Stack, R16, R8}; + match reg { + Reg::F => cpu.flags().into_bits() as _, + Reg::A => cpu[R8::A].0 as _, + Reg::B => cpu[R8::B].0 as _, + Reg::C => cpu[R8::C].0 as _, + Reg::D => cpu[R8::D].0 as _, + Reg::E => cpu[R8::E].0 as _, + Reg::H => cpu[R8::H].0 as _, + Reg::L => cpu[R8::L].0 as _, + Reg::AF => cpu[R16Stack::AF].0 as _, + Reg::BC => cpu[R16::BC].0 as _, + Reg::DE => cpu[R16::DE].0 as _, + Reg::HL => cpu[R16::HL].0 as _, + Reg::SP => cpu[R16::SP].0 as _, + Reg::PC => cpu.pc() as _, + } + } + + fn set_register(cpu: &mut CPU, reg: Reg, value: u16) { + use boy::cpu::{ + disasm::{R16Stack, R16, R8}, + Flags, + }; + match reg { + Reg::F => *cpu.flags_mut() = Flags::from_bits(value as _), + Reg::A => cpu[R8::A].0 = value as _, + Reg::B => cpu[R8::B].0 = value as _, + Reg::C => cpu[R8::C].0 = value as _, + Reg::D => cpu[R8::D].0 = value as _, + Reg::E => cpu[R8::E].0 = value as _, + Reg::H => cpu[R8::H].0 = value as _, + Reg::L => cpu[R8::L].0 = value as _, + Reg::AF => cpu[R16Stack::AF].0 = value, + Reg::BC => cpu[R16::BC].0 = value, + Reg::DE => cpu[R16::DE].0 = value, + Reg::HL => cpu[R16::HL].0 = value, + Reg::SP => cpu[R16::SP].0 = value, + Reg::PC => cpu.set_pc(value), + } + } + + fn bus_info(bus: &impl BusIO) { + println!("Entry: {:02x?}", bus.entry()); + println!("Logo: "); + let _ = match bus.logo() { + Ok(logo) | Err(logo) => bus_hexdump(&logo, 0..0x30), + }; + println!("Title: {:02x?}", bus.title()); + println!("MFCode: {:02x?}", bus.mfcode()); + println!("CGB Flag: {:02x?}", bus.cgbflag()); + println!("New Lic: {:02x?}", bus.newlicensee()); + println!("SGB Flag: {:02x?}", bus.sgbflag()); + println!("Cart Type: {:02x?}", bus.carttype()); + println!("ROM Size: {:02x?}", bus.romsize()); + println!("RAM Size: {:02x?}", bus.ramsize()); + println!("Dest Code: {:02x?}", bus.destcode()); + println!("Old Lic: {:02x?}", bus.oldlicencee()); + println!("ROM Ver: {:02x?}", bus.maskromver()); + println!("H Cksum: {:02x?}", bus.headercksum()); + println!("G Cksum: {:04x?}", bus.globalcksum()) + } +} diff --git a/boy-debug/src/main.rs b/boy-debug/src/main.rs new file mode 100644 index 0000000..7fd6061 --- /dev/null +++ b/boy-debug/src/main.rs @@ -0,0 +1,39 @@ +//! Debug interface for the Boy emulator +#![allow(unused_imports)] + +use boy::memory::{Bus, Cart}; +use boy_debug::{ + bus::BusIOTools, + cli::{lexer::Lexible, parser::Parsible}, + gameboy::Gameboy, + message::Response, +}; +use rustyline::{config::Configurer, history::FileHistory, Editor}; +use std::error::Error; + +fn main() -> Result<(), Box> { + // Set up the gameboy + // TODO: move off the main thread + let args: Vec<_> = std::env::args().collect(); + let mut bus = match args.len() { + 2 => vec![0; 0x10000].read_file(&args[1])?, + _ => return Err(format!("Usage: {} [rom.gb]", args[0]).into()), + }; + // let mut bus = Bus::new(Cart::new(bus)); + let mut gb = Gameboy::new(bus.ascii()); + + // Set up and run the REPL + let mut rl: Editor<(), FileHistory> = Editor::new()?; + rl.set_auto_add_history(true); + while let Ok(line) = rl.readline("\x1b[96mgb>\x1b[0m ") { + for request in line.chars().lex().parse() { + // TODO: Process requests in another thread, maybe + match gb.process(request) { + Response::Success => {} + Response::Failure => println!(":("), + Response::Data(data) => println!(" => 0x{data:02x} ({data})"), + } + } + } + Ok(()) +} diff --git a/boy-utils/Cargo.toml b/boy-utils/Cargo.toml new file mode 100644 index 0000000..466eef7 --- /dev/null +++ b/boy-utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "boy-utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +boy = { path = ".." } diff --git a/boy-utils/src/lib.rs b/boy-utils/src/lib.rs new file mode 100644 index 0000000..db28cdb --- /dev/null +++ b/boy-utils/src/lib.rs @@ -0,0 +1,69 @@ +/// Utilities and common behavior for the [boy] emulator +use std::io::Write; +use std::ops::Range; + +use boy::memory::io::*; + +/// Formats the data over a given range as a traditional hexdump +pub fn slice_hexdump(data: &[u8], range: Range) -> std::io::Result<()> { + const WIDTH: usize = 16; + const HEX: usize = 6; + const ASCII: usize = 3 * (WIDTH + 1) + HEX; + + let base = range.start; + let mut out = std::io::stdout().lock(); + + let Some(data) = data.get(range) else { + return Ok(()); + }; + + for (chunkno, chunk) in data.chunks(WIDTH).enumerate() { + write!(out, "{:04x}\x1b[{HEX}G", base + chunkno * WIDTH)?; + for (byteno, byte) in chunk.iter().enumerate() { + write!(out, "{}{byte:02x}", if byteno == 8 { " " } else { " " })? + } + write!(out, "\x1b[{ASCII}G|")?; + for byte in chunk.iter() { + match char::from_u32(*byte as u32) { + Some(c) if c.is_ascii_graphic() => write!(out, "{c}")?, + Some(_) | None => write!(out, "∙")?, + } + } + writeln!(out, "|")? + } + Ok(()) +} + +pub fn bus_hexdump(bus: &impl BusIO, range: Range) -> std::io::Result<()> { + use boy::memory::io::BusAux; + const WIDTH: usize = 16; + + let mut out = std::io::stdout(); + + let upper = range.end; + for lower in range.clone().step_by(WIDTH) { + let upper = (lower + WIDTH).min(upper); + // write!(out, "{:04x}\x1b[{HEX}G", lower)?; + let _data: [u8; WIDTH] = bus.read_arr(lower).unwrap_or_else(|e| e); + write!(out, "{:04x} ", lower)?; + // write hexadecimal repr + for (idx, addr) in (lower..upper).enumerate() { + let space = if idx == WIDTH / 2 { " " } else { " " }; + match bus.read(addr) { + Some(data) => write!(out, "{space}{data:02x}"), + None => write!(out, "{space}__"), + }?; + } + let padding = WIDTH - (upper - lower); + let padding = padding * 3 + if padding >= (WIDTH / 2) { 1 } else { 0 }; + write!(out, "{:padding$} |", "")?; + for data in (lower..upper).flat_map(|addr| bus.read(addr)) { + match char::from_u32(data as u32) { + Some(c) if !c.is_control() => write!(out, "{c}"), + _ => write!(out, "."), + }?; + } + writeln!(out, "|")?; + } + Ok(()) +} diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..f468977 --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,1048 @@ +//! The [CPU] executes [instructions](Insn), reading and writing from a [`BusIO`] device +use self::{ + disasm::{Cond, Insn, Prefixed, R16Indirect, R16Stack, R16, R8}, + split_register::SplitRegister, +}; +use crate::{ + error::{Error, ErrorKind::*}, + memory::io::BusIO, +}; +use bitfield_struct::bitfield; +use std::{ + collections::BTreeSet, + fmt::Display, + num::Wrapping, + ops::{Index, IndexMut}, +}; + +pub mod disasm; + +pub mod split_register; + +#[bitfield(u8, order = Msb)] +/// Stores the flags +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Flags { + z: bool, + n: bool, + h: bool, + c: bool, + #[bits(4)] + _reserved: (), +} + +/// Performs the half-carry calculation for addition +fn half_carry_add(a: u8, b: u8) -> bool { + (a & 0xf) + (b & 0xf) > 0xf +} +/// Performs the half-carry calculation for subtraction +fn half_carry_sub(a: u8, b: u8) -> bool { + (a & 0xf) < (b & 0xf) +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] +pub enum Ime { + #[default] + Disabled, + ShouldEnable, + Enabled, +} + +/// Processes interrupts, executes, fetches and decodes instructions +#[derive(Clone, Debug, Default, PartialEq, PartialOrd)] +pub struct CPU { + /// The last-fetched instruction + ir: Insn, + /// The Stack Pointer stores the location of the program stack + sp: Wrapping, + /// The Program Counter increments for each byte of an instruction read + pc: Wrapping, + /// Combines the Accumulator and Flags registers + af: SplitRegister, + /// Combines the B and C registers + bc: SplitRegister, + /// Combines the D and E registers + de: SplitRegister, + /// Combines the H and L registers + hl: SplitRegister, + /// The number of processor cycles executed in the last instruction + m_cycles: u8, + /// [Interrupt Master Enable](https://gbdev.io/pandocs/Interrupts.html#ime-interrupt-master-enable-flag-write-only) + ime: Ime, + /// The set of breakpoints + breakpoints: BTreeSet, +} + +/// Lifecycle management +impl CPU { + /// Initializes a new [CPU] + /// + /// This sets up the registers [as if it were a GBA][CPU Registers] + /// + /// [CPU Registers]: https://gbdev.io/pandocs/Power_Up_Sequence.html#cpu-registers + pub fn new() -> Self { + let mut out = Self::default(); + out.af.wide_mut().0 = 0x1100 | Flags::new().with_z(true).into_bits() as u16; + out.bc.wide_mut().0 = 0x0100; + out.de.wide_mut().0 = 0xff56; + out.hl.wide_mut().0 = 0x000d; + out.pc.0 = 0x0100; + out.sp.0 = 0xfffe; + out + } +} + +/// Getters for private data +impl CPU { + /// Gets the address of the next instruction + pub fn pc(&self) -> u16 { + self.pc.0 + } + /// Sets the address of the next instruction + pub fn set_pc(&mut self, addr: u16) { + self.pc.0 = addr + } + /// Gets the current instruction + pub fn ir(&self) -> Insn { + self.ir + } + /// Sets a breakpoint. Returns true if the breakpoint was not already set. + pub fn set_break(&mut self, addr: u16) -> bool { + self.breakpoints.insert(addr) + } + /// Removes a breakpoint. Returns true if the breakpoint was set. + pub fn unset_break(&mut self, addr: u16) -> bool { + self.breakpoints.remove(&addr) + } +} + +/// Bus operations +impl CPU { + /// Read an 8-bit immediate at the program counter, post-incrementing it (adds one cycle) + pub fn imm8(&mut self, bus: &impl BusIO) -> Result { + let out = self.read(self.pc.0, bus)?; + self.pc += 1; + Ok(out) + } + /// Read a 16-bit immediate at the program counter, post-incrementing it twice (adds two cycles) + pub fn imm16(&mut self, bus: &impl BusIO) -> Result { + let out = self.read16(self.pc.0, bus)?; + self.pc += 2; + Ok(out) + } + /// Read an 8-bit value at address `addr` on the `bus` (adds one cycle) + pub fn read(&mut self, addr: u16, bus: &impl BusIO) -> Result { + // Reading a byte takes one cycle + self.wait(); + bus.read(addr as usize).ok_or(InvalidAddress(addr).into()) + } + /// Read a 16-bit LE value at address `addr` on the `bus` (adds two cycles) + pub fn read16(&mut self, addr: u16, bus: &impl BusIO) -> Result { + let out = self.read(addr, bus)? as u16; + Ok(out | (self.read(addr.wrapping_add(1), bus)? as u16) << 8) + } + + /// Write an 8-bit value to address `addr` on the `bus` (adds one cycle) + pub fn write(&mut self, addr: u16, data: u8, bus: &mut impl BusIO) -> Result<(), Error> { + // Writing a byte takes one cycle + self.wait(); + bus.write(addr as usize, data) + .ok_or(InvalidAddress(addr).into()) + } + /// Write a 16-bit LE value to address `addr..=addr+1` on the `bus` (adds two cycles) + pub fn write16(&mut self, addr: u16, data: u16, bus: &mut impl BusIO) -> Result<(), Error> { + let data = data.to_le_bytes(); + self.write(addr, data[0], bus)?; + self.write(addr.wrapping_add(1), data[1], bus) + } + + /// Push a byte onto the stack (adds one cycle) + pub fn push(&mut self, data: u8, bus: &mut impl BusIO) -> Result<(), Error> { + self.sp -= 1; + self.write(self.sp.0, data, bus) + } + /// Push a 16-bit value onto the stack (adds two cycles) + pub fn push16(&mut self, data: u16, bus: &mut impl BusIO) -> Result<(), Error> { + let data = data.to_le_bytes(); + self.push(data[0], bus)?; + self.push(data[1], bus) + } + /// Pop a byte off the stack (adds one cycle) + pub fn pop(&mut self, bus: &mut impl BusIO) -> Result { + let out = self.read(self.sp.0, bus); + self.sp += 1; + out + } + /// Pop a double word off the stack (adds two cycles) + pub fn pop16(&mut self, bus: &mut impl BusIO) -> Result { + // pushes LH, pops HL + Ok(u16::from_be_bytes([self.pop(bus)?, self.pop(bus)?])) + } + + /// Jump to a memory address (adds one cycle) + pub fn jumpto(&mut self, addr: u16) { + // Jumping takes a cycle + self.wait().pc.0 = addr; + } + + /// Waits one M-Cycle (does not delay) + pub fn wait(&mut self) -> &mut Self { + self.m_cycles += 1; + self + } +} + +/// Interrupts +impl CPU { + pub fn service_irq(&mut self, bus: &mut impl BusIO) -> Result<(), Error> { + const IRQ_HANDLERS: [u16; 5] = [0x40, 0x48, 0x50, 0x58, 0x60]; + if self.ime != Ime::Enabled { + return Ok(()); + } + + let enabled = self.read(0xffff, bus)?; + let interrupt = (enabled & self.read(0xff0f, bus)?).trailing_zeros() as usize; + if interrupt < IRQ_HANDLERS.len() { + eprintln!("Interrupt requested: {interrupt}"); + self.di()?; + self.jumpto(IRQ_HANDLERS[interrupt]); + } + Ok(()) + } +} + +mod instructions { + use super::*; + + type IResult = std::result::Result<(), Error>; + + /// Control instructions (Nop, Stop, Halt, D/EI) + impl CPU { + pub fn nop(&mut self) -> IResult { + Ok(()) + } + pub fn stop(&mut self) -> IResult { + Err(UnimplementedInsn(self.ir).into()) + } + pub fn halt(&mut self) -> IResult { + Ok(()) + } + pub fn di(&mut self) -> IResult { + self.ime = Ime::Disabled; + Ok(()) + } + pub fn ei(&mut self) -> IResult { + // TODO: this is incorrect + self.ime = Ime::ShouldEnable; + Ok(()) + } + } + /// Loads and stores + impl CPU { + pub fn ld(&mut self, dst: R8, src: R8, bus: &mut impl BusIO) -> IResult { + let src = self.get_r8(src, bus); + self.set_r8(dst, src, bus); + Ok(()) + } + pub fn ld_imm(&mut self, dst: R8, bus: &mut impl BusIO) -> IResult { + // TODO: open bus behavior + let src = self.imm8(bus)?; + self.set_r8(dst, src, bus); + Ok(()) + } + pub fn ld_imm16(&mut self, dst: R16, bus: &mut impl BusIO) -> IResult { + let imm16 = self.imm16(bus)?; + self[dst].0 = imm16; + Ok(()) + } + pub fn ld_ind(&mut self, src: R16Indirect, bus: &mut impl BusIO) -> IResult { + self[R8::A].0 = self.read_r16ind(src, bus)?; + Ok(()) + } + pub fn st_ind(&mut self, dst: R16Indirect, bus: &mut impl BusIO) -> IResult { + self.write_r16ind(dst, self[R8::A].0, bus); + Ok(()) + } + pub fn ldc(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self[R8::C].0 as u16 | 0xff00; + self[R8::A].0 = self.read(addr, bus)?; + Ok(()) + } + pub fn stc(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self[R8::C].0 as u16 | 0xff00; + self.write(addr, self[R8::A].0, bus)?; + Ok(()) + } + pub fn ld_abs(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.imm16(bus)?; + self[R8::A].0 = self.read(addr, bus)?; + Ok(()) + } + pub fn st_abs(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.imm16(bus)?; + self.write(addr, self[R8::A].0, bus)?; + Ok(()) + } + pub fn st_sp_abs(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.imm16(bus)?; + self.write16(addr, self[R16::SP].0, bus)?; + Ok(()) + } + pub fn ld_sp_hl(&mut self) -> IResult { + self[R16::HL].0 = self[R16::SP].0; + Ok(()) + } + pub fn ld_hl_sp_rel(&mut self, bus: &mut impl BusIO) -> IResult { + let offset = self.imm8(bus)? as i8 as i16 as u16; + self[R16::HL].0 = self.sp.0.wrapping_add(offset); + Ok(()) + } + pub fn ldh(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.imm8(bus)? as u16 | 0xff00; + self[R8::A].0 = self.read(addr, bus)?; + Ok(()) + } + pub fn sth(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.imm8(bus)? as u16 | 0xff00; + self.write(addr, self[R8::A].0, bus)?; + Ok(()) + } + } + /// Inc and Dec + impl CPU { + pub fn inc(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let (data, carry) = self.get_r8(reg, bus).overflowing_add(1); + self.set_r8(reg, data, bus); + *self.flags_mut() = Flags::new().with_z(data == 0).with_c(carry); + Ok(()) + } + pub fn dec(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let (data, carry) = self.get_r8(reg, bus).overflowing_sub(1); + self.set_r8(reg, data, bus); + *self.flags_mut() = Flags::new().with_z(data == 0).with_c(carry); + Ok(()) + } + pub fn inc16(&mut self, reg: R16) -> IResult { + self[reg] -= 1; + Ok(()) + } + pub fn dec16(&mut self, reg: R16) -> IResult { + self[reg] -= 1; + Ok(()) + } + } + /// Rotates and shifts + impl CPU { + pub fn rlnca(&mut self) -> IResult { + self[R8::A].0 = self[R8::A].0.rotate_left(1); + let carry = self[R8::A].0 & 1 != 0; + self.set_z(false).set_n(false).set_h(false).set_c(carry); + Ok(()) + } + pub fn rrnca(&mut self) -> IResult { + let carry = self[R8::A].0 & 1 != 0; + self[R8::A].0 = self[R8::A].0.rotate_right(1); + self.set_z(false).set_n(false).set_h(false).set_c(carry); + Ok(()) + } + pub fn rla(&mut self) -> IResult { + // TODO: optimize RLA/RRA + let carry = self[R8::A].0 & 0x80 != 0; + self[R8::A] <<= 1; + let prev_carry = self.c(); + self[R8::A] |= prev_carry as u8; + self.set_z(false).set_n(false).set_h(false).set_c(carry); + Ok(()) + } + pub fn rra(&mut self) -> IResult { + let carry = self[R8::A].0 & 1 != 0; + self[R8::A] >>= 1; + let prev_carry = self.c(); + self[R8::A] |= (prev_carry as u8) << 7; + self.set_z(false).set_n(false).set_h(false).set_c(carry); + Ok(()) + } + } + /// Funky instructions + impl CPU { + pub fn daa(&mut self) -> IResult { + Err(UnimplementedInsn(self.ir))? + } + pub fn cpl(&mut self) -> IResult { + self[R8::A] ^= 0xff; + self.set_n(false).set_h(false); + Ok(()) + } + pub fn scf(&mut self) -> IResult { + let flags = self.flags_mut(); + *flags = flags.with_n(false).with_h(false).with_c(true); + Ok(()) + } + pub fn ccf(&mut self) -> IResult { + let flags = self.flags_mut(); + *flags = flags.with_n(false).with_h(false).with_c(!flags.c()); + Ok(()) + } + } + /// Addition + impl CPU { + fn add_impl(&mut self, value: u8) -> IResult { + let hc = half_carry_add(self[R8::A].0, value); + let (out, carry) = self[R8::A].0.overflowing_add(value); + self[R8::A].0 = out; + self.set_z(out == 0).set_n(false).set_h(hc).set_c(carry); + Ok(()) + } + pub fn add(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let src = self.get_r8(reg, bus); + self.add_impl(src) + } + pub fn addi(&mut self, bus: &mut impl BusIO) -> IResult { + let src = self.imm8(bus)?; + self.add_impl(src)?; + Ok(()) + } + pub fn add16(&mut self, reg: R16) -> IResult { + let hl = self.hl.wide().0.to_le_bytes(); + let addn = self[reg].0.to_le_bytes(); + eprintln!("Add {hl:?} to {addn:?} and store the half- and full-carry flags"); + Ok(()) + } + #[allow(unused)] + pub fn add16_spi(&mut self, bus: &mut impl BusIO) -> IResult { + let offset = self.imm8(bus)? as i8 as i16; // sign extend + + Err(UnimplementedInsn(self.ir).into()) + } + } + /// Addition with carry + impl CPU { + fn addc_impl(&mut self, value: u8) -> IResult { + let (src, carry_in) = value.overflowing_add(self.c() as u8); + let hc = half_carry_add(self[R8::A].0, src); + let (out, carry) = self[R8::A].0.overflowing_add(src); + self[R8::A].0 = out; + self.set_z(out == 0) + .set_n(false) + .set_h(hc) + .set_c(carry | carry_in); + Ok(()) + } + pub fn adc(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let src = self.get_r8(reg, bus); + self.addc_impl(src) + } + pub fn adci(&mut self, bus: &mut impl BusIO) -> IResult { + let src = self.imm8(bus)?; + self.addc_impl(src) + } + } + /// Subtraction + impl CPU { + fn sub_impl(&mut self, value: u8) -> IResult { + let hc = half_carry_sub(self[R8::A].0, value); + let (out, carry) = self[R8::A].0.overflowing_sub(value); + self.set_z(out == 0).set_n(true).set_h(hc).set_c(carry); + self[R8::A].0 = out; + Ok(()) + } + pub fn sub(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let value = self.get_r8(reg, bus); + self.sub_impl(value) + } + pub fn subi(&mut self, bus: &mut impl BusIO) -> IResult { + let value = self.imm8(bus)?; + self.sub_impl(value) + } + } + /// Subtraction with carry + impl CPU { + #[inline] + fn subc_impl(&mut self, value: u8) -> IResult { + let (src, cin) = value.overflowing_sub(self.c() as u8); + let (out, carry) = self[R8::A].0.overflowing_sub(src); + let hc = half_carry_sub(self[R8::A].0, src); + self[R8::A].0 = out; + self.set_z(out == 0) + .set_n(true) + .set_h(hc) + .set_c(carry | cin); + Ok(()) + } + pub fn sbc(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let value = self.get_r8(reg, bus); + self.subc_impl(value) + } + pub fn sbci(&mut self, bus: &mut impl BusIO) -> IResult { + let value = self.imm8(bus)?; + self.subc_impl(value) + } + } + /// Bitwise AND + impl CPU { + fn and_impl(&mut self, value: u8) -> IResult { + self[R8::A] &= value; + self.set_z(self[R8::A].0 == 0) + .set_n(false) + .set_h(true) + .set_c(false); + Ok(()) + } + pub fn and(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let value = self.get_r8(reg, bus); + self.and_impl(value) + } + pub fn andi(&mut self, bus: &mut impl BusIO) -> IResult { + let value = self.imm8(bus)?; + self.and_impl(value) + } + } + /// Bitwise XOR + impl CPU { + fn xor_impl(&mut self, value: u8) -> IResult { + self[R8::A] ^= value; + *self.flags_mut() = Flags::new().with_z(self[R8::A].0 == 0); + Ok(()) + } + pub fn xor(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let value = self.get_r8(reg, bus); + self.xor_impl(value) + } + pub fn xori(&mut self, bus: &mut impl BusIO) -> IResult { + let value = self.imm8(bus)?; + self.xor_impl(value) + } + } + /// Bitwise OR + impl CPU { + fn or_impl(&mut self, value: u8) -> IResult { + self[R8::A] |= value; + *self.flags_mut() = Flags::new().with_z(self[R8::A].0 == 0); + Ok(()) + } + pub fn or(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let value = self.get_r8(reg, bus); + self.or_impl(value) + } + pub fn ori(&mut self, bus: &mut impl BusIO) -> IResult { + let value = self.imm8(bus)?; + self.or_impl(value) + } + } + /// Comparison + impl CPU { + fn cmp_impl(&mut self, src: u8) -> IResult { + let dst = self[R8::A].0; + self.sub_impl(src)?; + self[R8::A].0 = dst; + Ok(()) + } + pub fn cmp(&mut self, reg: R8, bus: &mut impl BusIO) -> IResult { + let src = self.get_r8(reg, bus); + self.cmp_impl(src) + } + pub fn cmpi(&mut self, bus: &mut impl BusIO) -> IResult { + let src = self.imm8(bus)?; + self.cmp_impl(src) + } + } + /// Stack ops + impl CPU { + pub fn insn_push(&mut self, reg: R16Stack, bus: &mut impl BusIO) -> IResult { + self.push16(self[reg].0, bus) + } + pub fn insn_pop(&mut self, reg: R16Stack, bus: &mut impl BusIO) -> IResult { + self[reg].0 = self.pop16(bus)?; + Ok(()) + } + } + /// Jumps, branches, calls, and returns + impl CPU { + pub fn jr(&mut self, bus: &mut impl BusIO) -> IResult { + let rel = self.imm8(bus)? as i8 as i16; + self.pc += rel as u16; + Ok(()) + } + pub fn jrc(&mut self, cond: Cond, bus: &mut impl BusIO) -> IResult { + let rel = self.imm8(bus)? as i8 as i16; // sign extend to 16 bits + if self.cond(cond) { + self.pc += rel as u16; + } + Ok(()) + } + pub fn jp(&mut self, bus: &mut impl BusIO) -> IResult { + // Jump takes an extra cycle + let addr = self.imm16(bus)?; + self.jumpto(addr); + Ok(()) + } + pub fn jpc(&mut self, cond: Cond, bus: &mut impl BusIO) -> IResult { + let addr = self.imm16(bus)?; + if self.cond(cond) { + self.jumpto(addr) + } + Ok(()) + } + pub fn jp_hl(&mut self) -> IResult { + self.pc = *self.hl.wide(); + Ok(()) + } + + /// Pushes PC onto the stack, then jumps to the provided + #[inline] + pub fn call_addr(&mut self, addr: u16, bus: &mut impl BusIO) -> IResult { + self.push16(self.pc.0, bus)?; + self.jumpto(addr); + Ok(()) + } + pub fn call(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.imm16(bus)?; + self.call_addr(addr, bus) + } + pub fn callc(&mut self, cond: Cond, bus: &mut impl BusIO) -> IResult { + let addr = self.imm16(bus)?; + match self.cond(cond) { + true => self.call_addr(addr, bus), + false => Ok(()), + } + } + pub fn ret(&mut self, bus: &mut impl BusIO) -> IResult { + let addr = self.pop16(bus)?; + self.jumpto(addr); + Ok(()) + } + pub fn retc(&mut self, cond: Cond, bus: &mut impl BusIO) -> IResult { + // Condition checking here takes an extra cycle: https://gist.github.com/SonoSooS/c0055300670d678b5ae8433e20bea595#ret-cc + match self.wait().cond(cond) { + true => self.ret(bus), + false => Ok(()), + } + } + pub fn reti(&mut self, bus: &mut impl BusIO) -> IResult { + self.ime = Ime::Enabled; // Technically this should go after ret's pop + self.ret(bus) + } + pub fn reset(&mut self, addr: u8, bus: &mut impl BusIO) -> IResult { + self.call_addr(addr as u16 * 8, bus) + } + } + /// Prefixed instructions + impl CPU { + /// Rotates left, setting the carry flag to the highest bit + pub fn rlc(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + self.set_r8(reg, value.rotate_left(1), bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 0x80 > 0); + Ok(()) + } + /// Rotates right, setting the carry flag to the lowest bit + pub fn rrc(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + self.set_r8(reg, value.rotate_right(1), bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 1 > 0); + Ok(()) + } + /// Rotates left through the carry flag + pub fn rl(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + let carry = self.c() as u8; + self.set_r8(reg, (value << 1) | carry, bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 0x80 > 0); + Ok(()) + } + /// Rotates right through the carry flag + pub fn rr(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + let carry = self.c() as u8; + self.set_r8(reg, (carry << 7) | (value >> 1), bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 1 > 0); + Ok(()) + } + /// Shifts left arithmetically + pub fn sla(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 0x80 > 0); + self.set_r8(reg, value << 1, bus); + Ok(()) + } + /// Shifts right arithmetically (preserves bit 7) + pub fn sra(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + self.set_r8(reg, (value >> 1) | (value & 0x80), bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 1 > 0); + Ok(()) + } + /// Swaps the upper and lower nybbles of a byte + pub fn swap(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let mut value = self.get_r8(reg, bus); + value = (value & 0xf << 4) | (value & 0xf0 >> 4); + self.set_r8(reg, value, bus); + *self.flags_mut() = Flags::new().with_z(value == 0); + Ok(()) + } + /// Shifts right logically (shifting in zeroes) + pub fn srl(&mut self, reg: R8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + self.set_r8(reg, value >> 1, bus); + *self.flags_mut() = Flags::new().with_z(value == 0).with_c(value & 1 > 0); + Ok(()) + } + /// Tests whether the given bit of r8 is set + pub fn bit(&mut self, reg: R8, bit: u8, bus: &impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus) & (1 << bit) == 1; + self.set_z(value).set_n(false).set_h(true); + Ok(()) + } + /// Sets the given bit of the given register to 0 + pub fn res(&mut self, reg: R8, bit: u8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + self.set_r8(reg, value & !(1 << bit), bus); + Ok(()) + } + pub fn set(&mut self, reg: R8, bit: u8, bus: &mut impl BusIO) -> Result<(), Error> { + let value = self.get_r8(reg, bus); + self.set_r8(reg, value | 1 << bit, bus); + Ok(()) + } + } +} + +impl CPU { + pub fn run(&mut self, bus: &mut impl BusIO) -> Result { + self.m_cycles = 0; + // Run the current instruction + self.execute(bus)?; + + let prefetch_pc = self.pc.0; + // Handle interrupts + match self.ime { + Ime::Disabled => {} + Ime::ShouldEnable => self.ime = Ime::Enabled, + Ime::Enabled => self.service_irq(bus)?, + } + // Fetch the next instruction + self.fetch(bus)?; + + // Process breakpoints + if self.breakpoints.contains(&prefetch_pc) { + Err(HitBreak(prefetch_pc, self.m_cycles).into()) + } else { + Ok(self.m_cycles as usize) + } + } + pub fn fetch(&mut self, bus: &mut impl BusIO) -> Result<(), Error> { + self.ir = match self.ir { + // HALT fetches the current instruction, rather than the next + Insn::Halt => Insn::from(self.read(self.pc.0, bus)?), + // STOP becomes NOP when the clock resumes + Insn::Stop => Insn::Nop, + _ => Insn::from(self.imm8(bus)?), + }; + Ok(()) + } + pub fn execute(&mut self, bus: &mut impl BusIO) -> Result<(), Error> { + #[allow(unused_variables)] + match self.ir { + Insn::Nop => self.nop(), + Insn::Stop => self.stop(), + Insn::Halt => self.halt(), + + Insn::Ld(dst, src) => self.ld(dst, src, bus), + Insn::LdImm(dst) => self.ld_imm(dst, bus), + Insn::LdImm16(dst) => self.ld_imm16(dst, bus), + Insn::LdInd(src) => self.ld_ind(src, bus), + Insn::StInd(dst) => self.st_ind(dst, bus), + Insn::LdC => self.ldc(bus), + Insn::StC => self.stc(bus), + Insn::LdAbs => self.ld_abs(bus), + Insn::StAbs => self.st_abs(bus), + Insn::StSpAbs => self.st_sp_abs(bus), + Insn::LdSpHl => self.ld_sp_hl(), + Insn::LdHlSpRel => self.ld_hl_sp_rel(bus), + Insn::Ldh => self.ldh(bus), + Insn::Sth => self.sth(bus), + + Insn::Inc(reg) => self.inc(reg, bus), + Insn::Inc16(reg) => self.inc16(reg), + Insn::Dec(reg) => self.dec(reg, bus), + Insn::Dec16(reg) => self.dec16(reg), + + Insn::Rlnca => self.rlnca(), + Insn::Rrnca => self.rrnca(), + Insn::Rla => self.rla(), + Insn::Rra => self.rra(), + + Insn::Daa => self.daa(), + Insn::Cpl => self.cpl(), + Insn::Scf => self.scf(), + Insn::Ccf => self.ccf(), + + Insn::Add(reg) => self.add(reg, bus), + Insn::Add16HL(reg) => self.add16(reg), + Insn::AddI => self.addi(bus), + Insn::Add16SpI => self.add16_spi(bus), + Insn::Adc(reg) => self.adc(reg, bus), + Insn::AdcI => self.adci(bus), + Insn::Sub(reg) => self.sub(reg, bus), + Insn::SubI => self.subi(bus), + Insn::Sbc(reg) => self.sbc(reg, bus), + Insn::SbcI => self.sbci(bus), + Insn::And(reg) => self.and(reg, bus), + Insn::AndI => self.andi(bus), + Insn::Xor(reg) => self.xor(reg, bus), + Insn::XorI => self.xori(bus), + Insn::Or(reg) => self.or(reg, bus), + Insn::OrI => self.ori(bus), + Insn::Cp(reg) => self.cmp(reg, bus), + Insn::CpI => self.cmpi(bus), + Insn::Push(reg) => self.insn_push(reg, bus), + Insn::Pop(reg) => self.insn_pop(reg, bus), + // Jumps + Insn::Jr => self.jr(bus), + Insn::Jrc(cond) => self.jrc(cond, bus), + Insn::Jp => self.jp(bus), + Insn::Jpc(cond) => self.jpc(cond, bus), + Insn::JpHL => self.jp_hl(), + Insn::Call => self.call(bus), + Insn::Callc(cond) => self.callc(cond, bus), + Insn::Ret => self.ret(bus), + Insn::Retc(cond) => self.retc(cond, bus), + Insn::Di => self.di(), + Insn::Ei => self.ei(), + Insn::Reti => self.reti(bus), + Insn::Rst(addr) => self.reset(addr, bus), + Insn::PrefixCB => self.prefixed(bus), + Insn::Invalid(b) => Err(InvalidInstruction(b).into()), + } + } + pub fn prefixed(&mut self, bus: &mut impl BusIO) -> Result<(), Error> { + match Prefixed::from(self.imm8(bus)?) { + Prefixed::Rlc(reg) => self.rlc(reg, bus), + Prefixed::Rrc(reg) => self.rrc(reg, bus), + Prefixed::Rl(reg) => self.rl(reg, bus), + Prefixed::Rr(reg) => self.rr(reg, bus), + Prefixed::Sla(reg) => self.sla(reg, bus), + Prefixed::Sra(reg) => self.sra(reg, bus), + Prefixed::Swap(reg) => self.swap(reg, bus), + Prefixed::Srl(reg) => self.srl(reg, bus), + Prefixed::Bit(reg, bit) => self.bit(reg, bit as _, bus), + Prefixed::Res(reg, bit) => self.res(reg, bit as _, bus), + Prefixed::Set(reg, bit) => self.set(reg, bit as _, bus), + } + } + + /// Gets an 8-bit register. This requires bus access, since `[HL]` is a valid location + /// + /// Panic-free alternative to [Index] + pub fn get_r8(&mut self, r8: R8, bus: &impl BusIO) -> u8 { + match r8 { + R8::HLmem => { + self.m_cycles += 1; + self.read(self.hl.wide().0, bus).unwrap_or(0xff) + } + _ => self[r8].0, + } + } + /// Sets an 8-bit register. This requires bus access, since `[HL]` is a valid location + /// + /// Panic-free alternative to [IndexMut] + pub fn set_r8(&mut self, r8: R8, data: u8, bus: &mut impl BusIO) { + match r8 { + R8::HLmem => { + self.m_cycles += 1; + let _ = self.write(self.hl.wide().0, data, bus); + } + _ => self[r8].0 = data, + } + } + pub fn get_r16(&self, r16: R16) -> u16 { + self[r16].0 + } + pub fn set_r16(&mut self, r16: R16, data: u16) { + self[r16].0 = data; + } + pub fn get_r16ind_addr(&mut self, r16: R16Indirect) -> u16 { + match r16 { + R16Indirect::BC => self.bc.wide().0, + R16Indirect::DE => self.de.wide().0, + R16Indirect::HLI => { + let addr = self.hl.wide().0; + *self.hl.wide_mut() += 1; + addr + } + R16Indirect::HLD => { + let addr = self.hl.wide().0; + *self.hl.wide_mut() -= 1; + addr + } + } + } + /// Reads data at an R16-indirect address + pub fn read_r16ind(&mut self, r16: R16Indirect, bus: &impl BusIO) -> Result { + let addr = self.get_r16ind_addr(r16); + self.read(addr, bus) + } + pub fn write_r16ind(&mut self, r16: R16Indirect, data: u8, bus: &mut impl BusIO) { + let addr = self.get_r16ind_addr(r16); + let _ = self.write(addr, data, bus); + } + pub fn flags_mut(&mut self) -> &mut Flags { + self.af.flags_mut() + } + pub fn flags(&self) -> Flags { + self.af.flags().to_owned() + } + flags_getters_setters! { + /// the `Zero` Flag + z, set_z; + /// the `Subtract` Flag + n, set_n; + /// the `Half-Carry` Flag + h, set_h; + /// the `Carry` Flag + c, set_c; + } + /// Compares the given [Cond] to the CPU's condition flags + pub fn cond(&self, cond: Cond) -> bool { + match cond { + Cond::NZ => !self.z(), + Cond::Z => self.z(), + Cond::NC => !self.c(), + Cond::C => self.c(), + } + } +} + +/// Takes arguments of the form +/// ```rust,ignore +/// /// the `Flag` Flag +/// #[attributes ... ] +/// flag, set_flag, Flags::FLAG; +/// ``` +macro flags_getters_setters($($(#[$meta:meta])* $get:ident, $set:ident);*$(;)?) {$( + /// Gets + $(#[$meta])* + pub fn $get (&self) -> bool { + self.af.flags().$get() + } + /// Sets + $(#[$meta])* + pub fn $set (&mut self, to: bool) -> &mut Self{ + self.af.flags_mut().$set(to); + self + } +)*} + +impl Index for CPU { + type Output = Wrapping; + fn index(&self, index: R8) -> &Self::Output { + match index { + R8::A => self.af.hi(), + R8::B => self.bc.hi(), + R8::C => self.bc.lo(), + R8::D => self.de.hi(), + R8::E => self.de.lo(), + R8::H => self.hl.hi(), + R8::L => self.hl.lo(), + R8::HLmem => unimplemented!("Cannot index CPU with R8::HLmem without Bus"), + } + } +} +impl IndexMut for CPU { + /// Performs the mutable indexing (`container[index]`) operation. + /// # Panics + /// Will panic if the index is [`R8::HLmem`], as reading that requires a Bus + fn index_mut(&mut self, index: R8) -> &mut Self::Output { + match index { + R8::A => self.af.hi_mut(), + R8::B => self.bc.hi_mut(), + R8::C => self.bc.lo_mut(), + R8::D => self.de.hi_mut(), + R8::E => self.de.lo_mut(), + R8::H => self.hl.hi_mut(), + R8::L => self.hl.lo_mut(), + R8::HLmem => unimplemented!("Cannot index CPU with R8::HLmem without Bus"), + } + } +} +impl Index for CPU { + type Output = Wrapping; + /// Performs the indexing (`container[index]`) operation. + fn index(&self, index: R16) -> &Self::Output { + match index { + R16::BC => self.bc.wide(), + R16::DE => self.de.wide(), + R16::HL => self.hl.wide(), + R16::SP => &self.sp, + } + } +} +impl IndexMut for CPU { + /// Performs the mutable indexing (`container[index]`) operation. + fn index_mut(&mut self, index: R16) -> &mut Self::Output { + match index { + R16::BC => self.bc.wide_mut(), + R16::DE => self.de.wide_mut(), + R16::HL => self.hl.wide_mut(), + R16::SP => &mut self.sp, + } + } +} + +impl Index for CPU { + type Output = Wrapping; + fn index(&self, index: R16Stack) -> &Self::Output { + match index { + R16Stack::BC => self.bc.wide(), + R16Stack::DE => self.de.wide(), + R16Stack::HL => self.hl.wide(), + R16Stack::AF => self.af.wide(), + } + } +} +impl IndexMut for CPU { + fn index_mut(&mut self, index: R16Stack) -> &mut Self::Output { + match index { + R16Stack::BC => self.bc.wide_mut(), + R16Stack::DE => self.de.wide_mut(), + R16Stack::HL => self.hl.wide_mut(), + R16Stack::AF => self.af.wide_mut(), + } + } +} + +impl Display for CPU { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[rustfmt::skip] + let Self { ir, sp, pc, af, bc, de, hl, m_cycles, ime, breakpoints } = self; + writeln!(f, "Current instruction: {ir}\nPC: {pc:04x}\nSP: {sp:04x}")?; + writeln!(f, "A: {:02x}, F: {}", af.hi(), flag!(af, z, n, h, c))?; + // writeln!(f, "A: {:02x}, F: {:04b}", af.hi(), af.lo() >> 4)?; + writeln!(f, "B: {:02x}, C: {:02x}", bc.hi(), bc.lo())?; + writeln!(f, "D: {:02x}, E: {:02x}", de.hi(), de.lo())?; + writeln!(f, "H: {:02x}, L: {:02x}", hl.hi(), hl.lo())?; + write!( + f, + "Cycles: {m_cycles}\nInterrupts {}", + match ime { + Ime::Disabled => "Disabled", + Ime::ShouldEnable => "Should be Enabled", + Ime::Enabled => "Enabled", + } + )?; + + if breakpoints.is_empty() { + Ok(()) + } else { + write!(f, "\nBreakpoints: {breakpoints:04x?}") + } + } +} + +macro flag($reg:ident, $($name:ident),+) { + String::new() $( + if $reg.flags().$name() { + stringify!($name) + } else { + concat!("\x1b[30m", stringify!($name), "\x1b[0m") + })+ +} diff --git a/src/cpu/disasm.rs b/src/cpu/disasm.rs new file mode 100644 index 0000000..7bcfc1e --- /dev/null +++ b/src/cpu/disasm.rs @@ -0,0 +1,654 @@ +//! Disassembler for SM83 machine code + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Insn { + #[default] + /// No operation + Nop, + Stop, + Halt, + /// [Register](R8) to [register](R8) transfer + Ld(R8, R8), + /// Load an [8-bit](R8) immediate: `ld r8, #u8` + LdImm(R8), + /// Load a [16-bit](R16) immediate: `ld r16, #u16` + LdImm16(R16), + /// Load from [R16Indirect] into register [`a`](R8::A) + LdInd(R16Indirect), + /// Store register [`a`](R8::A) into [R16Indirect] + StInd(R16Indirect), + /// Load [`a`](R8::A) from 0xff00+[`c`](R8::C): `ld a, [c]` + LdC, + /// Store [`a`](R8::A) into 0xff00+[`c`](R8::C): `ld [c], a` + StC, + /// Load [`a`](R8::A) from absolute address: `ld a, [addr]` + LdAbs, + /// Store [`a`](R8::A) to absolute address: `ld [addr], a` + StAbs, + /// Store [stack pointer](R16::SP) to absolute address: `ld [addr], sp` + StSpAbs, + /// Load [`HL`](R16::HL) into SP + LdSpHl, + /// Load [`HL`](R16::HL) with SP + [u8] + LdHlSpRel, + /// Load from himem at immediate offset: `ldh a, [0xff00 + #u8]` + Ldh, + /// Store to himem at immediate offset: `ldh [0xff00 + #u8], a` + Sth, + + /// Increment the value in the [register](R8) + Inc(R8), + /// Increment the value in the [register](R16) + Inc16(R16), + /// Decrement the value in the [register](R8) + Dec(R8), + /// Decrement the value in the [register](R16) + Dec16(R16), + + /// Rotate left [`a`](R8::A) + Rlnca, + /// Rotate right [`a`](R8::A) + Rrnca, + /// Rotate left [`a`](R8::A) [through carry flag](https://rgbds.gbdev.io/docs/v0.7.0/gbz80.7#RLA) + Rla, + /// Rotate Right [`a`](R8::A) [through carry flag](https://rgbds.gbdev.io/docs/v0.7.0/gbz80.7#RRA) + Rra, + /// daa ; Decimal-adjust accumulator using sub and half-carry flag + Daa, + /// cpl ; Complement [`a`](R8::A) + Cpl, + /// scf ; Set carry flag + Scf, + // ccf ; Complement carry flag + Ccf, + + /// add [`a`](R8::A), [R8] + Add(R8), + /// add[`HL`](R16::HL), [R16] + Add16HL(R16), + /// add #[u8], [`a`](R8::A) + AddI, + /// add [`sp`](R16::SP), #[i8] + Add16SpI, + /// adc [R8], [`a`](R8::A) + Adc(R8), + /// adc #[u8], [`a`](R8::A) + AdcI, + /// sub [R8], [`a`](R8::A) + Sub(R8), + /// sub #[u8], [`a`](R8::A) + SubI, + /// sbc [R8], [`a`](R8::A) + Sbc(R8), + /// sbc #[u8], [`a`](R8::A) + SbcI, + /// and [R8], [`a`](R8::A) + And(R8), + /// and #[u8], [`a`](R8::A) + AndI, + /// xor [R8], [`a`](R8::A) + Xor(R8), + /// xor #[u8], [`a`](R8::A) + XorI, + /// or [R8], [`a`](R8::A) + Or(R8), + /// or #[u8], [`a`](R8::A) + OrI, + /// cp [R8], [`a`](R8::A) + Cp(R8), + /// cp #[u8], [`a`](R8::A) + CpI, + + /// push [r16](R16Stack) + Push(R16Stack), + /// pop [r16](R16Stack) + Pop(R16Stack), + + /// jr #[i8] + Jr, + /// jr [Cond], #[i8] + Jrc(Cond), + /// jp #[u16] + Jp, + /// jp [Cond], #[u16] + Jpc(Cond), + /// jp [HL](R16::HL) + JpHL, + /// call #[u16] + Call, + /// call [Cond], #[u16] + Callc(Cond), + /// ret + Ret, + /// ret [Cond] ; + Retc(Cond), + + /// di ; Disable Interrupts + Di, + /// ei ; Enable Interrupts + Ei, + /// reti ; Return from interrupt, re-enabling interrupts + Reti, + /// rst #vec ; Call one of the reset vectors in the first page + Rst(u8), + /// (cb) NN ; Next instruction is a [Prefixed] instruction + PrefixCB, + Invalid(u8), +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Prefixed { + Rlc(R8), + Rrc(R8), + Rl(R8), + Rr(R8), + Sla(R8), + Sra(R8), + Swap(R8), + Srl(R8), + Bit(R8, U3), + Res(R8, U3), + Set(R8, U3), +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum U3 { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, +} +impl From for Insn { + fn from(value: u8) -> Self { + // low, middle, and high nibbles of the instruction + let (low, mid, high) = (value.low(), value.mid(), value.high()); + // R16 specifier bits, and alternate-instruction bit + let (r16, alt) = (mid >> 1, mid & 1 == 0); + match high { + 0 => match (low, mid) { + (0, 0) => Insn::Nop, + (0, 1) => Insn::LdSpHl, + (0, 2) => Insn::Stop, + (0, 3) => Insn::Jr, + (0, _) => Insn::Jrc((mid & 3).into()), + (1, _) if alt => Insn::LdImm16(r16.into()), + (1, _) => Insn::Add16HL(r16.into()), + (2, _) if alt => Insn::StInd(r16.into()), + (2, _) => Insn::LdInd(r16.into()), + (3, _) if alt => Insn::Inc16(r16.into()), + (3, _) => Insn::Dec16(r16.into()), + (4, dst) => Insn::Inc(dst.into()), + (5, dst) => Insn::Dec(dst.into()), + (6, dst) => Insn::LdImm(dst.into()), + (7, 0) => Insn::Rlnca, + (7, 1) => Insn::Rrnca, + (7, 2) => Insn::Rla, + (7, 3) => Insn::Rra, + (7, 4) => Insn::Daa, + (7, 5) => Insn::Cpl, + (7, 6) => Insn::Scf, + (7, 7) => Insn::Ccf, + _ => unreachable!(), + }, + 1 if value == 0b01110110 => Insn::Halt, + 1 => Insn::Ld(mid.into(), low.into()), + 2 => match mid { + 0 => Insn::Add(low.into()), + 1 => Insn::Adc(low.into()), + 2 => Insn::Sub(low.into()), + 3 => Insn::Sbc(low.into()), + 4 => Insn::And(low.into()), + 5 => Insn::Or(low.into()), + 6 => Insn::Xor(low.into()), + 7 => Insn::Cp(low.into()), + _ => unreachable!(), + }, + 3 => match (low, mid) { + (0, 0..=3) => Insn::Retc((mid & 3).into()), + (0, 4) => Insn::Sth, + (0, 5) => Insn::Add16SpI, + (0, 6) => Insn::Ldh, + (0, 7) => Insn::LdHlSpRel, + (1, _) if alt => Insn::Pop(r16.into()), + (1, 1) => Insn::Ret, + (1, 3) => Insn::Reti, + (1, 5) => Insn::JpHL, + (1, 7) => Insn::LdSpHl, + (2, 0..=3) => Insn::Jpc((mid & 3).into()), + (2, 4) => Insn::StC, + (2, 5) => Insn::StAbs, + (2, 6) => Insn::LdC, + (2, 7) => Insn::LdAbs, + (3, 0) => Insn::Jp, + (3, 1) => Insn::PrefixCB, + (3, 6) => Insn::Di, + (3, 7) => Insn::Ei, + (3, _) => Insn::Invalid(value), + (4, 0..=3) => Insn::Callc((mid & 3).into()), + (4, _) => Insn::Invalid(value), + (5, 1) => Insn::Call, + (5, _) if alt => Insn::Push(r16.into()), + (5, _) => Insn::Invalid(value), + (6, 0) => Insn::AddI, + (6, 1) => Insn::AdcI, + (6, 2) => Insn::SubI, + (6, 3) => Insn::SbcI, + (6, 4) => Insn::AndI, + (6, 5) => Insn::XorI, + (6, 6) => Insn::OrI, + (6, 7) => Insn::CpI, + (7, rst) => Insn::Rst(rst), + _ => unreachable!(), + }, + _ => unreachable!(), + } + } +} +impl From for Prefixed { + fn from(value: u8) -> Self { + let r8 = value.low().into(); + match (value.high(), value.mid()) { + (0, 0) => Self::Rlc(r8), + (0, 1) => Self::Rrc(r8), + (0, 2) => Self::Rl(r8), + (0, 3) => Self::Rr(r8), + (0, 4) => Self::Sla(r8), + (0, 5) => Self::Sra(r8), + (0, 6) => Self::Swap(r8), + (0, 7) => Self::Srl(r8), + (1, b3) => Self::Bit(r8, b3.into()), + (2, b3) => Self::Res(r8, b3.into()), + (3, b3) => Self::Set(r8, b3.into()), + _ => unreachable!(), + } + } +} +impl From for U3 { + fn from(value: u8) -> Self { + match value & 7 { + 0 => Self::_0, + 1 => Self::_1, + 2 => Self::_2, + 3 => Self::_3, + 4 => Self::_4, + 5 => Self::_5, + 6 => Self::_6, + 7 => Self::_7, + _ => unreachable!(), + } + } +} +impl From for u8 { + fn from(value: U3) -> Self { + value as _ + } +} + +/// Note: Meant only for implementation on numeric types +pub trait U8ins: Sized { + /// Gets the Nth octal digit of Self + fn oct(self) -> u8; + /// Gets the low octal digit of self + /// ```binary + /// 00000111 -> 111 + /// ``` + fn low(self) -> u8 { + self.oct::<0>() + } + /// Gets the middle octal digit of self, also known as B3/TGT3 + /// ```binary + /// 00111000 -> 111 + /// ``` + fn mid(self) -> u8 { + self.oct::<1>() + } + /// Gets the high octal digit of self + /// ```binary + /// 11000000 -> 011 + /// ``` + fn high(self) -> u8 { + self.oct::<2>() + } +} +impl U8ins for u8 { + fn oct(self) -> u8 { + self >> (N * 3) & 0b111 + } +} + +/// 8 bit register or RAM access +/// 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 +/// --|---|---|---|---|---|---|-- +/// B | C | D | E | H | L |*HL| A +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum R8 { + B, + C, + D, + E, + H, + L, + HLmem, + A, +} +/// 16 bit register +/// 0 | 1 | 2 | 3 +/// ----|----|----|---- +/// BC | DE | HL | SP +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum R16 { + BC, + DE, + HL, + SP, +} +/// 16 bit operand to push and pop +/// 0 | 1 | 2 | 3 +/// ----|----|----|---- +/// BC | DE | HL | AF +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum R16Stack { + BC, + DE, + HL, + AF, +} +/// 16 bit memory access through pointer +/// 0 | 1 | 2 | 3 +/// ----|----|----|---- +/// BC | DE | HL+| HL- +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum R16Indirect { + /// Address in [R16::BC] + BC, + /// Address in [R16::DE] + DE, + /// Address in [R16::HL]. Post-increment HL. + HLI, + /// Address in [R16::HL]. Post-decrement HL. + HLD, +} + +/// 0 | 1 | 2 | 3 +/// ----|----|----|---- +/// NZ | Z | NC | C +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Cond { + NZ, + Z, + NC, + C, +} + +mod conv { + use super::*; + impl From for R8 { + fn from(value: u8) -> Self { + match value { + 0 => Self::B, + 1 => Self::C, + 2 => Self::D, + 3 => Self::E, + 4 => Self::H, + 5 => Self::L, + 6 => Self::HLmem, + 7 => Self::A, + n => panic!("Index out of bounds: {n} > 7"), + } + } + } + impl From for R16 { + fn from(value: u8) -> Self { + match value { + 0 => Self::BC, + 1 => Self::DE, + 2 => Self::HL, + 3 => Self::SP, + n => panic!("Index out of bounds: {n} > 3"), + } + } + } + impl From for R16Stack { + fn from(value: u8) -> Self { + match value { + 0 => Self::BC, + 1 => Self::DE, + 2 => Self::HL, + 3 => Self::AF, + n => panic!("Index out of bounds: {n} > 3"), + } + } + } + impl From for R16Indirect { + fn from(value: u8) -> Self { + match value { + 0 => Self::BC, + 1 => Self::DE, + 2 => Self::HLI, + 3 => Self::HLD, + n => panic!("Index out of bounds: {n} > 3"), + } + } + } + impl From for Cond { + fn from(value: u8) -> Self { + match value { + 0 => Self::NZ, + 1 => Self::Z, + 2 => Self::NC, + 3 => Self::C, + n => panic!("Index out of bounds: {n} > 3"), + } + } + } +} +mod disp { + use super::*; + use std::fmt::Display; + impl Display for Insn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Insn::Nop => write!(f, "nop"), + Insn::Stop => write!(f, "stop"), + Insn::Halt => write!(f, "halt"), + + Insn::Ld(dst, src) => write!(f, "ld\t{dst}, {src}"), + Insn::LdImm(r8) => write!(f, "ld\t{r8}, #u8"), + Insn::LdImm16(r16) => write!(f, "ld\t{r16}, #u16"), + Insn::LdInd(src) => write!(f, "ld\ta, {src}"), + Insn::StInd(dst) => write!(f, "ld\t{dst}, a"), + Insn::LdC => write!(f, "ld\ta, [c]"), + Insn::StC => write!(f, "ld\t[c], a"), + Insn::LdAbs => write!(f, "ld\ta, [#u16]"), + Insn::StAbs => write!(f, "ld\t[#u16], a"), + Insn::StSpAbs => write!(f, "ld\t[#u16], sp"), + Insn::LdSpHl => write!(f, "ld\tsp, hl"), + Insn::LdHlSpRel => write!(f, "ldhl\tsp, #u8"), + + Insn::Ldh => write!(f, "ldh\ta, [0xff00 + #u8]"), + Insn::Sth => write!(f, "ldh\t[0xff00 + #u8], a"), + + Insn::Inc(dst) => write!(f, "inc\t{dst}"), + Insn::Inc16(dst) => write!(f, "inc\t{dst}"), + Insn::Dec(dst) => write!(f, "dec\t{dst}"), + Insn::Dec16(dst) => write!(f, "dec\t{dst}"), + + Insn::Rlnca => write!(f, "rlca"), + Insn::Rrnca => write!(f, "rrca"), + Insn::Rla => write!(f, "rla"), + Insn::Rra => write!(f, "rra"), + Insn::Daa => write!(f, "daa"), + Insn::Cpl => write!(f, "cpl"), + Insn::Scf => write!(f, "scf"), + Insn::Ccf => write!(f, "ccf"), + + Insn::Add(src) => write!(f, "add\ta, {src}"), + Insn::AddI => write!(f, "add\ta, #u8"), + Insn::Add16HL(src) => write!(f, "add\tHL, {src}"), + Insn::Add16SpI => write!(f, "add\tSP, #i8"), + Insn::Adc(src) => write!(f, "adc\ta, {src}"), + Insn::AdcI => write!(f, "adc\ta, #u8"), + Insn::Sub(src) => write!(f, "sub\ta, {src}"), + Insn::SubI => write!(f, "sub\ta, #u8"), + Insn::Sbc(src) => write!(f, "sbc\ta, {src}"), + Insn::SbcI => write!(f, "sbc\ta, #u8"), + Insn::And(src) => write!(f, "and\ta, {src}"), + Insn::AndI => write!(f, "and\ta, #u8"), + Insn::Xor(src) => write!(f, "xor\ta, {src}"), + Insn::XorI => write!(f, "xor\ta, #u8"), + Insn::Or(src) => write!(f, "or\ta, {src}"), + Insn::OrI => write!(f, "or\ta, #u8"), + Insn::Cp(src) => write!(f, "cp\ta, {src}"), + Insn::CpI => write!(f, "cp\ta, #u8"), + + Insn::Push(src) => write!(f, "push\t{src}"), + Insn::Pop(dst) => write!(f, "pop\t{dst}"), + + Insn::Jr => write!(f, "jr\t#i8"), + Insn::Jrc(cond) => write!(f, "jr\t{cond}, #i8"), + Insn::Jp => write!(f, "jp\t#u16"), + Insn::Jpc(cond) => write!(f, "jp\t{cond}, #u16"), + Insn::JpHL => write!(f, "jp\tHL"), + Insn::Call => write!(f, "call\t#u16"), + Insn::Callc(cond) => write!(f, "call\t{cond}, #u16"), + Insn::Di => write!(f, "di"), + Insn::Ei => write!(f, "ei"), + Insn::Ret => write!(f, "ret"), + Insn::Retc(cond) => write!(f, "ret\t{cond}"), + Insn::Reti => write!(f, "reti"), + Insn::Rst(vector) => write!(f, "rst\t${:02x}", vector * 8), + Insn::PrefixCB => write!(f, "; 0xCB"), + Insn::Invalid(op) => write!(f, "_{op:02x}"), + } + } + } + impl Display for Prefixed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Prefixed::Rlc(r8) => write!(f, "rlc\t{r8}"), + Prefixed::Rrc(r8) => write!(f, "rrc\t{r8}"), + Prefixed::Rl(r8) => write!(f, "rl\t{r8}"), + Prefixed::Rr(r8) => write!(f, "rr\t{r8}"), + Prefixed::Sla(r8) => write!(f, "sla\t{r8}"), + Prefixed::Sra(r8) => write!(f, "sra\t{r8}"), + Prefixed::Swap(r8) => write!(f, "swap\t{r8}"), + Prefixed::Srl(r8) => write!(f, "srl\t{r8}"), + Prefixed::Bit(r8, u3) => write!(f, "bit\t{r8}, {:x}", *u3 as u8), + Prefixed::Res(r8, u3) => write!(f, "res\t{r8}, {:x}", *u3 as u8), + Prefixed::Set(r8, u3) => write!(f, "set\t{r8}, {:x}", *u3 as u8), + } + } + } + impl Display for R8 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + R8::B => write!(f, "b"), + R8::C => write!(f, "c"), + R8::D => write!(f, "d"), + R8::E => write!(f, "e"), + R8::H => write!(f, "h"), + R8::L => write!(f, "l"), + R8::HLmem => write!(f, "[hl]"), + R8::A => write!(f, "a"), + } + } + } + impl Display for R16 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + R16::BC => write!(f, "bc"), + R16::DE => write!(f, "de"), + R16::HL => write!(f, "hl"), + R16::SP => write!(f, "sp"), + } + } + } + impl Display for R16Stack { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + R16Stack::BC => write!(f, "bc"), + R16Stack::DE => write!(f, "de"), + R16Stack::HL => write!(f, "hl"), + R16Stack::AF => write!(f, "af"), + } + } + } + impl Display for R16Indirect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + R16Indirect::BC => write!(f, "[bc]"), + R16Indirect::DE => write!(f, "[de]"), + R16Indirect::HLI => write!(f, "[hl+]"), + R16Indirect::HLD => write!(f, "[hl-]"), + } + } + } + impl Display for Cond { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Cond::NZ => write!(f, "nz"), + Cond::Z => write!(f, "z"), + Cond::NC => write!(f, "nc"), + Cond::C => write!(f, "c"), + } + } + } +} +mod info { + use super::Insn; + + impl Insn { + /// Gets the length of the instruction in bytes + pub fn len(&self) -> usize { + match self { + Insn::Nop | Insn::Stop | Insn::Halt => 1, + Insn::Ld(_, _) => 1, + Insn::LdImm(_) => 2, + Insn::LdImm16(_) => 3, + Insn::LdInd(_) | Insn::StInd(_) => 1, + Insn::LdC | Insn::StC => 1, + Insn::LdAbs | Insn::StAbs | Insn::StSpAbs => 3, + Insn::LdSpHl => 1, + Insn::LdHlSpRel => 2, + Insn::Ldh | Insn::Sth => 2, + Insn::Inc(_) | Insn::Inc16(_) => 1, + Insn::Dec(_) | Insn::Dec16(_) => 1, + Insn::Rlnca | Insn::Rrnca | Insn::Rla | Insn::Rra => 1, + Insn::Daa | Insn::Cpl | Insn::Scf | Insn::Ccf => 1, + Insn::Add(_) | Insn::Add16HL(_) => 1, + Insn::AddI | Insn::Add16SpI => 2, + Insn::Adc(_) + | Insn::Sub(_) + | Insn::Sbc(_) + | Insn::And(_) + | Insn::Xor(_) + | Insn::Or(_) + | Insn::Cp(_) => 1, + Insn::AdcI + | Insn::SubI + | Insn::SbcI + | Insn::AndI + | Insn::XorI + | Insn::OrI + | Insn::CpI => 2, + Insn::Push(_) | Insn::Pop(_) => 1, + Insn::Jr | Insn::Jrc(_) => 2, + Insn::Jp | Insn::Jpc(_) => 3, + Insn::JpHL => 1, + Insn::Call | Insn::Callc(_) => 3, + Insn::Ret | Insn::Retc(_) => 1, + Insn::Di | Insn::Ei => 1, + Insn::Reti => 1, + Insn::Rst(_) => 1, + Insn::PrefixCB => 1, + Insn::Invalid(_) => 1, + } + } + #[must_use] + pub fn is_empty(&self) -> bool { + matches!(self, Insn::Invalid(_)) + } + } +} diff --git a/src/cpu/split_register.rs b/src/cpu/split_register.rs new file mode 100644 index 0000000..e826a72 --- /dev/null +++ b/src/cpu/split_register.rs @@ -0,0 +1,194 @@ +//! Represents two 8-bit registers which may be conjoined into one 16-bit register +//! +//! The [SplitRegister] is represented by a native-endian [`Wrapping`] +//! or two [`Wrapping`]s, respectively +//! +//! # Examples +//! The [SplitRegister] can be initialized +//! ```rust +//! # use boy::cpu::split_register::SplitRegister; +//! use std::num::Wrapping; +//! +//! let reg = SplitRegister::from(0x1000); +//! assert_eq!(*reg.lo(), Wrapping(0x00)); +//! assert_eq!(*reg.hi(), Wrapping(0x10)); +//! ``` + +use std::{fmt::Debug, hash::Hash, num::Wrapping, ops::*}; + +use super::Flags; + +/// The low-byte index constant +const LO: usize = if 1u16.to_be() != 1u16 { 0 } else { 1 }; +/// The high-byte index constant +const HI: usize = 1 - LO; + +#[derive(Clone, Copy)] +#[repr(C)] +pub union SplitRegister { + lh: [Wrapping; 2], + af: [Flags; 2], + r16: Wrapping, +} +impl SplitRegister { + /// Read the low byte of the register + pub fn lo(&self) -> &Wrapping { + // SAFETY: The GB's registers store POD types, so it's safe to transmute + unsafe { &self.lh[LO] } + } + /// Read the high byte of the register + pub fn hi(&self) -> &Wrapping { + // SAFETY: See [SplitRegister::lo] + unsafe { &self.lh[HI] } + } + /// Read the [flags byte](Flags) of the register + pub fn flags(&self) -> &Flags { + // SAFETY: See [SplitRegister::lo] + unsafe { &self.af[LO] } + } + /// Read the contents of the combined register + pub fn wide(&self) -> &Wrapping { + // SAFETY: See [SplitRegister::lo] + unsafe { &self.r16 } + } + + /// Get a mutable reference to the low byte of the register + pub fn lo_mut(&mut self) -> &mut Wrapping { + // SAFETY: See [SplitRegister::lo] + unsafe { &mut self.lh[LO] } + } + /// Gets a mutable reference to the high byte of the register + pub fn hi_mut(&mut self) -> &mut Wrapping { + // SAFETY: See [SplitRegister::lo] + unsafe { &mut self.lh[HI] } + } + /// Gets a mutable reference to the [flags byte](Flags) of the register + pub fn flags_mut(&mut self) -> &mut Flags { + // SAFETY: See [SplitRegister::lo] + unsafe { &mut self.af[LO] } + } + /// Gets a mutable reference to the combined register + pub fn wide_mut(&mut self) -> &mut Wrapping { + // SAFETY: See [SplitRegister::lo] + unsafe { &mut self.r16 } + } + + /// Constructs a SplitRegister from an array of big-endian bytes. + pub fn from_be_bytes(value: [u8; 2]) -> Self { + Self { + lh: [Wrapping(value[HI]), Wrapping(value[LO])], + } + } + /// Constructs a SplitRegister from an array of little-endian bytes. + pub fn from_le_bytes(value: [u8; 2]) -> Self { + Self { + lh: [Wrapping(value[LO]), Wrapping(value[HI])], + } + } + /// Constructs a SplitRegister from an array of host-endian bytes. + pub fn from_ne_bytes(value: [u8; 2]) -> Self { + Self { + lh: [Wrapping(value[0]), Wrapping(value[1])], + } + } +} +impl Debug for SplitRegister { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[{:02x}, {:02x}]", self.lo(), self.hi()) + } +} +impl Default for SplitRegister { + fn default() -> Self { + Self { r16: Wrapping(0) } + } +} +impl PartialEq for SplitRegister { + fn eq(&self, other: &Self) -> bool { + self.wide().eq(other.wide()) + } +} +impl Eq for SplitRegister {} +impl PartialOrd for SplitRegister { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for SplitRegister { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.wide().cmp(other.wide()) + } +} +impl Hash for SplitRegister { + fn hash(&self, state: &mut H) { + self.wide().hash(state) + } +} +impl From for SplitRegister { + fn from(value: u16) -> Self { + Self { + r16: Wrapping(value), + } + } +} +impl From> for SplitRegister { + fn from(value: Wrapping) -> Self { + Self { r16: value } + } +} + +macro impl_ops(for $base:ident {$($trait:ident<$($ty:ty),*$(,)?> $func:ident()),*$(,)?}) { + $( + impl $trait> for $base { + type Output = Self; + fn $func(self, rhs: Wrapping) -> Self::Output { + Self { r16: self.wide().$func(rhs) } + } + } + impl $trait for $base { + type Output = Self; + fn $func(self, rhs: Self) -> Self::Output { + self.$func(*rhs.wide()) + } + } + $(impl $trait<$ty> for $base { + type Output = Self; + fn $func(self, rhs: $ty) -> Self::Output { + self.$func(Wrapping(rhs as u16)) + } + })* + )* +} +impl_ops!(for SplitRegister { + Mul mul(), + Div div(), + Rem rem(), + Add add(), + Sub sub(), + BitAnd bitand(), + BitOr bitor(), + BitXor bitxor(), +}); + +#[test] +fn low_is_low() { + let reg = SplitRegister { + r16: Wrapping(0x00ff), + }; + assert_eq!(*reg.lo(), Wrapping(0xff)) +} +#[test] +fn hi_is_hi() { + let reg = SplitRegister { + r16: Wrapping(0xff00), + }; + assert_eq!(*reg.hi(), Wrapping(0xff)) +} +#[test] +fn from_bytes() { + let be = SplitRegister::from_be_bytes([0xc5, 0x77]); + let le = SplitRegister::from_le_bytes([0xc5, 0x77]); + let ne = SplitRegister::from_ne_bytes(0x1234u16.to_ne_bytes()); + assert_eq!(*be.wide(), Wrapping(0xc577)); + assert_eq!(*le.wide(), Wrapping(0x77c5)); + assert_eq!(*ne.wide(), Wrapping(0x1234)); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..785f404 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,420 @@ +//! A very inaccurate gameboy emulator +#![feature(decl_macro)] +#![allow(clippy::unit_arg)] + +/// A gameboy has: +/// - A 16-bit memory bus +/// - A CPU +/// - A picture processing unit + +pub mod error { + use crate::cpu::disasm::Insn; + use std::fmt::Display; + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct Error { + kind: ErrorKind, + // any other information about an error + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub enum ErrorKind { + InvalidInstruction(u8), + InvalidAddress(u16), + UnimplementedInsn(Insn), + Unimplemented(u8), + HitBreak(u16, u8), + Todo, + } + impl From for Error { + fn from(value: ErrorKind) -> Self { + Self { kind: value } + } + } + impl From for Result { + fn from(value: Error) -> Self { + Err(value) + } + } + impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.kind.fmt(f) + } + } + impl Display for ErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ErrorKind::InvalidInstruction(i) => write!(f, "Invalid instruction: {i}"), + ErrorKind::InvalidAddress(addr) => write!(f, "Invalid address: {addr:04x}"), + ErrorKind::UnimplementedInsn(i) => write!(f, "Unimplemented instruction: {i}"), + ErrorKind::Unimplemented(i) => write!(f, "Unimplemented instruction: {i}"), + ErrorKind::HitBreak(addr, m) => { + write!(f, "Hit breakpoint {addr:x} (Took {m} cycles)") + } + ErrorKind::Todo => write!(f, "Not yet implemented"), + } + } + } +} + +pub mod memory; + +pub mod graphics { + use bitfield_struct::bitfield; + + use crate::memory::io::BusIO; + use std::mem::size_of; + + /// The number of OAM entries supported by the PPU (on real hardware, 40) + pub const NUM_OAM_ENTRIES: usize = 40; + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Mode { + HBlank, + VBlank, + OAMScan, + Drawing, + } + impl Mode { + const fn from_bits(value: u8) -> Self { + match value & 3 { + 0b00 => Self::HBlank, + 0b01 => Self::VBlank, + 0b10 => Self::OAMScan, + 0b11 => Self::Drawing, + _ => unreachable!(), + } + } + const fn into_bits(self) -> u8 { + self as _ + } + } + + type OAMTable = [OAMEntry; NUM_OAM_ENTRIES]; + + #[derive(Clone, Debug, PartialEq, PartialOrd)] + pub struct PPU { + lcdc: LcdC, // LCD Control + stat: Stat, // LCD Status + scy: u8, // viewport y coordinate + scx: u8, // viewport x coordinate + /// Current line. Read-only. + ly: u8, // holds values in range 0..=153 (144..=153 = vblank) + /// Line Y for Comparison. Triggers interrupt if enabled in `stat` + lyc: u8, // line y compare, triggers interrupt if enabled + + oam: OAMTable, + } + + impl PPU { + pub fn oam(&self) -> &[u8] { + let data = self.oam.as_slice().as_ptr().cast(); + let len = size_of::(); + // SAFETY: + // [OAMEntry; NUM_OAM_ENTRIES] is sizeof * NUM_OAM_ENTRIES long, + // and already aligned + unsafe { core::slice::from_raw_parts(data, len) } + } + pub fn oam_mut(&mut self) -> &mut [u8] { + let data = self.oam.as_mut_slice().as_mut_ptr().cast(); + let len = size_of::(); + // SAFETY: see above + unsafe { core::slice::from_raw_parts_mut(data, len) } + } + /// Gets the current PPU operating mode + pub fn mode(&self) -> Mode { + self.stat.mode() + } + pub fn set_mode(&mut self, mode: Mode) { + self.stat.set_mode(mode); + } + /// Gets the STAT interrupt selection config. + /// + /// | Bit | Name | + /// |:---:|:---------------| + /// | `0` | H Blank | + /// | `1` | V Blank | + /// | `2` | OAM Scan | + /// | `3` | LY Coincidence | + pub fn stat_interrupt_selection(&self) -> u8 { + (self.stat.into_bits() & 0b1111000) >> 3 + } + /// Gets the LY coincidence flag. Should be true when LY = LYC + pub fn ly_coincidence(&self) -> bool { + self.stat.lyc_hit() + } + } + + impl Default for PPU { + fn default() -> Self { + Self { + lcdc: Default::default(), + stat: Default::default(), + scy: Default::default(), + scx: Default::default(), + ly: Default::default(), + lyc: Default::default(), + oam: [Default::default(); NUM_OAM_ENTRIES], + } + } + } + impl BusIO for PPU { + fn read(&self, addr: usize) -> Option { + match addr { + 0xfe00..=0xfe9f => self.oam().read(addr - 0xfe00), + 0xff40 => Some(self.lcdc.into_bits()), + 0xff41 => Some(self.stat.into_bits()), + 0xff42 => Some(self.scy), + 0xff43 => Some(self.scx), + 0xff44 => Some(self.ly), + 0xff45 => Some(self.lyc), + _ => None, + } + } + + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + match addr { + 0xfe00..=0xfe9f => self.oam_mut().write(addr - 0xfe00, data), + 0xff40 => Some(self.lcdc = LcdC::from_bits(data)), + 0xff41 => Some(self.stat.set_interrupts(Stat::from_bits(data).interrupts())), + 0xff42 => Some(self.scy = data), + 0xff43 => Some(self.scx = data), + 0xff44 => None, + 0xff45 => Some(self.lyc = data), + _ => None, + } + } + } + + #[repr(C)] + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct OAMEntry { + y: u8, // y coordinate of the bottom of a 16px sprite + x: u8, // x coordinate of the right of an 8px sprite + tid: u8, // tile identifier + attr: OAMAttr, // OAM Attributes + } + + #[bitfield(u8)] + /// OAM Attributes. Read-writable by CPU, read-only by PPU. + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct OAMAttr { + #[bits(3)] + /// Which palette (OBP 0-7) does this object use in CGB mode? + cgb_palette: u8, + #[bits(1)] + /// Which VRAM bank (0-1) contains this object's tile? + cgb_bank: u8, + #[bits(1)] + /// Which palette (OBP 0-1) does this object use in DMG mode? + dmg_palette: u8, + /// Is this sprite flipped horizontally? + x_flip: bool, + /// Is this sprite flipped vertically? + y_flip: bool, + /// Is this sprite drawn below the window and background layers? + priority: bool, + } + + #[bitfield(u8)] + /// LCD main control register + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct LcdC { + /// In DMG mode, disables Window and Background layers. + /// In CGB mode, causes objects to render on top of Window and Background layers + bg_window_enable_or_priority: bool, + /// Controls whether objects are rendered or not + obj_enable: bool, + /// Controls the height of objects: false = 8px tall, true = 16px tall + obj_size: bool, + /// Controls bit 10 of the backgroud tile map address (false, true) => (0x9800, 0x9c00) + bg_tile_map_area: bool, + /// Controls which addressing mode to use when picking tiles for the window and background layers + bg_window_tile_data_area: bool, + /// Enables or disables the window + window_enable: bool, + /// Controls bit 10 of the window tile map address (false, true) => (0x9800, 0x9c00) + window_tile_map_area: bool, + /// Controls whether the LCD/PPU recieves power + lcd_ppu_enable: bool, + } + + #[bitfield(u8)] + /// LCD Status and Interrupt Control register + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct Stat { + #[bits(2)] + /// The current PPU operating [Mode] + mode: Mode, + /// Set to true when LYC == LY. Read only. + lyc_hit: bool, + #[bits(4)] + /// Configures PPU to raise IRQ when a condition is met + interrupts: Interrupts, + #[bits(default = true)] + _reserved: bool, + } + + #[bitfield(u8)] + /// [Stat] register interrupt configuration + pub struct Interrupts { + /// Configures PPU to raise IRQ on VBlank start + hblank: bool, + /// Configures PPU to raise IRQ on VBlank start + vblank: bool, + /// Configures PPU to raise IRQ on OAM Scan start + oam: bool, + /// Configures PPU to raise IRQ on rising edge of [lyc_hit](Stat::lyc_hit) + lyc: bool, + #[bits(4)] + _unused: (), + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum DMGColor { + White, + LGray, + DGray, + Black, + } + + impl DMGColor { + pub const fn from_bits(value: u8) -> Self { + match value & 3 { + 0 => DMGColor::White, + 1 => DMGColor::LGray, + 2 => DMGColor::DGray, + 3 => DMGColor::Black, + _ => unreachable!(), + } + } + pub const fn to_bits(self) -> u8 { + self as _ + } + } +} + +pub mod cpu; + +pub mod audio { + use crate::memory::io::BusIO; + use bitfield_struct::bitfield; + + #[derive(Debug, Default)] + pub struct APU { + wave: [u8; 0x10], + amc: AMC, + pan: SndPan, + vol: Vol, + } + + impl BusIO for APU { + fn read(&self, addr: usize) -> Option { + match addr { + 0xff10 => todo!("Channel 1 sweep"), + 0xff11 => todo!("Channel 1 length timer & duty cycle"), + 0xff12 => todo!("Channel 1 volume & envelope"), + 0xff13 => todo!("Channel 1 period low [write-only]"), + 0xff14 => todo!("Channel 1 period high & control [trigger, length enable, period]"), + 0xff24 => Some(self.vol.into_bits()), + 0xff25 => Some(self.pan.into_bits()), + 0xff26 => Some(self.amc.into_bits()), + 0xff30..=0xff3f => self.wave.read(addr - 0xff30), + _ => None, + } + } + + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + #[allow(clippy::unit_arg)] + match addr { + 0xff10..=0xff14 => todo!("Channel 1"), + 0xff16..=0xff19 => todo!("Channel 2"), + 0xff1a..=0xff1e => todo!("Channel 3"), + 0xff20..=0xff23 => todo!("Channel 4"), + 0xff24 => Some(self.vol = Vol::from_bits(data)), + // Sound panning + 0xff25 => Some(self.pan = SndPan::from_bits(data)), + // Audio Master Control + 0xff26 => Some(self.amc.set_power(data & 0x80 != 0)), + // Wave table memory + 0xff30..=0xff3f => self.wave.write(addr - 0xff30, data), + _ => None, + } + } + } + + #[bitfield(u8)] + /// Audio Master Control register + pub struct AMC { + /// Set when CH1 is playing. Read-only. + ch1_on: bool, + /// Set when CH2 is playing. Read-only. + ch2_on: bool, + /// Set when CH3 is playing. Read-only. + ch3_on: bool, + /// Set when CH4 is playing. Read-only. + ch4_on: bool, + #[bits(3)] // 3 bits are unused + _reserved: (), + /// If false, all channels are muted. Read-writable. + power: bool, + } + + #[bitfield(u8)] + /// Sound Panning register. Setting `cNS` to true will send channel `N` to speaker `S` + pub struct SndPan { + c1r: bool, + c2r: bool, + c3r: bool, + c4r: bool, + c1l: bool, + c2l: bool, + c3l: bool, + c4l: bool, + } + + #[bitfield(u8)] + /// Volume control and VIN panning register + pub struct Vol { + #[bits(3)] + /// The volume of the right output + right: u8, + /// Whether VIN is sent to the right output + vin_right: bool, + #[bits(3)] + /// The volume of the left output + left: u8, + /// Whether VIN is sent to the left output + vin_left: bool, + } +} + +#[cfg(test)] +mod tests { + use crate::memory::{io::*, Cart}; + const TEST_ROMS: &[&str] = &[ + // "roms/Legend of Zelda, The - Link's Awakening DX (USA, Europe) (Rev B) (SGB Enhanced).gbc", + // "roms/Pokemon - Crystal Version (USA, Europe) (Rev A).gbc", + // "roms/Pokemon Pinball (USA) (Rumble Version) (SGB Enhanced).gbc", + // "roms/Pokemon - Red Version (USA, Europe) (SGB Enhanced).gb", + "roms/Super Mario Land 2 - 6 Golden Coins (USA, Europe) (Rev B).gb", + "roms/Super Mario Land (World) (Rev A).gb", + "roms/Tetris (World) (Rev A).gb", + ]; + #[test] + /// Checksums the cart headers of every cart defined in [TEST_ROMS], + /// and compares them against the checksum stored in the header + fn cart_header_checksum() { + for rom in TEST_ROMS { + let rom = std::fs::read(rom).unwrap(); + let cart = Cart::from(rom); + let mut cksum = 0u8; + for index in 0x134..0x14d { + cksum = cksum + .wrapping_sub(cart.read(index).unwrap()) + .wrapping_sub(1) + } + assert_eq!(cksum, cart.headercksum().unwrap()) + } + } +} diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..c0ce813 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,463 @@ +//! RAM, CROM, and cartridge mappers +//! +//! # Gameboy Color memory map: +//! - `0000..4000` => ROM bank 0 +//! - `4000..8000` => ROM banks 1-N (via [Mapper]) +//! - `8000..A000` => VRAM Bank 0-1 +//! - `A000..C000` => Cart RAM (via [Mapper]) +//! - `C000..D000` => WRAM Bank 0 +//! - `D000..E000` => WRAM Bank 1-7 +//! - `E000..FE00` => ECHO RAM (WRAM Bank 0) +//! - `FE00..FEA0` => Object Attribute Memory (OAM) +//! - `FEA0..FF00` => Prohibited region (corrupts OAM on DMG) +//! - `FF00..FF80` => [Memory mapped I/O][MMIO] +//! - `FF80..FFFF` => HiRAM +//! - `FFFF` => Interrupt Enable Register +//! +//! [MMIO]: https://gbdev.io/pandocs/Hardware_Reg_List.html + +use self::{ + banked::{Banked, UpperBanked}, + io::BusIO, + mapper::Mapper, +}; +pub mod banked; +pub mod io; + +/// The bus operating mode +#[derive(Clone, Copy, Debug)] +pub enum Mode { + DMG, + CGB, +} +impl Mode { + pub fn from_bits(value: u8) -> Self { + match value & 0x40 { + 0 => Self::DMG, + _ => Self::CGB, + } + } + pub fn to_bits(self) -> u8 { + (self as u8) << 7 + } +} + +#[derive(Debug)] +pub struct Bus { + mode: Mode, + // TODO: define a way to have an arbitrary memory map + cart: Cart, + // VRAM is a 0x2000 B window from 0x8000..0xa000, with two banks + vram: Banked<0x8000, 0x2000, 2>, + // WRAM is a 0x2000 B window from 0xc000..0xe000. The upper half is banked. + wram: UpperBanked<0xc000, 0x1000, 8>, + // HRAM is a 0x80 B window from 0xff80..0xffff + hram: [u8; 0x80], + // Joypad driver + // Serial driver + // Timer and divider + // Interrupt controller + // Audio controller + // PPU/LCD controller + mmio: [u8; 0x80], + // +} + +impl BusIO for Bus { + fn read(&self, addr: usize) -> Option { + match addr { + // Cart ROM + 0x0000..=0x7fff => self.cart.read(addr), + // VRAM + 0x8000..=0x9fff => self.vram.read(addr), + // Cart RAM + 0xa000..=0xbfff => self.cart.read(addr), + // Work RAM + 0xc000..=0xdfff => self.wram.read(addr), + // Wram mirror + 0xe000..=0xfdff => self.wram.read(addr - 0x2000), + // Graphics OAM + 0xfe00..=0xfe9f => Some(0x0a), + // Illegal + 0xfea0..=0xfeff => None, + // Memory mapped IO + 0xff00..=0xff7f => self.mmio.read(addr & 0x7f), + // HiRAM + 0xff80..=0xffff => self.hram.read(addr & 0x7f), + _ => None, + } + } + + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + match addr { + // Cart ROM + 0x0000..=0x7fff => self.cart.write(addr, data), + // VRAM + 0x8000..=0x9fff => self.vram.write(addr, data), + // Cart RAM + 0xa000..=0xbfff => self.cart.write(addr, data), + // Work RAM + 0xc000..=0xdfff => self.wram.write(addr, data), + // Wram mirror + 0xe000..=0xfdff => self.wram.write(addr - 0x2000, data), + // Graphics OAM + 0xfe00..=0xfe9f => Some(()), + // Illegal + 0xfea0..=0xfeff => None, + // Memory mapped IO + 0xff00..=0xff7f => self.mmio.write(addr & 0x7f, data), + // HiRAM + 0xff80..=0xffff => self.hram.write(addr & 0x7f, data), + _ => None, + } + } +} + +impl Bus { + pub fn new(cart: Cart) -> Self { + Self { + cart, + mode: Mode::CGB, + vram: Default::default(), + wram: Default::default(), + mmio: [0; 128], + hram: [0; 128], + } + } + pub fn mmio_read(&self, addr: usize) -> Option { + match addr { + // Joypad I/O + 0xff00 => eprintln!("DMG Joypad output"), + // DMG/CGB Serial transfer + 0xff01..=0xff02 => eprintln!("DMG Serial transfer"), + // DMG Timer and divider + 0xff04..=0xff07 => eprintln!("DMG Timer and divider"), + // Interrupt controller (IF) + 0xff0f => eprintln!("Interrupt controller"), + // DMG Audio + 0xff10..=0xff26 => eprintln!("DMG Audio"), + // DMG Wave pattern memory + 0xff30..=0xff3f => eprintln!("DMG Wave pattern memory"), + // DMG LCD control, status, position, scrolling, and palettes + 0xff40..=0xff4b => { + eprintln!("DMG LCD control, status, position, scrolling, and palettes") + } + // Disable bootrom + 0xff50 => eprintln!("Set to non-zero to disable bootrom"), + _ => match self.mode { + Mode::CGB => { + if let Some(data) = self.mmio_read_cgb(addr) { + return Some(data); + } + } + Mode::DMG => {} + }, + } + self.mmio.get(addr % 0x80).copied() + } + + pub fn mmio_read_cgb(&self, addr: usize) -> Option { + match addr { + // CGB Key1 speed switch + 0xff4d => eprintln!("CGB Key1"), + // CGB VRam bank select + 0xff4f => return Some(self.vram.selected() as u8 | 0xfc), + // CGB Vram DMA controller + 0xff51..=0xff55 => eprintln!("CGB Vram DMA"), + // CGB Infrared + 0xff56 => eprintln!("Infrared port"), + // CGB BG/OBJ palettes + 0xff68..=0xff6b => eprintln!("CGB BG/Obj palettes"), + // CGB WRam bank select + 0xff70 => return Some(self.wram.selected() as u8 | 0xfe), + // CGB Undocumented registers + 0xff72..=0xff75 => return Some(00), + // CGB PCM Read registers + 0xff76..=0xff77 => eprintln!("CGB PCM Read Registers"), + _ => {} + } + None + } + + pub fn mmio_write(&mut self, addr: usize, data: u8) -> Option<()> { + match addr { + // Joypad I/O + 0xff00 => eprintln!("DMG Joypad output"), + // DMG/CGB Serial transfer + 0xff01..=0xff02 => eprintln!("DMG Serial transfer"), + // DMG Timer and divider + 0xff04..=0xff07 => eprintln!("DMG Timer and divider"), + // Interrupt controller (IF) + 0xff0f => eprintln!("Interrupt controller"), + // DMG Audio + 0xff10..=0xff26 => eprintln!("DMG Audio"), + // DMG Wave pattern memory + 0xff30..=0xff3f => eprintln!("DMG Wave pattern memory"), + // DMG LCD control, status, position, scrolling, and palettes + 0xff40..=0xff4b => { + eprintln!("DMG LCD control, status, position, scrolling, and palettes") + } + // Disable bootrom + 0xff50 => todo!("Set to non-zero to disable bootrom"), + _ => match self.mode { + Mode::CGB => { + self.mmio_write_cgb(addr, data); + } + Mode::DMG => {} + }, + } + self.mmio.write(addr % 0x80, data) + } + + pub fn mmio_write_cgb(&mut self, addr: usize, data: u8) -> Option<()> { + match addr { + // CGB Key1 speed switch + 0xff4d => eprintln!("CGB Key1"), + // CGB VRam bank select + 0xff4f => { + self.vram.select(data as usize); + return Some(()); + } + // CGB Vram DMA controller + 0xff51..=0xff55 => eprintln!("CGB Vram DMA"), + // CGB Infrared + 0xff56 => eprintln!("Infrared port"), + // CGB BG/OBJ palettes + 0xff68..=0xff6b => eprintln!("CGB BG/Obj palettes"), + // CGB WRam bank select + 0xff70 => { + self.wram.select(data as usize); + return Some(()); + } + // CGB Undocumented registers + 0xff72..=0xff75 => return Some(()), + // CGB PCM Read registers + 0xff76..=0xff77 => eprintln!("CGB PCM Read Registers"), + _ => {} + } + None + } +} + +pub struct Cart { + pub rom: Vec, + pub ram: Vec, + mapper: Box, +} + +mod cart { + use crate::memory::io::BusAux; + + use super::{mapper::*, BusIO, Cart}; + use std::fmt::Debug; + impl BusIO for Cart { + fn read(&self, addr: usize) -> Option { + match self.mapper.read(addr) { + Response::None => None, + Response::Data(data) => Some(data), + Response::Ram(addr) => self.ram.get(addr).copied(), + Response::Rom(addr) => self.rom.get(addr).copied(), + } + } + + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + match self.mapper.write(addr, data) { + Response::None => None, + Response::Data(_) => Some(()), + Response::Ram(addr) => self.ram.get_mut(addr).map(|rambyte| *rambyte = data), + Response::Rom(addr) => { + panic!("Attempted to write to ROM address :{addr:x}") + } + } + } + } + /// Read cartridge header, as defined in the [Pan docs](https://gbdev.io/pandocs/The_Cartridge_Header.html) + impl Cart { + pub fn new(rom: Vec) -> Self { + let rom_size = rom.romsize().expect("ROM should have cart header!"); + let ram_size = rom.ramsize().expect("ROM should have cart header!"); + let cart_type = rom.carttype().expect("ROM should have cart header!"); + Self { + mapper: match cart_type { + 0 => Box::::default(), + 1 => Box::new(mbc1::MBC1::new(rom_size, 0)), // ROM + 2 => Box::new(mbc1::MBC1::new(rom_size, ram_size)), // ROM + RAM + 3 => Box::new(mbc1::MBC1::new(rom_size, ram_size)), // ROM + RAM + Battery + // _ => Box::::default(), + n => todo!("Mapper 0x{n:02x}"), + }, + rom, + ram: vec![], + } + } + } + + impl Debug for Cart { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Cart") + .field("rom", &self.rom) + .field("ram", &self.ram) + .finish_non_exhaustive() + } + } + + impl From> for Cart { + fn from(value: Vec) -> Self { + Self::new(value) + } + } + impl From<&[u8]> for Cart { + fn from(value: &[u8]) -> Self { + Self::new(Vec::from(value)) + } + } + + impl> FromIterator for Cart { + fn from_iter>(iter: T) -> Self { + Self::new(iter.into_iter().map(|item| *(item.as_ref())).collect()) + } + } + + impl std::fmt::Display for Cart { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Entry: {:02x?}", self.entry())?; + // if let Some(logo) = self.logo() { + // write!(f, "Logo: ")?; + // for byte in logo { + // write!(f, "{byte:02x}")?; + // } + // writeln!(f)?; + // } + write!(f, "Title: {:02x?}", self.title())?; + write!(f, "MFCode: {:02x?}", self.mfcode())?; + write!(f, "CGB Flag: {:02x?}", self.cgbflag())?; + write!(f, "New Lic: {:02x?}", self.newlicensee())?; + write!(f, "SGB Flag: {:02x?}", self.sgbflag())?; + write!(f, "Cart Type: {:02x?}", self.carttype())?; + write!(f, "ROM Size: {:02x?}", self.romsize())?; + write!(f, "RAM Size: {:02x?}", self.ramsize())?; + write!(f, "Dest Code: {:02x?}", self.destcode())?; + write!(f, "Old Lic: {:02x?}", self.oldlicencee())?; + write!(f, "ROM Ver: {:02x?}", self.maskromver())?; + write!(f, "H Cksum: {:02x?}", self.headercksum())?; + write!(f, "G Cksum: {:04x?}", self.globalcksum()) + } + } +} + +pub mod mapper { + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Response { + /// No data to return + #[default] + None, + /// [Mapper] returned some data directly + Data(u8), + /// [Mapper] mapped incoming address to cart RAM + Ram(usize), + /// [Mapper] mapped incoming address to cart ROM + Rom(usize), + } + + pub trait Mapper { + fn read(&self, addr: usize) -> Response; + fn write(&mut self, addr: usize, val: u8) -> Response; + } + + pub mod no_mbc { + use super::*; + #[derive(Clone, Copy, Debug, Default)] + pub struct NoMBC; + impl Mapper for NoMBC { + fn read(&self, addr: usize) -> Response { + match addr { + 0x0000..=0x7fff => Response::Rom(addr), + 0xa000..=0xbfff => Response::Ram(addr - 0x8000), + _ => Response::None, + } + } + + fn write(&mut self, addr: usize, _val: u8) -> Response { + match addr { + 0xa000..=0xbfff => Response::Ram(addr - 0xA000), + _ => Response::None, + } + } + } + } + + pub mod mbc1 { + use super::*; + + #[derive(Clone, Copy, Debug, Default)] + pub struct MBC1 { + rom_mask: u8, + ram_mask: u8, + mode: u8, + rom_bank: u8, + ram_bank: u8, + ram_enable: bool, + } + + impl MBC1 { + pub fn new(rom_size: usize, ram_size: usize) -> Self { + Self { + rom_mask: (rom_size).wrapping_sub(1) as u8, + ram_mask: (ram_size).wrapping_sub(1) as u8, + rom_bank: 1, + ..Default::default() + } + } + pub fn rom_bank_lower(&self) -> usize { + (((self.ram_bank << 5) & self.rom_mask) as usize) << 14 + } + pub fn rom_bank_upper(&self) -> usize { + ((self.rom_bank | (self.ram_bank << 5) & self.rom_mask) as usize) << 14 + } + pub fn ram_bank_base(&self) -> usize { + ((self.ram_bank & self.ram_mask) as usize) << 13 + } + } + + impl Mapper for MBC1 { + fn read(&self, addr: usize) -> Response { + match (addr, self.mode) { + (0x0000..=0x3fff, 0) => Response::Rom(addr), + (0x0000..=0x3fff, _) => Response::Rom(self.rom_bank_lower() | (addr & 0x3fff)), + + (0x4000..=0x7fff, _) => Response::Rom(self.rom_bank_upper() | (addr & 0x3fff)), + + (0xa000..=0xbfff, 0) => Response::Ram(addr & 0x1fff), + (0xa000..=0xbfff, _) => Response::Ram(self.ram_bank_base() | (addr & 0x1fff)), + + _ => Response::None, + } + } + + fn write(&mut self, addr: usize, val: u8) -> Response { + match (addr, self.mode) { + (0x0000..=0x1fff, _) => { + self.ram_enable = val & 0xf == 0xa; + } + (0x2000..=0x3fff, _) => { + self.rom_bank = (val & 0x1f).max(1); + } + (0x4000..=0x5fff, _) => { + self.ram_bank = val & 0x3; + } + (0x6000..=0x7fff, _) => { + self.mode = val & 1; + } + (0xa000..=0xbfff, 0) if self.ram_enable => { + return Response::Ram(addr & 0x1fff); + } + (0xa000..=0xbfff, 1) if self.ram_enable => { + return Response::Ram((self.ram_bank as usize & 0b11 << 13) | addr & 0x1fff) + } + _ => return Response::None, + } + Response::Data(0) + } + } + } +} diff --git a/src/memory/banked.rs b/src/memory/banked.rs new file mode 100644 index 0000000..422dc71 --- /dev/null +++ b/src/memory/banked.rs @@ -0,0 +1,167 @@ +//! Shares the upper half of a memory region among a number of memory banks +use super::io::BusIO; + +/// Shares an entire memory region from BASE to BASE + LEN among multiple banks +#[derive(Clone, Debug)] +pub struct Banked { + selected: usize, + mem: [[u8; LEN]; BANKS], +} + +impl Banked { + /// Creates a new [Banked](Banked) + pub fn new() -> Self { + Self::default() + } + /// Selects a bank between 0 and `LEN` - 1, saturating + pub fn select(&mut self, bank: usize) { + self.selected = bank.min(BANKS - 1) + } + pub fn selected(&self) -> usize { + self.selected + } +} + +impl Default for Banked { + #[rustfmt::skip] + fn default() -> Self { + Self { selected: 0, mem: [[0; LEN]; BANKS] } + } +} + +impl BusIO for Banked { + fn read(&self, addr: usize) -> Option { + let addr = addr.checked_sub(BASE).filter(|v| *v < LEN)?; + self.mem[self.selected].read(addr) + } + + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + let addr = addr.checked_sub(BASE).filter(|v| *v < LEN)?; + self.mem[self.selected].write(addr, data) + } +} + +impl AsRef<[u8]> for Banked { + fn as_ref(&self) -> &[u8] { + //! Adapted from [core::slice::flatten](https://doc.rust-lang.org/std/primitive.slice.html#method.flatten) + let slice = self.mem.as_slice().as_ptr().cast(); + // SAFETY: + // - `LEN * BANKS` cannot overflow because `self` is already in the address space. + // - `[T]` is layout-identical to `[T; N]` + unsafe { core::slice::from_raw_parts(slice, LEN * BANKS) } + } +} + +impl AsMut<[u8]> for Banked { + fn as_mut(&mut self) -> &mut [u8] { + let slice = self.mem.as_mut_slice().as_mut_ptr().cast(); + // SAFETY: see [Banked::as_ref] + unsafe { core::slice::from_raw_parts_mut(slice, LEN * BANKS) } + } +} + +// --------------------------------- // + +/// Shares the region from `BASE` + `LEN` to `BASE` + 2 * `LEN` among `BANKS`-1 banks, +/// while keeping the lower bank constant +/// # Constants: +/// - `BASE`: The location where the [UpperBanked] begins +/// - `LEN`: The length of one bank. This is *half* of the in-memory length, +/// - `BANKS`: The number of banks +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UpperBanked { + selected: usize, + mem: [[u8; LEN]; BANKS], +} + +impl UpperBanked { + /// Creates a new [`UpperBanked`](Banked) + pub fn new() -> Self { + Self::default() + } + /// Selects a bank between 1 and `LEN` - 1, saturating to `LEN` - 1 + pub fn select(&mut self, bank: usize) { + self.selected = bank.clamp(1, BANKS - 1) + } + /// Gets the currently selected upper bank + pub fn selected(&self) -> usize { + self.selected + } +} + +impl Default + for UpperBanked +{ + #[rustfmt::skip] + fn default() -> Self { + Self { selected: 1, mem: [[0; LEN]; BANKS] } + } +} + +impl BusIO + for UpperBanked +{ + fn read(&self, addr: usize) -> Option { + // Pray this gets optimized + let addr = addr.checked_sub(BASE).filter(|v| *v < LEN * 2)?; + let (bank, addr) = (addr / LEN, addr % LEN); + match bank { + 0 => self.mem[0].read(addr), + _ => self.mem[self.selected].read(addr), + } + } + + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + // Pray this gets optimized + let addr = addr.checked_sub(BASE).filter(|v| *v < LEN * 2)?; + let (bank, addr) = (addr / LEN, addr % LEN); + match bank { + 0 => self.mem[0].write(addr, data), + _ => self.mem[self.selected].write(addr, data), + } + } +} + +impl AsRef<[u8]> + for UpperBanked +{ + fn as_ref(&self) -> &[u8] { + let slice_ptr = self.mem.as_slice().as_ptr().cast(); + // SAFETY: see [Banked::as_ref] + unsafe { core::slice::from_raw_parts(slice_ptr, LEN * BANKS) } + } +} + +impl AsMut<[u8]> + for UpperBanked +{ + fn as_mut(&mut self) -> &mut [u8] { + let slice_ptr = self.mem.as_mut_slice().as_mut_ptr().cast(); + // SAFETY: see [UpperBanked::as_ref] + unsafe { core::slice::from_raw_parts_mut(slice_ptr, LEN * BANKS) } + } +} + +#[cfg(test)] +mod test { + use super::UpperBanked; + #[test] + fn upper_banked_asref_slice() { + let mut ub = UpperBanked::<0, 10, 20>::new(); + let mut counter = 0u8; + for bank in &mut ub.mem { + for addr in bank { + *addr = counter; + counter += 1; + } + } + for (idx, data) in ub.as_ref().iter().enumerate() { + assert_eq!(idx, *data as usize) + } + } + #[test] + fn upper_banked_bounds() { + let ub = UpperBanked::<0, 8, 32>::new(); + assert_eq!(ub.as_ref().len(), 8 * 32); + } +} diff --git a/src/memory/io.rs b/src/memory/io.rs new file mode 100644 index 0000000..d100cb3 --- /dev/null +++ b/src/memory/io.rs @@ -0,0 +1,222 @@ +//! Trait for transferring data to and from a CPU + +pub mod iter { + //! Iterates over the bytes in a [BusIO] + use super::BusIO; + + pub struct Iter<'a> { + start: usize, + bus: &'a dyn BusIO, + } + impl<'a> Iter<'a> { + pub fn new(bus: &'a dyn BusIO) -> Self { + Self { start: 0, bus } + } + pub fn start_at(bus: &'a dyn BusIO, start: usize) -> Self { + Self { start, bus } + } + } + impl<'a> Iterator for Iter<'a> { + type Item = u8; + fn next(&mut self) -> Option { + let Self { start, bus } = self; + let out = bus.read(*start); + if out.is_some() { + *start += 1; + } + out + } + } +} + +/// Trait for transferring data to and from a CPU +pub trait BusIO { + // required functions + /// Attempts to read a byte from self, returning None if the operation failed. + fn read(&self, addr: usize) -> Option; + /// Attempts to write a byte to self, returning None if the operation failed. + fn write(&mut self, addr: usize, data: u8) -> Option<()>; + + /// Does some implementation-specific debug function + fn diag(&mut self, _param: usize) {} + + /// Gets an iterator which reads the bytes from self + fn read_iter(&self) -> iter::Iter + where + Self: Sized, + { + iter::Iter::new(self) + } + /// Gets an iterator which reads bytes in an exclusive range + fn read_iter_from(&self, start: usize) -> iter::Iter + where + Self: Sized, + { + iter::Iter::start_at(self, start) + } +} + +impl BusIO for [u8; N] { + fn read(&self, addr: usize) -> Option { + self.get(addr).copied() + } + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + self.get_mut(addr).map(|v| *v = data) + } +} + +impl BusIO for [u8] { + fn read(&self, addr: usize) -> Option { + self.get(addr).copied() + } + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + self.get_mut(addr).map(|v| *v = data) + } +} + +impl BusIO for Vec { + fn read(&self, addr: usize) -> Option { + self.get(addr).copied() + } + fn write(&mut self, addr: usize, data: u8) -> Option<()> { + self.get_mut(addr).map(|v| *v = data) + } +} + +impl BusAux for T {} +pub trait BusAux: BusIO { + // derived functions + /// Copies a contiguous region from the bus. Returns an incomplete copy on error. + fn read_arr(&self, addr: usize) -> Result<[u8; N], [u8; N]> { + let mut out = [0; N]; + for (idx, byte) in out.iter_mut().enumerate() { + match self.read(addr + idx) { + Some(value) => *byte = value, + None => return Err(out), + } + } + Ok(out) + } + /// Gets the cart entrypoint bytes (machine code starting at 0x100) + fn entry(&self) -> Result<[u8; 4], [u8; 4]> { + self.read_arr(0x100) + } + /// Gets the logo bytes (0x104..0x134) + fn logo(&self) -> Result<[u8; 0x30], [u8; 0x30]> { + self.read_arr(0x104) + } + /// Gets the title bytes as a String + /// # Note + /// The title area was repartitioned for other purposes during the Game Boy's life. + /// See [mfcode](BusAux::mfcode) and [cgbflag](BusAux::cgbflag). + fn title(&self) -> Result { + // Safety: Chars are checked to be valid ascii before cast to char + Ok(self + .read_arr(0x134)? // to 0x144 + .iter() + .copied() + .take_while(|c| *c != 0 && c.is_ascii()) + .map(|c| unsafe { char::from_u32_unchecked(c as u32) }) + .collect()) + } + /// Gets the manufacturer code bytes + fn mfcode(&self) -> Result<[u8; 4], [u8; 4]> { + self.read_arr(0x13f) + } + /// Gets the CGB Flag byte + fn cgbflag(&self) -> Option { + self.read(0x143) + } + /// Gets the New Licensee bytes + fn newlicensee(&self) -> Result<[u8; 2], [u8; 2]> { + self.read_arr(0x144) + } + /// Gets the SGB Flag byte + fn sgbflag(&self) -> Option { + self.read(0x146) + } + /// Gets the Cartridge Type byte + fn carttype(&self) -> Option { + self.read(0x147) + } + /// Gets the size of the cartridge ROM (in 0x8000 byte banks) + fn romsize(&self) -> Option { + self.read(0x148).map(|s| 1usize.wrapping_shl(s as u32 + 1)) + } + /// Gets the size of the cartridge RAM (in 0x8000 byte banks) + fn ramsize(&self) -> Option { + self.read(0x149).map(|s| match s { + 0x02 => 1, + 0x03 => 4, + 0x04 => 16, + 0x05 => 8, + _ => 0, + }) + } + /// Gets the destination region code + fn destcode(&self) -> Option { + self.read(0x14a) + } + /// Gets the old licensee byte + fn oldlicencee(&self) -> Option { + self.read(0x14b) + } + /// Gets the Maskrom Version byte. Usually 0 + fn maskromver(&self) -> Option { + self.read(0x14c) + } + /// Gets the header checksum + fn headercksum(&self) -> Option { + self.read(0x14d) + } + /// Gets the checksum of all bytes in ROM + fn globalcksum(&self) -> Option { + self.read_arr(0x14e).ok().map(u16::from_be_bytes) + } +} + +pub mod interrupt { + use super::BusIO; + use bitfield_struct::bitfield; + + /// Interrupt Enable register + const IE: usize = 0xffff; + /// Interrupt Flags register + const IF: usize = 0xff0f; + + #[bitfield(u8)] + #[derive(PartialEq, Eq)] + pub struct Interrupt { + vblank: bool, + lcd: bool, + timer: bool, + serial: bool, + joypad: bool, + #[bits(3)] + _reserved: (), + } + + impl Interrupts for B {} + /// Functionality for working with [Interrupt]s + pub trait Interrupts: BusIO { + /// Gets the set of enabled [Interrupt]s + fn enabled(&self) -> Option { + self.read(IE).map(Interrupt::from_bits) + } + /// Gets the set of raised [Interrupt]s + #[inline] + fn raised(&self) -> Option { + self.read(IF).map(Interrupt::from_bits) + } + /// Raises [Interrupt]s with the provided mask. + fn raise(&mut self, int: Interrupt) { + let flags = self.raised().unwrap_or_default(); + let _ = self.write(IF, flags.into_bits() | int.into_bits()); + } + /// Clears [Interrupt]s with the provided mask. + fn clear(&mut self, int: Interrupt) { + let flags = self.raised().unwrap_or_default(); + let _ = self.write(IF, flags.into_bits() & !int.into_bits()); + } + } +}