Fixed interrupts

Previously the m68k wasn't masking interrupts with an equal priorty.
I also modified how they work, such that the cpus will check the
controller rather than wait for the notification call
This commit is contained in:
transistor 2021-10-29 22:02:29 -07:00
parent c1ca666aa4
commit 93c080eae6
7 changed files with 61 additions and 77 deletions

View File

@ -67,7 +67,8 @@ impl M68kDecoder {
pub fn decode_at(&mut self, memory: &mut dyn Addressable, start: u32) -> Result<(), Error> {
self.init(start);
self.instruction = self.decode_one(memory)
self.instruction = self.decode_one(memory)?;
Ok(())
}
pub fn decode_one(&mut self, memory: &mut dyn Addressable) -> Result<Instruction, Error> {

View File

@ -41,23 +41,7 @@ impl Steppable for M68k {
}
}
impl Interruptable for M68k {
fn interrupt_state_change(&mut self, state: bool, priority: u8, number: u8) -> Result<(), Error> {
let ipl = if state {
InterruptPriority::from_u8(priority)
} else {
InterruptPriority::NoInterrupt
};
if ipl != self.state.pending_ipl {
self.state.pending_ipl = ipl;
if ipl != InterruptPriority::NoInterrupt {
self.state.ipl_ack_num = number;
}
}
Ok(())
}
}
impl Interruptable for M68k { }
impl Transmutable for M68k {
fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
@ -90,7 +74,7 @@ impl M68k {
//Err(Error { err: ErrorType::Processor, native, .. }) => {
// TODO match arm conditional is temporary: illegal instructions generate a top level error in order to debug and fix issues with decode
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),
@ -121,17 +105,22 @@ impl M68k {
}
pub fn check_pending_interrupts(&mut self, system: &System) -> Result<(), Error> {
self.state.pending_ipl = match system.get_interrupt_controller().check() {
(true, priority) => InterruptPriority::from_u8(priority),
(false, _) => InterruptPriority::NoInterrupt,
};
let current_ipl = self.state.current_ipl as u8;
let pending_ipl = self.state.pending_ipl as u8;
if self.state.pending_ipl != InterruptPriority::NoInterrupt {
let priority_mask = ((self.state.sr & Flags::IntMask as u16) >> 8) as u8;
if (pending_ipl >= priority_mask || pending_ipl == 7) && pending_ipl >= current_ipl {
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.state.sr = (self.state.sr & !(Flags::IntMask as u16)) | ((self.state.current_ipl as u16) << 8);
let ack_num = system.get_interrupt_controller().acknowledge(self.state.current_ipl as u8)?;
self.exception(system, ack_num, true)?;
return Ok(());
}
}
@ -143,7 +132,7 @@ impl M68k {
Ok(())
}
pub fn exception(&mut self, system: &System, number: u8) -> Result<(), Error> {
pub fn exception(&mut self, system: &System, number: u8, is_interrupt: bool) -> Result<(), Error> {
debug!("{}: raising exception {}", DEV_NAME, number);
let offset = (number as u16) << 2;
if self.cputype >= M68kType::MC68010 {
@ -151,8 +140,13 @@ impl M68k {
}
self.push_long(system, self.state.pc)?;
self.push_word(system, self.state.sr)?;
// Changes to the flags must happen after the previous value has been pushed to the stack
self.set_flag(Flags::Supervisor, true);
self.set_flag(Flags::Tracing, false);
if is_interrupt {
self.state.sr = (self.state.sr & !(Flags::IntMask as u16)) | ((self.state.current_ipl as u16) << 8);
}
self.state.pc = self.port.read_beu32((self.state.vbr + offset as u32) as Address)?;
Ok(())
@ -351,7 +345,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(());
}
@ -370,7 +364,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(());
}
@ -651,11 +645,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

@ -88,7 +88,6 @@ pub struct M68kState {
pub status: Status,
pub current_ipl: InterruptPriority,
pub pending_ipl: InterruptPriority,
pub ipl_ack_num: u8,
pub pc: u32,
pub sr: u16,
@ -106,7 +105,6 @@ impl M68kState {
status: Status::Init,
current_ipl: InterruptPriority::NoInterrupt,
pending_ipl: InterruptPriority::NoInterrupt,
ipl_ack_num: 0,
pc: 0,
sr: FLAGS_ON_RESET,

View File

@ -32,7 +32,7 @@ pub trait Steppable {
/// A device that can receive an interrupt. The `interrupt_state_change()` method
/// will be called whenever an interrupt signal changes goes high or low.
pub trait Interruptable {
fn interrupt_state_change(&mut self, state: bool, priority: u8, number: u8) -> Result<(), Error>;
//fn interrupt_state_change(&mut self, state: bool, priority: u8, number: u8) -> Result<(), Error>;
}

View File

@ -39,7 +39,7 @@ pub enum LogLevel {
Debug,
}
static mut LOG_LEVEL: LogLevel = LogLevel::Warning;
static mut LOG_LEVEL: LogLevel = LogLevel::Debug;
pub fn log_level() -> LogLevel {
unsafe { LOG_LEVEL }

View File

@ -33,17 +33,18 @@ impl Signal {
}
pub struct InterruptController {
pub target: Option<TransmutableBox>,
pub priority: Vec<Signal>,
pub interrupts: Vec<(bool, u8)>,
pub highest: u8,
}
impl InterruptController {
pub fn new() -> InterruptController {
InterruptController {
target: None,
priority: iter::repeat_with(|| Signal::new()).take(7).collect::<Vec<_>>(), //vec![Signal::new(); 7],
interrupts: vec![(false, 0); 7],
highest: 0,
}
}
@ -57,28 +58,29 @@ impl InterruptController {
}
pub fn set(&mut self, state: bool, priority: u8, number: u8) -> Result<(), Error> {
let signal = &mut self.priority[priority as usize];
signal.set(state);
match signal.has_changed() {
Some(value) => self.notify_interrupt_state(value, priority, number)?,
None => { },
self.interrupts[priority as usize].0 = state;
self.interrupts[priority as usize].1 = number;
if state && priority > self.highest {
self.highest = priority;
}
Ok(())
}
fn notify_interrupt_state(&mut self, state: bool, priority: u8, number: u8) -> Result<(), Error> {
// TODO how does this find the specific device it's connected to?
// TODO for the time being, this will find the first device to handle it or fail
debug!("interrupts: priority {} state changed to {}", priority, state);
match &self.target {
Some(dev) => {
Ok(dev.borrow_mut().as_interruptable().unwrap().interrupt_state_change(state, priority, number)?)
},
None => {
Err(Error::new(&format!("unhandled interrupt: {:x}", number)))
},
pub fn check(&mut self) -> (bool, u8) {
if self.highest > 0 {
(true, self.highest)
} else {
(false, 0)
}
}
pub fn acknowledge(&mut self, priority: u8) -> Result<u8, Error> {
let acknowledge = self.interrupts[priority as usize].1;
self.interrupts[priority as usize].0 = false;
while self.highest > 0 && !self.interrupts[self.highest as usize].0 {
self.highest -= 1;
}
Ok(acknowledge)
}
}

View File

@ -1,37 +1,30 @@
* implement a Z80
* master system emulation?
* what about a Z280? Is it just an extension, like the 68k variants?
* separate the debugger out of m68k
* i need a better way of handling disperate reads/writes to I/O spaces, rather than having multiple devices or having a massive chunk of address space allocated, continuously
* should you modify Addressable to also take the absolute address as input? I'm thinking of how the same device could be mapped to multiple addresses in memory instead
of taking up a whole range of addresses
* implement a Z80
* maybe see about a Mac 128k or something
* add instruction timing to M68k
* YM7101 timing is causing it to be very slow... speeding this up increasing rendering speed a lot, even though the frame shouldn't be drawn that often
* improve the speed of the event loop somehow (replace heap?)
* modify the interrupt handling to make the interrupted device acknowledge the interrupt, probably via the interrupt controller somehow. It might need to be async
such that the cpu tells the int_controller it's acknowledged, and then the interrupting device can check the interrupt controller if it needs to see the ack.
The ym7101 could use the ack as the "off" signal, and perhaps use that for the vsync int bit
* there's potentially an issue with interrupts for m68k where it might not properly mask interrupts causing overlapping interrupts that cause problems (eventually executing RAM in Sonic)
* YM7101 timing is causing it to be very slow... speeding this up increasing rendering speed a lot, even though the frame shouldn't be drawn that often... not sure what's wrong with the timing
* make the ym7101 set/reset the v_int occurred flag based on the interrupt controller
* each device that can make a bus request should have a BusPort which is used to access the bus. Not sure how it'll be created or passed to the device, since
the offset should be set by the builder or system, and the mask and data size should be sent by the CPU (although I suppose some systems could hook it up differently)
* what about even vs odd accesses? If you access a byte, should the bus port possible turn it into a word access, and return only the byte portion?
this would be more accurate for the 68000 which doesn't have an A0 address pin
* there is clearly an issue with the ROM writing 4 bytes to the data port when the autoincrement is only 2. This might be an issue with the fact that the CPU
is making full long word requests even though the 68000 shouldn't be able to (a long word would be 2 word accesses)
* each device that can make a bus request should have a BusPort which is used to access the bus
* separate the debugger out of m68k
* make devices nameable, using a hashmap to store them
* can you eventually make the system connections all configurable via a config file?
* you could modify read/write to return the number of bytes read or written for dynamic bus sizing used by the MC68020+
* you could modify read()/write() in Addressable to return the number of bytes read or written for dynamic bus sizing used by the MC68020+
* should you simulate bus arbitration?
* maybe see about a Mac 128k or something
* make tests for each instruction
* check all instructions in the docs
* unimplemented: ABCD, ADDX, BFFFO, BFINS, BKPT, CHK, EXG, ILLEGAL, MOVEfromCCR, MOVEP, RTR, RTD, SBCD, SUBX
* >=MC68020 undecoded & unimplemented: CALLM, CAS, CAS2, CHK2, CMP2, RTM, PACK, TRAPcc, UNPK
@ -42,9 +35,5 @@
* how can you have multple CPUs
* should you simulate bus arbitration?
* can you eventually make the system connections all configurable via a config file?
* check all instructions in the docs