use crate::io::{Audio, Serial}; use crate::state::{flag, reg, GBState, MemError}; // The opcodes functions are returning the number of cycles used. impl GBState { fn r_16b_from_pc(&mut self) -> Result { let p: u16 = self.mem.r(self.cpu.pc)? as u16 | ((self.mem.r(self.cpu.pc + 1)? as u16) << 8); self.cpu.pc += 2; Ok(p) } fn r_8b_from_pc(&mut self) -> Result { let p = self.mem.r(self.cpu.pc)?; self.cpu.pc += 1; Ok(p) } fn ldrr(&mut self, n1: u8, n2: u8) -> Result<(), MemError> { // Load a register into another register // LD r, r self.w_reg(n1, self.r_reg(n2)?) } fn ldr8(&mut self, n1: u8) -> Result { // Load an raw 8b value into a register let p = self.r_8b_from_pc()?; self.w_reg(n1, p)?; Ok(8) } fn ldrr16(&mut self, rr: u8, x: u16) { // Load a raw 16b value into a register self.cpu.w16(rr, x); } fn ldnnsp(&mut self) -> Result { // Load SP into an arbitrary position in memory let p = self.r_16b_from_pc()?; self.mem.w(p, (self.cpu.sp & 0xff) as u8)?; self.mem.w(p + 1, (self.cpu.sp >> 8) as u8)?; Ok(20) } fn ldsphl(&mut self) -> u64 { self.cpu.sp = self.cpu.r16(reg::HL); 8 } fn ldnna(&mut self, nn: u16) -> Result<(), MemError> { // Load A into an arbitrary position in memory self.mem.w(nn, self.cpu.r[reg::A as usize])?; Ok(()) } fn ldann(&mut self, nn: u16) -> Result<(), MemError> { // Load A from an arbitrary position in memory self.cpu.r[reg::A as usize] = self.mem.r(nn)?; Ok(()) } pub fn push(&mut self, x: u16) -> Result<(), MemError> { self.cpu.sp -= 2; self.mem.w(self.cpu.sp, (x & 0xff) as u8)?; self.mem.w(self.cpu.sp + 1, (x >> 8) as u8)?; Ok(()) } fn pop(&mut self) -> Result { let res = self.mem.r(self.cpu.sp)? as u16 | ((self.mem.r(self.cpu.sp + 1)? as u16) << 8); self.cpu.sp += 2; Ok(res) } fn jr8(&mut self) -> Result { // Unconditional relative jump let p = self.r_8b_from_pc()?; self.cpu.pc = (self.cpu.pc as i16 + p as i8 as i16) as u16; Ok(12) } fn jrcc8(&mut self, n1: u8) -> Result { // Conditional relative jump let p = self.r_8b_from_pc()?; let mut cycles = 8; if self.cpu.check_flag(n1 & 0b11) { cycles += 4; self.cpu.pc = (self.cpu.pc as i16 + p as i8 as i16) as u16; } Ok(cycles) } fn jp16(&mut self) -> Result { // Unconditional absolute jump let p = self.r_16b_from_pc()?; self.cpu.pc = p; Ok(16) } fn jphl(&mut self) -> u64 { // Unconditional absolute jump to HL self.cpu.pc = self.cpu.r16(reg::HL); 4 } fn jpcc16(&mut self, n1: u8) -> Result { // Conditional absolute jump let p = self.r_16b_from_pc()?; let mut cycles = 8; if self.cpu.check_flag(n1 & 0b11) { cycles += 4; self.cpu.pc = p; } Ok(cycles) } fn call(&mut self) -> Result { // Unconditional function call let p = self.r_16b_from_pc()?; self.push(self.cpu.pc)?; self.cpu.pc = p; Ok(24) } fn callcc(&mut self, n1: u8) -> Result { // Conditional function call let p = self.r_16b_from_pc()?; let mut cycles = 12; if self.cpu.check_flag(n1 & 0b11) { cycles += 12; self.push(self.cpu.pc)?; self.cpu.pc = p; } Ok(cycles) } fn ret(&mut self) -> Result { let res = self.pop()?; if res == 0 { println!("DEBUG: {:?}", self.cpu); panic!("RET to start"); } self.cpu.pc = res; Ok(16) } fn retcc(&mut self, n1: u8) -> Result { let mut cycles = 8; if self.cpu.check_flag(n1 & 0b11) { cycles += 12; self.cpu.pc = self.pop()?; } Ok(cycles) } fn ld00a(&mut self, n1: u8) -> Result { // Load register A into or from memory pointed by rr (BC, DE or HL(+/-)) // LD (rr), A // LD A, (rr) let ptr_reg = match n1 & 0b110 { 0b000 => reg::B, 0b010 => reg::C, _ => reg::HL, }; if n1 & 0b001 == 1 { self.cpu.r[reg::A as usize] = self.mem.r(self.cpu.r16(ptr_reg))?; } else { self.mem .w(self.cpu.r16(ptr_reg), self.cpu.r[reg::A as usize])?; } if n1 & 0b110 == 0b100 { self.cpu.w16(reg::HL, self.cpu.r16(reg::HL) + 1); // (HL+) } if n1 & 0b110 == 0b110 { self.cpu.w16(reg::HL, self.cpu.r16(reg::HL) - 1); // (HL-) } Ok(8) } fn inc8(&mut self, n1: u8) -> Result { // Increment 8 bit register self.w_reg(n1, self.r_reg(n1)? + 1)?; self.cpu.r[reg::F as usize] &= !(flag::N | flag::ZF | flag::H); if self.r_reg(n1)? == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } if self.r_reg(n1)? & 0xf == 0x0 { self.cpu.r[reg::F as usize] |= flag::H; } Ok(4) } fn dec8(&mut self, n1: u8) -> Result { // Decrement 8 bit register self.w_reg(n1, self.r_reg(n1)? - 1)?; self.cpu.r[reg::F as usize] |= flag::N; self.cpu.r[reg::F as usize] &= !(flag::ZF | flag::H); if self.r_reg(n1)? == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } if self.r_reg(n1)? & 0xf == 0xf { self.cpu.r[reg::F as usize] |= flag::H; } Ok(4) } fn inc16(&mut self, rr: u8) -> u64 { // Increment 16 bit register self.cpu.w16(rr, self.cpu.r16(rr) + 1); 8 } fn dec16(&mut self, rr: u8) -> u64 { // Decrement 16 bit register self.cpu.w16(rr, self.cpu.r16(rr) - 1); 8 } fn ccf(&mut self) { // Flip carry flag self.cpu.r[reg::F as usize] = (self.cpu.r[reg::F as usize] & 0b10011111) ^ 0b00010000 } fn scf(&mut self) { // Set carry flag self.cpu.r[reg::F as usize] = (self.cpu.r[reg::F as usize] & 0b10011111) | 0b00010000 } fn daa(&mut self) { // Decimal Adjust Accumulator // Adjust the A register after a addition or substraction to keep valid BCD representation let nibble_low = self.cpu.r[reg::A as usize] & 0b1111; let sub_flag = (self.cpu.r[reg::F as usize] & flag::N) != 0; let half_carry_flag = (self.cpu.r[reg::F as usize] & flag::H) != 0; if (half_carry_flag || nibble_low > 9) && !sub_flag { self.cpu.r[reg::A as usize] += 0x06; } if (half_carry_flag || nibble_low > 9) && sub_flag { self.cpu.r[reg::A as usize] -= 0x06; } let nibble_high = self.cpu.r[reg::A as usize] >> 4; self.cpu.r[reg::F as usize] &= !(flag::CY | flag::ZF); if nibble_high > 9 && !sub_flag { self.cpu.r[reg::A as usize] += 0x60; self.cpu.r[reg::F as usize] |= flag::CY; } if nibble_high > 9 && sub_flag { self.cpu.r[reg::A as usize] -= 0x60; self.cpu.r[reg::F as usize] |= flag::CY; } if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } self.cpu.r[reg::F as usize] &= !flag::H; } fn cpl(&mut self) { // Flip all bits in register A self.cpu.r[reg::F as usize] = self.cpu.r[reg::F as usize] | flag::N | flag::H; self.cpu.r[reg::A as usize] ^= 0xff; } fn addsp8(&mut self) -> Result { let n = self.r_8b_from_pc()? as i8; self.cpu.sp = (self.cpu.sp as i32 + n as i32) as u16; self.cpu.r[reg::F as usize] &= !(flag::N | flag::H | flag::CY); if (self.cpu.sp & 0xff) as i32 + n as i32 & !0xff != 0 { self.cpu.r[reg::F as usize] |= flag::H; } if (self.cpu.sp as i32 + n as i32) & !0xffff != 0 { self.cpu.r[reg::F as usize] |= flag::CY; } Ok(16) } fn add(&mut self, x: u8) { // ADD a number to A and store the result in A let res = x as u16 + self.cpu.r[reg::A as usize] as u16; self.cpu.r[reg::F as usize] = 0; if (x & 0xf) + (self.cpu.r[reg::A as usize] & 0xf) > 0xf { self.cpu.r[reg::F as usize] |= flag::H; } if res > 0xff { self.cpu.r[reg::F as usize] |= flag::CY; } self.cpu.r[reg::A as usize] = res as u8; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn addhlrr(&mut self, rr: u8) -> u64 { let n = self.cpu.r16(rr); let hl = self.cpu.r16(reg::HL); self.cpu.w16(reg::HL, (hl as i32 + n as i32) as u16); self.cpu.r[reg::F as usize] &= !(flag::N | flag::H | flag::CY); if (hl & 0xff) as i32 + n as i32 & !0xff != 0 { self.cpu.r[reg::F as usize] |= flag::H; } if (hl as i32 + n as i32) & !0xffff != 0 { self.cpu.r[reg::F as usize] |= flag::CY; } 8 } fn adc(&mut self, x: u8) { // ADD a number and the carry flag to A and store the result in A let carry = (self.cpu.r[reg::F as usize] & flag::CY) >> 4; let res = x as u16 + self.cpu.r[reg::A as usize] as u16 + carry as u16; self.cpu.r[reg::F as usize] = 0; if (x & 0xf) + ((self.cpu.r[reg::A as usize] & 0xf) + carry) > 0xf { self.cpu.r[reg::F as usize] |= flag::H; } if res > 0xff { self.cpu.r[reg::F as usize] |= flag::CY; } self.cpu.r[reg::A as usize] = res as u8; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn sub(&mut self, x: u8) { // SUB a number to A and store the result in A self.cpu.r[reg::F as usize] = flag::N; if (x & 0xf) > (self.cpu.r[reg::A as usize] & 0xf) { self.cpu.r[reg::F as usize] |= flag::H; } if x > self.cpu.r[reg::A as usize] { self.cpu.r[reg::F as usize] |= flag::CY; } self.cpu.r[reg::A as usize] = self.cpu.r[reg::A as usize] - x; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn sbc(&mut self, x: u8) { // SUB a number and the carry flag to A and store the result in A let carry = (self.cpu.r[reg::F as usize] & flag::CY) >> 4; self.cpu.r[reg::F as usize] = flag::N; if (x & 0xf) > (self.cpu.r[reg::A as usize] & 0xf) - carry { self.cpu.r[reg::F as usize] |= flag::H; } if x as i32 > self.cpu.r[reg::A as usize] as i32 - carry as i32 { self.cpu.r[reg::F as usize] |= flag::CY; } self.cpu.r[reg::A as usize] = self.cpu.r[reg::A as usize] - x - carry; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn and(&mut self, x: u8) { // AND a number to A and store the result in A self.cpu.r[reg::A as usize] &= x; self.cpu.r[reg::F as usize] = flag::H; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn xor(&mut self, x: u8) { // XOR a number to A and store the result in A self.cpu.r[reg::A as usize] ^= x; self.cpu.r[reg::F as usize] = 0; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn or(&mut self, x: u8) { // OR a number to A and store the result in A self.cpu.r[reg::A as usize] |= x; self.cpu.r[reg::F as usize] = 0; if self.cpu.r[reg::A as usize] == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn cp(&mut self, x: u8) { // SUB a number to A and update the flags accordingly without updating A self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= flag::N; if x & 0xf > self.cpu.r[reg::A as usize] & 0xf { self.cpu.r[reg::F as usize] |= flag::H; } if x > self.cpu.r[reg::A as usize] { self.cpu.r[reg::F as usize] |= flag::CY; } let res = self.cpu.r[reg::A as usize] - x; if res == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } } fn rlc(&mut self, r_i: u8) -> Result<(), MemError> { // ROTATE LEFT the input register let mut n = self.r_reg(r_i)?; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n >> 7) << 4; n <<= 1; n |= (self.cpu.r[reg::F as usize] & flag::CY) >> 4; self.w_reg(r_i, n) } fn rrc(&mut self, r_i: u8) -> Result<(), MemError> { // ROTATE RIGHT the input register let mut n = self.r_reg(r_i)?; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n & 1) << 4; n >>= 1; n |= ((self.cpu.r[reg::F as usize] & flag::CY) >> 4) << 7; self.w_reg(r_i, n) } fn rl(&mut self, r_i: u8) -> Result<(), MemError> { // ROTATE LEFT THROUGH CARRY the input register // (RLC IS ROTATE AND RL IS ROTATE THROUGH CARRY ! IT DOESN'T MAKE ANY SENSE !!) let mut n = self.r_reg(r_i)?; let carry = (self.cpu.r[reg::F as usize] & flag::CY) >> 4; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n >> 7) << 4; n <<= 1; n |= carry; self.w_reg(r_i, n) } fn rr(&mut self, r_i: u8) -> Result<(), MemError> { // ROTATE RIGHT THROUGH CARRY the input register let mut n = self.r_reg(r_i)?; let carry = (self.cpu.r[reg::F as usize] & flag::CY) >> 4; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n & 1) << 4; n >>= 1; n |= carry << 7; self.w_reg(r_i, n) } fn sla(&mut self, r_i: u8) -> Result<(), MemError> { // Shift left Arithmetic (b0=0) the input register let mut n = self.r_reg(r_i)?; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n >> 7) << 4; n <<= 1; if n == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } self.w_reg(r_i, n) } fn sra(&mut self, r_i: u8) -> Result<(), MemError> { // Shift right Arithmetic (b7=b7) the input register let mut n = self.r_reg(r_i)?; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n & 0b1) << 4; let b7 = n & 0b10000000; n >>= 1; n |= b7; if n == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } self.w_reg(r_i, n) } fn swap(&mut self, r_i: u8) -> Result<(), MemError> { // Swap the high nibble and low nibble let mut n = self.r_reg(r_i)?; let nibble_low = n & 0b1111; let nibble_high = n >> 4; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); n = nibble_high | (nibble_low << 4); if n == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } self.w_reg(r_i, n) } fn srl(&mut self, r_i: u8) -> Result<(), MemError> { // Shift right Logical (b7=0) the input register let mut n = self.r_reg(r_i)?; self.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); self.cpu.r[reg::F as usize] |= (n & 0b1) << 4; n >>= 1; if n == 0 { self.cpu.r[reg::F as usize] |= flag::ZF; } self.w_reg(r_i, n) } fn bit(&mut self, n1: u8, n2: u8) -> Result<(), MemError> { let z = (((self.r_reg(n2)? >> n1) & 1) ^ 1) << 7; self.cpu.r[reg::F as usize] &= !(flag::N | flag::ZF); self.cpu.r[reg::F as usize] |= flag::H | z; Ok(()) } fn set(&mut self, n1: u8, n2: u8) -> Result<(), MemError> { self.w_reg(n2, self.r_reg(n2)? | (1 << n1)) } fn res(&mut self, n1: u8, n2: u8) -> Result<(), MemError> { self.w_reg(n2, self.r_reg(n2)? & !(1 << n1)) } // I don't remember why I separated op00, op01, op10 and op11 AND I'M NOT GOING TO CHANGE IT // BECAUSE I LOVE CHAOS fn op00(&mut self, n1: u8, n2: u8) -> Result { // Dispatcher for the instructions starting with 0b00 based on their 3 LSB match n2 { 0b000 => match n1 { 0b000 => Ok(4), 0b001 => self.ldnnsp(), 0b010 => todo!("STOP"), 0b011 => self.jr8(), _ => self.jrcc8(n1), }, 0b001 => match n1 { 0b001 | 0b011 | 0b101 | 0b111 => Ok(self.addhlrr(n1 >> 1)), 0b000 | 0b010 | 0b100 | 0b110 => { let p = self.r_16b_from_pc()?; self.ldrr16(n1 >> 1, p); Ok(12) } _ => panic!(), }, 0b010 => self.ld00a(n1), 0b011 => match n1 { 0b001 | 0b011 | 0b101 | 0b111 => Ok(self.dec16(n1 >> 1)), 0b000 | 0b010 | 0b100 | 0b110 => Ok(self.inc16(n1 >> 1)), _ => panic!(), }, 0b100 => self.inc8(n1), 0b101 => self.dec8(n1), 0b110 => self.ldr8(n1), 0b111 => { match n1 { 0b000 => self.rlc(7)?, 0b001 => self.rrc(7)?, 0b010 => self.rl(7)?, 0b011 => self.rr(7)?, 0b100 => self.daa(), 0b101 => self.cpl(), 0b110 => self.scf(), 0b111 => self.ccf(), _ => panic!(), }; Ok(4) } _ => panic!(), } } fn op01(&mut self, n1: u8, n2: u8) -> Result { // Dispatcher for the instructions starting with 0b01 (LD r,r and HALT) if n1 == 0b110 && n2 == 0b110 { self.mem.halt = true; Ok(4) } else { self.ldrr(n1, n2)?; if n1 == 0b110 || n2 == 0b110 { Ok(8) } else { Ok(4) } } } fn op10(&mut self, n1: u8, n2: u8) -> Result { // Dispatcher for the instructions starting with 0b10 (Arithmetic) match n1 { 0b000 => self.add(self.r_reg(n2)?), 0b001 => self.adc(self.r_reg(n2)?), 0b010 => self.sub(self.r_reg(n2)?), 0b011 => self.sbc(self.r_reg(n2)?), 0b100 => self.and(self.r_reg(n2)?), 0b101 => self.xor(self.r_reg(n2)?), 0b110 => self.or(self.r_reg(n2)?), 0b111 => self.cp(self.r_reg(n2)?), _ => panic!(), } if n2 == 0b110 { Ok(8) } else { Ok(4) } } fn op11(&mut self, n1: u8, n2: u8) -> Result { match n2 { 0b000 => match n1 { 0b100 => { let n = self.r_8b_from_pc()?; self.ldnna(n as u16 | 0xff00)?; Ok(12) } 0b101 => self.addsp8(), 0b110 => { let n = self.r_8b_from_pc()?; self.ldann(n as u16 | 0xff00)?; Ok(12) } 0b111 => { let n = self.r_8b_from_pc()?; self.ldrr16(reg::HL, n as u16 + self.cpu.sp); Ok(12) } _ => self.retcc(n1 & 0b11), }, 0b001 => match n1 { 0b001 => self.ret(), 0b011 => { self.mem.ime = true; self.ret() } 0b101 => Ok(self.jphl()), 0b111 => Ok(self.ldsphl()), _ => { let p = self.pop()?; self.cpu.r[(n1 >> 1) as usize * 2 + 1] = (p & 0xff) as u8; self.cpu.r[(n1 >> 1) as usize * 2] = (p >> 8) as u8; Ok(12) } }, 0b010 => match n1 { 0b100 => { self.ldnna(self.cpu.r[reg::C as usize] as u16 | 0xff00)?; Ok(8) } 0b101 => { let nn = self.r_16b_from_pc()?; self.ldnna(nn)?; Ok(16) } 0b110 => { self.ldann(self.cpu.r[reg::C as usize] as u16 | 0xff00)?; Ok(8) } 0b111 => { let nn = self.r_16b_from_pc()?; self.ldann(nn)?; Ok(16) } _ => self.jpcc16(n1 & 0b11), }, 0b011 => match n1 { 0b000 => self.jp16(), 0b001 => self.op_bitwise(), // Bitwise operations 0b011 | 0b100 | 0b101 => unimplemented!(), 0b010 => { self.cpu.print_debug(); Ok(4) } 0b110 => { self.mem.ime = false; Ok(4) } 0b111 => { self.mem.ime = true; Ok(4) } _ => panic!(), }, 0b100 => self.callcc(n1 & 0b11), 0b101 => match n1 { 0b001 => self.call(), 0b011 | 0b101 | 0b111 => unimplemented!(), _ => { let value = self.cpu.r[(n1 >> 1) as usize * 2 + 1] as u16 | ((self.cpu.r[(n1 >> 1) as usize * 2] as u16) << 8); self.push(value)?; Ok(16) } }, 0b110 => { let p = self.r_8b_from_pc()?; match n1 { 0b000 => self.add(p), 0b001 => self.adc(p), 0b010 => self.sub(p), 0b011 => self.sbc(p), 0b100 => self.and(p), 0b101 => self.xor(p), 0b110 => self.or(p), 0b111 => self.cp(p), _ => panic!(), } Ok(8) } 0b111 => { let p = n1 << 3; self.push(self.cpu.pc)?; self.cpu.pc = p as u16; Ok(16) } // RST _ => panic!(), } } fn op_bitwise(&mut self) -> Result { let p = self.r_8b_from_pc()?; let opcode = p >> 6; let n1 = p >> 3 & 0b111; let n2 = p & 0b111; match opcode { 0b00 => match n1 { 0b000 => self.rlc(n2), 0b001 => self.rrc(n2), 0b010 => self.rl(n2), 0b011 => self.rr(n2), 0b100 => self.sla(n2), 0b101 => self.sra(n2), 0b110 => self.swap(n2), 0b111 => self.srl(n2), _ => panic!(), }, 0b01 => self.bit(n1, n2), 0b10 => self.res(n1, n2), 0b11 => self.set(n1, n2), _ => panic!(), }?; if n2 == 0b110 { Ok(16) } else { Ok(8) } } pub fn exec_opcode(&mut self) -> Result { let opcode = self.mem.r(self.cpu.pc)?; if self.is_debug { println!( "{:02x}:{:04x} = {:02x} (IME: {})", self.mem.rom_bank, self.cpu.pc, opcode, self.mem.ime ); } self.cpu.pc += 1; let n1 = (opcode >> 3) & 0b111; let n2 = opcode & 0b111; match opcode >> 6 { 0b00 => self.op00(n1, n2), 0b01 => self.op01(n1, n2), 0b10 => self.op10(n1, n2), 0b11 => self.op11(n1, n2), _ => panic!(), } } }