1use crate::upd77c25::{FlagsRegister, Upd77c25, Upd77c25Variant}; 2use jgenesis_common::num::{GetBit, SignBit}; 3 4pub fn execute(cpu: &mut Upd77c25) { 5 if cpu.idling { 6 return; 7 } 8 9 let opcode = cpu.program_rom[cpu.registers.pc as usize]; 10 log::trace!("Got opcode {opcode:06X} from PC {:03X}", cpu.registers.pc); 11 cpu.registers.pc = (cpu.registers.pc + 1) & cpu.pc_mask; 12 13 match opcode & 0xC00000 { 14 0x000000 | 0x400000 => execute_alu(cpu, opcode), 15 0x800000 => execute_jump(cpu, opcode), 16 0xC00000 => execute_load(cpu, opcode), 17 _ => unreachable!("value & 0xC00000 is always one of the above values"), 18 } 19} 20 21fn execute_alu(cpu: &mut Upd77c25, opcode: u32) { 22 let alu_input = (opcode >> 20) & 0x3; 23 let alu_opcode = (opcode >> 16) & 0xF; 24 25 let source_register = (opcode >> 4) & 0xF; 26 let source = read_register(cpu, source_register); 27 28 let operand = match alu_input { 29 0x0 => cpu.ram[cpu.registers.dp as usize], 30 0x1 => source, 31 0x2 => ((2 * cpu.registers.kl()) >> 16) as u16, 32 0x3 => (2 * cpu.registers.kl()) as u16, 33 _ => unreachable!("value & 0x03 is always <= 0x03"), 34 }; 35 let operand = operand as i16; 36 37 let (accumulator, flags, other_flags) = if opcode.bit(15) { 38 (&mut cpu.registers.accumulator_b, &mut cpu.registers.flags_b, cpu.registers.flags_a) 39 } else { 40 (&mut cpu.registers.accumulator_a, &mut cpu.registers.flags_a, cpu.registers.flags_b) 41 }; 42 43 match alu_opcode { 44 0x00 => {} 45 0x01 => or(accumulator, operand, flags), 46 0x02 => and(accumulator, operand, flags), 47 0x03 => xor(accumulator, operand, flags), 48 0x04 => sub(accumulator, operand, false, flags), 49 0x05 => add(accumulator, operand, false, flags), 50 0x06 => sub(accumulator, operand, other_flags.c, flags), 51 0x07 => add(accumulator, operand, other_flags.c, flags), 52 0x08 => sub(accumulator, 1, false, flags), 53 0x09 => add(accumulator, 1, false, flags), 54 0x0A => not(accumulator, flags), 55 0x0B => sar1(accumulator, flags), 56 0x0C => rcl1(accumulator, other_flags.c, flags), 57 0x0D => sll2(accumulator, flags), 58 0x0E => sll4(accumulator, flags), 59 0x0F => xchg(accumulator, flags), 60 _ => unreachable!("value & 0x0F is always <= 0x0F"), 61 } 62 63 let dest_register = opcode & 0xF; 64 write_register(cpu, dest_register, source); 65 66 log::trace!(" Copied {source:04X} from {source_register:02X} to {dest_register:02X}"); 67 68 let dpl_adjust = (opcode >> 13) & 0x3; 69 match dpl_adjust { 70 0x00 => {} 71 0x01 => { 72 cpu.registers.dp = 73 (cpu.registers.dp & !0x0F) | (cpu.registers.dp.wrapping_add(1) & 0x0F); 74 } 75 0x02 => { 76 cpu.registers.dp = 77 (cpu.registers.dp & !0x0F) | (cpu.registers.dp.wrapping_sub(1) & 0x0F); 78 } 79 0x03 => { 80 cpu.registers.dp &= !0x0F; 81 } 82 _ => unreachable!("value & 0x03 is always <= 0x03"), 83 } 84 85 let dph_adjust = ((opcode >> 9) & 0xF) as u16; 86 cpu.registers.dp ^= dph_adjust << 4; 87 88 let rp_adjust = opcode.bit(8); 89 if rp_adjust { 90 cpu.registers.rp = cpu.registers.rp.wrapping_sub(1) & cpu.rp_mask; 91 } 92 93 let ret = opcode.bit(22); 94 if ret { 95 cpu.registers.pc = cpu.registers.pop_stack(); 96 } 97 98 log::trace!( 99 " ALU op: input={alu_input}, opcode={alu_opcode:02X}, accumulator={}, operand={operand:04X}, dpl_adjust={dpl_adjust}, dph_adjust={dph_adjust:X}, rp_adjust={rp_adjust}, ret={ret}", 100 if opcode.bit(15) { "B" } else { "A" } 101 ); 102} 103 104fn or(accumulator: &mut i16, operand: i16, flags: &mut FlagsRegister) { 105 *accumulator |= operand; 106 *flags = set_flags_bit_op(*accumulator); 107} 108 109fn and(accumulator: &mut i16, operand: i16, flags: &mut FlagsRegister) { 110 *accumulator &= operand; 111 *flags = set_flags_bit_op(*accumulator); 112} 113 114fn xor(accumulator: &mut i16, operand: i16, flags: &mut FlagsRegister) { 115 *accumulator ^= operand; 116 *flags = set_flags_bit_op(*accumulator); 117} 118 119fn sub(accumulator: &mut i16, operand: i16, borrow: bool, flags: &mut FlagsRegister) { 120 let (partial_unsigned_diff, carry1) = (*accumulator as u16).overflowing_sub(operand as u16); 121 let (unsigned_diff, carry2) = partial_unsigned_diff.overflowing_sub(borrow.into()); 122 let new_borrow = carry1 || carry2; 123 124 let signed_diff = i32::from(*accumulator) - i32::from(operand) - i32::from(borrow); 125 let overflow = signed_diff < i16::MIN.into() || signed_diff > i16::MAX.into(); 126 127 *accumulator = unsigned_diff as i16; 128 set_flags_add_sub(*accumulator, new_borrow, overflow, flags); 129} 130 131fn add(accumulator: &mut i16, operand: i16, carry: bool, flags: &mut FlagsRegister) { 132 let (partial_unsigned_sum, carry1) = (*accumulator as u16).overflowing_add(operand as u16); 133 let (unsigned_sum, carry2) = partial_unsigned_sum.overflowing_add(carry.into()); 134 let new_carry = carry1 || carry2; 135 136 let signed_sum = i32::from(*accumulator) + i32::from(operand) + i32::from(carry); 137 let overflow = signed_sum < i16::MIN.into() || signed_sum > i16::MAX.into(); 138 139 *accumulator = unsigned_sum as i16; 140 set_flags_add_sub(*accumulator, new_carry, overflow, flags); 141} 142 143fn not(accumulator: &mut i16, flags: &mut FlagsRegister) { 144 *accumulator = !(*accumulator); 145 *flags = set_flags_bit_op(*accumulator); 146} 147 148fn sar1(accumulator: &mut i16, flags: &mut FlagsRegister) { 149 let carry = accumulator.bit(0); 150 *accumulator >>= 1; 151 *flags = set_flags_shift_op(*accumulator, carry); 152} 153 154fn rcl1(accumulator: &mut i16, carry: bool, flags: &mut FlagsRegister) { 155 let new_carry = accumulator.sign_bit(); 156 *accumulator = (*accumulator << 1) | i16::from(carry); 157 *flags = set_flags_shift_op(*accumulator, new_carry); 158} 159 160fn sll2(accumulator: &mut i16, flags: &mut FlagsRegister) { 161 *accumulator = (*accumulator << 2) | 0x03; 162 *flags = set_flags_bit_op(*accumulator); 163} 164 165fn sll4(accumulator: &mut i16, flags: &mut FlagsRegister) { 166 *accumulator = (*accumulator << 4) | 0x0F; 167 *flags = set_flags_bit_op(*accumulator); 168} 169 170fn xchg(accumulator: &mut i16, flags: &mut FlagsRegister) { 171 *accumulator = accumulator.swap_bytes(); 172 *flags = set_flags_bit_op(*accumulator); 173} 174 175fn set_flags_bit_op(accumulator: i16) -> FlagsRegister { 176 FlagsRegister { 177 z: accumulator == 0, 178 c: false, 179 s0: accumulator < 0, 180 s1: accumulator < 0, 181 ov0: false, 182 ov1: false, 183 } 184} 185 186fn set_flags_shift_op(accumulator: i16, carry: bool) -> FlagsRegister { 187 FlagsRegister { 188 z: accumulator == 0, 189 c: carry, 190 s0: accumulator < 0, 191 s1: accumulator < 0, 192 ov0: false, 193 ov1: false, 194 } 195} 196 197fn set_flags_add_sub(accumulator: i16, carry: bool, overflow: bool, flags: &mut FlagsRegister) { 198 flags.z = accumulator == 0; 199 flags.c = carry; 200 flags.s0 = accumulator < 0; 201 flags.ov0 = overflow; 202 203 if overflow { 204 flags.s1 = accumulator < 0; 205 flags.ov1 = !flags.ov1; 206 } 207} 208 209fn execute_load(cpu: &mut Upd77c25, opcode: u32) { 210 log::trace!(" Load opcode: {opcode:06X}"); 211 212 let value = (opcode >> 6) as u16; 213 let dest = opcode & 0xF; 214 write_register(cpu, dest, value); 215} 216 217fn execute_jump(cpu: &mut Upd77c25, opcode: u32) { 218 log::trace!(" Jump opcode: {:03X}", (opcode >> 13) & 0x1FF); 219 220 let opcode_u16 = opcode as u16; 221 let mut jump_addr = match cpu.variant { 222 Upd77c25Variant::Dsp => (opcode_u16 >> 2) & 0x7FF, 223 Upd77c25Variant::St010 | Upd77c25Variant::St011 => { 224 // Normal jumps can't touch the highest bit of address, and bits 1-0 are used as A11-A12 225 (cpu.registers.pc & 0x2000) | ((opcode_u16 >> 2) & 0x7FF) | ((opcode_u16 & 0x03) << 11) 226 } 227 }; 228 229 let should_jump = match (opcode >> 13) & 0x1FF { 230 // JMPSO 231 0x000 => { 232 jump_addr = cpu.registers.so & cpu.pc_mask; 233 true 234 } 235 // JNCA / JCA 236 0x080 => !cpu.registers.flags_a.c, 237 0x082 => cpu.registers.flags_a.c, 238 // JNCB / JCB 239 0x084 => !cpu.registers.flags_b.c, 240 0x086 => cpu.registers.flags_b.c, 241 // JNZA / JZA 242 0x088 => !cpu.registers.flags_a.z, 243 0x08A => cpu.registers.flags_a.z, 244 // JNZB / JZB 245 0x08C => !cpu.registers.flags_b.z, 246 0x08E => cpu.registers.flags_b.z, 247 // JNOVA0 / JOVA0 248 0x090 => !cpu.registers.flags_a.ov0, 249 0x092 => cpu.registers.flags_a.ov0, 250 // JNOVB0 / JOVB0 251 0x094 => !cpu.registers.flags_b.ov0, 252 0x096 => cpu.registers.flags_b.ov0, 253 // JNOVA1 / JOVA1 254 0x098 => !cpu.registers.flags_a.ov1, 255 0x09A => cpu.registers.flags_a.ov1, 256 // JNOVB1 / JOVB1 257 0x09C => !cpu.registers.flags_b.ov1, 258 0x09E => cpu.registers.flags_b.ov1, 259 // JNSA0 / JSA0 260 0x0A0 => !cpu.registers.flags_a.s0, 261 0x0A2 => cpu.registers.flags_a.s0, 262 // JNSB0 / JSB0 263 0x0A4 => !cpu.registers.flags_b.s0, 264 0x0A6 => cpu.registers.flags_b.s0, 265 // JNSA1 / JSA1 266 0x0A8 => !cpu.registers.flags_a.s1, 267 0x0AA => cpu.registers.flags_a.s1, 268 // JNSB1 / JSB1 269 0x0AC => !cpu.registers.flags_b.s1, 270 0x0AE => cpu.registers.flags_b.s1, 271 // JDPL0 / JDPLN0 272 0x0B0 => cpu.registers.dp & 0x0F == 0, 273 0x0B1 => cpu.registers.dp & 0x0F != 0, 274 // JDPLF / JDPLNF 275 0x0B2 => cpu.registers.dp & 0x0F == 0x0F, 276 0x0B3 => cpu.registers.dp & 0x0F != 0x0F, 277 // JNSIAK / JSIAK / JNSOAK / JSOAK; not implemented 278 0x0B4 | 0x0B6 | 0x0B8 | 0x0BA => { 279 log::warn!("Unimplemented uPD77C25 jump opcode (serial registers): {opcode:06X}"); 280 false 281 } 282 // JNRQM / JRQM 283 0x0BC => !cpu.registers.sr.request_for_master, 284 0x0BE => cpu.registers.sr.request_for_master, 285 // JMP 286 0x100 => { 287 jump_addr &= 0x1FFF; 288 true 289 } 290 0x101 => { 291 jump_addr |= 0x2000; 292 true 293 } 294 // CALL 295 0x140 => { 296 jump_addr &= 0x1FFF; 297 cpu.registers.push_stack(cpu.registers.pc); 298 true 299 } 300 0x141 => { 301 jump_addr |= 0x2000; 302 cpu.registers.push_stack(cpu.registers.pc); 303 true 304 } 305 _ => panic!("invalid uPD77C25 jump opcode: {opcode:06X}"), 306 }; 307 308 if should_jump { 309 log::trace!(" Jumping to {jump_addr:03X}"); 310 311 if jump_addr == cpu.registers.pc.wrapping_sub(1) & cpu.pc_mask { 312 log::trace!(" Detected idle loop; halting CPU until next SNES DR write"); 313 cpu.idling = true; 314 } 315 316 cpu.registers.pc = jump_addr; 317 } 318} 319 320fn read_register(cpu: &mut Upd77c25, register: u32) -> u16 { 321 log::trace!(" Reading register {register:02X}"); 322 323 match register { 324 0x00 => cpu.registers.trb as u16, 325 0x01 => cpu.registers.accumulator_a as u16, 326 0x02 => cpu.registers.accumulator_b as u16, 327 0x03 => cpu.registers.tr as u16, 328 0x04 => cpu.registers.dp, 329 0x05 => cpu.registers.rp, 330 0x06 => cpu.data_rom[cpu.registers.rp as usize], 331 0x07 => 0x8000 - u16::from(cpu.registers.flags_a.s1), 332 0x08 => { 333 // Reading DR sets RQM 334 cpu.registers.sr.request_for_master = true; 335 cpu.registers.dr 336 } 337 0x09 => { 338 // Reading DRNF does not set RQM 339 cpu.registers.dr 340 } 341 0x0A => u16::from(u8::from(cpu.registers.sr)) << 8, 342 0x0B | 0x0C => cpu.registers.so, 343 0x0D => cpu.registers.k as u16, 344 0x0E => cpu.registers.l as u16, 345 0x0F => cpu.ram[cpu.registers.dp as usize], 346 _ => panic!("invalid uPD77C25 register read: {register:02X}"), 347 } 348} 349 350#[allow(clippy::match_same_arms)] 351fn write_register(cpu: &mut Upd77c25, register: u32, value: u16) { 352 log::trace!(" Writing {value:04X} to register {register:02X}"); 353 354 match register { 355 0x00 => {} 356 0x01 => cpu.registers.accumulator_a = value as i16, 357 0x02 => cpu.registers.accumulator_b = value as i16, 358 0x03 => cpu.registers.tr = value as i16, 359 0x04 => cpu.registers.dp = value & cpu.dp_mask, 360 0x05 => cpu.registers.rp = value & cpu.rp_mask, 361 0x06 => cpu.registers.upd_write_data(value), 362 0x07 => cpu.registers.sr.write(value), 363 0x08 | 0x09 => cpu.registers.so = value, 364 0x0A => cpu.registers.k = value as i16, 365 0x0B => { 366 cpu.registers.k = value as i16; 367 cpu.registers.l = cpu.data_rom[cpu.registers.rp as usize] as i16; 368 } 369 0x0C => { 370 cpu.registers.l = value as i16; 371 cpu.registers.k = cpu.ram[(cpu.registers.dp | 0x40) as usize] as i16; 372 } 373 0x0D => cpu.registers.l = value as i16, 374 0x0E => cpu.registers.trb = value as i16, 375 0x0F => cpu.ram[cpu.registers.dp as usize] = value, 376 _ => panic!("invalid uPD77C25 register write: {register:02X} {value:04X}"), 377 } 378}