diff --git a/src/emu.rs b/src/emu.rs index bf6ae12..27da06d 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -21,6 +21,44 @@ pub struct GameBoy { bp_manager: BreakPointManager, } +fn add(a: u8, b: u8) -> u8 { + if std::u8::MAX - b < a { + ((a as u16 + b as u16) - std::u8::MAX as u16) as u8 - 1 + } else { + a + b + } +} + +fn sub(a: u8, b: u8) -> u8 { + if a < b { + std::u8::MAX - (b - a) + 1 + } else { + a - b + } +} + +fn add_inplace(a: &mut u8, b: u8) { + *a = add(*a, b) +} + +fn sub_inplace(a: &mut u8, b: u8) { + *a = sub(*a, b) +} + +enum Flag { + Z, N, H ,CY +} + +fn flag_set(f: Flag, v: u8) -> bool { + use Flag::*; + match f { + Z => v << 0 >> 7 == 1, + N => v << 1 >> 6 == 1, + H => v << 2 >> 5 == 1, + CY => v << 3 >> 4 == 1, + } +} + impl GameBoy { pub fn new() -> GameBoy { GameBoy { @@ -34,10 +72,23 @@ impl GameBoy { } } + fn set(adr: u16, val: u8) { // check for special registers } + fn set_flag(&mut self, f: Flag, v: bool) { + let r = &mut self.reg[Register::F]; + let v = v as u8; + use Flag::*; + match f { + Z => *r = v << 7, + N => *r = v << 6, + H => *r = v << 5, + CY => *r = v << 4 + } + } + fn set_fast(adr: u16, val: u8) { // no checks for special registers } @@ -157,8 +208,8 @@ impl GameBoy { match postOp { PostOp::None => (), - PostOp::Inc(reg) => self.reg[reg] += 1, - PostOp::Dec(reg) => self.reg[reg] -= 1, + PostOp::Inc(reg) => add_inplace(&mut self.reg[reg], 1), + PostOp::Dec(reg) => sub_inplace(&mut self.reg[reg], 1), } } @@ -241,15 +292,33 @@ impl GameBoy { let new_pc = self.read16(pc+1); self.pc = new_pc; println!("jp {pc:#06x}->{new_pc:#06x}"); + // TODO remember cycle count! return; } - Jr => { - let e = self.mem[pc + 1] as i8 + 2; - let old_pc = self.pc; - let new_pc = (old_pc as i16 + e as i16) as u16; - self.pc = new_pc; - println!("jr {new_pc:#06x} <- {old_pc:#06x} + {e}"); - return; + Jr(cond) => { + let jump = match cond { + JmpCond::None => true, + JmpCond::Flags(f) => { + let reg_f = self.reg[Register::F]; + match f { + 0b00 => !flag_set(Flag::Z, reg_f), + 0b01 => flag_set(Flag::Z, reg_f), + 0b10 => !flag_set(Flag::CY, reg_f), + 0b11 => flag_set(Flag::CY, reg_f), + _ => panic!("invalid jump condition") + } + } + }; + + if jump { + let e = self.mem[pc + 1] as i8 + 2; + let old_pc = self.pc; + let new_pc = (old_pc as i16 + e as i16) as u16; + self.pc = new_pc; + println!("jr {new_pc:#06x} <- {old_pc:#06x} + {e}"); + // TODO remember cycle count 3 for jump, 2 for not!!! + return; + } } Call => { let sp = self.sp as usize; @@ -284,15 +353,21 @@ impl GameBoy { self.sp += 2; println!("pop Reg({reg:#04b})"); } - Inc(reg) => { - self.reg[reg] += 1; - println!("Inc Reg({reg:#04b})"); + Inc(dst) => { + match dst { + ValSrc::Reg(reg) => add_inplace(&mut self.reg[reg], 1), + _ => todo!() + } + println!("Inc {dst:?}"); } - Dec(reg) => { - self.reg[reg as usize] -= 1; - println!("Dec Reg({reg:#04b})"); + Dec(dst) => { + match dst { + ValSrc::Reg(reg) => sub_inplace(&mut self.reg[reg], 1), + _ => todo!() + } + println!("Dec {dst:?}"); } - Ld(dst, src, postOp) => self.op_ld(dst, src, postOp), + Ld(dst, src, post_op) => self.op_ld(dst, src, post_op), Ld16(dst, src) => self.op_ld16(dst, src), Bit(bit_op, src) => { let v = match src { @@ -324,3 +399,28 @@ impl GameBoy { self.pc = pc as u16; } } + +#[cfg(test)] +mod tests { + // #[test] + // fn add_overflow() { + // use crate::emu::add; + // assert_eq!(add(1, 1), 2); + + // assert_eq!(add(std::u8::MAX, 1), 0); + // assert_eq!(add(std::u8::MAX, 2), 1); + // assert_eq!(add(1, std::u8::MAX), 0); + + // assert_eq!(add(std::u8::MAX - 3, 3), std::u8::MAX); + // assert_eq!(add(3, std::u8::MAX - 3), std::u8::MAX); + // } + + // #[test] + // fn sub_underflow() { + // use crate::emu::sub; + // assert_eq!(sub(1, 1), 0); + // assert_eq!(sub(1, 2), std::u8::MAX); + // assert_eq!(sub(1, 3), std::u8::MAX - 1); + // } +} + diff --git a/src/op.rs b/src/op.rs index eef01aa..265f1d6 100644 --- a/src/op.rs +++ b/src/op.rs @@ -26,8 +26,8 @@ pub enum Instr { // Arith // TODO can we do Inc16/Inc8 with one via Register::WIDE - Inc(Register), - Dec(Register), + Inc(ValSrc), + Dec(ValSrc), Bit(BitOp, ValSrc), // CPU ctl @@ -35,7 +35,7 @@ pub enum Instr { // Jump Jp, - Jr, + Jr(JmpCond), Call, Ret, } @@ -61,6 +61,12 @@ pub enum ValSrc { Direct, } +#[derive(Debug)] +pub enum JmpCond { + None, + Flags(u8) +} + #[derive(Debug, Copy, Clone, PartialEq)] pub enum Register { A, F, @@ -195,7 +201,7 @@ impl TryFrom for Operation { let (inst, cycl, offs) = match value { 0b00_000_000 => (Nop, 1, 1), 0b11_000_011 => (Jp, 4, 3), - 0b00_011_000 => (Jr, 3, 2), + 0b00_011_000 => (Jr(JmpCond::None), 3, 2), 0b11_001_101 => (Call, 6, 3), 0b11_001_001 => (Ret, 4, 1), 0b11_110_011 => (Di, 1, 1), @@ -217,6 +223,10 @@ impl TryFrom for Operation { 0b11_110_110 => (Bit(Or, Direct), 2, 2), // TODO can bit be grouped via HD & T2? 0b11_101_110 => (Bit(Xor, Direct), 2, 2), 0b11_100_110 => (Bit(And, Direct), 2, 2), + _ if value & (HD_MSK | 0b100_000 | T2_MSK) == 0b00_100_000 => { + let cc = (value & 0b11_000) >> 3; + (Jr(JmpCond::Flags(cc)), 3, 2) // TODO cycle can be 2 or 3! + } _ if (value & (HD_MSK | T1_MSK) == 0b10_110_000) => { (Bit(Or, Reg(Register::try_from(value & 0b111)?)), 1, 1) } @@ -227,10 +237,24 @@ impl TryFrom for Operation { (Bit(And, Reg(Register::try_from(value & 0b111)?)), 1, 1) } _ if (value & (HD_MSK | 0b1_000 | T2_MSK) == 0b00_000_011) => { - (Inc(Register::from16_rep(payload(value))), 2, 1) + (Inc(Reg(Register::from16_rep(payload(value)))), 2, 1) } _ if (value & (HD_MSK | 0b1_000 | T2_MSK) == 0b00_001_011) => { - (Dec(Register::from16_rep(payload(value))), 2, 1) + (Dec(Reg(Register::from16_rep(payload(value)))), 2, 1) + } + _ if (value & (HD_MSK | T2_MSK) == 0b00_000_100) => { + if (T1_MSK & value) == 0b110_000 { + (Inc(Mem(AddrLoc::Reg(HL))), 3, 1) + } else { + (Inc(Reg(Register::try_from((value & T1_MSK) >> 3)?)), 1, 1) + } + } + _ if (value & (HD_MSK | T2_MSK) == 0b00_000_101) => { + if (T1_MSK & value) == 0b110_000 { + (Dec(Mem(AddrLoc::Reg(HL))), 3, 1) + } else { + (Dec(Reg(Register::try_from((value & T1_MSK) >> 3)?)), 1, 1) + } } _ if (value & (HD_MSK | 0b1_000 | T2_MSK) == 0b11_000_101) => { (Psh(Register::repr_psh(payload(value))), 4, 1)