// // Instruction.hpp // Clock Signal // // Created by Thomas Harte on 10/04/2022. // Copyright © 2022 Thomas Harte. All rights reserved. // #ifndef InstructionSets_68k_Instruction_hpp #define InstructionSets_68k_Instruction_hpp #include "Model.hpp" #include #include #include namespace InstructionSet { namespace M68k { enum class Operation: uint8_t { Undefined, // // 68000 operations. // NOP, ABCD, SBCD, NBCD, ADDb, ADDw, ADDl, ADDAw, ADDAl, ADDXb, ADDXw, ADDXl, SUBb, SUBw, SUBl, SUBAw, SUBAl, SUBXb, SUBXw, SUBXl, MOVEb, MOVEw, MOVEl, MOVEAw, MOVEAl, LEA, PEA, MOVEtoSR, MOVEfromSR, MOVEtoCCR, MOVEtoUSP, MOVEfromUSP, ORItoSR, ORItoCCR, ANDItoSR, ANDItoCCR, EORItoSR, EORItoCCR, BTST, BCLR, BCHG, BSET, CMPb, CMPw, CMPl, CMPAw, CMPAl, TSTb, TSTw, TSTl, JMP, JSR, RTS, DBcc, Scc, Bccb, Bccw, BSRb, BSRw, CLRb, CLRw, CLRl, NEGXb, NEGXw, NEGXl, NEGb, NEGw, NEGl, ASLb, ASLw, ASLl, ASLm, ASRb, ASRw, ASRl, ASRm, LSLb, LSLw, LSLl, LSLm, LSRb, LSRw, LSRl, LSRm, ROLb, ROLw, ROLl, ROLm, RORb, RORw, RORl, RORm, ROXLb, ROXLw, ROXLl, ROXLm, ROXRb, ROXRw, ROXRl, ROXRm, MOVEMtoRl, MOVEMtoRw, MOVEMtoMl, MOVEMtoMw, MOVEPl, MOVEPw, ANDb, ANDw, ANDl, EORb, EORw, EORl, NOTb, NOTw, NOTl, ORb, ORw, ORl, MULUw, MULSw, DIVUw, DIVSw, RTE, RTR, TRAP, TRAPV, CHKw, EXG, SWAP, TAS, EXTbtow, EXTwtol, LINKw, UNLINK, STOP, RESET, // // 68010 additions. // MOVEfromCCR, MOVEC, MOVES, BKPT, RTD, // // 68020 additions. // TRAPcc, CALLM, RTM, BFCHG, BFCLR, BFEXTS, BFEXTU, BFFFO, BFINS, BFSET, BFTST, PACK, UNPK, CASb, CASw, CASl, CAS2w, CAS2l, CHK2b, CHK2w, CHK2l, CMP2b, CMP2w, CMP2l, DIVSl, DIVUl, MULSl, MULUl, Bccl, BSRl, LINKl, CHKl, EXTbtol, cpBcc, cpDBcc, cpGEN, cpScc, cpTRAPcc, cpRESTORE, cpSAVE, // // 68030 additions. // PFLUSH, PFLUSHA, PLOADR, PLOADW, PMOVE, PMOVEFD, PTESTR, PTESTW, // // 68040 additions. // // TODO: the big addition of the 68040 is incorporation of the FPU; should I make decoding of those instructions // dependent upon a 68040 being selected, or should I offer a separate decoder in order to support systems with // a coprocessor? // // Introspection. // Max68000 = RESET, Max68010 = RTD, Max68020 = cpSAVE, Max68030 = PTESTW, Max68040 = PTESTW, }; // Provide per-model max entries in Operation. template struct OperationMax {}; template <> struct OperationMax { static constexpr Operation value = Operation::Max68000; }; template <> struct OperationMax { static constexpr Operation value = Operation::Max68010; }; template <> struct OperationMax { static constexpr Operation value = Operation::Max68020; }; template <> struct OperationMax { static constexpr Operation value = Operation::Max68030; }; template <> struct OperationMax { static constexpr Operation value = Operation::Max68040; }; const char *to_string(Operation op); template constexpr bool requires_supervisor(Operation op) { switch(op) { case Operation::MOVEfromSR: if constexpr (model == Model::M68000) { return false; } [[fallthrough]]; case Operation::ORItoSR: case Operation::ANDItoSR: case Operation::EORItoSR: case Operation::RTE: case Operation::RESET: case Operation::STOP: case Operation::MOVEtoUSP: case Operation::MOVEfromUSP: case Operation::MOVEtoSR: return true; default: return false; } } inline constexpr bool requires_extension_word(Operation op) { switch(op) { case Operation::PACK: return true; default: return false; } } enum class DataSize { Byte = 0, Word = 1, LongWord = 2, }; /// Classifies operations by the size of their memory accesses, if any. /// /// For any operations that don't fit the neat model of reading one or two operands, /// then writing zero or one, the size determines the data size of the operands only, /// not any other accesses. template constexpr DataSize operand_size(Operation operation = Operation::Undefined); template constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) { switch((t_op != Operation::Undefined) ? t_op : r_op) { case Operation::Bccb: case Operation::BSRb: case Operation::MOVEl: return uint32_t(int8_t(instruction)); case Operation::TRAP: return uint32_t(instruction & 15); case Operation::BKPT: return uint32_t(instruction & 7); default: { uint32_t value = (instruction >> 9) & 7; value |= (value - 1)&8; return value; } } } static constexpr uint8_t FetchOp1 = (1 << 0); static constexpr uint8_t FetchOp2 = (1 << 1); static constexpr uint8_t StoreOp1 = (1 << 2); static constexpr uint8_t StoreOp2 = (1 << 3); /*! Provides a bitfield with a value in the range 0–15 indicating which of the provided operation's operands are accessed via standard fetch and store cycles; the bitfield is composted of [Fetch/Store]Op[1/2] as defined above. Unusual bus sequences, such as TAS or MOVEM, are not described here. */ template constexpr uint8_t operand_flags(Operation r_operation = Operation::Undefined); /// Lists the various condition codes used by the 680x0. enum class Condition { True = 0x00, False = 0x01, High = 0x02, LowOrSame = 0x03, CarryClear = 0x04, CarrySet = 0x05, NotEqual = 0x06, Equal = 0x07, OverflowClear = 0x08, OverflowSet = 0x09, Positive = 0x0a, Negative = 0x0b, GreaterThanOrEqual = 0x0c, LessThan = 0x0d, GreaterThan = 0x0e, LessThanOrEqual = 0x0f, }; /// Indicates the addressing mode applicable to an operand. /// /// Implementation notes: /// /// Those entries starting 0b00 or 0b01 are mapped as per the 68000's native encoding; /// those starting 0b00 are those which are indicated directly by a mode field and those starting /// 0b01 are those which are indicated by a register field given a mode of 0b111. The only minor /// exception is AddressRegisterDirect, which exists on a 68000 but isn't specifiable by a /// mode and register, it's contextual based on the instruction. /// /// Those modes starting in 0b10 are the various extended addressing modes introduced as /// of the 68020, which can be detected only after interpreting an extension word. At the /// Preinstruction stage: /// /// * AddressRegisterIndirectWithIndexBaseDisplacement, MemoryIndirectPostindexed /// and MemoryIndirectPreindexed will have been partially decoded as /// AddressRegisterIndirectWithIndex8bitDisplacement; and /// * ProgramCounterIndirectWithIndexBaseDisplacement, /// ProgramCounterMemoryIndirectPostindexed and /// ProgramCounterMemoryIndirectPreindexed will have been partially decoded /// as ProgramCounterIndirectWithIndex8bitDisplacement. enum class AddressingMode: uint8_t { /// No adddressing mode; this operand doesn't exist. None = 0b01'101, /// Dn DataRegisterDirect = 0b00'000, /// An AddressRegisterDirect = 0b00'001, /// (An) AddressRegisterIndirect = 0b00'010, /// (An)+ AddressRegisterIndirectWithPostincrement = 0b00'011, /// -(An) AddressRegisterIndirectWithPredecrement = 0b00'100, /// (d16, An) AddressRegisterIndirectWithDisplacement = 0b00'101, /// (d8, An, Xn) AddressRegisterIndirectWithIndex8bitDisplacement = 0b00'110, /// (bd, An, Xn) [68020+] AddressRegisterIndirectWithIndexBaseDisplacement = 0b10'000, /// ([bd, An, Xn], od) [68020+] MemoryIndirectPostindexed = 0b10'001, /// ([bd, An], Xn, od) [68020+] MemoryIndirectPreindexed = 0b10'010, /// (d16, PC) ProgramCounterIndirectWithDisplacement = 0b01'010, /// (d8, PC, Xn) ProgramCounterIndirectWithIndex8bitDisplacement = 0b01'011, /// (bd, PC, Xn) [68020+] ProgramCounterIndirectWithIndexBaseDisplacement = 0b10'011, /// ([bd, PC, Xn], od) [68020+] ProgramCounterMemoryIndirectPostindexed = 0b10'100, /// ([bc, PC], Xn, od) [68020+] ProgramCounterMemoryIndirectPreindexed = 0b10'101, /// (xxx).W AbsoluteShort = 0b01'000, /// (xxx).L AbsoluteLong = 0b01'001, /// # ImmediateData = 0b01'100, /// An additional word of data. Differs from ImmediateData by being /// a fixed size, rather than the @c operand_size of the operation. ExtensionWord = 0b01'111, /// .q; value is embedded in the opcode. Quick = 0b01'110, }; /// Guaranteed to be 1+[largest value used by AddressingMode]. static constexpr int AddressingModeCount = 0b10'110; /*! A preinstruction is as much of an instruction as can be decoded with only the first instruction word — i.e. an operation, and: * on the 68000 and 68010, the complete addressing modes; * on subsequent, a decent proportion of the addressing mode. See the notes on @c AddressingMode for potential aliasing. */ class Preinstruction { public: Operation operation = Operation::Undefined; // Instructions come with 0, 1 or 2 operands; // the getters below act to provide a list of operands // that is terminated by an AddressingMode::None. // // For two-operand instructions, argument 0 is a source // and argument 1 is a destination. // // For one-operand instructions, only argument 0 will // be provided, and will be a source and/or destination as // per the semantics of the operation. // // The versions templated on index do a range check; // if using the runtime versions then results for indices // other than 0 and 1 are undefined. AddressingMode mode(int index) const { return AddressingMode(operands_[index] >> 3); } template AddressingMode mode() const { if constexpr (index > 1) { return AddressingMode::None; } return mode(index); } int reg(int index) const { return operands_[index] & 7; } template int reg() const { if constexpr (index > 1) { return 0; } return reg(index); } /// @returns 0–7 to indicate data registers 0 to 7, or 8–15 to indicate address registers 0 to 7 respectively. /// Provides undefined results if the addressing mode is not either @c DataRegisterDirect or /// @c AddressRegisterDirect. int lreg(int index) const { return operands_[index] & 0xf; } /// @returns @c true if this instruction requires supervisor privileges; @c false otherwise. bool requires_supervisor() const { return flags_ & Flags::IsSupervisor; } /// @returns @c true if this instruction will require further fetching than can be encoded in a /// @c Preinstruction. In practice this means it is one of a very small quantity of 68020+ /// instructions; those that can rationalise extension words into one of the two operands will /// do so. Use the free function @c extension_words(instruction.operation) to /// look up the number of additional words required. /// /// (specifically affected, at least: PACK, UNPK, CAS, CAS2) bool requires_further_extension() const { return flags_ & Flags::RequiresFurtherExtension; } /// @returns The @c DataSize used for operands of this instruction, i.e. byte, word or longword. DataSize operand_size() const { return DataSize((flags_ & Flags::SizeMask) >> Flags::SizeShift); } /// @returns The condition code evaluated by this instruction if applicable. If this instruction is not /// conditional, the result is undefined. Condition condition() const { return Condition((flags_ & Flags::ConditionMask) >> Flags::ConditionShift); } private: uint8_t operands_[2] = { uint8_t(AddressingMode::None), uint8_t(AddressingMode::None)}; uint8_t flags_ = 0; std::string operand_description(int index, int opcode) const; public: Preinstruction( Operation operation, AddressingMode op1_mode, int op1_reg, AddressingMode op2_mode, int op2_reg, bool is_supervisor, bool requires_further_extension, DataSize size, Condition condition) : operation(operation) { operands_[0] = uint8_t((uint8_t(op1_mode) << 3) | op1_reg); operands_[1] = uint8_t((uint8_t(op2_mode) << 3) | op2_reg); flags_ = uint8_t( (is_supervisor ? Flags::IsSupervisor : 0x00) | (requires_further_extension ? Flags::RequiresFurtherExtension : 0x00) | (int(condition) << Flags::ConditionShift) | (int(size) << Flags::SizeShift) ); } struct Flags { static constexpr uint8_t IsSupervisor = 0b1000'0000; static constexpr uint8_t RequiresFurtherExtension = 0b0100'0000; static constexpr uint8_t ConditionMask = 0b0011'1100; static constexpr uint8_t SizeMask = 0b0000'0011; static constexpr int ConditionShift = 2; static constexpr int SizeShift = 0; }; Preinstruction() {} /// Produces a string description of this instruction; if @c opcode /// is supplied then any quick fields in this instruction will be decoded; /// otherwise they'll be printed as just 'Q'. std::string to_string(int opcode = -1) const; /// Produces a slightly-more-idiomatic version of the operation name than /// a direct to_string(instruction.operation) would, given that this decoder /// sometimes aliases operations, disambiguating based on addressing mode /// (e.g. MOVEQ is MOVE.l with the Q addressing mode). const char *operation_string() const; }; } } #include "Implementation/InstructionOperandSize.hpp" #include "Implementation/InstructionOperandFlags.hpp" #endif /* InstructionSets_68k_Instruction_hpp */