mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-15 14:27:29 +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:
@@ -29,7 +29,7 @@ enum Exception {
|
|||||||
FormatError = 14,
|
FormatError = 14,
|
||||||
UninitialisedInterrupt = 15,
|
UninitialisedInterrupt = 15,
|
||||||
SpuriousInterrupt = 24,
|
SpuriousInterrupt = 24,
|
||||||
InterruptAutovectorBase = 25,
|
InterruptAutovectorBase = 25, // This is the vector for interrupt level _1_.
|
||||||
TrapBase = 32,
|
TrapBase = 32,
|
||||||
FPBranchOrSetOnUnorderedCondition = 48,
|
FPBranchOrSetOnUnorderedCondition = 48,
|
||||||
FPInexactResult = 49,
|
FPInexactResult = 49,
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
#include "Instruction.hpp"
|
#include "Instruction.hpp"
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "Perform.hpp"
|
#include "Perform.hpp"
|
||||||
|
#include "RegisterSet.hpp"
|
||||||
#include "Status.hpp"
|
#include "Status.hpp"
|
||||||
|
|
||||||
namespace InstructionSet {
|
namespace InstructionSet {
|
||||||
@@ -80,17 +81,9 @@ template <Model model, typename BusHandler> class Executor {
|
|||||||
/// Sets the current input interrupt level.
|
/// Sets the current input interrupt level.
|
||||||
void set_interrupt_level(int);
|
void set_interrupt_level(int);
|
||||||
|
|
||||||
// TODO: this will likely be shared in some capacity with the bus-accurate versions of the 680x0;
|
// State for the executor is just the register set.
|
||||||
// therefore it will almost certainly be factored out in future.
|
RegisterSet get_state();
|
||||||
struct Registers {
|
void set_state(const RegisterSet &);
|
||||||
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 &);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class State: public NullFlowController {
|
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>
|
template <Model model, typename BusHandler>
|
||||||
void Executor<model, BusHandler>::set_interrupt_level(int level) {
|
void Executor<model, BusHandler>::set_interrupt_level(int level) {
|
||||||
state_.interrupt_input_ = 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>
|
template <Model model, typename BusHandler>
|
||||||
@@ -102,8 +102,8 @@ void Executor<model, BusHandler>::run_for_instructions(int count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <Model model, typename BusHandler>
|
template <Model model, typename BusHandler>
|
||||||
typename Executor<model, BusHandler>::Registers Executor<model, BusHandler>::get_state() {
|
RegisterSet Executor<model, BusHandler>::get_state() {
|
||||||
Registers result;
|
RegisterSet result;
|
||||||
|
|
||||||
for(int c = 0; c < 8; c++) {
|
for(int c = 0; c < 8; c++) {
|
||||||
result.data[c] = Dn(c).l;
|
result.data[c] = Dn(c).l;
|
||||||
@@ -122,7 +122,7 @@ typename Executor<model, BusHandler>::Registers Executor<model, BusHandler>::get
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <Model model, typename BusHandler>
|
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++) {
|
for(int c = 0; c < 8; c++) {
|
||||||
Dn(c).l = state.data[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.
|
// also include the scale field even if not.
|
||||||
const auto extension = read_pc<uint16_t>();
|
const auto extension = read_pc<uint16_t>();
|
||||||
const auto offset = int8_t(extension);
|
const auto offset = int8_t(extension);
|
||||||
const int register_index = (extension >> 12) & 7;
|
const int register_index = (extension >> 12) & 15;
|
||||||
const uint32_t displacement = registers[register_index + ((extension >> 12) & 0x08)].l;
|
const uint32_t displacement = registers[register_index].l;
|
||||||
const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement);
|
const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement);
|
||||||
return offset + sized_displacement;
|
return offset + sized_displacement;
|
||||||
}
|
}
|
||||||
@@ -324,7 +324,7 @@ template <Model model, typename BusHandler>
|
|||||||
void Executor<model, BusHandler>::State::run(int &count) {
|
void Executor<model, BusHandler>::State::run(int &count) {
|
||||||
while(count--) {
|
while(count--) {
|
||||||
// Check for a new interrupt.
|
// 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);
|
const int vector = bus_handler_.acknowlege_interrupt(interrupt_input);
|
||||||
if(vector >= 0) {
|
if(vector >= 0) {
|
||||||
raise_exception<false>(vector);
|
raise_exception<false>(vector);
|
||||||
@@ -483,7 +483,7 @@ template <Model model, typename BusHandler>
|
|||||||
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
|
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
|
||||||
sp.l -= 4;
|
sp.l -= 4;
|
||||||
write<uint32_t>(sp.l, program_counter.l);
|
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>
|
template <Model model, typename BusHandler>
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
namespace InstructionSet {
|
namespace InstructionSet {
|
||||||
namespace M68k {
|
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) {
|
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
@@ -24,8 +24,6 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
|
|||||||
case Operation::PEA:
|
case Operation::PEA:
|
||||||
case Operation::JMP: case Operation::JSR:
|
case Operation::JMP: case Operation::JSR:
|
||||||
case Operation::MOVEPw: case Operation::MOVEPl:
|
case Operation::MOVEPw: case Operation::MOVEPl:
|
||||||
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
|
|
||||||
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
|
|
||||||
case Operation::TAS:
|
case Operation::TAS:
|
||||||
case Operation::RTR: case Operation::RTS: case Operation::RTE:
|
case Operation::RTR: case Operation::RTS: case Operation::RTE:
|
||||||
return 0;
|
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::Bccb: case Operation::Bccw: case Operation::Bccl:
|
||||||
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
|
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
|
||||||
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
|
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
|
||||||
|
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
|
||||||
|
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
|
||||||
return FetchOp1;
|
return FetchOp1;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Single-operand write.
|
// Single-operand write.
|
||||||
//
|
//
|
||||||
case Operation::MOVEfromSR: case Operation::MOVEfromUSP:
|
case Operation::MOVEfromUSP:
|
||||||
case Operation::Scc:
|
|
||||||
return StoreOp1;
|
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::LSLm: case Operation::LSRm:
|
||||||
case Operation::ROLm: case Operation::RORm:
|
case Operation::ROLm: case Operation::RORm:
|
||||||
case Operation::ROXLm: case Operation::ROXRm:
|
case Operation::ROXLm: case Operation::ROXRm:
|
||||||
|
case Operation::Scc:
|
||||||
return FetchOp1 | StoreOp1;
|
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:
|
case Operation::CLRb: case Operation::CLRw: case Operation::CLRl:
|
||||||
if constexpr (model == Model::M68000) {
|
if constexpr (model == Model::M68000) {
|
||||||
return FetchOp1 | StoreOp1;
|
return FetchOp1 | StoreOp1;
|
||||||
|
@@ -12,8 +12,9 @@
|
|||||||
namespace InstructionSet {
|
namespace InstructionSet {
|
||||||
namespace M68k {
|
namespace M68k {
|
||||||
|
|
||||||
constexpr DataSize operand_size(Operation operation) {
|
template <Operation t_operation>
|
||||||
switch(operation) {
|
constexpr DataSize operand_size(Operation r_operation) {
|
||||||
|
switch((t_operation == Operation::Undefined) ? r_operation : t_operation) {
|
||||||
// These are given a value arbitrarily, to
|
// These are given a value arbitrarily, to
|
||||||
// complete the switch statement.
|
// complete the switch statement.
|
||||||
case Operation::Undefined:
|
case Operation::Undefined:
|
||||||
|
@@ -228,7 +228,7 @@ template <
|
|||||||
|
|
||||||
status.zero_result = dest.l & bit_mask;
|
status.zero_result = dest.l & bit_mask;
|
||||||
dest.l &= ~bit_mask;
|
dest.l &= ~bit_mask;
|
||||||
flow_controller.did_bit_op(bit_position);
|
flow_controller.did_bit_op(int(bit_position));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Operation::BCHG: {
|
case Operation::BCHG: {
|
||||||
@@ -236,7 +236,7 @@ template <
|
|||||||
|
|
||||||
status.zero_result = dest.l & bit_mask;
|
status.zero_result = dest.l & bit_mask;
|
||||||
dest.l ^= bit_mask;
|
dest.l ^= bit_mask;
|
||||||
flow_controller.did_bit_op(bit_position);
|
flow_controller.did_bit_op(int(bit_position));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Operation::BSET: {
|
case Operation::BSET: {
|
||||||
@@ -244,7 +244,7 @@ template <
|
|||||||
|
|
||||||
status.zero_result = dest.l & bit_mask;
|
status.zero_result = dest.l & bit_mask;
|
||||||
dest.l |= bit_mask;
|
dest.l |= bit_mask;
|
||||||
flow_controller.did_bit_op(bit_position);
|
flow_controller.did_bit_op(int(bit_position));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
#undef get_mask
|
#undef get_mask
|
||||||
@@ -252,29 +252,29 @@ template <
|
|||||||
case Operation::Bccb:
|
case Operation::Bccb:
|
||||||
flow_controller.template complete_bcc<int8_t>(
|
flow_controller.template complete_bcc<int8_t>(
|
||||||
status.evaluate_condition(instruction.condition()),
|
status.evaluate_condition(instruction.condition()),
|
||||||
src.b);
|
int8_t(src.b));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Operation::Bccw:
|
case Operation::Bccw:
|
||||||
flow_controller.template complete_bcc<int16_t>(
|
flow_controller.template complete_bcc<int16_t>(
|
||||||
status.evaluate_condition(instruction.condition()),
|
status.evaluate_condition(instruction.condition()),
|
||||||
src.w);
|
int16_t(src.w));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Operation::Bccl:
|
case Operation::Bccl:
|
||||||
flow_controller.template complete_bcc<int32_t>(
|
flow_controller.template complete_bcc<int32_t>(
|
||||||
status.evaluate_condition(instruction.condition()),
|
status.evaluate_condition(instruction.condition()),
|
||||||
src.l);
|
int32_t(src.l));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Operation::BSRb:
|
case Operation::BSRb:
|
||||||
flow_controller.bsr(int8_t(src.b) + 2);
|
flow_controller.bsr(uint32_t(int8_t(src.b)));
|
||||||
break;
|
break;
|
||||||
case Operation::BSRw:
|
case Operation::BSRw:
|
||||||
flow_controller.bsr(int16_t(src.w) + 2);
|
flow_controller.bsr(uint32_t(int16_t(src.w)));
|
||||||
break;
|
break;
|
||||||
case Operation::BSRl:
|
case Operation::BSRl:
|
||||||
flow_controller.bsr(src.l + 2);
|
flow_controller.bsr(src.l);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Operation::DBcc: {
|
case Operation::DBcc: {
|
||||||
@@ -294,9 +294,11 @@ template <
|
|||||||
int16_t(dest.w));
|
int16_t(dest.w));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case Operation::Scc:
|
case Operation::Scc: {
|
||||||
src.b = status.evaluate_condition(instruction.condition()) ? 0xff : 0x00;
|
const bool condition = status.evaluate_condition(instruction.condition());
|
||||||
break;
|
src.b = condition ? 0xff : 0x00;
|
||||||
|
flow_controller.did_scc(condition);
|
||||||
|
} break;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CLRs: store 0 to the destination, set the zero flag, and clear
|
CLRs: store 0 to the destination, set the zero flag, and clear
|
||||||
@@ -520,17 +522,18 @@ template <
|
|||||||
#define DIV(Type16, Type32, flow_function) { \
|
#define DIV(Type16, Type32, flow_function) { \
|
||||||
status.carry_flag = 0; \
|
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.negative_flag = status.overflow_flag = 0; \
|
||||||
status.zero_result = 1; \
|
status.zero_result = 1; \
|
||||||
flow_controller.raise_exception(Exception::IntegerDivideByZero); \
|
flow_controller.raise_exception(Exception::IntegerDivideByZero); \
|
||||||
|
flow_controller.template flow_function<false>(dividend, divisor); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
const auto dividend = Type32(dest.l); \
|
const auto quotient = int64_t(dividend) / int64_t(divisor); \
|
||||||
const auto divisor = Type32(Type16(src.w)); \
|
|
||||||
const auto quotient = dividend / divisor; \
|
|
||||||
\
|
|
||||||
if(quotient != Type32(Type16(quotient))) { \
|
if(quotient != Type32(Type16(quotient))) { \
|
||||||
status.overflow_flag = 1; \
|
status.overflow_flag = 1; \
|
||||||
flow_controller.template flow_function<true>(dividend, divisor); \
|
flow_controller.template flow_function<true>(dividend, divisor); \
|
||||||
@@ -553,7 +556,7 @@ template <
|
|||||||
|
|
||||||
// TRAP, which is a nicer form of ILLEGAL.
|
// TRAP, which is a nicer form of ILLEGAL.
|
||||||
case Operation::TRAP:
|
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;
|
break;
|
||||||
|
|
||||||
case Operation::TRAPV: {
|
case Operation::TRAPV: {
|
||||||
@@ -678,7 +681,7 @@ template <
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
case Operation::LINKw:
|
case Operation::LINKw:
|
||||||
flow_controller.link(instruction, int16_t(dest.w));
|
flow_controller.link(instruction, uint32_t(int16_t(dest.w)));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Operation::UNLINK:
|
case Operation::UNLINK:
|
||||||
@@ -825,14 +828,14 @@ template <
|
|||||||
set_neg_zero(v, m); \
|
set_neg_zero(v, m); \
|
||||||
status.overflow_flag = (Status::FlagT(value) ^ status.zero_result) & Status::FlagT(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; \
|
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 set_flags_w(t) set_flags(src.w, 0x8000, t)
|
||||||
|
|
||||||
#define asl(destination, size) {\
|
#define asl(destination, size) {\
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
const auto value = destination; \
|
const auto value = destination; \
|
||||||
\
|
\
|
||||||
if(!shift_count) { \
|
if(!shift_count) { \
|
||||||
@@ -862,7 +865,7 @@ template <
|
|||||||
case Operation::ASLl: asl(dest.l, 32); break;
|
case Operation::ASLl: asl(dest.l, 32); break;
|
||||||
|
|
||||||
#define asr(destination, size) {\
|
#define asr(destination, size) {\
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
const auto value = destination; \
|
const auto value = destination; \
|
||||||
\
|
\
|
||||||
if(!shift_count) { \
|
if(!shift_count) { \
|
||||||
@@ -906,7 +909,7 @@ template <
|
|||||||
status.carry_flag = value & (t);
|
status.carry_flag = value & (t);
|
||||||
|
|
||||||
#define lsl(destination, size) {\
|
#define lsl(destination, size) {\
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
const auto value = destination; \
|
const auto value = destination; \
|
||||||
\
|
\
|
||||||
if(!shift_count) { \
|
if(!shift_count) { \
|
||||||
@@ -930,7 +933,7 @@ template <
|
|||||||
case Operation::LSLl: lsl(dest.l, 32); break;
|
case Operation::LSLl: lsl(dest.l, 32); break;
|
||||||
|
|
||||||
#define lsr(destination, size) {\
|
#define lsr(destination, size) {\
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
const auto value = destination; \
|
const auto value = destination; \
|
||||||
\
|
\
|
||||||
if(!shift_count) { \
|
if(!shift_count) { \
|
||||||
@@ -954,7 +957,7 @@ template <
|
|||||||
case Operation::LSRl: lsr(dest.l, 32); break;
|
case Operation::LSRl: lsr(dest.l, 32); break;
|
||||||
|
|
||||||
#define rol(destination, size) { \
|
#define rol(destination, size) { \
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
const auto value = destination; \
|
const auto value = destination; \
|
||||||
\
|
\
|
||||||
if(!shift_count) { \
|
if(!shift_count) { \
|
||||||
@@ -982,7 +985,7 @@ template <
|
|||||||
case Operation::ROLl: rol(dest.l, 32); break;
|
case Operation::ROLl: rol(dest.l, 32); break;
|
||||||
|
|
||||||
#define ror(destination, size) { \
|
#define ror(destination, size) { \
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
const auto value = destination; \
|
const auto value = destination; \
|
||||||
\
|
\
|
||||||
if(!shift_count) { \
|
if(!shift_count) { \
|
||||||
@@ -1010,7 +1013,7 @@ template <
|
|||||||
case Operation::RORl: ror(dest.l, 32); break;
|
case Operation::RORl: ror(dest.l, 32); break;
|
||||||
|
|
||||||
#define roxl(destination, size) { \
|
#define roxl(destination, size) { \
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
\
|
\
|
||||||
shift_count %= (size + 1); \
|
shift_count %= (size + 1); \
|
||||||
uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \
|
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;
|
case Operation::ROXLl: roxl(dest.l, 32); break;
|
||||||
|
|
||||||
#define roxr(destination, size) { \
|
#define roxr(destination, size) { \
|
||||||
decode_shift_count(); \
|
decode_shift_count(decltype(destination)); \
|
||||||
\
|
\
|
||||||
shift_count %= (size + 1); \
|
shift_count %= (size + 1); \
|
||||||
uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \
|
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;
|
const char *instruction;
|
||||||
|
|
||||||
switch(operation) {
|
switch(operation) {
|
||||||
case Operation::Undefined: instruction = "None"; break;
|
case Operation::Undefined: return "None";
|
||||||
case Operation::NOP: instruction = "NOP"; break;
|
case Operation::NOP: instruction = "NOP"; break;
|
||||||
case Operation::ABCD: instruction = "ABCD"; break;
|
case Operation::ABCD: instruction = "ABCD"; break;
|
||||||
case Operation::SBCD: instruction = "SBCD"; 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,
|
/// 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,
|
/// then writing zero or one, the size determines the data size of the operands only,
|
||||||
/// not any other accesses.
|
/// 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>
|
template <Operation t_op = Operation::Undefined>
|
||||||
constexpr uint32_t quick(uint16_t instruction, Operation r_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.
|
Unusual bus sequences, such as TAS or MOVEM, are not described here.
|
||||||
*/
|
*/
|
||||||
template <Model model, Operation t_operation = Operation::Undefined>
|
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.
|
/// Lists the various condition codes used by the 680x0.
|
||||||
enum class Condition {
|
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.
|
/// 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) {}
|
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.
|
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by
|
||||||
void did_shift([[maybe_unused]] int bit_count) {}
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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) {}
|
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;
|
/// 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.
|
/// this gives an opportunity to track the supervisor flag.
|
||||||
void did_update_status() {}
|
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 Negative = 1 << 3;
|
||||||
static constexpr uint16_t Extend = 1 << 4;
|
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 Supervisor = 1 << 13;
|
||||||
static constexpr uint16_t Trace = 1 << 15;
|
static constexpr uint16_t Trace = 1 << 15;
|
||||||
|
|
||||||
@@ -93,8 +95,25 @@ struct Status {
|
|||||||
return is_supervisor;
|
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.
|
/// Evaluates @c condition.
|
||||||
bool evaluate_condition(Condition condition) {
|
constexpr bool evaluate_condition(Condition condition) const {
|
||||||
switch(condition) {
|
switch(condition) {
|
||||||
default:
|
default:
|
||||||
case Condition::True: return true;
|
case Condition::True: return true;
|
||||||
@@ -119,6 +138,13 @@ struct Status {
|
|||||||
return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
|
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 "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||||
#include "../../../Processors/68000/68000.hpp"
|
#include "../../../Processors/68000/68000.hpp"
|
||||||
|
|
||||||
|
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
|
||||||
|
|
||||||
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
|
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||||
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
|
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
|
||||||
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
|
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
|
||||||
|
@@ -72,6 +72,14 @@ template <> union SlicedInt<uint32_t> {
|
|||||||
#endif
|
#endif
|
||||||
uint8_t b;
|
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>;
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
|
||||||
@@ -3218,6 +3222,7 @@
|
|||||||
4B79629C2819681F008130F9 /* Instruction.hpp */,
|
4B79629C2819681F008130F9 /* Instruction.hpp */,
|
||||||
4B79629D2819681F008130F9 /* Model.hpp */,
|
4B79629D2819681F008130F9 /* Model.hpp */,
|
||||||
4BB5B996281B1E3F00522DA9 /* Perform.hpp */,
|
4BB5B996281B1E3F00522DA9 /* Perform.hpp */,
|
||||||
|
4BA3AE44283317CB00328FED /* RegisterSet.hpp */,
|
||||||
4BB5B997281B1F7B00522DA9 /* Status.hpp */,
|
4BB5B997281B1F7B00522DA9 /* Status.hpp */,
|
||||||
4BB5B999281B244400522DA9 /* Implementation */,
|
4BB5B999281B244400522DA9 /* Implementation */,
|
||||||
);
|
);
|
||||||
@@ -4334,6 +4339,7 @@
|
|||||||
4B4DEC15252BFA9C004583AC /* 6502Esque */,
|
4B4DEC15252BFA9C004583AC /* 6502Esque */,
|
||||||
4BF8D4CC251C0C9C00BBE21B /* 65816 */,
|
4BF8D4CC251C0C9C00BBE21B /* 65816 */,
|
||||||
4BFF1D332233778C00838EA1 /* 68000 */,
|
4BFF1D332233778C00838EA1 /* 68000 */,
|
||||||
|
4BCA2F552832A643006C632A /* 68000Mk2 */,
|
||||||
4B77069E1EC9045B0053B588 /* Z80 */,
|
4B77069E1EC9045B0053B588 /* Z80 */,
|
||||||
);
|
);
|
||||||
name = Processors;
|
name = Processors;
|
||||||
@@ -4561,6 +4567,24 @@
|
|||||||
path = 6560;
|
path = 6560;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
4BCA6CC91D9DD9F500C2D7B2 /* Encodings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -32,105 +32,104 @@
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc302, // ABCD D2, D1
|
0xc302, // ABCD D2, D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.data[1] = 0x1234567a;
|
registers.data[1] = 0x1234567a;
|
||||||
state.data[2] = 0xf745ff78;
|
registers.data[2] = 0xf745ff78;
|
||||||
_machine->set_processor_state(state);
|
});
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Carry);
|
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||||
XCTAssertEqual(state.data[1], 0x12345658);
|
XCTAssertEqual(state.registers.data[1], 0x12345658);
|
||||||
XCTAssertEqual(state.data[2], 0xf745ff78);
|
XCTAssertEqual(state.registers.data[2], 0xf745ff78);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testABCDZero {
|
- (void)testABCDZero {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc302, // ABCD D2, D1
|
0xc302, // ABCD D2, D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.data[1] = 0x12345600;
|
registers.data[1] = 0x12345600;
|
||||||
state.data[2] = 0x12345600;
|
registers.data[2] = 0x12345600;
|
||||||
state.status = Flag::Zero;
|
registers.status = ConditionCode::Zero;
|
||||||
_machine->set_processor_state(state);
|
});
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Zero);
|
XCTAssert(state.registers.status & ConditionCode::Zero);
|
||||||
XCTAssertEqual(state.data[1], 0x12345600);
|
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||||
XCTAssertEqual(state.data[2], 0x12345600);
|
XCTAssertEqual(state.registers.data[2], 0x12345600);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testABCDNegative {
|
- (void)testABCDNegative {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc302, // ABCD D2, D1
|
0xc302, // ABCD D2, D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.data[1] = 0x12345645;
|
registers.data[1] = 0x12345645;
|
||||||
state.data[2] = 0x12345654;
|
registers.data[2] = 0x12345654;
|
||||||
state.status = Flag::Zero;
|
registers.status = ConditionCode::Zero;
|
||||||
_machine->set_processor_state(state);
|
});
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Negative);
|
XCTAssert(state.registers.status & ConditionCode::Negative);
|
||||||
XCTAssertEqual(state.data[1], 0x12345699);
|
XCTAssertEqual(state.registers.data[1], 0x12345699);
|
||||||
XCTAssertEqual(state.data[2], 0x12345654);
|
XCTAssertEqual(state.registers.data[2], 0x12345654);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testABCDWithX {
|
- (void)testABCDWithX {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc302, // ABCD D2, D1
|
0xc302, // ABCD D2, D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.data[1] = 0x12345645;
|
registers.data[1] = 0x12345645;
|
||||||
state.data[2] = 0x12345654;
|
registers.data[2] = 0x12345654;
|
||||||
state.status = Flag::Extend;
|
registers.status = ConditionCode::Extend;
|
||||||
_machine->set_processor_state(state);
|
});
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Carry);
|
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||||
XCTAssertEqual(state.data[1], 0x12345600);
|
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||||
XCTAssertEqual(state.data[2], 0x12345654);
|
XCTAssertEqual(state.registers.data[2], 0x12345654);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testABCDOverflow {
|
- (void)testABCDOverflow {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc302, // ABCD D2, D1
|
0xc302, // ABCD D2, D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.data[1] = 0x1234563e;
|
registers.data[1] = 0x1234563e;
|
||||||
state.data[2] = 0x1234563e;
|
registers.data[2] = 0x1234563e;
|
||||||
state.status = Flag::Extend;
|
registers.status = ConditionCode::Extend;
|
||||||
_machine->set_processor_state(state);
|
});
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Overflow);
|
XCTAssert(state.registers.status & ConditionCode::Overflow);
|
||||||
XCTAssertEqual(state.data[1], 0x12345683);
|
XCTAssertEqual(state.registers.data[1], 0x12345683);
|
||||||
XCTAssertEqual(state.data[2], 0x1234563e);
|
XCTAssertEqual(state.registers.data[2], 0x1234563e);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testABCDPredecDifferent {
|
- (void)testABCDPredecDifferent {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc30a, // ABCD -(A2), -(A1)
|
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(0x3000) = 0xa200;
|
||||||
*_machine->ram_at(0x4000) = 0x1900;
|
*_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);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Carry);
|
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||||
XCTAssert(state.status & Flag::Extend);
|
XCTAssert(state.registers.status & ConditionCode::Extend);
|
||||||
XCTAssertEqual(state.address[1], 0x3000);
|
XCTAssertEqual(state.registers.address[1], 0x3000);
|
||||||
XCTAssertEqual(state.address[2], 0x4000);
|
XCTAssertEqual(state.registers.address[2], 0x4000);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
|
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||||
}
|
}
|
||||||
@@ -139,18 +138,17 @@
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc309, // ABCD -(A1), -(A1)
|
0xc309, // ABCD -(A1), -(A1)
|
||||||
});
|
});
|
||||||
|
_machine->set_registers([=](auto ®isters){
|
||||||
|
registers.address[1] = 0x3002;
|
||||||
|
registers.status = ConditionCode::Extend;
|
||||||
|
});
|
||||||
*_machine->ram_at(0x3000) = 0x19a2;
|
*_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);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.status & Flag::Carry);
|
XCTAssert(state.registers.status & ConditionCode::Carry);
|
||||||
XCTAssert(state.status & Flag::Extend);
|
XCTAssert(state.registers.status & ConditionCode::Extend);
|
||||||
XCTAssertEqual(state.address[1], 0x3000);
|
XCTAssertEqual(state.registers.address[1], 0x3000);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
|
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,11 +158,10 @@
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4801 // NBCD D1
|
0x4801 // NBCD D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.status |= ccr;
|
registers.status |= ccr;
|
||||||
state.data[1] = d1;
|
registers.data[1] = d1;
|
||||||
|
});
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||||
@@ -174,32 +171,32 @@
|
|||||||
[self performNBCDd1:0x7a ccr:0];
|
[self performNBCDd1:0x7a ccr:0];
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x20);
|
XCTAssertEqual(state.registers.data[1], 0x20);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNBCD_Dn_extend {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x1234561f);
|
XCTAssertEqual(state.registers.data[1], 0x1234561f);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNBCD_Dn_zero {
|
- (void)testNBCD_Dn_zero {
|
||||||
[self performNBCDd1:0x12345600 ccr:Flag::Zero];
|
[self performNBCDd1:0x12345600 ccr:ConditionCode::Zero];
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x12345600);
|
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNBCD_Dn_negative {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x1234569a);
|
XCTAssertEqual(state.registers.data[1], 0x1234569a);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNBCD_Dn_XXXw {
|
- (void)testNBCD_Dn_XXXw {
|
||||||
@@ -213,7 +210,7 @@
|
|||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900);
|
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
|
// MARK: SBCD
|
||||||
@@ -222,49 +219,48 @@
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x8302 // SBCD D2, D1
|
0x8302 // SBCD D2, D1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.status |= ccr;
|
registers.status |= ccr;
|
||||||
state.data[1] = d1;
|
registers.data[1] = d1;
|
||||||
state.data[2] = d2;
|
registers.data[2] = d2;
|
||||||
|
});
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(6, _machine->get_cycle_count());
|
XCTAssertEqual(6, _machine->get_cycle_count());
|
||||||
XCTAssertEqual(state.data[2], d2);
|
XCTAssertEqual(state.registers.data[2], d2);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSBCD_Dn {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x12345611);
|
XCTAssertEqual(state.registers.data[1], 0x12345611);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSBCD_Dn_zero {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x12345600);
|
XCTAssertEqual(state.registers.data[1], 0x12345600);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSBCD_Dn_negative {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x12345688);
|
XCTAssertEqual(state.registers.data[1], 0x12345688);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSBCD_Dn_overflow {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], 0x12345643);
|
XCTAssertEqual(state.registers.data[1], 0x12345643);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSBCD_Dn_PreDec {
|
- (void)testSBCD_Dn_PreDec {
|
||||||
@@ -273,19 +269,18 @@
|
|||||||
});
|
});
|
||||||
*_machine->ram_at(0x3000) = 0xa200;
|
*_machine->ram_at(0x3000) = 0xa200;
|
||||||
*_machine->ram_at(0x4000) = 0x1900;
|
*_machine->ram_at(0x4000) = 0x1900;
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters){
|
||||||
state.address[1] = 0x3001;
|
registers.address[1] = 0x3001;
|
||||||
state.address[2] = 0x4001;
|
registers.address[2] = 0x4001;
|
||||||
state.status |= Flag::Extend;
|
registers.status |= ConditionCode::Extend;
|
||||||
|
});
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||||
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
|
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Negative);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#include "../../../Processors/68000/68000.hpp"
|
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
|
||||||
#include "../../../InstructionSets/M68k/Executor.hpp"
|
#include "../../../InstructionSets/M68k/Executor.hpp"
|
||||||
#include "../../../InstructionSets/M68k/Decoder.hpp"
|
#include "../../../InstructionSets/M68k/Decoder.hpp"
|
||||||
|
|
||||||
@@ -16,18 +16,18 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
//#define USE_EXISTING_IMPLEMENTATION
|
//#define USE_EXECUTOR
|
||||||
//#define MAKE_SUGGESTIONS
|
//#define MAKE_SUGGESTIONS
|
||||||
|
//#define LOG_UNTESTED
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// Binds a 68000 executor to 16mb of RAM.
|
/// Binds a 68000 executor to 16mb of RAM.
|
||||||
struct Test68000 {
|
struct TestExecutor {
|
||||||
std::array<uint8_t, 16*1024*1024> ram;
|
uint8_t *const ram;
|
||||||
InstructionSet::M68k::Executor<InstructionSet::M68k::Model::M68000, Test68000> processor;
|
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) {
|
void run_for_instructions(int instructions) {
|
||||||
processor.run_for_instructions(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
|
@interface M68000ComparativeTests : XCTestCase
|
||||||
@@ -95,23 +128,41 @@ struct Test68000 {
|
|||||||
NSMutableSet<NSString *> *_failures;
|
NSMutableSet<NSString *> *_failures;
|
||||||
NSMutableArray<NSNumber *> *_failingOpcodes;
|
NSMutableArray<NSNumber *> *_failingOpcodes;
|
||||||
NSMutableDictionary<NSNumber *, NSMutableArray<NSString *> *> *_suggestedCorrections;
|
NSMutableDictionary<NSNumber *, NSMutableArray<NSString *> *> *_suggestedCorrections;
|
||||||
|
NSMutableSet<NSNumber *> *_testedOpcodes;
|
||||||
|
|
||||||
InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> _decoder;
|
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 {
|
- (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.
|
// These will accumulate a list of failing tests and associated opcodes.
|
||||||
_failures = [[NSMutableSet alloc] init];
|
_failures = [[NSMutableSet alloc] init];
|
||||||
_failingOpcodes = [[NSMutableArray 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
|
#ifdef MAKE_SUGGESTIONS
|
||||||
_suggestedCorrections = [[NSMutableDictionary alloc] init];
|
_suggestedCorrections = [[NSMutableDictionary alloc] init];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// To limit tests run to a subset of files and/or of tests, uncomment and fill in below.
|
// To limit tests run to a subset of files and/or of tests, uncomment and fill in below.
|
||||||
// _fileSet = [NSSet setWithArray:@[@"exg.json"]];
|
// _fileSet = [NSSet setWithArray:@[@"abcd_sbcd.json"]];
|
||||||
// _testSet = [NSSet setWithArray:@[@"CHK 41a8"]];
|
// _testSet = [NSSet setWithArray:@[@"LINK.w 0007"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAll {
|
- (void)testAll {
|
||||||
@@ -126,7 +177,7 @@ struct Test68000 {
|
|||||||
// NSLog(@"Testing %@", url);
|
// NSLog(@"Testing %@", url);
|
||||||
[self testJSONAtURL:url];
|
[self testJSONAtURL:url];
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssert(_failures.count == 0);
|
XCTAssert(_failures.count == 0);
|
||||||
|
|
||||||
// Output a summary of failures, if any.
|
// 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 {
|
- (void)testJSONAtURL:(NSURL *)url {
|
||||||
@@ -153,9 +223,9 @@ struct Test68000 {
|
|||||||
NSError *error;
|
NSError *error;
|
||||||
NSArray *const jsonContents = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
NSArray *const jsonContents = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
||||||
|
|
||||||
if(!data || error || ![jsonContents isKindOfClass:[NSArray class]]) {
|
XCTAssertNil(error);
|
||||||
return;
|
XCTAssertNotNil(jsonContents);
|
||||||
}
|
XCTAssert([jsonContents isKindOfClass:[NSArray class]]);
|
||||||
|
|
||||||
// Perform each dictionary in the array as a test.
|
// Perform each dictionary in the array as a test.
|
||||||
for(NSDictionary *test in jsonContents) {
|
for(NSDictionary *test in jsonContents) {
|
||||||
@@ -168,52 +238,33 @@ struct Test68000 {
|
|||||||
// Compare against a test set if one has been supplied.
|
// Compare against a test set if one has been supplied.
|
||||||
if(_testSet && ![_testSet containsObject:name]) continue;
|
if(_testSet && ![_testSet containsObject:name]) continue;
|
||||||
|
|
||||||
#ifdef USE_EXISTING_IMPLEMENTATION
|
// Pull out the opcode and record it.
|
||||||
[self testOperationClassic:test name:name];
|
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
|
||||||
#else
|
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];
|
[self testOperationExecutor:test name:name];
|
||||||
|
#else
|
||||||
|
[self testOperationClassic:test name:name];
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOperationClassic:(NSDictionary *)test name:(NSString *)name {
|
- (void)testOperationClassic:(NSDictionary *)test name:(NSString *)name {
|
||||||
|
struct TerminateMarker {};
|
||||||
|
|
||||||
// This is the test class for 68000 execution.
|
auto uniqueTest68000 = std::make_unique<TestProcessor>(reinterpret_cast<uint8_t *>(_ram.data()));
|
||||||
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 test68000 = uniqueTest68000.get();
|
auto test68000 = uniqueTest68000.get();
|
||||||
memset(test68000->ram.data(), 0xce, test68000->ram.size());
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Apply initial memory state.
|
// Apply initial memory state.
|
||||||
@@ -228,47 +279,24 @@ struct Test68000 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply initial processor state.
|
// Apply initial processor state.
|
||||||
NSDictionary *const initialState = test[@"initial state"];
|
|
||||||
auto state = test68000->processor.get_state();
|
auto state = test68000->processor.get_state();
|
||||||
for(int c = 0; c < 8; ++c) {
|
state.registers = [self initialRegisters:test];
|
||||||
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];
|
|
||||||
test68000->processor.set_state(state);
|
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.
|
// Run the thing.
|
||||||
const auto comparitor = [=] {
|
const auto comparitor = [=] {
|
||||||
// Test the end state.
|
|
||||||
NSDictionary *const finalState = test[@"final state"];
|
|
||||||
const auto state = test68000->processor.get_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];
|
[self test:test name:name compareFinalRegisters:state.registers opcode:opcode pcOffset:-4];
|
||||||
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);
|
|
||||||
|
|
||||||
// Test final memory state.
|
// Test final memory state.
|
||||||
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
|
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
|
||||||
@@ -284,19 +312,19 @@ struct Test68000 {
|
|||||||
|
|
||||||
// Consider collating extra detail.
|
// Consider collating extra detail.
|
||||||
if([_failures containsObject:name]) {
|
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 {
|
- (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.
|
// Apply initial memory state.
|
||||||
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
|
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
|
||||||
NSEnumerator<NSNumber *> *enumerator = [initialMemory objectEnumerator];
|
NSEnumerator<NSNumber *> *enumerator = [initialMemory objectEnumerator];
|
||||||
@@ -305,72 +333,23 @@ struct Test68000 {
|
|||||||
NSNumber *const value = [enumerator nextObject];
|
NSNumber *const value = [enumerator nextObject];
|
||||||
|
|
||||||
if(!address || !value) break;
|
if(!address || !value) break;
|
||||||
_test68000.ram[address.integerValue] = value.integerValue;
|
_testExecutor->ram[address.integerValue] = value.integerValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply initial processor state.
|
// Apply initial processor state.
|
||||||
NSDictionary *const initialState = test[@"initial state"];
|
_testExecutor->processor.set_state([self initialRegisters:test]);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOperationExecutor:(NSDictionary *)test name:(NSString *)name {
|
- (void)testOperationExecutor:(NSDictionary *)test name:(NSString *)name {
|
||||||
[self setInitialState:test];
|
[self setInitialState:test];
|
||||||
|
|
||||||
// Run the thing.
|
// Run the thing.
|
||||||
_test68000.run_for_instructions(1);
|
_testExecutor->run_for_instructions(1);
|
||||||
|
|
||||||
// Test the end state.
|
// Test the end state.
|
||||||
NSDictionary *const finalState = test[@"final state"];
|
const auto state = _testExecutor->processor.get_state();
|
||||||
const auto state = _test68000.processor.get_state();
|
const uint16_t opcode = _testExecutor->read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode());
|
||||||
for(int c = 0; c < 8; ++c) {
|
[self test:test name:name compareFinalRegisters:state opcode:opcode pcOffset:0];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test final memory state.
|
// Test final memory state.
|
||||||
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
|
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
|
||||||
@@ -380,16 +359,14 @@ struct Test68000 {
|
|||||||
NSNumber *const value = [enumerator nextObject];
|
NSNumber *const value = [enumerator nextObject];
|
||||||
|
|
||||||
if(!address || !value) break;
|
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
|
// If this test is now in the failures set, add the corresponding opcode for
|
||||||
// later logging.
|
// later logging.
|
||||||
if([_failures containsObject:name]) {
|
if([_failures containsObject:name]) {
|
||||||
NSNumber *const opcode = @(_test68000.read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode()));
|
|
||||||
|
|
||||||
// Add this opcode to the failing list.
|
// 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,
|
// Generate the JSON that would have satisfied this test, at least as far as registers go,
|
||||||
// if those are being collected.
|
// if those are being collected.
|
||||||
@@ -412,10 +389,71 @@ struct Test68000 {
|
|||||||
[NSJSONSerialization dataWithJSONObject:generatedTest options:0 error:nil]
|
[NSJSONSerialization dataWithJSONObject:generatedTest options:0 error:nil]
|
||||||
encoding:NSUTF8StringEncoding];
|
encoding:NSUTF8StringEncoding];
|
||||||
|
|
||||||
if(_suggestedCorrections[opcode]) {
|
if(_suggestedCorrections[@(opcode)]) {
|
||||||
[_suggestedCorrections[opcode] addObject:generatedJSON];
|
[_suggestedCorrections[@(opcode)] addObject:generatedJSON];
|
||||||
} else {
|
} 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];
|
[self performBccb:0x6200];
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
[self performBccb:0x6500];
|
[self performBccb:0x6500];
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
XCTAssertEqual(_machine->get_cycle_count(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
[self performBccw:0x6200];
|
[self performBccw:0x6200];
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
[self performBccw:0x6500];
|
[self performBccw:0x6500];
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
XCTAssertEqual(_machine->get_cycle_count(), 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
XCTAssertEqual(_machine->get_cycle_count(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,15 +108,14 @@
|
|||||||
- (void)testBSRw {
|
- (void)testBSRw {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x6100, 0x0006 // BSR.w $1008
|
0x6100, 0x0006 // BSR.w $1008
|
||||||
});
|
}, 0x3000);
|
||||||
_machine->set_initial_stack_pointer(0x3000);
|
|
||||||
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x2ffc);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
|
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
|
||||||
|
|
||||||
@@ -126,15 +125,14 @@
|
|||||||
- (void)testBSRb {
|
- (void)testBSRb {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x6106 // BSR.b $1008
|
0x6106 // BSR.b $1008
|
||||||
});
|
}, 0x3000);
|
||||||
_machine->set_initial_stack_pointer(0x3000);
|
|
||||||
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x2ffc);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
|
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
|
||||||
|
|
||||||
@@ -146,56 +144,61 @@
|
|||||||
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
|
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4581 // CHK D1, D2
|
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);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[1], d1);
|
XCTAssertEqual(state.registers.data[1], d1);
|
||||||
XCTAssertEqual(state.data[2], d2);
|
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 {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
XCTAssertEqual(
|
||||||
|
state.registers.status & (ConditionCode::Extend | ConditionCode::Zero | ConditionCode::Overflow | ConditionCode::Carry),
|
||||||
|
ConditionCode::Extend);
|
||||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testCHK_1111v0000 {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.program_counter, 0x1002 + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero);
|
XCTAssertEqual(
|
||||||
|
state.registers.status & (ConditionCode::Extend | ConditionCode::Zero | ConditionCode::Overflow | ConditionCode::Carry),
|
||||||
|
ConditionCode::Extend | ConditionCode::Zero);
|
||||||
XCTAssertEqual(10, _machine->get_cycle_count());
|
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testCHK_8000v8001 {
|
- (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();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
XCTAssertNotEqual(state.registers.program_counter, 0x1002 + 4);
|
||||||
XCTAssertEqual(state.stack_pointer(), 0xfffffffa);
|
XCTAssertEqual(state.registers.stack_pointer(), 0xfffffffa);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Negative);
|
||||||
XCTAssertEqual(42, _machine->get_cycle_count());
|
XCTAssertEqual(38, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testCHK_8000v8000 {
|
- (void)testCHK_8000v8000 {
|
||||||
[self performCHKd1:0x8000 d2:0x8000];
|
[self performCHKd1:0x8000 d2:0x8000]; // Less than 0.
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
|
XCTAssertNotEqual(state.registers.program_counter, 0x1002 + 4);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Negative);
|
||||||
XCTAssertEqual(44, _machine->get_cycle_count());
|
XCTAssertEqual(40, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: DBcc
|
// MARK: DBcc
|
||||||
@@ -204,16 +207,15 @@
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
opcode, 0x0008 // DBcc D2, +8
|
opcode, 0x0008 // DBcc D2, +8
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters) {
|
||||||
state.status = status;
|
registers.status = status;
|
||||||
state.data[2] = 1;
|
registers.data[2] = 1;
|
||||||
|
});
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.data[2], d2Output);
|
XCTAssertEqual(state.registers.data[2], d2Output);
|
||||||
XCTAssertEqual(state.status, status);
|
XCTAssertEqual(state.registers.status, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBT {
|
- (void)testDBT {
|
||||||
@@ -229,27 +231,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBHI_Carry {
|
- (void)testDBHI_Carry {
|
||||||
[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0];
|
[self performDBccTestOpcode:0x52ca status:ConditionCode::Carry d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBHI_Zero {
|
- (void)testDBHI_Zero {
|
||||||
[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0];
|
[self performDBccTestOpcode:0x52ca status:ConditionCode::Zero d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLS_CarryOverflow {
|
- (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 {
|
- (void)testDBLS_Carry {
|
||||||
[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1];
|
[self performDBccTestOpcode:0x53ca status:ConditionCode::Carry d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLS_Overflow {
|
- (void)testDBLS_Overflow {
|
||||||
[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0];
|
[self performDBccTestOpcode:0x53ca status:ConditionCode::Overflow d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBCC_Carry {
|
- (void)testDBCC_Carry {
|
||||||
[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0];
|
[self performDBccTestOpcode:0x54ca status:ConditionCode::Carry d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBCC {
|
- (void)testDBCC {
|
||||||
@@ -261,7 +263,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBCS_Carry {
|
- (void)testDBCS_Carry {
|
||||||
[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1];
|
[self performDBccTestOpcode:0x55ca status:ConditionCode::Carry d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBNE {
|
- (void)testDBNE {
|
||||||
@@ -269,7 +271,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBNE_Zero {
|
- (void)testDBNE_Zero {
|
||||||
[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0];
|
[self performDBccTestOpcode:0x56ca status:ConditionCode::Zero d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBEQ {
|
- (void)testDBEQ {
|
||||||
@@ -277,7 +279,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBEQ_Zero {
|
- (void)testDBEQ_Zero {
|
||||||
[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1];
|
[self performDBccTestOpcode:0x57ca status:ConditionCode::Zero d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBVC {
|
- (void)testDBVC {
|
||||||
@@ -285,7 +287,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBVC_Overflow {
|
- (void)testDBVC_Overflow {
|
||||||
[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0];
|
[self performDBccTestOpcode:0x58ca status:ConditionCode::Overflow d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBVS {
|
- (void)testDBVS {
|
||||||
@@ -293,7 +295,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBVS_Overflow {
|
- (void)testDBVS_Overflow {
|
||||||
[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1];
|
[self performDBccTestOpcode:0x59ca status:ConditionCode::Overflow d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBPL {
|
- (void)testDBPL {
|
||||||
@@ -301,7 +303,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBPL_Negative {
|
- (void)testDBPL_Negative {
|
||||||
[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0];
|
[self performDBccTestOpcode:0x5aca status:ConditionCode::Negative d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBMI {
|
- (void)testDBMI {
|
||||||
@@ -309,11 +311,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBMI_Negative {
|
- (void)testDBMI_Negative {
|
||||||
[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1];
|
[self performDBccTestOpcode:0x5bca status:ConditionCode::Negative d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBGE_NegativeOverflow {
|
- (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 {
|
- (void)testDBGE {
|
||||||
@@ -321,15 +323,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBGE_Negative {
|
- (void)testDBGE_Negative {
|
||||||
[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0];
|
[self performDBccTestOpcode:0x5cca status:ConditionCode::Negative d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBGE_Overflow {
|
- (void)testDBGE_Overflow {
|
||||||
[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0];
|
[self performDBccTestOpcode:0x5cca status:ConditionCode::Overflow d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLT_NegativeOverflow {
|
- (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 {
|
- (void)testDBLT {
|
||||||
@@ -337,11 +339,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLT_Negative {
|
- (void)testDBLT_Negative {
|
||||||
[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1];
|
[self performDBccTestOpcode:0x5dca status:ConditionCode::Negative d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLT_Overflow {
|
- (void)testDBLT_Overflow {
|
||||||
[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1];
|
[self performDBccTestOpcode:0x5dca status:ConditionCode::Overflow d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBGT {
|
- (void)testDBGT {
|
||||||
@@ -349,15 +351,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBGT_ZeroNegativeOverflow {
|
- (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 {
|
- (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 {
|
- (void)testDBGT_Zero {
|
||||||
[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0];
|
[self performDBccTestOpcode:0x5eca status:ConditionCode::Zero d2Outcome:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLE {
|
- (void)testDBLE {
|
||||||
@@ -365,15 +367,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLE_Zero {
|
- (void)testDBLE_Zero {
|
||||||
[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1];
|
[self performDBccTestOpcode:0x5fca status:ConditionCode::Zero d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLE_Negative {
|
- (void)testDBLE_Negative {
|
||||||
[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1];
|
[self performDBccTestOpcode:0x5fca status:ConditionCode::Negative d2Outcome:1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testDBLE_NegativeOverflow {
|
- (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. */
|
/* 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)
|
0x4ed1 // JMP (A1)
|
||||||
});
|
});
|
||||||
|
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters) {
|
||||||
state.address[1] = 0x3000;
|
registers.address[1] = 0x3000;
|
||||||
|
});
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.address[1], 0x3000);
|
XCTAssertEqual(state.registers.address[1], 0x3000);
|
||||||
XCTAssertEqual(state.program_counter, 0x3000 + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x3000 + 4);
|
||||||
XCTAssertEqual(8, _machine->get_cycle_count());
|
XCTAssertEqual(8, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +406,7 @@
|
|||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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());
|
XCTAssertEqual(10, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,14 +415,13 @@
|
|||||||
- (void)testJSR_PC {
|
- (void)testJSR_PC {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
|
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
|
||||||
});
|
}, 0x2000);
|
||||||
_machine->set_initial_stack_pointer(0x2000);
|
|
||||||
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x1ffc);
|
||||||
XCTAssertEqual(state.program_counter, 0x100c + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x100c + 4);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
|
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
|
||||||
XCTAssertEqual(18, _machine->get_cycle_count());
|
XCTAssertEqual(18, _machine->get_cycle_count());
|
||||||
@@ -430,14 +430,13 @@
|
|||||||
- (void)testJSR_XXXl {
|
- (void)testJSR_XXXl {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
|
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
|
||||||
});
|
}, 0x2000);
|
||||||
_machine->set_initial_stack_pointer(0x2000);
|
|
||||||
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x1ffc);
|
||||||
XCTAssertEqual(state.program_counter, 0x1008 + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
|
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
|
||||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||||
@@ -458,8 +457,7 @@
|
|||||||
- (void)testRTR {
|
- (void)testRTR {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4e77 // RTR
|
0x4e77 // RTR
|
||||||
});
|
}, 0x2000);
|
||||||
_machine->set_initial_stack_pointer(0x2000);
|
|
||||||
*_machine->ram_at(0x2000) = 0x7fff;
|
*_machine->ram_at(0x2000) = 0x7fff;
|
||||||
*_machine->ram_at(0x2002) = 0;
|
*_machine->ram_at(0x2002) = 0;
|
||||||
*_machine->ram_at(0x2004) = 0xc;
|
*_machine->ram_at(0x2004) = 0xc;
|
||||||
@@ -467,9 +465,9 @@
|
|||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x2006);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x2006);
|
||||||
XCTAssertEqual(state.program_counter, 0x10);
|
XCTAssertEqual(state.registers.program_counter, 0x10);
|
||||||
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes);
|
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::AllConditions);
|
||||||
XCTAssertEqual(20, _machine->get_cycle_count());
|
XCTAssertEqual(20, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,16 +476,15 @@
|
|||||||
- (void)testRTS {
|
- (void)testRTS {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4e75 // RTS
|
0x4e75 // RTS
|
||||||
});
|
}, 0x2000);
|
||||||
_machine->set_initial_stack_pointer(0x2000);
|
|
||||||
*_machine->ram_at(0x2000) = 0x0000;
|
*_machine->ram_at(0x2000) = 0x0000;
|
||||||
*_machine->ram_at(0x2002) = 0x000c;
|
*_machine->ram_at(0x2002) = 0x000c;
|
||||||
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x2004);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x2004);
|
||||||
XCTAssertEqual(state.program_counter, 0x000c + 4);
|
XCTAssertEqual(state.registers.program_counter, 0x000c + 4);
|
||||||
XCTAssertEqual(16, _machine->get_cycle_count());
|
XCTAssertEqual(16, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,22 +494,21 @@
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4e41 // TRAP #1
|
0x4e41 // TRAP #1
|
||||||
});
|
});
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters) {
|
||||||
state.status = 0x700;
|
registers.status = 0x700;
|
||||||
state.user_stack_pointer = 0x200;
|
registers.user_stack_pointer = 0x200;
|
||||||
state.supervisor_stack_pointer = 0x206;
|
registers.supervisor_stack_pointer = 0x206;
|
||||||
|
});
|
||||||
*_machine->ram_at(0x84) = 0xfffe;
|
*_machine->ram_at(0x84) = 0xfffe;
|
||||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||||
|
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.status, 0x2700);
|
XCTAssertEqual(state.registers.status, 0x2700);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
|
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
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());
|
XCTAssertEqual(34, _machine->get_cycle_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,21 +517,20 @@
|
|||||||
- (void)testTRAPV_taken {
|
- (void)testTRAPV_taken {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x4e76 // TRAPV
|
0x4e76 // TRAPV
|
||||||
});
|
}, 0x206);
|
||||||
_machine->set_initial_stack_pointer(0x206);
|
|
||||||
|
|
||||||
auto state = _machine->get_processor_state();
|
_machine->set_registers([=](auto ®isters) {
|
||||||
state.status = 0x702;
|
registers.status = 0x702;
|
||||||
state.supervisor_stack_pointer = 0x206;
|
registers.supervisor_stack_pointer = 0x206;
|
||||||
|
});
|
||||||
*_machine->ram_at(0x1e) = 0xfffe;
|
*_machine->ram_at(0x1e) = 0xfffe;
|
||||||
*_machine->ram_at(0xfffe) = 0x4e71;
|
*_machine->ram_at(0xfffe) = 0x4e71;
|
||||||
|
|
||||||
_machine->set_processor_state(state);
|
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
state = _machine->get_processor_state();
|
const auto state = _machine->get_processor_state();
|
||||||
XCTAssertEqual(state.status, 0x2702);
|
XCTAssertEqual(state.registers.status, 0x2702);
|
||||||
XCTAssertEqual(state.stack_pointer(), 0x200);
|
XCTAssertEqual(state.registers.stack_pointer(), 0x200);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
|
||||||
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
|
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
|
||||||
@@ -550,7 +545,7 @@
|
|||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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());
|
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"
|
#include "TestRunner68000.hpp"
|
||||||
|
|
||||||
class CPU::MC68000::ProcessorStorageTests {
|
//@interface NSSet (CSHexDump)
|
||||||
public:
|
//
|
||||||
ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) {
|
//- (NSString *)hexDump;
|
||||||
false_valids_ = [NSMutableSet set];
|
//
|
||||||
false_invalids_ = [NSMutableSet set];
|
//@end
|
||||||
|
//
|
||||||
FILE *source = fopen(coverage_file_name, "rb");
|
//@implementation NSSet (CSHexDump)
|
||||||
|
//
|
||||||
// The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]...
|
//- (NSString *)hexDump {
|
||||||
// The file terminates with four additional bytes that begin with two zero bytes.
|
// NSMutableArray<NSString *> *components = [NSMutableArray array];
|
||||||
//
|
//
|
||||||
// The version of the file I grabbed seems to cover all opcodes, making their enumeration
|
// for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||||
// arguably redundant; the code below nevertheless uses the codes from the file.
|
// [components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
|
||||||
//
|
// }
|
||||||
// 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.
|
// return [components componentsJoinedByString:@" "];
|
||||||
uint16_t last_observed = 0;
|
//}
|
||||||
while(true) {
|
//
|
||||||
// Fetch opcode number.
|
//@end
|
||||||
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 M68000Tests : XCTestCase
|
@interface M68000Tests : XCTestCase
|
||||||
@@ -123,39 +54,38 @@ class CPU::MC68000::ProcessorStorageTests {
|
|||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0xc100 // ABCD D0, D0
|
0xc100 // ABCD D0, D0
|
||||||
});
|
});
|
||||||
|
|
||||||
auto state = _machine->get_processor_state();
|
|
||||||
const uint8_t bcd_d = ((d / 10) * 16) + (d % 10);
|
const uint8_t bcd_d = ((d / 10) * 16) + (d % 10);
|
||||||
state.data[0] = bcd_d;
|
_machine->set_registers([=](auto ®isters){
|
||||||
_machine->set_processor_state(state);
|
registers.data[0] = bcd_d;
|
||||||
|
});
|
||||||
_machine->run_for_instructions(1);
|
_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 double_d = (d * 2) % 100;
|
||||||
const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10);
|
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 {
|
- (void)testDivideByZero {
|
||||||
_machine->set_program({
|
_machine->set_program({
|
||||||
0x7000, // MOVE #0, D0; location 0x400
|
0x7000, // MOVE #0, D0; location 0x1000
|
||||||
0x3200, // MOVE D0, D1; location 0x402
|
0x3200, // MOVE D0, D1; location 0x1002
|
||||||
|
|
||||||
0x82C0, // DIVU; location 0x404
|
0x82C0, // DIVU; location 0x1004
|
||||||
|
|
||||||
/* Next instruction would be at 0x406 */
|
/* Next instruction would be at 0x1006 */
|
||||||
});
|
}, 0x1000);
|
||||||
_machine->set_initial_stack_pointer(0x1000);
|
|
||||||
|
|
||||||
_machine->run_for_instructions(4);
|
_machine->run_for_instructions(4);
|
||||||
|
|
||||||
const auto state = _machine->get_processor_state();
|
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);
|
// 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.");
|
// 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 {
|
- (void)testMOVE {
|
||||||
@@ -177,28 +107,28 @@ class CPU::MC68000::ProcessorStorageTests {
|
|||||||
// Perform MOVE #fb2e, D0
|
// Perform MOVE #fb2e, D0
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
auto state = _machine->get_processor_state();
|
auto state = _machine->get_processor_state();
|
||||||
XCTAssert(state.data[0] == 0xfb2e);
|
XCTAssert(state.registers.data[0] == 0xfb2e);
|
||||||
|
|
||||||
// Perform MOVE D0, D1
|
// Perform MOVE D0, D1
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
state = _machine->get_processor_state();
|
state = _machine->get_processor_state();
|
||||||
XCTAssert(state.data[1] == 0xfb2e);
|
XCTAssert(state.registers.data[1] == 0xfb2e);
|
||||||
|
|
||||||
// Perform MOVEA D0, A0
|
// Perform MOVEA D0, A0
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
state = _machine->get_processor_state();
|
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
|
// Perform MOVEA.w (0x1000), A1
|
||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
state = _machine->get_processor_state();
|
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
|
// Perform MOVE #$400, A4; MOVE.l (A4), D2
|
||||||
_machine->run_for_instructions(2);
|
_machine->run_for_instructions(2);
|
||||||
state = _machine->get_processor_state();
|
state = _machine->get_processor_state();
|
||||||
XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]);
|
XCTAssert(state.registers.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.registers.address[4]);
|
||||||
XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]);
|
XCTAssert(state.registers.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.registers.data[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testVectoredInterrupt {
|
- (void)testVectoredInterrupt {
|
||||||
@@ -223,7 +153,7 @@ class CPU::MC68000::ProcessorStorageTests {
|
|||||||
_machine->run_for_instructions(1);
|
_machine->run_for_instructions(1);
|
||||||
|
|
||||||
const auto state = _machine->processor().get_state();
|
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.
|
// 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);
|
XCTAssertEqual(_machine->get_cycle_count(), 6 + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOpcodeCoverage {
|
/*- (void)testOpcodeCoverage {
|
||||||
// Perform an audit of implemented instructions.
|
// Perform an audit of implemented instructions.
|
||||||
CPU::MC68000::ProcessorStorageTests storage_tests(
|
CPU::MC68000::ProcessorStorageTests storage_tests(
|
||||||
_machine->processor(),
|
_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(!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);
|
// XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include "68000.hpp"
|
#include "68000Mk2.hpp"
|
||||||
|
|
||||||
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
class ComparativeBusHandler: public CPU::MC68000Mk2::BusHandler {
|
||||||
public:
|
public:
|
||||||
ComparativeBusHandler(const char *trace_name) {
|
ComparativeBusHandler(const char *trace_name) {
|
||||||
trace = gzopen(trace_name, "rt");
|
trace = gzopen(trace_name, "rt");
|
||||||
@@ -30,14 +30,14 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler {
|
|||||||
++line_count;
|
++line_count;
|
||||||
|
|
||||||
// Generate state locally.
|
// Generate state locally.
|
||||||
const auto state = get_state();
|
const auto state = get_state().registers;
|
||||||
char local_state[300];
|
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",
|
sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
||||||
address,
|
address,
|
||||||
state.status,
|
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.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.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.
|
// 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:
|
private:
|
||||||
int line_count = 0;
|
int line_count = 0;
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
//#define LOG_TRACE
|
//#define LOG_TRACE
|
||||||
|
|
||||||
#include "68000.hpp"
|
#include "68000Mk2.hpp"
|
||||||
#include "Comparative68000.hpp"
|
#include "Comparative68000.hpp"
|
||||||
#include "CSROMFetcher.hpp"
|
#include "CSROMFetcher.hpp"
|
||||||
|
|
||||||
@@ -32,11 +32,11 @@ class EmuTOS: public ComparativeBusHandler {
|
|||||||
m68000_.run_for(cycles);
|
m68000_.run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
CPU::MC68000::ProcessorState get_state() final {
|
CPU::MC68000Mk2::State get_state() final {
|
||||||
return m68000_.get_state();
|
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();
|
const uint32_t address = cycle.word_address();
|
||||||
uint32_t word_address = address;
|
uint32_t word_address = address;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class EmuTOS: public ComparativeBusHandler {
|
|||||||
word_address %= ram_.size();
|
word_address %= ram_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
using Microcycle = CPU::MC68000::Microcycle;
|
using Microcycle = CPU::MC68000Mk2::Microcycle;
|
||||||
if(cycle.data_select_active()) {
|
if(cycle.data_select_active()) {
|
||||||
uint16_t peripheral_result = 0xffff;
|
uint16_t peripheral_result = 0xffff;
|
||||||
if(is_peripheral) {
|
if(is_peripheral) {
|
||||||
@@ -72,16 +72,16 @@ class EmuTOS: public ComparativeBusHandler {
|
|||||||
default: break;
|
default: break;
|
||||||
|
|
||||||
case Microcycle::SelectWord | Microcycle::Read:
|
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;
|
break;
|
||||||
case Microcycle::SelectByte | Microcycle::Read:
|
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;
|
break;
|
||||||
case Microcycle::SelectWord:
|
case Microcycle::SelectWord:
|
||||||
base[word_address] = cycle.value->full;
|
base[word_address] = cycle.value->w;
|
||||||
break;
|
break;
|
||||||
case Microcycle::SelectByte:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ class EmuTOS: public ComparativeBusHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
|
CPU::MC68000Mk2::Processor<EmuTOS, true, true> m68000_;
|
||||||
|
|
||||||
std::vector<uint16_t> emuTOS_;
|
std::vector<uint16_t> emuTOS_;
|
||||||
std::array<uint16_t, 256*1024> ram_;
|
std::array<uint16_t, 256*1024> ram_;
|
||||||
|
@@ -35,11 +35,11 @@ class QL: public ComparativeBusHandler {
|
|||||||
m68000_.run_for(cycles);
|
m68000_.run_for(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
CPU::MC68000::ProcessorState get_state() final {
|
CPU::MC68000Mk2::State get_state() final {
|
||||||
return m68000_.get_state();
|
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();
|
const uint32_t address = cycle.word_address();
|
||||||
uint32_t word_address = address;
|
uint32_t word_address = address;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class QL: public ComparativeBusHandler {
|
|||||||
word_address %= ram_.size();
|
word_address %= ram_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
using Microcycle = CPU::MC68000::Microcycle;
|
using Microcycle = CPU::MC68000Mk2::Microcycle;
|
||||||
if(cycle.data_select_active()) {
|
if(cycle.data_select_active()) {
|
||||||
uint16_t peripheral_result = 0xffff;
|
uint16_t peripheral_result = 0xffff;
|
||||||
|
|
||||||
@@ -64,18 +64,18 @@ class QL: public ComparativeBusHandler {
|
|||||||
default: break;
|
default: break;
|
||||||
|
|
||||||
case Microcycle::SelectWord | Microcycle::Read:
|
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;
|
break;
|
||||||
case Microcycle::SelectByte | Microcycle::Read:
|
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;
|
break;
|
||||||
case Microcycle::SelectWord:
|
case Microcycle::SelectWord:
|
||||||
assert(!(is_rom && !is_peripheral));
|
assert(!(is_rom && !is_peripheral));
|
||||||
if(!is_peripheral) base[word_address] = cycle.value->full;
|
if(!is_peripheral) base[word_address] = cycle.value->w;
|
||||||
break;
|
break;
|
||||||
case Microcycle::SelectByte:
|
case Microcycle::SelectByte:
|
||||||
assert(!(is_rom && !is_peripheral));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ class QL: public ComparativeBusHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CPU::MC68000::Processor<QL, true, true> m68000_;
|
CPU::MC68000Mk2::Processor<QL, true, false, true> m68000_;
|
||||||
|
|
||||||
std::vector<uint16_t> rom_;
|
std::vector<uint16_t> rom_;
|
||||||
std::array<uint16_t, 64*1024> ram_;
|
std::array<uint16_t, 64*1024> ram_;
|
||||||
|
@@ -10,104 +10,98 @@
|
|||||||
#define TestRunner68000_h
|
#define TestRunner68000_h
|
||||||
|
|
||||||
#include <array>
|
#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;
|
Provides a 68000 with 64kb of RAM in its low address space;
|
||||||
/RESET will put the supervisor stack pointer at 0xFFFF and
|
/RESET will put the supervisor stack pointer at 0xFFFF and
|
||||||
begin execution at 0x0400.
|
begin execution at 0x0400.
|
||||||
*/
|
*/
|
||||||
class RAM68000: public CPU::MC68000::BusHandler {
|
class RAM68000: public CPU::MC68000Mk2::BusHandler {
|
||||||
public:
|
public:
|
||||||
RAM68000() : m68000_(*this) {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t initial_pc() const {
|
uint32_t initial_pc() const {
|
||||||
return 0x1000;
|
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));
|
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
|
||||||
|
|
||||||
// Add a NOP suffix, to avoid corrupting flags should the attempt to
|
// Ensure the condition codes start unset and set the initial program counter
|
||||||
// run for a certain number of instructions overrun.
|
// and supervisor stack pointer, as well as starting in supervisor mode.
|
||||||
ram_[(0x1000 >> 1) + program.size()] = 0x4e71;
|
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) {
|
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
|
||||||
ram_[0] = sp >> 16;
|
auto state = m68000_.get_state();
|
||||||
ram_[1] = sp & 0xffff;
|
func(state.registers);
|
||||||
|
m68000_.set_state(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void will_perform(uint32_t, uint16_t) {
|
void will_perform(uint32_t, uint16_t) {
|
||||||
--instructions_remaining_;
|
--instructions_remaining_;
|
||||||
|
if(instructions_remaining_ < 0) {
|
||||||
|
throw StopException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_for_instructions(int count) {
|
void run_for_instructions(int count) {
|
||||||
instructions_remaining_ = count + (has_run_ ? 0 : 1);
|
duration_ = HalfCycles(0);
|
||||||
finish_reset_if_needed();
|
instructions_remaining_ = count;
|
||||||
|
if(!instructions_remaining_) return;
|
||||||
|
|
||||||
while(instructions_remaining_) {
|
try {
|
||||||
run_for(HalfCycles(2));
|
while(true) {
|
||||||
}
|
run_for(HalfCycles(2000));
|
||||||
|
}
|
||||||
|
} catch (const StopException &) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_for(HalfCycles cycles) {
|
void run_for(HalfCycles cycles) {
|
||||||
finish_reset_if_needed();
|
|
||||||
m68000_.run_for(cycles);
|
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) {
|
uint16_t *ram_at(uint32_t address) {
|
||||||
return &ram_[(address >> 1) % ram_.size()];
|
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();
|
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.data_select_active()) {
|
||||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||||
cycle.value->halves.low = 10;
|
cycle.value->b = 10;
|
||||||
} else {
|
} else {
|
||||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
||||||
default: break;
|
default: break;
|
||||||
|
|
||||||
case Microcycle::SelectWord | Microcycle::Read:
|
case Microcycle::SelectWord | Microcycle::Read:
|
||||||
cycle.value->full = ram_[word_address % ram_.size()];
|
cycle.value->w = ram_[word_address % ram_.size()];
|
||||||
break;
|
break;
|
||||||
case Microcycle::SelectByte | Microcycle::Read:
|
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;
|
break;
|
||||||
case Microcycle::SelectWord:
|
case Microcycle::SelectWord:
|
||||||
ram_[word_address % ram_.size()] = cycle.value->full;
|
ram_[word_address % ram_.size()] = cycle.value->w;
|
||||||
break;
|
break;
|
||||||
case Microcycle::SelectByte:
|
case Microcycle::SelectByte:
|
||||||
ram_[word_address % ram_.size()] = uint16_t(
|
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())
|
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -118,15 +112,11 @@ class RAM68000: public CPU::MC68000::BusHandler {
|
|||||||
return HalfCycles(0);
|
return HalfCycles(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
CPU::MC68000::Processor<RAM68000, true>::State get_processor_state() {
|
CPU::MC68000Mk2::State get_processor_state() {
|
||||||
return m68000_.get_state();
|
return m68000_.get_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_processor_state(const CPU::MC68000::Processor<RAM68000, true>::State &state) {
|
auto &processor() {
|
||||||
m68000_.set_state(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
CPU::MC68000::Processor<RAM68000, true, true> &processor() {
|
|
||||||
return m68000_;
|
return m68000_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,11 +129,12 @@ class RAM68000: public CPU::MC68000::BusHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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_{};
|
std::array<uint16_t, 256*1024> ram_{};
|
||||||
int instructions_remaining_;
|
int instructions_remaining_;
|
||||||
HalfCycles duration_;
|
HalfCycles duration_;
|
||||||
bool has_run_ = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* TestRunner68000_h */
|
#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 */
|
Reference in New Issue
Block a user