Modified to use a nanosecond clock

This commit is contained in:
transistor 2021-10-23 22:22:02 -07:00
parent 2ed528a140
commit fd894f0638
5 changed files with 103 additions and 73 deletions

View File

@ -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<Clock, Error> {
fn step(&mut self, system: &System) -> Result<ClockElapsed, Error> {
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) => {

View File

@ -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<Clock, Error>;
fn step(&mut self, system: &System) -> Result<ClockElapsed, Error>;
fn on_error(&mut self, _system: &System) { }
fn on_debug(&mut self) { }
}

View File

@ -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<ClockElapsed, Error> {
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<Clock, Error> {
self.step_internal(system)?;
Ok(1)
}
}
impl Transmutable for MC68681 {
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
Some(self)

View File

@ -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<TransmutableBox>,
pub event_queue: BinaryHeap<EventDevice>,
pub bus: RefCell<Bus>,
pub interrupt_controller: RefCell<InterruptController>,
}
@ -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<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for EventDevice {}
impl PartialEq for EventDevice {
fn eq(&self, other: &Self) -> bool {
self.next_clock == other.next_clock
}
}

View File

@ -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?