1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-01 22:41:32 +00:00

Merge pull request #1361 from TomHarte/Pipeline

Provide hooks for implementing pipeline prefetch.
This commit is contained in:
Thomas Harte 2024-04-19 06:58:45 -07:00 committed by GitHub
commit 807835b9fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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(CoprocessorDataOperation) {}
template <Flags> void perform(CoprocessorDataTransfer) {} template <Flags> void perform(CoprocessorDataTransfer) {}
void software_interrupt() { void software_interrupt(SoftwareInterrupt) {
instruction_.operation = Instruction::Operation::SWI; instruction_.operation = Instruction::Operation::SWI;
} }
void unknown() { 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() {} 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 /// 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 /// 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. /// 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 /// If a ControlFlowHandlerT is specified, it'll receive calls as defined in the NullControlFlowHandler above.
/// write to the status bits — i.e. any change that can affect mode or interrupt flags. template <Model model, typename MemoryT, typename ControlFlowHandlerT = NullControlFlowHandler>
template <Model model, typename MemoryT, typename StatusObserverT = NullStatusHandler>
struct Executor { struct Executor {
template <typename... Args> 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> template <typename... Args>
Executor(Args &&...args) : bus(std::forward<Args>(args)...) {} Executor(Args &&...args) : bus(std::forward<Args>(args)...) {}
@ -194,7 +202,7 @@ struct Executor {
} }
if(!is_comparison(flags.operation()) && fields.destination() == 15) { if(!is_comparison(flags.operation()) && fields.destination() == 15) {
registers_.set_pc(pc_proxy); set_pc<true>(pc_proxy);
} }
if constexpr (flags.set_condition_codes()) { 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 // "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 // 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" // PSR flags which are not protected by virtue of the processor mode"
if(fields.destination() == 15) { if(fields.destination() == 15) {
registers_.set_status(conditions); set_status(conditions);
status_observer_.did_set_status();
} else { } else {
// Set N and Z in a unified way. // Set N and Z in a unified way.
registers_.set_nz(conditions); registers_.set_nz(conditions);
@ -249,7 +256,7 @@ struct Executor {
if constexpr (flags.operation() == BranchFlags::Operation::BL) { if constexpr (flags.operation() == BranchFlags::Operation::BL) {
registers_[14] = registers_.pc_status(0); 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) { template <Flags f> void perform(SingleDataTransfer transfer) {
@ -288,7 +295,7 @@ struct Executor {
// Check for an address exception. // Check for an address exception.
if(is_invalid_address(address)) { if(is_invalid_address(address)) {
registers_.exception<Registers::Exception::Address>(); exception<Registers::Exception::Address>();
return; return;
} }
@ -322,7 +329,7 @@ struct Executor {
} }
if(!did_write) { if(!did_write) {
registers_.exception<Registers::Exception::DataAbort>(); exception<Registers::Exception::DataAbort>();
return; return;
} }
} else { } else {
@ -352,12 +359,12 @@ struct Executor {
} }
if(!did_read) { if(!did_read) {
registers_.exception<Registers::Exception::DataAbort>(); exception<Registers::Exception::DataAbort>();
return; return;
} }
if(transfer.destination() == 15) { if(transfer.destination() == 15) {
registers_.set_pc(value); set_pc<true>(value);
} else { } else {
registers_[transfer.destination()] = value; 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. // 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(flags.operation() == SingleDataTransferFlags::Operation::STR || transfer.base() != transfer.destination()) {
if(transfer.base() == 15) { if(transfer.base() == 15) {
registers_.set_pc(offsetted_address); set_pc<true>(offsetted_address);
} else { } else {
registers_[transfer.base()] = offsetted_address; registers_[transfer.base()] = offsetted_address;
} }
@ -548,37 +555,38 @@ struct Executor {
// Finally throw an exception if necessary. // Finally throw an exception if necessary.
if(address_error) { if(address_error) {
registers_.exception<Registers::Exception::Address>(); exception<Registers::Exception::Address>();
} else if(!accesses_succeeded) { } else if(!accesses_succeeded) {
registers_.exception<Registers::Exception::DataAbort>(); exception<Registers::Exception::DataAbort>();
} else { } else {
// If this was an LDM to R15 then apply it appropriately. // If this was an LDM to R15 then apply it appropriately.
if(is_ldm && list & (1 << 15)) { if(is_ldm && list & (1 << 15)) {
registers_.set_pc(pc_proxy); set_pc<true>(pc_proxy);
if constexpr (flags.load_psr()) { if constexpr (flags.load_psr()) {
registers_.set_status(pc_proxy); set_status(pc_proxy);
status_observer_.did_set_status();
} }
} }
} }
} }
void software_interrupt() { void software_interrupt(SoftwareInterrupt swi) {
registers_.exception<Registers::Exception::SoftwareInterrupt>(); if(control_flow_handler_.should_swi(swi.comment())) {
exception<Registers::Exception::SoftwareInterrupt>();
}
} }
void unknown() { void unknown() {
registers_.exception<Registers::Exception::UndefinedInstruction>(); exception<Registers::Exception::UndefinedInstruction>();
} }
// Act as if no coprocessors present. // Act as if no coprocessors present.
template <Flags> void perform(CoprocessorRegisterTransfer) { template <Flags> void perform(CoprocessorRegisterTransfer) {
registers_.exception<Registers::Exception::UndefinedInstruction>(); exception<Registers::Exception::UndefinedInstruction>();
} }
template <Flags> void perform(CoprocessorDataOperation) { template <Flags> void perform(CoprocessorDataOperation) {
registers_.exception<Registers::Exception::UndefinedInstruction>(); exception<Registers::Exception::UndefinedInstruction>();
} }
template <Flags> void perform(CoprocessorDataTransfer) { template <Flags> void perform(CoprocessorDataTransfer) {
registers_.exception<Registers::Exception::UndefinedInstruction>(); exception<Registers::Exception::UndefinedInstruction>();
} }
/// @returns The current registers state. /// @returns The current registers state.
@ -594,13 +602,19 @@ struct Executor {
/// Indicates a prefetch abort exception. /// Indicates a prefetch abort exception.
void prefetch_abort() { 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. /// Sets the expected address of the instruction after whichever is about to be executed.
/// So it's PC+4 compared to most other systems. /// 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) { void set_pc(uint32_t pc) {
registers_.set_pc(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 /// @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; MemoryT bus;
private: 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< typename std::conditional<
std::is_same_v<StatusObserverT, NullStatusHandler>, std::is_same_v<ControlFlowHandlerT, NullControlFlowHandler>,
StatusObserverT, ControlFlowHandlerT,
StatusObserverT &>::type; ControlFlowHandlerT &>::type;
StatusObserverTStorage status_observer_; ControlFlowHandlerTStorage control_flow_handler_;
Registers registers_; Registers registers_;
static bool is_invalid_address(uint32_t address) { static bool is_invalid_address(uint32_t address) {

View File

@ -388,6 +388,19 @@ private:
uint8_t flags_; 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 { struct CoprocessorDataTransfer {
constexpr CoprocessorDataTransfer(uint32_t opcode) noexcept : opcode_(opcode) {} constexpr CoprocessorDataTransfer(uint32_t opcode) noexcept : opcode_(opcode) {}
@ -461,7 +474,7 @@ struct OperationMapper {
// Software interreupt; cf. p.35. // Software interreupt; cf. p.35.
if constexpr (((partial >> 24) & 0b1111) == 0b1111) { if constexpr (((partial >> 24) & 0b1111) == 0b1111) {
scheduler.software_interrupt(); scheduler.software_interrupt(SoftwareInterrupt(instruction));
return; return;
} }
@ -517,7 +530,7 @@ struct SampleScheduler {
template <Flags> void perform(CoprocessorDataTransfer); template <Flags> void perform(CoprocessorDataTransfer);
// Irregular operations. // Irregular operations.
void software_interrupt(); void software_interrupt(SoftwareInterrupt);
void unknown(); void unknown();
}; };

View File

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