NEC µPD77C25 and µPD96050 CPUs, used in the following SNES coprocessor chips:

  • DSP-1 (~16 games, including Super Mario Kart and Pilotwings)
  • DSP-2 (1 game, Dungeon Master)
  • DSP-3 (1 game, SD Gundam GX)
  • DSP-4 (1 game, Top Gear 3000)
  • ST010 (1 game, F1 ROC II: Race of Champions)
  • ST011 (1 game, Hayazashi Nidan Morita Shougi)
9mod instructions;
11use crate::common;
12use bincode::{Decode, Encode};
13use jgenesis_common::frontend::TimingMode;
14use jgenesis_common::num::{GetBit, U16Ext};
15
16pub const ST01X_RAM_LEN_BYTES: usize = Upd77c25Variant::St011.ram_len_words() << 1;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
19pub enum Upd77c25Variant {
20    // DSP-1 / DSP-2 / DSP-3 / DSP-4
21    Dsp,
22    St010,
23    St011,
24}
25
26impl Upd77c25Variant {
27    const fn program_rom_len_opcodes(self) -> usize {
28        match self {
29            Self::Dsp => 1 << 11,
30            Self::St010 | Self::St011 => 1 << 14,
31        }
32    }
33
34    const fn data_rom_len_words(self) -> usize {
35        match self {
36            Self::Dsp => 1 << 10,
37            Self::St010 | Self::St011 => 1 << 11,
38        }
39    }
40
41    const fn ram_len_words(self) -> usize {
42        match self {
43            Self::Dsp => 1 << 8,
44            Self::St010 | Self::St011 => 1 << 11,
45        }
46    }
47
48    const fn stack_len(self) -> u8 {
49        match self {
50            Self::Dsp => 4,
51            Self::St010 | Self::St011 => 8,
52        }
53    }
54
55    const fn clock_speed(self) -> u64 {
56        match self {
57            Self::Dsp => 8_000_000,
58            Self::St010 => 10_000_000,
59            Self::St011 => 15_000_000,
60        }
61    }
62}
63
64#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
65struct FlagsRegister {
66    z: bool,
67    c: bool,
68    s0: bool,
69    s1: bool,
70    ov0: bool,
71    ov1: bool,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
75enum DataRegisterBits {
76    Eight,
77    #[default]
78    Sixteen,
79}
80
81impl DataRegisterBits {
82    fn to_bit(self) -> bool {
83        self == Self::Eight
84    }
85
86    fn from_bit(bit: bool) -> Self {
87        if bit { Self::Eight } else { Self::Sixteen }
88    }
89}
90
91#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
92struct StatusRegister {
93    request_for_master: bool,
94    user_flag_0: bool,
95    user_flag_1: bool,
96    dr_busy: bool,
97    dr_control: DataRegisterBits,
98}
99
100impl StatusRegister {
101    fn write(&mut self, value: u16) {
102        self.user_flag_1 = value.bit(14);
103        self.user_flag_0 = value.bit(13);
104        self.dr_control = DataRegisterBits::from_bit(value.bit(10));
105        log::trace!("DR control set to {:?}", self.dr_control);
106    }
107}
108
109impl From<StatusRegister> for u8 {
110    fn from(value: StatusRegister) -> Self {
111        (u8::from(value.request_for_master) << 7)
112            | (u8::from(value.user_flag_1) << 6)
113            | (u8::from(value.user_flag_0) << 5)
114            | (u8::from(value.dr_busy) << 4)
115            | (u8::from(value.dr_control.to_bit()) << 2)
116    }
117}
118
119#[derive(Debug, Clone, Encode, Decode)]
120struct Registers {
121    dp: u16,
122    rp: u16,
123    pc: u16,
124    stack: [u16; 8],
125    stack_idx: u8,
126    stack_len: u8,
127    k: i16,
128    l: i16,
129    accumulator_a: i16,
130    accumulator_b: i16,
131    flags_a: FlagsRegister,
132    flags_b: FlagsRegister,
133    tr: i16,
134    trb: i16,
135    sr: StatusRegister,
136    dr: u16,
137    so: u16,
138}
139
140impl Registers {
141    fn new(variant: Upd77c25Variant) -> Self {
142        let stack_len = variant.stack_len();
143
144        Self {
145            dp: 0,
146            rp: 0x3FF,
147            pc: 0,
148            stack: [0; 8],
149            stack_idx: 0,
150            stack_len,
151            k: 0,
152            l: 0,
153            accumulator_a: 0,
154            accumulator_b: 0,
155            flags_a: FlagsRegister::default(),
156            flags_b: FlagsRegister::default(),
157            tr: 0,
158            trb: 0,
159            sr: StatusRegister::default(),
160            dr: 0,
161            so: 0,
162        }
163    }
164
165    fn snes_read_data(&mut self) -> u8 {
166        match self.sr.dr_control {
167            DataRegisterBits::Eight => {
168                self.sr.request_for_master = false;
169
170                self.dr.lsb()
171            }
172            DataRegisterBits::Sixteen => {
173                if self.sr.dr_busy {
174                    self.sr.dr_busy = false;
175                    self.sr.request_for_master = false;
176
177                    self.dr.msb()
178                } else {
179                    self.sr.dr_busy = true;
180
181                    self.dr.lsb()
182                }
183            }
184        }
185    }
186
187    fn snes_write_data(&mut self, value: u8) {
188        match self.sr.dr_control {
189            DataRegisterBits::Eight => {
190                self.sr.request_for_master = false;
191
192                self.dr = value.into();
193            }
194            DataRegisterBits::Sixteen => {
195                if self.sr.dr_busy {
196                    self.sr.dr_busy = false;
197                    self.sr.request_for_master = false;
198
199                    self.dr.set_msb(value);
200                } else {
201                    self.sr.dr_busy = true;
202
203                    self.dr.set_lsb(value);
204                }
205            }
206        }
207    }
208
209    fn upd_write_data(&mut self, value: u16) {
210        log::trace!("Wrote {value:04X} to DR");
211
212        self.dr = value;
213        self.sr.request_for_master = true;
214    }
215
216    fn reset(&mut self) {
217        self.pc = 0;
218        self.flags_a = FlagsRegister::default();
219        self.flags_b = FlagsRegister::default();
220        self.sr = StatusRegister::default();
221        self.rp = 0x3FF;
222    }
223
224    fn kl(&self) -> i32 {
225        i32::from(self.k) * i32::from(self.l)
226    }
227
228    fn push_stack(&mut self, pc: u16) {
229        self.stack[self.stack_idx as usize] = pc;
230        self.stack_idx = (self.stack_idx + 1) & (self.stack_len - 1);
231    }
232
233    fn pop_stack(&mut self) -> u16 {
234        log::trace!("Returning, current stack IDX is {}", self.stack_idx);
235
236        self.stack_idx = self.stack_idx.wrapping_sub(1) & (self.stack_len - 1);
237        self.stack[self.stack_idx as usize]
238    }
239}
240
241#[derive(Debug, Clone, Encode, Decode)]
242pub struct Upd77c25 {
243    program_rom: Box<[u32]>,
244    data_rom: Box<[u16]>,
245    ram: Box<[u16]>,
246    registers: Registers,
247    idling: bool,
248    pc_mask: u16,
249    dp_mask: u16,
250    rp_mask: u16,
251    variant: Upd77c25Variant,
252    clock_speed: u64,
253    snes_mclk_speed: u64,
254    master_cycles_product: u64,
255}
256
257impl Upd77c25 {
258    #[must_use]
259    pub fn new(rom: &[u8], variant: Upd77c25Variant, sram: &[u8], timing_mode: TimingMode) -> Self {
260        let (program_rom, data_rom) = convert_rom(rom, variant);
261
262        let ram = match variant {
263            Upd77c25Variant::Dsp => vec![0; variant.ram_len_words()],
264            Upd77c25Variant::St010 | Upd77c25Variant::St011 => {
265                convert_to_u16(sram, Endianness::Little)
266            }
267        };
268
269        let snes_mclk_speed = match timing_mode {
270            TimingMode::Ntsc => common::NTSC_MASTER_CLOCK_FREQUENCY,
271            TimingMode::Pal => common::PAL_MASTER_CLOCK_FREQUENCY,
272        };
273
274        Self {
275            program_rom: program_rom.into_boxed_slice(),
276            data_rom: data_rom.into_boxed_slice(),
277            ram: ram.into_boxed_slice(),
278            registers: Registers::new(variant),
279            idling: false,
280            pc_mask: (variant.program_rom_len_opcodes() - 1) as u16,
281            dp_mask: (variant.ram_len_words() - 1) as u16,
282            rp_mask: (variant.data_rom_len_words() - 1) as u16,
283            variant,
284            clock_speed: variant.clock_speed(),
285            snes_mclk_speed,
286            master_cycles_product: 0,
287        }
288    }
289
290    pub fn read_data(&mut self) -> u8 {
291        let value = self.registers.snes_read_data();
292
293        if !self.registers.sr.request_for_master {
294            self.idling = false;
295        }
296
297        log::trace!("Data read: {value:02X}");
298
299        value
300    }
301
302    pub fn write_data(&mut self, value: u8) {
303        self.registers.snes_write_data(value);
304
305        log::trace!("Data write: {value:02X}");
306
307        if !self.registers.sr.request_for_master {
308            self.idling = false;
309        }
310    }
311
312    #[inline]
313    #[must_use]
314    pub fn read_status(&self) -> u8 {
315        self.registers.sr.into()
316    }
317
318    #[inline]
319    #[must_use]
320    pub fn read_ram(&self, address: u32) -> u8 {
321        let word = self.ram[((address >> 1) & 0x7FF) as usize];
322        if !address.bit(0) { word.lsb() } else { word.msb() }
323    }
324
325    #[inline]
326    pub fn write_ram(&mut self, address: u32, value: u8) {
327        let word_addr = ((address >> 1) & 0x7FF) as usize;
328        if !address.bit(0) {
329            self.ram[word_addr].set_lsb(value);
330        } else {
331            self.ram[word_addr].set_msb(value);
332        }
333    }
334
335    #[inline]
336    #[must_use]
337    pub fn sram(&self) -> &[u8] {
338        bytemuck::cast_slice(self.ram.as_ref())
339    }
340
341    #[inline]
342    pub fn tick(&mut self, master_cycles_elapsed: u64) {
343        if self.idling {
344            return;
345        }
346
347        self.master_cycles_product += master_cycles_elapsed * self.clock_speed;
348        while self.master_cycles_product >= self.snes_mclk_speed {
349            instructions::execute(self);
350            self.master_cycles_product -= self.snes_mclk_speed;
351        }
352    }
353
354    pub fn reset(&mut self) {
355        self.registers.reset();
356    }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360enum Endianness {
361    Little,
362    Big,
363}
364
365impl Endianness {
366    fn chunk_to_u16(self, chunk: [u8; 2]) -> u16 {
367        match self {
368            Self::Little => u16::from_le_bytes(chunk),
369            Self::Big => u16::from_be_bytes(chunk),
370        }
371    }
372
373    fn chunk_to_u32(self, chunk: [u8; 3]) -> u32 {
374        match self {
375            Self::Little => u32::from_le_bytes([chunk[0], chunk[1], chunk[2], 0]),
376            Self::Big => u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]]),
377        }
378    }
379}

Parse the ROM into program ROM and data ROM

382fn convert_rom(rom: &[u8], variant: Upd77c25Variant) -> (Vec<u32>, Vec<u16>) {
383    let endianness = detect_program_rom_endianness(rom);
384    log::info!("Detected ROM endian-ness: {endianness:?}");
385
386    let program_rom_len = 3 * variant.program_rom_len_opcodes();
387
388    let program_rom = convert_program_rom(&rom[..program_rom_len], endianness);
389    let data_rom = convert_to_u16(&rom[program_rom_len..], endianness);
390    (program_rom, data_rom)
391}

Convert program ROM from bytes to 24-bit opcodes

394fn convert_program_rom(program_rom: &[u8], endianness: Endianness) -> Vec<u32> {
395    program_rom.as_chunks::<3>().0.iter().map(|&chunk| endianness.chunk_to_u32(chunk)).collect()
396}

Convert data ROM from bytes to 16-bit words

399fn convert_to_u16(bytes: &[u8], endianness: Endianness) -> Vec<u16> {
400    bytes.as_chunks::<2>().0.iter().map(|&chunk| endianness.chunk_to_u16(chunk)).collect()
401}

All program ROMs used for this chip contain the opcode $97C00x in the first 4 opcodes, where x is 4 times the opcode number

405fn detect_program_rom_endianness(program_rom: &[u8]) -> Endianness {
406    for (i, &chunk) in program_rom.as_chunks::<3>().0.iter().enumerate().take(4) {
407        if chunk == [(i << 2) as u8, 0xC0, 0x97] {
408            return Endianness::Little;
409        }
410
411        if chunk == [0x97, 0xC0, (i << 2) as u8] {
412            return Endianness::Big;
413        }
414    }
415
416    log::warn!("Unable to detect uPD77C25 endian-ness; defaulting to little-endian");
417
418    Endianness::Little
419}