Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 16 additions & 24 deletions core/src/cpu_m68k/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4142,33 +4142,25 @@ where

self.prefetch_pump()?;

let (quotient, remainder) = match (extword.signed(), extword.size()) {
(false, false) => {
// 32-bit unsigned
(dividend / divisor, dividend % divisor)
}
(true, false) => {
// 32-bit signed
(
((dividend as u32 as i32) / (divisor as u32 as i32)) as i64 as u64,
((dividend as u32 as i32) % (divisor as u32 as i32)) as i64 as u64,
)
}
(false, true) => {
// 64-bit unsigned
(dividend / divisor, dividend % divisor)
}
(true, true) => {
// 64-bit signed
(
((dividend as i64) / (divisor as i64)) as u64,
((dividend as i64) % (divisor as i64)) as u64,
)
let (quotient, remainder, overflow) = if extword.signed() {
let a = if extword.size() {
dividend as i64
} else {
i64::from(dividend as u32 as i32)
};
let b = i64::from(divisor as u32 as i32);
// None for i64::MIN / -1, which overflows too
match a.checked_div(b) {
Some(q) => (q as u64, (a % b) as u64, i32::try_from(q).is_err()),
None => (0, 0, true),
}
} else {
let q = dividend / divisor;
(q, dividend % divisor, q > u64::from(u32::MAX))
};

// Check overflow conditions on 64-bit divisions if the result exceeds 32-bit
if extword.size() && ![u32::MIN, u32::MAX].contains(&((quotient >> 32) as u32)) {
// The quotient must fit in 32 bits; the operands are left unchanged
if overflow {
debug!("DIV.l overflow");
self.regs.sr.set_v(true);
return Ok(());
Expand Down
99 changes: 99 additions & 0 deletions core/src/cpu_m68k/tests/divl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! 68020 long division (DIVS.L/DIVU.L) tests

use crate::bus::Address;
use crate::bus::testbus::Testbus;
use crate::cpu_m68k::{CpuM68020Fpu, M68020_ADDRESS_MASK};
use crate::types::Long;

type TestCpu = CpuM68020Fpu<Testbus<Address, u8>>;

const SIGNED: u16 = 0x0800;
const SIZE_64: u16 = 0x0400;

/// Runs DIVx.L D2,D1:D0 (or DIVx.L D2,D0 without `SIZE_64`) and returns the CPU
fn divl(flags: u16, d1: Long, d0: Long, d2: Long) -> TestCpu {
const PC: Address = 0x1000;
let ext = flags | if flags & SIZE_64 != 0 { 1 } else { 0 };
let bus = Testbus::new(M68020_ADDRESS_MASK);
let mut cpu = TestCpu::new(bus);

for (i, word) in [0x4C42, ext, 0x4E71, 0x4E71].into_iter().enumerate() {
let addr = PC + (i as Address * 2);
cpu.bus.mem.insert(addr, (word >> 8) as u8);
cpu.bus.mem.insert(addr + 1, word as u8);
}

cpu.regs.isp = 0x800;
cpu.regs.sr.set_supervisor(true);
cpu.regs.sr.set_int_prio_mask(7);
cpu.set_pc(PC).expect("set_pc failed");
cpu.prefetch_refill().expect("prefetch_refill failed");

cpu.regs.write_d(0, d0);
cpu.regs.write_d(1, d1);
cpu.regs.write_d(2, d2);
cpu.step().unwrap();
cpu
}

fn regs(cpu: &TestCpu) -> (Long, Long) {
(cpu.regs.read_d(1), cpu.regs.read_d(0))
}

#[test]
fn divs_l_32_min_by_minus_one_overflows() {
let cpu = divl(SIGNED, 0x1234_5678, 0x8000_0000, 0xFFFF_FFFF);
assert!(cpu.regs.sr.v());
assert_eq!(regs(&cpu), (0x1234_5678, 0x8000_0000));
}

#[test]
fn divs_l_32() {
let cpu = divl(SIGNED, 0, -7i32 as Long, 2);
assert!(!cpu.regs.sr.v());
assert!(cpu.regs.sr.n());
assert_eq!(cpu.regs.read_d::<Long>(0), -3i32 as Long);
}

#[test]
fn divs_l_64_negative_divisor() {
// 0x1_0000_0000 / -2 = -0x8000_0000
let cpu = divl(SIGNED | SIZE_64, 1, 0, -2i32 as Long);
assert!(!cpu.regs.sr.v());
assert!(cpu.regs.sr.n());
assert_eq!(regs(&cpu), (0, 0x8000_0000));

// -7 / -2 = 3, remainder -1
let cpu = divl(SIGNED | SIZE_64, 0xFFFF_FFFF, -7i32 as Long, -2i32 as Long);
assert!(!cpu.regs.sr.v());
assert_eq!(regs(&cpu), (0xFFFF_FFFF, 3));
}

#[test]
fn divs_l_64_overflow() {
// 0x1_0000_0000 / 2 = 0x8000_0000 doesn't fit a signed long
let cpu = divl(SIGNED | SIZE_64, 1, 0, 2);
assert!(cpu.regs.sr.v());
assert_eq!(regs(&cpu), (1, 0));

// i64::MIN / -1
let cpu = divl(SIGNED | SIZE_64, 0x8000_0000, 0, 0xFFFF_FFFF);
assert!(cpu.regs.sr.v());
assert_eq!(regs(&cpu), (0x8000_0000, 0));
}

#[test]
fn divu_l_64() {
// 0x1_0000_0000 / 2 = 0x8000_0000
let cpu = divl(SIZE_64, 1, 0, 2);
assert!(!cpu.regs.sr.v());
assert_eq!(regs(&cpu), (0, 0x8000_0000));
}

#[test]
fn divu_l_64_overflow() {
// 0xFFFF_FFFF_0000_0000 / 1: the quotient's high long is all ones
let cpu = divl(SIZE_64, 0xFFFF_FFFF, 0, 1);
assert!(cpu.regs.sr.v());
assert_eq!(regs(&cpu), (0xFFFF_FFFF, 0));
}
1 change: 1 addition & 0 deletions core/src/cpu_m68k/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod divl;
mod group2_exceptions;
mod illegal_exception;
mod interrupt;
Expand Down
Loading