svp.rsannotatedsvp.rssource492 lines · 15.2 KB · raw

Sega Virtua Processor (SVP), auxiliary cartridge hardware used exclusively in Virtua Racing

Implementation based on documentation and reverse engineering work by notaz and Tasco Deluxe: https://notaz.gp2x.de/docs/svpdoc.txt

6mod ssp1601;
8use bincode::{Decode, Encode};
9use jgenesis_common::num::{GetBit, U16Ext};
10use std::array;
11
12const SVP_ENTRY_POINT: u16 = 0x400;
13
14const DRAM_LEN_WORDS: usize = 128 * 1024 / 2;
15const IRAM_LEN_WORDS: usize = 1024;
16const INTERNAL_RAM_LEN_WORDS: usize = 256;
17
18const STACK_LEN: u8 = 6;

External memory addresses are 21-bit

21const EXTERNAL_MEMORY_MASK: u32 = (1 << 21) - 1;
23type Dram = [u16; DRAM_LEN_WORDS];
24type Iram = [u16; IRAM_LEN_WORDS];
25type InternalRam = [u16; INTERNAL_RAM_LEN_WORDS];

ST register, control and status bits

28#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
29struct StatusRegister {
30    // Control bits
31    loop_size: u8,
32    st5: bool,
33    st6: bool,
34    // Status bits
35    zero: bool,
36    negative: bool,
37}
39impl StatusRegister {
40    fn loop_modulo(self) -> u8 {
41        if self.loop_size != 0 { 1 << self.loop_size } else { 0 }
42    }
43
44    // The ST5 and ST6 bits control whether register 8 maps to PM0 or XST status.
45    // They also supposedly control whether register 11 maps to PM3 or XST, but Virtua Racing never
46    // accesses register 11 with the bits set
47    fn st_bits_set(self) -> bool {
48        self.st5 || self.st6
49    }
50
51    fn write(&mut self, value: u16) {
52        self.loop_size = (value & 0x07) as u8;
53        self.st5 = value.bit(5);
54        self.st6 = value.bit(6);
55        self.zero = value.bit(13);
56        self.negative = value.bit(15);
57    }
58}
59
60impl From<StatusRegister> for u16 {
61    fn from(value: StatusRegister) -> Self {
62        (u16::from(value.negative) << 15)
63            | (u16::from(value.zero) << 13)
64            | (u16::from(value.st6) << 6)
65            | (u16::from(value.st5) << 5)
66            | u16::from(value.loop_size)
67    }
68}

STACK register, port to a 6-level hardware stack

71#[derive(Debug, Clone, Default, Encode, Decode)]
72struct StackRegister {
73    stack: [u16; STACK_LEN as usize],
74    pointer: u8,
75}
77impl StackRegister {
78    fn push(&mut self, value: u16) {
79        self.stack[self.pointer as usize] = value;
80        self.pointer = (self.pointer + 1) % STACK_LEN;
81    }
82
83    fn pop(&mut self) -> u16 {
84        self.pointer = if self.pointer == 0 { STACK_LEN - 1 } else { self.pointer - 1 };
85        self.stack[self.pointer as usize]
86    }
87}

PM0-4 registers, which are ports used by the DSP to access external memory. Each PM register can be individually configured with an external memory address, auto-increment settings, and an overwrite mode for writes

92#[derive(Debug, Clone, Default, Encode, Decode)]
93struct ProgrammableMemoryRegister {
94    address: u32,
95    auto_increment: u32,
96    auto_increment_negative: bool,
97    auto_increment_bits: u16,
98    special_increment_mode: bool,
99    overwrite_mode: bool,
100}
102impl ProgrammableMemoryRegister {
103    fn initialize(&mut self, address: u16, mode: u16) {
104        // Bits 4-0 of mode are bits 20-16 of the 21-bit address
105        self.address = u32::from(address) | (u32::from(mode & 0x001F) << 16);
106
107        self.overwrite_mode = mode.bit(10);
108
109        // Auto increment bits of 0 indicate 0, 7 indicate 128, and other values indicate 2^(N-1).
110        // 7 actually indicates a custom auto-increment value instead of 128, but Virtua Racing
111        // always uses a custom value of 128 when it sets the auto-increment bits to 7
112        let auto_increment_bits = (mode >> 11) & 0x07;
113        self.auto_increment_bits = auto_increment_bits;
114        self.auto_increment = match auto_increment_bits {
115            0 => 0,
116            7 => 128,
117            _ => 1 << (auto_increment_bits - 1),
118        };
119
120        self.special_increment_mode = mode.bit(14);
121        self.auto_increment_negative = mode.bit(15);
122    }
123
124    fn get_and_increment_address(&mut self) -> u32 {
125        let address = self.address;
126
127        if self.special_increment_mode {
128            // "Special" increment mode increments the address by 1 if it is even and 31 if it is odd
129            if !address.bit(0) {
130                self.address = (self.address + 1) & EXTERNAL_MEMORY_MASK;
131            } else {
132                self.address = (self.address + 31) & EXTERNAL_MEMORY_MASK;
133            }
134        } else if self.auto_increment != 0 {
135            if self.auto_increment_negative {
136                self.address =
137                    self.address.wrapping_sub(self.auto_increment) & EXTERNAL_MEMORY_MASK;
138            } else {
139                self.address =
140                    self.address.wrapping_add(self.auto_increment) & EXTERNAL_MEMORY_MASK;
141            }
142        }
143
144        address
145    }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
149enum PmcWaitingFor {
150    #[default]
151    Address,
152    Mode,
153}
154
155impl PmcWaitingFor {
156    fn toggle(self) -> Self {
157        match self {
158            Self::Address => Self::Mode,
159            Self::Mode => Self::Address,
160        }
161    }
162}

PMC register, used to program the PM registers

165#[derive(Debug, Clone, Default, Encode, Decode)]
166struct ProgrammableMemoryControlRegister {
167    waiting_for: PmcWaitingFor,
168    address: u16,
169    mode: u16,
170}
172impl ProgrammableMemoryControlRegister {
173    fn read(&mut self) -> u16 {
174        let value = match self.waiting_for {
175            PmcWaitingFor::Address => self.address,
176            PmcWaitingFor::Mode => {
177                // If waiting for mode, return address but rotated by 4; direction doesn't matter
178                // because SVP always does this with both bytes equal
179                self.address.rotate_left(4)
180            }
181        };
182
183        self.waiting_for = self.waiting_for.toggle();
184
185        value
186    }
187
188    fn write(&mut self, value: u16) {
189        match self.waiting_for {
190            PmcWaitingFor::Address => {
191                self.address = value;
192            }
193            PmcWaitingFor::Mode => {
194                self.mode = value;
195            }
196        }
197
198        self.waiting_for = self.waiting_for.toggle();
199    }
200
201    fn update_from(&mut self, pm_register: &ProgrammableMemoryRegister) {
202        self.address = pm_register.address as u16;
203        self.mode = (u16::from(pm_register.auto_increment_negative) << 15)
204            | (u16::from(pm_register.special_increment_mode) << 14)
205            | (pm_register.auto_increment_bits << 11)
206            | (u16::from(pm_register.overwrite_mode) << 10)
207            | (pm_register.address >> 16) as u16;
208
209        log::trace!("Set PMC address to {:04X} and mode to {:04X}", self.address, self.mode);
210    }
211}

XST register, an R/W register used for communication between the DSP and the 68000

214#[derive(Debug, Clone, Default, Encode, Decode)]
215struct ExternalStatusRegister {
216    value: u16,
217    m68k_written: bool,
218    ssp_written: bool,
219}
221impl ExternalStatusRegister {
222    fn m68k_write(&mut self, value: u16) {
223        self.value = value;
224        self.m68k_written = true;
225    }
226
227    fn ssp_write(&mut self, value: u16) {
228        self.value = value;
229        self.ssp_written = true;
230    }
231
232    fn status(&self) -> u16 {
233        (u16::from(self.m68k_written) << 1) | u16::from(self.ssp_written)
234    }
235
236    fn m68k_read_status(&mut self) -> u16 {
237        let status = self.status();
238        self.ssp_written = false;
239        status
240    }
241
242    fn ssp_read_status(&mut self) -> u16 {
243        let status = self.status();
244        self.m68k_written = false;
245        status
246    }
247}
248
249#[derive(Debug, Clone, Encode, Decode)]
250struct Registers {
251    // General registers (0-7)
252    x: u16,
253    y: u16,
254    accumulator: u32,
255    status: StatusRegister,
256    stack: StackRegister,
257    pc: u16,
258    // External registers (8-15)
259    // PM registers are programmed separately for reads and for writes
260    pm_read: [ProgrammableMemoryRegister; 5],
261    pm_write: [ProgrammableMemoryRegister; 5],
262    pmc: ProgrammableMemoryControlRegister,
263    xst: ExternalStatusRegister,
264    // Pointer registers (0-2 and 4-6, 3 and 7 are not stored)
265    ram0_pointers: [u8; 3],
266    ram1_pointers: [u8; 3],
267}
268
269impl Registers {
270    fn new() -> Self {
271        Self {
272            x: 0,
273            y: 0,
274            accumulator: 0,
275            status: StatusRegister::default(),
276            stack: StackRegister::default(),
277            pc: SVP_ENTRY_POINT,
278            pm_read: array::from_fn(|_| ProgrammableMemoryRegister::default()),
279            pm_write: array::from_fn(|_| ProgrammableMemoryRegister::default()),
280            pmc: ProgrammableMemoryControlRegister::default(),
281            xst: ExternalStatusRegister::default(),
282            ram0_pointers: [0; 3],
283            ram1_pointers: [0; 3],
284        }
285    }
286
287    fn product(&self) -> u32 {
288        // P register always contains 2 * X * Y, where X and Y are sign extended from 16 bits to 32 bits
289        2_u32.wrapping_mul(self.x as i16 as u32).wrapping_mul(self.y as i16 as u32)
290    }
291}
292
293#[derive(Debug, Clone, Encode, Decode)]
294pub struct Svp {
295    registers: Registers,
296    dram: Box<Dram>,
297    iram: Box<Iram>,
298    ram0: Box<InternalRam>,
299    ram1: Box<InternalRam>,
300    halted: bool,
301    // Flag marking whether the 68000 has written to specific addresses in DRAM that are used for
302    // communication; used for idle loop detection
303    dram_dirty: bool,
304}
305
306impl Svp {
307    pub fn new() -> Self {
308        Self {
309            registers: Registers::new(),
310            dram: vec![0; DRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
311            iram: vec![0; IRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
312            ram0: vec![0; INTERNAL_RAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
313            ram1: vec![0; INTERNAL_RAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
314            halted: false,
315            dram_dirty: false,
316        }
317    }
318
319    pub fn tick(&mut self, rom: &[u16], m68k_cycles: u32) {
320        if self.halted {
321            return;
322        }
323
324        // Somewhat arbitrarily execute 3 instructions for every 68k cycle; this is close enough to
325        // the chip's actual speed of somewhere in the 20-25 MHz range, and Virtua Racing's code is
326        // not timing-sensitive
327        for _ in 0..3 * m68k_cycles {
328            // Hacky idle loop detection: if the SSP1601 is waiting for the 68000 to give it a
329            // command, don't bother executing anything until the 68000 writes to $FE06 or $FE08 in
330            // DRAM
331            if self.registers.pc == 0x0425 || self.registers.pc == 0x2789 {
332                if !self.dram_dirty {
333                    return;
334                }
335                self.dram_dirty = false;
336            }
337
338            // At startup, the SVP spins until the 68000 writes to the XST; don't execute until that
339            // happens
340            if self.registers.pc == SVP_ENTRY_POINT && !self.registers.xst.m68k_written {
341                return;
342            }
343
344            ssp1601::execute_instruction(self, rom);
345        }
346    }
347
348    pub fn m68k_read(&mut self, address: u32, rom: &[u16]) -> u16 {
349        match address {
350            0xA15004 => {
351                // XST status; reads clear the SSP1601 written flag
352                self.registers.xst.m68k_read_status()
353            }
354            _ => {
355                // No other addresses require mutating inner state
356                self.m68k_peek(address, rom)
357            }
358        }
359    }
360
361    pub fn m68k_peek(&self, address: u32, rom: &[u16]) -> u16 {
362        match address {
363            0x000000..=0x1FFFFF => {
364                // ROM
365                rom[(address >> 1) as usize]
366            }
367            0x300000..=0x37FFFF => {
368                // DRAM, mirrored every 128KB / $1FFFF
369                self.dram[((address & 0x1FFFF) >> 1) as usize]
370            }
371            0xA15000 | 0xA15002 => {
372                // XST register
373                self.registers.xst.value
374            }
375            0xA15004 => {
376                // XST status
377                self.registers.xst.status()
378            }
379            _ => {
380                // Invalid or unused
381                0xFFFF
382            }
383        }
384    }
385
386    pub fn m68k_write_byte(&mut self, address: u32, value: u8) {
387        match address {
388            0x300000..=0x37FFFF => {
389                // DRAM, mirrored every 128KB / $1FFFF
390                let word_addr = ((address & 0x1FFFF) >> 1) as usize;
391                if address.bit(0) {
392                    self.dram[word_addr].set_lsb(value);
393                } else {
394                    self.dram[word_addr].set_msb(value);
395                }
396
397                // Specific DRAM addresses used for communication between the 68000 and DSP
398                if word_addr == 0x7F03 || word_addr == 0x7F04 {
399                    self.dram_dirty = true;
400                }
401            }
402            _ => {
403                // Treat other writes as word-size
404                if address.bit(0) {
405                    self.m68k_write_word(address & !1, value.into());
406                } else {
407                    self.m68k_write_word(address, u16::from(value) << 8);
408                }
409            }
410        }
411    }
412
413    pub fn m68k_write_word(&mut self, address: u32, value: u16) {
414        match address {
415            0x300000..=0x37FFFF => {
416                // DRAM, mirrored every 128KB / $1FFFF
417                let word_addr = (address & 0x1FFFF) >> 1;
418                self.dram[word_addr as usize] = value;
419
420                // Specific DRAM addresses used for communication between the 68000 and DSP
421                if word_addr == 0x7F03 || word_addr == 0x7F04 {
422                    self.dram_dirty = true;
423                }
424            }
425            0xA15000 | 0xA15002 => {
426                // XST register
427                self.registers.xst.m68k_write(value);
428            }
429            0xA15006 => {
430                // SVP halt register
431                self.halted = value == 0x000A;
432            }
433            _ => {
434                // Invalid or unused
435            }
436        }
437    }
438
439    fn read_program_memory(&self, address: u16, rom: &[u16]) -> u16 {
440        match address {
441            0x0000..=0x03FF => {
442                // IRAM
443                self.iram[address as usize]
444            }
445            0x0400..=0xFFFF => {
446                // ROM (first 128KB); program memory address maps to the same address in ROM
447                rom[address as usize]
448            }
449        }
450    }
451
452    fn read_external_memory(&mut self, address: u32, rom: &[u16]) -> u16 {
453        log::trace!("External memory read: {address:06X}");
454
455        match address {
456            0x000000..=0x0FFFFF => {
457                // ROM
458                rom[address as usize]
459            }
460            0x180000..=0x18FFFF => {
461                // DRAM
462                self.dram[(address & 0xFFFF) as usize]
463            }
464            0x1C8000..=0x1C83FF => {
465                // IRAM
466                self.iram[(address & 0x3FF) as usize]
467            }
468            _ => {
469                // Invalid or unused
470                0xFFFF
471            }
472        }
473    }
474
475    fn write_external_memory(&mut self, address: u32, value: u16) {
476        log::trace!("External memory write: {address:06X} {value:04X}");
477
478        match address {
479            0x180000..=0x18FFFF => {
480                // DRAM
481                self.dram[(address & 0xFFFF) as usize] = value;
482            }
483            0x1C8000..=0x1C83FF => {
484                // IRAM
485                self.iram[(address & 0x3FF) as usize] = value;
486            }
487            _ => {
488                // Invalid or unused
489            }
490        }
491    }
492}