Skip to content

Commit

Permalink
Implement multiplication and division unit
Browse files Browse the repository at this point in the history
  • Loading branch information
twvd committed Nov 5, 2023
1 parent 70196e2 commit 1962263
Showing 1 changed file with 130 additions and 0 deletions.
130 changes: 130 additions & 0 deletions src/snes/bus/mainbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ where
nmitimen: u8,

apumock: RefCell<[u8; 4]>,

/// Multiplication/division unit registers
wrmpya: u8,
wrdiv: u16,
rddiv: u16,
rdmpy: u16,
}

enum DMADirection {
Expand Down Expand Up @@ -164,6 +170,11 @@ where
nmitimen: 0,

apumock: RefCell::new([0xAA, 0, 0, 0]),

wrmpya: 0,
wrdiv: 0,
rddiv: 0,
rdmpy: 0,
}
}

Expand Down Expand Up @@ -246,6 +257,16 @@ where
0x2181..=0x2183 => None,
// NMITIMEN - Interrupt Enable and Joypad Request (W)
0x4200 => None,
// WRMPYA - Set unsigned 8bit Multiplicand (W)
0x4202 => None,
// WRMPYB - Set unsigned 8bit Multiplier and Start Multiplication (W)
0x4203 => None,
// WRDIVL - Set unsigned 16bit Dividend (lower 8bit) (W)
0x4204 => None,
// WRDIVH - Set unsigned 16bit Dividend (upper 8bit) (W)
0x4205 => None,
// WRDIVB - Set unsigned 8bit Divisor and Start Division (W)
0x4206 => None,
// MDMAEN - Select General Purpose DMA Channel(s) and Start Transfer
0x420B => Some(0xFF),
// MEMSEL - Memory-2 Waitstate Control
Expand All @@ -271,6 +292,14 @@ where

Some(result)
}
// RDDIVL - Unsigned Division Result (Quotient) (lower 8bit) (R)
0x4214 => Some(self.rddiv as u8),
// RDDIVH - Unsigned Division Result (Quotient) (upper 8bit) (R)
0x4215 => Some((self.rddiv >> 8) as u8),
// RDMPYL - Unsigned Division Remainder / Multiply Product (lo.8bit) (R)
0x4216 => Some(self.rdmpy as u8),
// RDMPYH - Unsigned Division Remainder / Multiply Product (up.8bit) (R)
0x4217 => Some((self.rdmpy >> 8) as u8),
// JOYxx - Joypads
0x4218..=0x421F => Some(0),
// DMA parameter area
Expand Down Expand Up @@ -383,10 +412,49 @@ where
}
Some(self.nmitimen = val)
}
// WRMPYA - Set unsigned 8bit Multiplicand (W)
0x4202 => Some(self.wrmpya = val),
// WRMPYB - Set unsigned 8bit Multiplier and Start Multiplication (W)
0x4203 => {
let result = u16::from(self.wrmpya).wrapping_mul(val as u16);

// This is a hardware quirk, multiplication destroys
// the division result.
self.rddiv = val as u16;

//println!("Multiply: {} * {} = {}", self.wrmpya, val, result);

Some(self.rdmpy = result)
}
// WRDIVL - Set unsigned 16bit Dividend (lower 8bit) (W)
0x4204 => Some(self.wrdiv = (self.wrdiv & 0xFF00) | (val as u16)),
// WRDIVH - Set unsigned 16bit Dividend (upper 8bit) (W)
0x4205 => Some(self.wrdiv = (self.wrdiv & 0x00FF) | ((val as u16) << 8)),
// WRDIVB - Set unsigned 8bit Divisor and Start Division (W)
0x4206 => {
if val == 0 {
println!("Division by zero: {} / {}", self.wrdiv, val);
self.rddiv = 0xFFFF;
self.rdmpy = self.wrdiv;
} else {
self.rddiv = self.wrdiv / val as u16;
self.rdmpy = self.wrdiv.rem_euclid(val as u16);
//println!("Division: {} / {} = {} ({})", self.wrdiv, val, self.rddiv, self.rdmpy);
}
Some(())
}
// MDMAEN - Select General Purpose DMA Channel(s) and Start Transfer
0x420B => Some(self.gdma_run(val)),
// MEMSEL - Memory-2 Waitstate Control
0x420D => Some(self.memsel = val),
// RDDIVL - Unsigned Division Result (Quotient) (lower 8bit) (R)
0x4214 => None,
// RDDIVH - Unsigned Division Result (Quotient) (upper 8bit) (R)
0x4215 => None,
// RDMPYL - Unsigned Division Remainder / Multiply Product (lo.8bit) (R)
0x4216 => None,
// RDMPYH - Unsigned Division Remainder / Multiply Product (up.8bit) (R)
0x4217 => None,
// DMA parameter area
0x4300..=0x43FF => {
let ch = (addr >> 4) & 0x07;
Expand Down Expand Up @@ -522,4 +590,66 @@ mod tests {
bus.read(0x000000);
assert_eq!(bus.read(0x004210), 0x72);
}

#[test]
fn multiply() {
let mut bus = mainbus();

let mut mul = |a, b| {
bus.write(0x4202, a);
bus.write(0x4203, b);
let l = bus.read(0x4216) as u16;
let h = bus.read(0x4217) as u16;

// Div results hardware quirk
assert_eq!(bus.read(0x4214), b);
assert_eq!(bus.read(0x4215), 0);
h << 8 | l
};

assert_eq!(mul(10, 12), 120);
assert_eq!(mul(0, 12), 0);
assert_eq!(mul(1, 12), 12);
assert_eq!(mul(255, 255), 65025);
}

#[test]
fn divide() {
let mut bus = mainbus();

let mut div = |a, b| {
bus.write16(0x4204, a);
bus.write(0x4206, b);
let l = bus.read(0x4214) as u16;
let h = bus.read(0x4215) as u16;
let rl = bus.read(0x4216) as u16;
let rh = bus.read(0x4217) as u16;

(h << 8 | l, rh << 8 | rl)
};

assert_eq!(div(12, 10), (1, 2));
assert_eq!(div(10, 10), (1, 0));
assert_eq!(div(65535, 255), (257, 0));
assert_eq!(div(65500, 255), (256, 220));
}

#[test]
fn divide_by_zero() {
let mut bus = mainbus();

let mut div = |a, b| {
bus.write16(0x4204, a);
bus.write(0x4206, b);
let l = bus.read(0x4214) as u16;
let h = bus.read(0x4215) as u16;
let rl = bus.read(0x4216) as u16;
let rh = bus.read(0x4217) as u16;

(h << 8 | l, rh << 8 | rl)
};

assert_eq!(div(123, 0), (0xFFFF, 123));
assert_eq!(div(0x1234, 0), (0xFFFF, 0x1234));
}
}

0 comments on commit 1962263

Please sign in to comment.