st018.rsannotatedst018.rssource367 lines · 11.7 KB · raw

ST018, a coprocessor with a pre-programmed 21 MHz ARMv3 CPU (probably an ARM6)

Used by only one game, Hayazashi Nidan Morita Shougi 2

ST018 is emulated using an ARM7TDMI implementation (ARMv4T) which is almost fully backwards compatible with ARMv3. ARMv4 removed support for the legacy 26-bit addressing mode, but HNMS2 does not use that functionality.

Memory access timings are not emulated because they are not known. All memory accesses are assumed to take 1 clock cycle which is probably not realistic.

12use arm7tdmi_emu::Arm7Tdmi;
13use arm7tdmi_emu::bus::{BusInterface, MemoryCycle, OpSize};
14use bincode::{Decode, Encode};
15use jgenesis_common::num::GetBit;
16use thiserror::Error;
18const PROGRAM_ROM_LEN_WORDS: usize = 128 * 1024 / 4;
19const DATA_ROM_LEN: usize = 32 * 1024;
20const RAM_LEN_WORDS: usize = 16 * 1024 / 4;
21
22const TOTAL_ROM_LEN: usize = 4 * PROGRAM_ROM_LEN_WORDS + DATA_ROM_LEN;
23
24type ProgramRom = [u32; PROGRAM_ROM_LEN_WORDS];
25type DataRom = [u8; DATA_ROM_LEN];
26type Ram = [u32; RAM_LEN_WORDS];
27
28#[derive(Debug, Clone, Encode, Decode)]
29struct Registers {
30    snes_to_arm_data: u8,
31    snes_to_arm_data_ready: bool,
32    arm_to_snes_data: u8,
33    arm_to_snes_data_ready: bool,
34    arm_to_snes_flag: bool,
35    arm_reset: bool,
36}
37
38impl Registers {
39    fn new() -> Self {
40        Self {
41            snes_to_arm_data: 0,
42            snes_to_arm_data_ready: false,
43            arm_to_snes_data: 0,
44            arm_to_snes_data_ready: false,
45            arm_to_snes_flag: false,
46            arm_reset: true,
47        }
48    }
49
50    fn arm_read(&mut self, address: u32) -> Option<u8> {
51        match address & 0xFF {
52            0x10 => {
53                // SNES-to-ARM data
54                self.snes_to_arm_data_ready = false;
55                Some(self.snes_to_arm_data)
56            }
57            0x20 => Some(self.read_status()),
58            _ => {
59                log::error!("Invalid ARM register read {address:08X}");
60                None
61            }
62        }
63    }
64
65    fn arm_write(&mut self, address: u32, value: u8) {
66        log::trace!("ARM register write {address:08X} {value:02X}");
67
68        match address & 0xFF {
69            0x00 => {
70                // ARM-to-SNES data
71                self.arm_to_snes_data = value;
72                self.arm_to_snes_data_ready = true;
73            }
74            0x10 => {
75                // ARM-to-SNES flag; writing any value sets the flag
76                self.arm_to_snes_flag = true;
77            }
78            0x20..=0x2F => {
79                // fullsnes says these are "config" registers; unclear what (if anything) they actually do
80                // HNMS2 writes to these very frequently but doesn't seem to depend on them doing anything
81            }
82            _ => {
83                log::error!("Invalid ARM register write {address:08X} {value:02X}");
84            }
85        }
86    }
87
88    fn snes_read(&mut self, address: u32) -> Option<u8> {
89        match address & 0xFFFF {
90            0x3800 => {
91                // ARM-to-SNES data
92                self.arm_to_snes_data_ready = false;
93                Some(self.arm_to_snes_data)
94            }
95            0x3802 => {
96                // Clear ARM-to-SNES flag; read value is undefined
97                self.arm_to_snes_flag = false;
98                None
99            }
100            0x3804 => Some(self.read_status()),
101            _ => {
102                log::error!("Invalid SNES register read {address:06X}");
103                None
104            }
105        }
106    }
107
108    fn snes_write(&mut self, address: u32, value: u8) {
109        log::trace!("SNES register write {address:06X} {value:02X}");
110
111        match address & 0xFFFF {
112            0x3802 => {
113                // SNES-to-ARM data
114                self.snes_to_arm_data = value;
115                self.snes_to_arm_data_ready = true;
116            }
117            0x3804 => {
118                // Reset ARM CPU
119                self.arm_reset = value.bit(0);
120            }
121            _ => {
122                log::error!("Invalid SNES register write {address:06X} {value:02X}");
123            }
124        }
125    }
126
127    fn read_status(&self) -> u8 {
128        // Reset finished (bit 7) and ??? (bit 6) hardcoded to 1
129        u8::from(self.arm_to_snes_data_ready)
130            | (u8::from(self.arm_to_snes_flag) << 2)
131            | (u8::from(self.snes_to_arm_data_ready) << 3)
132            | (1 << 6)
133            | (1 << 7)
134    }
135}
136
137#[derive(Debug, Clone, Encode, Decode)]
138struct ArmBus {
139    program_rom: Box<ProgramRom>,
140    data_rom: Box<DataRom>,
141    ram: Box<Ram>,
142    registers: Registers,
143    cycles: u64,
144    open_bus: u32,
145}
146
147macro_rules! invalid_size {
148    ($size:expr) => {
149        panic!("Invalid size, must be 0-2: {}", $size)
150    };
151}

All reads from $60000000-$7FFFFFFF return this value according to: https://forums.bannister.org/ubbthreads.php?ubb=showflat&Number=77760&page=all Not sure if the game depends on the value read, but it does occasionally read from these addresses

156const ADDRESS_60_READS: u32 = 0x40404001;
158impl ArmBus {
159    fn read_open_bus<const SIZE: u8>(&self, address: u32) -> u32 {
160        match SIZE {
161            OpSize::BYTE => self.open_bus.to_le_bytes()[(address & 3) as usize].into(),
162            OpSize::WORD => self.open_bus,
163            _ => invalid_size!(SIZE),
164        }
165    }
166
167    fn update_open_bus<const SIZE: u8>(&mut self, value: u32) {
168        // TODO this is probably not right for 8-bit open bus
169        match SIZE {
170            OpSize::BYTE => {
171                self.open_bus = u32::from_ne_bytes([value as u8; 4]);
172            }
173            OpSize::WORD => {
174                self.open_bus = value;
175            }
176            _ => invalid_size!(SIZE),
177        }
178    }
179}
180
181impl BusInterface for ArmBus {
182    #[inline]
183    fn read<const SIZE: u8>(&mut self, address: u32, _cycle: MemoryCycle) -> u32 {
184        self.cycles += 1;
185
186        if SIZE == OpSize::HALFWORD {
187            log::error!("ST018 has an ARMv3 CPU; does not support halfword reads");
188            return 0;
189        }
190
191        let value = match address {
192            0x00000000..=0x1FFFFFFF => {
193                // Program ROM
194                let rom_addr = ((address >> 2) as usize) & (PROGRAM_ROM_LEN_WORDS - 1);
195                let word = self.program_rom[rom_addr];
196                match SIZE {
197                    OpSize::BYTE => word.to_le_bytes()[(address & 3) as usize].into(),
198                    OpSize::WORD => word,
199                    _ => invalid_size!(SIZE),
200                }
201            }
202            0x40000000..=0x5FFFFFFF => {
203                // I/O registers
204                let Some(byte) = self.registers.arm_read(address) else {
205                    return self.read_open_bus::<SIZE>(address);
206                };
207                byte.into()
208            }
209            0x60000000..=0x7FFFFFFF => match SIZE {
210                OpSize::BYTE => ADDRESS_60_READS.to_le_bytes()[(address & 3) as usize].into(),
211                OpSize::WORD => ADDRESS_60_READS,
212                _ => invalid_size!(SIZE),
213            },
214            0xA0000000..=0xBFFFFFFF => {
215                // Data ROM; only has an 8-bit data bus
216                // TODO 32-bit reads are probably not accurate; this code path is not exercised
217                let rom_addr = (address as usize) & (DATA_ROM_LEN - 1);
218                let byte = self.data_rom[rom_addr];
219                match SIZE {
220                    OpSize::BYTE => byte.into(),
221                    OpSize::WORD => u32::from_ne_bytes([byte; 4]),
222                    _ => invalid_size!(SIZE),
223                }
224            }
225            0xE0000000..=0xFFFFFFFF => {
226                // RAM
227                let ram_addr = ((address >> 2) as usize) & (RAM_LEN_WORDS - 1);
228                let word = self.ram[ram_addr];
229                match SIZE {
230                    OpSize::BYTE => word.to_le_bytes()[(address & 3) as usize].into(),
231                    OpSize::WORD => word,
232                    _ => invalid_size!(SIZE),
233                }
234            }
235            _ => return self.read_open_bus::<SIZE>(address),
236        };
237
238        self.update_open_bus::<SIZE>(value);
239
240        value
241    }
242
243    #[inline]
244    fn write<const SIZE: u8>(&mut self, address: u32, value: u32, _cycle: MemoryCycle) {
245        self.cycles += 1;
246
247        if SIZE == OpSize::HALFWORD {
248            log::error!("ST018 has an ARMv3 CPU; does not support halfword writes");
249            return;
250        }
251
252        self.update_open_bus::<SIZE>(value);
253
254        match address {
255            0x40000000..=0x5FFFFFFF => {
256                // I/O registers
257                self.registers.arm_write(address, value as u8);
258            }
259            0xE0000000..=0xFFFFFFFF => {
260                // RAM
261                let ram_addr = ((address >> 2) as usize) & (RAM_LEN_WORDS - 1);
262                match SIZE {
263                    OpSize::BYTE => {
264                        let mut bytes = self.ram[ram_addr].to_le_bytes();
265                        bytes[(address & 3) as usize] = value as u8;
266                        self.ram[ram_addr] = u32::from_le_bytes(bytes);
267                    }
268                    OpSize::WORD => {
269                        self.ram[ram_addr] = value;
270                    }
271                    _ => invalid_size!(SIZE),
272                }
273            }
274            _ => {}
275        }
276    }
277
278    #[inline]
279    fn irq(&self) -> bool {
280        false
281    }
282
283    #[inline]
284    fn internal_cycles(&mut self, cycles: u32) {
285        self.cycles += u64::from(cycles);
286    }
287}
288
289#[derive(Debug, Error)]
290pub enum St018LoadError {
291    #[error("Expected ROM size of {expected} bytes, was {actual} bytes")]
292    IncorrectRomSize { expected: usize, actual: usize },
293}
294
295#[derive(Debug, Clone, Encode, Decode)]
296pub struct St018 {
297    cpu: Arm7Tdmi<ArmBus>,
298    bus: ArmBus,
299    snes_cycles: u64,
300}
301
302impl St018 {

Errors

Returns an error if the ST018 program/data ROM is invalid.

306    #[allow(clippy::missing_panics_doc)]
307    pub fn new(st018_rom: &[u8]) -> Result<Self, St018LoadError> {
308        let (program_rom, data_rom) = convert_st018_rom(st018_rom)?;
309
310        let bus = ArmBus {
311            program_rom,
312            data_rom,
313            ram: vec![0; RAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
314            registers: Registers::new(),
315            cycles: 0,
316            open_bus: 0,
317        };
318
319        Ok(Self { cpu: Arm7Tdmi::new(), bus, snes_cycles: 0 })
320    }
322    pub fn tick(&mut self, snes_master_cycles: u64) {
323        // ST018 has its own 21 MHz oscillator, but it runs at almost the exact same frequency as
324        // the SNES master oscillator, so just assume they're the same speed
325        self.snes_cycles += snes_master_cycles;
326
327        if self.bus.registers.arm_reset {
328            self.bus.registers.arm_reset = false;
329            self.cpu.reset(&mut self.bus);
330        }
331
332        while self.bus.cycles < self.snes_cycles {
333            self.cpu.execute_instruction(&mut self.bus);
334        }
335    }
336
337    pub fn snes_read(&mut self, address: u32) -> Option<u8> {
338        self.bus.registers.snes_read(address)
339    }
340
341    pub fn snes_write(&mut self, address: u32, value: u8) {
342        self.bus.registers.snes_write(address, value);
343    }
344}
345
346fn convert_st018_rom(rom: &[u8]) -> Result<(Box<ProgramRom>, Box<DataRom>), St018LoadError> {
347    if rom.len() < TOTAL_ROM_LEN {
348        return Err(St018LoadError::IncorrectRomSize {
349            expected: TOTAL_ROM_LEN,
350            actual: rom.len(),
351        });
352    }
353
354    let program_rom: Vec<_> = rom[..4 * PROGRAM_ROM_LEN_WORDS]
355        .as_chunks::<4>()
356        .0
357        .iter()
358        .map(|&chunk| u32::from_le_bytes(chunk))
359        .collect();
360    let program_rom: Box<ProgramRom> = program_rom.into_boxed_slice().try_into().unwrap();
361
362    let data_rom =
363        rom[4 * PROGRAM_ROM_LEN_WORDS..4 * PROGRAM_ROM_LEN_WORDS + DATA_ROM_LEN].to_vec();
364    let data_rom: Box<DataRom> = data_rom.into_boxed_slice().try_into().unwrap();
365
366    Ok((program_rom, data_rom))
367}