1mod rom;
2
3use crate::common;
4use crate::cx4::{Cx4Ram, Cx4Registers};
5use jgenesis_common::num::{GetBit, U16Ext, U24Ext};
6
7#[derive(Debug, Clone, Default)]
8struct DspRegisters {
9    a: u32,
10    m: i64,
11    // Used for reading data from cartridge ROM
12    ext_buffer: u8,
13    ext_pointer: u32,
14    // Used for reading data from CX4 ROM
15    rom_buffer: u32,
16    // Used for reading data from CX4 RAM
17    ram_buffer: u32,
18    ram_pointer: u16,
19    page: u16,
20    zero: bool,
21    negative: bool,
22    carry: bool,
23    call_stack: [(u16, u8); 16],
24    call_stack_ptr: u8,
25}
26
27impl DspRegisters {
28    fn push_call_stack(&mut self, page: u16, pointer: u8) {
29        self.call_stack[self.call_stack_ptr as usize] = (page, pointer);
30        self.call_stack_ptr = (self.call_stack_ptr + 1) & 0xF;
31    }
32
33    fn pull_call_stack(&mut self) -> (u16, u8) {
34        self.call_stack_ptr = self.call_stack_ptr.wrapping_sub(1) & 0xF;
35        self.call_stack[self.call_stack_ptr as usize]
36    }
37
38    fn set_nz(&mut self, value: u32) {
39        self.zero = value & 0xFFFFFF == 0;
40        self.negative = value.bit(23);
41    }
42}
43
44#[allow(clippy::match_same_arms)]
45pub fn execute(cx4_registers: &mut Cx4Registers, rom: &[u8], ram: &mut Cx4Ram) {
46    let mut dsp_registers = DspRegisters::default();
47
48    log::trace!(
49        "Beginning execution with page {:04X} and pointer {:02X}",
50        cx4_registers.instruction_page,
51        cx4_registers.instruction_pointer
52    );
53
54    loop {
55        let opcode_addr = cx4_registers.risc_pc();
56        let rom_addr = common::lorom_map_rom_address(opcode_addr, rom.len() as u32);
57        let opcode = u16::from_le_bytes([rom[rom_addr as usize], rom[(rom_addr + 1) as usize]]);
58        cx4_registers.increment_instruction_pointer();
59
60        log::trace!("opcode={opcode:04X}, PC={opcode_addr:06X}");
61
62        // The first 6 bits of opcode are enough to distinguish between instructions
63        match opcode & 0xFC00 {
64            0x0000 => {
65                // nop; do nothing
66            }
67            0x0800 => jmp(cx4_registers, &dsp_registers, opcode),
68            0x0C00 => jz(cx4_registers, &dsp_registers, opcode),
69            0x1000 => jc(cx4_registers, &dsp_registers, opcode),
70            0x1400 => jn(cx4_registers, &dsp_registers, opcode),
71            0x1C00 => {
72                // "finish"; effectively does nothing, exists for timing purposes only?
73            }
74            0x2400 => skip(cx4_registers, &dsp_registers, opcode),
75            0x2800 => call(cx4_registers, &mut dsp_registers, opcode),
76            0x2C00 => callz(cx4_registers, &mut dsp_registers, opcode),
77            0x3000 => callc(cx4_registers, &mut dsp_registers, opcode),
78            0x3400 => calln(cx4_registers, &mut dsp_registers, opcode),
79            0x3C00 => ret(cx4_registers, &mut dsp_registers),
80            0x4000 => inc_ext_ptr(&mut dsp_registers),
81            0x4800 => cmpr_a_op(cx4_registers, &mut dsp_registers, opcode),
82            0x4C00 => cmpr_a_imm(&mut dsp_registers, opcode),
83            0x5000 => cmp_a_op(cx4_registers, &mut dsp_registers, opcode),
84            0x5400 => cmp_a_imm(&mut dsp_registers, opcode),
85            0x5800 => sign_extend(&mut dsp_registers, opcode),
86            0x6000 => match opcode & 0x0300 {
87                0x0000 => mov_a_op(cx4_registers, &mut dsp_registers, opcode),
88                0x0100 => mov_mbr_op(cx4_registers, &mut dsp_registers, rom, opcode),
89                0x0300 => mov_page_op(cx4_registers, &mut dsp_registers, opcode),
90                _ => {
91                    log::warn!("Unexpected mov opcode: {opcode:04X}");
92                }
93            },
94            0x6400 => match opcode & 0x0300 {
95                0x0000 => mov_a_imm(&mut dsp_registers, opcode),
96                0x0300 => mov_page_imm(&mut dsp_registers, opcode),
97                _ => {
98                    log::warn!("Unexpected mov opcode: {opcode:04X}");
99                }
100            },
101            0x6800 => read_ram_op(cx4_registers, &mut dsp_registers, ram, opcode),
102            0x6C00 => read_ram_imm(&mut dsp_registers, ram, opcode),
103            0x7000 => read_rom(cx4_registers, &mut dsp_registers, opcode),
104            0x7800 => movb_page_op(cx4_registers, &mut dsp_registers, opcode),
105            0x7C00 => movb_page_imm(&mut dsp_registers, opcode),
106            0x8000 => add_a_op(cx4_registers, &mut dsp_registers, opcode),
107            0x8400 => add_a_imm(&mut dsp_registers, opcode),
108            0x8800 => subr_a_op(cx4_registers, &mut dsp_registers, opcode),
109            0x8C00 => subr_a_imm(&mut dsp_registers, opcode),
110            0x9000 => sub_a_op(cx4_registers, &mut dsp_registers, opcode),
111            0x9400 => sub_a_imm(&mut dsp_registers, opcode),
112            0x9800 => smul_op(cx4_registers, &mut dsp_registers, opcode),
113            0x9C00 => smul_imm(&mut dsp_registers, opcode),
114            0xA800 => xor_a_op(cx4_registers, &mut dsp_registers, opcode),
115            0xAC00 => xor_a_imm(&mut dsp_registers, opcode),
116            0xB000 => and_a_op(cx4_registers, &mut dsp_registers, opcode),
117            0xB400 => and_a_imm(&mut dsp_registers, opcode),
118            0xB800 => or_a_op(cx4_registers, &mut dsp_registers, opcode),
119            0xBC00 => or_a_imm(&mut dsp_registers, opcode),
120            0xC000 => shr_a_op(cx4_registers, &mut dsp_registers, opcode),
121            0xC400 => shr_a_imm(&mut dsp_registers, opcode),
122            0xC800 => sar_a_op(cx4_registers, &mut dsp_registers, opcode),
123            0xCC00 => sar_a_imm(&mut dsp_registers, opcode),
124            0xD000 => ror_a_op(cx4_registers, &mut dsp_registers, opcode),
125            0xD400 => ror_a_imm(&mut dsp_registers, opcode),
126            0xD800 => shl_a_op(cx4_registers, &mut dsp_registers, opcode),
127            0xDC00 => shl_a_imm(&mut dsp_registers, opcode),
128            0xE000 => mov_op_a(cx4_registers, &mut dsp_registers, opcode),
129            0xE800 => movb_ram_op(cx4_registers, &dsp_registers, ram, opcode),
130            0xEC00 => movb_ram_ptr(&dsp_registers, ram, opcode),
131            0xF000 => swap(cx4_registers, &mut dsp_registers, opcode),
132            0xFC00 => {
133                // stop; function is complete
134                return;
135            }
136            _ => {
137                log::warn!("Unexpected opcode: {opcode:04X}");
138            }
139        }
140    }
141}
142
143fn mov_a_imm(registers: &mut DspRegisters, opcode: u16) {
144    log::trace!("mov A, #${:02X}", opcode & 0xFF);
145
146    registers.a = (opcode & 0xFF).into();
147}
148
149fn mov_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
150    log::trace!("mov A, ${:02X}", opcode & 0xFF);
151
152    dsp_registers.a = read_register(cx4_registers, dsp_registers, opcode) & 0xFFFFFF;
153}
154
155fn mov_mbr_op(
156    cx4_registers: &Cx4Registers,
157    dsp_registers: &mut DspRegisters,
158    rom: &[u8],
159    opcode: u16,
160) {
161    log::trace!("mov MBR, ${:02X}", opcode & 0xFF);
162
163    let value = if opcode & 0xFF == 0x2E {
164        // $612E seems to be the only opcode that reads "register" $2E, which reads a byte from
165        // cartridge ROM using the current pointer
166        let rom_addr = common::lorom_map_rom_address(dsp_registers.ext_pointer, rom.len() as u32);
167        rom[rom_addr as usize]
168    } else {
169        read_register(cx4_registers, dsp_registers, opcode) as u8
170    };
171    dsp_registers.ext_buffer = value;
172}
173
174fn mov_op_a(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
175    log::trace!("mov ${:02X}, A", opcode & 0xFF);
176
177    let a = dsp_registers.a;
178    write_register(cx4_registers, dsp_registers, opcode, a);
179}
180
181fn mov_page_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
182    log::trace!("mov page, {:02X}", opcode & 0xFF);
183
184    let value = read_register(cx4_registers, dsp_registers, opcode);
185    dsp_registers.page = value as u16;
186}
187
188fn mov_page_imm(registers: &mut DspRegisters, opcode: u16) {
189    log::trace!("mov page, #${:02X}", opcode & 0xFF);
190
191    registers.page = opcode & 0xFF;
192}
193
194fn read_rom(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
195    log::trace!("readrom ${:02X}", opcode & 0xFF);
196
197    let address = read_register(cx4_registers, dsp_registers, opcode);
198    dsp_registers.rom_buffer = rom::read(address);
199}
200
201fn read_ram_op(
202    cx4_registers: &Cx4Registers,
203    dsp_registers: &mut DspRegisters,
204    ram: &Cx4Ram,
205    opcode: u16,
206) {
207    log::trace!("readram ${:02X}", opcode & 0xFF);
208
209    let address = read_register(cx4_registers, dsp_registers, opcode);
210    let value = ram.get((address & 0xFFF) as usize).copied().unwrap_or(0);
211    read_ram(dsp_registers, opcode, value);
212}
213
214fn read_ram_imm(dsp_registers: &mut DspRegisters, ram: &Cx4Ram, opcode: u16) {
215    log::trace!("readram #${:02X}", opcode & 0xFF);
216
217    let address = dsp_registers.ram_pointer.wrapping_add(opcode & 0xFF);
218    let value = ram.get(address as usize).copied().unwrap_or(0);
219    read_ram(dsp_registers, opcode, value);
220}
221
222fn read_ram(registers: &mut DspRegisters, opcode: u16, ram_value: u8) {
223    match opcode & 0x0300 {
224        0x0000 => {
225            registers.ram_buffer = (registers.ram_buffer & 0xFFFF_FF00) | u32::from(ram_value);
226        }
227        0x0100 => {
228            registers.ram_buffer =
229                (registers.ram_buffer & 0xFFFF_00FF) | (u32::from(ram_value) << 8);
230        }
231        0x0200 => {
232            registers.ram_buffer =
233                (registers.ram_buffer & 0x0000_FFFF) | (u32::from(ram_value) << 16);
234        }
235        _ => {
236            log::warn!("Unexpected read RAM opcode: {opcode:04X}");
237        }
238    }
239    log::trace!("  read RAM value {ram_value}; RAM buffer is {:06X}", registers.ram_buffer);
240}
241
242fn movb_ram_op(
243    cx4_registers: &Cx4Registers,
244    dsp_registers: &DspRegisters,
245    ram: &mut Cx4Ram,
246    opcode: u16,
247) {
248    log::trace!("movb ram[${:02X}], ram_buf", opcode & 0xFF);
249
250    let ram_addr = (read_register(cx4_registers, dsp_registers, opcode) & 0xFFF) as usize;
251    if ram_addr < ram.len() {
252        let value = match opcode & 0x0300 {
253            0x0000 => dsp_registers.ram_buffer.low_byte(),
254            0x0100 => dsp_registers.ram_buffer.mid_byte(),
255            0x0200 => dsp_registers.ram_buffer.high_byte(),
256            _ => {
257                log::warn!("Unexpected movb RAM[..] opcode: {opcode:02X}");
258                0x00
259            }
260        };
261        ram[ram_addr] = value;
262    }
263}
264
265fn movb_ram_ptr(registers: &DspRegisters, ram: &mut Cx4Ram, opcode: u16) {
266    log::trace!("movbram[ptr+#${:02X}], ram_buf", opcode & 0xFF);
267
268    let ram_addr = (registers.ram_pointer.wrapping_add(opcode & 0xFF) & 0xFFF) as usize;
269    if ram_addr < ram.len() {
270        let value = match opcode & 0x0300 {
271            0x0000 => registers.ram_buffer.low_byte(),
272            0x0100 => registers.ram_buffer.mid_byte(),
273            0x0200 => registers.ram_buffer.high_byte(),
274            _ => {
275                log::warn!("Unexpected movb RAM[..] opcode: {opcode:02X}");
276                0x00
277            }
278        };
279        ram[ram_addr] = value;
280        log::trace!("  wrote {value:02X} to {ram_addr:03X}");
281    }
282}
283
284fn movb_page_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
285    log::trace!("movb P, ${:02X}", opcode & 0xFF);
286
287    let value = read_register(cx4_registers, dsp_registers, opcode);
288    mov_page(dsp_registers, opcode, value as u8);
289}
290
291fn movb_page_imm(registers: &mut DspRegisters, opcode: u16) {
292    log::trace!("movb P, #${:02X}", opcode & 0xFF);
293
294    mov_page(registers, opcode, opcode as u8);
295}
296
297fn mov_page(registers: &mut DspRegisters, opcode: u16, value: u8) {
298    if !opcode.bit(8) {
299        registers.page.set_lsb(value);
300    } else {
301        registers.page.set_msb(value);
302    }
303}
304
305fn swap(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
306    log::trace!("swap A, ${:02}", opcode & 0xFF);
307
308    let old_a = dsp_registers.a;
309    dsp_registers.a = read_register(cx4_registers, dsp_registers, opcode);
310    write_register(cx4_registers, dsp_registers, opcode, old_a);
311}
312
313fn jmp(cx4_registers: &mut Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) {
314    log::trace!("jmp {:02X}", opcode & 0xFF);
315
316    execute_jump(cx4_registers, dsp_registers, opcode);
317}
318
319fn jz(cx4_registers: &mut Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) {
320    log::trace!("jz {:02X}", opcode & 0xFF);
321
322    if dsp_registers.zero {
323        execute_jump(cx4_registers, dsp_registers, opcode);
324    }
325}
326
327fn jc(cx4_registers: &mut Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) {
328    log::trace!("jc {:02X}", opcode & 0xFF);
329
330    if dsp_registers.carry {
331        execute_jump(cx4_registers, dsp_registers, opcode);
332    }
333}
334
335fn jn(cx4_registers: &mut Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) {
336    log::trace!("jn {:02X}", opcode & 0xFF);
337
338    if dsp_registers.negative {
339        execute_jump(cx4_registers, dsp_registers, opcode);
340    }
341}
342
343fn call(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
344    log::trace!("call {:02X}", opcode & 0xFF);
345
346    dsp_registers
347        .push_call_stack(cx4_registers.instruction_page, cx4_registers.instruction_pointer);
348    execute_jump(cx4_registers, dsp_registers, opcode);
349}
350
351fn callz(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
352    log::trace!("callz {:02X}", opcode & 0xFF);
353
354    if dsp_registers.zero {
355        dsp_registers
356            .push_call_stack(cx4_registers.instruction_page, cx4_registers.instruction_pointer);
357        execute_jump(cx4_registers, dsp_registers, opcode);
358    }
359}
360
361fn callc(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
362    log::trace!("callc {:02X}", opcode & 0xFF);
363
364    if dsp_registers.carry {
365        dsp_registers
366            .push_call_stack(cx4_registers.instruction_page, cx4_registers.instruction_pointer);
367        execute_jump(cx4_registers, dsp_registers, opcode);
368    }
369}
370
371fn calln(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
372    log::trace!("calln {:02X}", opcode & 0xFF);
373
374    if dsp_registers.negative {
375        dsp_registers
376            .push_call_stack(cx4_registers.instruction_page, cx4_registers.instruction_pointer);
377        execute_jump(cx4_registers, dsp_registers, opcode);
378    }
379}
380
381fn ret(cx4_registers: &mut Cx4Registers, dsp_registers: &mut DspRegisters) {
382    log::trace!("ret");
383
384    let (page, pointer) = dsp_registers.pull_call_stack();
385    cx4_registers.instruction_page = page;
386    cx4_registers.instruction_pointer = pointer;
387}
388
389fn execute_jump(cx4_registers: &mut Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) {
390    cx4_registers.instruction_pointer = opcode as u8;
391    if opcode.bit(9) {
392        cx4_registers.instruction_page = dsp_registers.page;
393    }
394}
395
396fn skip(cx4_registers: &mut Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) {
397    log::trace!("skip, opcode={opcode:04X}");
398
399    let value = opcode.bit(0);
400    let flag = match opcode & 0x0300 {
401        0x0100 => dsp_registers.carry,
402        0x0200 => dsp_registers.zero,
403        0x0300 => dsp_registers.negative,
404        _ => {
405            log::warn!("Unexpected skip opcode: {opcode:02X}");
406            false
407        }
408    };
409    if flag == value {
410        cx4_registers.instruction_pointer = cx4_registers.instruction_pointer.wrapping_add(1);
411    }
412}
413
414fn add_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
415    log::trace!("add A, ${:02X}", opcode & 0xFF);
416
417    let value = read_register(cx4_registers, dsp_registers, opcode);
418    add(dsp_registers, opcode, value);
419}
420
421fn add_a_imm(registers: &mut DspRegisters, opcode: u16) {
422    log::trace!("add A, #${:02X}", opcode & 0xFF);
423
424    add(registers, opcode, (opcode & 0xFF).into());
425}
426
427fn add(registers: &mut DspRegisters, opcode: u16, value: u32) {
428    let result = apply_a_shift(registers.a, opcode) + value;
429    registers.set_nz(result);
430    registers.carry = result > 0xFFFFFF;
431
432    registers.a = result & 0xFFFFFF;
433}
434
435fn subr_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
436    log::trace!("subr A, ${:02X}", opcode & 0xFF);
437
438    let value = read_register(cx4_registers, dsp_registers, opcode);
439    sub(dsp_registers, opcode, value, true);
440}
441
442fn subr_a_imm(registers: &mut DspRegisters, opcode: u16) {
443    log::trace!("subr A, #${:02X}", opcode & 0xFF);
444
445    sub(registers, opcode, (opcode & 0xFF).into(), true);
446}
447
448fn sub_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
449    log::trace!("sub A, ${:02X}", opcode & 0xFF);
450
451    let value = read_register(cx4_registers, dsp_registers, opcode);
452    sub(dsp_registers, opcode, value, false);
453}
454
455fn sub_a_imm(registers: &mut DspRegisters, opcode: u16) {
456    log::trace!("sub A, #${:02X}", opcode & 0xFF);
457
458    sub(registers, opcode, (opcode & 0xFF).into(), false);
459}
460
461fn sub(registers: &mut DspRegisters, opcode: u16, value: u32, reverse: bool) {
462    let a = apply_a_shift(registers.a, opcode);
463    let result = if reverse { value.wrapping_sub(a) } else { a.wrapping_sub(value) };
464    registers.set_nz(result);
465    registers.carry = result <= 0xFFFFFF;
466
467    registers.a = result & 0xFFFFFF;
468}
469
470fn cmpr_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
471    log::trace!("cmpr A, ${:02X}", opcode & 0xFF);
472
473    let value = read_register(cx4_registers, dsp_registers, opcode);
474    cmp(dsp_registers, opcode, value, true);
475}
476
477fn cmpr_a_imm(registers: &mut DspRegisters, opcode: u16) {
478    log::trace!("cmpr A, #${:02X}", opcode & 0xFF);
479
480    cmp(registers, opcode, (opcode & 0xFF).into(), true);
481}
482
483fn cmp_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
484    log::trace!("cmp A, ${:02X}", opcode & 0xFF);
485
486    let value = read_register(cx4_registers, dsp_registers, opcode);
487    cmp(dsp_registers, opcode, value, false);
488}
489
490fn cmp_a_imm(registers: &mut DspRegisters, opcode: u16) {
491    log::trace!("cmp A, #${:02X}", opcode & 0xFF);
492
493    cmp(registers, opcode, (opcode & 0xFF).into(), false);
494}
495
496fn cmp(registers: &mut DspRegisters, opcode: u16, value: u32, reverse: bool) {
497    let a = apply_a_shift(registers.a, opcode);
498    let result = if reverse { value.wrapping_sub(a) } else { a.wrapping_sub(value) };
499    registers.set_nz(result);
500    registers.carry = result <= 0xFFFFFF;
501}
502
503fn smul_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
504    log::trace!("smul ${:02X}", opcode & 0xFF);
505
506    let value = read_register(cx4_registers, dsp_registers, opcode);
507    smul(dsp_registers, value);
508}
509
510fn smul_imm(registers: &mut DspRegisters, opcode: u16) {
511    log::trace!("smul #${:02X}", opcode & 0xFF);
512
513    let value = (opcode & 0xFF).into();
514    smul(registers, value);
515}
516
517fn smul(registers: &mut DspRegisters, value: u32) {
518    // smul multiplies two signed 24-bit integers to produce a signed 48-bit result
519    // Simulate this by sign extending the 24-bit numbers to 64 bits and doing the multiplication
520    // in 64 bits
521    let a = ((registers.a as i32) << 8) >> 8;
522    let b = ((value as i32) << 8) >> 8;
523    registers.m = i64::from(a) * i64::from(b);
524}
525
526fn and_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
527    log::trace!("and A, ${:02X}", opcode & 0xFF);
528
529    let value = read_register(cx4_registers, dsp_registers, opcode);
530    and(dsp_registers, opcode, value);
531}
532
533fn and_a_imm(registers: &mut DspRegisters, opcode: u16) {
534    log::trace!("and A, #${:02X}", opcode & 0xFF);
535
536    and(registers, opcode, (opcode & 0xFF).into());
537}
538
539fn and(registers: &mut DspRegisters, opcode: u16, value: u32) {
540    let result = apply_a_shift(registers.a, opcode) & value;
541    registers.set_nz(result);
542
543    registers.a = result & 0xFFFFFF;
544}
545
546fn or_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
547    log::trace!("or A, ${:02X}", opcode & 0xFF);
548
549    let value = read_register(cx4_registers, dsp_registers, opcode);
550    or(dsp_registers, opcode, value);
551}
552
553fn or_a_imm(registers: &mut DspRegisters, opcode: u16) {
554    log::trace!("or A, #${:02X}", opcode & 0xFF);
555
556    or(registers, opcode, (opcode & 0xFF).into());
557}
558
559fn or(registers: &mut DspRegisters, opcode: u16, value: u32) {
560    let result = apply_a_shift(registers.a, opcode) | value;
561    registers.set_nz(result);
562
563    registers.a = result & 0xFFFFFF;
564}
565
566fn xor_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
567    log::trace!("xor A, ${:02X}", opcode & 0xFF);
568
569    let value = read_register(cx4_registers, dsp_registers, opcode);
570    xor(dsp_registers, opcode, value);
571}
572
573fn xor_a_imm(dsp_registers: &mut DspRegisters, opcode: u16) {
574    log::trace!("xor A, #${:02X}", opcode & 0xFF);
575
576    xor(dsp_registers, opcode, (opcode & 0xFF).into());
577}
578
579fn xor(registers: &mut DspRegisters, opcode: u16, value: u32) {
580    let result = apply_a_shift(registers.a, opcode) ^ value;
581    registers.set_nz(result);
582
583    registers.a = result & 0xFFFFFF;
584}
585
586fn shr_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
587    log::trace!("shr ${:02X}", opcode & 0xFF);
588
589    let value = read_register(cx4_registers, dsp_registers, opcode);
590    shr(dsp_registers, value);
591}
592
593fn shr_a_imm(registers: &mut DspRegisters, opcode: u16) {
594    log::trace!("shr #${:02X}", opcode & 0xFF);
595
596    shr(registers, (opcode & 0x1F).into());
597}
598
599fn shr(registers: &mut DspRegisters, value: u32) {
600    if value <= 24 {
601        registers.a >>= value;
602    }
603    registers.set_nz(registers.a);
604}
605
606fn sar_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
607    log::trace!("sar ${:02X}", opcode & 0xFF);
608
609    let value = read_register(cx4_registers, dsp_registers, opcode);
610    sar(dsp_registers, value);
611}
612
613fn sar_a_imm(registers: &mut DspRegisters, opcode: u16) {
614    log::trace!("sar #${:02X}", opcode & 0xFF);
615
616    sar(registers, (opcode & 0x1F).into());
617}
618
619fn sar(registers: &mut DspRegisters, value: u32) {
620    if value <= 24 {
621        registers.a = ((((registers.a as i32) << 8) >> (8 + value)) as u32) & 0xFFFFFF;
622    }
623    registers.set_nz(registers.a);
624}
625
626fn shl_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
627    log::trace!("shl ${:02X}", opcode & 0xFF);
628
629    let value = read_register(cx4_registers, dsp_registers, opcode);
630    shl(dsp_registers, value);
631}
632
633fn shl_a_imm(registers: &mut DspRegisters, opcode: u16) {
634    log::trace!("shl #${:02X}", opcode & 0xFF);
635
636    shl(registers, (opcode & 0x1F).into());
637}
638
639fn shl(registers: &mut DspRegisters, value: u32) {
640    if value <= 24 {
641        registers.a = (registers.a << value) & 0xFFFFFF;
642    }
643    registers.set_nz(registers.a);
644}
645
646fn ror_a_op(cx4_registers: &Cx4Registers, dsp_registers: &mut DspRegisters, opcode: u16) {
647    log::trace!("ror ${:02X}", opcode & 0xFF);
648
649    let value = read_register(cx4_registers, dsp_registers, opcode);
650    ror(dsp_registers, value);
651}
652
653fn ror_a_imm(registers: &mut DspRegisters, opcode: u16) {
654    log::trace!("ror #${:02X}", opcode & 0xFF);
655
656    ror(registers, (opcode & 0x1F).into());
657}
658
659fn ror(registers: &mut DspRegisters, value: u32) {
660    if value <= 24 {
661        registers.a = ((registers.a >> value) | (registers.a << (24 - value))) & 0xFFFFFF;
662    }
663    registers.set_nz(registers.a);
664}
665
666fn sign_extend(registers: &mut DspRegisters, opcode: u16) {
667    log::trace!("exts, opcode={opcode:04X}");
668
669    match opcode & 0x0300 {
670        0x0100 => {
671            registers.a = ((registers.a as i8) as u32) & 0xFFFFFF;
672        }
673        0x0200 => {
674            registers.a = ((registers.a as i16) as u32) & 0xFFFFFF;
675        }
676        _ => {
677            log::warn!("Unexpected sign extension opcode: {opcode:04X}");
678        }
679    }
680    registers.set_nz(registers.a);
681}
682
683fn inc_ext_ptr(registers: &mut DspRegisters) {
684    log::trace!("inc MAR");
685
686    registers.ext_pointer = (registers.ext_pointer + 1) & 0xFFFFFF;
687}
688
689fn apply_a_shift(a: u32, opcode: u16) -> u32 {
690    match opcode & 0x0300 {
691        0x0000 => a,
692        0x0100 => (a << 1) & 0xFFFFFF,
693        0x0200 => (a << 8) & 0xFFFFFF,
694        0x0300 => (a << 16) & 0xFFFFFF,
695        _ => unreachable!("value & 0x0300 is always one of the above values"),
696    }
697}
698
699const CONSTANT_REGISTERS: &[u32; 16] = &[
700    0x000000, 0xFFFFFF, 0x00FF00, 0xFF0000, 0x00FFFF, 0xFFFF00, 0x800000, 0x7FFFFF, 0x008000,
701    0x007FFF, 0xFF7FFF, 0xFFFF7F, 0x010000, 0xFEFFFF, 0x000100, 0x00FEFF,
702];
703
704fn read_register(cx4_registers: &Cx4Registers, dsp_registers: &DspRegisters, opcode: u16) -> u32 {
705    match opcode & 0xFF {
706        0x00 => dsp_registers.a,
707        0x01 => ((dsp_registers.m >> 24) as u32) & 0xFFFFFF,
708        0x02 => (dsp_registers.m as u32) & 0xFFFFFF,
709        0x03 => dsp_registers.ext_buffer.into(),
710        0x08 => dsp_registers.rom_buffer,
711        0x0C => dsp_registers.ram_buffer,
712        0x13 => dsp_registers.ext_pointer,
713        0x1C => dsp_registers.ram_pointer.into(),
714        0x20 => cx4_registers.instruction_pointer.into(),
715        0x28 => dsp_registers.page.into(),
716        // $50-$5F are "fake" registers that always read constant values
717        0x50..=0x5F => CONSTANT_REGISTERS[(opcode & 0xF) as usize],
718        0x60..=0x6F => cx4_registers.gpr[(opcode & 0xF) as usize],
719        _ => {
720            log::warn!("Unhandled CX4 coprocessor register read: {:02X}", opcode & 0xFF);
721            0x00
722        }
723    }
724}
725
726fn write_register(
727    cx4_registers: &mut Cx4Registers,
728    dsp_registers: &mut DspRegisters,
729    opcode: u16,
730    value: u32,
731) {
732    match opcode & 0xFF {
733        0x00 => {
734            dsp_registers.a = value;
735        }
736        0x03 => {
737            dsp_registers.ext_buffer = value as u8;
738        }
739        0x0C => {
740            dsp_registers.ram_buffer = value & 0xFFFFFF;
741        }
742        0x13 => {
743            dsp_registers.ext_pointer = value & 0xFFFFFF;
744        }
745        0x1C => {
746            dsp_registers.ram_pointer = (value & 0xFFF) as u16;
747        }
748        0x20 => {
749            cx4_registers.instruction_pointer = value as u8;
750        }
751        0x28 => {
752            dsp_registers.page = value as u16;
753        }
754        0x60..=0x6F => {
755            let register_idx = (opcode & 0xF) as usize;
756            cx4_registers.gpr[register_idx] = value & 0xFFFFFF;
757        }
758        _ => {
759            log::warn!("Unhandled CX4 coprocessor register write: {:02X}", opcode & 0xFF);
760        }
761    }
762}