divu.rsannotateddivu.rssource130 lines · 4.7 KB · raw

SH-2 division unit (DIVU)

Supports signed 64-bit ÷ 32-bit division with a 32-bit quotient and a 32-bit remainder

5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
8#[derive(Debug, Clone, Encode, Decode)]
9pub struct DivisionUnit {
10    pub divisor: i32,
11    pub quotient: u32,
12    pub remainder: u32,
13    pub overflow_interrupt_enabled: bool,
14    pub overflow_flag: bool,
15}
16
17impl DivisionUnit {
18    pub fn new() -> Self {
19        Self {
20            divisor: 0,
21            quotient: 0,
22            remainder: 0,
23            overflow_interrupt_enabled: false,
24            overflow_flag: false,
25        }
26    }
27
28    pub fn read_register(&self, address: u32) -> u32 {
29        log::trace!("DIVU register read {address:08X}");
30
31        match address {
32            // DVSR (Divisor)
33            0xFFFFFF00 => self.divisor as u32,
34            // DVDNT / DVDNTL (Quotient)
35            // Virtua Fighter seems to expect $FFFFFF1C to mirror $FFFFFF14
36            // TODO are DVDNT and DVDNTL actually the same register? it seems like it
37            0xFFFFFF04 | 0xFFFFFF14 | 0xFFFFFF1C => self.quotient,
38            // DVDNTH (Remainder)
39            // Virtua Fighter seems to expect $FFFFFF18 to mirror $FFFFFF10
40            0xFFFFFF10 | 0xFFFFFF18 => self.remainder,
41            // DVCNT (Division control)
42            0xFFFFFF08 => self.read_control(),
43            _ => {
44                log::error!("Invalid DIVU register address: {address:08X}");
45                0
46            }
47        }
48    }
49
50    pub fn read_control(&self) -> u32 {
51        (u32::from(self.overflow_interrupt_enabled) << 1) | u32::from(self.overflow_flag)
52    }
53
54    pub fn write_register(&mut self, address: u32, value: u32) {
55        log::trace!("DIVU register write {address:08X} {value:08X}");
56
57        match address {
58            0xFFFFFF00 => {
59                // DVSR: Divisor
60                self.divisor = value as i32;
61            }
62            0xFFFFFF04 => {
63                // DVDNT: Dividend for 32-bit division + execute 32-bit division
64                let dividend = value as i32;
65                if self.divisor == 0 {
66                    self.quotient = overflow_result(dividend.into());
67                    self.overflow_flag = true;
68                    // TODO overflow interrupt
69                    return;
70                }
71
72                let quotient = dividend / self.divisor;
73                let remainder = dividend % self.divisor;
74
75                log::trace!("div32 {dividend} / {} = {quotient} {remainder}", self.divisor);
76
77                self.quotient = quotient as u32;
78                self.remainder = remainder as u32;
79            }
80            0xFFFFFF08 => {
81                // DVCR: Division control register
82                self.overflow_interrupt_enabled = value.bit(1);
83                self.overflow_flag = value.bit(0);
84
85                if self.overflow_interrupt_enabled {
86                    log::error!("DIVU overflow interrupt enabled; not emulated");
87                }
88
89                log::debug!("DVCR write: {value:08X}");
90                log::debug!("  Overflow interrupt enabled: {}", self.overflow_interrupt_enabled);
91                log::debug!("  Overflow flag write: {}", self.overflow_flag);
92            }
93            0xFFFFFF10 | 0xFFFFFF18 => {
94                // DVDNTH: High longword of dividend for 64-bit division
95                // Store in the remainder register so software will get the same value back on reads
96                // if no division operation is executed in between
97                self.remainder = value;
98            }
99            0xFFFFFF14 | 0xFFFFFF1C => {
100                // DVDNTL: Low longword of dividend for 64-bit division + execute 64-bit division
101                let dividend = (i64::from(self.remainder) << 32) | i64::from(value);
102                if self.divisor == 0 {
103                    self.quotient = overflow_result(dividend);
104                    self.overflow_flag = true;
105                    // TODO overflow interrupt
106                    return;
107                }
108
109                let divisor: i64 = self.divisor.into();
110                let quotient = dividend / divisor;
111                let remainder = dividend % divisor;
112
113                let clamped_quotient = quotient.clamp(i32::MIN.into(), i32::MAX.into());
114                self.overflow_flag |= clamped_quotient != quotient;
115
116                log::trace!("div64 {dividend} / {divisor} = {quotient}, {remainder}");
117
118                self.quotient = clamped_quotient as u32;
119                self.remainder = remainder as u32;
120            }
121            _ => {
122                log::warn!("Invalid DIVU register address: {address:08X} {value:08X}");
123            }
124        }
125    }
126}
127
128fn overflow_result(dividend: i64) -> u32 {
129    if dividend >= 0 { 0x7FFFFFFF } else { 0x80000000 }
130}