// // ExecutorImplementation.hpp // Clock Signal // // Created by Thomas Harte on 01/05/2022. // Copyright © 2022 Thomas Harte. All rights reserved. // #ifndef InstructionSets_M68k_ExecutorImplementation_hpp #define InstructionSets_M68k_ExecutorImplementation_hpp #include "../Perform.hpp" #include namespace InstructionSet { namespace M68k { template Executor::Executor(BusHandler &handler) : bus_handler_(handler) { reset(); } template void Executor::reset() { // Establish: supervisor state, all interrupts blocked. status_.set_status(0b0010'0011'1000'0000); did_update_status(); // Seed stack pointer and program counter. data_[7].l = bus_handler_.template read(0); program_counter_.l = bus_handler_.template read(4); } template void Executor::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) { switch(size) { case DataSize::Byte: value.b = bus_handler_.template read(address); break; case DataSize::Word: value.w = bus_handler_.template read(address); break; case DataSize::LongWord: value.l = bus_handler_.template read(address); break; } } template void Executor::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) { switch(size) { case DataSize::Byte: bus_handler_.template write(address, value.b); break; case DataSize::Word: bus_handler_.template write(address, value.w); break; case DataSize::LongWord: bus_handler_.template write(address, value.l); break; } } template template IntT Executor::read_pc() { const IntT result = bus_handler_.template read(program_counter_.l); if constexpr (sizeof(IntT) == 4) { program_counter_.l += 4; } else { program_counter_.l += 2; } return result; } template uint32_t Executor::index_8bitdisplacement() { // TODO: if not a 68000, check bit 8 for whether this should be a full extension word; // also include the scale field even if not. const auto extension = read_pc(); const auto offset = int8_t(extension); const int register_index = (extension >> 12) & 7; const uint32_t displacement = (extension & 0x8000) ? address_[register_index].l : data_[register_index].l; const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement); return offset + sized_displacement; } template typename Executor::EffectiveAddress Executor::calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index) { EffectiveAddress ea; switch(instruction.mode(index)) { case AddressingMode::None: // Permit an uninitialised effective address to be returned; // this value shouldn't be used. break; // // Operands that don't have effective addresses, which are returned as values. // case AddressingMode::DataRegisterDirect: ea.value = data_[instruction.reg(index)]; ea.requires_fetch = false; break; case AddressingMode::AddressRegisterDirect: ea.value = address_[instruction.reg(index)]; ea.requires_fetch = false; break; case AddressingMode::Quick: ea.value.l = quick(opcode, instruction.operation); ea.requires_fetch = false; break; case AddressingMode::ImmediateData: switch(instruction.size()) { case DataSize::Byte: ea.value.l = read_pc() & 0xff; break; case DataSize::Word: ea.value.l = read_pc(); break; case DataSize::LongWord: ea.value.l = read_pc(); break; } ea.requires_fetch = false; break; // // Absolute addresses. // case AddressingMode::AbsoluteShort: ea.value.l = int16_t(read_pc()); ea.requires_fetch = true; break; case AddressingMode::AbsoluteLong: ea.value.l = read_pc(); ea.requires_fetch = true; break; // // Address register indirects. // case AddressingMode::AddressRegisterIndirect: ea.value = address_[instruction.reg(index)]; ea.requires_fetch = true; break; case AddressingMode::AddressRegisterIndirectWithPostincrement: { const auto reg = instruction.reg(index); ea.value = address_[reg]; ea.requires_fetch = true; switch(instruction.size()) { case DataSize::Byte: address_[reg].l += byte_increments[reg]; break; case DataSize::Word: address_[reg].l += 2; break; case DataSize::LongWord: address_[reg].l += 4; break; } } break; case AddressingMode::AddressRegisterIndirectWithPredecrement: { const auto reg = instruction.reg(index); switch(instruction.size()) { case DataSize::Byte: address_[reg].l -= byte_increments[reg]; break; case DataSize::Word: address_[reg].l -= 2; break; case DataSize::LongWord: address_[reg].l -= 4; break; } ea.value = address_[reg]; ea.requires_fetch = true; } break; case AddressingMode::AddressRegisterIndirectWithDisplacement: ea.value.l = address_[instruction.reg(index)].l + int16_t(read_pc()); ea.requires_fetch = true; break; case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement: ea.value.l = address_[instruction.reg(index)].l + index_8bitdisplacement(); ea.requires_fetch = true; break; // // PC-relative addresses. // // TODO: rephrase these in terms of instruction_address_. Just for security // against whatever mutations the PC has been through already to get to here. // case AddressingMode::ProgramCounterIndirectWithDisplacement: ea.value.l = program_counter_.l + int16_t(read_pc()); ea.requires_fetch = true; break; case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement: ea.value.l = program_counter_.l + index_8bitdisplacement(); ea.requires_fetch = true; break; default: // TODO. assert(false); break; } return ea; } template void Executor::run_for_instructions(int count) { while(count--) { // TODO: check interrupt level, trace flag. // Read the next instruction. instruction_address_ = program_counter_.l; const auto opcode = read_pc(); const Preinstruction instruction = decoder_.decode(opcode); if(!status_.is_supervisor_ && instruction.requires_supervisor()) { raise_exception(8); continue; } if(instruction.operation == Operation::Undefined) { switch(opcode & 0xf000) { default: raise_exception(4); continue; case 0xa000: raise_exception(10); continue; case 0xf000: raise_exception(11); continue; } } // Temporary storage. CPU::SlicedInt32 operand_[2]; EffectiveAddress effective_address_[2]; // Calculate effective addresses; copy 'addresses' into the // operands by default both: (i) because they might be values, // rather than addresses; and (ii) then they'll be there for use // by LEA and PEA. // // TODO: much of this work should be performed by a full Decoder, // so that it can be cached. effective_address_[0] = calculate_effective_address(instruction, opcode, 0); effective_address_[1] = calculate_effective_address(instruction, opcode, 1); operand_[0] = effective_address_[0].value; operand_[1] = effective_address_[1].value; // Obtain the appropriate sequence. // // TODO: make a decision about whether this goes into a fully-decoded Instruction. const auto flags = operand_flags(instruction.operation); // TODO: potential alignment exception, here and in store. #define fetch_operand(n) \ if(effective_address_[n].requires_fetch) { \ read(instruction.size(), effective_address_[n].value.l, operand_[n]); \ } if(flags & FetchOp1) { fetch_operand(0); } if(flags & FetchOp2) { fetch_operand(1); } #undef fetch_operand perform(instruction, operand_[0], operand_[1], status_, *this); // TODO: is it worth holding registers as a single block to avoid conditional below? #define store_operand(n) \ if(!effective_address_[n].requires_fetch) { \ if(instruction.mode(n) == AddressingMode::DataRegisterDirect) { \ data_[instruction.reg(n)] = operand_[n]; \ } else { \ address_[instruction.reg(n)] = operand_[n]; \ } \ } else { \ write(instruction.size(), effective_address_[n].value.l, operand_[n]); \ } if(flags & StoreOp1) { store_operand(0); } if(flags & StoreOp2) { store_operand(1); } #undef store_operand } } // MARK: - State template typename Executor::Registers Executor::get_state() { Registers result; for(int c = 0; c < 8; c++) { result.data[c] = data_[c].l; } for(int c = 0; c < 7; c++) { result.address[c] = address_[c].l; } result.status = status_.status(); result.program_counter = program_counter_.l; stack_pointers_[status_.is_supervisor_] = address_[7]; result.user_stack_pointer = stack_pointers_[0].l; result.supervisor_stack_pointer = stack_pointers_[1].l; return result; } template void Executor::set_state(const Registers &state) { for(int c = 0; c < 8; c++) { data_[c].l = state.data[c]; } for(int c = 0; c < 7; c++) { address_[c].l = state.address[c]; } status_.set_status(state.status); program_counter_.l = state.program_counter; stack_pointers_[0].l = state.user_stack_pointer; stack_pointers_[1].l = state.supervisor_stack_pointer; address_[7] = stack_pointers_[status_.is_supervisor_]; } // MARK: - Flow Control. // TODO: flow control, all below here. template void Executor::raise_exception(int index) { const uint32_t address = index << 2; // Grab the status to store, then switch into supervisor mode. const uint16_t status = status_.status(); status_.is_supervisor_ = 1; did_update_status(); // Push status and the program counter at instruction start. bus_handler_.template write(address_[7].l - 4, instruction_address_); bus_handler_.template write(address_[7].l - 6, status); address_[7].l -= 6; // Fetch the new program counter. program_counter_.l = bus_handler_.template read(address); } template void Executor::did_update_status() { // Shuffle the stack pointers. stack_pointers_[active_stack_pointer_] = address_[7]; address_[7] = stack_pointers_[status_.is_supervisor_]; active_stack_pointer_ = status_.is_supervisor_; } template void Executor::stop() {} template void Executor::set_pc(uint32_t address) { program_counter_.l = address; } template void Executor::add_pc(uint32_t offset) { program_counter_.l = instruction_address_ + offset; } template void Executor::bsr(uint32_t offset) { address_[7].l -= 4; bus_handler_.template write(address_[7].l, program_counter_.l); program_counter_.l = instruction_address_ + offset; } template void Executor::jsr(uint32_t address) { address_[7].l -= 4; bus_handler_.template write(address_[7].l, program_counter_.l); program_counter_.l = address; } template void Executor::link(uint32_t &address, uint32_t offset) { address_[7].l -= 4; bus_handler_.template write(address_[7].l, address); address = address_[7].l; address_[7].l += offset; } template void Executor::unlink(uint32_t &address) { address_[7].l = address; address = bus_handler_.template read(address_[7].l); address_[7].l += 4; } template template void Executor::movep(Preinstruction instruction, uint32_t source, uint32_t dest) { if(instruction.mode<0>() == AddressingMode::DataRegisterDirect) { const uint32_t reg = source; uint32_t address = dest; // Move register to memory. if constexpr (sizeof(IntT) == 4) { bus_handler_.template write(address, uint8_t(reg >> 24)); address += 2; bus_handler_.template write(address, uint8_t(reg >> 16)); address += 2; } bus_handler_.template write(address, uint8_t(reg >> 8)); address += 2; bus_handler_.template write(address, uint8_t(reg)); } else { // Move memory to register. uint32_t ® = data_[instruction.reg<1>()].l; uint32_t address = source; if constexpr (sizeof(IntT) == 4) { reg = bus_handler_.template read(address) << 24; address += 2; reg |= bus_handler_.template read(address) << 26; address += 2; } else { reg &= 0xffff0000; } reg |= bus_handler_.template read(address) << 8; address += 2; reg |= bus_handler_.template read(address); } } } } #endif /* InstructionSets_M68k_ExecutorImplementation_hpp */