Compare commits

...

4 Commits

Author SHA1 Message Date
Thomas Harte 807835b9fe
Merge pull request #1361 from TomHarte/Pipeline
Provide hooks for implementing pipeline prefetch.
2024-04-19 06:58:45 -07:00
Thomas Harte 4bf02122ee Fix disassembler. 2024-04-18 23:17:44 -04:00
Thomas Harte e6c4454059 Provide a means for SWI interception. 2024-04-18 22:13:58 -04:00
Thomas Harte d464ce831a Add `did_set_pc`. 2024-04-18 19:30:07 -04:00
4 changed files with 79 additions and 34 deletions

View File

@ -255,7 +255,7 @@ struct Disassembler {
template <Flags> void perform(CoprocessorDataOperation) {}
template <Flags> void perform(CoprocessorDataTransfer) {}
void software_interrupt() {
void software_interrupt(SoftwareInterrupt) {
instruction_.operation = Instruction::Operation::SWI;
}
void unknown() {

View File

@ -29,20 +29,28 @@ DestinationT read_bus(SourceT value) {
}
}
struct NullStatusHandler {
struct NullControlFlowHandler {
/// Indicates that a potential pipeline-affecting status flag change occurred,
/// i.e. a change to processor mode or interrupt flags.
void did_set_status() {}
/// Indicates that the PC was altered by the instruction.
void did_set_pc() {}
/// Provides notification that an SWI is about to happen along with the option of skipping it; this gives handlers the
/// chance to substitute a high-level reimplementation of the service call.
bool should_swi([[maybe_unused]] uint32_t comment) { return true; }
};
/// A class compatible with the @c OperationMapper definition of a scheduler which applies all actions
/// immediately, updating either a set of @c Registers or using the templated @c MemoryT to access
/// memory. No hooks are currently provided for applying realistic timing.
///
/// If a StatusObserverT is specified, it'll receive calls to @c did_set_status() following every direct
/// write to the status bits — i.e. any change that can affect mode or interrupt flags.
template <Model model, typename MemoryT, typename StatusObserverT = NullStatusHandler>
/// If a ControlFlowHandlerT is specified, it'll receive calls as defined in the NullControlFlowHandler above.
template <Model model, typename MemoryT, typename ControlFlowHandlerT = NullControlFlowHandler>
struct Executor {
template <typename... Args>
Executor(StatusObserverT &observer, Args &&...args) : bus(std::forward<Args>(args)...), status_observer_(observer) {}
Executor(ControlFlowHandlerT &handler, Args &&...args) : bus(std::forward<Args>(args)...), control_flow_handler_(handler) {}
template <typename... Args>
Executor(Args &&...args) : bus(std::forward<Args>(args)...) {}
@ -194,7 +202,7 @@ struct Executor {
}
if(!is_comparison(flags.operation()) && fields.destination() == 15) {
registers_.set_pc(pc_proxy);
set_pc<true>(pc_proxy);
}
if constexpr (flags.set_condition_codes()) {
// "When Rd is R15 and the S flag in the instruction is set, the PSR is overwritten by the
@ -202,8 +210,7 @@ struct Executor {
// normally produce a result (CMP, CMN, TST, TEQ) ... the result will be used to update those
// PSR flags which are not protected by virtue of the processor mode"
if(fields.destination() == 15) {
registers_.set_status(conditions);
status_observer_.did_set_status();
set_status(conditions);
} else {
// Set N and Z in a unified way.
registers_.set_nz(conditions);
@ -249,7 +256,7 @@ struct Executor {
if constexpr (flags.operation() == BranchFlags::Operation::BL) {
registers_[14] = registers_.pc_status(0);
}
registers_.set_pc(registers_.pc(4) + branch.offset());
set_pc<true>(registers_.pc(4) + branch.offset());
}
template <Flags f> void perform(SingleDataTransfer transfer) {
@ -288,7 +295,7 @@ struct Executor {
// Check for an address exception.
if(is_invalid_address(address)) {
registers_.exception<Registers::Exception::Address>();
exception<Registers::Exception::Address>();
return;
}
@ -322,7 +329,7 @@ struct Executor {
}
if(!did_write) {
registers_.exception<Registers::Exception::DataAbort>();
exception<Registers::Exception::DataAbort>();
return;
}
} else {
@ -352,12 +359,12 @@ struct Executor {
}
if(!did_read) {
registers_.exception<Registers::Exception::DataAbort>();
exception<Registers::Exception::DataAbort>();
return;
}
if(transfer.destination() == 15) {
registers_.set_pc(value);
set_pc<true>(value);
} else {
registers_[transfer.destination()] = value;
}
@ -368,7 +375,7 @@ struct Executor {
// So if this is a load, don't allow write back to overwrite what was loaded.
if(flags.operation() == SingleDataTransferFlags::Operation::STR || transfer.base() != transfer.destination()) {
if(transfer.base() == 15) {
registers_.set_pc(offsetted_address);
set_pc<true>(offsetted_address);
} else {
registers_[transfer.base()] = offsetted_address;
}
@ -548,37 +555,38 @@ struct Executor {
// Finally throw an exception if necessary.
if(address_error) {
registers_.exception<Registers::Exception::Address>();
exception<Registers::Exception::Address>();
} else if(!accesses_succeeded) {
registers_.exception<Registers::Exception::DataAbort>();
exception<Registers::Exception::DataAbort>();
} else {
// If this was an LDM to R15 then apply it appropriately.
if(is_ldm && list & (1 << 15)) {
registers_.set_pc(pc_proxy);
set_pc<true>(pc_proxy);
if constexpr (flags.load_psr()) {
registers_.set_status(pc_proxy);
status_observer_.did_set_status();
set_status(pc_proxy);
}
}
}
}
void software_interrupt() {
registers_.exception<Registers::Exception::SoftwareInterrupt>();
void software_interrupt(SoftwareInterrupt swi) {
if(control_flow_handler_.should_swi(swi.comment())) {
exception<Registers::Exception::SoftwareInterrupt>();
}
}
void unknown() {
registers_.exception<Registers::Exception::UndefinedInstruction>();
exception<Registers::Exception::UndefinedInstruction>();
}
// Act as if no coprocessors present.
template <Flags> void perform(CoprocessorRegisterTransfer) {
registers_.exception<Registers::Exception::UndefinedInstruction>();
exception<Registers::Exception::UndefinedInstruction>();
}
template <Flags> void perform(CoprocessorDataOperation) {
registers_.exception<Registers::Exception::UndefinedInstruction>();
exception<Registers::Exception::UndefinedInstruction>();
}
template <Flags> void perform(CoprocessorDataTransfer) {
registers_.exception<Registers::Exception::UndefinedInstruction>();
exception<Registers::Exception::UndefinedInstruction>();
}
/// @returns The current registers state.
@ -594,13 +602,19 @@ struct Executor {
/// Indicates a prefetch abort exception.
void prefetch_abort() {
registers_.exception<Registers::Exception::PrefetchAbort>();
exception<Registers::Exception::PrefetchAbort>();
}
/// Sets the expected address of the instruction after whichever is about to be executed.
/// So it's PC+4 compared to most other systems.
///
/// By default this is not forwarded to the control-flow handler.
template <bool notify = false>
void set_pc(uint32_t pc) {
registers_.set_pc(pc);
if constexpr (notify) {
control_flow_handler_.did_set_pc();
}
}
/// @returns The address of the instruction that should be fetched next. So as execution of each instruction
@ -613,12 +627,23 @@ struct Executor {
MemoryT bus;
private:
using StatusObserverTStorage =
template <Registers::Exception type>
void exception() {
registers_.exception<type>();
control_flow_handler_.did_set_pc();
}
void set_status(uint32_t status) {
registers_.set_status(status);
control_flow_handler_.did_set_status();
}
using ControlFlowHandlerTStorage =
typename std::conditional<
std::is_same_v<StatusObserverT, NullStatusHandler>,
StatusObserverT,
StatusObserverT &>::type;
StatusObserverTStorage status_observer_;
std::is_same_v<ControlFlowHandlerT, NullControlFlowHandler>,
ControlFlowHandlerT,
ControlFlowHandlerT &>::type;
ControlFlowHandlerTStorage control_flow_handler_;
Registers registers_;
static bool is_invalid_address(uint32_t address) {

View File

@ -388,6 +388,19 @@ private:
uint8_t flags_;
};
//
// Software interrupt.
//
struct SoftwareInterrupt {
constexpr SoftwareInterrupt(uint32_t opcode) noexcept : opcode_(opcode) {}
/// The 24-bit comment field, often decoded by the receiver of an SWI.
uint32_t comment() const { return opcode_ & 0xff'ffff; }
private:
uint32_t opcode_;
};
struct CoprocessorDataTransfer {
constexpr CoprocessorDataTransfer(uint32_t opcode) noexcept : opcode_(opcode) {}
@ -461,7 +474,7 @@ struct OperationMapper {
// Software interreupt; cf. p.35.
if constexpr (((partial >> 24) & 0b1111) == 0b1111) {
scheduler.software_interrupt();
scheduler.software_interrupt(SoftwareInterrupt(instruction));
return;
}
@ -517,7 +530,7 @@ struct SampleScheduler {
template <Flags> void perform(CoprocessorDataTransfer);
// Irregular operations.
void software_interrupt();
void software_interrupt(SoftwareInterrupt);
void unknown();
};

View File

@ -420,6 +420,13 @@ class ConcreteMachine:
update_interrupts();
}
void did_set_pc() {
}
bool should_swi(uint32_t) {
return true;
}
void update_clock_rates() {
video_divider_ = executor_.bus.video().clock_divider();
}