1//! SH-2 division unit (DIVU) 2//! 3//! Supports signed 64-bit ÷ 32-bit division with a 32-bit quotient and a 32-bit remainder 4 5use bincode::{Decode, Encode}; 6use jgenesis_common::num::GetBit; 7 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}