mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-19 08:31:11 +00:00
Attempts to fix exception selection and timing.
This commit is contained in:
parent
c72bdd776e
commit
574a37814c
@ -48,8 +48,19 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
// The exception program will determine the appropriate way to respond
|
// The exception program will determine the appropriate way to respond
|
||||||
// based on the pending exception if one exists; otherwise just do a
|
// based on the pending exception if one exists; otherwise just do a
|
||||||
// standard fetch-decode-execute.
|
// standard fetch-decode-execute.
|
||||||
const size_t slot = size_t(selected_exceptions_ ? OperationSlot::Exception : OperationSlot::FetchDecodeExecute);
|
if(selected_exceptions_) {
|
||||||
active_instruction_ = &instructions[slot];
|
exception_is_interrupt_ = true;
|
||||||
|
|
||||||
|
// Do enough quick early decoding to spot a reset.
|
||||||
|
if(selected_exceptions_ & (Reset | PowerOn)) {
|
||||||
|
active_instruction_ = &instructions[size_t(OperationSlot::Reset)];
|
||||||
|
} else {
|
||||||
|
active_instruction_ = &instructions[size_t(OperationSlot::Exception)];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exception_is_interrupt_ = false;
|
||||||
|
active_instruction_ = &instructions[size_t(OperationSlot::FetchDecodeExecute)];
|
||||||
|
}
|
||||||
|
|
||||||
next_op_ = µ_ops_[active_instruction_->program_offsets[0]];
|
next_op_ = µ_ops_[active_instruction_->program_offsets[0]];
|
||||||
instruction_buffer_.clear();
|
instruction_buffer_.clear();
|
||||||
@ -199,7 +210,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
//
|
//
|
||||||
|
|
||||||
case CycleRepeatingNone:
|
case CycleRepeatingNone:
|
||||||
if(pending_exceptions_ & required_exceptions_) {
|
if(selected_exceptions_ & required_exceptions_) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
--next_op_;
|
--next_op_;
|
||||||
@ -243,6 +254,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
registers_.pc = uint16_t(data_buffer_.value);
|
registers_.pc = uint16_t(data_buffer_.value);
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
case OperationClearDataBuffer:
|
||||||
|
data_buffer_.clear();
|
||||||
|
continue;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Address construction.
|
// Address construction.
|
||||||
//
|
//
|
||||||
@ -390,62 +405,10 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
data_buffer_.size = 2;
|
data_buffer_.size = 2;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case OperationPrepareException: {
|
case OperationPrepareException:
|
||||||
// Put the proper exception vector into the data address, put the flags and PC
|
|
||||||
// into the data buffer (possibly also PBR), and skip an instruction if in
|
|
||||||
// emulation mode.
|
|
||||||
//
|
|
||||||
// I've assumed here that interrupts, BRKs and COPs can be usurped similarly
|
|
||||||
// to a 6502 but may not have the exact details correct. E.g. if IRQ has
|
|
||||||
// become inactive since the decision was made to start an interrupt, should
|
|
||||||
// that turn into a BRK?
|
|
||||||
//
|
|
||||||
// Also: priority here is a guess.
|
|
||||||
|
|
||||||
bool is_brk = false;
|
|
||||||
|
|
||||||
if(pending_exceptions_ & (Reset | PowerOn)) {
|
|
||||||
pending_exceptions_ &= ~(Reset | PowerOn);
|
|
||||||
data_address_ = 0xfffc;
|
|
||||||
set_reset_state();
|
|
||||||
|
|
||||||
// Also switch tracks to the reset program, and don't load up the
|
|
||||||
// data buffer. set_reset_state() will already have fixed the
|
|
||||||
// interrupt and decimal flags.
|
|
||||||
active_instruction_ = &instructions[size_t(OperationSlot::ResetTail)];
|
|
||||||
next_op_ = µ_ops_[active_instruction_->program_offsets[0]];
|
|
||||||
continue;
|
|
||||||
} else if(pending_exceptions_ & NMI) {
|
|
||||||
pending_exceptions_ &= ~NMI;
|
|
||||||
data_address_ = registers_.emulation_flag ? 0xfffa : 0xffea;
|
|
||||||
} else if(pending_exceptions_ & IRQ & registers_.flags.inverse_interrupt) {
|
|
||||||
// TODO: this isn't a correct way to handle usurption, I think;
|
|
||||||
// if an IRQ was selected for servicing I think it'll now be servied
|
|
||||||
// even if the IRQ line has gone low in the interim.
|
|
||||||
pending_exceptions_ &= ~IRQ;
|
|
||||||
data_address_ = registers_.emulation_flag ? 0xfffe : 0xffee;
|
|
||||||
} else if(pending_exceptions_ & Abort) {
|
|
||||||
// Special case: restore registers from start of instruction.
|
|
||||||
registers_ = abort_registers_copy_;
|
|
||||||
|
|
||||||
pending_exceptions_ &= ~Abort;
|
|
||||||
data_address_ = registers_.emulation_flag ? 0xfff8 : 0xffe8;;
|
|
||||||
} else {
|
|
||||||
// Test that this really is BRK or COP.
|
|
||||||
assert((active_instruction_ == instructions) || (active_instruction_ == &instructions[0x02]));
|
|
||||||
|
|
||||||
is_brk = active_instruction_ == instructions; // Given that BRK has opcode 00.
|
|
||||||
if(is_brk) {
|
|
||||||
data_address_ = registers_.emulation_flag ? 0xfffe : 0xffe6;
|
|
||||||
} else {
|
|
||||||
// Implicitly: COP.
|
|
||||||
data_address_ = registers_.emulation_flag ? 0xfff4 : 0xffe4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data_buffer_.value = uint32_t((registers_.pc << 8) | get_flags());
|
data_buffer_.value = uint32_t((registers_.pc << 8) | get_flags());
|
||||||
if(registers_.emulation_flag) {
|
if(registers_.emulation_flag) {
|
||||||
if(is_brk) data_buffer_.value |= Flag::Break;
|
if(!exception_is_interrupt_) data_buffer_.value |= Flag::Break;
|
||||||
data_buffer_.size = 3;
|
data_buffer_.size = 3;
|
||||||
++next_op_;
|
++next_op_;
|
||||||
} else {
|
} else {
|
||||||
@ -456,7 +419,50 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
|
|
||||||
registers_.flags.inverse_interrupt = 0;
|
registers_.flags.inverse_interrupt = 0;
|
||||||
registers_.flags.decimal = 0;
|
registers_.flags.decimal = 0;
|
||||||
} continue;
|
continue;
|
||||||
|
|
||||||
|
case OperationPickExceptionVector:
|
||||||
|
// Priority for abort and reset here is a guess.
|
||||||
|
|
||||||
|
if(pending_exceptions_ & (Reset | PowerOn)) {
|
||||||
|
pending_exceptions_ &= ~(Reset | PowerOn);
|
||||||
|
data_address_ = 0xfffc;
|
||||||
|
set_reset_state();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pending_exceptions_ & Abort) {
|
||||||
|
// Special case: restore registers from start of instruction.
|
||||||
|
registers_ = abort_registers_copy_;
|
||||||
|
|
||||||
|
pending_exceptions_ &= ~Abort;
|
||||||
|
data_address_ = registers_.emulation_flag ? 0xfff8 : 0xffe8;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pending_exceptions_ & NMI) {
|
||||||
|
pending_exceptions_ &= ~NMI;
|
||||||
|
data_address_ = registers_.emulation_flag ? 0xfffa : 0xffea;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last chance saloon for the interrupt process.
|
||||||
|
if(exception_is_interrupt_) {
|
||||||
|
data_address_ = registers_.emulation_flag ? 0xfffe : 0xffee;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... then this must be a BRK or COP that is being treated as such.
|
||||||
|
assert((active_instruction_ == instructions) || (active_instruction_ == &instructions[0x02]));
|
||||||
|
|
||||||
|
// Test for BRK, given that it has opcode 00.
|
||||||
|
if(active_instruction_ == instructions) {
|
||||||
|
data_address_ = registers_.emulation_flag ? 0xfffe : 0xffe6;
|
||||||
|
} else {
|
||||||
|
// Implicitly: COP.
|
||||||
|
data_address_ = registers_.emulation_flag ? 0xfff4 : 0xffe4;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Performance.
|
// Performance.
|
||||||
|
@ -89,16 +89,16 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
|
|||||||
++opcode;
|
++opcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_exception_generator(Generator generator, Generator reset_tail_generator) {
|
void set_exception_generator(Generator generator, Generator reset_generator) {
|
||||||
const auto map_entry = install(generator);
|
const auto map_entry = install(generator);
|
||||||
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].program_offsets[0] =
|
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].program_offsets[0] =
|
||||||
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].program_offsets[1] = uint16_t(map_entry->second.first);
|
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].program_offsets[1] = uint16_t(map_entry->second.first);
|
||||||
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].operation = JMPind;
|
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].operation = JMPind;
|
||||||
|
|
||||||
const auto reset_tail_entry = install(reset_tail_generator);
|
const auto reset_tail_entry = install(reset_generator);
|
||||||
storage_.instructions[size_t(ProcessorStorage::OperationSlot::ResetTail)].program_offsets[0] =
|
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Reset)].program_offsets[0] =
|
||||||
storage_.instructions[size_t(ProcessorStorage::OperationSlot::ResetTail)].program_offsets[1] = uint16_t(reset_tail_entry->second.first);
|
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Reset)].program_offsets[1] = uint16_t(reset_tail_entry->second.first);
|
||||||
storage_.instructions[size_t(ProcessorStorage::OperationSlot::ResetTail)].operation = JMPind;
|
storage_.instructions[size_t(ProcessorStorage::OperationSlot::Reset)].operation = JMPind;
|
||||||
}
|
}
|
||||||
|
|
||||||
void install_fetch_decode_execute() {
|
void install_fetch_decode_execute() {
|
||||||
@ -617,7 +617,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 22a. Stack; s, abort/irq/nmi/res.
|
// 22a. Stack; s, abort/irq/nmi/res.
|
||||||
static void stack_exception(AccessType, bool, const std::function<void(MicroOp)> &target) {
|
//
|
||||||
|
// Combined here with reset, which is the same sequence but with a different stack access.
|
||||||
|
static void stack_exception_impl(AccessType, bool, const std::function<void(MicroOp)> &target, MicroOp stack_op) {
|
||||||
target(CycleFetchPCThrowaway); // IO.
|
target(CycleFetchPCThrowaway); // IO.
|
||||||
target(CycleFetchPCThrowaway); // IO.
|
target(CycleFetchPCThrowaway); // IO.
|
||||||
|
|
||||||
@ -625,10 +627,15 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
|
|||||||
// reset then switches to the reset tail program.
|
// reset then switches to the reset tail program.
|
||||||
// Otherwise skips a micro-op if in emulation mode.
|
// Otherwise skips a micro-op if in emulation mode.
|
||||||
|
|
||||||
target(CyclePush); // PBR [skipped in emulation mode].
|
target(stack_op); // PBR [skipped in emulation mode].
|
||||||
target(CyclePush); // PCH.
|
target(stack_op); // PCH.
|
||||||
target(CyclePush); // PCL.
|
target(stack_op); // PCL.
|
||||||
target(CyclePush); // P.
|
|
||||||
|
target(OperationPickExceptionVector); // Select vector address.
|
||||||
|
|
||||||
|
target(stack_op); // P.
|
||||||
|
|
||||||
|
target(OperationClearDataBuffer); // Prepare to fetch the vector.
|
||||||
|
|
||||||
target(CycleFetchIncrementVector); // AAVL.
|
target(CycleFetchIncrementVector); // AAVL.
|
||||||
target(CycleFetchVector); // AAVH.
|
target(CycleFetchVector); // AAVH.
|
||||||
@ -636,18 +643,13 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
|
|||||||
target(OperationPerform); // Jumps to the vector address.
|
target(OperationPerform); // Jumps to the vector address.
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reset_tail(AccessType, bool, const std::function<void(MicroOp)> &target) {
|
static void stack_exception(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
|
||||||
// The reset program still walks through three stack accesses as if it were doing
|
stack_exception_impl(type, is8bit, target, CyclePush);
|
||||||
// the usual exception stack activity, but forces them to reads that don't modify
|
}
|
||||||
// the stack pointer. Here they are:
|
|
||||||
target(CycleAccessStack); // PCH.
|
|
||||||
target(CycleAccessStack); // PCL.
|
|
||||||
target(CycleAccessStack); // P.
|
|
||||||
|
|
||||||
target(CycleFetchIncrementVector); // AAVL.
|
static void reset(AccessType type, bool is8bit, const std::function<void(MicroOp)> &target) {
|
||||||
target(CycleFetchVector); // AAVH.
|
// The reset program just disables the usual pushes.
|
||||||
|
stack_exception_impl(type, is8bit, target, CycleAccessStack);
|
||||||
target(OperationPerform); // Jumps to the vector address.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 22b. Stack; s, PLx.
|
// 22b. Stack; s, PLx.
|
||||||
@ -754,6 +756,11 @@ struct CPU::WDC65816::ProcessorStorageConstructor {
|
|||||||
target(CyclePush); // PBR [skipped in emulation mode].
|
target(CyclePush); // PBR [skipped in emulation mode].
|
||||||
target(CyclePush); // PCH.
|
target(CyclePush); // PCH.
|
||||||
target(CyclePush); // PCL.
|
target(CyclePush); // PCL.
|
||||||
|
|
||||||
|
target(OperationPickExceptionVector); // Selects the exception's target; I've assumed the same
|
||||||
|
// logic as a 6502 here — i.e. that some interrupts can
|
||||||
|
// usurp the vectors of others.
|
||||||
|
|
||||||
target(CyclePush); // P.
|
target(CyclePush); // P.
|
||||||
|
|
||||||
target(CycleFetchIncrementVector); // AAVL.
|
target(CycleFetchIncrementVector); // AAVL.
|
||||||
@ -1069,7 +1076,7 @@ ProcessorStorage::ProcessorStorage() {
|
|||||||
/* 0xff SBC al, x */ op(absolute_long_x, SBC);
|
/* 0xff SBC al, x */ op(absolute_long_x, SBC);
|
||||||
#undef op
|
#undef op
|
||||||
|
|
||||||
constructor.set_exception_generator(&ProcessorStorageConstructor::stack_exception, &ProcessorStorageConstructor::reset_tail);
|
constructor.set_exception_generator(&ProcessorStorageConstructor::stack_exception, &ProcessorStorageConstructor::reset);
|
||||||
constructor.install_fetch_decode_execute();
|
constructor.install_fetch_decode_execute();
|
||||||
|
|
||||||
// Find any OperationMoveToNextProgram.
|
// Find any OperationMoveToNextProgram.
|
||||||
|
@ -148,11 +148,17 @@ enum MicroOp: uint8_t {
|
|||||||
/// Copies the data buffer to A.
|
/// Copies the data buffer to A.
|
||||||
OperationCopyDataToA,
|
OperationCopyDataToA,
|
||||||
|
|
||||||
|
/// Clears the data buffer.
|
||||||
|
OperationClearDataBuffer,
|
||||||
|
|
||||||
/// Fills the data buffer with three or four bytes, depending on emulation mode, containing the program
|
/// Fills the data buffer with three or four bytes, depending on emulation mode, containing the program
|
||||||
/// counter, flags and possibly the program bank. Also puts the appropriate vector address into the
|
/// counter, flags and possibly the program bank. Skips the next operation if only three are filled.
|
||||||
/// address register.
|
|
||||||
OperationPrepareException,
|
OperationPrepareException,
|
||||||
|
|
||||||
|
/// Picks the appropriate vector address to service the current exception and places it into
|
||||||
|
/// the data address register.
|
||||||
|
OperationPickExceptionVector,
|
||||||
|
|
||||||
/// Sets the memory lock output for the rest of this instruction.
|
/// Sets the memory lock output for the rest of this instruction.
|
||||||
OperationSetMemoryLock,
|
OperationSetMemoryLock,
|
||||||
|
|
||||||
@ -250,7 +256,7 @@ struct ProcessorStorage {
|
|||||||
|
|
||||||
enum class OperationSlot {
|
enum class OperationSlot {
|
||||||
Exception = 256,
|
Exception = 256,
|
||||||
ResetTail,
|
Reset,
|
||||||
FetchDecodeExecute,
|
FetchDecodeExecute,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,6 +308,7 @@ struct ProcessorStorage {
|
|||||||
static constexpr int default_exceptions = PowerOn;
|
static constexpr int default_exceptions = PowerOn;
|
||||||
int pending_exceptions_ = default_exceptions;
|
int pending_exceptions_ = default_exceptions;
|
||||||
int selected_exceptions_ = default_exceptions;
|
int selected_exceptions_ = default_exceptions;
|
||||||
|
bool exception_is_interrupt_ = false;
|
||||||
|
|
||||||
bool ready_line_ = false;
|
bool ready_line_ = false;
|
||||||
bool memory_lock_ = false;
|
bool memory_lock_ = false;
|
||||||
|
Loading…
Reference in New Issue
Block a user