Skip to content
Merged
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ against real hardware.
real speed.
- **Configurable CPU** (68000 / 68010 / 68EC020 / 68020 / 68030 / 68040 / 68060) and clock,
with an optional 68881/68882 FPU (default-on for the 68040/68060) and the
68030/68040 MMUs.
68030/68040 MMUs. The 68020 integer timing model selects the cache or
uncached execution totals from the MC68020 User's Manual per instruction.
- **Peripherals**: a bit-timed keyboard (6500/1 MCU), mouse, USB gamepad
(via the pure-Rust `gilrs`, no SDL2), 4-channel Paula audio, floppy
(ADF / ADZ / ZIP / DMS, read-only SCP), Gayle and A4000 IDE, SCSI (A2091,
Expand Down Expand Up @@ -318,7 +319,7 @@ release needs resolved first. Release steps for every channel are in

| Subsystem | Notes |
| --- | --- |
| M68K CPU | Via a vendored pure-Rust m68k crate; model selectable through 68060, accurate 68000 cycle counts, 020+ caches, 6888x FPU, 68030/68040 MMUs. |
| M68K CPU | Via a vendored pure-Rust m68k crate; model selectable through 68060, accurate 68000 cycle counts, datasheet-based 68020 integer timing, 020+ caches, 6888x FPU, 68030/68040 MMUs. |
| Chip RAM | mem_map'd; reset starts with ROM overlaid at $0 until CIA-A releases /OVL. |
| Fast RAM | Optional Zorro II autoconfig RAM at $00200000 and Zorro III autoconfig RAM (`[memory] z3`); runs at the CPU clock. |
| Slow RAM | Optional A500 trapdoor/fake-fast RAM at $00C00000; arbitrated on the chip bus through Agnus like chip RAM. |
Expand Down
21 changes: 9 additions & 12 deletions crates/m68k/src/core/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ pub struct CpuCore {
pub run_mode: u32,
/// True while processing an exception (for double-fault detection)
pub exception_processing: bool,
/// Vector taken by the instruction currently being timed. Unlike the
/// debugger field below, this is cleared before every opcode fetch.
#[serde(skip)]
pub(crate) instruction_exception_vector: Option<u32>,
/// Vector number of the most recent exception entry (trap, fault, or
/// interrupt -- everything routed through `jump_vector`), for the
/// host debugger's exception catchpoints. Polled and cleared by the
Expand Down Expand Up @@ -388,18 +392,10 @@ impl Default for CpuCore {
}

impl CpuCore {
/// Approximate 68020/030/040 instruction timing from the 68000 cycle
/// counts the instruction handlers produce: the 020's three-stage
/// pipeline and instruction cache make most instructions cost roughly
/// half their 68000 cycles (memory-bound work is additionally dominated
/// by the host bus model), with a two-cycle floor. Calibrated against a
/// cycle-exact A1200 reference (FS-UAE) using the Copperline
/// timing-test ADF: 68000 MULU.W #imm with 8 set bits = 54 cycles
/// scales to the measured 27, and the chip-RAM dbra loop lands on the
/// measured 8 clocks per iteration. The 68000/68010 paths (validated
/// against SingleStepTests) are untouched; see the "68020+ timing"
/// section of docs/internals/cpu.md for why no per-instruction 020
/// reference exists.
/// Legacy 68030/040 approximation from the handlers' corrected 68000
/// counts. The 68020/68EC020 is routed through its MC68020UM section-8
/// timing model before this function; the 68060 has a separate pipeline
/// engine. The 68000/68010 paths remain untouched.
#[inline]
pub(crate) fn scale_cycles_for_cpu_type(&self, cycles: i32) -> i32 {
use crate::core::types::CpuType;
Expand Down Expand Up @@ -459,6 +455,7 @@ impl CpuCore {
instr_mode: 0,
run_mode: 0,
exception_processing: false,
instruction_exception_vector: None,
last_exception_vector: None,
has_pmmu: false,
pmmu_enabled: false,
Expand Down
37 changes: 34 additions & 3 deletions crates/m68k/src/core/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ impl CpuCore {

// Main execution loop
while self.cycles_remaining > 0 {
self.instruction_exception_vector = None;
bus.begin_instruction_fetches();
// Save previous PC
self.ppc = self.pc;

Expand All @@ -79,8 +81,16 @@ impl CpuCore {

// Dispatch instruction (sampling whether the opcode fetch
// hit the icache before dispatch consumes more of the stream).
let fetch_cached = bus.last_fetch_was_cached();
let opcode_fetch_cached = bus.last_fetch_was_cached();
let result = dispatch_instruction(self, bus, self.ir as u16);
let fetch_cached = if matches!(
self.cpu_type,
super::types::CpuType::M68EC020 | super::types::CpuType::M68020
) {
bus.instruction_fetches_were_cached()
} else {
opcode_fetch_cached
};

// Auto-take all trap exceptions, extract cycles
use crate::core::types::InternalStepResult;
Expand Down Expand Up @@ -153,6 +163,8 @@ impl CpuCore {
return StepResult::Stopped;
}

self.instruction_exception_vector = None;
bus.begin_instruction_fetches();
self.ppc = self.pc;
self.dar_save = self.dar;
self.sr_save = self.get_sr();
Expand All @@ -163,8 +175,16 @@ impl CpuCore {
return StepResult::Ok { cycles: 0 };
}

let fetch_cached = bus.last_fetch_was_cached();
let opcode_fetch_cached = bus.last_fetch_was_cached();
let result = dispatch_instruction(self, bus, self.ir as u16);
let fetch_cached = if matches!(
self.cpu_type,
super::types::CpuType::M68EC020 | super::types::CpuType::M68020
) {
bus.instruction_fetches_were_cached()
} else {
opcode_fetch_cached
};

let res = match result {
InternalStepResult::Ok { cycles } => StepResult::Ok {
Expand Down Expand Up @@ -249,6 +269,8 @@ impl CpuCore {
return StepResult::Stopped;
}

self.instruction_exception_vector = None;
bus.begin_instruction_fetches();
self.ppc = self.pc;
self.dar_save = self.dar;
self.sr_save = self.get_sr();
Expand All @@ -259,8 +281,16 @@ impl CpuCore {
return StepResult::Ok { cycles: 0 };
}

let fetch_cached = bus.last_fetch_was_cached();
let opcode_fetch_cached = bus.last_fetch_was_cached();
let result = dispatch_instruction(self, bus, self.ir as u16);
let fetch_cached = if matches!(
self.cpu_type,
super::types::CpuType::M68EC020 | super::types::CpuType::M68020
) {
bus.instruction_fetches_were_cached()
} else {
opcode_fetch_cached
};

// Handle trap results via callbacks, fallback to exception if not handled
let cycles = match result {
Expand Down Expand Up @@ -385,6 +415,7 @@ impl CpuCore {
// restores normal instruction fetching.
self.loop_mode = false;
self.last_exception_vector = Some(vector);
self.instruction_exception_vector = Some(vector);
let addr = (vector << 2).wrapping_add(self.vbr);
self.pc = self.read_32(bus, addr);
// Exception entry refills the prefetch queue from the handler
Expand Down
14 changes: 14 additions & 0 deletions crates/m68k/src/core/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ pub trait AddressBus {
true
}

/// Start tracking cache residency for one complete instruction. The
/// MC68020 timing tables define their cache case for an instruction that
/// is in the cache, including extension and immediate words rather than
/// only the opcode word. Hosts with an instruction-cache model use this
/// hook to reset their per-instruction hit accumulator.
fn begin_instruction_fetches(&mut self) {}

/// Whether every instruction-stream access since
/// `begin_instruction_fetches` hit the instruction cache. Functional test
/// buses without a cache model default to the most recent fetch result.
fn instruction_fetches_were_cached(&self) -> bool {
self.last_fetch_was_cached()
}

fn interrupt_acknowledge(&mut self, _level: u8) -> u32 {
0xFFFF_FFFF
}
Expand Down
1 change: 1 addition & 0 deletions crates/m68k/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ pub mod memory;
pub mod registers;
pub mod status;
pub mod timing;
pub mod timing_020;
pub mod timing_060;
pub mod types;
Loading
Loading