diff --git a/src/cpus/m68k/execute.rs b/src/cpus/m68k/execute.rs index 15babf7..5b15ae3 100644 --- a/src/cpus/m68k/execute.rs +++ b/src/cpus/m68k/execute.rs @@ -1,7 +1,7 @@ use crate::system::System; use crate::error::{ErrorType, Error}; -use crate::devices::{Clock, Address, Steppable, Interruptable, Addressable, Transmutable}; +use crate::devices::{ClockElapsed, Address, Steppable, Interruptable, Addressable, Transmutable}; use super::instructions::{ Register, @@ -27,9 +27,9 @@ use super::state::{M68k, M68kType, Status, Flags, Exceptions, InterruptPriority} impl Steppable for M68k { - fn step(&mut self, system: &System) -> Result { + fn step(&mut self, system: &System) -> Result { self.step_internal(system)?; - Ok(1) + Ok(1_000_000_000 / 8_000_000) } fn on_error(&mut self, system: &System) { @@ -86,7 +86,7 @@ impl M68k { //Err(Error { err: ErrorType::Processor, native, .. }) => { // TODO temporary: we are passing illegal instructions upward in order to fix them Err(Error { err: ErrorType::Processor, native, .. }) if native != Exceptions::IllegalInstruction as u32 => { - self.exception(system, native as u8)?; + self.exception(system, native as u8, false)?; Ok(()) }, Err(err) => Err(err), @@ -126,7 +126,7 @@ impl M68k { if (pending_ipl >= priority_mask || pending_ipl == 7) && pending_ipl >= current_ipl { debug!("{} interrupt: {} {}", DEV_NAME, pending_ipl, priority_mask); self.state.current_ipl = self.state.pending_ipl; - self.exception(system, self.state.ipl_ack_num)?; + self.exception(system, self.state.ipl_ack_num, true)?; return Ok(()); } } @@ -138,7 +138,7 @@ impl M68k { Ok(()) } - pub fn exception(&mut self, system: &System, number: u8) -> Result<(), Error> { + pub fn exception(&mut self, system: &System, number: u8, update_ipl: bool) -> Result<(), Error> { debug!("{}: raising exception {}", DEV_NAME, number); let offset = (number as u16) << 2; self.push_word(system, offset)?; @@ -146,6 +146,9 @@ impl M68k { self.push_word(system, self.state.sr)?; self.set_flag(Flags::Supervisor, true); self.set_flag(Flags::Tracing, false); + if update_ipl { + self.state.sr = (self.state.sr & !(Flags::IntMask as u16)) | ((self.state.current_ipl as u16) << 8); + } self.state.pc = system.get_bus().read_beu32((self.state.vbr + offset as u32) as Address)?; Ok(()) } @@ -343,7 +346,7 @@ impl M68k { Instruction::DIVW(src, dest, sign) => { let value = self.get_target_value(system, src, Size::Word)?; if value == 0 { - self.exception(system, Exceptions::ZeroDivide as u8)?; + self.exception(system, Exceptions::ZeroDivide as u8, false)?; return Ok(()); } @@ -362,7 +365,7 @@ impl M68k { Instruction::DIVL(src, dest_h, dest_l, sign) => { let value = self.get_target_value(system, src, Size::Long)?; if value == 0 { - self.exception(system, Exceptions::ZeroDivide as u8)?; + self.exception(system, Exceptions::ZeroDivide as u8, false)?; return Ok(()); } @@ -641,11 +644,11 @@ impl M68k { self.set_logic_flags(value, size); }, Instruction::TRAP(number) => { - self.exception(system, 32 + number)?; + self.exception(system, 32 + number, false)?; }, Instruction::TRAPV => { if self.get_flag(Flags::Overflow) { - self.exception(system, Exceptions::TrapvInstruction as u8)?; + self.exception(system, Exceptions::TrapvInstruction as u8, false)?; } }, Instruction::UNLK(reg) => { diff --git a/src/devices.rs b/src/devices.rs index 74defd5..136dc63 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -8,7 +8,13 @@ use crate::system::System; pub const MAX_READ: usize = 4; +/// The time in nanoseconds that have elapsed since the start of the simulation pub type Clock = u64; + +/// The time in nanoseconds until the `step()` method should be called again +pub type ClockElapsed = u64; + +/// A universal memory address used by the Addressable trait pub type Address = u64; @@ -17,7 +23,7 @@ pub type Address = u64; /// with any device, the `on_error()` method will be called to display any state /// information that might be helpful for debugging. pub trait Steppable { - fn step(&mut self, system: &System) -> Result; + fn step(&mut self, system: &System) -> Result; fn on_error(&mut self, _system: &System) { } fn on_debug(&mut self) { } } diff --git a/src/peripherals/mc68681.rs b/src/peripherals/mc68681.rs index c50a9be..016d4f1 100644 --- a/src/peripherals/mc68681.rs +++ b/src/peripherals/mc68681.rs @@ -1,7 +1,7 @@ use crate::error::Error; use crate::system::System; -use crate::devices::{Clock, Address, Steppable, Addressable, Transmutable, MAX_READ}; +use crate::devices::{ClockElapsed, Address, Steppable, Addressable, Transmutable, MAX_READ}; use crate::host::traits::Tty; @@ -206,7 +206,17 @@ impl MC68681 { } } - pub fn step_internal(&mut self, system: &System) -> Result<(), Error> { + fn set_interrupt_flag(&mut self, flag: u8, value: bool) { + self.int_status = (self.int_status & !flag) | (if value { flag } else { 0 }); + } + + fn check_interrupt_state(&mut self, system: &System) -> Result<(), Error> { + system.get_interrupt_controller().set((self.int_status & self.int_mask) != 0, 4, self.int_vector) + } +} + +impl Steppable for MC68681 { + fn step(&mut self, system: &System) -> Result { if self.port_a.check_rx()? { self.set_interrupt_flag(ISR_CH_A_RX_READY_FULL, true); } @@ -242,15 +252,7 @@ impl MC68681 { self.set_interrupt_flag(ISR_CH_B_TX_READY, true); } - Ok(()) - } - - fn set_interrupt_flag(&mut self, flag: u8, value: bool) { - self.int_status = (self.int_status & !flag) | (if value { flag } else { 0 }); - } - - fn check_interrupt_state(&mut self, system: &System) -> Result<(), Error> { - system.get_interrupt_controller().set((self.int_status & self.int_mask) != 0, 4, self.int_vector) + Ok(1_000_000_000 / 3_646_800) } } @@ -371,13 +373,6 @@ impl Addressable for MC68681 { } } -impl Steppable for MC68681 { - fn step(&mut self, system: &System) -> Result { - self.step_internal(system)?; - Ok(1) - } -} - impl Transmutable for MC68681 { fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { Some(self) diff --git a/src/system.rs b/src/system.rs index b8385f7..7f151fa 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,15 +1,18 @@ +use std::cmp::Ordering; use std::cell::{RefCell, RefMut}; +use std::collections::BinaryHeap; use crate::error::Error; use crate::memory::Bus; use crate::interrupts::InterruptController; -use crate::devices::{Clock, Address, TransmutableBox}; +use crate::devices::{Clock, ClockElapsed, Address, TransmutableBox}; pub struct System { pub clock: Clock, pub devices: Vec, + pub event_queue: BinaryHeap, pub bus: RefCell, pub interrupt_controller: RefCell, } @@ -19,6 +22,7 @@ impl System { System { clock: 0, devices: vec![], + event_queue: BinaryHeap::new(), bus: RefCell::new(Bus::new()), interrupt_controller: RefCell::new(InterruptController::new()), } @@ -32,31 +36,57 @@ impl System { self.interrupt_controller.borrow_mut() } - pub fn add_addressable_device(&mut self, addr: Address, device: TransmutableBox) -> Result<(), Error> { let length = device.borrow_mut().as_addressable().unwrap().len(); self.bus.borrow_mut().insert(addr, length, device.clone()); + self.insert_steppable_device(device.clone()); self.devices.push(device); Ok(()) } pub fn add_interruptable_device(&mut self, device: TransmutableBox) -> Result<(), Error> { self.interrupt_controller.borrow_mut().set_target(device.clone())?; + self.insert_steppable_device(device.clone()); self.devices.push(device); Ok(()) } + fn insert_steppable_device(&mut self, device: TransmutableBox) -> Result<(), Error> { + if device.borrow_mut().as_steppable().is_some() { + let event_device = EventDevice::new(device); + self.event_queue.push(event_device); + } + Ok(()) + } + pub fn step(&mut self) -> Result<(), Error> { - self.clock += 1; - for dev in &self.devices { - match dev.borrow_mut().as_steppable() { - Some(dev) => { dev.step(&self)?; }, - None => { }, + let mut event_device = self.event_queue.pop().unwrap(); + self.clock = event_device.next_clock; + event_device.next_clock = self.clock + event_device.device.borrow_mut().as_steppable().unwrap().step(&self)?; + self.event_queue.push(event_device); + Ok(()) + } + + pub fn run_for(&mut self, clocks: Clock) -> Result<(), Error> { + let target = self.clock + clocks; + + while self.clock < target { + match self.step() { + Ok(()) => { } + Err(err) => { + self.exit_error(); + println!("{:?}", err); + return Err(err); + }, } } Ok(()) } + pub fn run_loop(&mut self) { + self.run_for(u64::MAX); + } + pub fn exit_error(&mut self) { for dev in &self.devices { match dev.borrow_mut().as_steppable() { @@ -75,33 +105,41 @@ impl System { } Ok(()) } +} - pub fn run_loop(&mut self) { - loop { - match self.step() { - Ok(()) => { }, - Err(err) => { - self.exit_error(); - println!("{:?}", err); - break; - }, - } - } - } - pub fn run_for(&mut self, clocks: Clock) -> Result<(), Error> { - let target = self.clock + clocks; - while self.clock < target { - match self.step() { - Ok(()) => { }, - Err(err) => { - self.exit_error(); - println!("{:?}", err); - return Err(err); - }, - } +pub struct EventDevice { + pub next_clock: Clock, + pub device: TransmutableBox, +} + +impl EventDevice { + pub fn new(device: TransmutableBox) -> Self { + Self { + next_clock: 0, + device, } - Ok(()) + } +} + +impl Ord for EventDevice { + fn cmp(&self, other: &Self) -> Ordering { + // NOTE this is reversed so that an event with a lower clock will be higher + other.next_clock.cmp(&self.next_clock) + } +} + +impl PartialOrd for EventDevice { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for EventDevice {} + +impl PartialEq for EventDevice { + fn eq(&self, other: &Self) -> bool { + self.next_clock == other.next_clock } } diff --git a/todo.txt b/todo.txt index 77a382a..be424be 100644 --- a/todo.txt +++ b/todo.txt @@ -1,19 +1,8 @@ +* should you simulate clock ticks, and only step devices when they next request it -* generics for the frontend that's passed to the builder functions is much better than a trait object -* you could possibly use a backend representation that is given to the frontened, rather than vice versa -* you could use callbacks in some way (ie. callbacks from the gui loop to the system) - - -* should the frontend and simulator parts be separated so that the simulator part can be a library package? -* it should be call something other than canvas because it'll have input as well -* how will you get the canvas/app shared object between the sim thread and the io thread? It kind of has to be passed through the system-creation - functions (an only if they need them), which raises questions about how to configure things. It'd be nice if you could still run the same hardware - simulated without using a frontend * can you eventually make the system connections all configurable via a config file? -* test using mpsc to pass messages with the tty IO thread, and test if it's slower than what you have now - * make it possible to break out of the current execution, into the debugger, by using a certain keystroke * make tests for each instruction @@ -28,7 +17,6 @@ * Coprocessor instructions: cpBcc, cpDBcc, cpGEN, cpScc, cpTRAPcc -* should you simulate clock ticks, and only step devices when they next request it * how can you have multple CPUs * should you simulate bus arbitration?