use super::cpu::CpuCore;
use super::timing_060::{F_BRANCH, F_DBCC, F_EA_INDEXED, F_UNCLASSIFIED, F_VARIABLE, info_060};
pub const CYC_040_BRANCH_TAKEN: i32 = 3;
pub const CYC_040_BRANCH_NOT_TAKEN: i32 = 2;
pub const CYC_040_DBCC_TAKEN: i32 = 3;
pub const CYC_040_DBCC_EXPIRED: i32 = 3;
pub const CYC_040_FLOW_MIN: i32 = 4;
pub const CYC_040_CACHE_OP: i32 = 12;
#[inline]
fn fallback_cycles(raw: i32) -> i32 {
(raw / 4).max(1)
}
impl CpuCore {
pub(crate) fn cycles_040(&mut self, raw: i32) -> i32 {
if self.instruction_exception_vector.is_some() {
return self.scale_cycles_for_cpu_type(raw);
}
let info = info_060(self.ir as u16);
let flowed = self.change_of_flow;
if info.has(F_BRANCH) {
let taken = if info.has(F_DBCC) {
self.pc != self.ppc.wrapping_add(4)
} else {
flowed
};
return match (info.has(F_DBCC), taken) {
(true, true) => CYC_040_DBCC_TAKEN,
(true, false) => CYC_040_DBCC_EXPIRED,
(false, true) => CYC_040_BRANCH_TAKEN,
(false, false) => CYC_040_BRANCH_NOT_TAKEN,
};
}
if flowed {
return fallback_cycles(raw).max(CYC_040_FLOW_MIN);
}
let op = self.ir as u16;
if op & 0xFF00 == 0xF400 {
return fallback_cycles(raw).max(CYC_040_CACHE_OP);
}
let is_muldiv = matches!(op & 0xF1C0, 0xC0C0 | 0xC1C0 | 0x80C0 | 0x81C0)
|| matches!(op & 0xFFC0, 0x4C00 | 0x4C40);
let mut cycles = if is_muldiv || info.has(F_VARIABLE) || info.has(F_UNCLASSIFIED) {
fallback_cycles(raw)
} else {
info.cycles()
};
if info.has(F_EA_INDEXED) {
cycles += 1;
}
cycles
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::types::CpuType;
fn cpu_040() -> CpuCore {
let mut cpu = CpuCore::new();
cpu.set_cpu_type(CpuType::M68040);
cpu
}
#[test]
fn simple_alu_ops_cost_one_clock() {
let mut cpu = cpu_040();
cpu.ir = 0x7001; assert_eq!(cpu.cycles_040(4), 1);
cpu.ir = 0x3002; assert_eq!(cpu.cycles_040(4), 1);
cpu.ir = 0xD280; assert_eq!(cpu.cycles_040(4), 1);
}
#[test]
fn taken_dbcc_makes_the_measured_one_clock_body_loop_four_clocks() {
let mut cpu = cpu_040();
cpu.ir = 0x51CE; cpu.ppc = 0x1000;
cpu.pc = 0x0FFC; assert_eq!(cpu.cycles_040(12), CYC_040_DBCC_TAKEN);
cpu.pc = cpu.ppc.wrapping_add(4); assert_eq!(cpu.cycles_040(14), CYC_040_DBCC_EXPIRED);
}
#[test]
fn data_dependent_costs_derive_from_the_68000_reference() {
let mut cpu = cpu_040();
cpu.ir = 0xC0FC; assert_eq!(cpu.cycles_040(54), 13);
}
#[test]
fn cache_line_maintenance_costs_a_dozen_clocks() {
let mut cpu = cpu_040();
cpu.ir = 0xF468; assert_eq!(cpu.cycles_040(8), CYC_040_CACHE_OP);
}
#[test]
fn flow_changes_pay_the_refill_floor() {
let mut cpu = cpu_040();
cpu.ir = 0x4E75; cpu.change_of_flow = true;
assert_eq!(cpu.cycles_040(16), CYC_040_FLOW_MIN);
}
}