From 8e3cccf4d6e72c9521c46d96b6d4a8d8ff1837d0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Apr 2022 15:00:55 -0400 Subject: [PATCH] Begins a formalised 68k decoder. --- InstructionSets/68k/Decoder.cpp | 166 +++++++++++++ InstructionSets/68k/Decoder.hpp | 38 +++ InstructionSets/68k/Instruction.hpp | 219 ++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 18 ++ 4 files changed, 441 insertions(+) create mode 100644 InstructionSets/68k/Decoder.cpp create mode 100644 InstructionSets/68k/Decoder.hpp create mode 100644 InstructionSets/68k/Instruction.hpp diff --git a/InstructionSets/68k/Decoder.cpp b/InstructionSets/68k/Decoder.cpp new file mode 100644 index 000000000..bd10e0626 --- /dev/null +++ b/InstructionSets/68k/Decoder.cpp @@ -0,0 +1,166 @@ +// +// Decoder.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/04/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#include "Decoder.hpp" + +using namespace InstructionSet::M68k; + +namespace { + +/// @returns The @c AddressingMode given the specified mode and reg, subject to potential +/// aliasing on the '020+ as described above the @c AddressingMode enum. +constexpr AddressingMode combined_mode(int mode, int reg) { + return (mode != 7) ? AddressingMode(mode) : AddressingMode(0b01'000 | reg); +} + +} + +// MARK: - Instruction decoders. + +template Preinstruction Predecoder::decode(uint16_t instruction) { + // Fields used pervasively below. + // + // Underlying assumption: the compiler will discard whatever of these + // isn't actually used. + const auto ea_register = instruction & 7; + const auto ea_mode = (instruction >> 3) & 7; + const auto ea_combined_mode = combined_mode(ea_mode, ea_register); + + const auto opmode = (instruction >> 6) & 7; + const auto data_register = (instruction >> 9) & 7; + + switch(operation) { + + // + // MARK: ABCD, SBCD. + // + case Operation::ABCD: case Operation::SBCD: { + const auto addressing_mode = (instruction & 8) ? + AddressingMode::AddressRegisterIndirectWithPredecrement : AddressingMode::DataRegisterDirect; + + return Preinstruction(operation, + addressing_mode, ea_register, + addressing_mode, data_register); + } + + // + // MARK: AND, OR, EOR. + // + case Operation::ANDb: case Operation::ANDw: case Operation::ANDl: + case Operation::ORb: case Operation::ORw: case Operation::ORl: + case Operation::EORb: case Operation::EORw: case Operation::EORl: { + // Opmode 7 is illegal. + if(opmode == 7) { + return Preinstruction(); + } + + constexpr bool is_eor = operation == Operation::EORb || operation == Operation::EORw || operation == Operation::EORl; + + if(opmode & 4) { + // Dn Λ < ea > → < ea > + + // The operations other than EOR do not permit + // to be a data register; targetting a data register + // should be achieved with the alternative opmode. + if constexpr (!is_eor) { + if(ea_combined_mode == AddressingMode::DataRegisterDirect) { + return Preinstruction(); + } + } + + return Preinstruction(operation, + AddressingMode::DataRegisterDirect, data_register, + ea_combined_mode, ea_register); + } else { + // < ea > Λ Dn → Dn + + // EOR doesn't permit → Dn. + if constexpr (is_eor) { + return Preinstruction(); + } + + return Preinstruction(operation, + ea_combined_mode, ea_register, + AddressingMode::DataRegisterDirect, data_register); + } + + return Preinstruction(); + } + + // + // MARK: EXG. + // + case Operation::EXG: + switch((instruction >> 3)&31) { + default: return Preinstruction(); + + case 0x08: return Preinstruction(operation, + AddressingMode::DataRegisterDirect, ea_register, + AddressingMode::DataRegisterDirect, data_register); + + case 0x09: return Preinstruction(operation, + AddressingMode::AddressRegisterDirect, ea_register, + AddressingMode::AddressRegisterDirect, data_register); + + case 0x11: return Preinstruction(operation, + AddressingMode::AddressRegisterDirect, ea_register, + AddressingMode::DataRegisterDirect, data_register); + } + + // + // MARK: MULU, MULS. + // + case Operation::MULU: case Operation::MULS: + return Preinstruction(operation, + ea_combined_mode, ea_register, + AddressingMode::DataRegisterDirect, data_register); + + } +} + +// MARK: - Page decoders. + +Preinstruction Predecoder::decodeC(uint16_t instruction) { + // 4-3 (p107) + if((instruction & 0x1f0) == 0x100) return decode(instruction); + + // 4-15 (p119) + switch(instruction & 0x0c0) { + case 0x00: return decode(instruction); + case 0x40: return decode(instruction); + case 0x80: return decode(instruction); + default: break; + } + + switch(instruction & 0x1c0) { + case 0x0c0: return decode(instruction); // 4-139 (p243) + case 0x1c0: return decode(instruction); // 4-136 (p240) + default: break; + } + + // 4-105 (p209) + switch(instruction & 0x1f8) { + case 0x140: + case 0x148: + case 0x188: return decode(instruction); + } + return Preinstruction(); +} + +// MARK: - Main decoder. + +Preinstruction Predecoder::decode(uint16_t instruction) { + // Divide first based on line. + switch(instruction & 0xf000) { + case 0xc000: return decodeC(instruction); + + default: break; + } + + return Preinstruction(); +} diff --git a/InstructionSets/68k/Decoder.hpp b/InstructionSets/68k/Decoder.hpp new file mode 100644 index 000000000..06f450e32 --- /dev/null +++ b/InstructionSets/68k/Decoder.hpp @@ -0,0 +1,38 @@ +// +// Decoder.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/04/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#ifndef Decoder_hpp +#define Decoder_hpp + +#include "Instruction.hpp" + +namespace InstructionSet { +namespace M68k { + +/*! + A stateless decoder that can map from instruction words to preinstructions + (i.e. enough to know the operation and size, and either know the addressing mode + and registers or else know how many further extension words are needed). +*/ +class Predecoder { + public: + Preinstruction decode(uint16_t instruction); + + private: + // Page by page decoders; each gets a bit ad hoc so + // it is neater to separate them. + Preinstruction decodeC(uint16_t instruction); + + // Specific instruction decoders. + template Preinstruction decode(uint16_t instruction); +}; + +} +} + +#endif /* Decoder_hpp */ diff --git a/InstructionSets/68k/Instruction.hpp b/InstructionSets/68k/Instruction.hpp new file mode 100644 index 000000000..3f687edd8 --- /dev/null +++ b/InstructionSets/68k/Instruction.hpp @@ -0,0 +1,219 @@ +// +// 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 + +namespace InstructionSet { +namespace M68k { + +enum class Operation: uint8_t { + Undefined, + + ABCD, SBCD, NBCD, + + ADDb, ADDw, ADDl, + ADDQb, ADDQw, ADDQl, + ADDAw, ADDAl, + ADDQAw, ADDQAl, + ADDXb, ADDXw, ADDXl, + + SUBb, SUBw, SUBl, + SUBQb, SUBQw, SUBQl, + SUBAw, SUBAl, + SUBQAw, SUBQAl, + SUBXb, SUBXw, SUBXl, + + MOVEb, MOVEw, MOVEl, MOVEq, + MOVEAw, MOVEAl, + PEA, + + MOVEtoSR, MOVEfromSR, + MOVEtoCCR, + + ORItoSR, ORItoCCR, + ANDItoSR, ANDItoCCR, + EORItoSR, EORItoCCR, + + BTSTb, BTSTl, + BCLRl, BCLRb, + CMPb, CMPw, CMPl, + CMPAw, + TSTb, TSTw, TSTl, + + JMP, RTS, + BRA, Bcc, + DBcc, + Scc, + + 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, + + MOVEPtoRl, MOVEPtoRw, + MOVEPtoMl, MOVEPtoMw, + + ANDb, ANDw, ANDl, + EORb, EORw, EORl, + NOTb, NOTw, NOTl, + ORb, ORw, ORl, + + MULU, MULS, + DIVU, DIVS, + + RTE_RTR, + + TRAP, TRAPV, + CHK, + + EXG, SWAP, + + BCHGl, BCHGb, + BSETl, BSETb, + + TAS, + + EXTbtow, EXTwtol, + + LINK, UNLINK, + + STOP, +}; + +/// 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 = 0b11'111, + + /// Dn + DataRegisterDirect = 0b00'000, + + /// An + AddressRegisterDirect = 0b11'000, + /// (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) + AddressRegisterIndirectWithIndexBaseDisplacement = 0b10'000, + + /// ([bd, An, Xn], od) + MemoryIndirectPostindexed = 0b10'001, + /// ([bd, An], Xn, od) + MemoryIndirectPreindexed = 0b10'010, + + /// (d16, PC) + ProgramCounterIndirectWithDisplacement = 0b01'010, + /// (d8, PC, Xn) + ProgramCounterIndirectWithIndex8bitDisplacement = 0b01'011, + /// (bd, PC, Xn) + ProgramCounterIndirectWithIndexBaseDisplacement = 0b10'011, + /// ([bd, PC, Xn], od) + ProgramCounterMemoryIndirectPostindexed = 0b10'100, + /// ([bc, PC], Xn, od) + ProgramCounterMemoryIndirectPreindexed = 0b10'101, + + /// (xxx).W + AbsoluteShort = 0b01'000, + /// (xxx).L + AbsoluteLong = 0b01'001, + + /// # + ImmediateData = 0b01'100, +}; + +/*! + 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; + + // First operand. + AddressingMode source_mode() { + return AddressingMode(source_ & 0x1f); + } + int source() { + return source_ >> 5; + } + + // Second operand. + AddressingMode destination_mode() { + return AddressingMode(destination_ & 0x1f); + } + int destination() { + return destination_ >> 5; + } + + private: + uint8_t source_ = 0; + uint8_t destination_ = 0; + + public: + Preinstruction( + Operation operation, + AddressingMode source_mode, + int source, + AddressingMode destination_mode, + int destination) : operation(operation) { + source_ = uint8_t(source_mode) | uint8_t(source << 5); + destination_ = uint8_t(destination_mode) | uint8_t(destination << 5); + } + + Preinstruction() {} +}; + +} +} + +#endif /* InstructionSets_68k_Instruction_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 42b4a3b09..3032c9e6d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -902,6 +902,8 @@ 4BBB70A8202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; }; 4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; }; 4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; }; + 4BBD689928037E53004790C1 /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBD689728037E53004790C1 /* Decoder.cpp */; }; + 4BBD689A28037E53004790C1 /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBD689728037E53004790C1 /* Decoder.cpp */; }; 4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */; }; 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; }; 4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; }; @@ -1946,6 +1948,9 @@ 4BBB70A7202014E2002FE009 /* MultiProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiProducer.hpp; sourceTree = ""; }; 4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i8272.cpp; sourceTree = ""; }; 4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8272.hpp; sourceTree = ""; }; + 4BBD689328037B0E004790C1 /* Instruction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Instruction.hpp; sourceTree = ""; }; + 4BBD689728037E53004790C1 /* Decoder.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = ""; }; + 4BBD689828037E53004790C1 /* Decoder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = ""; }; 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = ""; }; 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = ""; }; @@ -4282,6 +4287,16 @@ path = 8272; sourceTree = ""; }; + 4BBD689228037B0E004790C1 /* 68k */ = { + isa = PBXGroup; + children = ( + 4BBD689328037B0E004790C1 /* Instruction.hpp */, + 4BBD689728037E53004790C1 /* Decoder.cpp */, + 4BBD689828037E53004790C1 /* Decoder.hpp */, + ); + path = 68k; + sourceTree = ""; + }; 4BBF49B41ED2881600AB3669 /* FUSE */ = { isa = PBXGroup; children = ( @@ -4718,6 +4733,7 @@ 4BEDA45425B5ECAB000C2DBD /* CachingExecutor.hpp */, 4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */, 4BEDA3B625B25563000C2DBD /* README.md */, + 4BBD689228037B0E004790C1 /* 68k */, 4BEDA40925B2844B000C2DBD /* M50740 */, 4BEDA3B325B25563000C2DBD /* PowerPC */, 4BEDA3B725B25563000C2DBD /* x86 */, @@ -5431,6 +5447,7 @@ 4BFEA2F02682A7B900EBF94C /* Dave.cpp in Sources */, 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, + 4BBD689A28037E53004790C1 /* Decoder.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, 4B4DEC08252BFA56004583AC /* 65816Base.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, @@ -5684,6 +5701,7 @@ 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */, 4B9EC0EA26B384080060A31F /* Keyboard.cpp in Sources */, 4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */, + 4BBD689928037E53004790C1 /* Decoder.cpp in Sources */, 4BC57CD92436A62900FBC404 /* State.cpp in Sources */, 4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */, 4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,