Replies: 5 comments
-
|
Hi Philippe, thanks for the detailed explanation! If I got everything right, the issue is that Moira does not always increment the PC at the correct cycles. Thus, the ideal fix would be to increment the PC at the proper places. However, this would be a critical change and could potentially break much of the existing AddressBus exception behavior, on which vAmiga heavily relies (setting up the correct AddressError stack frames was already a mystery for me, so there is already a lot of code that compensates for a wrong PC. I basically adjusted the frames again and again, until I got what the real Amiga showed). Implementing a proper fix would require a significant amount of time, which I unfortunately don’t have right now. For that reason, I am generally in favor of implementing a workaround at the moment.
Exposing a method that returns the “hardware PC” would likely require maintaining an additional PC counter, which would have performance implications. Thus, it doesn't seem like a real option at the moment.
If I understand correctly, would the fix be implemented here? template <Core C> void
Moira::execBusError(StackFrame frame, int delay)
{
u16 status = getSR();
// Inform the delegate
willExecute(M68kException::BUS_ERROR, 2);
// Emulate additional delay
sync(delay);
// Enter supervisor mode
setSupervisorMode(true);
// Disable tracing
clearTraceFlags();
flags &= ~State::TRACE_EXC;
SYNC(8);
// A misaligned stack pointer will cause a double fault
if (misaligned<C>(reg.sp)) throw DoubleFault();
// Write stack frame
if constexpr (C == Core::C68000) {
writeStackFrameAEBE<C>(frame);
} else {
writeStackFrame1000<C>(frame, status, frame.pc, reg.pc0, 2, frame.addr);
}
SYNC(2);
// Jump to exception vector
jumpToVector<C>(2);
// Inform the delegate
didExecute(M68kException::BUS_ERROR, 2);
}I’m comfortable with all changes made inside this function, since no Amiga emulator would ever call it (thus, it's zero-risk for vAmiga). What I’m not yet sure about is whether we have sufficient information available at this point to correctly construct the stack frame. Would it be enough to simply write an incremented PC, or is additional pipeline state required to make it accurate? |
Beta Was this translation helpful? Give feedback.
-
|
For now, I'm using a workaround on the caller side: when constructing the StackFrame before throwing BusError, I set frame.pc = getPC() + 2. This limits the adjustment to the specific bus errors that need it, without altering Moira's internal behavior. I'll keep testing and let you know if a cleaner solution emerges. |
Beta Was this translation helpful? Give feedback.
-
|
A possible middle ground would be to expose the prefetchDone flag as a read-only accessor, so the caller can decide whether to apply the +2: // Moira.h // MoiraDataflow_cpp.h — end of prefetch() // Moira.cpp — before instruction handler (both paths) // Caller side This keeps execBusError untouched while giving callers accurate pipeline state. But this would need more testing before I can confirm it's safe. |
Beta Was this translation helpful? Give feedback.
-
Okay, that makes sense. Considering options, I think the most practical way forward is to address the issue in three phases:
|
Beta Was this translation helpful? Give feedback.
-
|
That's a great approach. Having realPC track the true hardware state eliminates the need for any conditional logic in the stack frame construction. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello, I use Moira for my ST emulator for Mac (https://nostalgia.phgsoftware.be). When trying to run Spectre, a Mac emulator for the AtariST, I had the following problem:
When a memory callback throws a BusError exception during a write operation, the stack frame needs to reflect the 68000's post-prefetch state. However, the callback only has access to reg.pc and queue.ird, which may not match what the real 68000 would expose at that point.
The problem:
Many instructions (e.g., CLR, MOVE) perform a mid-instruction prefetch() before the final write cycle:
readOp(...) → read destination
prefetch() → ird = irc; irc = read(pc+2); pc0 = pc
writeOp(...) → write result → BusError thrown here
After prefetch():
queue.ird contains the next instruction's opcode (correct for real 68000)
reg.pc has not advanced (unlike the real 68000 where the prefetch bus cycle increments the program counter)
On the real 68000, the PC pushed in the bus error stack frame is reg.pc + 2 because the prefetch bus cycle did advance the address counter. Software like Spectre GCR relies on the consistency between IRD and PC in the stack frame: it searches memory at PC-2, PC-4, etc. looking for the IRD value to identify the faulting instruction.
Workaround:
frame.pc = cpu->getPC() + 2; // compensate for prefetch not advancing reg.pc
frame.ird = cpu->getIRD(); // post-prefetch value (correct)
This workaround allows Spectre to run.
But maybe :
A cleaner solution might be to have Moira provide the correct bus error stack frame internally, rather than relying on the callback to reconstruct it. Either:
Expose a method that returns the "hardware PC" (accounting for prefetch advances), or
Have Moira build the StackFrame itself when catching a BusError from a callback, using its internal pipeline state
Thanks,
Philippe
Beta Was this translation helpful? Give feedback.
All reactions