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}