mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
Merge pull request #1033 from TomHarte/68000Mk2
Implement a bus binding for the discrete 68000 decoder and performer.
This commit is contained in:
commit
fd66a9b396
@ -29,7 +29,7 @@ enum Exception {
|
||||
FormatError = 14,
|
||||
UninitialisedInterrupt = 15,
|
||||
SpuriousInterrupt = 24,
|
||||
InterruptAutovectorBase = 25,
|
||||
InterruptAutovectorBase = 25, // This is the vector for interrupt level _1_.
|
||||
TrapBase = 32,
|
||||
FPBranchOrSetOnUnorderedCondition = 48,
|
||||
FPInexactResult = 49,
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Perform.hpp"
|
||||
#include "RegisterSet.hpp"
|
||||
#include "Status.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
@ -80,17 +81,9 @@ template <Model model, typename BusHandler> class Executor {
|
||||
/// Sets the current input interrupt level.
|
||||
void set_interrupt_level(int);
|
||||
|
||||
// TODO: this will likely be shared in some capacity with the bus-accurate versions of the 680x0;
|
||||
// therefore it will almost certainly be factored out in future.
|
||||
struct Registers {
|
||||
uint32_t data[8], address[7];
|
||||
uint32_t user_stack_pointer;
|
||||
uint32_t supervisor_stack_pointer;
|
||||
uint16_t status;
|
||||
uint32_t program_counter;
|
||||
};
|
||||
Registers get_state();
|
||||
void set_state(const Registers &);
|
||||
// State for the executor is just the register set.
|
||||
RegisterSet get_state();
|
||||
void set_state(const RegisterSet &);
|
||||
|
||||
private:
|
||||
class State: public NullFlowController {
|
||||
|
@ -53,7 +53,7 @@ void Executor<model, BusHandler>::signal_bus_error(FunctionCode code, uint32_t a
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::set_interrupt_level(int level) {
|
||||
state_.interrupt_input_ = level;
|
||||
state_.stopped &= state_.interrupt_input_ <= state_.status.interrupt_level;
|
||||
state_.stopped &= !state_.status.would_accept_interrupt(level);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
@ -102,8 +102,8 @@ void Executor<model, BusHandler>::run_for_instructions(int count) {
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
typename Executor<model, BusHandler>::Registers Executor<model, BusHandler>::get_state() {
|
||||
Registers result;
|
||||
RegisterSet Executor<model, BusHandler>::get_state() {
|
||||
RegisterSet result;
|
||||
|
||||
for(int c = 0; c < 8; c++) {
|
||||
result.data[c] = Dn(c).l;
|
||||
@ -122,7 +122,7 @@ typename Executor<model, BusHandler>::Registers Executor<model, BusHandler>::get
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::set_state(const Registers &state) {
|
||||
void Executor<model, BusHandler>::set_state(const RegisterSet &state) {
|
||||
for(int c = 0; c < 8; c++) {
|
||||
Dn(c).l = state.data[c];
|
||||
}
|
||||
@ -205,8 +205,8 @@ uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement() {
|
||||
// also include the scale field even if not.
|
||||
const auto extension = read_pc<uint16_t>();
|
||||
const auto offset = int8_t(extension);
|
||||
const int register_index = (extension >> 12) & 7;
|
||||
const uint32_t displacement = registers[register_index + ((extension >> 12) & 0x08)].l;
|
||||
const int register_index = (extension >> 12) & 15;
|
||||
const uint32_t displacement = registers[register_index].l;
|
||||
const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement);
|
||||
return offset + sized_displacement;
|
||||
}
|
||||
@ -324,7 +324,7 @@ template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::run(int &count) {
|
||||
while(count--) {
|
||||
// Check for a new interrupt.
|
||||
if(interrupt_input > status.interrupt_level) {
|
||||
if(status.would_accept_interrupt(interrupt_input)) {
|
||||
const int vector = bus_handler_.acknowlege_interrupt(interrupt_input);
|
||||
if(vector >= 0) {
|
||||
raise_exception<false>(vector);
|
||||
@ -483,7 +483,7 @@ template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, program_counter.l);
|
||||
program_counter.l = instruction_address + offset;
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_operation) {
|
||||
template <Model model, Operation t_operation> constexpr uint8_t operand_flags(Operation r_operation) {
|
||||
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
|
||||
default:
|
||||
assert(false);
|
||||
@ -24,8 +24,6 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
|
||||
case Operation::PEA:
|
||||
case Operation::JMP: case Operation::JSR:
|
||||
case Operation::MOVEPw: case Operation::MOVEPl:
|
||||
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
|
||||
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
|
||||
case Operation::TAS:
|
||||
case Operation::RTR: case Operation::RTS: case Operation::RTE:
|
||||
return 0;
|
||||
@ -40,13 +38,14 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
|
||||
case Operation::Bccb: case Operation::Bccw: case Operation::Bccl:
|
||||
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
|
||||
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
|
||||
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
|
||||
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
|
||||
return FetchOp1;
|
||||
|
||||
//
|
||||
// Single-operand write.
|
||||
//
|
||||
case Operation::MOVEfromSR: case Operation::MOVEfromUSP:
|
||||
case Operation::Scc:
|
||||
case Operation::MOVEfromUSP:
|
||||
return StoreOp1;
|
||||
|
||||
//
|
||||
@ -63,11 +62,13 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
|
||||
case Operation::LSLm: case Operation::LSRm:
|
||||
case Operation::ROLm: case Operation::RORm:
|
||||
case Operation::ROXLm: case Operation::ROXRm:
|
||||
case Operation::Scc:
|
||||
return FetchOp1 | StoreOp1;
|
||||
|
||||
//
|
||||
// CLR, which is model-dependent.
|
||||
// CLR and MOVE SR, which are model-dependent.
|
||||
//
|
||||
case Operation::MOVEfromSR:
|
||||
case Operation::CLRb: case Operation::CLRw: case Operation::CLRl:
|
||||
if constexpr (model == Model::M68000) {
|
||||
return FetchOp1 | StoreOp1;
|
||||
|
@ -12,8 +12,9 @@
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
constexpr DataSize operand_size(Operation operation) {
|
||||
switch(operation) {
|
||||
template <Operation t_operation>
|
||||
constexpr DataSize operand_size(Operation r_operation) {
|
||||
switch((t_operation == Operation::Undefined) ? r_operation : t_operation) {
|
||||
// These are given a value arbitrarily, to
|
||||
// complete the switch statement.
|
||||
case Operation::Undefined:
|
||||
|
@ -228,7 +228,7 @@ template <
|
||||
|
||||
status.zero_result = dest.l & bit_mask;
|
||||
dest.l &= ~bit_mask;
|
||||
flow_controller.did_bit_op(bit_position);
|
||||
flow_controller.did_bit_op(int(bit_position));
|
||||
} break;
|
||||
|
||||
case Operation::BCHG: {
|
||||
@ -236,7 +236,7 @@ template <
|
||||
|
||||
status.zero_result = dest.l & bit_mask;
|
||||
dest.l ^= bit_mask;
|
||||
flow_controller.did_bit_op(bit_position);
|
||||
flow_controller.did_bit_op(int(bit_position));
|
||||
} break;
|
||||
|
||||
case Operation::BSET: {
|
||||
@ -244,7 +244,7 @@ template <
|
||||
|
||||
status.zero_result = dest.l & bit_mask;
|
||||
dest.l |= bit_mask;
|
||||
flow_controller.did_bit_op(bit_position);
|
||||
flow_controller.did_bit_op(int(bit_position));
|
||||
} break;
|
||||
|
||||
#undef get_mask
|
||||
@ -252,29 +252,29 @@ template <
|
||||
case Operation::Bccb:
|
||||
flow_controller.template complete_bcc<int8_t>(
|
||||
status.evaluate_condition(instruction.condition()),
|
||||
src.b);
|
||||
int8_t(src.b));
|
||||
break;
|
||||
|
||||
case Operation::Bccw:
|
||||
flow_controller.template complete_bcc<int16_t>(
|
||||
status.evaluate_condition(instruction.condition()),
|
||||
src.w);
|
||||
int16_t(src.w));
|
||||
break;
|
||||
|
||||
case Operation::Bccl:
|
||||
flow_controller.template complete_bcc<int32_t>(
|
||||
status.evaluate_condition(instruction.condition()),
|
||||
src.l);
|
||||
int32_t(src.l));
|
||||
break;
|
||||
|
||||
case Operation::BSRb:
|
||||
flow_controller.bsr(int8_t(src.b) + 2);
|
||||
flow_controller.bsr(uint32_t(int8_t(src.b)));
|
||||
break;
|
||||
case Operation::BSRw:
|
||||
flow_controller.bsr(int16_t(src.w) + 2);
|
||||
flow_controller.bsr(uint32_t(int16_t(src.w)));
|
||||
break;
|
||||
case Operation::BSRl:
|
||||
flow_controller.bsr(src.l + 2);
|
||||
flow_controller.bsr(src.l);
|
||||
break;
|
||||
|
||||
case Operation::DBcc: {
|
||||
@ -294,9 +294,11 @@ template <
|
||||
int16_t(dest.w));
|
||||
} break;
|
||||
|
||||
case Operation::Scc:
|
||||
src.b = status.evaluate_condition(instruction.condition()) ? 0xff : 0x00;
|
||||
break;
|
||||
case Operation::Scc: {
|
||||
const bool condition = status.evaluate_condition(instruction.condition());
|
||||
src.b = condition ? 0xff : 0x00;
|
||||
flow_controller.did_scc(condition);
|
||||
} break;
|
||||
|
||||
/*
|
||||
CLRs: store 0 to the destination, set the zero flag, and clear
|
||||
@ -520,17 +522,18 @@ template <
|
||||
#define DIV(Type16, Type32, flow_function) { \
|
||||
status.carry_flag = 0; \
|
||||
\
|
||||
if(!src.w) { \
|
||||
const auto dividend = Type32(dest.l); \
|
||||
const auto divisor = Type32(Type16(src.w)); \
|
||||
\
|
||||
if(!divisor) { \
|
||||
status.negative_flag = status.overflow_flag = 0; \
|
||||
status.zero_result = 1; \
|
||||
flow_controller.raise_exception(Exception::IntegerDivideByZero); \
|
||||
flow_controller.template flow_function<false>(dividend, divisor); \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
const auto dividend = Type32(dest.l); \
|
||||
const auto divisor = Type32(Type16(src.w)); \
|
||||
const auto quotient = dividend / divisor; \
|
||||
\
|
||||
const auto quotient = int64_t(dividend) / int64_t(divisor); \
|
||||
if(quotient != Type32(Type16(quotient))) { \
|
||||
status.overflow_flag = 1; \
|
||||
flow_controller.template flow_function<true>(dividend, divisor); \
|
||||
@ -553,7 +556,7 @@ template <
|
||||
|
||||
// TRAP, which is a nicer form of ILLEGAL.
|
||||
case Operation::TRAP:
|
||||
flow_controller.template raise_exception<false>(src.l + Exception::TrapBase);
|
||||
flow_controller.template raise_exception<false>(int(src.l + Exception::TrapBase));
|
||||
break;
|
||||
|
||||
case Operation::TRAPV: {
|
||||
@ -678,7 +681,7 @@ template <
|
||||
*/
|
||||
|
||||
case Operation::LINKw:
|
||||
flow_controller.link(instruction, int16_t(dest.w));
|
||||
flow_controller.link(instruction, uint32_t(int16_t(dest.w)));
|
||||
break;
|
||||
|
||||
case Operation::UNLINK:
|
||||
@ -825,14 +828,14 @@ template <
|
||||
set_neg_zero(v, m); \
|
||||
status.overflow_flag = (Status::FlagT(value) ^ status.zero_result) & Status::FlagT(m);
|
||||
|
||||
#define decode_shift_count() \
|
||||
#define decode_shift_count(type) \
|
||||
int shift_count = src.l & 63; \
|
||||
flow_controller.did_shift(shift_count);
|
||||
flow_controller.template did_shift<type>(shift_count);
|
||||
|
||||
#define set_flags_w(t) set_flags(src.w, 0x8000, t)
|
||||
|
||||
#define asl(destination, size) {\
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
const auto value = destination; \
|
||||
\
|
||||
if(!shift_count) { \
|
||||
@ -862,7 +865,7 @@ template <
|
||||
case Operation::ASLl: asl(dest.l, 32); break;
|
||||
|
||||
#define asr(destination, size) {\
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
const auto value = destination; \
|
||||
\
|
||||
if(!shift_count) { \
|
||||
@ -906,7 +909,7 @@ template <
|
||||
status.carry_flag = value & (t);
|
||||
|
||||
#define lsl(destination, size) {\
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
const auto value = destination; \
|
||||
\
|
||||
if(!shift_count) { \
|
||||
@ -930,7 +933,7 @@ template <
|
||||
case Operation::LSLl: lsl(dest.l, 32); break;
|
||||
|
||||
#define lsr(destination, size) {\
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
const auto value = destination; \
|
||||
\
|
||||
if(!shift_count) { \
|
||||
@ -954,7 +957,7 @@ template <
|
||||
case Operation::LSRl: lsr(dest.l, 32); break;
|
||||
|
||||
#define rol(destination, size) { \
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
const auto value = destination; \
|
||||
\
|
||||
if(!shift_count) { \
|
||||
@ -982,7 +985,7 @@ template <
|
||||
case Operation::ROLl: rol(dest.l, 32); break;
|
||||
|
||||
#define ror(destination, size) { \
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
const auto value = destination; \
|
||||
\
|
||||
if(!shift_count) { \
|
||||
@ -1010,7 +1013,7 @@ template <
|
||||
case Operation::RORl: ror(dest.l, 32); break;
|
||||
|
||||
#define roxl(destination, size) { \
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
\
|
||||
shift_count %= (size + 1); \
|
||||
uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \
|
||||
@ -1034,7 +1037,7 @@ template <
|
||||
case Operation::ROXLl: roxl(dest.l, 32); break;
|
||||
|
||||
#define roxr(destination, size) { \
|
||||
decode_shift_count(); \
|
||||
decode_shift_count(decltype(destination)); \
|
||||
\
|
||||
shift_count %= (size + 1); \
|
||||
uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \
|
||||
|
@ -61,7 +61,7 @@ std::string Preinstruction::to_string(int opcode) const {
|
||||
const char *instruction;
|
||||
|
||||
switch(operation) {
|
||||
case Operation::Undefined: instruction = "None"; break;
|
||||
case Operation::Undefined: return "None";
|
||||
case Operation::NOP: instruction = "NOP"; break;
|
||||
case Operation::ABCD: instruction = "ABCD"; break;
|
||||
case Operation::SBCD: instruction = "SBCD"; break;
|
||||
|
@ -135,7 +135,8 @@ enum class DataSize {
|
||||
/// 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.
|
||||
constexpr DataSize operand_size(Operation operation);
|
||||
template <Operation t_operation = Operation::Undefined>
|
||||
constexpr DataSize operand_size(Operation operation = Operation::Undefined);
|
||||
|
||||
template <Operation t_op = Operation::Undefined>
|
||||
constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) {
|
||||
@ -165,7 +166,7 @@ static constexpr uint8_t StoreOp2 = (1 << 3);
|
||||
Unusual bus sequences, such as TAS or MOVEM, are not described here.
|
||||
*/
|
||||
template <Model model, Operation t_operation = Operation::Undefined>
|
||||
uint8_t operand_flags(Operation r_operation = Operation::Undefined);
|
||||
constexpr uint8_t operand_flags(Operation r_operation = Operation::Undefined);
|
||||
|
||||
/// Lists the various condition codes used by the 680x0.
|
||||
enum class Condition {
|
||||
|
@ -32,8 +32,11 @@ struct NullFlowController {
|
||||
/// Indicates that a @c CHK was performed, along with whether the result @c was_under zero or @c was_over the source operand.
|
||||
void did_chk([[maybe_unused]] bool was_under, [[maybe_unused]] bool was_over) {}
|
||||
|
||||
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by.
|
||||
void did_shift([[maybe_unused]] int bit_count) {}
|
||||
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by
|
||||
/// and the type shifted.
|
||||
///
|
||||
/// @c IntT may be uint8_t, uint16_t or uint32_t.
|
||||
template <typename IntT> void did_shift([[maybe_unused]] int bit_count) {}
|
||||
|
||||
/// Indicates that a @c DIVU was performed, providing the @c dividend and @c divisor.
|
||||
/// If @c did_overflow is @c true then the divide ended in overflow.
|
||||
@ -46,6 +49,10 @@ struct NullFlowController {
|
||||
/// Indicates that a bit-manipulation operation (i.e. BTST, BCHG or BSET) was performed, affecting the bit at posiition @c bit_position.
|
||||
void did_bit_op([[maybe_unused]] int bit_position) {}
|
||||
|
||||
/// Indicates that an @c Scc was performed; if @c did_set_ff is true then the condition was true and FF
|
||||
/// written to the operand; otherwise 00 was written.
|
||||
void did_scc([[maybe_unused]] bool did_set_ff) {}
|
||||
|
||||
/// Provides a notification that the upper byte of the status register has been affected by the current instruction;
|
||||
/// this gives an opportunity to track the supervisor flag.
|
||||
void did_update_status() {}
|
||||
|
31
InstructionSets/M68k/RegisterSet.hpp
Normal file
31
InstructionSets/M68k/RegisterSet.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// RegisterSet.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_RegisterSet_h
|
||||
#define InstructionSets_M68k_RegisterSet_h
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
struct RegisterSet {
|
||||
uint32_t data[8], address[7];
|
||||
uint32_t user_stack_pointer;
|
||||
uint32_t supervisor_stack_pointer;
|
||||
uint16_t status;
|
||||
uint32_t program_counter;
|
||||
|
||||
/// @returns The active stack pointer, whichever it may be.
|
||||
uint32_t stack_pointer() const {
|
||||
return (status & 0x2000) ? supervisor_stack_pointer : user_stack_pointer;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_RegisterSet_h */
|
@ -22,6 +22,8 @@ static constexpr uint16_t Zero = 1 << 2;
|
||||
static constexpr uint16_t Negative = 1 << 3;
|
||||
static constexpr uint16_t Extend = 1 << 4;
|
||||
|
||||
static constexpr uint16_t AllConditions = Carry | Overflow | Zero | Negative | Extend;
|
||||
|
||||
static constexpr uint16_t Supervisor = 1 << 13;
|
||||
static constexpr uint16_t Trace = 1 << 15;
|
||||
|
||||
@ -93,8 +95,25 @@ struct Status {
|
||||
return is_supervisor;
|
||||
}
|
||||
|
||||
/// Adjusts the status for exception processing — sets supervisor mode, disables trace,
|
||||
/// and if @c new_interrupt_level is greater than or equal to 0 sets that as the new
|
||||
/// interrupt level.
|
||||
///
|
||||
/// @returns The status prior to those changes.
|
||||
uint16_t begin_exception(int new_interrupt_level = -1) {
|
||||
const uint16_t initial_status = status();
|
||||
|
||||
if(new_interrupt_level >= 0) {
|
||||
interrupt_level = new_interrupt_level;
|
||||
}
|
||||
is_supervisor = true;
|
||||
trace_flag = 0;
|
||||
|
||||
return initial_status;
|
||||
}
|
||||
|
||||
/// Evaluates @c condition.
|
||||
bool evaluate_condition(Condition condition) {
|
||||
constexpr bool evaluate_condition(Condition condition) const {
|
||||
switch(condition) {
|
||||
default:
|
||||
case Condition::True: return true;
|
||||
@ -119,6 +138,13 @@ struct Status {
|
||||
return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns @c true if an interrupt at level @c level should be accepted; @c false otherwise.
|
||||
constexpr bool would_accept_interrupt(int level) const {
|
||||
// TODO: is level seven really non-maskable? If so then what mechanism prevents
|
||||
// rapid stack overflow upon a level-seven interrupt?
|
||||
return level > interrupt_level;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
|
||||
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
|
||||
|
||||
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
|
||||
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
|
||||
|
@ -72,6 +72,14 @@ template <> union SlicedInt<uint32_t> {
|
||||
#endif
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
struct {
|
||||
#if TARGET_RT_BIG_ENDIAN
|
||||
SlicedInt<uint16_t> high, low;
|
||||
#else
|
||||
SlicedInt<uint16_t> low, high;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
using SlicedInt16 = SlicedInt<uint16_t>;
|
||||
|
@ -1680,6 +1680,7 @@
|
||||
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BA3AE44283317CB00328FED /* RegisterSet.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSet.hpp; sourceTree = "<group>"; };
|
||||
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
|
||||
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
|
||||
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MasterSystemVDPTests.mm; sourceTree = "<group>"; };
|
||||
@ -2065,6 +2066,9 @@
|
||||
4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = "<group>"; };
|
||||
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
|
||||
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502InterruptTests.swift; sourceTree = "<group>"; };
|
||||
4BCA2F562832A643006C632A /* 68000Mk2.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Mk2.hpp; sourceTree = "<group>"; };
|
||||
4BCA2F592832A807006C632A /* 68000Mk2Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Mk2Storage.hpp; sourceTree = "<group>"; };
|
||||
4BCA2F5A2832A81C006C632A /* 68000Mk2Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 68000Mk2Implementation.hpp; sourceTree = "<group>"; };
|
||||
4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreROM.cpp; path = Encodings/CommodoreROM.cpp; sourceTree = "<group>"; };
|
||||
4BCA6CC71D9DD9F000C2D7B2 /* CommodoreROM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreROM.hpp; path = Encodings/CommodoreROM.hpp; sourceTree = "<group>"; };
|
||||
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
|
||||
@ -3218,6 +3222,7 @@
|
||||
4B79629C2819681F008130F9 /* Instruction.hpp */,
|
||||
4B79629D2819681F008130F9 /* Model.hpp */,
|
||||
4BB5B996281B1E3F00522DA9 /* Perform.hpp */,
|
||||
4BA3AE44283317CB00328FED /* RegisterSet.hpp */,
|
||||
4BB5B997281B1F7B00522DA9 /* Status.hpp */,
|
||||
4BB5B999281B244400522DA9 /* Implementation */,
|
||||
);
|
||||
@ -4334,6 +4339,7 @@
|
||||
4B4DEC15252BFA9C004583AC /* 6502Esque */,
|
||||
4BF8D4CC251C0C9C00BBE21B /* 65816 */,
|
||||
4BFF1D332233778C00838EA1 /* 68000 */,
|
||||
4BCA2F552832A643006C632A /* 68000Mk2 */,
|
||||
4B77069E1EC9045B0053B588 /* Z80 */,
|
||||
);
|
||||
name = Processors;
|
||||
@ -4561,6 +4567,24 @@
|
||||
path = 6560;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCA2F552832A643006C632A /* 68000Mk2 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BCA2F562832A643006C632A /* 68000Mk2.hpp */,
|
||||
4BCA2F582832A807006C632A /* Implementation */,
|
||||
);
|
||||
path = 68000Mk2;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCA2F582832A807006C632A /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BCA2F592832A807006C632A /* 68000Mk2Storage.hpp */,
|
||||
4BCA2F5A2832A81C006C632A /* 68000Mk2Implementation.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCA6CC91D9DD9F500C2D7B2 /* Encodings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -32,105 +32,104 @@
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x1234567a;
|
||||
state.data[2] = 0xf745ff78;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.data[1] = 0x1234567a;
|
||||
registers.data[2] = 0xf745ff78;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssertEqual(state.data[1], 0x12345658);
|
||||
XCTAssertEqual(state.data[2], 0xf745ff78);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345658);
|
||||
XCTAssertEqual(state.registers.data[2], 0xf745ff78);
|
||||
}
|
||||
|
||||
- (void)testABCDZero {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345600;
|
||||
state.data[2] = 0x12345600;
|
||||
state.status = Flag::Zero;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.data[1] = 0x12345600;
|
||||
registers.data[2] = 0x12345600;
|
||||
registers.status = ConditionCode::Zero;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Zero);
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.data[2], 0x12345600);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Zero);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.registers.data[2], 0x12345600);
|
||||
}
|
||||
|
||||
- (void)testABCDNegative {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345645;
|
||||
state.data[2] = 0x12345654;
|
||||
state.status = Flag::Zero;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.data[1] = 0x12345645;
|
||||
registers.data[2] = 0x12345654;
|
||||
registers.status = ConditionCode::Zero;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Negative);
|
||||
XCTAssertEqual(state.data[1], 0x12345699);
|
||||
XCTAssertEqual(state.data[2], 0x12345654);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Negative);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345699);
|
||||
XCTAssertEqual(state.registers.data[2], 0x12345654);
|
||||
}
|
||||
|
||||
- (void)testABCDWithX {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x12345645;
|
||||
state.data[2] = 0x12345654;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.data[1] = 0x12345645;
|
||||
registers.data[2] = 0x12345654;
|
||||
registers.status = ConditionCode::Extend;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.data[2], 0x12345654);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.registers.data[2], 0x12345654);
|
||||
}
|
||||
|
||||
- (void)testABCDOverflow {
|
||||
_machine->set_program({
|
||||
0xc302, // ABCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = 0x1234563e;
|
||||
state.data[2] = 0x1234563e;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.data[1] = 0x1234563e;
|
||||
registers.data[2] = 0x1234563e;
|
||||
registers.status = ConditionCode::Extend;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Overflow);
|
||||
XCTAssertEqual(state.data[1], 0x12345683);
|
||||
XCTAssertEqual(state.data[2], 0x1234563e);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Overflow);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345683);
|
||||
XCTAssertEqual(state.registers.data[2], 0x1234563e);
|
||||
}
|
||||
|
||||
- (void)testABCDPredecDifferent {
|
||||
_machine->set_program({
|
||||
0xc30a, // ABCD -(A2), -(A1)
|
||||
});
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.address[1] = 0x3001;
|
||||
registers.address[2] = 0x4001;
|
||||
registers.status = ConditionCode::Extend;
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0xa200;
|
||||
*_machine->ram_at(0x4000) = 0x1900;
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3001;
|
||||
state.address[2] = 0x4001;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssert(state.status & Flag::Extend);
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(state.address[2], 0x4000);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||
XCTAssert(state.registers.status & ConditionCode::Extend);
|
||||
XCTAssertEqual(state.registers.address[1], 0x3000);
|
||||
XCTAssertEqual(state.registers.address[2], 0x4000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||
}
|
||||
@ -139,18 +138,17 @@
|
||||
_machine->set_program({
|
||||
0xc309, // ABCD -(A1), -(A1)
|
||||
});
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.address[1] = 0x3002;
|
||||
registers.status = ConditionCode::Extend;
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0x19a2;
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3002;
|
||||
state.status = Flag::Extend;
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.status & Flag::Carry);
|
||||
XCTAssert(state.status & Flag::Extend);
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||
XCTAssert(state.registers.status & ConditionCode::Extend);
|
||||
XCTAssertEqual(state.registers.address[1], 0x3000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
|
||||
}
|
||||
|
||||
@ -160,11 +158,10 @@
|
||||
_machine->set_program({
|
||||
0x4801 // NBCD D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status |= ccr;
|
||||
state.data[1] = d1;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.status |= ccr;
|
||||
registers.data[1] = d1;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||
@ -174,32 +171,32 @@
|
||||
[self performNBCDd1:0x7a ccr:0];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x20);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
XCTAssertEqual(state.registers.data[1], 0x20);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_extend {
|
||||
[self performNBCDd1:0x1234567a ccr:Flag::Extend | Flag::Zero];
|
||||
[self performNBCDd1:0x1234567a ccr:ConditionCode::Extend | ConditionCode::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x1234561f);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
XCTAssertEqual(state.registers.data[1], 0x1234561f);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_zero {
|
||||
[self performNBCDd1:0x12345600 ccr:Flag::Zero];
|
||||
[self performNBCDd1:0x12345600 ccr:ConditionCode::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Zero);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_negative {
|
||||
[self performNBCDd1:0x123456ff ccr:Flag::Extend | Flag::Zero];
|
||||
[self performNBCDd1:0x123456ff ccr:ConditionCode::Extend | ConditionCode::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x1234569a);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
XCTAssertEqual(state.registers.data[1], 0x1234569a);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
|
||||
}
|
||||
|
||||
- (void)testNBCD_Dn_XXXw {
|
||||
@ -213,7 +210,7 @@
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
|
||||
}
|
||||
|
||||
// MARK: SBCD
|
||||
@ -222,49 +219,48 @@
|
||||
_machine->set_program({
|
||||
0x8302 // SBCD D2, D1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status |= ccr;
|
||||
state.data[1] = d1;
|
||||
state.data[2] = d2;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.status |= ccr;
|
||||
registers.data[1] = d1;
|
||||
registers.data[2] = d2;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||
XCTAssertEqual(state.data[2], d2);
|
||||
XCTAssertEqual(state.registers.data[2], d2);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn {
|
||||
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:Flag::Zero];
|
||||
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:ConditionCode::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345611);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345611);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_zero {
|
||||
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:Flag::Zero];
|
||||
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:ConditionCode::Zero];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Zero);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_negative {
|
||||
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:Flag::Extend];
|
||||
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:ConditionCode::Extend];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345688);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345688);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_overflow {
|
||||
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:Flag::Extend];
|
||||
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:ConditionCode::Extend];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], 0x12345643);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
||||
XCTAssertEqual(state.registers.data[1], 0x12345643);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
|
||||
}
|
||||
|
||||
- (void)testSBCD_Dn_PreDec {
|
||||
@ -273,19 +269,18 @@
|
||||
});
|
||||
*_machine->ram_at(0x3000) = 0xa200;
|
||||
*_machine->ram_at(0x4000) = 0x1900;
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3001;
|
||||
state.address[2] = 0x4001;
|
||||
state.status |= Flag::Extend;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.address[1] = 0x3001;
|
||||
registers.address[2] = 0x4001;
|
||||
registers.status |= ConditionCode::Extend;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Negative);
|
||||
}
|
||||
|
||||
@end
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
|
||||
#include "../../../InstructionSets/M68k/Executor.hpp"
|
||||
#include "../../../InstructionSets/M68k/Decoder.hpp"
|
||||
|
||||
@ -16,18 +16,18 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
//#define USE_EXISTING_IMPLEMENTATION
|
||||
//#define USE_EXECUTOR
|
||||
//#define MAKE_SUGGESTIONS
|
||||
//#define LOG_UNTESTED
|
||||
|
||||
namespace {
|
||||
|
||||
/// Binds a 68000 executor to 16mb of RAM.
|
||||
struct Test68000 {
|
||||
std::array<uint8_t, 16*1024*1024> ram;
|
||||
InstructionSet::M68k::Executor<InstructionSet::M68k::Model::M68000, Test68000> processor;
|
||||
struct TestExecutor {
|
||||
uint8_t *const ram;
|
||||
InstructionSet::M68k::Executor<InstructionSet::M68k::Model::M68000, TestExecutor> processor;
|
||||
|
||||
Test68000() : processor(*this) {
|
||||
}
|
||||
TestExecutor(uint8_t *ram) : ram(ram), processor(*this) {}
|
||||
|
||||
void run_for_instructions(int instructions) {
|
||||
processor.run_for_instructions(instructions);
|
||||
@ -83,6 +83,39 @@ struct Test68000 {
|
||||
}
|
||||
};
|
||||
|
||||
/// Binds a bus-accurate 68000 to 16mb of RAM.
|
||||
struct TestProcessor: public CPU::MC68000Mk2::BusHandler {
|
||||
uint8_t *const ram;
|
||||
CPU::MC68000Mk2::Processor<TestProcessor, true, true, true> processor;
|
||||
std::function<void(void)> comparitor;
|
||||
|
||||
TestProcessor(uint8_t *ram) : ram(ram), processor(*this) {}
|
||||
|
||||
void will_perform(uint32_t, uint16_t) {
|
||||
--instructions_remaining_;
|
||||
if(!instructions_remaining_) comparitor();
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
|
||||
using Microcycle = CPU::MC68000Mk2::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
cycle.apply(&ram[cycle.host_endian_byte_address()]);
|
||||
}
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void run_for_instructions(int instructions, const std::function<void(void)> &compare) {
|
||||
instructions_remaining_ = instructions + 1; // i.e. run up to the will_perform of the instruction after.
|
||||
comparitor = std::move(compare);
|
||||
while(instructions_remaining_) {
|
||||
processor.run_for(HalfCycles(2));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int instructions_remaining_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@interface M68000ComparativeTests : XCTestCase
|
||||
@ -95,23 +128,41 @@ struct Test68000 {
|
||||
NSMutableSet<NSString *> *_failures;
|
||||
NSMutableArray<NSNumber *> *_failingOpcodes;
|
||||
NSMutableDictionary<NSNumber *, NSMutableArray<NSString *> *> *_suggestedCorrections;
|
||||
NSMutableSet<NSNumber *> *_testedOpcodes;
|
||||
|
||||
InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> _decoder;
|
||||
Test68000 _test68000;
|
||||
|
||||
std::array<uint16_t, 8*1024*1024> _ram;
|
||||
std::unique_ptr<TestExecutor> _testExecutor;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
// Definitively erase any prior memory contents;
|
||||
// 0xce is arbitrary but hopefully easier to spot
|
||||
// in potential errors than e.g. 0x00 or 0xff.
|
||||
_ram.fill(0xcece);
|
||||
|
||||
// TODO: possibly, worry about resetting RAM to 0xce after tests have completed.
|
||||
|
||||
#ifdef USE_EXECUTOR
|
||||
_testExecutor = std::make_unique<TestExecutor>(reinterpret_cast<uint8_t *>(_ram.data()));
|
||||
#endif
|
||||
|
||||
// These will accumulate a list of failing tests and associated opcodes.
|
||||
_failures = [[NSMutableSet alloc] init];
|
||||
_failingOpcodes = [[NSMutableArray alloc] init];
|
||||
|
||||
// This will simply accumulate a list of all tested opcodes, in order to report
|
||||
// on those that are missing.
|
||||
_testedOpcodes = [[NSMutableSet alloc] init];
|
||||
|
||||
#ifdef MAKE_SUGGESTIONS
|
||||
_suggestedCorrections = [[NSMutableDictionary alloc] init];
|
||||
#endif
|
||||
|
||||
// To limit tests run to a subset of files and/or of tests, uncomment and fill in below.
|
||||
// _fileSet = [NSSet setWithArray:@[@"exg.json"]];
|
||||
// _testSet = [NSSet setWithArray:@[@"CHK 41a8"]];
|
||||
// _fileSet = [NSSet setWithArray:@[@"abcd_sbcd.json"]];
|
||||
// _testSet = [NSSet setWithArray:@[@"LINK.w 0007"]];
|
||||
}
|
||||
|
||||
- (void)testAll {
|
||||
@ -126,7 +177,7 @@ struct Test68000 {
|
||||
// NSLog(@"Testing %@", url);
|
||||
[self testJSONAtURL:url];
|
||||
}
|
||||
|
||||
|
||||
XCTAssert(_failures.count == 0);
|
||||
|
||||
// Output a summary of failures, if any.
|
||||
@ -145,6 +196,25 @@ struct Test68000 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef LOG_UNTESTED
|
||||
// Output a list of untested opcodes.
|
||||
NSMutableArray<NSString *> *untested = [[NSMutableArray alloc] init];
|
||||
for(int opcode = 0; opcode < 65536; opcode++) {
|
||||
const auto instruction = _decoder.decode(uint16_t(opcode));
|
||||
|
||||
if(instruction.operation == InstructionSet::M68k::Operation::Undefined) {
|
||||
continue;
|
||||
}
|
||||
if([_testedOpcodes containsObject:@(opcode)]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[untested addObject:[NSString stringWithFormat:@"%04x %s", opcode, instruction.to_string(uint16_t(opcode)).c_str()]];
|
||||
}
|
||||
|
||||
NSLog(@"Untested: %@", untested);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)testJSONAtURL:(NSURL *)url {
|
||||
@ -153,9 +223,9 @@ struct Test68000 {
|
||||
NSError *error;
|
||||
NSArray *const jsonContents = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
||||
|
||||
if(!data || error || ![jsonContents isKindOfClass:[NSArray class]]) {
|
||||
return;
|
||||
}
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(jsonContents);
|
||||
XCTAssert([jsonContents isKindOfClass:[NSArray class]]);
|
||||
|
||||
// Perform each dictionary in the array as a test.
|
||||
for(NSDictionary *test in jsonContents) {
|
||||
@ -168,52 +238,33 @@ struct Test68000 {
|
||||
// Compare against a test set if one has been supplied.
|
||||
if(_testSet && ![_testSet containsObject:name]) continue;
|
||||
|
||||
#ifdef USE_EXISTING_IMPLEMENTATION
|
||||
[self testOperationClassic:test name:name];
|
||||
#else
|
||||
// Pull out the opcode and record it.
|
||||
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
|
||||
uint16_t opcode = 0;
|
||||
NSEnumerator<NSNumber *> *enumerator = [initialMemory objectEnumerator];
|
||||
while(true) {
|
||||
NSNumber *const address = [enumerator nextObject];
|
||||
NSNumber *const value = [enumerator nextObject];
|
||||
|
||||
if(!address || !value) break;
|
||||
if(address.integerValue == 0x100) opcode |= value.integerValue << 8;
|
||||
if(address.integerValue == 0x101) opcode |= value.integerValue;
|
||||
}
|
||||
[_testedOpcodes addObject:@(opcode)];
|
||||
|
||||
#ifdef USE_EXECUTOR
|
||||
[self testOperationExecutor:test name:name];
|
||||
#else
|
||||
[self testOperationClassic:test name:name];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testOperationClassic:(NSDictionary *)test name:(NSString *)name {
|
||||
struct TerminateMarker {};
|
||||
|
||||
// This is the test class for 68000 execution.
|
||||
struct Test68000: public CPU::MC68000::BusHandler {
|
||||
std::array<uint8_t, 16*1024*1024> ram;
|
||||
CPU::MC68000::Processor<Test68000, true, true> processor;
|
||||
std::function<void(void)> comparitor;
|
||||
|
||||
Test68000() : processor(*this) {
|
||||
}
|
||||
|
||||
void will_perform(uint32_t, uint16_t) {
|
||||
--instructions_remaining_;
|
||||
if(!instructions_remaining_) comparitor();
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
cycle.apply(&ram[cycle.host_endian_byte_address()]);
|
||||
}
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void run_for_instructions(int instructions, const std::function<void(void)> &compare) {
|
||||
instructions_remaining_ = instructions + 1; // i.e. run up to the will_perform of the instruction after.
|
||||
comparitor = std::move(compare);
|
||||
while(instructions_remaining_) {
|
||||
processor.run_for(HalfCycles(2));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int instructions_remaining_;
|
||||
};
|
||||
auto uniqueTest68000 = std::make_unique<Test68000>();
|
||||
auto uniqueTest68000 = std::make_unique<TestProcessor>(reinterpret_cast<uint8_t *>(_ram.data()));
|
||||
auto test68000 = uniqueTest68000.get();
|
||||
memset(test68000->ram.data(), 0xce, test68000->ram.size());
|
||||
|
||||
{
|
||||
// Apply initial memory state.
|
||||
@ -228,47 +279,24 @@ struct Test68000 {
|
||||
}
|
||||
|
||||
// Apply initial processor state.
|
||||
NSDictionary *const initialState = test[@"initial state"];
|
||||
auto state = test68000->processor.get_state();
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
|
||||
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
|
||||
|
||||
state.data[c] = uint32_t([initialState[dX] integerValue]);
|
||||
if(c < 7)
|
||||
state.address[c] = uint32_t([initialState[aX] integerValue]);
|
||||
}
|
||||
state.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]);
|
||||
state.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]);
|
||||
state.status = [initialState[@"sr"] integerValue];
|
||||
state.registers = [self initialRegisters:test];
|
||||
test68000->processor.set_state(state);
|
||||
}
|
||||
|
||||
// Check that this is a defined opcode; capture of the unrecognised instruction
|
||||
// exception doesn't work correctly with the way that this test class tries
|
||||
// to detect the gaps between operations.
|
||||
const uint16_t opcode = (test68000->ram[0x101] << 8) | test68000->ram[0x100];
|
||||
if(_decoder.decode(opcode).operation == InstructionSet::M68k::Operation::Undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the thing.
|
||||
const auto comparitor = [=] {
|
||||
// Test the end state.
|
||||
NSDictionary *const finalState = test[@"final state"];
|
||||
const auto state = test68000->processor.get_state();
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
|
||||
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
|
||||
|
||||
if(state.data[c] != [finalState[dX] integerValue]) [_failures addObject:name];
|
||||
if(c < 7 && state.address[c] != [finalState[aX] integerValue]) [_failures addObject:name];
|
||||
|
||||
XCTAssertEqual(state.data[c], [finalState[dX] integerValue], @"%@: D%d inconsistent", name, c);
|
||||
if(c < 7) {
|
||||
XCTAssertEqual(state.address[c], [finalState[aX] integerValue], @"%@: A%d inconsistent", name, c);
|
||||
}
|
||||
}
|
||||
if(state.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name];
|
||||
if(state.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name];
|
||||
if(state.status != [finalState[@"sr"] integerValue]) [_failures addObject:name];
|
||||
|
||||
XCTAssertEqual(state.supervisor_stack_pointer, [finalState[@"a7"] integerValue], @"%@: A7 inconsistent", name);
|
||||
XCTAssertEqual(state.user_stack_pointer, [finalState[@"usp"] integerValue], @"%@: USP inconsistent", name);
|
||||
XCTAssertEqual(state.status, [finalState[@"sr"] integerValue], @"%@: Status inconsistent", name);
|
||||
XCTAssertEqual(state.program_counter - 4, [finalState[@"pc"] integerValue], @"%@: Program counter inconsistent", name);
|
||||
[self test:test name:name compareFinalRegisters:state.registers opcode:opcode pcOffset:-4];
|
||||
|
||||
// Test final memory state.
|
||||
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
|
||||
@ -284,19 +312,19 @@ struct Test68000 {
|
||||
|
||||
// Consider collating extra detail.
|
||||
if([_failures containsObject:name]) {
|
||||
[_failingOpcodes addObject:@((test68000->ram[0x101] << 8) | test68000->ram[0x100])];
|
||||
[_failingOpcodes addObject:@(opcode)];
|
||||
}
|
||||
|
||||
// Make sure nothing further occurs; keep this test isolated.
|
||||
throw TerminateMarker();
|
||||
};
|
||||
|
||||
test68000->run_for_instructions(1, comparitor);
|
||||
try {
|
||||
test68000->run_for_instructions(1, comparitor);
|
||||
} catch(TerminateMarker m) {}
|
||||
}
|
||||
|
||||
- (void)setInitialState:(NSDictionary *)test {
|
||||
// Definitively erase any prior memory contents;
|
||||
// 0xce is arbitrary but hopefully easier to spot
|
||||
// in potential errors than e.g. 0x00 or 0xff.
|
||||
memset(_test68000.ram.data(), 0xce, _test68000.ram.size());
|
||||
|
||||
// Apply initial memory state.
|
||||
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
|
||||
NSEnumerator<NSNumber *> *enumerator = [initialMemory objectEnumerator];
|
||||
@ -305,72 +333,23 @@ struct Test68000 {
|
||||
NSNumber *const value = [enumerator nextObject];
|
||||
|
||||
if(!address || !value) break;
|
||||
_test68000.ram[address.integerValue] = value.integerValue;
|
||||
_testExecutor->ram[address.integerValue] = value.integerValue;
|
||||
}
|
||||
|
||||
// Apply initial processor state.
|
||||
NSDictionary *const initialState = test[@"initial state"];
|
||||
auto state = _test68000.processor.get_state();
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
|
||||
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
|
||||
|
||||
state.data[c] = uint32_t([initialState[dX] integerValue]);
|
||||
if(c < 7)
|
||||
state.address[c] = uint32_t([initialState[aX] integerValue]);
|
||||
}
|
||||
state.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]);
|
||||
state.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]);
|
||||
state.status = [initialState[@"sr"] integerValue];
|
||||
state.program_counter = uint32_t([initialState[@"pc"] integerValue]);
|
||||
_test68000.processor.set_state(state);
|
||||
_testExecutor->processor.set_state([self initialRegisters:test]);
|
||||
}
|
||||
|
||||
- (void)testOperationExecutor:(NSDictionary *)test name:(NSString *)name {
|
||||
[self setInitialState:test];
|
||||
|
||||
// Run the thing.
|
||||
_test68000.run_for_instructions(1);
|
||||
_testExecutor->run_for_instructions(1);
|
||||
|
||||
// Test the end state.
|
||||
NSDictionary *const finalState = test[@"final state"];
|
||||
const auto state = _test68000.processor.get_state();
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
|
||||
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
|
||||
|
||||
if(state.data[c] != [finalState[dX] integerValue]) [_failures addObject:name];
|
||||
if(c < 7 && state.address[c] != [finalState[aX] integerValue]) [_failures addObject:name];
|
||||
}
|
||||
if(state.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name];
|
||||
if(state.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name];
|
||||
|
||||
const uint16_t correctSR = [finalState[@"sr"] integerValue];
|
||||
if(state.status != correctSR) {
|
||||
const uint16_t opcode = _test68000.read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode());
|
||||
const auto instruction = _decoder.decode(opcode);
|
||||
|
||||
// For DIVU and DIVS, for now, test only the well-defined flags.
|
||||
if(
|
||||
instruction.operation != InstructionSet::M68k::Operation::DIVS &&
|
||||
instruction.operation != InstructionSet::M68k::Operation::DIVU
|
||||
) {
|
||||
[_failures addObject:name];
|
||||
} else {
|
||||
uint16_t status_mask = 0xff13; // i.e. extend, which should be unaffected, and overflow, which
|
||||
// is well-defined unless there was a divide by zero. But this
|
||||
// test set doesn't include any divide by zeroes.
|
||||
|
||||
if(!(correctSR & InstructionSet::M68k::ConditionCode::Overflow)) {
|
||||
// If overflow didn't occur then negative and zero are also well-defined.
|
||||
status_mask |= 0x000c;
|
||||
}
|
||||
|
||||
if((state.status & status_mask) != (([finalState[@"sr"] integerValue]) & status_mask)) {
|
||||
[_failures addObject:name];
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto state = _testExecutor->processor.get_state();
|
||||
const uint16_t opcode = _testExecutor->read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode());
|
||||
[self test:test name:name compareFinalRegisters:state opcode:opcode pcOffset:0];
|
||||
|
||||
// Test final memory state.
|
||||
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
|
||||
@ -380,16 +359,14 @@ struct Test68000 {
|
||||
NSNumber *const value = [enumerator nextObject];
|
||||
|
||||
if(!address || !value) break;
|
||||
if(_test68000.ram[address.integerValue] != value.integerValue) [_failures addObject:name];
|
||||
if(_testExecutor->ram[address.integerValue] != value.integerValue) [_failures addObject:name];
|
||||
}
|
||||
|
||||
// If this test is now in the failures set, add the corresponding opcode for
|
||||
// later logging.
|
||||
if([_failures containsObject:name]) {
|
||||
NSNumber *const opcode = @(_test68000.read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode()));
|
||||
|
||||
// Add this opcode to the failing list.
|
||||
[_failingOpcodes addObject:opcode];
|
||||
[_failingOpcodes addObject:@(opcode)];
|
||||
|
||||
// Generate the JSON that would have satisfied this test, at least as far as registers go,
|
||||
// if those are being collected.
|
||||
@ -412,10 +389,71 @@ struct Test68000 {
|
||||
[NSJSONSerialization dataWithJSONObject:generatedTest options:0 error:nil]
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
if(_suggestedCorrections[opcode]) {
|
||||
[_suggestedCorrections[opcode] addObject:generatedJSON];
|
||||
if(_suggestedCorrections[@(opcode)]) {
|
||||
[_suggestedCorrections[@(opcode)] addObject:generatedJSON];
|
||||
} else {
|
||||
_suggestedCorrections[opcode] = [NSMutableArray arrayWithObject:generatedJSON];
|
||||
_suggestedCorrections[@(opcode)] = [NSMutableArray arrayWithObject:generatedJSON];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (InstructionSet::M68k::RegisterSet)initialRegisters:(NSDictionary *)test {
|
||||
InstructionSet::M68k::RegisterSet registers;
|
||||
|
||||
NSDictionary *const initialState = test[@"initial state"];
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
|
||||
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
|
||||
|
||||
registers.data[c] = uint32_t([initialState[dX] integerValue]);
|
||||
if(c < 7)
|
||||
registers.address[c] = uint32_t([initialState[aX] integerValue]);
|
||||
}
|
||||
registers.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]);
|
||||
registers.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]);
|
||||
registers.status = [initialState[@"sr"] integerValue];
|
||||
registers.program_counter = uint32_t([initialState[@"pc"] integerValue]);
|
||||
|
||||
return registers;
|
||||
}
|
||||
|
||||
- (void)test:(NSDictionary *)test name:(NSString *)name compareFinalRegisters:(InstructionSet::M68k::RegisterSet)registers opcode:(uint16_t)opcode pcOffset:(int)pcOffset {
|
||||
// Test the end state.
|
||||
NSDictionary *const finalState = test[@"final state"];
|
||||
for(int c = 0; c < 8; ++c) {
|
||||
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
|
||||
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
|
||||
|
||||
if(registers.data[c] != [finalState[dX] integerValue]) [_failures addObject:name];
|
||||
if(c < 7 && registers.address[c] != [finalState[aX] integerValue]) [_failures addObject:name];
|
||||
}
|
||||
if(registers.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name];
|
||||
if(registers.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name];
|
||||
if(registers.program_counter + pcOffset != [finalState[@"pc"] integerValue]) [_failures addObject:name];
|
||||
|
||||
const uint16_t correctSR = [finalState[@"sr"] integerValue];
|
||||
if(registers.status != correctSR) {
|
||||
const auto instruction = _decoder.decode(opcode);
|
||||
|
||||
// For DIVU and DIVS, for now, test only the well-defined flags.
|
||||
if(
|
||||
instruction.operation != InstructionSet::M68k::Operation::DIVS &&
|
||||
instruction.operation != InstructionSet::M68k::Operation::DIVU
|
||||
) {
|
||||
[_failures addObject:name];
|
||||
} else {
|
||||
uint16_t status_mask = 0xff13; // i.e. extend, which should be unaffected, and overflow, which
|
||||
// is well-defined unless there was a divide by zero. But this
|
||||
// test set doesn't include any divide by zeroes.
|
||||
|
||||
if(!(correctSR & InstructionSet::M68k::ConditionCode::Overflow)) {
|
||||
// If overflow didn't occur then negative and zero are also well-defined.
|
||||
status_mask |= 0x000c;
|
||||
}
|
||||
|
||||
if((registers.status & status_mask) != (([finalState[@"sr"] integerValue]) & status_mask)) {
|
||||
[_failures addObject:name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@
|
||||
[self performBccb:0x6200];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
[self performBccb:0x6500];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 8);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
[self performBccw:0x6200];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
[self performBccw:0x6500];
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1004 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1004 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 12);
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1006 + 4);
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||
}
|
||||
|
||||
@ -108,15 +108,14 @@
|
||||
- (void)testBSRw {
|
||||
_machine->set_program({
|
||||
0x6100, 0x0006 // BSR.w $1008
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x3000);
|
||||
}, 0x3000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
|
||||
|
||||
@ -126,15 +125,14 @@
|
||||
- (void)testBSRb {
|
||||
_machine->set_program({
|
||||
0x6106 // BSR.b $1008
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x3000);
|
||||
}, 0x3000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x2ffc);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
|
||||
|
||||
@ -146,56 +144,61 @@
|
||||
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
|
||||
_machine->set_program({
|
||||
0x4581 // CHK D1, D2
|
||||
}, 0);
|
||||
_machine->set_registers([=](auto ®isters) {
|
||||
registers.data[1] = d1;
|
||||
registers.data[2] = d2;
|
||||
registers.status |= ConditionCode::AllConditions;
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.data[1] = d1;
|
||||
state.data[2] = d2;
|
||||
state.status |= Flag::ConditionCodes;
|
||||
|
||||
_machine->set_initial_stack_pointer(0);
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[1], d1);
|
||||
XCTAssertEqual(state.data[2], d2);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.registers.data[1], d1);
|
||||
XCTAssertEqual(state.registers.data[2], d2);
|
||||
}
|
||||
|
||||
// Re: CHK, below; the final state of N is undocumented if Dn >= 0 and Dn < <ea>.
|
||||
// Z, V and C are also undocumented by Motorola, but are documneted by 68knotes.txt.
|
||||
|
||||
- (void)testCHK_1111v1111 {
|
||||
[self performCHKd1:0x1111 d2:0x1111];
|
||||
[self performCHKd1:0x1111 d2:0x1111]; // Neither exception-generating state applies.
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(
|
||||
state.registers.status & (ConditionCode::Extend | ConditionCode::Zero | ConditionCode::Overflow | ConditionCode::Carry),
|
||||
ConditionCode::Extend);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_1111v0000 {
|
||||
[self performCHKd1:0x1111 d2:0x0000];
|
||||
[self performCHKd1:0x1111 d2:0x0000]; // Neither exception-generating state applies.
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(
|
||||
state.registers.status & (ConditionCode::Extend | ConditionCode::Zero | ConditionCode::Overflow | ConditionCode::Carry),
|
||||
ConditionCode::Extend | ConditionCode::Zero);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_8000v8001 {
|
||||
[self performCHKd1:0x8000 d2:0x8001];
|
||||
[self performCHKd1:0x8000 d2:0x8001]; // Both less than 0 and D2 greater than D1.
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.stack_pointer(), 0xfffffffa);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
||||
XCTAssertEqual(42, _machine->get_cycle_count());
|
||||
XCTAssertNotEqual(state.registers.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0xfffffffa);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Negative);
|
||||
XCTAssertEqual(38, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
- (void)testCHK_8000v8000 {
|
||||
[self performCHKd1:0x8000 d2:0x8000];
|
||||
[self performCHKd1:0x8000 d2:0x8000]; // Less than 0.
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative);
|
||||
XCTAssertEqual(44, _machine->get_cycle_count());
|
||||
XCTAssertNotEqual(state.registers.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Negative);
|
||||
XCTAssertEqual(40, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
// MARK: DBcc
|
||||
@ -204,16 +207,15 @@
|
||||
_machine->set_program({
|
||||
opcode, 0x0008 // DBcc D2, +8
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = status;
|
||||
state.data[2] = 1;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters) {
|
||||
registers.status = status;
|
||||
registers.data[2] = 1;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.data[2], d2Output);
|
||||
XCTAssertEqual(state.status, status);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.registers.data[2], d2Output);
|
||||
XCTAssertEqual(state.registers.status, status);
|
||||
}
|
||||
|
||||
- (void)testDBT {
|
||||
@ -229,27 +231,27 @@
|
||||
}
|
||||
|
||||
- (void)testDBHI_Carry {
|
||||
[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x52ca status:ConditionCode::Carry d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBHI_Zero {
|
||||
[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x52ca status:ConditionCode::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLS_CarryOverflow {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry | Flag::Overflow d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x53ca status:ConditionCode::Carry | ConditionCode::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLS_Carry {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x53ca status:ConditionCode::Carry d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLS_Overflow {
|
||||
[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x53ca status:ConditionCode::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCC_Carry {
|
||||
[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x54ca status:ConditionCode::Carry d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBCC {
|
||||
@ -261,7 +263,7 @@
|
||||
}
|
||||
|
||||
- (void)testDBCS_Carry {
|
||||
[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x55ca status:ConditionCode::Carry d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBNE {
|
||||
@ -269,7 +271,7 @@
|
||||
}
|
||||
|
||||
- (void)testDBNE_Zero {
|
||||
[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x56ca status:ConditionCode::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBEQ {
|
||||
@ -277,7 +279,7 @@
|
||||
}
|
||||
|
||||
- (void)testDBEQ_Zero {
|
||||
[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x57ca status:ConditionCode::Zero d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBVC {
|
||||
@ -285,7 +287,7 @@
|
||||
}
|
||||
|
||||
- (void)testDBVC_Overflow {
|
||||
[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x58ca status:ConditionCode::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBVS {
|
||||
@ -293,7 +295,7 @@
|
||||
}
|
||||
|
||||
- (void)testDBVS_Overflow {
|
||||
[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x59ca status:ConditionCode::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBPL {
|
||||
@ -301,7 +303,7 @@
|
||||
}
|
||||
|
||||
- (void)testDBPL_Negative {
|
||||
[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5aca status:ConditionCode::Negative d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBMI {
|
||||
@ -309,11 +311,11 @@
|
||||
}
|
||||
|
||||
- (void)testDBMI_Negative {
|
||||
[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5bca status:ConditionCode::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative | Flag::Overflow d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5cca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGE {
|
||||
@ -321,15 +323,15 @@
|
||||
}
|
||||
|
||||
- (void)testDBGE_Negative {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5cca status:ConditionCode::Negative d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBGE_Overflow {
|
||||
[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5cca status:ConditionCode::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5dca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLT {
|
||||
@ -337,11 +339,11 @@
|
||||
}
|
||||
|
||||
- (void)testDBLT_Negative {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5dca status:ConditionCode::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLT_Overflow {
|
||||
[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5dca status:ConditionCode::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT {
|
||||
@ -349,15 +351,15 @@
|
||||
}
|
||||
|
||||
- (void)testDBGT_ZeroNegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero | Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5eca status:ConditionCode::Zero | ConditionCode::Negative | ConditionCode::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBGT_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Negative | Flag::Overflow d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5eca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBGT_Zero {
|
||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5eca status:ConditionCode::Zero d2Outcome:0];
|
||||
}
|
||||
|
||||
- (void)testDBLE {
|
||||
@ -365,15 +367,15 @@
|
||||
}
|
||||
|
||||
- (void)testDBLE_Zero {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5fca status:ConditionCode::Zero d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLE_Negative {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1];
|
||||
[self performDBccTestOpcode:0x5fca status:ConditionCode::Negative d2Outcome:1];
|
||||
}
|
||||
|
||||
- (void)testDBLE_NegativeOverflow {
|
||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative | Flag::Overflow d2Outcome:0];
|
||||
[self performDBccTestOpcode:0x5fca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:0];
|
||||
}
|
||||
|
||||
/* Further DBF tests omitted; they seemed to be duplicative, assuming I'm not suffering a failure of comprehension. */
|
||||
@ -385,15 +387,14 @@
|
||||
0x4ed1 // JMP (A1)
|
||||
});
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.address[1] = 0x3000;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->set_registers([=](auto ®isters) {
|
||||
registers.address[1] = 0x3000;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.address[1], 0x3000);
|
||||
XCTAssertEqual(state.program_counter, 0x3000 + 4);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.registers.address[1], 0x3000);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x3000 + 4);
|
||||
XCTAssertEqual(8, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@ -405,7 +406,7 @@
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@ -414,14 +415,13 @@
|
||||
- (void)testJSR_PC {
|
||||
_machine->set_program({
|
||||
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
}, 0x2000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x100c + 4);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
|
||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||
@ -430,14 +430,13 @@
|
||||
- (void)testJSR_XXXl {
|
||||
_machine->set_program({
|
||||
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
}, 0x2000);
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x1ffc);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
|
||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||
@ -458,8 +457,7 @@
|
||||
- (void)testRTR {
|
||||
_machine->set_program({
|
||||
0x4e77 // RTR
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
}, 0x2000);
|
||||
*_machine->ram_at(0x2000) = 0x7fff;
|
||||
*_machine->ram_at(0x2002) = 0;
|
||||
*_machine->ram_at(0x2004) = 0xc;
|
||||
@ -467,9 +465,9 @@
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2006);
|
||||
XCTAssertEqual(state.program_counter, 0x10);
|
||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x2006);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x10);
|
||||
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::AllConditions);
|
||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@ -478,16 +476,15 @@
|
||||
- (void)testRTS {
|
||||
_machine->set_program({
|
||||
0x4e75 // RTS
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x2000);
|
||||
}, 0x2000);
|
||||
*_machine->ram_at(0x2000) = 0x0000;
|
||||
*_machine->ram_at(0x2002) = 0x000c;
|
||||
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.stack_pointer(), 0x2004);
|
||||
XCTAssertEqual(state.program_counter, 0x000c + 4);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x2004);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x000c + 4);
|
||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@ -497,22 +494,21 @@
|
||||
_machine->set_program({
|
||||
0x4e41 // TRAP #1
|
||||
});
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = 0x700;
|
||||
state.user_stack_pointer = 0x200;
|
||||
state.supervisor_stack_pointer = 0x206;
|
||||
_machine->set_registers([=](auto ®isters) {
|
||||
registers.status = 0x700;
|
||||
registers.user_stack_pointer = 0x200;
|
||||
registers.supervisor_stack_pointer = 0x206;
|
||||
});
|
||||
*_machine->ram_at(0x84) = 0xfffe;
|
||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.status, 0x2700);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.registers.status, 0x2700);
|
||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
|
||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||
XCTAssertEqual(state.supervisor_stack_pointer, 0x200);
|
||||
XCTAssertEqual(state.registers.supervisor_stack_pointer, 0x200);
|
||||
XCTAssertEqual(34, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
@ -521,21 +517,20 @@
|
||||
- (void)testTRAPV_taken {
|
||||
_machine->set_program({
|
||||
0x4e76 // TRAPV
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x206);
|
||||
}, 0x206);
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
state.status = 0x702;
|
||||
state.supervisor_stack_pointer = 0x206;
|
||||
_machine->set_registers([=](auto ®isters) {
|
||||
registers.status = 0x702;
|
||||
registers.supervisor_stack_pointer = 0x206;
|
||||
});
|
||||
*_machine->ram_at(0x1e) = 0xfffe;
|
||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||
|
||||
_machine->set_processor_state(state);
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.status, 0x2702);
|
||||
XCTAssertEqual(state.stack_pointer(), 0x200);
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.registers.status, 0x2702);
|
||||
XCTAssertEqual(state.registers.stack_pointer(), 0x200);
|
||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
|
||||
@ -550,7 +545,7 @@
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
|
||||
XCTAssertEqual(4, _machine->get_cycle_count());
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -12,94 +12,25 @@
|
||||
|
||||
#include "TestRunner68000.hpp"
|
||||
|
||||
class CPU::MC68000::ProcessorStorageTests {
|
||||
public:
|
||||
ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) {
|
||||
false_valids_ = [NSMutableSet set];
|
||||
false_invalids_ = [NSMutableSet set];
|
||||
|
||||
FILE *source = fopen(coverage_file_name, "rb");
|
||||
|
||||
// The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]...
|
||||
// The file terminates with four additional bytes that begin with two zero bytes.
|
||||
//
|
||||
// The version of the file I grabbed seems to cover all opcodes, making their enumeration
|
||||
// arguably redundant; the code below nevertheless uses the codes from the file.
|
||||
//
|
||||
// Similarly, I'm testing for exactly the strings VA or IN to ensure no further
|
||||
// types creep into any updated version of the table that I then deal with incorrectly.
|
||||
uint16_t last_observed = 0;
|
||||
while(true) {
|
||||
// Fetch opcode number.
|
||||
uint16_t next_opcode = fgetc(source) << 8;
|
||||
next_opcode |= fgetc(source);
|
||||
if(next_opcode < last_observed) break;
|
||||
last_observed = next_opcode;
|
||||
|
||||
// Determine whether it's meant to be valid.
|
||||
char type[3];
|
||||
type[0] = fgetc(source);
|
||||
type[1] = fgetc(source);
|
||||
type[2] = '\0';
|
||||
|
||||
// TEMPORARY: factor out A- and F-line exceptions.
|
||||
if((next_opcode&0xf000) == 0xa000) continue;
|
||||
if((next_opcode&0xf000) == 0xf000) continue;
|
||||
|
||||
if(!strcmp(type, "VA")) {
|
||||
// Test for validity.
|
||||
if(storage.instructions[next_opcode].micro_operations == std::numeric_limits<uint32_t>::max()) {
|
||||
[false_invalids_ addObject:@(next_opcode)];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!strcmp(type, "IN")) {
|
||||
// Test for invalidity.
|
||||
if(storage.instructions[next_opcode].micro_operations != std::numeric_limits<uint32_t>::max()) {
|
||||
[false_valids_ addObject:@(next_opcode)];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
fclose(source);
|
||||
}
|
||||
|
||||
NSSet<NSNumber *> *false_valids() const {
|
||||
return false_valids_;
|
||||
}
|
||||
|
||||
NSSet<NSNumber *> *false_invalids() const {
|
||||
return false_invalids_;
|
||||
}
|
||||
|
||||
private:
|
||||
NSMutableSet<NSNumber *> *false_invalids_;
|
||||
NSMutableSet<NSNumber *> *false_valids_;
|
||||
};
|
||||
|
||||
@interface NSSet (CSHexDump)
|
||||
|
||||
- (NSString *)hexDump;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSSet (CSHexDump)
|
||||
|
||||
- (NSString *)hexDump {
|
||||
NSMutableArray<NSString *> *components = [NSMutableArray array];
|
||||
|
||||
for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
[components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
|
||||
}
|
||||
|
||||
return [components componentsJoinedByString:@" "];
|
||||
}
|
||||
|
||||
@end
|
||||
//@interface NSSet (CSHexDump)
|
||||
//
|
||||
//- (NSString *)hexDump;
|
||||
//
|
||||
//@end
|
||||
//
|
||||
//@implementation NSSet (CSHexDump)
|
||||
//
|
||||
//- (NSString *)hexDump {
|
||||
// NSMutableArray<NSString *> *components = [NSMutableArray array];
|
||||
//
|
||||
// for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
// [components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
|
||||
// }
|
||||
//
|
||||
// return [components componentsJoinedByString:@" "];
|
||||
//}
|
||||
//
|
||||
//@end
|
||||
|
||||
|
||||
@interface M68000Tests : XCTestCase
|
||||
@ -123,39 +54,38 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
_machine->set_program({
|
||||
0xc100 // ABCD D0, D0
|
||||
});
|
||||
|
||||
auto state = _machine->get_processor_state();
|
||||
const uint8_t bcd_d = ((d / 10) * 16) + (d % 10);
|
||||
state.data[0] = bcd_d;
|
||||
_machine->set_processor_state(state);
|
||||
|
||||
_machine->set_registers([=](auto ®isters){
|
||||
registers.data[0] = bcd_d;
|
||||
});
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
state = _machine->get_processor_state();
|
||||
const auto state = _machine->get_processor_state();
|
||||
const uint8_t double_d = (d * 2) % 100;
|
||||
const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10);
|
||||
XCTAssert(state.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.data[0], bcd_double_d);
|
||||
XCTAssert(state.registers.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.registers.data[0], bcd_double_d);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDivideByZero {
|
||||
_machine->set_program({
|
||||
0x7000, // MOVE #0, D0; location 0x400
|
||||
0x3200, // MOVE D0, D1; location 0x402
|
||||
0x7000, // MOVE #0, D0; location 0x1000
|
||||
0x3200, // MOVE D0, D1; location 0x1002
|
||||
|
||||
0x82C0, // DIVU; location 0x404
|
||||
0x82C0, // DIVU; location 0x1004
|
||||
|
||||
/* Next instruction would be at 0x406 */
|
||||
});
|
||||
_machine->set_initial_stack_pointer(0x1000);
|
||||
/* Next instruction would be at 0x1006 */
|
||||
}, 0x1000);
|
||||
|
||||
_machine->run_for_instructions(4);
|
||||
|
||||
const auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
|
||||
XCTAssert(state.registers.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
|
||||
|
||||
const uint16_t *const stack_top = _machine->ram_at(state.supervisor_stack_pointer);
|
||||
XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
|
||||
// const uint16_t *const stack_top = _machine->ram_at(state.registers.supervisor_stack_pointer);
|
||||
// XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
|
||||
// TODO: determine whether above is a valid test; if so then it's suspicious that the exception
|
||||
// is raised so as to avoid a final prefetch.
|
||||
}
|
||||
|
||||
- (void)testMOVE {
|
||||
@ -177,28 +107,28 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
// Perform MOVE #fb2e, D0
|
||||
_machine->run_for_instructions(1);
|
||||
auto state = _machine->get_processor_state();
|
||||
XCTAssert(state.data[0] == 0xfb2e);
|
||||
XCTAssert(state.registers.data[0] == 0xfb2e);
|
||||
|
||||
// Perform MOVE D0, D1
|
||||
_machine->run_for_instructions(1);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.data[1] == 0xfb2e);
|
||||
XCTAssert(state.registers.data[1] == 0xfb2e);
|
||||
|
||||
// Perform MOVEA D0, A0
|
||||
_machine->run_for_instructions(1);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.address[0]);
|
||||
XCTAssert(state.registers.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.registers.address[0]);
|
||||
|
||||
// Perform MOVEA.w (0x1000), A1
|
||||
_machine->run_for_instructions(1);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.address[1]);
|
||||
XCTAssert(state.registers.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.registers.address[1]);
|
||||
|
||||
// Perform MOVE #$400, A4; MOVE.l (A4), D2
|
||||
_machine->run_for_instructions(2);
|
||||
state = _machine->get_processor_state();
|
||||
XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]);
|
||||
XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]);
|
||||
XCTAssert(state.registers.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.registers.address[4]);
|
||||
XCTAssert(state.registers.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.registers.data[2]);
|
||||
}
|
||||
|
||||
- (void)testVectoredInterrupt {
|
||||
@ -223,7 +153,7 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
_machine->run_for_instructions(1);
|
||||
|
||||
const auto state = _machine->processor().get_state();
|
||||
XCTAssertEqual(state.program_counter, 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore
|
||||
XCTAssertEqual(state.registers.program_counter, 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore
|
||||
// by the wonders of prefetch the program counter is now at 1008.
|
||||
}
|
||||
|
||||
@ -287,7 +217,7 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
XCTAssertEqual(_machine->get_cycle_count(), 6 + 2);
|
||||
}
|
||||
|
||||
- (void)testOpcodeCoverage {
|
||||
/*- (void)testOpcodeCoverage {
|
||||
// Perform an audit of implemented instructions.
|
||||
CPU::MC68000::ProcessorStorageTests storage_tests(
|
||||
_machine->processor(),
|
||||
@ -479,6 +409,6 @@ class CPU::MC68000::ProcessorStorageTests {
|
||||
XCTAssert(!trimmedInvalids.count, "%@ opcodes should be valid but aren't: %@", @(trimmedInvalids.count), trimmedInvalids.hexDump);
|
||||
|
||||
// XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump);
|
||||
}
|
||||
}*/
|
||||
|
||||
@end
|
||||
|
@ -11,9 +11,9 @@
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "68000.hpp"
|
||||
#include "68000Mk2.hpp"
|
||||
|
||||
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
||||
class ComparativeBusHandler: public CPU::MC68000Mk2::BusHandler {
|
||||
public:
|
||||
ComparativeBusHandler(const char *trace_name) {
|
||||
trace = gzopen(trace_name, "rt");
|
||||
@ -30,14 +30,14 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
||||
++line_count;
|
||||
|
||||
// Generate state locally.
|
||||
const auto state = get_state();
|
||||
const auto state = get_state().registers;
|
||||
char local_state[300];
|
||||
sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||
address,
|
||||
state.status,
|
||||
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
|
||||
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
|
||||
(state.status & 0x2000) ? state.supervisor_stack_pointer : state.user_stack_pointer
|
||||
state.stack_pointer()
|
||||
);
|
||||
|
||||
// Check that the two coincide.
|
||||
@ -49,7 +49,7 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
||||
}
|
||||
}
|
||||
|
||||
virtual CPU::MC68000::ProcessorState get_state() = 0;
|
||||
virtual CPU::MC68000Mk2::State get_state() = 0;
|
||||
|
||||
private:
|
||||
int line_count = 0;
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
//#define LOG_TRACE
|
||||
|
||||
#include "68000.hpp"
|
||||
#include "68000Mk2.hpp"
|
||||
#include "Comparative68000.hpp"
|
||||
#include "CSROMFetcher.hpp"
|
||||
|
||||
@ -32,11 +32,11 @@ class EmuTOS: public ComparativeBusHandler {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
CPU::MC68000::ProcessorState get_state() final {
|
||||
CPU::MC68000Mk2::State get_state() final {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
|
||||
const uint32_t address = cycle.word_address();
|
||||
uint32_t word_address = address;
|
||||
|
||||
@ -56,7 +56,7 @@ class EmuTOS: public ComparativeBusHandler {
|
||||
word_address %= ram_.size();
|
||||
}
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
using Microcycle = CPU::MC68000Mk2::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
uint16_t peripheral_result = 0xffff;
|
||||
if(is_peripheral) {
|
||||
@ -72,16 +72,16 @@ class EmuTOS: public ComparativeBusHandler {
|
||||
default: break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = is_peripheral ? peripheral_result : base[word_address];
|
||||
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
base[word_address] = cycle.value->full;
|
||||
base[word_address] = cycle.value->w;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
base[word_address] = (cycle.value->halves.low << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ class EmuTOS: public ComparativeBusHandler {
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
|
||||
CPU::MC68000Mk2::Processor<EmuTOS, true, true> m68000_;
|
||||
|
||||
std::vector<uint16_t> emuTOS_;
|
||||
std::array<uint16_t, 256*1024> ram_;
|
||||
|
@ -35,11 +35,11 @@ class QL: public ComparativeBusHandler {
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
CPU::MC68000::ProcessorState get_state() final {
|
||||
CPU::MC68000Mk2::State get_state() final {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
|
||||
const uint32_t address = cycle.word_address();
|
||||
uint32_t word_address = address;
|
||||
|
||||
@ -56,7 +56,7 @@ class QL: public ComparativeBusHandler {
|
||||
word_address %= ram_.size();
|
||||
}
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
using Microcycle = CPU::MC68000Mk2::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
uint16_t peripheral_result = 0xffff;
|
||||
|
||||
@ -64,18 +64,18 @@ class QL: public ComparativeBusHandler {
|
||||
default: break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = is_peripheral ? peripheral_result : base[word_address];
|
||||
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
assert(!(is_rom && !is_peripheral));
|
||||
if(!is_peripheral) base[word_address] = cycle.value->full;
|
||||
if(!is_peripheral) base[word_address] = cycle.value->w;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
assert(!(is_rom && !is_peripheral));
|
||||
if(!is_peripheral) base[word_address] = (cycle.value->halves.low << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ class QL: public ComparativeBusHandler {
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<QL, true, true> m68000_;
|
||||
CPU::MC68000Mk2::Processor<QL, true, false, true> m68000_;
|
||||
|
||||
std::vector<uint16_t> rom_;
|
||||
std::array<uint16_t, 64*1024> ram_;
|
||||
|
@ -10,104 +10,98 @@
|
||||
#define TestRunner68000_h
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
|
||||
|
||||
using Flag = CPU::MC68000::Flag;
|
||||
using namespace InstructionSet::M68k;
|
||||
|
||||
/*!
|
||||
Provides a 68000 with 64kb of RAM in its low address space;
|
||||
/RESET will put the supervisor stack pointer at 0xFFFF and
|
||||
begin execution at 0x0400.
|
||||
*/
|
||||
class RAM68000: public CPU::MC68000::BusHandler {
|
||||
class RAM68000: public CPU::MC68000Mk2::BusHandler {
|
||||
public:
|
||||
RAM68000() : m68000_(*this) {
|
||||
// Setup the /RESET vector.
|
||||
ram_[0] = 0;
|
||||
ram_[1] = 0x206; // Supervisor stack pointer.
|
||||
ram_[2] = 0;
|
||||
ram_[3] = 0x1000; // Initial PC.
|
||||
|
||||
// Ensure the condition codes start unset.
|
||||
auto state = get_processor_state();
|
||||
state.status &= ~Flag::ConditionCodes;
|
||||
set_processor_state(state);
|
||||
}
|
||||
RAM68000() : m68000_(*this) {}
|
||||
|
||||
uint32_t initial_pc() const {
|
||||
return 0x1000;
|
||||
}
|
||||
|
||||
void set_program(const std::vector<uint16_t> &program) {
|
||||
void set_program(
|
||||
const std::vector<uint16_t> &program,
|
||||
uint32_t stack_pointer = 0x206
|
||||
) {
|
||||
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
|
||||
|
||||
// Add a NOP suffix, to avoid corrupting flags should the attempt to
|
||||
// run for a certain number of instructions overrun.
|
||||
ram_[(0x1000 >> 1) + program.size()] = 0x4e71;
|
||||
// Ensure the condition codes start unset and set the initial program counter
|
||||
// and supervisor stack pointer, as well as starting in supervisor mode.
|
||||
auto registers = m68000_.get_state().registers;
|
||||
registers.status = 0x2700;
|
||||
registers.program_counter = initial_pc();
|
||||
registers.supervisor_stack_pointer = stack_pointer;
|
||||
m68000_.decode_from_state(registers);
|
||||
}
|
||||
|
||||
void set_initial_stack_pointer(uint32_t sp) {
|
||||
ram_[0] = sp >> 16;
|
||||
ram_[1] = sp & 0xffff;
|
||||
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
|
||||
auto state = m68000_.get_state();
|
||||
func(state.registers);
|
||||
m68000_.set_state(state);
|
||||
}
|
||||
|
||||
void will_perform(uint32_t, uint16_t) {
|
||||
--instructions_remaining_;
|
||||
if(instructions_remaining_ < 0) {
|
||||
throw StopException();
|
||||
}
|
||||
}
|
||||
|
||||
void run_for_instructions(int count) {
|
||||
instructions_remaining_ = count + (has_run_ ? 0 : 1);
|
||||
finish_reset_if_needed();
|
||||
duration_ = HalfCycles(0);
|
||||
instructions_remaining_ = count;
|
||||
if(!instructions_remaining_) return;
|
||||
|
||||
while(instructions_remaining_) {
|
||||
run_for(HalfCycles(2));
|
||||
}
|
||||
try {
|
||||
while(true) {
|
||||
run_for(HalfCycles(2000));
|
||||
}
|
||||
} catch (const StopException &) {}
|
||||
}
|
||||
|
||||
void run_for(HalfCycles cycles) {
|
||||
finish_reset_if_needed();
|
||||
m68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
void finish_reset_if_needed() {
|
||||
// If the 68000 hasn't run yet, build in the necessary
|
||||
// cycles to finish the reset program, and set the stored state.
|
||||
if(!has_run_) {
|
||||
has_run_ = true;
|
||||
m68000_.run_for(HalfCycles(76));
|
||||
duration_ -= HalfCycles(76);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t *ram_at(uint32_t address) {
|
||||
return &ram_[(address >> 1) % ram_.size()];
|
||||
}
|
||||
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
|
||||
const uint32_t word_address = cycle.word_address();
|
||||
if(instructions_remaining_) duration_ += cycle.length;
|
||||
duration_ += cycle.length;
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
using Microcycle = CPU::MC68000Mk2::Microcycle;
|
||||
if(cycle.data_select_active()) {
|
||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||
cycle.value->halves.low = 10;
|
||||
cycle.value->b = 10;
|
||||
} else {
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
||||
default: break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = ram_[word_address % ram_.size()];
|
||||
cycle.value->w = ram_[word_address % ram_.size()];
|
||||
break;
|
||||
case Microcycle::SelectByte | Microcycle::Read:
|
||||
cycle.value->halves.low = ram_[word_address % ram_.size()] >> cycle.byte_shift();
|
||||
cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift();
|
||||
break;
|
||||
case Microcycle::SelectWord:
|
||||
ram_[word_address % ram_.size()] = cycle.value->full;
|
||||
ram_[word_address % ram_.size()] = cycle.value->w;
|
||||
break;
|
||||
case Microcycle::SelectByte:
|
||||
ram_[word_address % ram_.size()] = uint16_t(
|
||||
(cycle.value->halves.low << cycle.byte_shift()) |
|
||||
(cycle.value->b << cycle.byte_shift()) |
|
||||
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
|
||||
);
|
||||
break;
|
||||
@ -118,15 +112,11 @@ class RAM68000: public CPU::MC68000::BusHandler {
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true>::State get_processor_state() {
|
||||
CPU::MC68000Mk2::State get_processor_state() {
|
||||
return m68000_.get_state();
|
||||
}
|
||||
|
||||
void set_processor_state(const CPU::MC68000::Processor<RAM68000, true>::State &state) {
|
||||
m68000_.set_state(state);
|
||||
}
|
||||
|
||||
CPU::MC68000::Processor<RAM68000, true, true> &processor() {
|
||||
auto &processor() {
|
||||
return m68000_;
|
||||
}
|
||||
|
||||
@ -139,11 +129,12 @@ class RAM68000: public CPU::MC68000::BusHandler {
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
|
||||
struct StopException {};
|
||||
|
||||
CPU::MC68000Mk2::Processor<RAM68000, true, true, true> m68000_;
|
||||
std::array<uint16_t, 256*1024> ram_{};
|
||||
int instructions_remaining_;
|
||||
HalfCycles duration_;
|
||||
bool has_run_ = false;
|
||||
};
|
||||
|
||||
#endif /* TestRunner68000_h */
|
||||
|
453
Processors/68000Mk2/68000Mk2.hpp
Normal file
453
Processors/68000Mk2/68000Mk2.hpp
Normal file
@ -0,0 +1,453 @@
|
||||
//
|
||||
// 68000Mk2.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _8000Mk2_h
|
||||
#define _8000Mk2_h
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Numeric/RegisterSizes.hpp"
|
||||
#include "../../InstructionSets/M68k/RegisterSet.hpp"
|
||||
|
||||
namespace CPU {
|
||||
namespace MC68000Mk2 {
|
||||
|
||||
/*!
|
||||
A microcycle is an atomic unit of 68000 bus activity — it is a single item large enough
|
||||
fully to specify a sequence of bus events that occur without any possible interruption.
|
||||
|
||||
Concretely, a standard read cycle breaks down into at least two microcycles:
|
||||
|
||||
1) a 4 half-cycle length microcycle in which the address strobe is signalled; and
|
||||
2) a 4 half-cycle length microcycle in which at least one of the data strobes is
|
||||
signalled, and the data bus is sampled.
|
||||
|
||||
That is, assuming DTack were signalled when microcycle (1) ended. If not then additional
|
||||
wait state microcycles would fall between those two parts.
|
||||
|
||||
The 68000 data sheet defines when the address becomes valid during microcycle (1), and
|
||||
when the address strobe is actually asserted. But those timings are fixed. So simply
|
||||
telling you that this was a microcycle during which the address trobe was signalled is
|
||||
sufficient fully to describe the bus activity.
|
||||
|
||||
(Aside: see the 68000 template's definition for options re: implicit DTack; if your
|
||||
68000 owner can always predict exactly how long it will hold DTack following observation
|
||||
of an address-strobing microcycle, it can just supply those periods for accounting and
|
||||
avoid the runtime cost of actual DTack emulation. But such as the bus allows.)
|
||||
*/
|
||||
struct Microcycle {
|
||||
using OperationT = unsigned int;
|
||||
|
||||
/// Indicates that the address strobe and exactly one of the data strobes are active; you can determine
|
||||
/// which by inspecting the low bit of the provided address. The RW line indicates a read.
|
||||
static constexpr OperationT SelectByte = 1 << 0;
|
||||
// Maintenance note: this is bit 0 to reduce the cost of getting a host-endian
|
||||
// bytewise address. The assumption that it is bit 0 is also used for branchless
|
||||
// selection in a few places. See implementation of host_endian_byte_address(),
|
||||
// value8_high(), value8_low() and value16().
|
||||
|
||||
/// Indicates that the address and both data select strobes are active.
|
||||
static constexpr OperationT SelectWord = 1 << 1;
|
||||
|
||||
/// If set, indicates a read. Otherwise, a write.
|
||||
static constexpr OperationT Read = 1 << 2;
|
||||
|
||||
// Two-bit gap deliberately left here for PermitRead/Write below.
|
||||
|
||||
/// A NewAddress cycle is one in which the address strobe is initially low but becomes high;
|
||||
/// this correlates to states 0 to 5 of a standard read/write cycle.
|
||||
static constexpr OperationT NewAddress = 1 << 5;
|
||||
|
||||
/// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither
|
||||
/// of the data strobes are.
|
||||
static constexpr OperationT SameAddress = 1 << 6;
|
||||
|
||||
/// A Reset cycle is one in which the RESET output is asserted.
|
||||
static constexpr OperationT Reset = 1 << 7;
|
||||
|
||||
/// Contains the value of line FC0 if it is not implicit via InterruptAcknowledge.
|
||||
static constexpr OperationT IsData = 1 << 8;
|
||||
|
||||
/// Contains the value of line FC1 if it is not implicit via InterruptAcknowledge.
|
||||
static constexpr OperationT IsProgram = 1 << 9;
|
||||
|
||||
/// The interrupt acknowledge cycle is that during which the 68000 seeks to obtain the vector for
|
||||
/// an interrupt it plans to observe. Noted on a real 68000 by all FCs being set to 1.
|
||||
static constexpr OperationT InterruptAcknowledge = 1 << 10;
|
||||
|
||||
/// Represents the state of the 68000's valid memory address line — indicating whether this microcycle
|
||||
/// is synchronised with the E clock to satisfy a valid peripheral address request.
|
||||
static constexpr OperationT IsPeripheral = 1 << 11;
|
||||
|
||||
/// Provides the 68000's bus grant line — indicating whether a bus request has been acknowledged.
|
||||
static constexpr OperationT BusGrant = 1 << 12;
|
||||
|
||||
/// Contains a valid combination of the various static constexpr int flags, describing the operation
|
||||
/// performed by this Microcycle.
|
||||
OperationT operation = 0;
|
||||
|
||||
/// Describes the duration of this Microcycle.
|
||||
HalfCycles length = HalfCycles(4);
|
||||
|
||||
/*!
|
||||
For expediency, this provides a full 32-bit byte-resolution address — e.g.
|
||||
if reading indirectly via an address register, this will indicate the full
|
||||
value of the address register.
|
||||
|
||||
The receiver should ignore bits 0 and 24+. Use word_address() automatically
|
||||
to obtain the only the 68000's real address lines, giving a 23-bit address
|
||||
at word resolution.
|
||||
*/
|
||||
const uint32_t *address = nullptr;
|
||||
|
||||
/*!
|
||||
If this is a write cycle, dereference value to get the value loaded onto
|
||||
the data bus.
|
||||
|
||||
If this is a read cycle, write the value on the data bus to it.
|
||||
|
||||
Otherwise, this value is undefined.
|
||||
|
||||
If this bus cycle provides a byte then its value is provided via
|
||||
@c value->b and @c value->w is undefined. This is true regardless of
|
||||
whether the upper or lower byte of a word is being accessed.
|
||||
|
||||
Word values occupy the entirety of @c value->w.
|
||||
*/
|
||||
SlicedInt16 *value = nullptr;
|
||||
|
||||
Microcycle(OperationT operation) : operation(operation) {}
|
||||
Microcycle(OperationT operation, HalfCycles length) : operation(operation), length(length) {}
|
||||
Microcycle() {}
|
||||
|
||||
/// @returns @c true if two Microcycles are equal; @c false otherwise.
|
||||
bool operator ==(const Microcycle &rhs) const {
|
||||
if(value != rhs.value) return false;
|
||||
if(address != rhs.address) return false;
|
||||
if(length != rhs.length) return false;
|
||||
if(operation != rhs.operation) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Various inspectors.
|
||||
|
||||
/*! @returns true if any data select line is active; @c false otherwise. */
|
||||
forceinline bool data_select_active() const {
|
||||
return bool(operation & (SelectWord | SelectByte | InterruptAcknowledge));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns 0 if this byte access wants the low part of a 16-bit word; 8 if it wants the high part.
|
||||
*/
|
||||
forceinline unsigned int byte_shift() const {
|
||||
return (((*address) & 1) << 3) ^ 8;
|
||||
}
|
||||
|
||||
/*!
|
||||
Obtains the mask to apply to a word that will leave only the byte this microcycle is selecting.
|
||||
|
||||
@returns 0x00ff if this byte access wants the low part of a 16-bit word; 0xff00 if it wants the high part.
|
||||
*/
|
||||
forceinline uint16_t byte_mask() const {
|
||||
return uint16_t(0xff00) >> (((*address) & 1) << 3);
|
||||
}
|
||||
|
||||
/*!
|
||||
Obtains the mask to apply to a word that will leave only the byte this microcycle **isn't** selecting.
|
||||
i.e. this is the part of a word that should be untouched by this microcycle.
|
||||
|
||||
@returns 0xff00 if this byte access wants the low part of a 16-bit word; 0x00ff if it wants the high part.
|
||||
*/
|
||||
forceinline uint16_t untouched_byte_mask() const {
|
||||
return uint16_t(uint16_t(0xff) << (((*address) & 1) << 3));
|
||||
}
|
||||
|
||||
/*!
|
||||
Assuming this cycle is a byte write, mutates @c destination by writing the byte to the proper upper or
|
||||
lower part, retaining the other half.
|
||||
*/
|
||||
forceinline uint16_t write_byte(uint16_t destination) const {
|
||||
return uint16_t((destination & untouched_byte_mask()) | (value->b << byte_shift()));
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns non-zero if this is a byte read and 68000 LDS is asserted.
|
||||
*/
|
||||
forceinline int lower_data_select() const {
|
||||
return (operation & SelectByte) & ((*address & 1) << 3);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns non-zero if this is a byte read and 68000 UDS is asserted.
|
||||
*/
|
||||
forceinline int upper_data_select() const {
|
||||
return (operation & SelectByte) & ~((*address & 1) << 3);
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the address being accessed at the precision a 68000 supplies it —
|
||||
only 24 address bit precision, with the low bit shifted out. So it's the
|
||||
68000 address at word precision: address 0 is the first word in the address
|
||||
space, address 1 is the second word (i.e. the third and fourth bytes) in
|
||||
the address space, etc.
|
||||
*/
|
||||
forceinline uint32_t word_address() const {
|
||||
return (address ? (*address) & 0x00fffffe : 0) >> 1;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the address of the word or byte being accessed at byte precision,
|
||||
in the endianness of the host platform.
|
||||
|
||||
So: if this is a word access, and the 68000 wants to select the word at address
|
||||
@c n, this will evaluate to @c n regardless of the host machine's endianness..
|
||||
|
||||
If this is a byte access and the host machine is big endian it will evalue to @c n.
|
||||
|
||||
If the host machine is little endian then it will evaluate to @c n^1.
|
||||
*/
|
||||
forceinline uint32_t host_endian_byte_address() const {
|
||||
#if TARGET_RT_BIG_ENDIAN
|
||||
return *address & 0xffffff;
|
||||
#else
|
||||
return (*address ^ (1 & operation & SelectByte)) & 0xffffff;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the value on the data bus — all 16 bits, with any inactive lines
|
||||
(as er the upper and lower data selects) being represented by 1s. Assumes
|
||||
this is a write cycle.
|
||||
*/
|
||||
forceinline uint16_t value16() const {
|
||||
const uint16_t values[] = { value->w, uint16_t((value->b << 8) | value->b) };
|
||||
return values[operation & SelectByte];
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the value currently on the high 8 lines of the data bus if any;
|
||||
@c 0xff otherwise. Assumes this is a write cycle.
|
||||
*/
|
||||
forceinline uint8_t value8_high() const {
|
||||
const uint8_t values[] = { uint8_t(value->w), value->b};
|
||||
return values[operation & SelectByte];
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the value currently on the low 8 lines of the data bus if any;
|
||||
@c 0xff otherwise. Assumes this is a write cycle.
|
||||
*/
|
||||
forceinline uint8_t value8_low() const {
|
||||
const uint8_t values[] = { uint8_t(value->w), value->b};
|
||||
return values[operation & SelectByte];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets to @c value the 8- or 16-bit portion of the supplied value that is
|
||||
currently being read. Assumes this is a read cycle.
|
||||
*/
|
||||
forceinline void set_value16(uint16_t v) const {
|
||||
assert(operation & Microcycle::Read);
|
||||
if(operation & Microcycle::SelectWord) {
|
||||
value->w = v;
|
||||
} else {
|
||||
value->b = uint8_t(v >> byte_shift());
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to set_value16((v << 8) | 0x00ff).
|
||||
*/
|
||||
forceinline void set_value8_high(uint8_t v) const {
|
||||
assert(operation & Microcycle::Read);
|
||||
if(operation & Microcycle::SelectWord) {
|
||||
value->w = uint16_t(0x00ff | (v << 8));
|
||||
} else {
|
||||
value->b = uint8_t(v | (0xff00 >> ((*address & 1) << 3)));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to set_value16((v) | 0xff00).
|
||||
*/
|
||||
forceinline void set_value8_low(uint8_t v) const {
|
||||
assert(operation & Microcycle::Read);
|
||||
if(operation & Microcycle::SelectWord) {
|
||||
value->w = 0xff00 | v;
|
||||
} else {
|
||||
value->b = uint8_t(v | (0x00ff << ((*address & 1) << 3)));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the same value as word_address() for any Microcycle with the NewAddress or
|
||||
SameAddress flags set; undefined behaviour otherwise.
|
||||
*/
|
||||
forceinline uint32_t active_operation_word_address() const {
|
||||
return ((*address) & 0x00fffffe) >> 1;
|
||||
}
|
||||
|
||||
// PermitRead and PermitWrite are used as part of the read/write mask
|
||||
// supplied to @c apply; they are picked to be small enough values that
|
||||
// a byte can be used for storage.
|
||||
static constexpr OperationT PermitRead = 1 << 3;
|
||||
static constexpr OperationT PermitWrite = 1 << 4;
|
||||
|
||||
/*!
|
||||
Assuming this to be a cycle with a data select active, applies it to @c target
|
||||
subject to the read_write_mask, where 'applies' means:
|
||||
|
||||
* if this is a byte read, reads a single byte from @c target;
|
||||
* if this is a word read, reads a word (in the host platform's endianness) from @c target; and
|
||||
* if this is a write, does the converse of a read.
|
||||
*/
|
||||
forceinline void apply(uint8_t *target, OperationT read_write_mask = PermitRead | PermitWrite) const {
|
||||
assert( (operation & (SelectWord | SelectByte)) != (SelectWord | SelectByte));
|
||||
|
||||
switch((operation | read_write_mask) & (SelectWord | SelectByte | Read | PermitRead | PermitWrite)) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case SelectWord | Read | PermitRead:
|
||||
case SelectWord | Read | PermitRead | PermitWrite:
|
||||
value->w = *reinterpret_cast<uint16_t *>(target);
|
||||
break;
|
||||
case SelectByte | Read | PermitRead:
|
||||
case SelectByte | Read | PermitRead | PermitWrite:
|
||||
value->b = *target;
|
||||
break;
|
||||
case SelectWord | PermitWrite:
|
||||
case SelectWord | PermitWrite | PermitRead:
|
||||
*reinterpret_cast<uint16_t *>(target) = value->w;
|
||||
break;
|
||||
case SelectByte | PermitWrite:
|
||||
case SelectByte | PermitWrite | PermitRead:
|
||||
*target = value->b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
This is the prototype for a 68000 bus handler; real bus handlers can descend from this
|
||||
in order to get default implementations of any changes that may occur in the expected interface.
|
||||
*/
|
||||
class BusHandler {
|
||||
public:
|
||||
/*!
|
||||
Provides the bus handler with a single Microcycle to 'perform'.
|
||||
|
||||
FC0 and FC1 are provided inside the microcycle as the IsData and IsProgram
|
||||
flags; FC2 is provided here as is_supervisor — it'll be either 0 or 1.
|
||||
*/
|
||||
HalfCycles perform_bus_operation([[maybe_unused]] const Microcycle &cycle, [[maybe_unused]] int is_supervisor) {
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
void flush() {}
|
||||
|
||||
/*!
|
||||
Provides information about the path of execution if enabled via the template.
|
||||
*/
|
||||
void will_perform([[maybe_unused]] uint32_t address, [[maybe_unused]] uint16_t opcode) {}
|
||||
};
|
||||
|
||||
struct State {
|
||||
InstructionSet::M68k::RegisterSet registers;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/68000Mk2Storage.hpp"
|
||||
|
||||
namespace CPU {
|
||||
namespace MC68000Mk2 {
|
||||
|
||||
/*!
|
||||
Provides an emulation of the 68000 with accurate bus logic via the @c BusHandler, subject to the following template parameters:
|
||||
|
||||
@c dtack_is_implicit means that the 68000 won't wait around for DTACK during any data access. BERR or VPA may still be
|
||||
signalled at the appropriate moment and will override the implicit DTACK, but the processor won't spin if nothing is explicitly
|
||||
signalled. Enabling this simplifies the internal state machine and therefore improves performance; bus handlers can still indicate
|
||||
that time was spent waiting for DTACK by returning an appropriate value from @c perform_bus_operation.
|
||||
|
||||
@c permit_overrun allows the 68000 to be relaxed in how it interprets the constraint specified by the @c duration parameter to
|
||||
@c run_for. If this is @c false, @c run_for will always return as soon as it has called @c perform_bus_operation with whichever
|
||||
operation is ongoing at the requested stopping time. If it is @c true then the 68000 is granted leeway to overrun the requested stop
|
||||
time by 'a small amount' as and when it is a benefit to do so. Any overrun will be subtracted from the next @c run_for.
|
||||
|
||||
In practice this allows the implementation to avoid a bunch of conditional checks by considering whether it needs to exit less frequently.
|
||||
|
||||
Teleologically, it's expected that most — if not all — single-processor machines can permit overruns for a performance boost with
|
||||
no user-visible difference.
|
||||
|
||||
@c signal_will_perform indicates whether the 68000 will call the bus handler's @c will_perform. Unlike the popular 8-bit CPUs,
|
||||
the 68000 doesn't offer an indication of when instruction dispatch will occur so this is provided *for testing purposes*. It allows test cases
|
||||
to track execution and inspect internal state in a wholly unrealistic fashion.
|
||||
*/
|
||||
template <class BusHandler, bool dtack_is_implicit = true, bool permit_overrun = true, bool signal_will_perform = false>
|
||||
class Processor: private ProcessorBase {
|
||||
public:
|
||||
Processor(BusHandler &bus_handler) : ProcessorBase(), bus_handler_(bus_handler) {}
|
||||
|
||||
void run_for(HalfCycles duration);
|
||||
|
||||
/// @returns The current processor state.
|
||||
CPU::MC68000Mk2::State get_state();
|
||||
|
||||
/// Sets the current processor state.
|
||||
void set_state(const CPU::MC68000Mk2::State &);
|
||||
|
||||
/// Sets all registers to the values provided, fills the prefetch queue and ensures the
|
||||
/// next action the processor will take is to decode whatever is in the queue.
|
||||
///
|
||||
/// The queue is filled synchronously, during this call, causing calls to the bus handler.
|
||||
void decode_from_state(const InstructionSet::M68k::RegisterSet &);
|
||||
|
||||
// TODO: bus ack/grant, halt,
|
||||
|
||||
/// Sets the DTack line — @c true for active, @c false for inactive.
|
||||
inline void set_dtack(bool dtack) {
|
||||
dtack_ = dtack;
|
||||
}
|
||||
|
||||
/// Sets the VPA (valid peripheral address) line — @c true for active, @c false for inactive.
|
||||
inline void set_is_peripheral_address(bool is_peripheral_address) {
|
||||
vpa_ = is_peripheral_address;
|
||||
}
|
||||
|
||||
/// Sets the bus error line — @c true for active, @c false for inactive.
|
||||
inline void set_bus_error(bool bus_error) {
|
||||
berr_ = bus_error;
|
||||
}
|
||||
|
||||
/// Sets the interrupt lines, IPL0, IPL1 and IPL2.
|
||||
inline void set_interrupt_level(int interrupt_level) {
|
||||
bus_interrupt_level_ = interrupt_level;
|
||||
}
|
||||
|
||||
/// @returns The current phase of the E clock; this will be a number of
|
||||
/// half-cycles between 0 and 19 inclusive, indicating how far the 68000
|
||||
/// is into the current E cycle.
|
||||
///
|
||||
/// This is guaranteed to be 0 at initial 68000 construction. It is not guaranteed
|
||||
/// to return the correct result if called during a bus transaction.
|
||||
HalfCycles get_e_clock_phase() {
|
||||
return e_clock_phase_;
|
||||
}
|
||||
|
||||
private:
|
||||
BusHandler &bus_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/68000Mk2Implementation.hpp"
|
||||
|
||||
#endif /* _8000Mk2_h */
|
2787
Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
Normal file
2787
Processors/68000Mk2/Implementation/68000Mk2Implementation.hpp
Normal file
File diff suppressed because it is too large
Load Diff
220
Processors/68000Mk2/Implementation/68000Mk2Storage.hpp
Normal file
220
Processors/68000Mk2/Implementation/68000Mk2Storage.hpp
Normal file
@ -0,0 +1,220 @@
|
||||
//
|
||||
// 68000Mk2Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _8000Mk2Storage_h
|
||||
#define _8000Mk2Storage_h
|
||||
|
||||
#include "../../../InstructionSets/M68k/Decoder.hpp"
|
||||
#include "../../../InstructionSets/M68k/Perform.hpp"
|
||||
#include "../../../InstructionSets/M68k/Status.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace CPU {
|
||||
namespace MC68000Mk2 {
|
||||
|
||||
struct ProcessorBase: public InstructionSet::M68k::NullFlowController {
|
||||
ProcessorBase() {
|
||||
read_program_announce.address = read_program.address = &program_counter_.l;
|
||||
}
|
||||
|
||||
int state_ = std::numeric_limits<int>::min();
|
||||
|
||||
/// Counts time left on the clock before the current batch of processing
|
||||
/// is complete; may be less than zero.
|
||||
HalfCycles time_remaining_;
|
||||
|
||||
/// E clock phase.
|
||||
HalfCycles e_clock_phase_;
|
||||
|
||||
/// Current supervisor state, for direct provision to the bus handler.
|
||||
int is_supervisor_ = 1;
|
||||
|
||||
// A decoder for instructions, plus all collected information about the
|
||||
// current instruction.
|
||||
InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> decoder_;
|
||||
InstructionSet::M68k::Preinstruction instruction_;
|
||||
uint16_t opcode_;
|
||||
uint8_t operand_flags_;
|
||||
SlicedInt32 instruction_address_;
|
||||
|
||||
// Register state.
|
||||
InstructionSet::M68k::Status status_;
|
||||
SlicedInt32 program_counter_;
|
||||
SlicedInt32 registers_[16]{}; // D0–D7 followed by A0–A7.
|
||||
SlicedInt32 stack_pointers_[2];
|
||||
|
||||
/// Current state of the DTACK input.
|
||||
bool dtack_ = false;
|
||||
/// Current state of the VPA input.
|
||||
bool vpa_ = false;
|
||||
/// Current state of the BERR input.
|
||||
bool berr_ = false;
|
||||
/// Current input interrupt level.
|
||||
int bus_interrupt_level_ = 0;
|
||||
|
||||
// Whether to trace at the end of this instruction.
|
||||
InstructionSet::M68k::Status::FlagT should_trace_ = 0;
|
||||
|
||||
// I don't have great information on the 68000 interrupt latency; as a first
|
||||
// guess, capture the bus interrupt level upon every prefetch, and use that for
|
||||
// the inner-loop decision.
|
||||
int captured_interrupt_level_ = 0;
|
||||
|
||||
/// Contains the prefetch queue; the most-recently fetched thing is the
|
||||
/// low portion of this word, and the thing fetched before that has
|
||||
/// proceeded to the high portion.
|
||||
SlicedInt32 prefetch_;
|
||||
|
||||
// Temporary storage for the current instruction's operands
|
||||
// and the corresponding effective addresses.
|
||||
CPU::SlicedInt32 operand_[2];
|
||||
CPU::SlicedInt32 effective_address_[2];
|
||||
|
||||
/// If currently in the wait-for-DTACK state, this indicates where to go
|
||||
/// upon receipt of DTACK or VPA. BERR will automatically segue
|
||||
/// into the proper exception.
|
||||
int post_dtack_state_ = 0;
|
||||
|
||||
/// If using CalcEffectiveAddress, this is the state to adopt after the
|
||||
/// effective address for next_operand_ has been calculated.
|
||||
int post_ea_state_ = 0;
|
||||
|
||||
/// The perform state for this operation.
|
||||
int perform_state_ = 0;
|
||||
|
||||
/// When fetching or storing operands, this is the next one to fetch
|
||||
/// or store.
|
||||
int next_operand_ = -1;
|
||||
|
||||
/// Storage for a temporary address, which can't be a local because it'll
|
||||
/// be used to populate microcycles, which may persist beyond an entry
|
||||
/// and exit of run_for (especially between an address announcement, and
|
||||
/// a data select).
|
||||
SlicedInt32 temporary_address_;
|
||||
|
||||
/// Storage for a temporary value; primarily used by MOVEP to split a 32-bit
|
||||
/// source into bus-compatible byte units.
|
||||
SlicedInt32 temporary_value_;
|
||||
|
||||
/// A record of the exception to trigger.
|
||||
int exception_vector_ = 0;
|
||||
|
||||
/// Transient storage for exception processing.
|
||||
SlicedInt16 captured_status_;
|
||||
|
||||
/// An internal flag used during various dynamically-sized instructions
|
||||
/// (e.g. BCHG, DIVU) to indicate how much additional processing happened;
|
||||
/// this is measured in microcycles.
|
||||
int dynamic_instruction_length_ = 0;
|
||||
|
||||
/// Two bits of state for MOVEM, being the curent register and what to
|
||||
/// add to it to get to the next register.
|
||||
int register_index_ = 0, register_delta_ = 0;
|
||||
|
||||
// A lookup table that aids with effective address calculation in
|
||||
// predecrement and postincrement modes; index as [size][register]
|
||||
// and note that [0][7] is 2 rather than 1.
|
||||
static constexpr uint32_t address_increments[3][8] = {
|
||||
{ 1, 1, 1, 1, 1, 1, 1, 2, },
|
||||
{ 2, 2, 2, 2, 2, 2, 2, 2, },
|
||||
{ 4, 4, 4, 4, 4, 4, 4, 4, },
|
||||
};
|
||||
|
||||
// A lookup table that ensures write-back to data registers affects
|
||||
// only the correct bits.
|
||||
static constexpr uint32_t size_masks[3] = { 0xff, 0xffff, 0xffff'ffff };
|
||||
|
||||
// Assumptions used by the lookup tables above:
|
||||
static_assert(int(InstructionSet::M68k::DataSize::Byte) == 0);
|
||||
static_assert(int(InstructionSet::M68k::DataSize::Word) == 1);
|
||||
static_assert(int(InstructionSet::M68k::DataSize::LongWord) == 2);
|
||||
|
||||
/// Used by some dedicated read-modify-write perform patterns to
|
||||
/// determine the size of the bus operation.
|
||||
Microcycle::OperationT select_flag_ = 0;
|
||||
|
||||
// Captured bus/address-error state.
|
||||
Microcycle bus_error_;
|
||||
|
||||
// Flow controller methods implemented.
|
||||
using Preinstruction = InstructionSet::M68k::Preinstruction;
|
||||
template <typename IntT> void did_mulu(IntT);
|
||||
template <typename IntT> void did_muls(IntT);
|
||||
inline void did_chk(bool, bool);
|
||||
inline void did_scc(bool);
|
||||
template <typename IntT> void did_shift(int);
|
||||
template <bool did_overflow> void did_divu(uint32_t, uint32_t);
|
||||
template <bool did_overflow> void did_divs(int32_t, int32_t);
|
||||
inline void did_bit_op(int);
|
||||
inline void did_update_status();
|
||||
template <typename IntT> void complete_bcc(bool, IntT);
|
||||
inline void complete_dbcc(bool, bool, int16_t);
|
||||
inline void move_to_usp(uint32_t);
|
||||
inline void move_from_usp(uint32_t &);
|
||||
inline void tas(Preinstruction, uint32_t);
|
||||
template <bool use_current_instruction_pc = true> void raise_exception(int);
|
||||
|
||||
// These aren't implemented because the specific details of the implementation
|
||||
// mean that the performer call-out isn't necessary.
|
||||
template <typename IntT> void movep(Preinstruction, uint32_t, uint32_t) {}
|
||||
template <typename IntT> void movem_toM(Preinstruction, uint32_t, uint32_t) {}
|
||||
template <typename IntT> void movem_toR(Preinstruction, uint32_t, uint32_t) {}
|
||||
void jsr(uint32_t) {}
|
||||
void bsr(uint32_t) {}
|
||||
void jmp(uint32_t) {}
|
||||
inline void pea(uint32_t) {}
|
||||
inline void link(Preinstruction, uint32_t) {}
|
||||
inline void unlink(uint32_t &) {}
|
||||
inline void rtr() {}
|
||||
inline void rte() {}
|
||||
inline void rts() {}
|
||||
inline void reset() {}
|
||||
inline void stop() {}
|
||||
|
||||
// Some microcycles that will be modified as required and used in the main loop;
|
||||
// the semantics of a switch statement make in-place declarations awkward and
|
||||
// some of these may persist across multiple calls to run_for.
|
||||
Microcycle idle{0};
|
||||
|
||||
// Read a program word. All accesses via the program counter are word sized.
|
||||
Microcycle read_program_announce {
|
||||
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsProgram
|
||||
};
|
||||
Microcycle read_program {
|
||||
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsProgram
|
||||
};
|
||||
|
||||
// Read a data word or byte.
|
||||
Microcycle access_announce {
|
||||
Microcycle::Read | Microcycle::NewAddress | Microcycle::IsData
|
||||
};
|
||||
Microcycle access {
|
||||
Microcycle::Read | Microcycle::SameAddress | Microcycle::SelectWord | Microcycle::IsData
|
||||
};
|
||||
|
||||
// TAS.
|
||||
Microcycle tas_cycles[5] = {
|
||||
{ Microcycle::Read | Microcycle::NewAddress | Microcycle::IsData },
|
||||
{ Microcycle::Read | Microcycle::SameAddress | Microcycle::IsData | Microcycle::SelectByte },
|
||||
{ Microcycle::SameAddress },
|
||||
{ Microcycle::SameAddress | Microcycle::IsData },
|
||||
{ Microcycle::SameAddress | Microcycle::IsData | Microcycle::SelectByte },
|
||||
};
|
||||
|
||||
// Reset.
|
||||
Microcycle reset_cycle { Microcycle::Reset, HalfCycles(248) };
|
||||
|
||||
// Holding spot when awaiting DTACK/etc.
|
||||
Microcycle awaiting_dtack;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _8000Mk2Storage_h */
|
Loading…
Reference in New Issue
Block a user