use super::cpu::CpuCore;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum OepClass {
PoepSoep = 0,
PoepOnly = 1,
PoepUntilLast = 2,
PoepButAllowsSoep = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Info060(pub u16);
const CLASS_SHIFT: u16 = 0;
const CYCLES_SHIFT: u16 = 2;
const CYCLES_MASK: u16 = 0x1F;
pub const F_BRANCH: u16 = 1 << 7;
pub const F_DBCC: u16 = 1 << 8;
pub const F_VARIABLE: u16 = 1 << 9;
pub const F_DEFINES_CCR: u16 = 1 << 10;
pub const F_USES_CCR_LATE: u16 = 1 << 11;
pub const F_EA_INDEXED: u16 = 1 << 12;
pub const F_UNCLASSIFIED: u16 = 1 << 13;
pub const F_READS_MEM: u16 = 1 << 14;
pub const F_WRITES_MEM: u16 = 1 << 15;
impl Info060 {
const fn new(class: OepClass, cycles: u16, flags: u16) -> Self {
Self((class as u16) << CLASS_SHIFT | (cycles & CYCLES_MASK) << CYCLES_SHIFT | flags)
}
pub fn class(self) -> OepClass {
match (self.0 >> CLASS_SHIFT) & 3 {
0 => OepClass::PoepSoep,
1 => OepClass::PoepOnly,
2 => OepClass::PoepUntilLast,
_ => OepClass::PoepButAllowsSoep,
}
}
pub fn cycles(self) -> i32 {
i32::from((self.0 >> CYCLES_SHIFT) & CYCLES_MASK)
}
pub fn has(self, flag: u16) -> bool {
self.0 & flag != 0
}
}
pub const CYC_060_BRANCH_TAKEN: i32 = 7;
pub const CYC_060_BRANCH_NOT_TAKEN: i32 = 1;
pub const CYC_060_DBCC_TAKEN: i32 = 2;
pub const CYC_060_DBCC_EXPIRED: i32 = 3;
pub const CYC_060_FLOW_MIN: i32 = 5;
pub const CYC_060_MISPREDICT: i32 = 7;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BranchCache060 {
tags: Vec<u32>,
state: Vec<u8>,
}
const BC_VALID: u8 = 1 << 2;
const BC_USER: u8 = 1 << 3;
const BC_COUNTER: u8 = 0x03;
impl Default for BranchCache060 {
fn default() -> Self {
Self {
tags: vec![0; 256],
state: vec![0; 256],
}
}
}
impl BranchCache060 {
#[inline]
fn slot(pc: u32) -> usize {
((pc >> 1) & 0xFF) as usize
}
pub fn clear_all(&mut self) {
self.state.iter_mut().for_each(|s| *s = 0);
}
pub fn clear_user(&mut self) {
for s in self.state.iter_mut() {
if *s & BC_USER != 0 {
*s = 0;
}
}
}
fn predict(&self, pc: u32) -> Option<bool> {
let i = Self::slot(pc);
if self.state[i] & BC_VALID != 0 && self.tags[i] == pc {
Some(self.state[i] & BC_COUNTER >= 2)
} else {
None
}
}
fn update(&mut self, pc: u32, taken: bool, user: bool) {
let i = Self::slot(pc);
let hit = self.state[i] & BC_VALID != 0 && self.tags[i] == pc;
if hit {
let mut counter = self.state[i] & BC_COUNTER;
counter = if taken {
(counter + 1).min(3)
} else {
counter.saturating_sub(1)
};
self.state[i] = (self.state[i] & !BC_COUNTER) | counter;
} else if taken {
self.tags[i] = pc;
self.state[i] = BC_VALID | if user { BC_USER } else { 0 } | 2;
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PendingHead060 {
pub def_mask: u16,
pub defines_ccr: bool,
pub accessed_mem: bool,
pub head_cached: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Oep060Timing {
pub branch_cache: BranchCache060,
pub pending_head: Option<PendingHead060>,
}
#[inline]
fn fallback_cycles(raw: i32) -> i32 {
(raw / 4).max(1)
}
const fn ea_flags(mode: u16, reg: u16, read: bool, write: bool) -> u16 {
let mut flags = 0;
let is_mem = mode >= 2 && !(mode == 7 && reg == 4); if is_mem {
if read {
flags |= F_READS_MEM;
}
if write {
flags |= F_WRITES_MEM;
}
}
if mode == 6 || (mode == 7 && reg == 3) {
flags |= F_EA_INDEXED;
}
flags
}
const fn alu(mode: u16, reg: u16, read: bool, write: bool) -> Info060 {
Info060::new(
OepClass::PoepSoep,
1,
F_DEFINES_CCR | ea_flags(mode, reg, read, write),
)
}
pub fn classify_060_opcode(op: u16) -> Info060 {
let group = op >> 12;
let mode = (op >> 3) & 7;
let reg = op & 7;
let unclassified = Info060::new(OepClass::PoepOnly, 1, F_UNCLASSIFIED | F_VARIABLE);
match group {
0x0 => {
if op & 0x0100 != 0 || (op & 0x0F00) == 0x0800 {
Info060::new(
OepClass::PoepOnly,
1,
F_DEFINES_CCR | ea_flags(mode, reg, true, op & 0x00C0 != 0),
)
} else if (op & 0x0FC0) == 0x00C0 || (op & 0x09C0) == 0x08C0 {
unclassified
} else {
if mode == 7 && reg == 4 {
Info060::new(OepClass::PoepOnly, 1, F_VARIABLE) } else {
alu(mode, reg, true, (op & 0x0F00) != 0x0C00) }
}
}
0x1..=0x3 => {
let dst_mode = (op >> 6) & 7;
let dst_reg = (op >> 9) & 7;
Info060::new(
OepClass::PoepSoep,
1,
F_DEFINES_CCR
| ea_flags(mode, reg, true, false)
| ea_flags(dst_mode, dst_reg, false, true),
)
}
0x4 => {
match op & 0x0FC0 {
0x00C0 | 0x02C0 => Info060::new(OepClass::PoepOnly, 1, F_VARIABLE),
0x04C0 | 0x06C0 => Info060::new(OepClass::PoepOnly, 1, F_VARIABLE),
_ => {
if (op & 0x0B80) == 0x0880 && mode != 0 {
Info060::new(
OepClass::PoepUntilLast,
2,
F_VARIABLE | F_READS_MEM | F_WRITES_MEM,
)
} else if (op & 0x0FC0) == 0x0AC0 {
Info060::new(
OepClass::PoepOnly,
2,
F_DEFINES_CCR | F_READS_MEM | F_WRITES_MEM,
)
} else if (op & 0x0F80) == 0x0C00 {
Info060::new(OepClass::PoepOnly, 2, F_DEFINES_CCR | F_VARIABLE)
} else if (op & 0x0FF8) == 0x0840 {
alu(0, 0, false, false)
} else if (op & 0x0E00) == 0x0800 && (op & 0x00C0) != 0x0040 {
alu(mode, reg, true, true)
} else if (op & 0x0F00) == 0x0200
|| (op & 0x0F00) == 0x0000
|| (op & 0x0F00) == 0x0400
|| (op & 0x0F00) == 0x0600
{
alu(mode, reg, true, true)
} else if (op & 0x01C0) == 0x01C0 {
Info060::new(OepClass::PoepSoep, 1, ea_flags(mode, reg, false, false))
} else if (op & 0x01C0) == 0x0180 {
Info060::new(OepClass::PoepOnly, 2, F_VARIABLE)
} else {
if (op & 0x0F00) == 0x0A00 {
alu(mode, reg, true, false)
} else {
Info060::new(OepClass::PoepOnly, 1, F_VARIABLE)
}
}
}
}
}
0x5 => {
if (op & 0x00F8) == 0x00C8 {
Info060::new(
OepClass::PoepOnly,
CYC_060_DBCC_EXPIRED as u16,
F_BRANCH | F_DBCC | F_USES_CCR_LATE,
)
} else if (op & 0x00C0) == 0x00C0 {
Info060::new(
OepClass::PoepSoep,
1,
F_USES_CCR_LATE | ea_flags(mode, reg, false, true),
)
} else {
alu(mode, reg, true, true)
}
}
0x6 => {
Info060::new(
OepClass::PoepOnly,
CYC_060_BRANCH_TAKEN as u16,
F_BRANCH
| if (op & 0x0F00) != 0 {
F_USES_CCR_LATE
} else {
0
},
)
}
0x7 => {
Info060::new(OepClass::PoepSoep, 1, F_DEFINES_CCR)
}
0x8 => {
if (op & 0x00C0) == 0x00C0 {
Info060::new(OepClass::PoepOnly, 2, F_DEFINES_CCR | F_VARIABLE)
} else if (op & 0x01F0) == 0x0100 || (op & 0x01F0) == 0x0140 {
Info060::new(OepClass::PoepOnly, 2, F_DEFINES_CCR | F_VARIABLE)
} else {
alu(mode, reg, true, (op & 0x0100) != 0)
}
}
0x9 | 0xD => {
if (op & 0x0130) == 0x0100 && mode <= 1 {
Info060::new(
OepClass::PoepOnly,
1,
F_DEFINES_CCR | F_USES_CCR_LATE | ea_flags(mode, reg, true, true),
)
} else {
alu(
mode,
reg,
true,
(op & 0x0100) != 0 && (op & 0x00C0) != 0x00C0,
)
}
}
0xB => {
alu(
mode,
reg,
true,
(op & 0x0100) != 0 && (op & 0x00C0) != 0x00C0,
)
}
0xC => {
if (op & 0x00C0) == 0x00C0 {
Info060::new(OepClass::PoepOnly, 2, F_DEFINES_CCR)
} else if (op & 0x01F0) == 0x0100 {
Info060::new(OepClass::PoepOnly, 2, F_DEFINES_CCR | F_VARIABLE)
} else if (op & 0x01F8) == 0x0140 || (op & 0x01F8) == 0x0148 || (op & 0x01F8) == 0x0188
{
Info060::new(OepClass::PoepOnly, 1, 0)
} else {
alu(mode, reg, true, (op & 0x0100) != 0)
}
}
0xE => {
if (op & 0x08C0) == 0x08C0 {
unclassified
} else if (op & 0x00C0) == 0x00C0 {
Info060::new(
OepClass::PoepSoep,
1,
F_DEFINES_CCR | F_READS_MEM | F_WRITES_MEM,
)
} else if (op & 0x0018) == 0x0010 {
Info060::new(OepClass::PoepOnly, 1, F_DEFINES_CCR | F_USES_CCR_LATE)
} else {
Info060::new(OepClass::PoepSoep, 1, F_DEFINES_CCR)
}
}
_ => unclassified,
}
}
pub fn reg_masks_060(op: u16) -> (u16, u16) {
let mode = (op >> 3) & 7;
let reg = op & 7;
let full = (0xFFFFu16, 0xFFFFu16);
fn ea_masks(mode: u16, reg: u16) -> (u16, u16) {
match mode {
0 => (1 << reg, 0), 1 => (1 << (8 + reg), 0), 2 | 5 => (1 << (8 + reg), 0), 3 | 4 => (1 << (8 + reg), 1 << (8 + reg)),
6 => (0xFFFF, 0),
_ => (0, 0), }
}
match op >> 12 {
0x1..=0x3 => {
let (su, sd) = ea_masks(mode, reg);
let dst_mode = (op >> 6) & 7;
let dst_reg = (op >> 9) & 7;
let (du, dd) = ea_masks(dst_mode, dst_reg);
let extra_def = match dst_mode {
0 => 1 << dst_reg,
1 => 1 << (8 + dst_reg),
_ => 0,
};
(su | du, sd | dd | extra_def)
}
0x7 => (0, 1 << ((op >> 9) & 7)),
0x8 | 0x9 | 0xB | 0xC | 0xD => {
let opmode = (op >> 6) & 7;
let dn = (op >> 9) & 7;
let (eu, ed) = ea_masks(mode, reg);
match opmode {
0..=2 => (eu | (1 << dn), ed | (1 << dn)),
3 | 7 => (eu | (1 << (8 + dn)), ed | (1 << (8 + dn))),
_ => ((1 << dn) | eu, ed),
}
}
0x5 => {
let (eu, ed) = ea_masks(mode, reg);
let operand_def = match mode {
0 => 1 << reg,
1 => 1 << (8 + reg),
_ => 0,
};
(eu, ed | operand_def)
}
0xE if (op & 0x00C0) != 0x00C0 => {
let dn = op & 7;
let count_use = if (op & 0x0020) != 0 {
1 << ((op >> 9) & 7)
} else {
0
};
((1 << dn) | count_use, 1 << dn)
}
0x4 => {
if (op & 0x01C0) == 0x01C0 {
let an = (op >> 9) & 7;
let (eu, _) = ea_masks(mode, reg);
(eu, 1 << (8 + an))
} else if (op & 0x0F00) < 0x0700 || (op & 0x0F00) == 0x0A00 {
let (eu, ed) = ea_masks(mode, reg);
let operand_def = if mode == 0 { 1 << reg } else { 0 };
(eu, ed | operand_def)
} else {
full
}
}
_ => full,
}
}
fn info_table() -> &'static [u16; 0x10000] {
static TABLE: OnceLock<Box<[u16; 0x10000]>> = OnceLock::new();
TABLE.get_or_init(|| {
let mut table = vec![0u16; 0x10000];
for (op, slot) in table.iter_mut().enumerate() {
*slot = classify_060_opcode(op as u16).0;
}
table.into_boxed_slice().try_into().unwrap()
})
}
#[inline]
pub fn info_060(op: u16) -> Info060 {
Info060(info_table()[op as usize])
}
impl CpuCore {
pub(crate) fn cycles_060(&mut self, raw: i32, fetch_cached: bool) -> i32 {
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
};
let fold_target_open = self.oep060.pending_head.is_some();
self.oep060.pending_head = None;
return self.branch_cost_060(taken, info.has(F_DBCC), fetch_cached, fold_target_open);
}
if flowed {
self.oep060.pending_head = None;
return fallback_cycles(raw).max(CYC_060_FLOW_MIN);
}
let mut cycles = if info.has(F_VARIABLE) || info.has(F_UNCLASSIFIED) {
fallback_cycles(raw)
} else {
info.cycles()
};
if info.has(F_EA_INDEXED) {
cycles += 1;
}
let (use_mask, def_mask) = reg_masks_060(self.ir as u16);
if let Some(head) = self.oep060.pending_head
&& self.soep_dispatch_test(&head, info, use_mask, def_mask, fetch_cached)
{
self.oep060.pending_head = None;
return (cycles - 1).max(0);
}
self.oep060.pending_head = match info.class() {
OepClass::PoepSoep | OepClass::PoepButAllowsSoep | OepClass::PoepUntilLast => {
Some(PendingHead060 {
def_mask,
defines_ccr: info.has(F_DEFINES_CCR),
accessed_mem: info.has(F_READS_MEM) || info.has(F_WRITES_MEM),
head_cached: fetch_cached,
})
}
OepClass::PoepOnly => None,
};
cycles
}
fn soep_dispatch_test(
&self,
head: &PendingHead060,
info: Info060,
use_mask: u16,
def_mask: u16,
fetch_cached: bool,
) -> bool {
if self.pcr & super::cpu::PCR_ESS == 0 {
return false;
}
if !(head.head_cached && fetch_cached) {
return false;
}
if info.class() != OepClass::PoepSoep
|| info.has(F_EA_INDEXED)
|| info.has(F_UNCLASSIFIED)
|| info.has(F_VARIABLE)
{
return false;
}
if (use_mask | def_mask) & head.def_mask != 0 {
return false;
}
if info.has(F_USES_CCR_LATE) && head.defines_ccr {
return false;
}
if head.accessed_mem && (info.has(F_READS_MEM) || info.has(F_WRITES_MEM)) {
return false;
}
true
}
#[inline]
pub(crate) fn break_060_pipeline(&mut self) {
self.oep060.pending_head = None;
}
fn branch_cost_060(
&mut self,
taken: bool,
is_dbcc: bool,
fetch_cached: bool,
fold_target_open: bool,
) -> i32 {
if self.cacr & super::cpu::CACR_060_EBC == 0 || !fetch_cached {
return match (is_dbcc, taken) {
(true, true) => CYC_060_DBCC_TAKEN,
(true, false) => CYC_060_DBCC_EXPIRED,
(false, true) => CYC_060_BRANCH_TAKEN,
(false, false) => CYC_060_BRANCH_NOT_TAKEN,
};
}
let user = !self.is_supervisor();
let predicted_taken = self.oep060.branch_cache.predict(self.ppc).unwrap_or(false);
let cost = match (predicted_taken, taken) {
(true, true) => i32::from(!fold_target_open),
(false, false) => 1,
_ => CYC_060_MISPREDICT,
};
self.oep060.branch_cache.update(self.ppc, taken, user);
cost
}
#[inline]
pub(crate) fn finalize_cycles(&mut self, raw: i32, fetch_cached: bool) -> i32 {
match self.cpu_type {
super::types::CpuType::M68EC020 | super::types::CpuType::M68020 => {
self.cycles_020(raw, fetch_cached)
}
super::types::CpuType::M68060 => self.cycles_060(raw, fetch_cached),
_ => self.scale_cycles_for_cpu_type(raw),
}
}
}