1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 06:29:33 +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:
Thomas Harte 2022-06-03 10:30:44 -04:00 committed by GitHub
commit fd66a9b396
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 5855 additions and 2528 deletions

View File

@ -29,7 +29,7 @@ enum Exception {
FormatError = 14,
UninitialisedInterrupt = 15,
SpuriousInterrupt = 24,
InterruptAutovectorBase = 25,
InterruptAutovectorBase = 25, // This is the vector for interrupt level _1_.
TrapBase = 32,
FPBranchOrSetOnUnorderedCondition = 48,
FPInexactResult = 49,

View File

@ -13,6 +13,7 @@
#include "Instruction.hpp"
#include "Model.hpp"
#include "Perform.hpp"
#include "RegisterSet.hpp"
#include "Status.hpp"
namespace InstructionSet {
@ -80,17 +81,9 @@ template <Model model, typename BusHandler> class Executor {
/// Sets the current input interrupt level.
void set_interrupt_level(int);
// TODO: this will likely be shared in some capacity with the bus-accurate versions of the 680x0;
// therefore it will almost certainly be factored out in future.
struct Registers {
uint32_t data[8], address[7];
uint32_t user_stack_pointer;
uint32_t supervisor_stack_pointer;
uint16_t status;
uint32_t program_counter;
};
Registers get_state();
void set_state(const Registers &);
// State for the executor is just the register set.
RegisterSet get_state();
void set_state(const RegisterSet &);
private:
class State: public NullFlowController {

View File

@ -53,7 +53,7 @@ void Executor<model, BusHandler>::signal_bus_error(FunctionCode code, uint32_t a
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::set_interrupt_level(int level) {
state_.interrupt_input_ = level;
state_.stopped &= state_.interrupt_input_ <= state_.status.interrupt_level;
state_.stopped &= !state_.status.would_accept_interrupt(level);
}
template <Model model, typename BusHandler>
@ -102,8 +102,8 @@ void Executor<model, BusHandler>::run_for_instructions(int count) {
}
template <Model model, typename BusHandler>
typename Executor<model, BusHandler>::Registers Executor<model, BusHandler>::get_state() {
Registers result;
RegisterSet Executor<model, BusHandler>::get_state() {
RegisterSet result;
for(int c = 0; c < 8; c++) {
result.data[c] = Dn(c).l;
@ -122,7 +122,7 @@ typename Executor<model, BusHandler>::Registers Executor<model, BusHandler>::get
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::set_state(const Registers &state) {
void Executor<model, BusHandler>::set_state(const RegisterSet &state) {
for(int c = 0; c < 8; c++) {
Dn(c).l = state.data[c];
}
@ -205,8 +205,8 @@ uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement() {
// also include the scale field even if not.
const auto extension = read_pc<uint16_t>();
const auto offset = int8_t(extension);
const int register_index = (extension >> 12) & 7;
const uint32_t displacement = registers[register_index + ((extension >> 12) & 0x08)].l;
const int register_index = (extension >> 12) & 15;
const uint32_t displacement = registers[register_index].l;
const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement);
return offset + sized_displacement;
}
@ -324,7 +324,7 @@ template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::run(int &count) {
while(count--) {
// Check for a new interrupt.
if(interrupt_input > status.interrupt_level) {
if(status.would_accept_interrupt(interrupt_input)) {
const int vector = bus_handler_.acknowlege_interrupt(interrupt_input);
if(vector >= 0) {
raise_exception<false>(vector);
@ -483,7 +483,7 @@ template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
sp.l -= 4;
write<uint32_t>(sp.l, program_counter.l);
program_counter.l = instruction_address + offset;
program_counter.l = instruction_address + offset + 2;
}
template <Model model, typename BusHandler>

View File

@ -12,7 +12,7 @@
namespace InstructionSet {
namespace M68k {
template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_operation) {
template <Model model, Operation t_operation> constexpr uint8_t operand_flags(Operation r_operation) {
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
default:
assert(false);
@ -24,8 +24,6 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
case Operation::PEA:
case Operation::JMP: case Operation::JSR:
case Operation::MOVEPw: case Operation::MOVEPl:
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
case Operation::TAS:
case Operation::RTR: case Operation::RTS: case Operation::RTE:
return 0;
@ -40,13 +38,14 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
case Operation::Bccb: case Operation::Bccw: case Operation::Bccl:
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
return FetchOp1;
//
// Single-operand write.
//
case Operation::MOVEfromSR: case Operation::MOVEfromUSP:
case Operation::Scc:
case Operation::MOVEfromUSP:
return StoreOp1;
//
@ -63,11 +62,13 @@ template <Model model, Operation t_operation> uint8_t operand_flags(Operation r_
case Operation::LSLm: case Operation::LSRm:
case Operation::ROLm: case Operation::RORm:
case Operation::ROXLm: case Operation::ROXRm:
case Operation::Scc:
return FetchOp1 | StoreOp1;
//
// CLR, which is model-dependent.
// CLR and MOVE SR, which are model-dependent.
//
case Operation::MOVEfromSR:
case Operation::CLRb: case Operation::CLRw: case Operation::CLRl:
if constexpr (model == Model::M68000) {
return FetchOp1 | StoreOp1;

View File

@ -12,8 +12,9 @@
namespace InstructionSet {
namespace M68k {
constexpr DataSize operand_size(Operation operation) {
switch(operation) {
template <Operation t_operation>
constexpr DataSize operand_size(Operation r_operation) {
switch((t_operation == Operation::Undefined) ? r_operation : t_operation) {
// These are given a value arbitrarily, to
// complete the switch statement.
case Operation::Undefined:

View File

@ -228,7 +228,7 @@ template <
status.zero_result = dest.l & bit_mask;
dest.l &= ~bit_mask;
flow_controller.did_bit_op(bit_position);
flow_controller.did_bit_op(int(bit_position));
} break;
case Operation::BCHG: {
@ -236,7 +236,7 @@ template <
status.zero_result = dest.l & bit_mask;
dest.l ^= bit_mask;
flow_controller.did_bit_op(bit_position);
flow_controller.did_bit_op(int(bit_position));
} break;
case Operation::BSET: {
@ -244,7 +244,7 @@ template <
status.zero_result = dest.l & bit_mask;
dest.l |= bit_mask;
flow_controller.did_bit_op(bit_position);
flow_controller.did_bit_op(int(bit_position));
} break;
#undef get_mask
@ -252,29 +252,29 @@ template <
case Operation::Bccb:
flow_controller.template complete_bcc<int8_t>(
status.evaluate_condition(instruction.condition()),
src.b);
int8_t(src.b));
break;
case Operation::Bccw:
flow_controller.template complete_bcc<int16_t>(
status.evaluate_condition(instruction.condition()),
src.w);
int16_t(src.w));
break;
case Operation::Bccl:
flow_controller.template complete_bcc<int32_t>(
status.evaluate_condition(instruction.condition()),
src.l);
int32_t(src.l));
break;
case Operation::BSRb:
flow_controller.bsr(int8_t(src.b) + 2);
flow_controller.bsr(uint32_t(int8_t(src.b)));
break;
case Operation::BSRw:
flow_controller.bsr(int16_t(src.w) + 2);
flow_controller.bsr(uint32_t(int16_t(src.w)));
break;
case Operation::BSRl:
flow_controller.bsr(src.l + 2);
flow_controller.bsr(src.l);
break;
case Operation::DBcc: {
@ -294,9 +294,11 @@ template <
int16_t(dest.w));
} break;
case Operation::Scc:
src.b = status.evaluate_condition(instruction.condition()) ? 0xff : 0x00;
break;
case Operation::Scc: {
const bool condition = status.evaluate_condition(instruction.condition());
src.b = condition ? 0xff : 0x00;
flow_controller.did_scc(condition);
} break;
/*
CLRs: store 0 to the destination, set the zero flag, and clear
@ -520,17 +522,18 @@ template <
#define DIV(Type16, Type32, flow_function) { \
status.carry_flag = 0; \
\
if(!src.w) { \
const auto dividend = Type32(dest.l); \
const auto divisor = Type32(Type16(src.w)); \
\
if(!divisor) { \
status.negative_flag = status.overflow_flag = 0; \
status.zero_result = 1; \
flow_controller.raise_exception(Exception::IntegerDivideByZero); \
flow_controller.template flow_function<false>(dividend, divisor); \
return; \
} \
\
const auto dividend = Type32(dest.l); \
const auto divisor = Type32(Type16(src.w)); \
const auto quotient = dividend / divisor; \
\
const auto quotient = int64_t(dividend) / int64_t(divisor); \
if(quotient != Type32(Type16(quotient))) { \
status.overflow_flag = 1; \
flow_controller.template flow_function<true>(dividend, divisor); \
@ -553,7 +556,7 @@ template <
// TRAP, which is a nicer form of ILLEGAL.
case Operation::TRAP:
flow_controller.template raise_exception<false>(src.l + Exception::TrapBase);
flow_controller.template raise_exception<false>(int(src.l + Exception::TrapBase));
break;
case Operation::TRAPV: {
@ -678,7 +681,7 @@ template <
*/
case Operation::LINKw:
flow_controller.link(instruction, int16_t(dest.w));
flow_controller.link(instruction, uint32_t(int16_t(dest.w)));
break;
case Operation::UNLINK:
@ -825,14 +828,14 @@ template <
set_neg_zero(v, m); \
status.overflow_flag = (Status::FlagT(value) ^ status.zero_result) & Status::FlagT(m);
#define decode_shift_count() \
#define decode_shift_count(type) \
int shift_count = src.l & 63; \
flow_controller.did_shift(shift_count);
flow_controller.template did_shift<type>(shift_count);
#define set_flags_w(t) set_flags(src.w, 0x8000, t)
#define asl(destination, size) {\
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
const auto value = destination; \
\
if(!shift_count) { \
@ -862,7 +865,7 @@ template <
case Operation::ASLl: asl(dest.l, 32); break;
#define asr(destination, size) {\
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
const auto value = destination; \
\
if(!shift_count) { \
@ -906,7 +909,7 @@ template <
status.carry_flag = value & (t);
#define lsl(destination, size) {\
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
const auto value = destination; \
\
if(!shift_count) { \
@ -930,7 +933,7 @@ template <
case Operation::LSLl: lsl(dest.l, 32); break;
#define lsr(destination, size) {\
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
const auto value = destination; \
\
if(!shift_count) { \
@ -954,7 +957,7 @@ template <
case Operation::LSRl: lsr(dest.l, 32); break;
#define rol(destination, size) { \
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
const auto value = destination; \
\
if(!shift_count) { \
@ -982,7 +985,7 @@ template <
case Operation::ROLl: rol(dest.l, 32); break;
#define ror(destination, size) { \
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
const auto value = destination; \
\
if(!shift_count) { \
@ -1010,7 +1013,7 @@ template <
case Operation::RORl: ror(dest.l, 32); break;
#define roxl(destination, size) { \
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
\
shift_count %= (size + 1); \
uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \
@ -1034,7 +1037,7 @@ template <
case Operation::ROXLl: roxl(dest.l, 32); break;
#define roxr(destination, size) { \
decode_shift_count(); \
decode_shift_count(decltype(destination)); \
\
shift_count %= (size + 1); \
uint64_t compound = uint64_t(destination) | (status.extend_flag ? (1ull << size) : 0); \

View File

@ -61,7 +61,7 @@ std::string Preinstruction::to_string(int opcode) const {
const char *instruction;
switch(operation) {
case Operation::Undefined: instruction = "None"; break;
case Operation::Undefined: return "None";
case Operation::NOP: instruction = "NOP"; break;
case Operation::ABCD: instruction = "ABCD"; break;
case Operation::SBCD: instruction = "SBCD"; break;

View File

@ -135,7 +135,8 @@ enum class DataSize {
/// For any operations that don't fit the neat model of reading one or two operands,
/// then writing zero or one, the size determines the data size of the operands only,
/// not any other accesses.
constexpr DataSize operand_size(Operation operation);
template <Operation t_operation = Operation::Undefined>
constexpr DataSize operand_size(Operation operation = Operation::Undefined);
template <Operation t_op = Operation::Undefined>
constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) {
@ -165,7 +166,7 @@ static constexpr uint8_t StoreOp2 = (1 << 3);
Unusual bus sequences, such as TAS or MOVEM, are not described here.
*/
template <Model model, Operation t_operation = Operation::Undefined>
uint8_t operand_flags(Operation r_operation = Operation::Undefined);
constexpr uint8_t operand_flags(Operation r_operation = Operation::Undefined);
/// Lists the various condition codes used by the 680x0.
enum class Condition {

View File

@ -32,8 +32,11 @@ struct NullFlowController {
/// Indicates that a @c CHK was performed, along with whether the result @c was_under zero or @c was_over the source operand.
void did_chk([[maybe_unused]] bool was_under, [[maybe_unused]] bool was_over) {}
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by.
void did_shift([[maybe_unused]] int bit_count) {}
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by
/// and the type shifted.
///
/// @c IntT may be uint8_t, uint16_t or uint32_t.
template <typename IntT> void did_shift([[maybe_unused]] int bit_count) {}
/// Indicates that a @c DIVU was performed, providing the @c dividend and @c divisor.
/// If @c did_overflow is @c true then the divide ended in overflow.
@ -46,6 +49,10 @@ struct NullFlowController {
/// Indicates that a bit-manipulation operation (i.e. BTST, BCHG or BSET) was performed, affecting the bit at posiition @c bit_position.
void did_bit_op([[maybe_unused]] int bit_position) {}
/// Indicates that an @c Scc was performed; if @c did_set_ff is true then the condition was true and FF
/// written to the operand; otherwise 00 was written.
void did_scc([[maybe_unused]] bool did_set_ff) {}
/// Provides a notification that the upper byte of the status register has been affected by the current instruction;
/// this gives an opportunity to track the supervisor flag.
void did_update_status() {}

View 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 */

View File

@ -22,6 +22,8 @@ static constexpr uint16_t Zero = 1 << 2;
static constexpr uint16_t Negative = 1 << 3;
static constexpr uint16_t Extend = 1 << 4;
static constexpr uint16_t AllConditions = Carry | Overflow | Zero | Negative | Extend;
static constexpr uint16_t Supervisor = 1 << 13;
static constexpr uint16_t Trace = 1 << 15;
@ -93,8 +95,25 @@ struct Status {
return is_supervisor;
}
/// Adjusts the status for exception processing — sets supervisor mode, disables trace,
/// and if @c new_interrupt_level is greater than or equal to 0 sets that as the new
/// interrupt level.
///
/// @returns The status prior to those changes.
uint16_t begin_exception(int new_interrupt_level = -1) {
const uint16_t initial_status = status();
if(new_interrupt_level >= 0) {
interrupt_level = new_interrupt_level;
}
is_supervisor = true;
trace_flag = 0;
return initial_status;
}
/// Evaluates @c condition.
bool evaluate_condition(Condition condition) {
constexpr bool evaluate_condition(Condition condition) const {
switch(condition) {
default:
case Condition::True: return true;
@ -119,6 +138,13 @@ struct Status {
return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
}
}
/// @returns @c true if an interrupt at level @c level should be accepted; @c false otherwise.
constexpr bool would_accept_interrupt(int level) const {
// TODO: is level seven really non-maskable? If so then what mechanism prevents
// rapid stack overflow upon a level-seven interrupt?
return level > interrupt_level;
}
};
}

View File

@ -36,6 +36,8 @@
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
#include "../../../Processors/68000/68000.hpp"
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"

View File

@ -72,6 +72,14 @@ template <> union SlicedInt<uint32_t> {
#endif
uint8_t b;
};
struct {
#if TARGET_RT_BIG_ENDIAN
SlicedInt<uint16_t> high, low;
#else
SlicedInt<uint16_t> low, high;
#endif
};
};
using SlicedInt16 = SlicedInt<uint16_t>;

View File

@ -1680,6 +1680,7 @@
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = "<group>"; };
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = "<group>"; };
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BA3AE44283317CB00328FED /* RegisterSet.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSet.hpp; sourceTree = "<group>"; };
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MasterSystemVDPTests.mm; sourceTree = "<group>"; };
@ -2065,6 +2066,9 @@
4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = "<group>"; };
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502InterruptTests.swift; sourceTree = "<group>"; };
4BCA2F562832A643006C632A /* 68000Mk2.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Mk2.hpp; sourceTree = "<group>"; };
4BCA2F592832A807006C632A /* 68000Mk2Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Mk2Storage.hpp; sourceTree = "<group>"; };
4BCA2F5A2832A81C006C632A /* 68000Mk2Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 68000Mk2Implementation.hpp; sourceTree = "<group>"; };
4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreROM.cpp; path = Encodings/CommodoreROM.cpp; sourceTree = "<group>"; };
4BCA6CC71D9DD9F000C2D7B2 /* CommodoreROM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreROM.hpp; path = Encodings/CommodoreROM.hpp; sourceTree = "<group>"; };
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
@ -3218,6 +3222,7 @@
4B79629C2819681F008130F9 /* Instruction.hpp */,
4B79629D2819681F008130F9 /* Model.hpp */,
4BB5B996281B1E3F00522DA9 /* Perform.hpp */,
4BA3AE44283317CB00328FED /* RegisterSet.hpp */,
4BB5B997281B1F7B00522DA9 /* Status.hpp */,
4BB5B999281B244400522DA9 /* Implementation */,
);
@ -4334,6 +4339,7 @@
4B4DEC15252BFA9C004583AC /* 6502Esque */,
4BF8D4CC251C0C9C00BBE21B /* 65816 */,
4BFF1D332233778C00838EA1 /* 68000 */,
4BCA2F552832A643006C632A /* 68000Mk2 */,
4B77069E1EC9045B0053B588 /* Z80 */,
);
name = Processors;
@ -4561,6 +4567,24 @@
path = 6560;
sourceTree = "<group>";
};
4BCA2F552832A643006C632A /* 68000Mk2 */ = {
isa = PBXGroup;
children = (
4BCA2F562832A643006C632A /* 68000Mk2.hpp */,
4BCA2F582832A807006C632A /* Implementation */,
);
path = 68000Mk2;
sourceTree = "<group>";
};
4BCA2F582832A807006C632A /* Implementation */ = {
isa = PBXGroup;
children = (
4BCA2F592832A807006C632A /* 68000Mk2Storage.hpp */,
4BCA2F5A2832A81C006C632A /* 68000Mk2Implementation.hpp */,
);
path = Implementation;
sourceTree = "<group>";
};
4BCA6CC91D9DD9F500C2D7B2 /* Encodings */ = {
isa = PBXGroup;
children = (

File diff suppressed because it is too large Load Diff

View File

@ -32,105 +32,104 @@
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x1234567a;
state.data[2] = 0xf745ff78;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.data[1] = 0x1234567a;
registers.data[2] = 0xf745ff78;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssertEqual(state.data[1], 0x12345658);
XCTAssertEqual(state.data[2], 0xf745ff78);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Carry);
XCTAssertEqual(state.registers.data[1], 0x12345658);
XCTAssertEqual(state.registers.data[2], 0xf745ff78);
}
- (void)testABCDZero {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x12345600;
state.data[2] = 0x12345600;
state.status = Flag::Zero;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.data[1] = 0x12345600;
registers.data[2] = 0x12345600;
registers.status = ConditionCode::Zero;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Zero);
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.data[2], 0x12345600);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Zero);
XCTAssertEqual(state.registers.data[1], 0x12345600);
XCTAssertEqual(state.registers.data[2], 0x12345600);
}
- (void)testABCDNegative {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x12345645;
state.data[2] = 0x12345654;
state.status = Flag::Zero;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.data[1] = 0x12345645;
registers.data[2] = 0x12345654;
registers.status = ConditionCode::Zero;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Negative);
XCTAssertEqual(state.data[1], 0x12345699);
XCTAssertEqual(state.data[2], 0x12345654);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Negative);
XCTAssertEqual(state.registers.data[1], 0x12345699);
XCTAssertEqual(state.registers.data[2], 0x12345654);
}
- (void)testABCDWithX {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x12345645;
state.data[2] = 0x12345654;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.data[1] = 0x12345645;
registers.data[2] = 0x12345654;
registers.status = ConditionCode::Extend;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.data[2], 0x12345654);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Carry);
XCTAssertEqual(state.registers.data[1], 0x12345600);
XCTAssertEqual(state.registers.data[2], 0x12345654);
}
- (void)testABCDOverflow {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x1234563e;
state.data[2] = 0x1234563e;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.data[1] = 0x1234563e;
registers.data[2] = 0x1234563e;
registers.status = ConditionCode::Extend;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Overflow);
XCTAssertEqual(state.data[1], 0x12345683);
XCTAssertEqual(state.data[2], 0x1234563e);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Overflow);
XCTAssertEqual(state.registers.data[1], 0x12345683);
XCTAssertEqual(state.registers.data[2], 0x1234563e);
}
- (void)testABCDPredecDifferent {
_machine->set_program({
0xc30a, // ABCD -(A2), -(A1)
});
_machine->set_registers([=](auto &registers){
registers.address[1] = 0x3001;
registers.address[2] = 0x4001;
registers.status = ConditionCode::Extend;
});
*_machine->ram_at(0x3000) = 0xa200;
*_machine->ram_at(0x4000) = 0x1900;
auto state = _machine->get_processor_state();
state.address[1] = 0x3001;
state.address[2] = 0x4001;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssert(state.status & Flag::Extend);
XCTAssertEqual(state.address[1], 0x3000);
XCTAssertEqual(state.address[2], 0x4000);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Carry);
XCTAssert(state.registers.status & ConditionCode::Extend);
XCTAssertEqual(state.registers.address[1], 0x3000);
XCTAssertEqual(state.registers.address[2], 0x4000);
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
}
@ -139,18 +138,17 @@
_machine->set_program({
0xc309, // ABCD -(A1), -(A1)
});
_machine->set_registers([=](auto &registers){
registers.address[1] = 0x3002;
registers.status = ConditionCode::Extend;
});
*_machine->ram_at(0x3000) = 0x19a2;
auto state = _machine->get_processor_state();
state.address[1] = 0x3002;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssert(state.status & Flag::Extend);
XCTAssertEqual(state.address[1], 0x3000);
const auto state = _machine->get_processor_state();
XCTAssert(state.registers.status & ConditionCode::Carry);
XCTAssert(state.registers.status & ConditionCode::Extend);
XCTAssertEqual(state.registers.address[1], 0x3000);
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
}
@ -160,11 +158,10 @@
_machine->set_program({
0x4801 // NBCD D1
});
auto state = _machine->get_processor_state();
state.status |= ccr;
state.data[1] = d1;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.status |= ccr;
registers.data[1] = d1;
});
_machine->run_for_instructions(1);
XCTAssertEqual(6, _machine->get_cycle_count());
@ -174,32 +171,32 @@
[self performNBCDd1:0x7a ccr:0];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x20);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
XCTAssertEqual(state.registers.data[1], 0x20);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
}
- (void)testNBCD_Dn_extend {
[self performNBCDd1:0x1234567a ccr:Flag::Extend | Flag::Zero];
[self performNBCDd1:0x1234567a ccr:ConditionCode::Extend | ConditionCode::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x1234561f);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
XCTAssertEqual(state.registers.data[1], 0x1234561f);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
}
- (void)testNBCD_Dn_zero {
[self performNBCDd1:0x12345600 ccr:Flag::Zero];
[self performNBCDd1:0x12345600 ccr:ConditionCode::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
XCTAssertEqual(state.registers.data[1], 0x12345600);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Zero);
}
- (void)testNBCD_Dn_negative {
[self performNBCDd1:0x123456ff ccr:Flag::Extend | Flag::Zero];
[self performNBCDd1:0x123456ff ccr:ConditionCode::Extend | ConditionCode::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x1234569a);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
XCTAssertEqual(state.registers.data[1], 0x1234569a);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
}
- (void)testNBCD_Dn_XXXw {
@ -213,7 +210,7 @@
const auto state = _machine->get_processor_state();
XCTAssertEqual(16, _machine->get_cycle_count());
XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
}
// MARK: SBCD
@ -222,49 +219,48 @@
_machine->set_program({
0x8302 // SBCD D2, D1
});
auto state = _machine->get_processor_state();
state.status |= ccr;
state.data[1] = d1;
state.data[2] = d2;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.status |= ccr;
registers.data[1] = d1;
registers.data[2] = d2;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
const auto state = _machine->get_processor_state();
XCTAssertEqual(6, _machine->get_cycle_count());
XCTAssertEqual(state.data[2], d2);
XCTAssertEqual(state.registers.data[2], d2);
}
- (void)testSBCD_Dn {
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:Flag::Zero];
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:ConditionCode::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345611);
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
XCTAssertEqual(state.registers.data[1], 0x12345611);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
}
- (void)testSBCD_Dn_zero {
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:Flag::Zero];
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:ConditionCode::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
XCTAssertEqual(state.registers.data[1], 0x12345600);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Zero);
}
- (void)testSBCD_Dn_negative {
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:Flag::Extend];
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:ConditionCode::Extend];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345688);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
XCTAssertEqual(state.registers.data[1], 0x12345688);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Negative);
}
- (void)testSBCD_Dn_overflow {
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:Flag::Extend];
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:ConditionCode::Extend];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345643);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
XCTAssertEqual(state.registers.data[1], 0x12345643);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Carry | ConditionCode::Overflow);
}
- (void)testSBCD_Dn_PreDec {
@ -273,19 +269,18 @@
});
*_machine->ram_at(0x3000) = 0xa200;
*_machine->ram_at(0x4000) = 0x1900;
auto state = _machine->get_processor_state();
state.address[1] = 0x3001;
state.address[2] = 0x4001;
state.status |= Flag::Extend;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.address[1] = 0x3001;
registers.address[2] = 0x4001;
registers.status |= ConditionCode::Extend;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
const auto state = _machine->get_processor_state();
XCTAssertEqual(18, _machine->get_cycle_count());
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Negative);
}
@end

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
#import <XCTest/XCTest.h>
#include "../../../Processors/68000/68000.hpp"
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
#include "../../../InstructionSets/M68k/Executor.hpp"
#include "../../../InstructionSets/M68k/Decoder.hpp"
@ -16,18 +16,18 @@
#include <memory>
#include <functional>
//#define USE_EXISTING_IMPLEMENTATION
//#define USE_EXECUTOR
//#define MAKE_SUGGESTIONS
//#define LOG_UNTESTED
namespace {
/// Binds a 68000 executor to 16mb of RAM.
struct Test68000 {
std::array<uint8_t, 16*1024*1024> ram;
InstructionSet::M68k::Executor<InstructionSet::M68k::Model::M68000, Test68000> processor;
struct TestExecutor {
uint8_t *const ram;
InstructionSet::M68k::Executor<InstructionSet::M68k::Model::M68000, TestExecutor> processor;
Test68000() : processor(*this) {
}
TestExecutor(uint8_t *ram) : ram(ram), processor(*this) {}
void run_for_instructions(int instructions) {
processor.run_for_instructions(instructions);
@ -83,6 +83,39 @@ struct Test68000 {
}
};
/// Binds a bus-accurate 68000 to 16mb of RAM.
struct TestProcessor: public CPU::MC68000Mk2::BusHandler {
uint8_t *const ram;
CPU::MC68000Mk2::Processor<TestProcessor, true, true, true> processor;
std::function<void(void)> comparitor;
TestProcessor(uint8_t *ram) : ram(ram), processor(*this) {}
void will_perform(uint32_t, uint16_t) {
--instructions_remaining_;
if(!instructions_remaining_) comparitor();
}
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
using Microcycle = CPU::MC68000Mk2::Microcycle;
if(cycle.data_select_active()) {
cycle.apply(&ram[cycle.host_endian_byte_address()]);
}
return HalfCycles(0);
}
void run_for_instructions(int instructions, const std::function<void(void)> &compare) {
instructions_remaining_ = instructions + 1; // i.e. run up to the will_perform of the instruction after.
comparitor = std::move(compare);
while(instructions_remaining_) {
processor.run_for(HalfCycles(2));
}
}
private:
int instructions_remaining_;
};
}
@interface M68000ComparativeTests : XCTestCase
@ -95,23 +128,41 @@ struct Test68000 {
NSMutableSet<NSString *> *_failures;
NSMutableArray<NSNumber *> *_failingOpcodes;
NSMutableDictionary<NSNumber *, NSMutableArray<NSString *> *> *_suggestedCorrections;
NSMutableSet<NSNumber *> *_testedOpcodes;
InstructionSet::M68k::Predecoder<InstructionSet::M68k::Model::M68000> _decoder;
Test68000 _test68000;
std::array<uint16_t, 8*1024*1024> _ram;
std::unique_ptr<TestExecutor> _testExecutor;
}
- (void)setUp {
// Definitively erase any prior memory contents;
// 0xce is arbitrary but hopefully easier to spot
// in potential errors than e.g. 0x00 or 0xff.
_ram.fill(0xcece);
// TODO: possibly, worry about resetting RAM to 0xce after tests have completed.
#ifdef USE_EXECUTOR
_testExecutor = std::make_unique<TestExecutor>(reinterpret_cast<uint8_t *>(_ram.data()));
#endif
// These will accumulate a list of failing tests and associated opcodes.
_failures = [[NSMutableSet alloc] init];
_failingOpcodes = [[NSMutableArray alloc] init];
// This will simply accumulate a list of all tested opcodes, in order to report
// on those that are missing.
_testedOpcodes = [[NSMutableSet alloc] init];
#ifdef MAKE_SUGGESTIONS
_suggestedCorrections = [[NSMutableDictionary alloc] init];
#endif
// To limit tests run to a subset of files and/or of tests, uncomment and fill in below.
// _fileSet = [NSSet setWithArray:@[@"exg.json"]];
// _testSet = [NSSet setWithArray:@[@"CHK 41a8"]];
// _fileSet = [NSSet setWithArray:@[@"abcd_sbcd.json"]];
// _testSet = [NSSet setWithArray:@[@"LINK.w 0007"]];
}
- (void)testAll {
@ -126,7 +177,7 @@ struct Test68000 {
// NSLog(@"Testing %@", url);
[self testJSONAtURL:url];
}
XCTAssert(_failures.count == 0);
// Output a summary of failures, if any.
@ -145,6 +196,25 @@ struct Test68000 {
}
}
}
#ifdef LOG_UNTESTED
// Output a list of untested opcodes.
NSMutableArray<NSString *> *untested = [[NSMutableArray alloc] init];
for(int opcode = 0; opcode < 65536; opcode++) {
const auto instruction = _decoder.decode(uint16_t(opcode));
if(instruction.operation == InstructionSet::M68k::Operation::Undefined) {
continue;
}
if([_testedOpcodes containsObject:@(opcode)]) {
continue;
}
[untested addObject:[NSString stringWithFormat:@"%04x %s", opcode, instruction.to_string(uint16_t(opcode)).c_str()]];
}
NSLog(@"Untested: %@", untested);
#endif
}
- (void)testJSONAtURL:(NSURL *)url {
@ -153,9 +223,9 @@ struct Test68000 {
NSError *error;
NSArray *const jsonContents = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if(!data || error || ![jsonContents isKindOfClass:[NSArray class]]) {
return;
}
XCTAssertNil(error);
XCTAssertNotNil(jsonContents);
XCTAssert([jsonContents isKindOfClass:[NSArray class]]);
// Perform each dictionary in the array as a test.
for(NSDictionary *test in jsonContents) {
@ -168,52 +238,33 @@ struct Test68000 {
// Compare against a test set if one has been supplied.
if(_testSet && ![_testSet containsObject:name]) continue;
#ifdef USE_EXISTING_IMPLEMENTATION
[self testOperationClassic:test name:name];
#else
// Pull out the opcode and record it.
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
uint16_t opcode = 0;
NSEnumerator<NSNumber *> *enumerator = [initialMemory objectEnumerator];
while(true) {
NSNumber *const address = [enumerator nextObject];
NSNumber *const value = [enumerator nextObject];
if(!address || !value) break;
if(address.integerValue == 0x100) opcode |= value.integerValue << 8;
if(address.integerValue == 0x101) opcode |= value.integerValue;
}
[_testedOpcodes addObject:@(opcode)];
#ifdef USE_EXECUTOR
[self testOperationExecutor:test name:name];
#else
[self testOperationClassic:test name:name];
#endif
}
}
- (void)testOperationClassic:(NSDictionary *)test name:(NSString *)name {
struct TerminateMarker {};
// This is the test class for 68000 execution.
struct Test68000: public CPU::MC68000::BusHandler {
std::array<uint8_t, 16*1024*1024> ram;
CPU::MC68000::Processor<Test68000, true, true> processor;
std::function<void(void)> comparitor;
Test68000() : processor(*this) {
}
void will_perform(uint32_t, uint16_t) {
--instructions_remaining_;
if(!instructions_remaining_) comparitor();
}
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
using Microcycle = CPU::MC68000::Microcycle;
if(cycle.data_select_active()) {
cycle.apply(&ram[cycle.host_endian_byte_address()]);
}
return HalfCycles(0);
}
void run_for_instructions(int instructions, const std::function<void(void)> &compare) {
instructions_remaining_ = instructions + 1; // i.e. run up to the will_perform of the instruction after.
comparitor = std::move(compare);
while(instructions_remaining_) {
processor.run_for(HalfCycles(2));
}
}
private:
int instructions_remaining_;
};
auto uniqueTest68000 = std::make_unique<Test68000>();
auto uniqueTest68000 = std::make_unique<TestProcessor>(reinterpret_cast<uint8_t *>(_ram.data()));
auto test68000 = uniqueTest68000.get();
memset(test68000->ram.data(), 0xce, test68000->ram.size());
{
// Apply initial memory state.
@ -228,47 +279,24 @@ struct Test68000 {
}
// Apply initial processor state.
NSDictionary *const initialState = test[@"initial state"];
auto state = test68000->processor.get_state();
for(int c = 0; c < 8; ++c) {
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
state.data[c] = uint32_t([initialState[dX] integerValue]);
if(c < 7)
state.address[c] = uint32_t([initialState[aX] integerValue]);
}
state.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]);
state.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]);
state.status = [initialState[@"sr"] integerValue];
state.registers = [self initialRegisters:test];
test68000->processor.set_state(state);
}
// Check that this is a defined opcode; capture of the unrecognised instruction
// exception doesn't work correctly with the way that this test class tries
// to detect the gaps between operations.
const uint16_t opcode = (test68000->ram[0x101] << 8) | test68000->ram[0x100];
if(_decoder.decode(opcode).operation == InstructionSet::M68k::Operation::Undefined) {
return;
}
// Run the thing.
const auto comparitor = [=] {
// Test the end state.
NSDictionary *const finalState = test[@"final state"];
const auto state = test68000->processor.get_state();
for(int c = 0; c < 8; ++c) {
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
if(state.data[c] != [finalState[dX] integerValue]) [_failures addObject:name];
if(c < 7 && state.address[c] != [finalState[aX] integerValue]) [_failures addObject:name];
XCTAssertEqual(state.data[c], [finalState[dX] integerValue], @"%@: D%d inconsistent", name, c);
if(c < 7) {
XCTAssertEqual(state.address[c], [finalState[aX] integerValue], @"%@: A%d inconsistent", name, c);
}
}
if(state.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name];
if(state.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name];
if(state.status != [finalState[@"sr"] integerValue]) [_failures addObject:name];
XCTAssertEqual(state.supervisor_stack_pointer, [finalState[@"a7"] integerValue], @"%@: A7 inconsistent", name);
XCTAssertEqual(state.user_stack_pointer, [finalState[@"usp"] integerValue], @"%@: USP inconsistent", name);
XCTAssertEqual(state.status, [finalState[@"sr"] integerValue], @"%@: Status inconsistent", name);
XCTAssertEqual(state.program_counter - 4, [finalState[@"pc"] integerValue], @"%@: Program counter inconsistent", name);
[self test:test name:name compareFinalRegisters:state.registers opcode:opcode pcOffset:-4];
// Test final memory state.
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
@ -284,19 +312,19 @@ struct Test68000 {
// Consider collating extra detail.
if([_failures containsObject:name]) {
[_failingOpcodes addObject:@((test68000->ram[0x101] << 8) | test68000->ram[0x100])];
[_failingOpcodes addObject:@(opcode)];
}
// Make sure nothing further occurs; keep this test isolated.
throw TerminateMarker();
};
test68000->run_for_instructions(1, comparitor);
try {
test68000->run_for_instructions(1, comparitor);
} catch(TerminateMarker m) {}
}
- (void)setInitialState:(NSDictionary *)test {
// Definitively erase any prior memory contents;
// 0xce is arbitrary but hopefully easier to spot
// in potential errors than e.g. 0x00 or 0xff.
memset(_test68000.ram.data(), 0xce, _test68000.ram.size());
// Apply initial memory state.
NSArray<NSNumber *> *const initialMemory = test[@"initial memory"];
NSEnumerator<NSNumber *> *enumerator = [initialMemory objectEnumerator];
@ -305,72 +333,23 @@ struct Test68000 {
NSNumber *const value = [enumerator nextObject];
if(!address || !value) break;
_test68000.ram[address.integerValue] = value.integerValue;
_testExecutor->ram[address.integerValue] = value.integerValue;
}
// Apply initial processor state.
NSDictionary *const initialState = test[@"initial state"];
auto state = _test68000.processor.get_state();
for(int c = 0; c < 8; ++c) {
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
state.data[c] = uint32_t([initialState[dX] integerValue]);
if(c < 7)
state.address[c] = uint32_t([initialState[aX] integerValue]);
}
state.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]);
state.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]);
state.status = [initialState[@"sr"] integerValue];
state.program_counter = uint32_t([initialState[@"pc"] integerValue]);
_test68000.processor.set_state(state);
_testExecutor->processor.set_state([self initialRegisters:test]);
}
- (void)testOperationExecutor:(NSDictionary *)test name:(NSString *)name {
[self setInitialState:test];
// Run the thing.
_test68000.run_for_instructions(1);
_testExecutor->run_for_instructions(1);
// Test the end state.
NSDictionary *const finalState = test[@"final state"];
const auto state = _test68000.processor.get_state();
for(int c = 0; c < 8; ++c) {
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
if(state.data[c] != [finalState[dX] integerValue]) [_failures addObject:name];
if(c < 7 && state.address[c] != [finalState[aX] integerValue]) [_failures addObject:name];
}
if(state.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name];
if(state.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name];
const uint16_t correctSR = [finalState[@"sr"] integerValue];
if(state.status != correctSR) {
const uint16_t opcode = _test68000.read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode());
const auto instruction = _decoder.decode(opcode);
// For DIVU and DIVS, for now, test only the well-defined flags.
if(
instruction.operation != InstructionSet::M68k::Operation::DIVS &&
instruction.operation != InstructionSet::M68k::Operation::DIVU
) {
[_failures addObject:name];
} else {
uint16_t status_mask = 0xff13; // i.e. extend, which should be unaffected, and overflow, which
// is well-defined unless there was a divide by zero. But this
// test set doesn't include any divide by zeroes.
if(!(correctSR & InstructionSet::M68k::ConditionCode::Overflow)) {
// If overflow didn't occur then negative and zero are also well-defined.
status_mask |= 0x000c;
}
if((state.status & status_mask) != (([finalState[@"sr"] integerValue]) & status_mask)) {
[_failures addObject:name];
}
}
}
const auto state = _testExecutor->processor.get_state();
const uint16_t opcode = _testExecutor->read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode());
[self test:test name:name compareFinalRegisters:state opcode:opcode pcOffset:0];
// Test final memory state.
NSArray<NSNumber *> *const finalMemory = test[@"final memory"];
@ -380,16 +359,14 @@ struct Test68000 {
NSNumber *const value = [enumerator nextObject];
if(!address || !value) break;
if(_test68000.ram[address.integerValue] != value.integerValue) [_failures addObject:name];
if(_testExecutor->ram[address.integerValue] != value.integerValue) [_failures addObject:name];
}
// If this test is now in the failures set, add the corresponding opcode for
// later logging.
if([_failures containsObject:name]) {
NSNumber *const opcode = @(_test68000.read<uint16_t>(0x100, InstructionSet::M68k::FunctionCode()));
// Add this opcode to the failing list.
[_failingOpcodes addObject:opcode];
[_failingOpcodes addObject:@(opcode)];
// Generate the JSON that would have satisfied this test, at least as far as registers go,
// if those are being collected.
@ -412,10 +389,71 @@ struct Test68000 {
[NSJSONSerialization dataWithJSONObject:generatedTest options:0 error:nil]
encoding:NSUTF8StringEncoding];
if(_suggestedCorrections[opcode]) {
[_suggestedCorrections[opcode] addObject:generatedJSON];
if(_suggestedCorrections[@(opcode)]) {
[_suggestedCorrections[@(opcode)] addObject:generatedJSON];
} else {
_suggestedCorrections[opcode] = [NSMutableArray arrayWithObject:generatedJSON];
_suggestedCorrections[@(opcode)] = [NSMutableArray arrayWithObject:generatedJSON];
}
}
}
}
- (InstructionSet::M68k::RegisterSet)initialRegisters:(NSDictionary *)test {
InstructionSet::M68k::RegisterSet registers;
NSDictionary *const initialState = test[@"initial state"];
for(int c = 0; c < 8; ++c) {
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
registers.data[c] = uint32_t([initialState[dX] integerValue]);
if(c < 7)
registers.address[c] = uint32_t([initialState[aX] integerValue]);
}
registers.supervisor_stack_pointer = uint32_t([initialState[@"a7"] integerValue]);
registers.user_stack_pointer = uint32_t([initialState[@"usp"] integerValue]);
registers.status = [initialState[@"sr"] integerValue];
registers.program_counter = uint32_t([initialState[@"pc"] integerValue]);
return registers;
}
- (void)test:(NSDictionary *)test name:(NSString *)name compareFinalRegisters:(InstructionSet::M68k::RegisterSet)registers opcode:(uint16_t)opcode pcOffset:(int)pcOffset {
// Test the end state.
NSDictionary *const finalState = test[@"final state"];
for(int c = 0; c < 8; ++c) {
const NSString *dX = [@"d" stringByAppendingFormat:@"%d", c];
const NSString *aX = [@"a" stringByAppendingFormat:@"%d", c];
if(registers.data[c] != [finalState[dX] integerValue]) [_failures addObject:name];
if(c < 7 && registers.address[c] != [finalState[aX] integerValue]) [_failures addObject:name];
}
if(registers.supervisor_stack_pointer != [finalState[@"a7"] integerValue]) [_failures addObject:name];
if(registers.user_stack_pointer != [finalState[@"usp"] integerValue]) [_failures addObject:name];
if(registers.program_counter + pcOffset != [finalState[@"pc"] integerValue]) [_failures addObject:name];
const uint16_t correctSR = [finalState[@"sr"] integerValue];
if(registers.status != correctSR) {
const auto instruction = _decoder.decode(opcode);
// For DIVU and DIVS, for now, test only the well-defined flags.
if(
instruction.operation != InstructionSet::M68k::Operation::DIVS &&
instruction.operation != InstructionSet::M68k::Operation::DIVU
) {
[_failures addObject:name];
} else {
uint16_t status_mask = 0xff13; // i.e. extend, which should be unaffected, and overflow, which
// is well-defined unless there was a divide by zero. But this
// test set doesn't include any divide by zeroes.
if(!(correctSR & InstructionSet::M68k::ConditionCode::Overflow)) {
// If overflow didn't occur then negative and zero are also well-defined.
status_mask |= 0x000c;
}
if((registers.status & status_mask) != (([finalState[@"sr"] integerValue]) & status_mask)) {
[_failures addObject:name];
}
}
}

View File

@ -49,7 +49,7 @@
[self performBccb:0x6200];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
@ -57,7 +57,7 @@
[self performBccb:0x6500];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 8);
}
@ -65,7 +65,7 @@
[self performBccw:0x6200];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
@ -73,7 +73,7 @@
[self performBccw:0x6500];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1004 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1004 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 12);
}
@ -87,7 +87,7 @@
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1006 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1006 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
@ -99,7 +99,7 @@
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1006 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1006 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
@ -108,15 +108,14 @@
- (void)testBSRw {
_machine->set_program({
0x6100, 0x0006 // BSR.w $1008
});
_machine->set_initial_stack_pointer(0x3000);
}, 0x3000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
XCTAssertEqual(state.registers.stack_pointer(), 0x2ffc);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
@ -126,15 +125,14 @@
- (void)testBSRb {
_machine->set_program({
0x6106 // BSR.b $1008
});
_machine->set_initial_stack_pointer(0x3000);
}, 0x3000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
XCTAssertEqual(state.registers.stack_pointer(), 0x2ffc);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, 0);
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
@ -146,56 +144,61 @@
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
_machine->set_program({
0x4581 // CHK D1, D2
}, 0);
_machine->set_registers([=](auto &registers) {
registers.data[1] = d1;
registers.data[2] = d2;
registers.status |= ConditionCode::AllConditions;
});
auto state = _machine->get_processor_state();
state.data[1] = d1;
state.data[2] = d2;
state.status |= Flag::ConditionCodes;
_machine->set_initial_stack_pointer(0);
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], d1);
XCTAssertEqual(state.data[2], d2);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.registers.data[1], d1);
XCTAssertEqual(state.registers.data[2], d2);
}
// Re: CHK, below; the final state of N is undocumented if Dn >= 0 and Dn < <ea>.
// Z, V and C are also undocumented by Motorola, but are documneted by 68knotes.txt.
- (void)testCHK_1111v1111 {
[self performCHKd1:0x1111 d2:0x1111];
[self performCHKd1:0x1111 d2:0x1111]; // Neither exception-generating state applies.
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
XCTAssertEqual(
state.registers.status & (ConditionCode::Extend | ConditionCode::Zero | ConditionCode::Overflow | ConditionCode::Carry),
ConditionCode::Extend);
XCTAssertEqual(10, _machine->get_cycle_count());
}
- (void)testCHK_1111v0000 {
[self performCHKd1:0x1111 d2:0x0000];
[self performCHKd1:0x1111 d2:0x0000]; // Neither exception-generating state applies.
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero);
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
XCTAssertEqual(
state.registers.status & (ConditionCode::Extend | ConditionCode::Zero | ConditionCode::Overflow | ConditionCode::Carry),
ConditionCode::Extend | ConditionCode::Zero);
XCTAssertEqual(10, _machine->get_cycle_count());
}
- (void)testCHK_8000v8001 {
[self performCHKd1:0x8000 d2:0x8001];
[self performCHKd1:0x8000 d2:0x8001]; // Both less than 0 and D2 greater than D1.
const auto state = _machine->get_processor_state();
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.stack_pointer(), 0xfffffffa);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
XCTAssertEqual(42, _machine->get_cycle_count());
XCTAssertNotEqual(state.registers.program_counter, 0x1002 + 4);
XCTAssertEqual(state.registers.stack_pointer(), 0xfffffffa);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Negative);
XCTAssertEqual(38, _machine->get_cycle_count());
}
- (void)testCHK_8000v8000 {
[self performCHKd1:0x8000 d2:0x8000];
[self performCHKd1:0x8000 d2:0x8000]; // Less than 0.
const auto state = _machine->get_processor_state();
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative);
XCTAssertEqual(44, _machine->get_cycle_count());
XCTAssertNotEqual(state.registers.program_counter, 0x1002 + 4);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::Extend | ConditionCode::Negative);
XCTAssertEqual(40, _machine->get_cycle_count());
}
// MARK: DBcc
@ -204,16 +207,15 @@
_machine->set_program({
opcode, 0x0008 // DBcc D2, +8
});
auto state = _machine->get_processor_state();
state.status = status;
state.data[2] = 1;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers) {
registers.status = status;
registers.data[2] = 1;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.data[2], d2Output);
XCTAssertEqual(state.status, status);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.registers.data[2], d2Output);
XCTAssertEqual(state.registers.status, status);
}
- (void)testDBT {
@ -229,27 +231,27 @@
}
- (void)testDBHI_Carry {
[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0];
[self performDBccTestOpcode:0x52ca status:ConditionCode::Carry d2Outcome:0];
}
- (void)testDBHI_Zero {
[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0];
[self performDBccTestOpcode:0x52ca status:ConditionCode::Zero d2Outcome:0];
}
- (void)testDBLS_CarryOverflow {
[self performDBccTestOpcode:0x53ca status:Flag::Carry | Flag::Overflow d2Outcome:1];
[self performDBccTestOpcode:0x53ca status:ConditionCode::Carry | ConditionCode::Overflow d2Outcome:1];
}
- (void)testDBLS_Carry {
[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1];
[self performDBccTestOpcode:0x53ca status:ConditionCode::Carry d2Outcome:1];
}
- (void)testDBLS_Overflow {
[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0];
[self performDBccTestOpcode:0x53ca status:ConditionCode::Overflow d2Outcome:0];
}
- (void)testDBCC_Carry {
[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0];
[self performDBccTestOpcode:0x54ca status:ConditionCode::Carry d2Outcome:0];
}
- (void)testDBCC {
@ -261,7 +263,7 @@
}
- (void)testDBCS_Carry {
[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1];
[self performDBccTestOpcode:0x55ca status:ConditionCode::Carry d2Outcome:1];
}
- (void)testDBNE {
@ -269,7 +271,7 @@
}
- (void)testDBNE_Zero {
[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0];
[self performDBccTestOpcode:0x56ca status:ConditionCode::Zero d2Outcome:0];
}
- (void)testDBEQ {
@ -277,7 +279,7 @@
}
- (void)testDBEQ_Zero {
[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1];
[self performDBccTestOpcode:0x57ca status:ConditionCode::Zero d2Outcome:1];
}
- (void)testDBVC {
@ -285,7 +287,7 @@
}
- (void)testDBVC_Overflow {
[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0];
[self performDBccTestOpcode:0x58ca status:ConditionCode::Overflow d2Outcome:0];
}
- (void)testDBVS {
@ -293,7 +295,7 @@
}
- (void)testDBVS_Overflow {
[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1];
[self performDBccTestOpcode:0x59ca status:ConditionCode::Overflow d2Outcome:1];
}
- (void)testDBPL {
@ -301,7 +303,7 @@
}
- (void)testDBPL_Negative {
[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0];
[self performDBccTestOpcode:0x5aca status:ConditionCode::Negative d2Outcome:0];
}
- (void)testDBMI {
@ -309,11 +311,11 @@
}
- (void)testDBMI_Negative {
[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1];
[self performDBccTestOpcode:0x5bca status:ConditionCode::Negative d2Outcome:1];
}
- (void)testDBGE_NegativeOverflow {
[self performDBccTestOpcode:0x5cca status:Flag::Negative | Flag::Overflow d2Outcome:1];
[self performDBccTestOpcode:0x5cca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:1];
}
- (void)testDBGE {
@ -321,15 +323,15 @@
}
- (void)testDBGE_Negative {
[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0];
[self performDBccTestOpcode:0x5cca status:ConditionCode::Negative d2Outcome:0];
}
- (void)testDBGE_Overflow {
[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0];
[self performDBccTestOpcode:0x5cca status:ConditionCode::Overflow d2Outcome:0];
}
- (void)testDBLT_NegativeOverflow {
[self performDBccTestOpcode:0x5dca status:Flag::Negative | Flag::Overflow d2Outcome:0];
[self performDBccTestOpcode:0x5dca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:0];
}
- (void)testDBLT {
@ -337,11 +339,11 @@
}
- (void)testDBLT_Negative {
[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1];
[self performDBccTestOpcode:0x5dca status:ConditionCode::Negative d2Outcome:1];
}
- (void)testDBLT_Overflow {
[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1];
[self performDBccTestOpcode:0x5dca status:ConditionCode::Overflow d2Outcome:1];
}
- (void)testDBGT {
@ -349,15 +351,15 @@
}
- (void)testDBGT_ZeroNegativeOverflow {
[self performDBccTestOpcode:0x5eca status:Flag::Zero | Flag::Negative | Flag::Overflow d2Outcome:0];
[self performDBccTestOpcode:0x5eca status:ConditionCode::Zero | ConditionCode::Negative | ConditionCode::Overflow d2Outcome:0];
}
- (void)testDBGT_NegativeOverflow {
[self performDBccTestOpcode:0x5eca status:Flag::Negative | Flag::Overflow d2Outcome:1];
[self performDBccTestOpcode:0x5eca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:1];
}
- (void)testDBGT_Zero {
[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0];
[self performDBccTestOpcode:0x5eca status:ConditionCode::Zero d2Outcome:0];
}
- (void)testDBLE {
@ -365,15 +367,15 @@
}
- (void)testDBLE_Zero {
[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1];
[self performDBccTestOpcode:0x5fca status:ConditionCode::Zero d2Outcome:1];
}
- (void)testDBLE_Negative {
[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1];
[self performDBccTestOpcode:0x5fca status:ConditionCode::Negative d2Outcome:1];
}
- (void)testDBLE_NegativeOverflow {
[self performDBccTestOpcode:0x5fca status:Flag::Negative | Flag::Overflow d2Outcome:0];
[self performDBccTestOpcode:0x5fca status:ConditionCode::Negative | ConditionCode::Overflow d2Outcome:0];
}
/* Further DBF tests omitted; they seemed to be duplicative, assuming I'm not suffering a failure of comprehension. */
@ -385,15 +387,14 @@
0x4ed1 // JMP (A1)
});
auto state = _machine->get_processor_state();
state.address[1] = 0x3000;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers) {
registers.address[1] = 0x3000;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.address[1], 0x3000);
XCTAssertEqual(state.program_counter, 0x3000 + 4);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.registers.address[1], 0x3000);
XCTAssertEqual(state.registers.program_counter, 0x3000 + 4);
XCTAssertEqual(8, _machine->get_cycle_count());
}
@ -405,7 +406,7 @@
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x100c + 4);
XCTAssertEqual(state.registers.program_counter, 0x100c + 4);
XCTAssertEqual(10, _machine->get_cycle_count());
}
@ -414,14 +415,13 @@
- (void)testJSR_PC {
_machine->set_program({
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
});
_machine->set_initial_stack_pointer(0x2000);
}, 0x2000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
XCTAssertEqual(state.program_counter, 0x100c + 4);
XCTAssertEqual(state.registers.stack_pointer(), 0x1ffc);
XCTAssertEqual(state.registers.program_counter, 0x100c + 4);
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
XCTAssertEqual(18, _machine->get_cycle_count());
@ -430,14 +430,13 @@
- (void)testJSR_XXXl {
_machine->set_program({
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
});
_machine->set_initial_stack_pointer(0x2000);
}, 0x2000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.registers.stack_pointer(), 0x1ffc);
XCTAssertEqual(state.registers.program_counter, 0x1008 + 4);
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
XCTAssertEqual(20, _machine->get_cycle_count());
@ -458,8 +457,7 @@
- (void)testRTR {
_machine->set_program({
0x4e77 // RTR
});
_machine->set_initial_stack_pointer(0x2000);
}, 0x2000);
*_machine->ram_at(0x2000) = 0x7fff;
*_machine->ram_at(0x2002) = 0;
*_machine->ram_at(0x2004) = 0xc;
@ -467,9 +465,9 @@
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x2006);
XCTAssertEqual(state.program_counter, 0x10);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes);
XCTAssertEqual(state.registers.stack_pointer(), 0x2006);
XCTAssertEqual(state.registers.program_counter, 0x10);
XCTAssertEqual(state.registers.status & ConditionCode::AllConditions, ConditionCode::AllConditions);
XCTAssertEqual(20, _machine->get_cycle_count());
}
@ -478,16 +476,15 @@
- (void)testRTS {
_machine->set_program({
0x4e75 // RTS
});
_machine->set_initial_stack_pointer(0x2000);
}, 0x2000);
*_machine->ram_at(0x2000) = 0x0000;
*_machine->ram_at(0x2002) = 0x000c;
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x2004);
XCTAssertEqual(state.program_counter, 0x000c + 4);
XCTAssertEqual(state.registers.stack_pointer(), 0x2004);
XCTAssertEqual(state.registers.program_counter, 0x000c + 4);
XCTAssertEqual(16, _machine->get_cycle_count());
}
@ -497,22 +494,21 @@
_machine->set_program({
0x4e41 // TRAP #1
});
auto state = _machine->get_processor_state();
state.status = 0x700;
state.user_stack_pointer = 0x200;
state.supervisor_stack_pointer = 0x206;
_machine->set_registers([=](auto &registers) {
registers.status = 0x700;
registers.user_stack_pointer = 0x200;
registers.supervisor_stack_pointer = 0x206;
});
*_machine->ram_at(0x84) = 0xfffe;
*_machine->ram_at(0xfffe) = 0x4e71;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.status, 0x2700);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.registers.status, 0x2700);
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
XCTAssertEqual(state.supervisor_stack_pointer, 0x200);
XCTAssertEqual(state.registers.supervisor_stack_pointer, 0x200);
XCTAssertEqual(34, _machine->get_cycle_count());
}
@ -521,21 +517,20 @@
- (void)testTRAPV_taken {
_machine->set_program({
0x4e76 // TRAPV
});
_machine->set_initial_stack_pointer(0x206);
}, 0x206);
auto state = _machine->get_processor_state();
state.status = 0x702;
state.supervisor_stack_pointer = 0x206;
_machine->set_registers([=](auto &registers) {
registers.status = 0x702;
registers.supervisor_stack_pointer = 0x206;
});
*_machine->ram_at(0x1e) = 0xfffe;
*_machine->ram_at(0xfffe) = 0x4e71;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.status, 0x2702);
XCTAssertEqual(state.stack_pointer(), 0x200);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.registers.status, 0x2702);
XCTAssertEqual(state.registers.stack_pointer(), 0x200);
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
@ -550,7 +545,7 @@
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.registers.program_counter, 0x1002 + 4);
XCTAssertEqual(4, _machine->get_cycle_count());
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,94 +12,25 @@
#include "TestRunner68000.hpp"
class CPU::MC68000::ProcessorStorageTests {
public:
ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) {
false_valids_ = [NSMutableSet set];
false_invalids_ = [NSMutableSet set];
FILE *source = fopen(coverage_file_name, "rb");
// The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]...
// The file terminates with four additional bytes that begin with two zero bytes.
//
// The version of the file I grabbed seems to cover all opcodes, making their enumeration
// arguably redundant; the code below nevertheless uses the codes from the file.
//
// Similarly, I'm testing for exactly the strings VA or IN to ensure no further
// types creep into any updated version of the table that I then deal with incorrectly.
uint16_t last_observed = 0;
while(true) {
// Fetch opcode number.
uint16_t next_opcode = fgetc(source) << 8;
next_opcode |= fgetc(source);
if(next_opcode < last_observed) break;
last_observed = next_opcode;
// Determine whether it's meant to be valid.
char type[3];
type[0] = fgetc(source);
type[1] = fgetc(source);
type[2] = '\0';
// TEMPORARY: factor out A- and F-line exceptions.
if((next_opcode&0xf000) == 0xa000) continue;
if((next_opcode&0xf000) == 0xf000) continue;
if(!strcmp(type, "VA")) {
// Test for validity.
if(storage.instructions[next_opcode].micro_operations == std::numeric_limits<uint32_t>::max()) {
[false_invalids_ addObject:@(next_opcode)];
}
continue;
}
if(!strcmp(type, "IN")) {
// Test for invalidity.
if(storage.instructions[next_opcode].micro_operations != std::numeric_limits<uint32_t>::max()) {
[false_valids_ addObject:@(next_opcode)];
}
continue;
}
assert(false);
}
fclose(source);
}
NSSet<NSNumber *> *false_valids() const {
return false_valids_;
}
NSSet<NSNumber *> *false_invalids() const {
return false_invalids_;
}
private:
NSMutableSet<NSNumber *> *false_invalids_;
NSMutableSet<NSNumber *> *false_valids_;
};
@interface NSSet (CSHexDump)
- (NSString *)hexDump;
@end
@implementation NSSet (CSHexDump)
- (NSString *)hexDump {
NSMutableArray<NSString *> *components = [NSMutableArray array];
for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
[components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
}
return [components componentsJoinedByString:@" "];
}
@end
//@interface NSSet (CSHexDump)
//
//- (NSString *)hexDump;
//
//@end
//
//@implementation NSSet (CSHexDump)
//
//- (NSString *)hexDump {
// NSMutableArray<NSString *> *components = [NSMutableArray array];
//
// for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
// [components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
// }
//
// return [components componentsJoinedByString:@" "];
//}
//
//@end
@interface M68000Tests : XCTestCase
@ -123,39 +54,38 @@ class CPU::MC68000::ProcessorStorageTests {
_machine->set_program({
0xc100 // ABCD D0, D0
});
auto state = _machine->get_processor_state();
const uint8_t bcd_d = ((d / 10) * 16) + (d % 10);
state.data[0] = bcd_d;
_machine->set_processor_state(state);
_machine->set_registers([=](auto &registers){
registers.data[0] = bcd_d;
});
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
const auto state = _machine->get_processor_state();
const uint8_t double_d = (d * 2) % 100;
const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10);
XCTAssert(state.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.data[0], bcd_double_d);
XCTAssert(state.registers.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.registers.data[0], bcd_double_d);
}
}
- (void)testDivideByZero {
_machine->set_program({
0x7000, // MOVE #0, D0; location 0x400
0x3200, // MOVE D0, D1; location 0x402
0x7000, // MOVE #0, D0; location 0x1000
0x3200, // MOVE D0, D1; location 0x1002
0x82C0, // DIVU; location 0x404
0x82C0, // DIVU; location 0x1004
/* Next instruction would be at 0x406 */
});
_machine->set_initial_stack_pointer(0x1000);
/* Next instruction would be at 0x1006 */
}, 0x1000);
_machine->run_for_instructions(4);
const auto state = _machine->get_processor_state();
XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
XCTAssert(state.registers.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
const uint16_t *const stack_top = _machine->ram_at(state.supervisor_stack_pointer);
XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
// const uint16_t *const stack_top = _machine->ram_at(state.registers.supervisor_stack_pointer);
// XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
// TODO: determine whether above is a valid test; if so then it's suspicious that the exception
// is raised so as to avoid a final prefetch.
}
- (void)testMOVE {
@ -177,28 +107,28 @@ class CPU::MC68000::ProcessorStorageTests {
// Perform MOVE #fb2e, D0
_machine->run_for_instructions(1);
auto state = _machine->get_processor_state();
XCTAssert(state.data[0] == 0xfb2e);
XCTAssert(state.registers.data[0] == 0xfb2e);
// Perform MOVE D0, D1
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.data[1] == 0xfb2e);
XCTAssert(state.registers.data[1] == 0xfb2e);
// Perform MOVEA D0, A0
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.address[0]);
XCTAssert(state.registers.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.registers.address[0]);
// Perform MOVEA.w (0x1000), A1
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.address[1]);
XCTAssert(state.registers.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.registers.address[1]);
// Perform MOVE #$400, A4; MOVE.l (A4), D2
_machine->run_for_instructions(2);
state = _machine->get_processor_state();
XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]);
XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]);
XCTAssert(state.registers.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.registers.address[4]);
XCTAssert(state.registers.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.registers.data[2]);
}
- (void)testVectoredInterrupt {
@ -223,7 +153,7 @@ class CPU::MC68000::ProcessorStorageTests {
_machine->run_for_instructions(1);
const auto state = _machine->processor().get_state();
XCTAssertEqual(state.program_counter, 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore
XCTAssertEqual(state.registers.program_counter, 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore
// by the wonders of prefetch the program counter is now at 1008.
}
@ -287,7 +217,7 @@ class CPU::MC68000::ProcessorStorageTests {
XCTAssertEqual(_machine->get_cycle_count(), 6 + 2);
}
- (void)testOpcodeCoverage {
/*- (void)testOpcodeCoverage {
// Perform an audit of implemented instructions.
CPU::MC68000::ProcessorStorageTests storage_tests(
_machine->processor(),
@ -479,6 +409,6 @@ class CPU::MC68000::ProcessorStorageTests {
XCTAssert(!trimmedInvalids.count, "%@ opcodes should be valid but aren't: %@", @(trimmedInvalids.count), trimmedInvalids.hexDump);
// XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump);
}
}*/
@end

View File

@ -11,9 +11,9 @@
#include <zlib.h>
#include "68000.hpp"
#include "68000Mk2.hpp"
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
class ComparativeBusHandler: public CPU::MC68000Mk2::BusHandler {
public:
ComparativeBusHandler(const char *trace_name) {
trace = gzopen(trace_name, "rt");
@ -30,14 +30,14 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler {
++line_count;
// Generate state locally.
const auto state = get_state();
const auto state = get_state().registers;
char local_state[300];
sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
address,
state.status,
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
(state.status & 0x2000) ? state.supervisor_stack_pointer : state.user_stack_pointer
state.stack_pointer()
);
// Check that the two coincide.
@ -49,7 +49,7 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler {
}
}
virtual CPU::MC68000::ProcessorState get_state() = 0;
virtual CPU::MC68000Mk2::State get_state() = 0;
private:
int line_count = 0;

View File

@ -13,7 +13,7 @@
//#define LOG_TRACE
#include "68000.hpp"
#include "68000Mk2.hpp"
#include "Comparative68000.hpp"
#include "CSROMFetcher.hpp"
@ -32,11 +32,11 @@ class EmuTOS: public ComparativeBusHandler {
m68000_.run_for(cycles);
}
CPU::MC68000::ProcessorState get_state() final {
CPU::MC68000Mk2::State get_state() final {
return m68000_.get_state();
}
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
@ -56,7 +56,7 @@ class EmuTOS: public ComparativeBusHandler {
word_address %= ram_.size();
}
using Microcycle = CPU::MC68000::Microcycle;
using Microcycle = CPU::MC68000Mk2::Microcycle;
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
if(is_peripheral) {
@ -72,16 +72,16 @@ class EmuTOS: public ComparativeBusHandler {
default: break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = is_peripheral ? peripheral_result : base[word_address];
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
break;
case Microcycle::SelectWord:
base[word_address] = cycle.value->full;
base[word_address] = cycle.value->w;
break;
case Microcycle::SelectByte:
base[word_address] = (cycle.value->halves.low << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
break;
}
}
@ -90,7 +90,7 @@ class EmuTOS: public ComparativeBusHandler {
}
private:
CPU::MC68000::Processor<EmuTOS, true, true> m68000_;
CPU::MC68000Mk2::Processor<EmuTOS, true, true> m68000_;
std::vector<uint16_t> emuTOS_;
std::array<uint16_t, 256*1024> ram_;

View File

@ -35,11 +35,11 @@ class QL: public ComparativeBusHandler {
m68000_.run_for(cycles);
}
CPU::MC68000::ProcessorState get_state() final {
CPU::MC68000Mk2::State get_state() final {
return m68000_.get_state();
}
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
const uint32_t address = cycle.word_address();
uint32_t word_address = address;
@ -56,7 +56,7 @@ class QL: public ComparativeBusHandler {
word_address %= ram_.size();
}
using Microcycle = CPU::MC68000::Microcycle;
using Microcycle = CPU::MC68000Mk2::Microcycle;
if(cycle.data_select_active()) {
uint16_t peripheral_result = 0xffff;
@ -64,18 +64,18 @@ class QL: public ComparativeBusHandler {
default: break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = is_peripheral ? peripheral_result : base[word_address];
cycle.value->w = is_peripheral ? peripheral_result : base[word_address];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
cycle.value->b = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift();
break;
case Microcycle::SelectWord:
assert(!(is_rom && !is_peripheral));
if(!is_peripheral) base[word_address] = cycle.value->full;
if(!is_peripheral) base[word_address] = cycle.value->w;
break;
case Microcycle::SelectByte:
assert(!(is_rom && !is_peripheral));
if(!is_peripheral) base[word_address] = (cycle.value->halves.low << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
if(!is_peripheral) base[word_address] = (cycle.value->b << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask()));
break;
}
}
@ -84,7 +84,7 @@ class QL: public ComparativeBusHandler {
}
private:
CPU::MC68000::Processor<QL, true, true> m68000_;
CPU::MC68000Mk2::Processor<QL, true, false, true> m68000_;
std::vector<uint16_t> rom_;
std::array<uint16_t, 64*1024> ram_;

View File

@ -10,104 +10,98 @@
#define TestRunner68000_h
#include <array>
#include <functional>
#include <vector>
#include "../../../Processors/68000/68000.hpp"
#include "../../../Processors/68000Mk2/68000Mk2.hpp"
using Flag = CPU::MC68000::Flag;
using namespace InstructionSet::M68k;
/*!
Provides a 68000 with 64kb of RAM in its low address space;
/RESET will put the supervisor stack pointer at 0xFFFF and
begin execution at 0x0400.
*/
class RAM68000: public CPU::MC68000::BusHandler {
class RAM68000: public CPU::MC68000Mk2::BusHandler {
public:
RAM68000() : m68000_(*this) {
// Setup the /RESET vector.
ram_[0] = 0;
ram_[1] = 0x206; // Supervisor stack pointer.
ram_[2] = 0;
ram_[3] = 0x1000; // Initial PC.
// Ensure the condition codes start unset.
auto state = get_processor_state();
state.status &= ~Flag::ConditionCodes;
set_processor_state(state);
}
RAM68000() : m68000_(*this) {}
uint32_t initial_pc() const {
return 0x1000;
}
void set_program(const std::vector<uint16_t> &program) {
void set_program(
const std::vector<uint16_t> &program,
uint32_t stack_pointer = 0x206
) {
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));
// Add a NOP suffix, to avoid corrupting flags should the attempt to
// run for a certain number of instructions overrun.
ram_[(0x1000 >> 1) + program.size()] = 0x4e71;
// Ensure the condition codes start unset and set the initial program counter
// and supervisor stack pointer, as well as starting in supervisor mode.
auto registers = m68000_.get_state().registers;
registers.status = 0x2700;
registers.program_counter = initial_pc();
registers.supervisor_stack_pointer = stack_pointer;
m68000_.decode_from_state(registers);
}
void set_initial_stack_pointer(uint32_t sp) {
ram_[0] = sp >> 16;
ram_[1] = sp & 0xffff;
void set_registers(std::function<void(InstructionSet::M68k::RegisterSet &)> func) {
auto state = m68000_.get_state();
func(state.registers);
m68000_.set_state(state);
}
void will_perform(uint32_t, uint16_t) {
--instructions_remaining_;
if(instructions_remaining_ < 0) {
throw StopException();
}
}
void run_for_instructions(int count) {
instructions_remaining_ = count + (has_run_ ? 0 : 1);
finish_reset_if_needed();
duration_ = HalfCycles(0);
instructions_remaining_ = count;
if(!instructions_remaining_) return;
while(instructions_remaining_) {
run_for(HalfCycles(2));
}
try {
while(true) {
run_for(HalfCycles(2000));
}
} catch (const StopException &) {}
}
void run_for(HalfCycles cycles) {
finish_reset_if_needed();
m68000_.run_for(cycles);
}
void finish_reset_if_needed() {
// If the 68000 hasn't run yet, build in the necessary
// cycles to finish the reset program, and set the stored state.
if(!has_run_) {
has_run_ = true;
m68000_.run_for(HalfCycles(76));
duration_ -= HalfCycles(76);
}
}
uint16_t *ram_at(uint32_t address) {
return &ram_[(address >> 1) % ram_.size()];
}
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
HalfCycles perform_bus_operation(const CPU::MC68000Mk2::Microcycle &cycle, int) {
const uint32_t word_address = cycle.word_address();
if(instructions_remaining_) duration_ += cycle.length;
duration_ += cycle.length;
using Microcycle = CPU::MC68000::Microcycle;
using Microcycle = CPU::MC68000Mk2::Microcycle;
if(cycle.data_select_active()) {
if(cycle.operation & Microcycle::InterruptAcknowledge) {
cycle.value->halves.low = 10;
cycle.value->b = 10;
} else {
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
default: break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = ram_[word_address % ram_.size()];
cycle.value->w = ram_[word_address % ram_.size()];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = ram_[word_address % ram_.size()] >> cycle.byte_shift();
cycle.value->b = ram_[word_address % ram_.size()] >> cycle.byte_shift();
break;
case Microcycle::SelectWord:
ram_[word_address % ram_.size()] = cycle.value->full;
ram_[word_address % ram_.size()] = cycle.value->w;
break;
case Microcycle::SelectByte:
ram_[word_address % ram_.size()] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(cycle.value->b << cycle.byte_shift()) |
(ram_[word_address % ram_.size()] & cycle.untouched_byte_mask())
);
break;
@ -118,15 +112,11 @@ class RAM68000: public CPU::MC68000::BusHandler {
return HalfCycles(0);
}
CPU::MC68000::Processor<RAM68000, true>::State get_processor_state() {
CPU::MC68000Mk2::State get_processor_state() {
return m68000_.get_state();
}
void set_processor_state(const CPU::MC68000::Processor<RAM68000, true>::State &state) {
m68000_.set_state(state);
}
CPU::MC68000::Processor<RAM68000, true, true> &processor() {
auto &processor() {
return m68000_;
}
@ -139,11 +129,12 @@ class RAM68000: public CPU::MC68000::BusHandler {
}
private:
CPU::MC68000::Processor<RAM68000, true, true> m68000_;
struct StopException {};
CPU::MC68000Mk2::Processor<RAM68000, true, true, true> m68000_;
std::array<uint16_t, 256*1024> ram_{};
int instructions_remaining_;
HalfCycles duration_;
bool has_run_ = false;
};
#endif /* TestRunner68000_h */

View 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 */

File diff suppressed because it is too large Load Diff

View 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]{}; // D0D7 followed by A0A7.
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 */