mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-12 08:30:05 +00:00
242 lines
8.3 KiB
C++
242 lines
8.3 KiB
C++
//
|
|
// Instruction.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 15/01/21.
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include "../AccessType.hpp"
|
|
|
|
namespace InstructionSet::M50740 {
|
|
|
|
enum class AddressingMode {
|
|
Implied, Accumulator, Immediate,
|
|
Absolute, AbsoluteX, AbsoluteY,
|
|
ZeroPage, ZeroPageX, ZeroPageY,
|
|
XIndirect, IndirectY,
|
|
Relative,
|
|
AbsoluteIndirect, ZeroPageIndirect,
|
|
SpecialPage,
|
|
ImmediateZeroPage,
|
|
AccumulatorRelative, ZeroPageRelative
|
|
};
|
|
|
|
static constexpr auto MaxAddressingMode = int(AddressingMode::ZeroPageRelative);
|
|
static constexpr auto MinAddressingMode = int(AddressingMode::Implied);
|
|
|
|
constexpr int size(const AddressingMode mode) {
|
|
// This is coupled to the AddressingMode list above; be careful!
|
|
constexpr int sizes[] = {
|
|
0, 0, 1,
|
|
2, 2, 2,
|
|
1, 1, 1,
|
|
1, 1,
|
|
1,
|
|
2, 1,
|
|
1,
|
|
2,
|
|
1, 2
|
|
};
|
|
static_assert(sizeof(sizes)/sizeof(*sizes) == int(MaxAddressingMode) + 1);
|
|
return sizes[int(mode)];
|
|
}
|
|
|
|
enum class Operation: uint8_t {
|
|
Invalid,
|
|
|
|
// Operations that don't access memory.
|
|
BBC0, BBC1, BBC2, BBC3, BBC4, BBC5, BBC6, BBC7,
|
|
BBS0, BBS1, BBS2, BBS3, BBS4, BBS5, BBS6, BBS7,
|
|
BCC, BCS,
|
|
BEQ, BMI, BNE, BPL,
|
|
BVC, BVS, BRA, BRK,
|
|
JMP, JSR,
|
|
RTI, RTS,
|
|
CLC, CLD, CLI, CLT, CLV,
|
|
SEC, SED, SEI, SET,
|
|
INX, INY, DEX, DEY,
|
|
FST, SLW,
|
|
NOP,
|
|
PHA, PHP, PLA, PLP,
|
|
STP,
|
|
TAX, TAY, TSX, TXA,
|
|
TXS, TYA,
|
|
|
|
// Read operations.
|
|
ADC, SBC,
|
|
AND, ORA, EOR, BIT,
|
|
CMP, CPX, CPY,
|
|
LDA, LDX, LDY,
|
|
TST,
|
|
|
|
// Read-modify-write operations.
|
|
ASL, LSR,
|
|
CLB0, CLB1, CLB2, CLB3, CLB4, CLB5, CLB6, CLB7,
|
|
SEB0, SEB1, SEB2, SEB3, SEB4, SEB5, SEB6, SEB7,
|
|
COM,
|
|
DEC, INC,
|
|
ROL, ROR, RRF,
|
|
|
|
// Write operations.
|
|
LDM,
|
|
STA, STX, STY,
|
|
};
|
|
|
|
static constexpr auto MaxOperation = int(Operation::STY);
|
|
static constexpr auto MinOperation = int(Operation::BBC0);
|
|
|
|
constexpr AccessType access_type(const Operation operation) {
|
|
if(operation < Operation::ADC) return AccessType::None;
|
|
if(operation < Operation::ASL) return AccessType::Read;
|
|
if(operation < Operation::LDM) return AccessType::ReadModifyWrite;
|
|
return AccessType::Write;
|
|
}
|
|
|
|
constexpr bool uses_index_mode(const Operation operation) {
|
|
return
|
|
operation == Operation::ADC || operation == Operation::AND ||
|
|
operation == Operation::CMP || operation == Operation::EOR ||
|
|
operation == Operation::LDA || operation == Operation::ORA ||
|
|
operation == Operation::SBC;
|
|
}
|
|
|
|
/*!
|
|
@returns The name of @c operation.
|
|
*/
|
|
inline constexpr const char *operation_name(const Operation operation) {
|
|
#define MAP(x) case Operation::x: return #x;
|
|
switch(operation) {
|
|
default: break;
|
|
MAP(BBC0); MAP(BBC1); MAP(BBC2); MAP(BBC3); MAP(BBC4); MAP(BBC5); MAP(BBC6); MAP(BBC7);
|
|
MAP(BBS0); MAP(BBS1); MAP(BBS2); MAP(BBS3); MAP(BBS4); MAP(BBS5); MAP(BBS6); MAP(BBS7);
|
|
MAP(BCC); MAP(BCS); MAP(BEQ); MAP(BMI); MAP(BNE); MAP(BPL); MAP(BVC); MAP(BVS);
|
|
MAP(BRA); MAP(BRK); MAP(JMP); MAP(JSR); MAP(RTI); MAP(RTS); MAP(CLC); MAP(CLD);
|
|
MAP(CLI); MAP(CLT); MAP(CLV); MAP(SEC); MAP(SED); MAP(SEI); MAP(SET); MAP(INX);
|
|
MAP(INY); MAP(DEX); MAP(DEY); MAP(FST); MAP(SLW); MAP(NOP); MAP(PHA); MAP(PHP);
|
|
MAP(PLA); MAP(PLP); MAP(STP); MAP(TAX); MAP(TAY); MAP(TSX); MAP(TXA); MAP(TXS);
|
|
MAP(TYA); MAP(ADC); MAP(SBC); MAP(AND); MAP(ORA); MAP(EOR); MAP(BIT); MAP(CMP);
|
|
MAP(CPX); MAP(CPY); MAP(LDA); MAP(LDX); MAP(LDY); MAP(TST); MAP(ASL); MAP(LSR);
|
|
MAP(CLB0); MAP(CLB1); MAP(CLB2); MAP(CLB3); MAP(CLB4); MAP(CLB5); MAP(CLB6); MAP(CLB7);
|
|
MAP(SEB0); MAP(SEB1); MAP(SEB2); MAP(SEB3); MAP(SEB4); MAP(SEB5); MAP(SEB6); MAP(SEB7);
|
|
MAP(COM); MAP(DEC); MAP(INC); MAP(ROL); MAP(ROR); MAP(RRF); MAP(LDM); MAP(STA);
|
|
MAP(STX); MAP(STY);
|
|
}
|
|
#undef MAP
|
|
|
|
return "???";
|
|
}
|
|
|
|
inline std::ostream &operator <<(std::ostream &stream, const Operation operation) {
|
|
stream << operation_name(operation);
|
|
return stream;
|
|
}
|
|
|
|
/*!
|
|
@returns The name of @c addressing_mode.
|
|
*/
|
|
inline constexpr const char *addressing_mode_name(const AddressingMode addressing_mode) {
|
|
switch(addressing_mode) {
|
|
default: break;
|
|
case AddressingMode::Implied: return "";
|
|
case AddressingMode::Accumulator: return "A";
|
|
case AddressingMode::Immediate: return "#";
|
|
case AddressingMode::Absolute: return "abs";
|
|
case AddressingMode::AbsoluteX: return "abs, x";
|
|
case AddressingMode::AbsoluteY: return "abs, y";
|
|
case AddressingMode::ZeroPage: return "zp";
|
|
case AddressingMode::ZeroPageX: return "zp, x";
|
|
case AddressingMode::ZeroPageY: return "zp, y";
|
|
case AddressingMode::XIndirect: return "((zp, x))";
|
|
case AddressingMode::IndirectY: return "((zp), y)";
|
|
case AddressingMode::Relative: return "rel";
|
|
case AddressingMode::AbsoluteIndirect: return "(abs)";
|
|
case AddressingMode::ZeroPageIndirect: return "(zp)";
|
|
case AddressingMode::SpecialPage: return "\\sp";
|
|
case AddressingMode::ImmediateZeroPage: return "#, zp";
|
|
case AddressingMode::AccumulatorRelative: return "A, rel";
|
|
case AddressingMode::ZeroPageRelative: return "zp, rel";
|
|
}
|
|
|
|
return "???";
|
|
}
|
|
|
|
inline std::ostream &operator <<(std::ostream &stream, const AddressingMode mode) {
|
|
stream << addressing_mode_name(mode);
|
|
return stream;
|
|
}
|
|
|
|
/*!
|
|
@returns The way that the address for an operation with @c addressing_mode and encoded starting from @c operation
|
|
would appear in an assembler. E.g. '$5a' for that zero page address, or '$5a, x' for zero-page indexed from $5a. This function
|
|
may access up to three bytes from @c operation onwards.
|
|
*/
|
|
inline std::string address(
|
|
const AddressingMode addressing_mode,
|
|
const uint8_t *operation,
|
|
const uint16_t program_counter
|
|
) {
|
|
std::stringstream output;
|
|
output << std::hex;
|
|
|
|
#define NUM(x) std::setfill('0') << std::setw(2) << int(x)
|
|
#define NUM4(x) std::setfill('0') << std::setw(4) << int(x)
|
|
switch(addressing_mode) {
|
|
default: return "???";
|
|
case AddressingMode::Implied: return "";
|
|
case AddressingMode::Accumulator: return "A ";
|
|
case AddressingMode::Immediate: output << "#$" << NUM(operation[1]); break;
|
|
case AddressingMode::Absolute: output << "$" << NUM(operation[2]) << NUM(operation[1]); break;
|
|
case AddressingMode::AbsoluteX: output << "$" << NUM(operation[2]) << NUM(operation[1]) << ", x"; break;
|
|
case AddressingMode::AbsoluteY: output << "$" << NUM(operation[2]) << NUM(operation[1]) << ", y"; break;
|
|
case AddressingMode::ZeroPage: output << "$" << NUM(operation[1]); break;
|
|
case AddressingMode::ZeroPageX: output << "$" << NUM(operation[1]) << ", x"; break;
|
|
case AddressingMode::ZeroPageY: output << "$" << NUM(operation[1]) << ", y"; break;
|
|
case AddressingMode::XIndirect: output << "(($" << NUM(operation[1]) << ", x))"; break;
|
|
case AddressingMode::IndirectY: output << "(($" << NUM(operation[1]) << "), y)"; break;
|
|
case AddressingMode::Relative: output << "#$" << NUM4(2 + program_counter + int8_t(operation[1])); break;
|
|
case AddressingMode::AbsoluteIndirect: output << "($" << NUM(operation[2]) << NUM(operation[1]) << ") "; break;
|
|
case AddressingMode::ZeroPageIndirect: output << "($" << NUM(operation[1]) << ")"; break;
|
|
case AddressingMode::SpecialPage: output << "$1f" << NUM(operation[1]); break;
|
|
case AddressingMode::ImmediateZeroPage: output << "#$" << NUM(operation[1]) << ", $" << NUM(operation[2]); break;
|
|
case AddressingMode::AccumulatorRelative: output << "A, $" << NUM4(2 + program_counter + int8_t(operation[1])); break;
|
|
case AddressingMode::ZeroPageRelative:
|
|
output << "$" << NUM(operation[1]) << ", $" << NUM4(3 + program_counter + int8_t(operation[2]));
|
|
break;
|
|
}
|
|
#undef NUM4
|
|
#undef NUM
|
|
|
|
return output.str();
|
|
}
|
|
|
|
/*!
|
|
Models a complete M50740-style instruction, including its operation, addressing mode and opcode.
|
|
*/
|
|
struct Instruction {
|
|
Operation operation = Operation::Invalid;
|
|
AddressingMode addressing_mode = AddressingMode::Implied;
|
|
uint8_t opcode = 0;
|
|
|
|
Instruction(const Operation operation, const AddressingMode addressing_mode, const uint8_t opcode) :
|
|
operation(operation), addressing_mode(addressing_mode), opcode(opcode) {}
|
|
Instruction(uint8_t opcode) : opcode(opcode) {}
|
|
Instruction() = default;
|
|
};
|
|
|
|
/*!
|
|
Outputs a description of @c instruction to @c stream.
|
|
*/
|
|
inline std::ostream &operator <<(std::ostream &stream, const Instruction &instruction) {
|
|
stream << operation_name(instruction.operation) << " " << addressing_mode_name(instruction.addressing_mode);
|
|
return stream;
|
|
}
|
|
|
|
}
|