mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
853 lines
26 KiB
C++
853 lines
26 KiB
C++
//
|
||
// Executor.hpp
|
||
// Clock Signal
|
||
//
|
||
// Created by Thomas Harte on 16/1/21.
|
||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||
//
|
||
|
||
#include "Executor.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <cassert>
|
||
#include <cstring>
|
||
|
||
#include "../../Machines/Utility/MemoryFuzzer.hpp"
|
||
|
||
#define LOG_PREFIX "[M50740] "
|
||
#include "../../Outputs/Log.hpp"
|
||
|
||
using namespace InstructionSet::M50740;
|
||
|
||
namespace {
|
||
constexpr int port_remap[] = {0, 1, 2, 0, 3};
|
||
}
|
||
|
||
Executor::Executor(PortHandler &port_handler) : port_handler_(port_handler) {
|
||
// Cut down the list of all generated performers to those the processor actually uses, and install that
|
||
// for future referencing by action_for.
|
||
Decoder decoder;
|
||
for(size_t c = 0; c < 256; c++) {
|
||
const auto instruction = decoder.instrucion_for_opcode(uint8_t(c));
|
||
|
||
// Treat invalid as NOP, because I've got to do _something_.
|
||
if(instruction.operation == Operation::Invalid) {
|
||
performers_[c] = performer_lookup_.performer(Operation::NOP, instruction.addressing_mode);
|
||
} else {
|
||
performers_[c] = performer_lookup_.performer(instruction.operation, instruction.addressing_mode);
|
||
}
|
||
}
|
||
|
||
// Fuzz RAM; then set anything that may be replaced by ROM to FF.
|
||
Memory::Fuzz(memory_);
|
||
memset(&memory_[0x100], 0xff, memory_.size() - 0x100);
|
||
}
|
||
|
||
void Executor::set_rom(const std::vector<uint8_t> &rom) {
|
||
// Copy into place, and reset.
|
||
const auto length = std::min(size_t(0x1000), rom.size());
|
||
memcpy(&memory_[0x2000 - length], rom.data(), length);
|
||
reset();
|
||
}
|
||
|
||
void Executor::run_for(Cycles cycles) {
|
||
// The incoming clock is divided by four; the local cycles_ count
|
||
// ensures that fractional parts are kept track of.
|
||
cycles_ += cycles;
|
||
CachingExecutor::run_for(cycles_.divide(Cycles(4)).as<int>());
|
||
}
|
||
|
||
void Executor::reset() {
|
||
// Just jump to the reset vector.
|
||
set_program_counter(uint16_t(memory_[0x1ffe] | (memory_[0x1fff] << 8)));
|
||
}
|
||
|
||
void Executor::set_interrupt_line(bool line) {
|
||
// Super hack: interrupt now, if permitted. Otherwise do nothing.
|
||
// So this will fail to catch enabling of interrupts while the line
|
||
// is active, amongst other things.
|
||
if(interrupt_line_ != line) {
|
||
interrupt_line_ = line;
|
||
if(line) {
|
||
set_interrupt_request(interrupt_control_, 0x02, 0x1ff4);
|
||
}
|
||
}
|
||
}
|
||
|
||
uint8_t Executor::read(uint16_t address) {
|
||
address &= 0x1fff;
|
||
|
||
// Deal with RAM and ROM accesses quickly.
|
||
if(address < 0x60 || address >= 0x100) return memory_[address];
|
||
|
||
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
|
||
switch(address) {
|
||
default:
|
||
LOG("Unrecognised read from " << PADHEX(4) << address);
|
||
return 0xff;
|
||
|
||
// "Port R"; sixteen four-bit ports
|
||
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
||
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
||
LOG("Unimplemented Port R read from " << PADHEX(4) << address);
|
||
return 0x00;
|
||
|
||
// Ports P0–P3.
|
||
case 0xe0: case 0xe2:
|
||
case 0xe4: case 0xe8: {
|
||
const int port = port_remap[(address - 0xe0) >> 1];
|
||
const uint8_t input = port_handler_.get_port_input(port);
|
||
|
||
// In the direction registers, a 0 indicates input, a 1 indicates output.
|
||
return (input &~ port_directions_[port]) | (port_outputs_[port] & port_directions_[port]);
|
||
}
|
||
|
||
case 0xe1: case 0xe3:
|
||
case 0xe5: case 0xe9:
|
||
return port_directions_[port_remap[(address - 0xe0) >> 1]];
|
||
|
||
// Timers.
|
||
case 0xf9: return prescalers_[0].value;
|
||
case 0xfa: return timers_[0].value;
|
||
case 0xfb: return timers_[1].value;
|
||
case 0xfc: return prescalers_[1].value;
|
||
case 0xfd: return timers_[2].value;
|
||
|
||
case 0xfe: return interrupt_control_;
|
||
case 0xff: return timer_control_;
|
||
}
|
||
}
|
||
|
||
void Executor::set_port_output(int port) {
|
||
// Force 'output' to a 1 anywhere that a bit is set as input.
|
||
port_handler_.set_port_output(port, port_outputs_[port] | ~port_directions_[port]);
|
||
}
|
||
|
||
void Executor::write(uint16_t address, uint8_t value) {
|
||
address &= 0x1fff;
|
||
|
||
// RAM writes are easy.
|
||
if(address < 0x60) {
|
||
memory_[address] = value;
|
||
return;
|
||
}
|
||
|
||
// ROM 'writes' are almost as easy (albeit unexpected).
|
||
if(address >= 0x100) {
|
||
LOG("Attempted ROM write of " << PADHEX(2) << value << " to " << PADHEX(4) << address);
|
||
return;
|
||
}
|
||
|
||
// Push time to the port handler.
|
||
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
|
||
|
||
switch(address) {
|
||
default:
|
||
LOG("Unrecognised write of " << PADHEX(2) << value << " to " << PADHEX(4) << address);
|
||
break;
|
||
|
||
// "Port R"; sixteen four-bit ports
|
||
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
||
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
||
LOG("Unimplemented Port R write of " << PADHEX(2) << value << " from " << PADHEX(4) << address);
|
||
break;
|
||
|
||
// Ports P0–P3.
|
||
case 0xe0: case 0xe2:
|
||
case 0xe4: case 0xe8: {
|
||
const int port = port_remap[(address - 0xe0) >> 1];
|
||
port_outputs_[port] = value;
|
||
set_port_output(port);
|
||
}
|
||
break;
|
||
|
||
case 0xe1: case 0xe3:
|
||
case 0xe5: case 0xe9: {
|
||
const int port = port_remap[(address - 0xe0) >> 1];
|
||
port_directions_[port] = value;
|
||
set_port_output(port);
|
||
} break;
|
||
|
||
// Timers.
|
||
//
|
||
// Reloading of value with the reload value is a guess, based upon what I take
|
||
// to be the intended usage of timer 2 in handling key repeat on the Apple IIgs.
|
||
case 0xf9: prescalers_[0].value = prescalers_[0].reload_value = value; break;
|
||
case 0xfa: timers_[0].value = timers_[0].reload_value = value; break;
|
||
case 0xfb: timers_[1].value = timers_[1].reload_value = value; break;
|
||
case 0xfc: prescalers_[1].value = prescalers_[1].reload_value = value; break;
|
||
case 0xfd: timers_[2].value = timers_[2].reload_value = value; break;
|
||
|
||
case 0xfe: interrupt_control_ = value; break;
|
||
case 0xff: timer_control_ = value; break;
|
||
}
|
||
}
|
||
|
||
void Executor::push(uint8_t value) {
|
||
write(s_, value);
|
||
--s_;
|
||
}
|
||
|
||
uint8_t Executor::pull() {
|
||
++s_;
|
||
return read(s_);
|
||
}
|
||
|
||
void Executor::set_flags(uint8_t flags) {
|
||
negative_result_ = flags;
|
||
overflow_result_ = uint8_t(flags << 1);
|
||
index_mode_ = flags & 0x20;
|
||
decimal_mode_ = flags & 0x08;
|
||
interrupt_disable_ = flags & 0x04;
|
||
zero_result_ = !(flags & 0x02);
|
||
carry_flag_ = flags & 0x01;
|
||
}
|
||
|
||
uint8_t Executor::flags() {
|
||
return
|
||
(negative_result_ & 0x80) |
|
||
((overflow_result_ & 0x80) >> 1) |
|
||
(index_mode_ ? 0x20 : 0x00) |
|
||
(decimal_mode_ ? 0x08 : 0x00) |
|
||
interrupt_disable_ |
|
||
(zero_result_ ? 0x00 : 0x02) |
|
||
carry_flag_;
|
||
}
|
||
|
||
template<bool is_brk> inline void Executor::perform_interrupt(uint16_t vector) {
|
||
// BRK has an unused operand.
|
||
++program_counter_;
|
||
push(uint8_t(program_counter_ >> 8));
|
||
push(uint8_t(program_counter_ & 0xff));
|
||
push(flags() | (is_brk ? 0x10 : 0x00));
|
||
set_program_counter(uint16_t(memory_[vector] | (memory_[vector+1] << 8)));
|
||
}
|
||
|
||
void Executor::set_interrupt_request(uint8_t ®, uint8_t value, uint16_t vector) {
|
||
// TODO: this allows interrupts through only if fully enabled at the time they
|
||
// signal. Which isn't quite correct, albeit that it seems sufficient for the
|
||
// IIgs ADB controller.
|
||
reg |= value;
|
||
if(!interrupt_disable_ && (reg & (value >> 1))) {
|
||
perform_interrupt<false>(vector);
|
||
}
|
||
}
|
||
|
||
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
|
||
// Post cycle cost; this emulation _does not provide accurate timing_.
|
||
#define TLength(mode, base) case AddressingMode::mode: subtract_duration(base + t_lengths[index_mode_]); break;
|
||
#define Length(mode, base) case AddressingMode::mode: subtract_duration(base); break;
|
||
|
||
switch(operation) {
|
||
case Operation::ADC: case Operation::AND: case Operation::CMP: case Operation::EOR:
|
||
case Operation::LDA: case Operation::ORA: case Operation::SBC:
|
||
{
|
||
constexpr int t_lengths[] = {
|
||
0,
|
||
operation == Operation::LDA ? 2 : (operation == Operation::CMP ? 1 : 3)
|
||
};
|
||
switch(addressing_mode) {
|
||
TLength(XIndirect, 6);
|
||
TLength(ZeroPage, 3);
|
||
TLength(Immediate, 2);
|
||
TLength(Absolute, 4);
|
||
TLength(IndirectY, 6);
|
||
TLength(ZeroPageX, 4);
|
||
TLength(AbsoluteY, 5);
|
||
TLength(AbsoluteX, 5);
|
||
default: assert(false);
|
||
}
|
||
} break;
|
||
|
||
case Operation::ASL: case Operation::DEC: case Operation::INC: case Operation::LSR:
|
||
case Operation::ROL: case Operation::ROR:
|
||
switch(addressing_mode) {
|
||
Length(ZeroPage, 5);
|
||
Length(Accumulator, 2);
|
||
Length(Absolute, 6);
|
||
Length(ZeroPageX, 6);
|
||
Length(AbsoluteX, 7);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
|
||
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
|
||
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7:
|
||
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
|
||
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7:
|
||
switch(addressing_mode) {
|
||
Length(AccumulatorRelative, 4);
|
||
Length(ZeroPageRelative, 5);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
case Operation::BPL: case Operation::BMI: case Operation::BEQ: case Operation::BNE:
|
||
case Operation::BCS: case Operation::BCC: case Operation::BVS: case Operation::BVC:
|
||
case Operation::INX: case Operation::INY:
|
||
subtract_duration(2);
|
||
break;
|
||
|
||
case Operation::CPX: case Operation::CPY: case Operation::BIT: case Operation::LDX:
|
||
case Operation::LDY:
|
||
switch(addressing_mode) {
|
||
Length(Immediate, 2);
|
||
Length(ZeroPage, 3);
|
||
Length(Absolute, 4);
|
||
Length(ZeroPageX, 4);
|
||
Length(ZeroPageY, 4);
|
||
Length(AbsoluteX, 5);
|
||
Length(AbsoluteY, 5);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
|
||
case Operation::BRA: subtract_duration(4); break;
|
||
case Operation::BRK: subtract_duration(7); break;
|
||
|
||
case Operation::CLB0: case Operation::CLB1: case Operation::CLB2: case Operation::CLB3:
|
||
case Operation::CLB4: case Operation::CLB5: case Operation::CLB6: case Operation::CLB7:
|
||
case Operation::SEB0: case Operation::SEB1: case Operation::SEB2: case Operation::SEB3:
|
||
case Operation::SEB4: case Operation::SEB5: case Operation::SEB6: case Operation::SEB7:
|
||
switch(addressing_mode) {
|
||
Length(Accumulator, 2);
|
||
Length(ZeroPage, 5);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
|
||
case Operation::CLC: case Operation::CLD: case Operation::CLT: case Operation::CLV:
|
||
case Operation::CLI:
|
||
case Operation::DEX: case Operation::DEY: case Operation::FST: case Operation::NOP:
|
||
case Operation::SEC: case Operation::SED: case Operation::SEI: case Operation::SET:
|
||
case Operation::SLW: case Operation::STP: case Operation::TAX: case Operation::TAY:
|
||
case Operation::TSX: case Operation::TXA: case Operation::TXS: case Operation::TYA:
|
||
subtract_duration(2);
|
||
break;
|
||
|
||
case Operation::COM: subtract_duration(5); break;
|
||
|
||
case Operation::JMP:
|
||
switch(addressing_mode) {
|
||
Length(Absolute, 3);
|
||
Length(AbsoluteIndirect, 5);
|
||
Length(ZeroPageIndirect, 4);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
|
||
case Operation::JSR:
|
||
switch(addressing_mode) {
|
||
Length(ZeroPageIndirect, 7);
|
||
Length(Absolute, 6);
|
||
Length(SpecialPage, 5);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
|
||
case Operation::LDM: subtract_duration(4); break;
|
||
|
||
case Operation::PHA: case Operation::PHP: case Operation::TST:
|
||
subtract_duration(3);
|
||
break;
|
||
|
||
case Operation::PLA: case Operation::PLP:
|
||
subtract_duration(4);
|
||
break;
|
||
|
||
case Operation::RRF: subtract_duration(8); break;
|
||
case Operation::RTI: subtract_duration(6); break;
|
||
case Operation::RTS: subtract_duration(6); break;
|
||
|
||
case Operation::STA: case Operation::STX: case Operation::STY:
|
||
switch(addressing_mode) {
|
||
Length(XIndirect, 7);
|
||
Length(ZeroPage, 4);
|
||
Length(Absolute, 5);
|
||
Length(IndirectY, 7);
|
||
Length(ZeroPageX, 5);
|
||
Length(ZeroPageY, 5);
|
||
Length(AbsoluteY, 6);
|
||
Length(AbsoluteX, 6);
|
||
default: assert(false);
|
||
}
|
||
break;
|
||
|
||
default: assert(false);
|
||
}
|
||
|
||
// Deal with all modes that don't access memory up here;
|
||
// those that access memory will go through a slightly longer
|
||
// sequence below that wraps the address and checks whether
|
||
// a write is valid [if required].
|
||
|
||
unsigned int address;
|
||
#define next8() memory_[(program_counter_ + 1) & 0x1fff]
|
||
#define next16() uint16_t(memory_[(program_counter_ + 1) & 0x1fff] | (memory_[(program_counter_ + 2) & 0x1fff] << 8))
|
||
|
||
// Underlying assumption below: the instruction stream will never
|
||
// overlap with IO ports.
|
||
switch(addressing_mode) {
|
||
|
||
// Addressing modes with no further memory access.
|
||
|
||
case AddressingMode::Implied:
|
||
perform<operation>(nullptr);
|
||
++program_counter_;
|
||
return;
|
||
|
||
case AddressingMode::Accumulator:
|
||
perform<operation>(&a_);
|
||
++program_counter_;
|
||
return;
|
||
|
||
case AddressingMode::Immediate:
|
||
perform<operation>(&next8());
|
||
program_counter_ += 2;
|
||
return;
|
||
|
||
// Special-purpose addressing modes.
|
||
|
||
case AddressingMode::Relative:
|
||
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(next8()));
|
||
break;
|
||
|
||
case AddressingMode::SpecialPage: address = 0x1f00 | next8(); break;
|
||
|
||
case AddressingMode::ImmediateZeroPage:
|
||
// LDM only...
|
||
write(memory_[(program_counter_+2)&0x1fff], memory_[(program_counter_+1)&0x1fff]);
|
||
program_counter_ += 1 + size(addressing_mode);
|
||
return;
|
||
|
||
case AddressingMode::AccumulatorRelative:
|
||
case AddressingMode::ZeroPageRelative: {
|
||
// Order of bytes is: (i) zero page address; (ii) relative jump.
|
||
uint8_t value;
|
||
if constexpr (addressing_mode == AddressingMode::AccumulatorRelative) {
|
||
value = a_;
|
||
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(next8()));
|
||
} else {
|
||
value = read(next8());
|
||
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(memory_[(program_counter_+2)&0x1fff]));
|
||
}
|
||
program_counter_ += 1 + size(addressing_mode);
|
||
switch(operation) {
|
||
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
|
||
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7: {
|
||
if constexpr (operation >= Operation::BBS0 && operation <= Operation::BBS7) {
|
||
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBS0));
|
||
if(value & mask) {
|
||
set_program_counter(uint16_t(address));
|
||
subtract_duration(2);
|
||
}
|
||
}
|
||
} return;
|
||
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
|
||
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7: {
|
||
if constexpr (operation >= Operation::BBC0 && operation <= Operation::BBC7) {
|
||
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBC0));
|
||
if(!(value & mask)) {
|
||
set_program_counter(uint16_t(address));
|
||
subtract_duration(2);
|
||
}
|
||
}
|
||
} return;
|
||
default: assert(false);
|
||
}
|
||
} break;
|
||
|
||
// Addressing modes with a memory access.
|
||
|
||
case AddressingMode::Absolute: address = next16(); break;
|
||
case AddressingMode::AbsoluteX: address = next16() + x_; break;
|
||
case AddressingMode::AbsoluteY: address = next16() + y_; break;
|
||
case AddressingMode::ZeroPage: address = next8(); break;
|
||
case AddressingMode::ZeroPageX: address = (next8() + x_) & 0xff; break;
|
||
case AddressingMode::ZeroPageY: address = (next8() + y_) & 0xff; break;
|
||
|
||
case AddressingMode::ZeroPageIndirect:
|
||
address = next8();
|
||
address = unsigned(memory_[address] | (memory_[(address + 1) & 0xff] << 8));
|
||
break;
|
||
|
||
case AddressingMode::XIndirect:
|
||
address = (next8() + x_) & 0xff;
|
||
address = unsigned(memory_[address] | (memory_[(address + 1)&0xff] << 8));
|
||
break;
|
||
|
||
case AddressingMode::IndirectY:
|
||
address = unsigned((memory_[next8()] | (memory_[(next8()+1)&0xff] << 8)) + y_);
|
||
break;
|
||
|
||
case AddressingMode::AbsoluteIndirect:
|
||
address = next16();
|
||
address = unsigned(memory_[address & 0x1fff] | (memory_[(address + 1) & 0x1fff] << 8));
|
||
break;
|
||
|
||
default:
|
||
assert(false);
|
||
}
|
||
|
||
#undef next16
|
||
#undef next8
|
||
program_counter_ += 1 + size(addressing_mode);
|
||
|
||
// Check for a branch; those don't go through the memory accesses below.
|
||
switch(operation) {
|
||
case Operation::BRA: case Operation::JMP:
|
||
set_program_counter(uint16_t(address));
|
||
return;
|
||
|
||
case Operation::JSR: {
|
||
// Push one less than the actual return address.
|
||
const auto return_address = program_counter_ - 1;
|
||
push(uint8_t(return_address >> 8));
|
||
push(uint8_t(return_address & 0xff));
|
||
set_program_counter(uint16_t(address));
|
||
} return;
|
||
|
||
#define Bcc(c) if(c) { set_program_counter(uint16_t(address)); subtract_duration(2); } return
|
||
case Operation::BPL: Bcc(!(negative_result_&0x80));
|
||
case Operation::BMI: Bcc(negative_result_&0x80);
|
||
case Operation::BEQ: Bcc(!zero_result_);
|
||
case Operation::BNE: Bcc(zero_result_);
|
||
case Operation::BCS: Bcc(carry_flag_);
|
||
case Operation::BCC: Bcc(!carry_flag_);
|
||
case Operation::BVS: Bcc(overflow_result_ & 0x80);
|
||
case Operation::BVC: Bcc(!(overflow_result_ & 0x80));
|
||
#undef Bcc
|
||
|
||
default: break;
|
||
}
|
||
|
||
assert(access_type(operation) != AccessType::None);
|
||
|
||
if constexpr(access_type(operation) == AccessType::Read) {
|
||
uint8_t source = read(uint16_t(address));
|
||
perform<operation>(&source);
|
||
return;
|
||
}
|
||
|
||
uint8_t value;
|
||
if constexpr(access_type(operation) == AccessType::ReadModifyWrite) {
|
||
value = read(uint16_t(address));
|
||
} else {
|
||
value = 0xff;
|
||
}
|
||
perform<operation>(&value);
|
||
write(uint16_t(address), value);
|
||
}
|
||
|
||
template <Operation operation> void Executor::perform(uint8_t *operand [[maybe_unused]]) {
|
||
|
||
#define set_nz(a) negative_result_ = zero_result_ = (a)
|
||
switch(operation) {
|
||
case Operation::LDA:
|
||
if(index_mode_) {
|
||
write(x_, *operand);
|
||
set_nz(*operand);
|
||
} else {
|
||
set_nz(a_ = *operand);
|
||
}
|
||
break;
|
||
case Operation::LDX: set_nz(x_ = *operand); break;
|
||
case Operation::LDY: set_nz(y_ = *operand); break;
|
||
|
||
case Operation::STA: *operand = a_; break;
|
||
case Operation::STX: *operand = x_; break;
|
||
case Operation::STY: *operand = y_; break;
|
||
|
||
case Operation::TXA: set_nz(a_ = x_); break;
|
||
case Operation::TYA: set_nz(a_ = y_); break;
|
||
case Operation::TXS: s_ = x_; break;
|
||
case Operation::TAX: set_nz(x_ = a_); break;
|
||
case Operation::TAY: set_nz(y_ = a_); break;
|
||
case Operation::TSX: set_nz(x_ = s_); break;
|
||
|
||
case Operation::SEB0: case Operation::SEB1: case Operation::SEB2: case Operation::SEB3:
|
||
case Operation::SEB4: case Operation::SEB5: case Operation::SEB6: case Operation::SEB7:
|
||
if constexpr(operation >= Operation::SEB0 && operation <= Operation::SEB7) {
|
||
*operand |= 1 << (int(operation) - int(Operation::SEB0));
|
||
}
|
||
break;
|
||
case Operation::CLB0: case Operation::CLB1: case Operation::CLB2: case Operation::CLB3:
|
||
case Operation::CLB4: case Operation::CLB5: case Operation::CLB6: case Operation::CLB7:
|
||
if constexpr(operation >= Operation::CLB0 && operation <= Operation::CLB7) {
|
||
*operand &= ~(1 << (int(operation) - int(Operation::CLB0)));
|
||
}
|
||
break;
|
||
|
||
case Operation::CLI: interrupt_disable_ = 0x00; break;
|
||
case Operation::SEI: interrupt_disable_ = 0x04; break;
|
||
case Operation::CLT: index_mode_ = false; break;
|
||
case Operation::SET: index_mode_ = true; break;
|
||
case Operation::CLD: decimal_mode_ = false; break;
|
||
case Operation::SED: decimal_mode_ = true; break;
|
||
case Operation::CLC: carry_flag_ = 0; break;
|
||
case Operation::SEC: carry_flag_ = 1; break;
|
||
case Operation::CLV: overflow_result_ = 0; break;
|
||
|
||
case Operation::DEX: --x_; set_nz(x_); break;
|
||
case Operation::INX: ++x_; set_nz(x_); break;
|
||
case Operation::DEY: --y_; set_nz(y_); break;
|
||
case Operation::INY: ++y_; set_nz(y_); break;
|
||
case Operation::DEC: --*operand; set_nz(*operand); break;
|
||
case Operation::INC: ++*operand; set_nz(*operand); break;
|
||
|
||
case Operation::RTS: {
|
||
uint16_t target = pull();
|
||
target |= pull() << 8;
|
||
set_program_counter(target+1);
|
||
--program_counter_; // To undo the unavoidable increment
|
||
// after exiting from here.
|
||
} break;
|
||
|
||
case Operation::RTI: {
|
||
set_flags(pull());
|
||
uint16_t target = pull();
|
||
target |= pull() << 8;
|
||
set_program_counter(target);
|
||
--program_counter_; // To undo the unavoidable increment
|
||
// after exiting from here.
|
||
} break;
|
||
|
||
case Operation::BRK:
|
||
perform_interrupt<true>(0x1ff4);
|
||
--program_counter_; // To undo the unavoidable increment
|
||
// after exiting from here.
|
||
break;
|
||
|
||
case Operation::STP: set_is_stopped(true); break;
|
||
|
||
case Operation::COM: set_nz(*operand ^= 0xff); break;
|
||
|
||
case Operation::FST: case Operation::SLW: case Operation::NOP:
|
||
// TODO: communicate FST and SLW onwards, I imagine. Find out what they interface with.
|
||
break;
|
||
|
||
case Operation::PHA: push(a_); break;
|
||
case Operation::PHP: push(flags()); break;
|
||
case Operation::PLA: set_nz(a_ = pull()); break;
|
||
case Operation::PLP: set_flags(pull()); break;
|
||
|
||
case Operation::ASL:
|
||
carry_flag_ = *operand >> 7;
|
||
*operand <<= 1;
|
||
set_nz(*operand);
|
||
break;
|
||
|
||
case Operation::LSR:
|
||
carry_flag_ = *operand & 1;
|
||
*operand >>= 1;
|
||
set_nz(*operand);
|
||
break;
|
||
|
||
case Operation::ROL: {
|
||
const uint8_t temp8 = uint8_t((*operand << 1) | carry_flag_);
|
||
carry_flag_ = *operand >> 7;
|
||
set_nz(*operand = temp8);
|
||
} break;
|
||
|
||
case Operation::ROR: {
|
||
const uint8_t temp8 = uint8_t((*operand >> 1) | (carry_flag_ << 7));
|
||
carry_flag_ = *operand & 1;
|
||
set_nz(*operand = temp8);
|
||
} break;
|
||
|
||
case Operation::RRF:
|
||
*operand = uint8_t((*operand >> 4) | (*operand << 4));
|
||
break;
|
||
|
||
case Operation::BIT:
|
||
zero_result_ = *operand & a_;
|
||
negative_result_ = *operand;
|
||
overflow_result_ = uint8_t(*operand << 1);
|
||
break;
|
||
|
||
case Operation::TST:
|
||
set_nz(*operand);
|
||
break;
|
||
|
||
/*
|
||
Operations affected by the index mode flag: ADC, AND, CMP, EOR, LDA, ORA, and SBC.
|
||
*/
|
||
|
||
#define index(op) \
|
||
if(index_mode_) { \
|
||
uint8_t t = read(x_); \
|
||
op(t); \
|
||
write(x_, t); \
|
||
} else { \
|
||
op(a_); \
|
||
}
|
||
|
||
#define op_ora(x) set_nz(x |= *operand)
|
||
#define op_and(x) set_nz(x &= *operand)
|
||
#define op_eor(x) set_nz(x ^= *operand)
|
||
case Operation::ORA: index(op_ora); break;
|
||
case Operation::AND: index(op_and); break;
|
||
case Operation::EOR: index(op_eor); break;
|
||
#undef op_eor
|
||
#undef op_and
|
||
#undef op_ora
|
||
#undef index
|
||
|
||
#define op_cmp(x) { \
|
||
const uint16_t temp16 = x - *operand; \
|
||
set_nz(uint8_t(temp16)); \
|
||
carry_flag_ = (~temp16 >> 8)&1; \
|
||
}
|
||
case Operation::CMP:
|
||
if(index_mode_) {
|
||
op_cmp(read(x_));
|
||
} else {
|
||
op_cmp(a_);
|
||
}
|
||
break;
|
||
case Operation::CPX: op_cmp(x_); break;
|
||
case Operation::CPY: op_cmp(y_); break;
|
||
#undef op_cmp
|
||
|
||
case Operation::SBC:
|
||
case Operation::ADC: {
|
||
const uint8_t a = index_mode_ ? read(x_) : a_;
|
||
|
||
if(decimal_mode_) {
|
||
if(operation == Operation::ADC) {
|
||
uint16_t partials = 0;
|
||
int result = carry_flag_;
|
||
|
||
#define nibble(mask, limit, adjustment, carry) \
|
||
result += (a & mask) + (*operand & mask); \
|
||
partials += result & mask; \
|
||
if(result >= limit) result = ((result + (adjustment)) & (carry - 1)) + carry;
|
||
|
||
nibble(0x000f, 0x000a, 0x0006, 0x00010);
|
||
nibble(0x00f0, 0x00a0, 0x0060, 0x00100);
|
||
|
||
#undef nibble
|
||
|
||
overflow_result_ = uint8_t((partials ^ a) & (partials ^ *operand));
|
||
set_nz(uint8_t(result));
|
||
carry_flag_ = (result >> 8) & 1;
|
||
} else {
|
||
unsigned int result = 0;
|
||
unsigned int borrow = carry_flag_ ^ 1;
|
||
const uint16_t decimal_result = uint16_t(a - *operand - borrow);
|
||
|
||
#define nibble(mask, adjustment, carry) \
|
||
result += (a & mask) - (*operand & mask) - borrow; \
|
||
if(result > mask) result -= adjustment; \
|
||
borrow = (result > mask) ? carry : 0; \
|
||
result &= (carry - 1);
|
||
|
||
nibble(0x000f, 0x0006, 0x00010);
|
||
nibble(0x00f0, 0x0060, 0x00100);
|
||
|
||
#undef nibble
|
||
|
||
overflow_result_ = uint8_t((decimal_result ^ a) & (~decimal_result ^ *operand));
|
||
set_nz(uint8_t(result));
|
||
carry_flag_ = ((borrow >> 8)&1)^1;
|
||
}
|
||
} else {
|
||
int result;
|
||
if(operation == Operation::ADC) {
|
||
result = int(a + *operand + carry_flag_);
|
||
overflow_result_ = uint8_t((result ^ a) & (result ^ *operand));
|
||
} else {
|
||
result = int(a + ~*operand + carry_flag_);
|
||
overflow_result_ = uint8_t((result ^ a) & (result ^ ~*operand));
|
||
}
|
||
set_nz(uint8_t(result));
|
||
carry_flag_ = (result >> 8) & 1;
|
||
}
|
||
|
||
if(index_mode_) {
|
||
write(x_, a);
|
||
} else {
|
||
a_ = a;
|
||
}
|
||
} break;
|
||
|
||
|
||
/*
|
||
Already removed from the instruction stream:
|
||
|
||
* all branches and jumps;
|
||
* LDM.
|
||
*/
|
||
|
||
default:
|
||
LOG("Unimplemented operation: " << operation);
|
||
assert(false);
|
||
}
|
||
#undef set_nz
|
||
}
|
||
|
||
inline void Executor::subtract_duration(int duration) {
|
||
// Pass along.
|
||
CachingExecutor::subtract_duration(duration);
|
||
|
||
// Update count for potential port accesses.
|
||
cycles_since_port_handler_ += Cycles(duration);
|
||
|
||
// Update timer 1 and 2 prescaler.
|
||
constexpr int t12_divider = 4; // A divide by 4 has already been applied before counting instruction lengths; therefore
|
||
// this additional divide by 4 produces the correct net divide by 16.
|
||
|
||
timer_divider_ += duration;
|
||
const int clock_ticks = timer_divider_ / t12_divider;
|
||
timer_divider_ &= (t12_divider-1);
|
||
|
||
// Update timers 1 and 2.
|
||
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
|
||
if(update_timer(timers_[0], t12_ticks)) {
|
||
set_interrupt_request(interrupt_control_, 0x20, 0x1ff8);
|
||
}
|
||
if(update_timer(timers_[1], t12_ticks)) {
|
||
set_interrupt_request(interrupt_control_, 0x08, 0x1ff6);
|
||
}
|
||
|
||
// If timer X is disabled, stop.
|
||
if(timer_control_&0x20) {
|
||
return;
|
||
}
|
||
|
||
// Update timer X prescaler.
|
||
switch(timer_control_ & 0x0c) {
|
||
default: {
|
||
const int tx_ticks = update_timer(prescalers_[1], clock_ticks);
|
||
if(update_timer(timers_[2], tx_ticks)) {
|
||
set_interrupt_request(timer_control_, 0x80, 0x1ffa);
|
||
}
|
||
} break;
|
||
case 0x04:
|
||
LOG("TODO: Timer X; Pulse output mode");
|
||
break;
|
||
case 0x08:
|
||
LOG("TODO: Timer X; Event counter mode");
|
||
break;
|
||
case 0x0c:
|
||
LOG("TODO: Timer X; Pulse width measurement mode");
|
||
break;
|
||
}
|
||
}
|
||
|
||
inline int Executor::update_timer(Timer &timer, int count) {
|
||
const int next_value = timer.value - count;
|
||
if(next_value < 0) {
|
||
// Determine how many reloads were required to get above zero.
|
||
const int reload_value = timer.reload_value ? timer.reload_value : 256;
|
||
const int underflow_count = 1 - next_value / reload_value;
|
||
timer.value = uint8_t((next_value % reload_value) + timer.reload_value);
|
||
return underflow_count;
|
||
}
|
||
timer.value = uint8_t(next_value);
|
||
return 0;
|
||
}
|
||
|
||
uint8_t Executor::get_output_mask(int port) {
|
||
return port_directions_[port];
|
||
}
|