1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-09 15:39:08 +00:00

Merge pull request #978 from TomHarte/Amiga

Introduces nascent Amiga emulation
This commit is contained in:
Thomas Harte 2021-12-07 04:18:53 -05:00 committed by GitHub
commit 5138216ba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 2830610 additions and 415 deletions

View File

@ -17,6 +17,7 @@ enum class Machine {
AppleIIgs,
Atari2600,
AtariST,
Amiga,
ColecoVision,
Electron,
Enterprise,

View File

@ -0,0 +1,25 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Amiga::Target;
auto *const target = new Target();
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Amiga_StaticAnalyser_hpp
#define Analyser_Static_Amiga_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Amiga {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */

View File

@ -0,0 +1,27 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Amiga_Target_h
#define Analyser_Static_Amiga_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Amiga {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
Target() : Analyser::Static::Target(Machine::Amiga) {}
};
}
}
}
#endif /* Analyser_Static_Amiga_Target_h */

View File

@ -15,6 +15,7 @@
// Analysers
#include "Acorn/StaticAnalyser.hpp"
#include "Amiga/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "AppleIIgs/StaticAnalyser.hpp"
@ -38,6 +39,7 @@
// Disks
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
@ -128,7 +130,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
@ -253,6 +256,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
Append(AmstradCPC);
Append(AppleII);
Append(AppleIIgs);
Append(Amiga);
Append(Atari2600);
Append(AtariST);
Append(Coleco);

View File

@ -11,6 +11,7 @@
#include "ForceInline.hpp"
#include <algorithm>
#include <cstdint>
#include <limits>
@ -138,8 +139,11 @@ template <class T> class WrappedInt {
forceinline constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
/// @returns The underlying int, cast to an integral type of your choosing.
template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
template<typename Type = IntType> forceinline constexpr Type as() const {
const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
return Type(clamped);
}
/// @returns The underlying int, in its native form.
forceinline constexpr IntType as_integral() const { return length_; }
@ -222,6 +226,15 @@ class HalfCycles: public WrappedInt<HalfCycles> {
return result;
}
/*!
Equivalent to @c divide_cycles(Cycles(1)) but faster.
*/
forceinline Cycles divide_cycles() {
const Cycles result(length_ >> 1);
length_ &= 1;
return result;
}
private:
friend WrappedInt;
void fill(Cycles &result) {

View File

@ -0,0 +1,48 @@
//
// DeferredValue.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/08/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef DeferredValue_h
#define DeferredValue_h
/*!
Provides storage for a single deferred value: one with a current value and a certain number
of future values.
*/
template <int DeferredDepth, typename ValueT> class DeferredValue {
private:
static_assert(sizeof(ValueT) <= 4);
constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
constexpr int unit_shift = sizeof(ValueT) * 8;
constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
public:
/// @returns the current value.
ValueT value() const {
return uint8_t(backlog[0]);
}
/// Advances to the next enqueued value.
void advance() {
for(size_t c = 0; c < backlog.size() - 1; c--) {
backlog[c] = (backlog[c] >> unit_shift) | (backlog[c+1] << (32 - unit_shift));
}
backlog[backlog.size() - 1] >>= unit_shift;
}
/// Inserts a new value, replacing whatever is currently at the end of the queue.
void insert(ValueT value) {
backlog[DeferredDepth / elements_per_uint32] =
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
}
};
#endif /* DeferredValue_h */

View File

@ -9,6 +9,7 @@
#ifndef JustInTime_h
#define JustInTime_h
#include "ClockReceiver.hpp"
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"

View File

@ -10,8 +10,6 @@
#define _522_hpp
#include <cstdint>
#include <typeinfo>
#include <cstdio>
#include "Implementation/6522Storage.hpp"
@ -37,22 +35,24 @@ enum Line {
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
uint8_t get_port_input([[maybe_unused]] Port port) {
return 0xff;
}
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status([[maybe_unused]] bool status) {}
void set_interrupt_status([[maybe_unused]] bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for([[maybe_unused]] HalfCycles duration) {}
void run_for([[maybe_unused]] HalfCycles duration) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
void flush() {}
};
/*!
@ -88,9 +88,9 @@ class IRQDelegatePortHandler: public PortHandler {
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522: public MOS6522Storage {
template <class BusHandlerT> class MOS6522: public MOS6522Storage {
public:
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
@ -100,7 +100,7 @@ template <class T> class MOS6522: public MOS6522Storage {
uint8_t read(int address);
/*! @returns the bus handler. */
T &bus_handler();
BusHandlerT &bus_handler();
/// Sets the input value of line @c line on port @c port.
void set_control_line_input(Port port, Line line, bool value);
@ -123,7 +123,7 @@ template <class T> class MOS6522: public MOS6522Storage {
void shift_in();
void shift_out();
T &bus_handler_;
BusHandlerT &bus_handler_;
HalfCycles time_since_bus_handler_call_;
void access(int address);

94
Components/6526/6526.hpp Normal file
View File

@ -0,0 +1,94 @@
//
// 6526.h
// Clock Signal
//
// Created by Thomas Harte on 18/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef _526_h
#define _526_h
#include <cstdint>
#include "Implementation/6526Storage.hpp"
#include "../Serial/Line.hpp"
namespace MOS {
namespace MOS6526 {
enum Port {
A = 0,
B = 1
};
struct PortHandler {
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input([[maybe_unused]] Port port) {
return 0xff;
}
/// Sets the current output value of @c port; any bits marked as input will be supplied as 1s.
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value) {}
};
enum class Personality {
// The 6526, used in machines such as the C64, has a BCD time-of-day clock.
P6526,
// The 8250, used in the Amiga, provides a binary time-of-day clock.
P8250,
};
template <typename PortHandlerT, Personality personality> class MOS6526:
private MOS6526Storage,
private Serial::Line<true>::ReadDelegate
{
public:
MOS6526(PortHandlerT &port_handler) noexcept : port_handler_(port_handler) {
serial_input.set_read_delegate(this);
}
MOS6526(const MOS6526 &) = delete;
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void write(int address, uint8_t value);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t read(int address);
/// Pulses Phi2 to advance by the specified number of half cycles.
void run_for(const HalfCycles half_cycles);
/// Pulses the TOD input the specified number of times.
void advance_tod(int count);
/// @returns @c true if the interrupt output is active, @c false otherwise.
bool get_interrupt_line();
/// Sets the current state of the CNT input.
void set_cnt_input(bool active);
/// Provides both the serial input bit and an additional source of CNT.
Serial::Line<true> serial_input;
/// Sets the current state of the FLG input.
void set_flag_input(bool low);
private:
PortHandlerT &port_handler_;
TODStorage<personality == Personality::P8250> tod_;
template <int port> void set_port_output();
template <int port> uint8_t get_port_input();
void update_interrupts();
void posit_interrupt(uint8_t mask);
void advance_counters(int);
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
};
}
}
#include "Implementation/6526Implementation.hpp"
#endif /* _526_h */

View File

@ -0,0 +1,244 @@
//
// 6526Implementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef _526Implementation_h
#define _526Implementation_h
#include <cassert>
#include <cstdio>
namespace MOS {
namespace MOS6526 {
enum Interrupts: uint8_t {
TimerA = 1 << 0,
TimerB = 1 << 1,
Alarm = 1 << 2,
SerialPort = 1 << 3,
Flag = 1 << 4,
};
template <typename BusHandlerT, Personality personality>
template <int port> void MOS6526<BusHandlerT, personality>::set_port_output() {
const uint8_t output = output_[port] | (~data_direction_[port]);
port_handler_.set_port_output(Port(port), output);
}
template <typename BusHandlerT, Personality personality>
template <int port> uint8_t MOS6526<BusHandlerT, personality>::get_port_input() {
// Avoid bothering the port handler if there's no input active.
const uint8_t input_mask = ~data_direction_[port];
const uint8_t input = input_mask ? port_handler_.get_port_input(Port(port)) : 0x00;
return (input & input_mask) | (output_[port] & data_direction_[port]);
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::posit_interrupt(uint8_t mask) {
if(!mask) {
return;
}
interrupt_state_ |= mask;
update_interrupts();
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::update_interrupts() {
if(interrupt_state_ & interrupt_control_) {
pending_ |= InterruptInOne;
}
}
template <typename BusHandlerT, Personality personality>
bool MOS6526<BusHandlerT, personality>::get_interrupt_line() {
return interrupt_state_ & 0x80;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::set_cnt_input(bool active) {
cnt_edge_ = active && !cnt_state_;
cnt_state_ = active;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
if(low && !flag_state_) {
posit_interrupt(Interrupts::Flag);
}
flag_state_ = low;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::write(int address, uint8_t value) {
address &= 0xf;
switch(address) {
// Port output.
case 0:
output_[0] = value;
set_port_output<0>();
break;
case 1:
output_[1] = value;
set_port_output<1>();
break;
// Port direction.
case 2:
data_direction_[0] = value;
set_port_output<0>();
break;
case 3:
data_direction_[1] = value;
set_port_output<1>();
break;
// Counters; writes set the reload values.
case 4: counter_[0].template set_reload<0, personality == Personality::P8250>(value); break;
case 5: counter_[0].template set_reload<8, personality == Personality::P8250>(value); break;
case 6: counter_[1].template set_reload<0, personality == Personality::P8250>(value); break;
case 7: counter_[1].template set_reload<8, personality == Personality::P8250>(value); break;
// Time-of-day clock.
case 8: tod_.template write<0>(value); break;
case 9: tod_.template write<1>(value); break;
case 10: tod_.template write<2>(value); break;
case 11: tod_.template write<3>(value); break;
// Interrupt control.
case 13: {
if(value & 0x80) {
interrupt_control_ |= value & 0x7f;
} else {
interrupt_control_ &= ~(value & 0x7f);
}
update_interrupts();
} break;
// Control. Posted to both the counters and the clock as it affects both.
case 14:
counter_[0].template set_control<false>(value);
tod_.template set_control<false>(value);
if(shifter_is_output_ != bool(value & 0x40)) {
shifter_is_output_ = value & 0x40;
shift_bits_ = 0;
}
break;
case 15:
counter_[1].template set_control<true>(value);
tod_.template set_control<true>(value);
break;
// Shift control.
case 12:
printf("TODO: write to shift register\n");
break;
default:
printf("Unhandled 6526 write: %02x to %d\n", value, address);
assert(false);
break;
}
}
template <typename BusHandlerT, Personality personality>
uint8_t MOS6526<BusHandlerT, personality>::read(int address) {
address &= 0xf;
switch(address) {
case 0: return get_port_input<0>();
case 1: return get_port_input<1>();
case 2: case 3:
return data_direction_[address - 2];
// Counters; reads obtain the current values.
case 4: return uint8_t(counter_[0].value >> 0);
case 5: return uint8_t(counter_[0].value >> 8);
case 6: return uint8_t(counter_[1].value >> 0);
case 7: return uint8_t(counter_[1].value >> 8);
// Interrupt state.
case 13: {
const uint8_t result = interrupt_state_;
interrupt_state_ = 0;
pending_ &= ~(InterruptNow | InterruptInOne);
update_interrupts();
return result;
} break;
case 14: case 15:
return counter_[address - 14].control;
// Time-of-day clock.
case 8: return tod_.template read<0>();
case 9: return tod_.template read<1>();
case 10: return tod_.template read<2>();
case 11: return tod_.template read<3>();
// Shift register.
case 12: return shift_data_;
default:
printf("Unhandled 6526 read from %d\n", address);
assert(false);
break;
}
return 0xff;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::run_for(const HalfCycles half_cycles) {
half_divider_ += half_cycles;
int sub = half_divider_.divide_cycles().template as<int>();
while(sub--) {
pending_ <<= 1;
if(pending_ & InterruptNow) {
interrupt_state_ |= 0x80;
}
pending_ &= PendingClearMask;
// TODO: use CNT potentially to clock timer A, elimiante conditional above.
const bool timer1_did_reload = counter_[0].template advance<false>(false, cnt_state_, cnt_edge_);
const bool timer1_carry = timer1_did_reload && (counter_[1].control & 0x60) == 0x40;
const bool timer2_did_reload = counter_[1].template advance<true>(timer1_carry, cnt_state_, cnt_edge_);
posit_interrupt((timer1_did_reload ? Interrupts::TimerA : 0x00) | (timer2_did_reload ? Interrupts::TimerB : 0x00));
cnt_edge_ = false;
}
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
if(!count) return;
if(tod_.advance(count)) {
posit_interrupt(Interrupts::Alarm);
}
}
template <typename BusHandlerT, Personality personality>
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, int bit) {
// TODO: post CNT change; might affect timer.
if(!shifter_is_output_) {
shift_register_ = uint8_t((shift_register_ << 1) | bit);
++shift_bits_;
if(shift_bits_ == 8) {
shift_bits_ = 0;
shift_data_ = shift_register_;
posit_interrupt(Interrupts::SerialPort);
}
}
return true;
}
}
}
#endif /* _526Implementation_h */

View File

@ -0,0 +1,339 @@
//
// 6526Storage.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef _526Storage_h
#define _526Storage_h
#include <array>
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
namespace MOS6526 {
class TODBase {
public:
template <bool is_timer2> void set_control(uint8_t value) {
if constexpr (is_timer2) {
write_alarm = value & 0x80;
} else {
is_50Hz = value & 0x80;
}
}
protected:
bool write_alarm = false, is_50Hz = false;
};
template <bool is_8250> class TODStorage {};
template <> class TODStorage<false>: public TODBase {
private:
bool increment_ = true, latched_ = false;
int divider_ = 0;
std::array<uint8_t, 4> value_;
std::array<uint8_t, 4> latch_;
std::array<uint8_t, 4> alarm_;
static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f};
void bcd_increment(uint8_t &value) {
++value;
if((value&0x0f) > 0x09) value += 0x06;
}
public:
template <int byte> void write(uint8_t v) {
if(write_alarm) {
alarm_[byte] = v & masks[byte];
} else {
value_[byte] = v & masks[byte];
if constexpr (byte == 0) {
increment_ = true;
}
if constexpr (byte == 3) {
increment_ = false;
}
}
}
template <int byte> uint8_t read() {
if(latched_) {
const uint8_t result = latch_[byte];
if constexpr (byte == 0) {
latched_ = false;
}
return result;
}
if constexpr (byte == 3) {
latched_ = true;
latch_ = value_;
}
return value_[byte];
}
bool advance(int count) {
if(!increment_) {
return false;
}
while(count--) {
// Increment the pre-10ths divider.
++divider_;
if(divider_ < 5) continue;
if(divider_ < 6 && !is_50Hz) continue;
divider_ = 0;
// Increments 10ths of a second. One BCD digit.
++value_[0];
if(value_[0] < 10) {
continue;
}
// Increment seconds. Actual BCD needed from here onwards.
bcd_increment(value_[1]);
if(value_[1] != 60) {
continue;
}
value_[1] = 0;
// Increment minutes.
bcd_increment(value_[2]);
if(value_[2] != 60) {
continue;
}
value_[2] = 0;
// TODO: increment hours, keeping AM/PM separate?
}
return false; // TODO: test against alarm.
}
};
template <> class TODStorage<true>: public TODBase {
private:
uint32_t increment_mask_ = uint32_t(~0);
uint32_t latch_ = 0;
uint32_t value_ = 0;
uint32_t alarm_ = 0xff'ffff;
public:
template <int byte> void write(uint8_t v) {
if constexpr (byte == 3) {
return;
}
constexpr int shift = byte << 3;
// Write to either the alarm or the current value as directed;
// writing to any part of the current value other than the LSB
// pauses incrementing until the LSB is written.
const uint32_t mask = uint32_t(~(0xff << shift));
if(write_alarm) {
alarm_ = (alarm_ & mask) | uint32_t(v << shift);
} else {
value_ = (value_ & mask) | uint32_t(v << shift);
increment_mask_ = (byte == 0) ? uint32_t(~0) : 0;
}
}
template <int byte> uint8_t read() {
if constexpr (byte == 3) {
return 0xff; // Assumed. Just a guess.
}
constexpr int shift = byte << 3;
if constexpr (byte == 2) {
latch_ = value_ | 0xff00'0000;
}
const uint32_t source = latch_ ? latch_ : value_;
const uint8_t result = uint8_t((source >> shift) & 0xff);
if constexpr (byte == 0) {
latch_ = 0;
}
return result;
}
bool advance(int count) {
// The 8250 uses a simple binary counter to replace the
// 6526's time-of-day clock. So this is easy.
const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff;
const auto increment = uint32_t(count) & increment_mask_;
value_ = (value_ + increment) & 0xff'ffff;
return distance_to_alarm <= increment;
}
};
struct MOS6526Storage {
bool cnt_state_ = false; // Inactive by default.
bool cnt_edge_ = false;
bool flag_state_ = false;
HalfCycles half_divider_;
uint8_t output_[2] = {0, 0};
uint8_t data_direction_[2] = {0, 0};
uint8_t interrupt_control_ = 0;
uint8_t interrupt_state_ = 0;
uint8_t shift_data_ = 0;
uint8_t shift_register_ = 0;
int shift_bits_ = 0;
bool shifter_is_output_ = false;
struct Counter {
uint16_t reload = 0;
uint16_t value = 0;
uint8_t control = 0;
template <int shift, bool is_8250> void set_reload(uint8_t v) {
reload = (reload & (0xff00 >> shift)) | uint16_t(v << shift);
if constexpr (shift == 8) {
// This seems to be a special 8250 feature per the Amiga
// Hardware Reference Manual; cf. Appendix F.
if(is_8250) {
control |= 1;
pending |= ReloadInOne;
} else {
if(!(control&1)) {
pending |= ReloadInOne;
}
}
}
// If this write has hit during a reload cycle, reload.
if(pending & ReloadNow) {
value = reload;
}
}
template <bool is_counter_2> void set_control(uint8_t v) {
control = v;
if(v&2) {
printf("UNIMPLEMENTED: PB strobe\n");
}
}
template <bool is_counter_2> bool advance(bool chained_input, bool cnt_state, bool cnt_edge) {
// TODO: remove most of the conditionals here in favour of bit shuffling.
pending = (pending & PendingClearMask) << 1;
//
// Apply feeder states inputs: anything that
// will take effect in the future.
//
// Schedule a force reload if requested.
if(control & 0x10) {
pending |= ReloadInOne;
control &= ~0x10;
}
// Keep a history of the one-shot bit.
if(control & 0x08) {
pending |= OneShotInOne;
}
// Determine whether an input clock is applicable.
if constexpr(is_counter_2) {
switch(control&0x60) {
case 0x00: // Count Phi2 pulses.
pending |= TestInputNow;
break;
case 0x20: // Count negative CNTs, with an extra cycle of delay.
pending |= cnt_edge ? TestInputInOne : 0;
break;
case 0x40: // Count timer A reloads.
pending |= chained_input ? TestInputNow : 0;
break;
case 0x60: // Count timer A transitions when CNT is low.
pending |= chained_input && cnt_state ? TestInputNow : 0;
break;
}
} else {
if(!(control&0x20)) {
pending |= TestInputNow;
} else if (cnt_edge) {
pending |= TestInputInOne;
}
}
if(pending&TestInputNow && control&1) {
pending |= ApplyClockInTwo;
}
//
// Perform a timer tick and decide whether a reload is prompted.
//
if(pending & ApplyClockNow) {
--value;
}
const bool should_reload = !value && (pending & ApplyClockInOne);
// Schedule a reload if so ordered.
if(should_reload) {
pending |= ReloadNow; // Combine this decision with a deferred
// input from the force-reoad test above.
// If this was one-shot, stop.
if(pending&(OneShotInOne | OneShotNow)) {
control &= ~1;
pending &= ~(ApplyClockInOne|ApplyClockInTwo); // Cancel scheduled ticks.
}
}
// Reload if scheduled.
if(pending & ReloadNow) {
value = reload;
pending &= ~ApplyClockInOne; // Skip next decrement.
}
return should_reload;
}
private:
int pending = 0;
static constexpr int ReloadInOne = 1 << 0;
static constexpr int ReloadNow = 1 << 1;
static constexpr int OneShotInOne = 1 << 2;
static constexpr int OneShotNow = 1 << 3;
static constexpr int ApplyClockInTwo = 1 << 4;
static constexpr int ApplyClockInOne = 1 << 5;
static constexpr int ApplyClockNow = 1 << 6;
static constexpr int TestInputInOne = 1 << 7;
static constexpr int TestInputNow = 1 << 8;
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
bool active_ = false;
} counter_[2];
static constexpr int InterruptInOne = 1 << 0;
static constexpr int InterruptNow = 1 << 1;
static constexpr int PendingClearMask = ~(InterruptNow);
int pending_ = 0;
};
}
}
#endif /* _526Storage_h */

View File

@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;

View File

@ -18,7 +18,7 @@
namespace Motorola {
namespace ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
@ -77,13 +77,13 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
void reset();
// Input lines.
Serial::Line receive;
Serial::Line clear_to_send;
Serial::Line data_carrier_detect;
Serial::Line<false> receive;
Serial::Line<false> clear_to_send;
Serial::Line<false> data_carrier_detect;
// Output lines.
Serial::Line transmit;
Serial::Line request_to_send;
Serial::Line<false> transmit;
Serial::Line<false> request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
@ -118,7 +118,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
bool serial_line_did_produce_bit(Serial::Line<false> *line, int bit) final;
bool interrupt_line_ = false;
void update_interrupt_line();

View File

@ -22,7 +22,10 @@ namespace {
DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command),
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
drives_{
Storage::Disk::Drive{clock_rate, 300, 1},
Storage::Disk::Drive{clock_rate, 300, 1}
}
{
drives_[0].set_clocking_hint_observer(this);
drives_[1].set_clocking_hint_observer(this);

View File

@ -8,13 +8,18 @@
#include "Line.hpp"
#include <cassert>
#include <limits>
using namespace Serial;
void Line::set_writer_clock_rate(HalfCycles clock_rate) {
template <bool include_clock>
void Line<include_clock>::set_writer_clock_rate(HalfCycles clock_rate) {
clock_rate_ = clock_rate;
}
void Line::advance_writer(HalfCycles cycles) {
template <bool include_clock>
void Line<include_clock>::advance_writer(HalfCycles cycles) {
if(cycles == HalfCycles(0)) return;
const auto integral_cycles = cycles.as_integral();
@ -25,7 +30,9 @@ void Line::advance_writer(HalfCycles cycles) {
transmission_extra_ -= integral_cycles;
if(transmission_extra_ <= 0) {
transmission_extra_ = 0;
update_delegate(level_);
if constexpr (!include_clock) {
update_delegate(level_);
}
}
}
} else {
@ -38,12 +45,17 @@ void Line::advance_writer(HalfCycles cycles) {
auto iterator = events_.begin() + 1;
while(iterator != events_.end() && iterator->type != Event::Delay) {
level_ = iterator->type == Event::SetHigh;
if constexpr(include_clock) {
update_delegate(level_);
}
++iterator;
}
events_.erase(events_.begin(), iterator);
if(old_level != level_) {
update_delegate(old_level);
if constexpr (!include_clock) {
if(old_level != level_) {
update_delegate(old_level);
}
}
// Book enough extra time for the read delegate to be posted
@ -60,7 +72,8 @@ void Line::advance_writer(HalfCycles cycles) {
}
}
void Line::write(bool level) {
template <bool include_clock>
void Line<include_clock>::write(bool level) {
if(!events_.empty()) {
events_.emplace_back();
events_.back().type = level ? Event::SetHigh : Event::SetLow;
@ -70,7 +83,8 @@ void Line::write(bool level) {
}
}
void Line::write(HalfCycles cycles, int count, int levels) {
template <bool include_clock>
template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(HalfCycles cycles, int count, IntT levels) {
remaining_delays_ += count * cycles.as_integral();
auto event = events_.size();
@ -78,63 +92,122 @@ void Line::write(HalfCycles cycles, int count, int levels) {
while(count--) {
events_[event].type = Event::Delay;
events_[event].delay = int(cycles.as_integral());
events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
levels >>= 1;
IntT bit;
if constexpr (lsb_first) {
bit = levels & 1;
levels >>= 1;
} else {
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
bit = levels & top_bit;
levels <<= 1;
}
events_[event+1].type = bit ? Event::SetHigh : Event::SetLow;
event += 2;
}
}
void Line::reset_writing() {
template <bool include_clock>
void Line<include_clock>::write(HalfCycles cycles, int count, int levels) {
write_internal<true, int>(cycles, count, levels);
}
template <bool include_clock>
template <bool lsb_first, typename IntT> void Line<include_clock>::write(HalfCycles cycles, IntT value) {
write_internal<lsb_first, IntT>(cycles, 8 * sizeof(IntT), value);
}
template <bool include_clock>
void Line<include_clock>::reset_writing() {
remaining_delays_ = 0;
events_.clear();
}
bool Line::read() const {
template <bool include_clock>
bool Line<include_clock>::read() const {
return level_;
}
void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
template <bool include_clock>
void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
read_delegate_ = delegate;
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
if constexpr (!include_clock) {
assert(bit_length > Storage::Time(0));
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
}
}
void Line::update_delegate(bool level) {
template <bool include_clock>
void Line<include_clock>::update_delegate(bool level) {
// Exit early if there's no delegate, or if the delegate is waiting for
// zero and this isn't zero.
if(!read_delegate_) return;
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
if constexpr (!include_clock) {
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
}
time_left_in_bit_ -= time_left;
} else {
read_delegate_->serial_line_did_produce_bit(this, level);
}
time_left_in_bit_ -= time_left;
}
Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
template <bool include_clock>
Cycles::IntType Line<include_clock>::minimum_write_cycles_for_read_delegate_bit() {
if(!read_delegate_) return 0;
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).get<int>();
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).template get<int>();
}
//
// Explicitly instantiate the meaningful instances of templates above;
// this class uses templates primarily to keep the interface compact and
// to take advantage of constexpr functionality selection, not so as
// to be generic.
//
template class Serial::Line<true>;
template class Serial::Line<false>;
template void Line<true>::write<true, uint8_t>(HalfCycles, uint8_t);
template void Line<true>::write<false, uint8_t>(HalfCycles, uint8_t);
template void Line<true>::write<true, uint16_t>(HalfCycles, uint16_t);
template void Line<true>::write<false, uint16_t>(HalfCycles, uint16_t);
template void Line<true>::write<true, uint32_t>(HalfCycles, uint32_t);
template void Line<true>::write<false, uint32_t>(HalfCycles, uint32_t);
template void Line<true>::write<true, uint64_t>(HalfCycles, uint64_t);
template void Line<true>::write<false, uint64_t>(HalfCycles, uint64_t);
template void Line<false>::write<true, uint8_t>(HalfCycles, uint8_t);
template void Line<false>::write<false, uint8_t>(HalfCycles, uint8_t);
template void Line<false>::write<true, uint16_t>(HalfCycles, uint16_t);
template void Line<false>::write<false, uint16_t>(HalfCycles, uint16_t);
template void Line<false>::write<true, uint32_t>(HalfCycles, uint32_t);
template void Line<false>::write<false, uint32_t>(HalfCycles, uint32_t);
template void Line<false>::write<true, uint64_t>(HalfCycles, uint64_t);
template void Line<false>::write<false, uint64_t>(HalfCycles, uint64_t);

View File

@ -17,25 +17,42 @@
namespace Serial {
/*!
@c Line connects a single reader and a single writer, allowing timestamped events to be
published and consumed, potentially with a clock conversion in between. It allows line
levels to be written and read in larger collections.
Models one of two connections, either:
It is assumed that the owner of the reader and writer will ensure that the reader will never
get ahead of the writer. If the writer posts events behind the reader they will simply be
given instanteous effect.
(i) a plain single-line serial; or
(ii) a two-line data + clock.
In both cases connects a single reader to a single writer.
When operating as a single-line serial connection:
Provides a mechanism for the writer to enqueue levels arbitrarily far
ahead of the current time, which are played back only as the
write queue advances. Permits the reader and writer to work at
different clock rates, and provides a virtual delegate protocol with
start bit detection.
Can alternatively be used by reader and/or writer only in immediate
mode, getting or setting the current level now, without the actor on
the other end having to have made the same decision.
When operating as a two-line connection:
Implies a clock over enqueued data and provides the reader with
all enqueued bits at appropriate times.
*/
class Line {
template <bool include_clock> class Line {
public:
void set_writer_clock_rate(HalfCycles clock_rate);
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Sets the line to @c level.
/// Sets the line to @c level instantaneously.
void write(bool level);
/// @returns The instantaneous level of this line.
bool read() const;
/// Sets the denominator for the between levels for any data enqueued
/// via an @c write.
void set_writer_clock_rate(HalfCycles clock_rate);
/// Enqueues @c count level changes, the first occurring immediately
/// after the final event currently posted and each subsequent event
/// occurring @c cycles after the previous. An additional gap of @c cycles
@ -44,6 +61,10 @@ class Line {
/// relative to the writer's clock rate.
void write(HalfCycles cycles, int count, int levels);
/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
/// either in LSB or MSB order as per the @c lsb_first template flag.
template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
/// @returns the number of cycles until currently enqueued write data is exhausted.
forceinline HalfCycles write_data_time_remaining() const {
return HalfCycles(remaining_delays_);
@ -55,25 +76,36 @@ class Line {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
/// @returns The instantaneous level of this line.
bool read() const;
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
};
/*!
Sets a read delegate, which will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
Sets a read delegate.
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Single line serial connection:
The delegate will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Two-line clock + data connection:
The delegate will receive every bit that has been enqueued, spaced as nominated
by the writer. @c bit_length is ignored, as is the return result of
@c ReadDelegate::serial_line_did_produce_bit.
*/
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length = Storage::Time());
private:
struct Event {
@ -98,6 +130,9 @@ class Line {
void update_delegate(bool level);
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
template <bool lsb_first, typename IntT> void
write_internal(HalfCycles, int, IntT);
};
/*!

View File

@ -36,7 +36,10 @@ class Joystick {
// Fire buttons.
Fire,
// Other labelled keys.
Key
Key,
// The maximum value this enum can contain.
Max = Key
};
const Type type;

253
Machines/Amiga/Amiga.cpp Normal file
View File

@ -0,0 +1,253 @@
//
// Amiga.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Amiga.hpp"
#include "../../Activity/Source.hpp"
#include "../MachineTypes.hpp"
#include "../../Processors/68000/68000.hpp"
#include "../../Analyser/Static/Amiga/Target.hpp"
#include "../Utility/MemoryPacker.hpp"
#include "../Utility/MemoryFuzzer.hpp"
//#define NDEBUG
#define LOG_PREFIX "[Amiga] "
#include "../../Outputs/Log.hpp"
#include "Chipset.hpp"
#include "Keyboard.hpp"
#include "MemoryMap.hpp"
namespace {
// NTSC clock rate: 2*3.579545 = 7.15909Mhz.
// PAL clock rate: 7.09379Mhz; 227 cycles/line.
constexpr int PALClockRate = 7'093'790;
//constexpr int NTSCClockRate = 7'159'090;
}
namespace Amiga {
class ConcreteMachine:
public Activity::Source,
public CPU::MC68000::BusHandler,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::MouseMachine,
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
chipset_(memory_, PALClockRate)
{
// Temporary: use a hard-coded Kickstart selection.
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
// For now, also hard-code assumption of PAL.
// (Assumption is both here and in the video timing of the Chipset).
set_clock_rate(PALClockRate);
// Insert supplied media.
insert_media(target.media);
}
// MARK: - MediaTarget.
bool insert_media(const Analyser::Static::Media &media) final {
return chipset_.insert(media.disks);
}
// MARK: - MC68000::BusHandler.
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
HalfCycles access_delay;
if(cycle.operation & Microcycle::NewAddress && *cycle.address < 0x20'0000) {
access_delay = chipset_.run_until_cpu_slot().duration;
}
// Compute total length.
const HalfCycles total_length = cycle.length + access_delay;
chipset_.run_for(total_length);
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
// Check for assertion of reset.
if(cycle.operation & Microcycle::Reset) {
memory_.reset();
LOG("Reset; PC is around " << PADHEX(8) << mc68000_.get_state().program_counter);
}
// Autovector interrupts.
if(cycle.operation & Microcycle::InterruptAcknowledge) {
mc68000_.set_is_peripheral_address(true);
return access_delay;
}
// Do nothing if no address is exposed.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return access_delay;
// Grab the target address to pick a memory source.
const uint32_t address = cycle.host_endian_byte_address();
// Set VPA if this is [going to be] a CIA access.
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
if(!memory_.regions[address >> 18].read_write_mask) {
if((cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord))) {
// Check for various potential chip accesses.
// Per the manual:
//
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
//
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
// these might be listed the wrong way around.
//
// Additional assumption: the relevant CIA select lines are connected
// directly to the chip enables.
if((address & 0xe0'0000) == 0xa0'0000) {
const int reg = address >> 8;
const bool select_a = !(address & 0x1000);
const bool select_b = !(address & 0x2000);
if(cycle.operation & Microcycle::Read) {
uint16_t result = 0xffff;
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
cycle.set_value16(result);
} else {
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
}
// LOG("CIA " << (((address >> 12) & 3)^3) << " " << (cycle.operation & Microcycle::Read ? "read " : "write ") << std::dec << (reg & 0xf) << " of " << PADHEX(4) << +cycle.value16());
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
chipset_.perform(cycle);
} else {
// This'll do for open bus, for now.
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(0xffff);
}
// Don't log for the region that is definitely just ROM this machine doesn't have.
if(address < 0xf0'0000) {
LOG("Unmapped " << (cycle.operation & Microcycle::Read ? "read from " : "write to ") << PADHEX(6) << ((*cycle.address)&0xffffff) << " of " << cycle.value16());
}
}
}
} else {
// A regular memory access.
cycle.apply(
&memory_.regions[address >> 18].contents[address],
memory_.regions[address >> 18].read_write_mask
);
}
return access_delay;
}
void flush() {
chipset_.flush();
}
private:
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
// MARK: - Memory map.
MemoryMap memory_;
// MARK: - Chipset.
Chipset chipset_;
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
chipset_.set_activity_observer(observer);
}
// MARK: - MachineTypes::AudioProducer.
Outputs::Speaker::Speaker *get_speaker() final {
return chipset_.get_speaker();
}
// MARK: - MachineTypes::ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
chipset_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return chipset_.get_scaled_scan_status();
}
// MARK: - MachineTypes::TimedMachine.
void run_for(const Cycles cycles) {
mc68000_.run_for(cycles);
}
// MARK: - MachineTypes::MouseMachine.
Inputs::Mouse &get_mouse() final {
return chipset_.get_mouse();;
}
// MARK: - MachineTypes::JoystickMachine.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return chipset_.get_joysticks();
}
// MARK: - Keyboard.
Amiga::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) {
chipset_.get_keyboard().set_key_state(key, is_pressed);
}
void clear_all_keys() {
chipset_.get_keyboard().clear_all_keys();
}
};
}
using namespace Amiga;
Machine *Machine::Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Amiga::Target;
const Target *const amiga_target = dynamic_cast<const Target *>(target);
return new Amiga::ConcreteMachine(*amiga_target, rom_fetcher);
}
Machine::~Machine() {}

27
Machines/Amiga/Amiga.hpp Normal file
View File

@ -0,0 +1,27 @@
//
// Amiga.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Amiga_hpp
#define Amiga_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
namespace Amiga {
class Machine {
public:
virtual ~Machine();
/// Creates and returns an Amiga.
static Machine *Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}
#endif /* Amiga_hpp */

678
Machines/Amiga/Audio.cpp Normal file
View File

@ -0,0 +1,678 @@
//
// Audio.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Audio.hpp"
#include "Flags.hpp"
#define LOG_PREFIX "[Audio] "
#include "../../Outputs/Log.hpp"
#include <cassert>
#include <tuple>
using namespace Amiga;
Audio::Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate) :
DMADevice<4>(chipset, ram, word_size) {
// Mark all buffers as available.
for(auto &flag: buffer_available_) {
flag.store(true, std::memory_order::memory_order_relaxed);
}
speaker_.set_input_rate(output_rate);
speaker_.set_high_frequency_cutoff(7000.0f);
}
// MARK: - Exposed setters.
void Audio::set_length(int channel, uint16_t length) {
assert(channel >= 0 && channel < 4);
channels_[channel].length = length;
}
void Audio::set_period(int channel, uint16_t period) {
assert(channel >= 0 && channel < 4);
channels_[channel].period = period;
}
void Audio::set_volume(int channel, uint16_t volume) {
assert(channel >= 0 && channel < 4);
channels_[channel].volume = (volume & 0x40) ? 64 : (volume & 0x3f);
}
template <bool is_external> void Audio::set_data(int channel, uint16_t data) {
assert(channel >= 0 && channel < 4);
channels_[channel].wants_data = false;
channels_[channel].data = data;
// TODO: "the [PWM] counter is reset when ... AUDxDAT is written", but
// does that just mean written by the CPU, or does it include DMA?
// My guess is the former. But TODO.
if constexpr (is_external) {
channels_[channel].reset_output_phase();
}
}
template void Audio::set_data<false>(int, uint16_t);
template void Audio::set_data<true>(int, uint16_t);
void Audio::set_channel_enables(uint16_t enables) {
channels_[0].dma_enabled = enables & 1;
channels_[1].dma_enabled = enables & 2;
channels_[2].dma_enabled = enables & 4;
channels_[3].dma_enabled = enables & 8;
}
void Audio::set_modulation_flags(uint16_t flags) {
channels_[3].attach_period = flags & 0x80;
channels_[2].attach_period = flags & 0x40;
channels_[1].attach_period = flags & 0x20;
channels_[0].attach_period = flags & 0x10;
channels_[3].attach_volume = flags & 0x08;
channels_[2].attach_volume = flags & 0x04;
channels_[1].attach_volume = flags & 0x02;
channels_[0].attach_volume = flags & 0x01;
}
void Audio::set_interrupt_requests(uint16_t requests) {
channels_[0].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel0);
channels_[1].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel1);
channels_[2].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel2);
channels_[3].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel3);
}
// MARK: - DMA and mixing.
bool Audio::advance_dma(int channel) {
if(!channels_[channel].wants_data) {
return false;
}
if(channels_[channel].should_reload_address) {
channels_[channel].data_address = pointer_[size_t(channel)];
channels_[channel].should_reload_address = false;
}
set_data<false>(channel, ram_[channels_[channel].data_address & ram_mask_]);
if(channels_[channel].state != Channel::State::WaitingForDummyDMA) {
++channels_[channel].data_address;
}
return true;
}
void Audio::output() {
constexpr InterruptFlag interrupts[] = {
InterruptFlag::AudioChannel0,
InterruptFlag::AudioChannel1,
InterruptFlag::AudioChannel2,
InterruptFlag::AudioChannel3,
};
Channel *const modulands[] = {
&channels_[1],
&channels_[2],
&channels_[3],
nullptr,
};
for(int c = 0; c < 4; c++) {
if(channels_[c].output(modulands[c])) {
posit_interrupt(interrupts[c]);
}
}
// Spin until the next buffer is available if just entering it for the first time.
// Contention here should be essentially non-existent.
if(!sample_pointer_) {
while(!buffer_available_[buffer_pointer_].load(std::memory_order::memory_order_relaxed));
}
// Left.
static_assert(std::tuple_size<AudioBuffer>::value % 2 == 0);
buffer_[buffer_pointer_][sample_pointer_] = int16_t(
(
channels_[1].output_level * channels_[1].output_enabled +
channels_[2].output_level * channels_[2].output_enabled
) << 7
);
// Right.
buffer_[buffer_pointer_][sample_pointer_ + 1] = int16_t(
(
channels_[0].output_level * channels_[0].output_enabled +
channels_[3].output_level * channels_[3].output_enabled
) << 7
);
sample_pointer_ += 2;
if(sample_pointer_ == buffer_[buffer_pointer_].size()) {
const auto &buffer = buffer_[buffer_pointer_];
auto &flag = buffer_available_[buffer_pointer_];
flag.store(false, std::memory_order::memory_order_release);
queue_.enqueue([this, &buffer, &flag] {
speaker_.push(buffer.data(), buffer.size() >> 1);
flag.store(true, std::memory_order::memory_order_relaxed);
});
buffer_pointer_ = (buffer_pointer_ + 1) % BufferCount;
sample_pointer_ = 0;
}
}
// MARK: - Per-channel logic.
/*
Big spiel on the state machine:
Commodore's Hardware Rerefence Manual provides the audio subsystem's state
machine, so I've just tried to reimplement it verbatim. It's depicted
diagrammatically in the original source as a finite state automata, the
below is my attempt to translate that into text.
000 State::Disabled:
-> State::Disabled (000)
if: N/A
action: percntrld
-> State::PlayingHigh (010)
if: AUDDAT, and not AUDxON, and not AUDxIP
action: percntrld, AUDxIR, volcntrld, pbudld1
-> State::WaitingForDummyDMA (001)
if: AUDxON
action: percntrld, AUDxDR, lencntrld, dmasen*
* NOTE: except for this case, dmasen is true only when
LENFIN = 1. Also, AUDxDSR = (AUDxDR and dmasen).
001 State::WaitingForDummyDMA:
-> State::WaitingForDummyDMA (001)
if: N/A
action: None
-> State::Disabled (000)
if: not AUDxON
action: None
-> State::WaitingForDMA (101)
if: AUDxON, and AUDxDAT
action:
1. AUDxIR
2. if not lenfin, then lencount
101 State::WaitingForDMA:
-> State::WaitingForDMA (101)
if: N/A
action: None
-> State:Disabled (000)
if: not AUDxON
action: None
-> State::PlayingHigh (010)
if: AUDxON, and AUDxDAT
action:
1. volcntrld, percntrld, pbufld1
2. if napnav, then AUDxDR
010 State::PlayingHigh
-> State::PlayingHigh (010)
if: N/A
action: percount, and penhi
-> State::PlayingLow (011)
if: perfin
action:
1. if AUDxAP, then pbufld2
2. if AUDxAP and AUDxON, then AUDxDR
3. percntrld
4. if intreq2 and AUDxON and AUDxAP, then AUDxIR
5. if AUDxAP and AUDxON, then AUDxIR
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
8. if lenfin and AUDxON and AUDxDAT, then intreq2
[note that 68 are shared with the Low -> High transition]
011 State::PlayingLow
-> State::PlayingLow (011)
if: N/A
action: percount, and not penhi
-> State::Disabled (000)
if: perfin and not (AUDxON or not AUDxIP)
action: None
-> State::PlayingHigh (010)
if: perfin and (AUDxON or not AUDxIP)
action:
1. pbufld1
2. percntrld
3. if napnav and AUDxON, then AUDxDR
4. if napnav and AUDxON and intreq2, AUDxIR
5. if napnav and not AUDxON, AUDxIR
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
8. if lenfin and AUDxON and AUDxDAT, then intreq2
[note that 6-8 are shared with the High -> Low transition]
Definitions:
AUDxON DMA on "x" indicates channel number (signal from DMACON).
AUDxIP Audio interrupt pending (input to channel from interrupt circuitry).
AUDxIR Audio interrupt request (output from channel to interrupt circuitry).
intreq1 Interrupt request that combines with intreq2 to form AUDxIR.
intreq2 Prepare for interrupt request. Request comes out after the
next 011->010 transition in normal operation.
AUDxDAT Audio data load signal. Loads 16 bits of data to audio channel.
AUDxDR Audio DMA request to Agnus for one word of data.
AUDxDSR Audio DMA request to Agnus to reset pointer to start of block.
dmasen Restart request enable.
percntrld Reload period counter from back-up latch typically written
by processor with AUDxPER (can also be written by attach mode).
percount Count period counter down one latch.
perfin Period counter finished (value = 1).
lencntrld Reload length counter from back-up latch.
lencount Count length counter down one notch.
lenfin Length counter finished (value = 1).
volcntrld Reload volume counter from back-up latch.
pbufld1 Load output buffer from holding latch written to by AUDxDAT.
pbufld2 Like pbufld1, but only during 010->011 with attach period.
AUDxAV Attach volume. Send data to volume latch of next channel
instead of to D->A converter.
AUDxAP Attach period. Send data to period latch of next channel
instead of to the D->A converter.
penhi Enable the high 8 bits of data to go to the D->A converter.
napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach
volume. Condition for normal DMA and interrupt requests.
*/
//
// Non-action fallback transition and setter, plus specialised begin_state declarations.
//
template <Audio::Channel::State end> void Audio::Channel::begin_state(Channel *) {
state = end;
}
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *);
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *);
template <
Audio::Channel::State begin,
Audio::Channel::State end> bool Audio::Channel::transit(Channel *moduland) {
begin_state<end>(moduland);
return false;
}
//
// Audio::Channel::State::Disabled
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::Disabled,
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
begin_state<State::PlayingHigh>(moduland);
// percntrld
period_counter = period;
// [AUDxIR]: see return result.
// volcntrld
volume_latch = volume;
reset_output_phase();
// pbufld1
data_latch = data;
wants_data = true;
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
// AUDxIR.
return true;
}
template <> bool Audio::Channel::transit<
Audio::Channel::State::Disabled,
Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
begin_state<State::WaitingForDummyDMA>(moduland);
// percntrld
period_counter = period;
// AUDxDR
wants_data = true;
// lencntrld
length_counter = length;
// dmasen / AUDxDSR
should_reload_address = true;
return false;
}
template <> bool Audio::Channel::output<Audio::Channel::State::Disabled>(Channel *moduland) {
// if AUDDAT, and not AUDxON, and not AUDxIP.
if(!wants_data && !dma_enabled && !interrupt_pending) {
return transit<State::Disabled, State::PlayingHigh>(moduland);
}
// if AUDxON.
if(dma_enabled) {
return transit<State::Disabled, State::WaitingForDummyDMA>(moduland);
}
return false;
}
//
// Audio::Channel::State::WaitingForDummyDMA
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::WaitingForDummyDMA,
Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
begin_state<State::WaitingForDMA>(moduland);
// AUDxDR
wants_data = true;
// if not lenfin, then lencount
if(length != 1) {
-- length_counter;
}
// AUDxIR
return true;
}
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
// if not AUDxON
if(!dma_enabled) {
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
}
// if AUDxON and AUDxDAT
if(dma_enabled && !wants_data) {
return transit<State::WaitingForDummyDMA, State::WaitingForDMA>(moduland);
}
return false;
}
//
// Audio::Channel::State::WaitingForDMA
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::WaitingForDMA,
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
begin_state<State::PlayingHigh>(moduland);
// volcntrld
volume_latch = volume;
reset_output_phase();
// percntrld
period_counter = period;
// pbufld1
data_latch = data;
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
// if napnav
if(attach_volume || !(attach_volume || attach_period)) {
// AUDxDR
wants_data = true;
}
return false;
}
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
// if: not AUDxON
if(!dma_enabled) {
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
}
// if: AUDxON, and AUDxDAT
if(dma_enabled && !wants_data) {
return transit<State::WaitingForDummyDMA, State::PlayingHigh>(moduland);
}
return false;
}
//
// Audio::Channel::State::PlayingHigh
//
void Audio::Channel::decrement_length() {
// if lenfin and AUDxON and AUDxDAT, then lencntrld
// if (not lenfin) and AUDxON and AUDxDAT, then lencount
// if lenfin and AUDxON and AUDxDAT, then intreq2
if(dma_enabled && !wants_data) {
-- length_counter;
if(!length_counter) {
length_counter = length;
will_request_interrupt = true;
should_reload_address = true; // This feels logical to me; it's a bit
// of a stab in the dark though.
}
}
}
template <> bool Audio::Channel::transit<
Audio::Channel::State::PlayingHigh,
Audio::Channel::State::PlayingLow>(Channel *moduland) {
begin_state<State::PlayingLow>(moduland);
bool wants_interrupt = false;
// if AUDxAP
if(attach_period) {
// pbufld2
data_latch = data;
if(moduland) moduland->period = data_latch;
// [if AUDxAP] and AUDxON
if(dma_enabled) {
// AUDxDR
wants_data = true;
// [if AUDxAP and AUDxON] and intreq2
if(will_request_interrupt) {
will_request_interrupt = false;
// AUDxIR
wants_interrupt = true;
}
} else {
// i.e. if AUDxAP and AUDxON, then AUDxIR
wants_interrupt = true;
}
}
// percntrld
period_counter = period;
decrement_length();
return wants_interrupt;
}
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *) {
state = Audio::Channel::State::PlayingHigh;
// penhi.
output_level = int8_t(data_latch >> 8);
}
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingHigh>(Channel *moduland) {
// This is a reasonable guess as to the exit condition for this node;
// Commodore doesn't document.
if(period_counter == 1) {
return transit<State::PlayingHigh, State::PlayingLow>(moduland);
}
// percount.
-- period_counter;
return false;
}
//
// Audio::Channel::State::PlayingLow
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::PlayingLow,
Audio::Channel::State::Disabled>(Channel *moduland) {
begin_state<State::Disabled>(moduland);
// Clear the slightly nebulous 'if intreq2 occurred' state.
will_request_interrupt = false;
return false;
}
template <> bool Audio::Channel::transit<
Audio::Channel::State::PlayingLow,
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
begin_state<State::PlayingHigh>(moduland);
bool wants_interrupt = false;
// volcntrld
volume_latch = volume;
reset_output_phase(); // Is this correct?
// percntrld
period_counter = period;
// pbufld1
data_latch = data;
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
// if napnav
if(attach_volume || !(attach_volume || attach_period)) {
// [if napnav] and AUDxON
if(dma_enabled) {
// AUDxDR
wants_data = true;
// [if napnav and AUDxON] and intreq2
if(will_request_interrupt) {
will_request_interrupt = false;
wants_interrupt = true;
}
} else {
// AUDxIR
wants_interrupt = true;
}
}
decrement_length();
return wants_interrupt;
}
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *) {
state = Audio::Channel::State::PlayingLow;
// Output low byte.
output_level = int8_t(data_latch & 0xff);
}
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingLow>(Channel *moduland) {
-- period_counter;
if(!period_counter) {
const bool dma_or_no_interrupt = dma_enabled || !interrupt_pending;
if(dma_or_no_interrupt) {
return transit<State::PlayingLow, State::PlayingHigh>(moduland);
} else {
return transit<State::PlayingLow, State::Disabled>(moduland);
}
}
return false;
}
//
// Dispatcher
//
bool Audio::Channel::output(Channel *moduland) {
// Update pulse-width modulation.
output_phase = output_phase + 1;
if(output_phase == 64) {
reset_output_phase();
} else {
output_enabled &= output_phase != volume_latch;
}
switch(state) {
case State::Disabled: return output<State::Disabled>(moduland);
case State::WaitingForDummyDMA: return output<State::WaitingForDummyDMA>(moduland);
case State::WaitingForDMA: return output<State::WaitingForDMA>(moduland);
case State::PlayingHigh: return output<State::PlayingHigh>(moduland);
case State::PlayingLow: return output<State::PlayingLow>(moduland);
default:
assert(false);
break;
}
return false;
}

163
Machines/Amiga/Audio.hpp Normal file
View File

@ -0,0 +1,163 @@
//
// Audio.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Audio_hpp
#define Audio_hpp
#include <atomic>
#include <cstdint>
#include "DMADevice.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
namespace Amiga {
class Audio: public DMADevice<4> {
public:
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
/// Idiomatic call-in for DMA scheduling; indicates that this class may
/// perform a DMA access for the stated channel now.
bool advance_dma(int channel);
/// Advances output by one DMA window, which is implicitly two cycles
/// at the output rate that was specified to the constructor.
void output();
/// Sets the total number of words to fetch for the given channel.
void set_length(int channel, uint16_t);
/// Sets the number of DMA windows between each 8-bit output,
/// in the same time base as @c ticks_per_line.
void set_period(int channel, uint16_t);
/// Sets the output volume for the given channel; if bit 6 is set
/// then output is maximal; otherwise bits 05 select
/// a volume of [063]/64, on a logarithmic scale.
void set_volume(int channel, uint16_t);
/// Sets the next two samples of audio to output.
template <bool is_external = true> void set_data(int channel, uint16_t);
/// Provides a copy of the DMA enable flags, for the purpose of
/// determining which channels are enabled for DMA.
void set_channel_enables(uint16_t);
/// Sets which channels, if any, modulate period or volume of
/// their neighbours.
void set_modulation_flags(uint16_t);
/// Sets which interrupt requests are currently active.
void set_interrupt_requests(uint16_t);
/// Obtains the output source.
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
}
private:
struct Channel {
// The data latch plus a count of unused samples
// in the latch, which will always be 0, 1 or 2.
uint16_t data = 0x0000;
bool wants_data = false;
uint16_t data_latch = 0x0000;
// The DMA address; unlike most of the Amiga Chipset,
// the user posts a value to feed a pointer, rather
// than having access to the pointer itself.
bool should_reload_address = false;
uint32_t data_address = 0x0000'0000;
// Number of words remaining in DMA data.
uint16_t length = 0;
uint16_t length_counter = 0;
// Number of ticks between each sample, plus the
// current counter, which counts downward.
uint16_t period = 0;
uint16_t period_counter = 0;
// Modulation / attach flags.
bool attach_period = false;
bool attach_volume = false;
// Output volume, [0, 64].
uint8_t volume = 0;
uint8_t volume_latch = 0;
// Indicates whether DMA is enabled for this channel.
bool dma_enabled = false;
// Records whether this audio interrupt is pending.
bool interrupt_pending = false;
bool will_request_interrupt = false;
// Replicates the Hardware Reference Manual state machine;
// comments indicate which of the documented states each
// label refers to.
enum class State {
Disabled, // 000
WaitingForDummyDMA, // 001
WaitingForDMA, // 101
PlayingHigh, // 010
PlayingLow, // 011
} state = State::Disabled;
/// Dispatches to the appropriate templatised output for the current state.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
bool output(Channel *moduland);
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State state> bool output(Channel *moduland);
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
/// and taking any steps specific to that particular transition.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State begin, State end> bool transit(Channel *moduland);
/// Begins @c state, performing all fixed logic that would otherwise have to be
/// repeated endlessly in the relevant @c output.
/// @param moduland The channel to modulate, if modulation is enabled.
template <State state> void begin_state(Channel *moduland);
/// Provides the common length-decrementing logic used when transitioning
/// between PlayingHigh and PlayingLow in either direction.
void decrement_length();
// Output state.
int8_t output_level = 0;
uint8_t output_phase = 0;
bool output_enabled = false;
void reset_output_phase() {
output_phase = 0;
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
}
} channels_[4];
// Transient output state, and its destination.
Outputs::Speaker::PushLowpass<true> speaker_;
Concurrency::AsyncTaskQueue queue_;
using AudioBuffer = std::array<int16_t, 4096>;
static constexpr int BufferCount = 3;
AudioBuffer buffer_[BufferCount];
std::atomic<bool> buffer_available_[BufferCount];
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
};
}
#endif /* Audio_hpp */

View File

@ -0,0 +1,132 @@
//
// Bitplanes.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Bitplanes.hpp"
#include "Chipset.hpp"
using namespace Amiga;
namespace {
/// Expands @c source so that b7 is the least-significant bit of the most-significant byte of the result,
/// b6 is the least-significant bit of the next most significant byte, etc. b0 stays in place.
constexpr uint64_t expand_bitplane_byte(uint8_t source) {
uint64_t result = source; // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 abcd efgh
result = (result | (result << 28)) & 0x0000'000f'0000'000f; // 0000 0000 0000 0000 0000 0000 0000 abcd 0000 0000 0000 0000 0000 0000 0000 efgh
result = (result | (result << 14)) & 0x0003'0003'0003'0003; // 0000 0000 0000 00ab 0000 0000 0000 00cd 0000 0000 0000 00ef 0000 0000 0000 00gh
result = (result | (result << 7)) & 0x0101'0101'0101'0101; // 0000 000a 0000 000b 0000 000c 0000 000d 0000 000e 0000 000f 0000 000g 0000 000h
return result;
}
// A very small selection of test cases.
static_assert(expand_bitplane_byte(0xff) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_bitplane_byte(0x55) == 0x00'01'00'01'00'01'00'01);
static_assert(expand_bitplane_byte(0xaa) == 0x01'00'01'00'01'00'01'00);
static_assert(expand_bitplane_byte(0x00) == 0x00'00'00'00'00'00'00'00);
}
// MARK: - BitplaneShifter.
void BitplaneShifter::set(const BitplaneData &previous, const BitplaneData &next, int odd_delay, int even_delay) {
const uint16_t planes[6] = {
uint16_t(((previous[0] << 16) | next[0]) >> even_delay),
uint16_t(((previous[1] << 16) | next[1]) >> odd_delay),
uint16_t(((previous[2] << 16) | next[2]) >> even_delay),
uint16_t(((previous[3] << 16) | next[3]) >> odd_delay),
uint16_t(((previous[4] << 16) | next[4]) >> even_delay),
uint16_t(((previous[5] << 16) | next[5]) >> odd_delay),
};
// Swizzle bits into the form:
//
// [b5 b3 b1 b4 b2 b0]
//
// ... and assume a suitably adjusted palette is in use elsewhere.
// This makes dual playfields very easy to separate.
data_[0] =
(expand_bitplane_byte(uint8_t(planes[0])) << 0) |
(expand_bitplane_byte(uint8_t(planes[2])) << 1) |
(expand_bitplane_byte(uint8_t(planes[4])) << 2) |
(expand_bitplane_byte(uint8_t(planes[1])) << 3) |
(expand_bitplane_byte(uint8_t(planes[3])) << 4) |
(expand_bitplane_byte(uint8_t(planes[5])) << 5);
data_[1] =
(expand_bitplane_byte(uint8_t(planes[0] >> 8)) << 0) |
(expand_bitplane_byte(uint8_t(planes[2] >> 8)) << 1) |
(expand_bitplane_byte(uint8_t(planes[4] >> 8)) << 2) |
(expand_bitplane_byte(uint8_t(planes[1] >> 8)) << 3) |
(expand_bitplane_byte(uint8_t(planes[3] >> 8)) << 4) |
(expand_bitplane_byte(uint8_t(planes[5] >> 8)) << 5);
}
// MARK: - Bitplanes.
bool Bitplanes::advance_dma(int cycle) {
#define BIND_CYCLE(offset, plane) \
case offset: \
if(plane_count_ > plane) { \
next[plane] = ram_[pointer_[plane] & ram_mask_]; \
++pointer_[plane]; \
if constexpr (!plane) { \
chipset_.post_bitplanes(next); \
} \
return true; \
} \
return false;
if(is_high_res_) {
switch(cycle&3) {
default: return false;
BIND_CYCLE(0, 3);
BIND_CYCLE(1, 1);
BIND_CYCLE(2, 2);
BIND_CYCLE(3, 0);
}
} else {
switch(cycle&7) {
default: return false;
/* Omitted: 0. */
BIND_CYCLE(1, 3);
BIND_CYCLE(2, 5);
BIND_CYCLE(3, 1);
/* Omitted: 4. */
BIND_CYCLE(5, 2);
BIND_CYCLE(6, 4);
BIND_CYCLE(7, 0);
}
}
return false;
#undef BIND_CYCLE
}
void Bitplanes::do_end_of_line() {
// Apply modulos here. Posssibly correct?
pointer_[0] += modulos_[1];
pointer_[2] += modulos_[1];
pointer_[4] += modulos_[1];
pointer_[1] += modulos_[0];
pointer_[3] += modulos_[0];
pointer_[5] += modulos_[0];
}
void Bitplanes::set_control(uint16_t control) {
is_high_res_ = control & 0x8000;
plane_count_ = (control >> 12) & 7;
// TODO: who really has responsibility for clearing the other
// bit plane fields?
std::fill(next.begin() + plane_count_, next.end(), 0);
if(plane_count_ == 7) {
plane_count_ = 4;
}
}

View File

@ -0,0 +1,96 @@
//
// Bitplanes.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Bitplanes_hpp
#define Bitplanes_hpp
#include <cstdint>
#include "DMADevice.hpp"
namespace Amiga {
struct BitplaneData: public std::array<uint16_t, 6> {
BitplaneData &operator <<= (int c) {
(*this)[0] <<= c;
(*this)[1] <<= c;
(*this)[2] <<= c;
(*this)[3] <<= c;
(*this)[4] <<= c;
(*this)[5] <<= c;
return *this;
}
void clear() {
std::fill(begin(), end(), 0);
}
};
class Bitplanes: public DMADevice<6, 2> {
public:
using DMADevice::DMADevice;
bool advance_dma(int cycle);
void do_end_of_line();
void set_control(uint16_t);
private:
bool is_high_res_ = false;
int plane_count_ = 0;
BitplaneData next;
};
template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
return
(value&0x21) |
((value&0x02) << 2) |
((value&0x04) >> 1) |
((value&0x08) << 1) |
((value&0x10) >> 2);
}
class BitplaneShifter {
public:
/// Installs a new set of output pixels.
void set(
const BitplaneData &previous,
const BitplaneData &next,
int odd_delay,
int even_delay);
/// Shifts either two pixels (in low-res mode) and four pixels (in high-res).
void shift(bool high_res) {
constexpr int shifts[] = {16, 32};
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
data_[0] <<= shifts[high_res];
}
/// @returns The next four pixels to output; in low-resolution mode only two
/// of them will be unique. The value is arranges so that MSB = first pixel to output,
/// LSB = last. Each byte is formed as 00[bitplane 5][bitplane 4]...[bitplane 0].
uint32_t get(bool high_res) {
if(high_res) {
return uint32_t(data_[1] >> 32);
} else {
uint32_t result = uint16_t(data_[1] >> 48);
result = ((result & 0xff00) << 8) | (result & 0x00ff);
result |= result << 8;
return result;
}
}
private:
std::array<uint64_t, 2> data_{};
};
}
#endif /* Bitplanes_hpp */

324
Machines/Amiga/Blitter.cpp Normal file
View File

@ -0,0 +1,324 @@
//
// Blitter.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Blitter.hpp"
#include "Minterms.hpp"
#include <cassert>
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[Blitter] "
#include "../../Outputs/Log.hpp"
using namespace Amiga;
void Blitter::set_control(int index, uint16_t value) {
if(index) {
line_mode_ = (value & 0x0001);
one_dot_ = value & 0x0002;
line_direction_ = (value >> 2) & 7;
line_sign_ = (value & 0x0040) ? -1 : 1;
direction_ = one_dot_ ? uint32_t(-1) : uint32_t(1);
exclusive_fill_ = (value & 0x0010);
inclusive_fill_ = !exclusive_fill_ && (value & 0x0008); // Exclusive fill takes precedence. Probably? TODO: verify.
fill_carry_ = (value & 0x0004);
} else {
minterms_ = value & 0xff;
channel_enables_[3] = value & 0x100;
channel_enables_[2] = value & 0x200;
channel_enables_[1] = value & 0x400;
channel_enables_[0] = value & 0x800;
}
shifts_[index] = value >> 12;
LOG("Set control " << index << " to " << PADHEX(4) << value);
}
void Blitter::set_first_word_mask(uint16_t value) {
LOG("Set first word mask: " << PADHEX(4) << value);
a_mask_[0] = value;
}
void Blitter::set_last_word_mask(uint16_t value) {
LOG("Set last word mask: " << PADHEX(4) << value);
a_mask_[1] = value;
}
void Blitter::set_size(uint16_t value) {
// width_ = (width_ & ~0x3f) | (value & 0x3f);
// height_ = (height_ & ~0x3ff) | (value >> 6);
width_ = value & 0x3f;
if(!width_) width_ = 0x40;
height_ = value >> 6;
if(!height_) height_ = 1024;
LOG("Set size to " << std::dec << width_ << ", " << height_);
// Current assumption: writing this register informs the
// blitter that it should treat itself as about to start a new line.
}
void Blitter::set_minterms(uint16_t value) {
LOG("Set minterms " << PADHEX(4) << value);
minterms_ = value & 0xff;
}
//void Blitter::set_vertical_size([[maybe_unused]] uint16_t value) {
// LOG("Set vertical size " << PADHEX(4) << value);
// // TODO. This is ECS only, I think. Ditto set_horizontal_size.
//}
//
//void Blitter::set_horizontal_size([[maybe_unused]] uint16_t value) {
// LOG("Set horizontal size " << PADHEX(4) << value);
//}
void Blitter::set_data(int channel, uint16_t value) {
LOG("Set data " << channel << " to " << PADHEX(4) << value);
// Ugh, backed myself into a corner. TODO: clean.
switch(channel) {
case 0: a_data_ = value; break;
case 1: b_data_ = value; break;
case 2: c_data_ = value; break;
default: break;
}
}
uint16_t Blitter::get_status() {
const uint16_t result =
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
LOG("Returned status of " << result);
return result;
}
bool Blitter::advance_dma() {
if(!height_) return false;
not_zero_flag_ = false;
if(line_mode_) {
// As-yet unimplemented:
assert(b_data_ == 0xffff);
//
// Line mode.
//
// Bluffer's guide to line mode:
//
// In Bresenham terms, the following registers have been set up:
//
// [A modulo] = 4 * (dy - dx)
// [B modulo] = 4 * dy
// [A pointer] = 4 * dy - 2 * dx, with the sign flag in BLTCON1 indicating sign.
//
// [A data] = 0x8000
// [Both masks] = 0xffff
// [A shift] = x1 & 15
//
// [B data] = texture
// [B shift] = bit at which to start the line texture (0 = LSB)
//
// [C and D pointers] = word containing the first pixel of the line
// [C and D modulo] = width of the bitplane in bytes
//
// height = number of pixels
//
// If ONEDOT of BLTCON1 is set, plot only a single bit per horizontal row.
//
// BLTCON1 quadrants are (bits 24):
//
// 110 -> step in x, x positive, y negative
// 111 -> step in x, x negative, y negative
// 101 -> step in x, x negative, y positive
// 100 -> step in x, x positive, y positive
//
// 001 -> step in y, x positive, y negative
// 011 -> step in y, x negative, y negative
// 010 -> step in y, x negative, y positive
// 000 -> step in y, x positive, y positive
//
// So that's:
//
// * bit 4 = x [=1] or y [=0] major;
// * bit 3 = 1 => major variable negative; otherwise positive;
// * bit 2 = 1 => minor variable negative; otherwise positive.
//
// Implementation below is heavily based on the documentation found
// at https://github.com/niklasekstrom/blitter-subpixel-line/blob/master/Drawing%20lines%20using%20the%20Amiga%20blitter.pdf
//
int error = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
bool draw_ = true;
while(height_--) {
if(draw_) {
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
// Probably extend it to a full word?
c_data_ = ram_[pointer_[3] & ram_mask_];
const uint16_t output =
apply_minterm<uint16_t>(a_data_ >> shifts_[0], b_data_, c_data_, minterms_);
ram_[pointer_[3] & ram_mask_] = output;
not_zero_flag_ |= output;
draw_ &= !one_dot_;
}
constexpr int LEFT = 1 << 0;
constexpr int RIGHT = 1 << 1;
constexpr int UP = 1 << 2;
constexpr int DOWN = 1 << 3;
int step = (line_direction_ & 4) ?
((line_direction_ & 1) ? LEFT : RIGHT) :
((line_direction_ & 1) ? UP : DOWN);
if(error < 0) {
error += modulos_[1];
} else {
step |=
(line_direction_ & 4) ?
((line_direction_ & 2) ? UP : DOWN) :
((line_direction_ & 2) ? LEFT : RIGHT);
error += modulos_[0];
}
if(step & LEFT) {
--shifts_[0];
if(shifts_[0] == -1) {
--pointer_[3];
}
} else if(step & RIGHT) {
++shifts_[0];
if(shifts_[0] == 16) {
++pointer_[3];
}
}
shifts_[0] &= 15;
if(step & UP) {
pointer_[3] -= modulos_[2];
draw_ = true;
} else if(step & DOWN) {
pointer_[3] += modulos_[2];
draw_ = true;
}
}
} else {
// Copy mode.
// Quick hack: do the entire action atomically.
a32_ = 0;
b32_ = 0;
for(int y = 0; y < height_; y++) {
bool fill_carry = fill_carry_;
for(int x = 0; x < width_; x++) {
uint16_t a_mask = 0xffff;
if(x == 0) a_mask &= a_mask_[0];
if(x == width_ - 1) a_mask &= a_mask_[1];
if(channel_enables_[0]) {
a_data_ = ram_[pointer_[0] & ram_mask_];
pointer_[0] += direction_;
}
a32_ = (a32_ << 16) | (a_data_ & a_mask);
if(channel_enables_[1]) {
b_data_ = ram_[pointer_[1] & ram_mask_];
pointer_[1] += direction_;
}
b32_ = (b32_ << 16) | b_data_;
if(channel_enables_[2]) {
c_data_ = ram_[pointer_[2] & ram_mask_];
pointer_[2] += direction_;
}
uint16_t a, b;
// The barrel shifter shifts to the right in ascending address mode,
// but to the left otherwise
if(!one_dot_) {
a = uint16_t(a32_ >> shifts_[0]);
b = uint16_t(b32_ >> shifts_[1]);
} else {
// TODO: there must be a neater solution than this.
a = uint16_t(
(a32_ << shifts_[0]) |
(a32_ >> (32 - shifts_[0]))
);
b = uint16_t(
(b32_ << shifts_[1]) |
(b32_ >> (32 - shifts_[1]))
);
}
uint16_t output =
apply_minterm<uint16_t>(
a,
b,
c_data_,
minterms_);
// TODO: don't be so dense as below. This is the initial
// does-it-pass-the-tests? version.
if(exclusive_fill_ || inclusive_fill_) {
uint16_t fill_output = 0;
uint16_t bit = one_dot_ ? 0x0001 : 0x8000;
uint16_t flag = fill_carry ? bit : 0x0000;
while(bit) {
uint16_t pre_toggle = output & bit, post_toggle = pre_toggle;
if(inclusive_fill_) {
pre_toggle &= ~flag; // Accept bits that would transition to set immediately.
post_toggle &= flag; // Accept bits that would transition to clear after the fact.
} else {
post_toggle = 0; // Just do the pre toggle.
}
flag ^= pre_toggle;
fill_output |= flag;
flag ^= post_toggle;
fill_carry = flag;
if(one_dot_) {
bit <<= 1;
flag <<= 1;
} else {
bit >>= 1;
flag >>= 1;
}
}
output = fill_output;
}
not_zero_flag_ |= output;
if(channel_enables_[3]) {
ram_[pointer_[3] & ram_mask_] = output;
pointer_[3] += direction_;
}
}
pointer_[0] += modulos_[0] * channel_enables_[0] * direction_;
pointer_[1] += modulos_[1] * channel_enables_[1] * direction_;
pointer_[2] += modulos_[2] * channel_enables_[2] * direction_;
pointer_[3] += modulos_[3] * channel_enables_[3] * direction_;
}
}
posit_interrupt(InterruptFlag::Blitter);
height_ = 0;
return true;
}

View File

@ -0,0 +1,69 @@
//
// Blitter.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Blitter_hpp
#define Blitter_hpp
#include <cstddef>
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "DMADevice.hpp"
namespace Amiga {
class Blitter: public DMADevice<4, 4> {
public:
using DMADevice::DMADevice;
// Various setters; it's assumed that address decoding is handled externally.
//
// In all cases where a channel is identified numerically, it's taken that
// 0 = A, 1 = B, 2 = C, 3 = D.
void set_control(int index, uint16_t value);
void set_first_word_mask(uint16_t value);
void set_last_word_mask(uint16_t value);
void set_size(uint16_t value);
void set_minterms(uint16_t value);
// void set_vertical_size(uint16_t value);
// void set_horizontal_size(uint16_t value);
void set_data(int channel, uint16_t value);
uint16_t get_status();
bool advance_dma();
private:
int width_ = 0, height_ = 0;
int shifts_[2]{};
uint16_t a_mask_[2] = {0xffff, 0xffff};
bool line_mode_ = false;
bool one_dot_ = false;
int line_direction_ = 0;
int line_sign_ = 1;
uint32_t direction_ = 1;
bool inclusive_fill_ = false;
bool exclusive_fill_ = false;
bool fill_carry_ = false;
bool channel_enables_[4]{};
uint8_t minterms_ = 0;
uint32_t a32_ = 0, b32_ = 0;
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
bool not_zero_flag_ = false;
};
}
#endif /* Blitter_hpp */

1227
Machines/Amiga/Chipset.cpp Normal file

File diff suppressed because it is too large Load Diff

360
Machines/Amiga/Chipset.hpp Normal file
View File

@ -0,0 +1,360 @@
//
// Chipset.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Chipset_hpp
#define Chipset_hpp
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include "../../Activity/Source.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Components/6526/6526.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Processors/68000/68000.hpp"
#include "../../Storage/Disk/Controller/DiskController.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include "Audio.hpp"
#include "Bitplanes.hpp"
#include "Blitter.hpp"
#include "Copper.hpp"
#include "DMADevice.hpp"
#include "Flags.hpp"
#include "Keyboard.hpp"
#include "MouseJoystick.hpp"
#include "MemoryMap.hpp"
#include "Sprites.hpp"
namespace Amiga {
class Chipset: private ClockingHint::Observer {
public:
Chipset(MemoryMap &memory_map, int input_clock_rate);
struct Changes {
int interrupt_level = 0;
HalfCycles duration;
Changes &operator += (const Changes &rhs) {
duration += rhs.duration;
return *this;
}
};
/// Advances the stated amount of time.
Changes run_for(HalfCycles);
/// Advances to the next available CPU slot.
Changes run_until_cpu_slot();
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
void perform(const CPU::MC68000::Microcycle &);
/// Sets the current state of the CIA interrupt lines.
void set_cia_interrupts(bool cia_a, bool cia_b);
/// Provides the chipset's current interrupt level.
int get_interrupt_level() {
return interrupt_level_;
}
/// Inserts the disks provided.
/// @returns @c true if anything was inserted; @c false otherwise.
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
// The standard CRT set.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void set_display_type(Outputs::Display::DisplayType);
Outputs::Display::DisplayType get_display_type() const;
// Activity observation.
void set_activity_observer(Activity::Observer *observer) {
cia_a_handler_.set_activity_observer(observer);
disk_controller_.set_activity_observer(observer);
}
// Keyboard and mouse exposure.
Keyboard &get_keyboard() {
return keyboard_;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
// Synchronisation.
void flush();
// Input for receiving collected bitplanes.
void post_bitplanes(const BitplaneData &data);
// Obtains the source of audio output.
Outputs::Speaker::Speaker *get_speaker() {
return audio_.get_speaker();
}
private:
friend class DMADeviceBase;
// MARK: - E Clock and keyboard dividers.
HalfCycles cia_divider_;
HalfCycles keyboard_divider_;
// MARK: - Interrupts.
uint16_t interrupt_enable_ = 0;
uint16_t interrupt_requests_ = 0;
int interrupt_level_ = 0;
void update_interrupts();
void posit_interrupt(InterruptFlag);
// MARK: - Scheduler.
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
template <bool stop_on_cpu> int advance_slots(int, int);
template <int cycle, bool stop_if_cpu> bool perform_cycle();
template <int cycle> void output();
void output_pixels(int cycles_until_sync);
void apply_ham(uint8_t);
// MARK: - DMA Control, Scheduler and Blitter.
uint16_t dma_control_ = 0;
Blitter blitter_;
// MARK: - Sprites and collision flags.
std::array<Sprite, 8> sprites_;
std::array<TwoSpriteShifter, 4> sprite_shifters_;
uint16_t collisions_ = 0, collisions_flags_= 0;
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
// MARK: - Raster position and state.
// Definitions related to PAL/NTSC.
// (Default values are PAL).
int line_length_ = 227;
int short_field_height_ = 312;
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
// Current raster position.
int line_cycle_ = 0, y_ = 0;
// Parameters affecting bitplane collection and output.
uint16_t display_window_start_[2] = {0, 0};
uint16_t display_window_stop_[2] = {0, 0};
uint16_t fetch_window_[2] = {0, 0};
// Ephemeral bitplane collection state.
bool fetch_vertical_ = false, fetch_horizontal_ = false;
bool display_horizontal_ = false;
bool did_fetch_ = false;
uint16_t fetch_stop_ = 0xffff;
// Output state.
uint16_t border_colour_ = 0;
bool is_border_ = true;
int zone_duration_ = 0;
uint16_t *pixels_ = nullptr;
uint16_t last_colour_ = 0; // Retained for HAM mode.
void flush_output();
Bitplanes bitplanes_;
BitplaneData next_bitplanes_, previous_bitplanes_;
bool has_next_bitplanes_ = false;
int odd_priority_ = 0, even_priority_ = 0;
bool even_over_odd_ = false;
bool hold_and_modify_ = false;
bool dual_playfields_ = false;
bool interlace_ = false;
bool is_long_field_ = false;
BitplaneShifter bitplane_pixels_;
int odd_delay_ = 0, even_delay_ = 0;
bool is_high_res_ = false;
// MARK: - Copper.
Copper copper_;
// MARK: - Audio.
Audio audio_;
// MARK: - Serial port.
class SerialPort {
public:
void set_control(uint16_t) {}
private:
uint16_t value = 0, reload = 0;
uint16_t shift = 0, receive_shift = 0;
uint16_t status;
} serial_;
// MARK: - Pixel output.
Outputs::CRT::CRT crt_;
uint16_t palette_[32]{};
uint16_t swizzled_palette_[64]{};
// MARK: - Mouse.
private:
Mouse mouse_;
public:
Inputs::Mouse &get_mouse() {
return mouse_;
}
// MARK: - Joystick.
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Joystick &joystick(size_t index) const {
return *static_cast<Joystick *>(joysticks_[index].get());
}
// MARK: - CIAs.
private:
class DiskController;
class CIAAHandler: public MOS::MOS6526::PortHandler {
public:
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port port);
void set_activity_observer(Activity::Observer *observer);
// TEMPORARY.
// TODO: generalise mice and joysticks.
// This is a hack. A TEMPORARY HACK.
void set_joystick(Joystick *joystick) {
joystick_ = joystick;
}
private:
MemoryMap &map_;
DiskController &controller_;
Mouse &mouse_;
Joystick *joystick_ = nullptr;
Activity::Observer *observer_ = nullptr;
inline static const std::string led_name = "Power";
} cia_a_handler_;
class CIABHandler: public MOS::MOS6526::PortHandler {
public:
CIABHandler(DiskController &controller);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port);
private:
DiskController &controller_;
} cia_b_handler_;
public:
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
// CIAs are provided for direct access; it's up to the caller properly
// to distinguish relevant accesses.
CIAA cia_a;
CIAB cia_b;
private:
// MARK: - Disk drives.
class DiskDMA: public DMADevice<1> {
public:
using DMADevice::DMADevice;
void set_length(uint16_t);
void set_control(uint16_t);
bool advance_dma();
void enqueue(uint16_t value, bool matches_sync);
private:
uint16_t length_;
bool dma_enable_ = false;
bool write_ = false;
uint16_t last_set_length_ = 0;
bool sync_with_word_ = false;
std::array<uint16_t, 4> buffer_;
size_t buffer_read_ = 0, buffer_write_ = 0;
enum class State {
Inactive,
WaitingForSync,
Reading,
} state_ = State::Inactive;
} disk_;
class DiskController: public Storage::Disk::Controller {
public:
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
void set_mtr_sel_side_dir_step(uint8_t);
uint8_t get_rdy_trk0_wpro_chng();
void run_for(Cycles duration) {
Storage::Disk::Controller::run_for(duration);
}
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
void set_activity_observer(Activity::Observer *);
void set_sync_word(uint16_t);
void set_control(uint16_t);
private:
void process_input_bit(int value) final;
void process_index_hole() final;
// Implement the Amiga's drive ID shift registers
// directly in the controller for now.
uint32_t drive_ids_[4]{};
uint32_t previous_select_ = 0;
uint16_t data_ = 0;
int bit_count_ = 0;
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
bool sync_with_word_ = false;
Chipset &chipset_;
DiskDMA &disk_dma_;
CIAB &cia_;
} disk_controller_;
friend DiskController;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
bool disk_controller_is_sleeping_ = false;
uint16_t paula_disk_control_ = 0;
// MARK: - Keyboard.
Keyboard keyboard_;
};
}
#endif /* Chipset_hpp */

147
Machines/Amiga/Copper.cpp Normal file
View File

@ -0,0 +1,147 @@
//
// Copper.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[Copper] "
#include "../../Outputs/Log.hpp"
#include "Chipset.hpp"
#include "Copper.hpp"
using namespace Amiga;
namespace {
bool satisfies_raster(uint16_t position, uint16_t blitter_status, uint16_t *instruction) {
const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe);
return
(position & mask) >= (instruction[0] & mask)
&& (!(blitter_status & 0x4000) || (instruction[1] & 0x8000));
}
}
//
// Quick notes on the Copper:
//
// There are three instructions: move, wait and skip. All are two words in length.
//
// Move writes a value to one of the Chipset registers; it is encoded as:
//
// First word:
// b0: 0
// b1b8: register address
// b9+: unused ("should be set to 0")
//
// Second word:
// b0b15: value to move.
//
//
// Wait waits until the raster gets to at least a certain position, and
// optionally until the Blitter has finished. It is encoded as:
//
// First word:
// b0: 1
// b1b7: horizontal beam position
// b8+: vertical beam position
//
// Second word:
// b0: 0
// b1b7: horizontal beam comparison mask
// b8b14: vertical beam comparison mask
// b15: 1 => don't also wait for the Blitter to be finished; 0 => wait.
//
//
// Skip skips the next instruction if the raster has already reached a certain
// position, and optionally only if the Blitter has finished, and only if the
// next instruction is a move.
//
// First word:
// b0: 1
// b1b7: horizontal beam position
// b8+: vertical beam position
//
// Second word:
// b0: 1
// b1b7: horizontal beam comparison mask
// b8b14: vertical beam comparison mask
// b15: 1 => don't also test whether the Blitter is finished; 0 => test.
//
bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) {
switch(state_) {
default: return false;
case State::Waiting:
if(satisfies_raster(position, blitter_status, instruction_)) {
LOG("Unblocked waiting for " << PADHEX(4) << instruction_[0] << " at " << position);
state_ = State::FetchFirstWord;
}
return false;
case State::FetchFirstWord:
instruction_[0] = ram_[address_ & ram_mask_];
++address_;
state_ = State::FetchSecondWord;
break;
case State::FetchSecondWord: {
// Get and reset the should-skip-next flag.
const bool should_skip_move = skip_next_;
skip_next_ = false;
// Read in the second instruction word.
instruction_[1] = ram_[address_ & ram_mask_];
++address_;
// Check for a MOVE.
if(!(instruction_[0] & 1)) {
if(!should_skip_move) {
// Stop if this move would be a privilege violation.
instruction_[0] &= 0x1fe;
if((instruction_[0] < 0x10) || (instruction_[0] < 0x20 && !(control_&1))) {
LOG("Invalid MOVE to " << PADHEX(4) << instruction_[0] << "; stopping");
state_ = State::Stopped;
break;
}
// Construct a 68000-esque Microcycle in order to be able to perform the access.
CPU::MC68000::Microcycle cycle;
cycle.operation = CPU::MC68000::Microcycle::SelectWord;
uint32_t full_address = instruction_[0];
CPU::RegisterPair16 data = instruction_[1];
cycle.address = &full_address;
cycle.value = &data;
chipset_.perform(cycle);
}
// Roll onto the next command.
state_ = State::FetchFirstWord;
break;
}
// Got to here => this is a WAIT or a SKIP.
if(!(instruction_[1] & 1)) {
// A WAIT. Just note that this is now waiting; the proper test
// will be applied from the next potential `advance_dma` onwards.
state_ = State::Waiting;
break;
}
// Neither a WAIT nor a MOVE => a SKIP.
skip_next_ = satisfies_raster(position, blitter_status, instruction_);
state_ = State::FetchFirstWord;
} break;
}
return true;
}

54
Machines/Amiga/Copper.hpp Normal file
View File

@ -0,0 +1,54 @@
//
// Copper.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Copper_h
#define Copper_h
#include "DMADevice.hpp"
namespace Amiga {
class Copper: public DMADevice<2> {
public:
using DMADevice<2>::DMADevice;
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
///
/// @returns @c true if the slot was used; @c false otherwise.
bool advance_dma(uint16_t position, uint16_t blitter_status);
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
template <int id> void reload() {
address_ = pointer_[id];
state_ = State::FetchFirstWord;
}
/// Sets the Copper control word.
void set_control(uint16_t c) {
control_ = c;
}
/// Forces the Copper into the stopped state.
void stop() {
state_ = State::Stopped;
}
private:
uint32_t address_ = 0;
uint16_t control_ = 0;
enum class State {
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
} state_ = State::Stopped;
bool skip_next_ = false;
uint16_t instruction_[2]{};
};
}
#endif /* Copper_h */

View File

@ -0,0 +1,74 @@
//
// DMADevice.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef DMADevice_hpp
#define DMADevice_hpp
#include <array>
#include <cstddef>
#include <cstdint>
#include "Flags.hpp"
namespace Amiga {
class Chipset;
class DMADeviceBase {
public:
DMADeviceBase(Chipset &chipset, uint16_t *ram, size_t word_size) :
chipset_(chipset), ram_(ram), ram_mask_(uint32_t(word_size - 1)) {}
void posit_interrupt(Amiga::InterruptFlag);
protected:
Chipset &chipset_;
uint16_t *const ram_ = nullptr;
const uint32_t ram_mask_ = 0;
};
template <size_t num_addresses, size_t num_modulos = 0> class DMADevice: public DMADeviceBase {
public:
using DMADeviceBase::DMADeviceBase;
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
template <int id, int shift> void set_pointer(uint16_t value) {
static_assert(id < num_addresses);
static_assert(shift == 0 || shift == 16);
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
pointer_[id] = byte_pointer_[id] >> 1;
}
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
template <int id> void set_modulo(uint16_t value) {
static_assert(id < num_modulos);
// Convert by sign extension.
modulos_[id] = uint32_t(int16_t(value) >> 1);
}
template <int id, int shift> uint16_t get_pointer() {
// Restore the original least-significant bit.
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
return uint16_t(source >> shift);
}
protected:
// These are shifted right one to provide word-indexing pointers;
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
std::array<uint32_t, num_addresses> pointer_{};
std::array<uint32_t, num_modulos> modulos_{};
private:
std::array<uint32_t, num_addresses> byte_pointer_{};
};
}
#endif /* DMADevice_hpp */

259
Machines/Amiga/Disk.cpp Normal file
View File

@ -0,0 +1,259 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Chipset.hpp"
#define LOG_PREFIX "[Disk] "
#include "../../Outputs/Log.hpp"
using namespace Amiga;
// MARK: - Disk DMA.
void Chipset::DiskDMA::enqueue(uint16_t value, bool matches_sync) {
if(matches_sync && state_ == State::WaitingForSync) {
state_ = State::Reading;
return;
}
if(state_ == State::Reading) {
buffer_[buffer_write_ & 3] = value;
if(buffer_write_ == buffer_read_ + 4) {
++buffer_read_;
}
++buffer_write_;
}
}
void Chipset::DiskDMA::set_control(uint16_t control) {
sync_with_word_ = control & 0x400;
}
void Chipset::DiskDMA::set_length(uint16_t value) {
if(value == last_set_length_) {
dma_enable_ = value & 0x8000;
write_ = value & 0x4000;
length_ = value & 0x3fff;
buffer_read_ = buffer_write_ = 0;
if(dma_enable_) {
LOG("Disk DMA " << (write_ ? "write" : "read") << " of " << length_ << " to " << PADHEX(8) << pointer_[0]);
}
state_ = sync_with_word_ ? State::WaitingForSync : State::Reading;
}
last_set_length_ = value;
}
bool Chipset::DiskDMA::advance_dma() {
if(!dma_enable_) return false;
if(!write_) {
if(length_ && buffer_read_ != buffer_write_) {
ram_[pointer_[0] & ram_mask_] = buffer_[buffer_read_ & 3];
++pointer_[0];
++buffer_read_;
--length_;
if(!length_) {
chipset_.posit_interrupt(InterruptFlag::DiskBlock);
state_ = State::Inactive;
}
return true;
}
}
return false;
}
// MARK: - Disk Controller.
Chipset::DiskController::DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia) :
Storage::Disk::Controller(clock_rate),
chipset_(chipset),
disk_dma_(disk_dma),
cia_(cia) {
// Add four drives.
for(int c = 0; c < 4; c++) {
emplace_drive(clock_rate.as<int>(), 300, 2, Storage::Disk::Drive::ReadyType::IBMRDY);
}
}
void Chipset::DiskController::process_input_bit(int value) {
data_ = uint16_t((data_ << 1) | value);
++bit_count_;
const bool sync_matches = data_ == sync_word_;
if(sync_matches) {
chipset_.posit_interrupt(InterruptFlag::DiskSyncMatch);
if(sync_with_word_) {
bit_count_ = 0;
}
}
if(!(bit_count_ & 15)) {
disk_dma_.enqueue(data_, sync_matches);
}
}
void Chipset::DiskController::set_sync_word(uint16_t value) {
LOG("Set disk sync word to " << PADHEX(4) << value);
sync_word_ = value;
}
void Chipset::DiskController::set_control(uint16_t control) {
// b13 and b14: precompensation length specifier
// b12: 0 => GCR precompensation; 1 => MFM.
// b10: 1 => enable use of word sync; 0 => disable.
// b9: 1 => sync on MSB (Disk II style, presumably?); 0 => don't.
// b8: 1 => 2µs per bit; 0 => 4µs.
sync_with_word_ = control & 0x400;
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = (control & 0x100) ? 500000 : 250000;
set_expected_bit_length(bit_length);
LOG((sync_with_word_ ? "Will" : "Won't") << " sync with word; bit length is " << ((control & 0x100) ? "short" : "long"));
}
void Chipset::DiskController::process_index_hole() {
// Pulse the CIA flag input.
//
// TODO: rectify once drives do an actual index pulse, with length.
cia_.set_flag_input(true);
cia_.set_flag_input(false);
// Resync word output. Experimental!!
bit_count_ = 0;
}
void Chipset::DiskController::set_mtr_sel_side_dir_step(uint8_t value) {
// b7: /MTR
// b6: /SEL3
// b5: /SEL2
// b4: /SEL1
// b3: /SEL0
// b2: /SIDE
// b1: DIR
// b0: /STEP
// Select active drive.
set_drive(((value >> 3) & 0x0f) ^ 0x0f);
// "[The MTR] signal is nonstandard on the Amiga system.
// Each drive will latch the motor signal at the time its
// select signal turns on." — The Hardware Reference Manual.
const auto difference = int(previous_select_ ^ value);
previous_select_ = value;
// Check for changes in the SEL line per drive.
const bool motor_on = !(value & 0x80);
const int side = (value & 0x04) ? 0 : 1;
const bool did_step = difference & value & 0x01;
const auto direction = Storage::Disk::HeadPosition(
(value & 0x02) ? -1 : 1
);
for(int c = 0; c < 4; c++) {
auto &drive = get_drive(size_t(c));
const int select_mask = 0x08 << c;
const bool is_selected = !(value & select_mask);
// Both the motor state and the ID shifter are affected upon
// changes in drive selection only.
if(difference & select_mask) {
// If transitioning to inactive, shift the drive ID value;
// if transitioning to active, possibly reset the drive
// ID and definitely latch the new motor state.
if(!is_selected) {
drive_ids_[c] <<= 1;
LOG("Shifted drive ID shift register for drive " << +c << " to " << PADHEX(4) << std::bitset<16>{drive_ids_[c]});
} else {
// Motor transition on -> off => reload register.
if(!motor_on && drive.get_motor_on()) {
// NB:
// 0xffff'ffff = 3.5" drive;
// 0x5555'5555 = 5.25" drive;
// 0x0000'0000 = no drive.
drive_ids_[c] = 0xffff'ffff;
LOG("Reloaded drive ID shift register for drive " << +c);
}
// Also latch the new motor state.
drive.set_motor_on(motor_on);
}
}
// Set the new side.
drive.set_head(side);
// Possibly step.
if(did_step && is_selected) {
LOG("Stepped drive " << +c << " by " << std::dec << +direction.as_int());
drive.step(direction);
}
}
}
uint8_t Chipset::DiskController::get_rdy_trk0_wpro_chng() {
// b5: /RDY
// b4: /TRK0
// b3: /WPRO
// b2: /CHNG
// My interpretation:
//
// RDY isn't RDY, it's a shift value as described above, combined with the motor state.
// CHNG is what is normally RDY.
const uint32_t combined_id =
((previous_select_ & 0x40) ? 0 : drive_ids_[3]) |
((previous_select_ & 0x20) ? 0 : drive_ids_[2]) |
((previous_select_ & 0x10) ? 0 : drive_ids_[1]) |
((previous_select_ & 0x08) ? 0 : drive_ids_[0]);
auto &drive = get_drive();
const uint8_t active_high =
((combined_id & 0x8000) >> 10) |
(drive.get_motor_on() ? 0x20 : 0x00) |
(drive.get_is_ready() ? 0x00 : 0x04) |
(drive.get_is_track_zero() ? 0x10 : 0x00) |
(drive.get_is_read_only() ? 0x08 : 0x00);
return ~active_high;
}
void Chipset::DiskController::set_activity_observer(Activity::Observer *observer) {
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
});
}
bool Chipset::DiskController::insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive) {
if(drive >= 4) return false;
get_drive(drive).set_disk(disk);
return true;
}
bool Chipset::insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks) {
bool inserted = false;
size_t target = 0;
for(const auto &disk: disks) {
inserted |= disk_controller_.insert(disk, target);
++target;
}
return inserted;
}

49
Machines/Amiga/Flags.hpp Normal file
View File

@ -0,0 +1,49 @@
//
// Flags.hpp
// Clock Signal
//
// Created by Thomas Harte on 13/10/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Flags_hpp
#define Flags_hpp
namespace Amiga {
enum class InterruptFlag: uint16_t {
SerialPortTransmit = 1 << 0,
DiskBlock = 1 << 1,
Software = 1 << 2,
IOPortsAndTimers = 1 << 3, // i.e. CIA A.
Copper = 1 << 4,
VerticalBlank = 1 << 5,
Blitter = 1 << 6,
AudioChannel0 = 1 << 7,
AudioChannel1 = 1 << 8,
AudioChannel2 = 1 << 9,
AudioChannel3 = 1 << 10,
SerialPortReceive = 1 << 11,
DiskSyncMatch = 1 << 12,
External = 1 << 13, // i.e. CIA B.
};
enum class DMAFlag: uint16_t {
AudioChannel0 = 1 << 0,
AudioChannel1 = 1 << 1,
AudioChannel2 = 1 << 2,
AudioChannel3 = 1 << 3,
Disk = 1 << 4,
Sprites = 1 << 5,
Blitter = 1 << 6,
Copper = 1 << 7,
Bitplane = 1 << 8,
AllBelow = 1 << 9,
BlitterPriority = 1 << 10,
BlitterZero = 1 << 13,
BlitterBusy = 1 << 14,
};
};
#endif /* Flags_hpp */

188
Machines/Amiga/Keyboard.cpp Normal file
View File

@ -0,0 +1,188 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
// Notes to self:
//
//
// Before
// the transmission starts, both KCLK and KDAT are high. The keyboard starts
// the transmission by putting out the first data bit (on KDAT), followed by
// a pulse on KCLK (low then high); then it puts out the second data bit and
// pulses KCLK until all eight data bits have been sent.
//
// When the computer has received the eighth bit, it must pulse KDAT low for
// at least 1 (one) microsecond, as a handshake signal to the keyboard. The
// keyboard must be able to detect pulses greater than or equal
// to 1 microsecond. Software MUST pulse the line low for 85 microseconds to
// ensure compatibility with all keyboard models.
//
//
// If the handshake pulse does not arrive within
// 143 ms of the last clock of the transmission, the keyboard will assume
// that the computer is still waiting for the rest of the transmission and is
// therefore out of sync. The keyboard will then attempt to restore sync by
// going into "resync mode." In this mode, the keyboard clocks out a 1 and
// waits for a handshake pulse. If none arrives within 143 ms, it clocks out
// another 1 and waits again.
//
// The keyboard Hard Resets the Amiga by pulling KCLK low and starting a 500
// millisecond timer. When one or more of the keys is released and 500
// milliseconds have passed, the keyboard will release KCLK.
//
// The usual sequence of events will therefore be: power-up; synchronize;
// transmit "initiate power-up key stream" ($FD); transmit "terminate key
// stream" ($FE).
using namespace Amiga;
Keyboard::Keyboard(Serial::Line<true> &output) : output_(output) {
output_.set_writer_clock_rate(HalfCycles(1'000'000)); // Use µs.
}
/*uint8_t Keyboard::update(uint8_t input) {
// If a bit transmission is ongoing, continue that, up to and including
// the handshake. If no handshake comes, set a macro state of synchronising.
switch(shift_state_) {
case ShiftState::Shifting:
// The keyboard processor sets the KDAT line about 20 microseconds before it
// pulls KCLK low. KCLK stays low for about 20 microseconds, then goes high
// again. The processor waits another 20 microseconds before changing KDAT.
switch(bit_phase_) {
default: break;
case 0: lines_ = Lines::Clock | (shift_sequence_ & 1); break;
case 20: lines_ = (shift_sequence_ & 1); break;
case 40: lines_ = Lines::Clock | (shift_sequence_ & 1); break;
}
bit_phase_ = (bit_phase_ + 1) % 60;
if(!bit_phase_) {
--bits_remaining_;
shift_sequence_ >>= 1;
if(!bits_remaining_) {
shift_state_ = ShiftState::AwaitingHandshake;
}
}
return lines_;
case ShiftState::AwaitingHandshake:
if(!(input & Lines::Data)) {
shift_state_ = ShiftState::Idle;
}
++bit_phase_;
if(bit_phase_ == 143) {
// shift_state_ = ShiftState::Synchronising;
}
return lines_;
default: break;
}
switch(state_) {
case State::Startup:
bit_phase_ = 0;
shift_sequence_ = 0xff;
shift_state_ = ShiftState::Shifting;
break;
}
return lines_;
}*/
void Keyboard::set_key_state(uint16_t key, bool is_pressed) {
if(pressed_[key] == is_pressed) {
return;
}
pressed_[key] = is_pressed;
output_.write<false>(
HalfCycles(60),
uint8_t(((key << 1) | (is_pressed ? 0 : 1)) ^ 0xff)
);
}
void Keyboard::clear_all_keys() {
for(uint16_t c = 0; c < uint16_t(pressed_.size()); c++) {
if(pressed_[c]) set_key_state(c, false);
}
}
// MARK: - KeyboardMapper.
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest)
#define DIRECTBIND(source) BIND(source, source)
switch(key) {
default: break;
DIRECTBIND(Escape);
DIRECTBIND(Delete);
DIRECTBIND(F1); DIRECTBIND(F2); DIRECTBIND(F3); DIRECTBIND(F4); DIRECTBIND(F5);
DIRECTBIND(F6); DIRECTBIND(F7); DIRECTBIND(F8); DIRECTBIND(F9); DIRECTBIND(F10);
BIND(BackTick, Tilde);
DIRECTBIND(k1); DIRECTBIND(k2); DIRECTBIND(k3); DIRECTBIND(k4); DIRECTBIND(k5);
DIRECTBIND(k6); DIRECTBIND(k7); DIRECTBIND(k8); DIRECTBIND(k9); DIRECTBIND(k0);
DIRECTBIND(Hyphen);
DIRECTBIND(Equals);
DIRECTBIND(Backslash);
DIRECTBIND(Backspace);
DIRECTBIND(Tab);
DIRECTBIND(CapsLock);
BIND(LeftControl, Control);
BIND(RightControl, Control);
DIRECTBIND(LeftShift);
DIRECTBIND(RightShift);
BIND(LeftOption, Alt);
BIND(RightOption, Alt);
BIND(LeftMeta, LeftAmiga);
BIND(RightMeta, RightAmiga);
DIRECTBIND(Q); DIRECTBIND(W); DIRECTBIND(E); DIRECTBIND(R); DIRECTBIND(T);
DIRECTBIND(Y); DIRECTBIND(U); DIRECTBIND(I); DIRECTBIND(O); DIRECTBIND(P);
DIRECTBIND(A); DIRECTBIND(S); DIRECTBIND(D); DIRECTBIND(F); DIRECTBIND(G);
DIRECTBIND(H); DIRECTBIND(J); DIRECTBIND(K); DIRECTBIND(L); DIRECTBIND(Z);
DIRECTBIND(X); DIRECTBIND(C); DIRECTBIND(V); DIRECTBIND(B); DIRECTBIND(N);
DIRECTBIND(M);
DIRECTBIND(OpenSquareBracket);
DIRECTBIND(CloseSquareBracket);
DIRECTBIND(Help);
BIND(Insert, Help);
BIND(Home, Help);
BIND(End, Help);
BIND(Enter, Return);
DIRECTBIND(Semicolon);
DIRECTBIND(Quote);
DIRECTBIND(Comma);
DIRECTBIND(FullStop);
DIRECTBIND(ForwardSlash);
DIRECTBIND(Space);
DIRECTBIND(Up);
DIRECTBIND(Down);
DIRECTBIND(Left);
DIRECTBIND(Right);
DIRECTBIND(Keypad0); DIRECTBIND(Keypad1); DIRECTBIND(Keypad2);
DIRECTBIND(Keypad3); DIRECTBIND(Keypad4); DIRECTBIND(Keypad5);
DIRECTBIND(Keypad6); DIRECTBIND(Keypad7); DIRECTBIND(Keypad8);
DIRECTBIND(Keypad9);
DIRECTBIND(KeypadDecimalPoint);
DIRECTBIND(KeypadMinus);
DIRECTBIND(KeypadEnter);
}
#undef DIRECTBIND
#undef BIND
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
}

121
Machines/Amiga/Keyboard.hpp Normal file
View File

@ -0,0 +1,121 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Machines_Amiga_Keyboard_hpp
#define Machines_Amiga_Keyboard_hpp
#include <array>
#include <cstdint>
#include "../KeyboardMachine.hpp"
#include "../../Components/Serial/Line.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Amiga {
enum class Key: uint16_t {
Escape = 0x45,
Delete = 0x46,
F1 = 0x50, F2 = 0x51, F3 = 0x52, F4 = 0x53, F5 = 0x54,
F6 = 0x55, F7 = 0x56, F8 = 0x57, F9 = 0x58, F10 = 0x59,
Tilde = 0x00,
k1 = 0x01, k2 = 0x02, k3 = 0x03, k4 = 0x04, k5 = 0x05,
k6 = 0x06, k7 = 0x07, k8 = 0x08, k9 = 0x09, k0 = 0x0a,
Hyphen = 0x0b,
Equals = 0x0c,
Backslash = 0x0d,
Backspace = 0x41,
Tab = 0x42,
Control = 0x63,
CapsLock = 0x62,
LeftShift = 0x60,
RightShift = 0x61,
Q = 0x10, W = 0x11, E = 0x12, R = 0x13, T = 0x14,
Y = 0x15, U = 0x16, I = 0x17, O = 0x18, P = 0x19,
A = 0x20, S = 0x21, D = 0x22, F = 0x23, G = 0x24,
H = 0x25, J = 0x26, K = 0x27, L = 0x28, Z = 0x31,
X = 0x32, C = 0x33, V = 0x34, B = 0x35, N = 0x36,
M = 0x37,
OpenSquareBracket = 0x1a,
CloseSquareBracket = 0x1b,
Help = 0x5f,
Return = 0x44,
Semicolon = 0x29,
Quote = 0x2a,
Comma = 0x38,
FullStop = 0x39,
ForwardSlash = 0x3a,
Alt = 0x64,
LeftAmiga = 0x66,
RightAmiga = 0x67,
Space = 0x40,
Up = 0x4c, Left = 0x4f, Right = 0x4e, Down = 0x4d,
Keypad7 = 0x3d, Keypad8 = 0x3e, Keypad9 = 0x3f,
Keypad4 = 0x2d, Keypad5 = 0x2e, Keypad6 = 0x2f,
Keypad1 = 0x1d, Keypad2 = 0x1e, Keypad3 = 0x1f,
Keypad0 = 0x0f, KeypadDecimalPoint = 0x3c,
KeypadMinus = 0x4a, KeypadEnter = 0x43,
KeypadOpenBracket = 0x5a,
KeypadCloseBracket = 0x5b,
KeypadDivide = 0x5c,
KeypadMultiply = 0x5d,
KeypadPlus = 0x5e,
};
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};
class Keyboard {
public:
Keyboard(Serial::Line<true> &output);
// enum Lines: uint8_t {
// Data = (1 << 0),
// Clock = (1 << 1),
// };
//
// uint8_t update(uint8_t);
void set_key_state(uint16_t, bool);
void clear_all_keys();
void run_for(HalfCycles duration) {
output_.advance_writer(duration);
}
private:
enum class ShiftState {
Shifting,
AwaitingHandshake,
Idle,
} shift_state_ = ShiftState::Idle;
enum class State {
Startup,
} state_ = State::Startup;
int bit_phase_ = 0;
uint32_t shift_sequence_ = 0;
int bits_remaining_ = 0;
uint8_t lines_ = 0;
Serial::Line<true> &output_;
std::array<bool, 128> pressed_{};
};
}
#endif /* Machines_Amiga_Keyboard_hpp */

View File

@ -0,0 +1,85 @@
//
// MemoryMap.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef MemoryMap_hpp
#define MemoryMap_hpp
namespace Amiga {
class MemoryMap {
private:
static constexpr auto PermitRead = CPU::MC68000::Microcycle::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000::Microcycle::PermitWrite;
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
public:
// TODO: decide what of the below I want to be dynamic.
std::array<uint8_t, 1024*1024> chip_ram{};
std::array<uint8_t, 512*1024> kickstart{0xff};
struct MemoryRegion {
uint8_t *contents = nullptr;
unsigned int read_write_mask = 0;
} regions[64]; // i.e. top six bits are used as an index.
MemoryMap() {
// Address spaces that matter:
//
// 00'0000 08'0000: chip RAM. [or overlayed KickStart]
// 10'0000: extended chip ram for ECS.
// 20'0000: slow RAM and further chip RAM.
// a0'0000: auto-config space (/fast RAM).
// ...
// bf'd000 c0'0000: 8250s.
// c0'0000 d8'0000: pseudo-fast RAM.
// ...
// dc'0000 dd'0000: optional real-time clock.
// df'f000 - e0'0000: custom chip registers.
// ...
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
// fc'0000 : 256kb Kickstart otherwise.
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
reset();
}
void reset() {
set_overlay(true);
}
void set_overlay(bool enabled) {
if(overlay_ == enabled) {
return;
}
overlay_ = enabled;
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
if(enabled) {
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
}
}
private:
bool overlay_ = false;
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
assert(!(start & precision_loss_mask));
assert(!((end - (1 << 18)) & precision_loss_mask));
assert(end > start);
if(base) base -= start;
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
regions[c].contents = base;
regions[c].read_write_mask = read_write_mask;
}
}
};
}
#endif /* MemoryMap_hpp */

387
Machines/Amiga/Minterms.hpp Normal file
View File

@ -0,0 +1,387 @@
//
// Minterms.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Minterms_hpp
#define Minterms_hpp
namespace Amiga {
/// @returns the result of applying the Amiga-format @c minterm to inputs @c a, @c b and @c c.
template <typename IntT> IntT apply_minterm(IntT a, IntT b, IntT c, int minterm) {
// Quick implementation notes:
//
// This was created very lazily. I tried to enter as many logical combinations of
// a, b and c as I could think of and had a test program match them up to the
// Amiga minterm IDs, prioritising by simplicity.
//
// That got me most of the way; starting from the point indicated below I had run
// out of good ideas and automatically generated the rest.
//
switch(minterm) {
default:
case 0x00: return IntT(0);
case 0xff: return IntT(~0);
case 0xf0: return a;
case 0xcc: return b;
case 0xaa: return c;
case 0x0f: return ~a;
case 0x33: return ~b;
case 0x55: return ~c;
case 0xfc: return a | b;
case 0xfa: return a | c;
case 0xee: return b | c;
case 0xfe: return a | b | c;
case 0xf3: return a | ~b;
case 0xf5: return a | ~c;
case 0xdd: return b | ~c;
case 0xfd: return a | b | ~c;
case 0xfb: return a | ~b | c;
case 0xf7: return a | ~b | ~c;
case 0xcf: return ~a | b;
case 0xaf: return ~a | c;
case 0xbb: return ~b | c;
case 0xef: return ~a | b | c;
case 0xdf: return ~a | b | ~c;
case 0x7f: return ~a | ~b | ~c;
case 0x3c: return a ^ b;
case 0x5a: return a ^ c;
case 0x66: return b ^ c;
case 0x96: return a ^ b ^ c;
case 0xc3: return ~a ^ b;
case 0xa5: return ~a ^ c;
case 0x99: return ~b ^ c;
case 0x69: return ~a ^ b ^ c;
case 0xc0: return a & b;
case 0xa0: return a & c;
case 0x88: return b & c;
case 0x80: return a & b & c;
case 0x30: return a & ~b;
case 0x50: return a & ~c;
case 0x44: return b & ~c;
case 0x0c: return ~a & b;
case 0x0a: return ~a & c;
case 0x22: return ~b & c;
case 0x40: return a & b & ~c;
case 0x20: return a & ~b & c;
case 0x08: return ~a & b & c;
case 0x10: return a & ~b & ~c;
case 0x04: return ~a & b & ~c;
case 0x02: return ~a & ~b & c;
case 0x03: return ~a & ~b;
case 0x05: return ~a & ~c;
case 0x11: return ~b & ~c;
case 0x01: return ~a & ~b & ~c;
case 0x70: return a & ~(b & c);
case 0x4c: return b & ~(a & c);
case 0x2a: return c & ~(a & b);
case 0x07: return ~a & ~(b & c);
case 0x13: return ~b & ~(a & c);
case 0x15: return ~c & ~(a & b);
case 0xe0: return a & (b | c);
case 0xc8: return b & (a | c);
case 0xa8: return c & (a | b);
case 0x0e: return ~a & (b | c);
case 0x32: return ~b & (a | c);
case 0x54: return ~c & (a | b);
case 0x60: return a & (b ^ c);
case 0x48: return b & (a ^ c);
case 0x28: return c & (a ^ b);
case 0x06: return ~a & (b ^ c);
case 0x12: return ~b & (a ^ c);
case 0x14: return ~c & (a ^ b);
case 0x90: return a & ~(b ^ c);
case 0x84: return b & ~(a ^ c);
case 0x82: return c & ~(a ^ b);
case 0x09: return ~a & ~(b ^ c);
case 0x21: return ~b & ~(a ^ c);
case 0x41: return ~c & ~(a ^ b);
case 0xb0: return a & (~b | c);
case 0xd0: return a & (b | ~c);
case 0x0b: return ~a & (~b | c);
case 0x0d: return ~a & (b | ~c);
case 0xf6: return a | (b ^ c);
case 0xde: return b | (a ^ c);
case 0xbe: return c | (a ^ b);
case 0x6f: return ~a | (b ^ c);
case 0x7b: return ~b | (a ^ c);
case 0x7d: return ~c | (a ^ b);
case 0x9f: return ~a | ~(b ^ c);
case 0xb7: return ~b | ~(a ^ c);
case 0xd7: return ~c | ~(a ^ b);
case 0xf8: return a | (b & c);
case 0xec: return b | (a & c);
case 0xea: return c | (a & b);
case 0x8f: return ~a | (b & c);
case 0xb3: return ~b | (a & c);
case 0xd5: return ~c | (a & b);
case 0xf1: return a | ~(b | c);
case 0xcd: return b | ~(a | c);
case 0xab: return c | ~(a | b);
case 0x1f: return ~a | ~(b | c);
case 0x37: return ~b | ~(a | c);
case 0x57: return ~c | ~(a | b);
case 0x8c: return b & (~a | c);
case 0x8a: return c & (~a | b);
case 0xc4: return b & (a | ~c);
case 0xa2: return c & (a | ~b);
case 0x78: return a ^ (b & c);
case 0x6c: return b ^ (a & c);
case 0x6a: return c ^ (a & b);
case 0x87: return ~a ^ (b & c);
case 0x93: return ~b ^ (a & c);
case 0x95: return ~c ^ (a & b);
case 0x1e: return a ^ (b | c);
case 0x36: return b ^ (a | c);
case 0x56: return c ^ (a | b);
case 0x2d: return a ^ (b | ~c);
case 0x4b: return a ^ (~b | c);
case 0xe1: return a ^ ~(b | c);
case 0x39: return b ^ (a | ~c);
case 0x63: return b ^ (~a | c);
case 0xc9: return b ^ ~(a | c);
case 0x59: return c ^ (a | ~b);
case 0x65: return c ^ (~a | b);
case 0xa9: return c ^ ~(a | b);
case 0x24: return (a ^ b) & (b ^ c);
case 0x18: return (a ^ b) & (a ^ c);
case 0x42: return (a ^ c) & (b ^ c);
case 0xa6: return (a & b) ^ (b ^ c);
case 0xc6: return (a & c) ^ (b ^ c);
case 0x5c: return (a | b) ^ (a & c);
case 0x74: return (a | b) ^ (b & c);
case 0x72: return (a | c) ^ (b & c);
case 0x4e: return (b | c) ^ (a & c);
case 0x58: return (a | b) & (a ^ c);
case 0x62: return (a | c) & (b ^ c);
case 0x7e: return (a ^ b) | (a ^ c);
case 0xca: return (a & b) | (~a & c);
case 0xac: return (~a & b) | (a & c);
case 0xa3: return (~a & ~b) | (a & c);
case 0xf4: return a | ((a ^ b) & (b ^ c));
case 0xf2: return a | ((a ^ c) & (b ^ c));
case 0xdc: return b | ((a ^ b) & (a ^ c));
case 0xce: return b | ((a ^ c) & (b ^ c));
case 0xae: return c | ((a ^ b) & (b ^ c));
case 0xba: return c | ((a ^ b) & (a ^ c));
case 0x2f: return ~a | ((a ^ b) & (b ^ c));
case 0x4f: return ~a | ((a ^ c) & (b ^ c));
case 0x3b: return ~b | ((a ^ b) & (a ^ c));
case 0x73: return ~b | ((a ^ c) & (b ^ c));
case 0x75: return ~c | ((a ^ b) & (b ^ c));
case 0x5d: return ~c | ((a ^ b) & (a ^ c));
case 0x3f: return ~a | ~b | ((a ^ b) & (b ^ c));
case 0x77: return ~b | ~c | ((a ^ b) & (b ^ c));
case 0x27: return ~(a | b) | ((a ^ b) & (b ^ c));
case 0x47: return ~(a | c) | ((a ^ c) & (b ^ c));
case 0x53: return ~(b | c) | ((a ^ c) & (b ^ c));
case 0x43: return ~(a | b | c) | ((a ^ c) & (b ^ c));
case 0x7a: return (a & ~b) | (a ^ c);
case 0x76: return (a & ~b) | (b ^ c);
case 0x7c: return (a & ~c) | (a ^ b);
case 0x5e: return (~a & b) | (a ^ c);
case 0x6e: return (~a & b) | (b ^ c);
case 0x3e: return (~a & c) | (a ^ b);
case 0xad: return (~a & b) | ~(a ^ c);
case 0xb5: return (a & ~b) | ~(a ^ c);
case 0xcb: return (~a & c) | ~(a ^ b);
case 0xd3: return (a & ~c) | ~(a ^ b);
case 0x9b: return (~a & c) | ~(b ^ c);
case 0xd9: return (a & ~c) | ~(b ^ c);
case 0x9d: return (~a & b) | ~(b ^ c);
case 0xb9: return (a & ~b) | ~(b ^ c);
case 0x9e: return (~a & b) | (a ^ b ^ c);
case 0xb6: return (a & ~b) | (a ^ b ^ c);
case 0xd6: return (a & ~c) | (a ^ b ^ c);
case 0xbf: return ~(a & b) | (a ^ b ^ c);
case 0x6d: return (~a & b) | ~(a ^ b ^ c);
case 0x79: return (a & ~b) | ~(a ^ b ^ c);
case 0x6b: return (~a & c) | ~(a ^ b ^ c);
case 0xe9: return (b & c) | ~(a ^ b ^ c);
case 0xb8: return (a & ~b) | (c & b);
case 0xd8: return (a & ~c) | (b & c);
case 0xe4: return (b & ~c) | (a & c);
case 0xe2: return (c & ~b) | (a & b);
case 0x2c: return (~a & b) | ((a ^ b) & (b ^ c));
case 0x34: return (a & ~b) | ((a ^ b) & (b ^ c));
case 0x4a: return (~a & c) | ((a ^ c) & (b ^ c));
case 0x52: return (a & ~c) | ((a ^ c) & (b ^ c));
case 0x5f: return ~(a & c) | ((a ^ c) & (b ^ c));
case 0x16: return (a & ~(c | b)) | (c & ~(b | a)) | (b & ~(a | c));
case 0x81: return (a ^ ~(c | b)) & (c ^ ~(b | a)) & (b ^ ~(a | c));
case 0x2e: return (~a & (b | c)) | (~b & c);
case 0x3a: return (~b & (a | c)) | (~a & c);
case 0x8b: return (~a & ~b) | (c & b);
case 0x8d: return (~a & ~c) | (b & c);
case 0xb1: return (~b & ~c) | (a & c);
case 0xd1: return (~c & ~b) | (a & b);
case 0x98: return (a & ~(c | b)) | (b & c);
case 0x8e: return (~a & (c | b)) | (b & c);
case 0x46: return (~a | b) & (b ^ c);
case 0xe6: return ((~a | b) & (b ^ c)) ^ (a & c);
case 0xc2: return ((a | ~b) & (b ^ c)) ^ (a & c);
case 0x85: return (~a | b) & ~(a ^ c);
case 0x83: return (~a | c) & ~(a ^ b);
case 0x89: return (~a | c) & ~(b ^ c);
case 0xa1: return (a | ~b) & ~(a ^ c);
case 0x91: return (a | ~b) & ~(b ^ c);
case 0xc1: return (a | ~c) & ~(a ^ b);
case 0x94: return (a | b) & (a ^ b ^ c);
case 0x86: return (b | c) & (a ^ b ^ c);
case 0x92: return (a | c) & (a ^ b ^ c);
case 0x68: return (a | b) & ~(a ^ b ^ c);
case 0x61: return (a | ~b) & ~(a ^ b ^ c);
case 0x49: return (~a | b) & ~(a ^ b ^ c);
case 0x29: return (~a | c) & ~(a ^ b ^ c);
case 0x64: return (a & ~b & c) | (b & ~c);
//
// From here downwards functions were found automatically.
// Neater versions likely exist of many of the functions below.
//
case 0xe8: return (a & b) | ((b | a) & c);
case 0xd4: return (a & b) | ((b | a) & ~c);
case 0xb2: return (a & ~b) | ((~b | a) & c);
case 0x17: return (~a & ~b) | ((~b | ~a) & ~c);
case 0x1b: return (~a & ~b) | (~b & ~c) | (~a & c);
case 0x1d: return (~a & b) | ((~b | ~a) & ~c);
case 0x2b: return (~a & ~b) | ((~b | ~a) & c);
case 0x35: return (a & ~b) | ((~b | ~a) & ~c);
case 0x4d: return (~a & b) | ((b | ~a) & ~c);
case 0x71: return (a & ~b) | ((~b | a) & ~c);
case 0xbd: return (~a & b) | (~b & ~c) | (a & c);
case 0xc5: return (a & b) | ((b | ~a) & ~c);
case 0xdb: return (a & b) | (~b & ~c) | (~a & c);
case 0xe7: return (~a & ~b) | (b & ~c) | (a & c);
case 0x1c: return (~a & b) | (a & ~b & ~c);
case 0x23: return (~a & ~b) | (a & ~b & c);
case 0x31: return (a & ~b) | (~a & ~b & ~c);
case 0x38: return (a & ~b) | (~a & b & c);
case 0x1a: return (~a & c) | (a & ~b & ~c);
case 0x25: return (~a & ~c) | (a & ~b & c);
case 0x45: return (~a & ~c) | (a & b & ~c);
case 0x51: return (a & ~c) | (~a & ~b & ~c);
case 0xa4: return (a & c) | (~a & b & ~c);
case 0x19: return (~b & ~c) | (~a & b & c);
case 0x26: return (~b & c) | (~a & b & ~c);
case 0xc7: return (a & b) | (~a & (~b | ~c));
case 0x3d: return (a & ~b) | (~a & (b | ~c));
case 0xbc: return (~a & b) | (a & (~b | c));
case 0xe3: return (~a & ~b) | (a & (b | c));
case 0xa7: return (a & c) | (~a & (~b | ~c));
case 0x5b: return (a & ~c) | (~a & (~b | c));
case 0xda: return (~a & c) | (a & (b | ~c));
case 0xe5: return (~a & ~c) | (a & (b | c));
case 0x67: return (~a & ~b) | ((~a | b) & ~c) | (~b & c);
case 0x97: return (~a & ~b) | ((~a | ~b) & ~c) | (a & b & c);
case 0xb4: return (a & ~b) | (a & c) | (~a & b & ~c);
case 0x9c: return (~a & b) | (b & c) | (a & ~b & ~c);
case 0xd2: return ((~c | b) & a) | (~a & ~b & c);
case 0x9a: return ((~a | b) & c) | (a & ~b & ~c);
case 0xf9: return a | (~b & ~c) | (b & c);
case 0xed: return b | (~a & ~c) | (a & c);
case 0xeb: return c | (~a & ~b) | (a & b);
}
// Should be unreachable.
return 0;
}
}
#endif /* Minterms_hpp */

View File

@ -0,0 +1,124 @@
//
// MouseJoystick.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "MouseJoystick.hpp"
#include <algorithm>
using namespace Amiga;
// MARK: - Mouse.
int Mouse::get_number_of_buttons() {
return 2;
}
void Mouse::set_button_pressed(int button, bool is_set) {
switch(button) {
case 0:
cia_state_ = (cia_state_ &~ 0x40) | (is_set ? 0 : 0x40);
break;
default:
break;
}
}
uint8_t Mouse::get_cia_button() const {
return cia_state_;
}
void Mouse::reset_all_buttons() {
cia_state_ = 0xff;
}
void Mouse::move(int x, int y) {
position_[0] += x;
position_[1] += y;
}
uint16_t Mouse::get_position() {
// The Amiga hardware retains only eight bits of position
// for the mouse; its software polls frequently and maps
// changes into a larger space.
//
// On modern computers with 5k+ displays and trackpads, it
// proved empirically possible to overflow the hardware
// counters more quickly than software would poll.
//
// Therefore the approach taken for mapping mouse motion
// into the Amiga is to do it in steps of no greater than
// [-128, +127], as per the below.
const int pending[] = {
position_[0], position_[1]
};
const int8_t change[] = {
int8_t(std::clamp(pending[0], -128, 127)),
int8_t(std::clamp(pending[1], -128, 127))
};
position_[0] -= change[0];
position_[1] -= change[1];
declared_position_[0] += change[0];
declared_position_[1] += change[1];
return uint16_t(
(declared_position_[1] << 8) |
declared_position_[0]
);
}
// MARK: - Joystick.
// TODO: add second fire button.
Joystick::Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire, 0),
}) {}
void Joystick::did_set_input(const Input &input, bool is_active) {
// Accumulate state.
inputs_[input.type] = is_active;
// Determine what that does to the two position bits.
const auto low =
(inputs_[Input::Type::Down] ^ inputs_[Input::Type::Right]) |
(inputs_[Input::Type::Right] << 1);
const auto high =
(inputs_[Input::Type::Up] ^ inputs_[Input::Type::Left]) |
(inputs_[Input::Type::Left] << 1);
// Ripple upwards if that affects the mouse position counters.
const uint8_t previous_low = position_ & 3;
uint8_t low_upper = (position_ >> 2) & 0x3f;
const uint8_t previous_high = (position_ >> 8) & 3;
uint8_t high_upper = (position_ >> 10) & 0x3f;
if(!low && previous_low == 3) ++low_upper;
if(!previous_low && low == 3) --low_upper;
if(!high && previous_high == 3) ++high_upper;
if(!previous_high && high == 3) --high_upper;
position_ = uint16_t(
low | ((low_upper & 0x3f) << 2) |
(high << 8) | ((high_upper & 0x3f) << 10)
);
}
uint16_t Joystick::get_position() {
return position_;
}
uint8_t Joystick::get_cia_button() const {
return inputs_[Input::Type::Fire] ? 0xbf : 0xff;
}

View File

@ -0,0 +1,57 @@
//
// MouseJoystick.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef MouseJoystick_hpp
#define MouseJoystick_hpp
#include <array>
#include <atomic>
#include "../../Inputs/Joystick.hpp"
#include "../../Inputs/Mouse.hpp"
namespace Amiga {
struct MouseJoystickInput {
virtual uint16_t get_position() = 0;
virtual uint8_t get_cia_button() const = 0;
};
class Mouse: public Inputs::Mouse, public MouseJoystickInput {
public:
uint16_t get_position() final;
uint8_t get_cia_button() const final;
private:
int get_number_of_buttons() final;
void set_button_pressed(int, bool) final;
void reset_all_buttons() final;
void move(int, int) final;
uint8_t declared_position_[2]{};
uint8_t cia_state_ = 0xff;
std::array<std::atomic<int>, 2> position_{};
};
class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput {
public:
Joystick();
uint16_t get_position() final;
uint8_t get_cia_button() const final;
private:
void did_set_input(const Input &input, bool is_active) final;
bool inputs_[Joystick::Input::Type::Max]{};
uint16_t position_ = 0;
};
}
#endif /* MouseJoystick_hpp */

115
Machines/Amiga/Sprites.cpp Normal file
View File

@ -0,0 +1,115 @@
//
// Sprites.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Sprites.hpp"
using namespace Amiga;
namespace {
/// Expands @c source from b15 ... b0 to 000b15 ... 000b0.
constexpr uint64_t expand_sprite_word(uint16_t source) {
uint64_t result = source;
result = (result | (result << 24)) & 0x0000'00ff'0000'00ff;
result = (result | (result << 12)) & 0x000f'000f'000f'000f;
result = (result | (result << 6)) & 0x0303'0303'0303'0303;
result = (result | (result << 3)) & 0x1111'1111'1111'1111;
return result;
}
// A very small selection of test cases.
static_assert(expand_sprite_word(0xffff) == 0x11'11'11'11'11'11'11'11);
static_assert(expand_sprite_word(0x5555) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_sprite_word(0xaaaa) == 0x10'10'10'10'10'10'10'10);
static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);
}
// MARK: - Sprites.
void Sprite::set_start_position(uint16_t value) {
v_start_ = (v_start_ & 0xff00) | (value >> 8);
h_start = uint16_t((h_start & 0x0001) | ((value & 0xff) << 1));
}
void Sprite::set_stop_and_control(uint16_t value) {
h_start = uint16_t((h_start & 0x01fe) | (value & 0x01));
v_stop_ = uint16_t((value >> 8) | ((value & 0x02) << 7));
v_start_ = uint16_t((v_start_ & 0x00ff) | ((value & 0x04) << 6));
attached = value & 0x80;
// Disarm the sprite, but expect graphics next from DMA.
visible = false;
dma_state_ = DMAState::FetchImage;
}
void Sprite::set_image_data(int slot, uint16_t value) {
data[slot] = value;
visible |= slot == 0;
}
void Sprite::advance_line(int y, bool is_end_of_blank) {
if(dma_state_ == DMAState::FetchImage && y == v_start_) {
visible = true;
}
if(is_end_of_blank || y == v_stop_) {
dma_state_ = DMAState::FetchControl;
visible = true;
}
}
bool Sprite::advance_dma(int offset) {
if(!visible) return false;
// Fetch another word.
const uint16_t next_word = ram_[pointer_[0] & ram_mask_];
++pointer_[0];
// Put the fetched word somewhere appropriate and update the DMA state.
switch(dma_state_) {
// i.e. stopped.
default: return false;
case DMAState::FetchControl:
if(offset) {
set_stop_and_control(next_word);
} else {
set_start_position(next_word);
}
return true;
case DMAState::FetchImage:
set_image_data(1 - bool(offset), next_word);
return true;
}
return false;
}
template <int sprite> void TwoSpriteShifter::load(
uint16_t lsb,
uint16_t msb,
int delay) {
constexpr int sprite_shift = sprite << 1;
const int delay_shift = delay << 2;
// Clear out any current sprite pixels; this is a reload.
data_ &= 0xcccc'cccc'cccc'ccccull >> (sprite_shift + delay_shift);
// Map LSB and MSB up to 64-bits and load into the shifter.
const uint64_t new_data =
(
expand_sprite_word(lsb) |
(expand_sprite_word(msb) << 1)
) << sprite_shift;
data_ |= new_data >> delay_shift;
overflow_ |= uint8_t((new_data << 8) >> delay_shift);
}
template void TwoSpriteShifter::load<0>(uint16_t, uint16_t, int);
template void TwoSpriteShifter::load<1>(uint16_t, uint16_t, int);

View File

@ -0,0 +1,76 @@
//
// Sprites.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Sprites_hpp
#define Sprites_hpp
#include <cstdint>
#include "DMADevice.hpp"
namespace Amiga {
class Sprite: public DMADevice<1> {
public:
using DMADevice::DMADevice;
void set_start_position(uint16_t value);
void set_stop_and_control(uint16_t value);
void set_image_data(int slot, uint16_t value);
void advance_line(int y, bool is_end_of_blank);
bool advance_dma(int offset);
uint16_t data[2]{};
bool attached = false;
bool visible = false;
uint16_t h_start = 0;
private:
uint16_t v_start_ = 0, v_stop_ = 0;
enum class DMAState {
FetchControl,
FetchImage
} dma_state_ = DMAState::FetchControl;
};
class TwoSpriteShifter {
public:
/// Installs new pixel data for @c sprite (either 0 or 1),
/// with @c delay being either 0 or 1 to indicate whether
/// output should begin now or in one pixel's time.
template <int sprite> void load(
uint16_t lsb,
uint16_t msb,
int delay);
/// Shifts two pixels.
void shift() {
data_ <<= 8;
data_ |= overflow_;
overflow_ = 0;
}
/// @returns The next two pixels to output, formulated as
/// abcd efgh where ab and ef are two pixels of the first sprite
/// and cd and gh are two pixels of the second. In each case the
/// more significant two are output first.
uint8_t get() {
return uint8_t(data_ >> 56);
}
private:
uint64_t data_;
uint8_t overflow_;
};
}
#endif /* Sprites_hpp */

View File

@ -8,6 +8,8 @@
#include "Mouse.hpp"
#include <algorithm>
using namespace Apple::ADB;
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
@ -20,8 +22,8 @@ void Mouse::perform_command(const Command &command) {
const int buttons = button_flags_;
// Clamp deltas.
delta_x = std::max(std::min(delta_x, int16_t(127)), int16_t(-128));
delta_y = std::max(std::min(delta_y, int16_t(127)), int16_t(-128));
delta_x = std::clamp(delta_x, int16_t(-128), int16_t(127));
delta_y = std::clamp(delta_y, int16_t(-128), int16_t(127));
// Figure out what that would look like, and don't respond if there's
// no change to report.

View File

@ -21,19 +21,19 @@ namespace {
pulse widths from the values stored into the PWM buffer.
*/
template<uint8_t value> constexpr uint8_t lfsr() {
if constexpr (value == 0x20 || !value) return 0;
return 1+lfsr<(((value ^ (value >> 1))&1) << 5) | (value >> 1)>();
if constexpr (value == 0x20 || !value) return 0;
return 1+lfsr<(((value ^ (value >> 1))&1) << 5) | (value >> 1)>();
}
constexpr uint8_t pwm_lookup[] = {
lfsr<0>(), lfsr<1>(), lfsr<2>(), lfsr<3>(), lfsr<4>(), lfsr<5>(), lfsr<6>(), lfsr<7>(),
lfsr<8>(), lfsr<9>(), lfsr<10>(), lfsr<11>(), lfsr<12>(), lfsr<13>(), lfsr<14>(), lfsr<15>(),
lfsr<16>(), lfsr<17>(), lfsr<18>(), lfsr<19>(), lfsr<20>(), lfsr<21>(), lfsr<22>(), lfsr<23>(),
lfsr<24>(), lfsr<25>(), lfsr<26>(), lfsr<27>(), lfsr<28>(), lfsr<29>(), lfsr<30>(), lfsr<31>(),
lfsr<32>(), lfsr<33>(), lfsr<34>(), lfsr<35>(), lfsr<36>(), lfsr<37>(), lfsr<38>(), lfsr<39>(),
lfsr<40>(), lfsr<41>(), lfsr<42>(), lfsr<43>(), lfsr<44>(), lfsr<45>(), lfsr<46>(), lfsr<47>(),
lfsr<48>(), lfsr<49>(), lfsr<50>(), lfsr<51>(), lfsr<52>(), lfsr<53>(), lfsr<54>(), lfsr<55>(),
lfsr<56>(), lfsr<57>(), lfsr<58>(), lfsr<59>(), lfsr<60>(), lfsr<61>(), lfsr<62>(), lfsr<63>(),
lfsr<0>(), lfsr<1>(), lfsr<2>(), lfsr<3>(), lfsr<4>(), lfsr<5>(), lfsr<6>(), lfsr<7>(),
lfsr<8>(), lfsr<9>(), lfsr<10>(), lfsr<11>(), lfsr<12>(), lfsr<13>(), lfsr<14>(), lfsr<15>(),
lfsr<16>(), lfsr<17>(), lfsr<18>(), lfsr<19>(), lfsr<20>(), lfsr<21>(), lfsr<22>(), lfsr<23>(),
lfsr<24>(), lfsr<25>(), lfsr<26>(), lfsr<27>(), lfsr<28>(), lfsr<29>(), lfsr<30>(), lfsr<31>(),
lfsr<32>(), lfsr<33>(), lfsr<34>(), lfsr<35>(), lfsr<36>(), lfsr<37>(), lfsr<38>(), lfsr<39>(),
lfsr<40>(), lfsr<41>(), lfsr<42>(), lfsr<43>(), lfsr<44>(), lfsr<45>(), lfsr<46>(), lfsr<47>(),
lfsr<48>(), lfsr<49>(), lfsr<50>(), lfsr<51>(), lfsr<52>(), lfsr<53>(), lfsr<54>(), lfsr<55>(),
lfsr<56>(), lfsr<57>(), lfsr<58>(), lfsr<59>(), lfsr<60>(), lfsr<61>(), lfsr<62>(), lfsr<63>(),
};
}

View File

@ -176,7 +176,7 @@ class ConcreteMachine:
// An interrupt acknowledge, perhaps?
if(cycle.operation & Microcycle::InterruptAcknowledge) {
// Current implementation: everything other than 6 (i.e. the MFP is autovectored.
// Current implementation: everything other than 6 (i.e. the MFP) is autovectored.
const int interrupt_level = cycle.word_address()&7;
if(interrupt_level != 6) {
video_interrupts_pending_ &= ~interrupt_level;

View File

@ -15,7 +15,7 @@
using namespace Atari::ST;
IntelligentKeyboard::IntelligentKeyboard(Serial::Line &input, Serial::Line &output) : output_line_(output) {
IntelligentKeyboard::IntelligentKeyboard(Serial::Line<false> &input, Serial::Line<false> &output) : output_line_(output) {
input.set_read_delegate(this, Storage::Time(2, 15625));
output_line_.set_writer_clock_rate(15625);
@ -24,7 +24,7 @@ IntelligentKeyboard::IntelligentKeyboard(Serial::Line &input, Serial::Line &outp
joysticks_.emplace_back(new Joystick);
}
bool IntelligentKeyboard::serial_line_did_produce_bit(Serial::Line *, int bit) {
bool IntelligentKeyboard::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
// Shift.
command_ = (command_ >> 1) | (bit << 9);
@ -68,8 +68,8 @@ void IntelligentKeyboard::run_for(HalfCycles duration) {
mouse_position_[1] += mouse_y_multiplier_ * scaled_movement[1];
// Clamp to range.
mouse_position_[0] = std::min(std::max(mouse_position_[0], 0), mouse_range_[0]);
mouse_position_[1] = std::min(std::max(mouse_position_[1], 0), mouse_range_[1]);
mouse_position_[0] = std::clamp(mouse_position_[0], 0, mouse_range_[0]);
mouse_position_[1] = std::clamp(mouse_position_[1], 0, mouse_range_[1]);
mouse_movement_[0] -= scaled_movement[0] * mouse_scale_[0];
mouse_movement_[1] -= scaled_movement[1] * mouse_scale_[1];
@ -157,7 +157,7 @@ void IntelligentKeyboard::dispatch_command(uint8_t command) {
// If not, exit. If so, perform and drop out of the switch.
switch(command_sequence_.front()) {
default:
printf("Unrecognised IKBD command %02x\n", command);
LOG("Unrecognised IKBD command " << PADHEX(2) << +command);
break;
case 0x80:

View File

@ -53,11 +53,11 @@ static_assert(uint16_t(Key::KeypadEnter) == 0x72, "KeypadEnter should have key c
keyboard input and output and mouse handling.
*/
class IntelligentKeyboard:
public Serial::Line::ReadDelegate,
public Serial::Line<false>::ReadDelegate,
public ClockingHint::Source,
public Inputs::Mouse {
public:
IntelligentKeyboard(Serial::Line &input, Serial::Line &output);
IntelligentKeyboard(Serial::Line<false> &input, Serial::Line<false> &output);
ClockingHint::Preference preferred_clocking() const final;
void run_for(HalfCycles duration);
@ -78,10 +78,10 @@ class IntelligentKeyboard:
// MARK: - Serial line state.
int bit_count_ = 0;
int command_ = 0;
Serial::Line &output_line_;
Serial::Line<false> &output_line_;
void output_bytes(std::initializer_list<uint8_t> value);
bool serial_line_did_produce_bit(Serial::Line *, int bit) final;
bool serial_line_did_produce_bit(Serial::Line<false> *, int bit) final;
// MARK: - Command dispatch.
std::vector<uint8_t> command_sequence_;

View File

@ -11,6 +11,7 @@
#include <algorithm>
// Sources for runtime options and machines.
#include "../Amiga/Amiga.hpp"
#include "../AmstradCPC/AmstradCPC.hpp"
#include "../Apple/AppleII/AppleII.hpp"
#include "../Apple/AppleIIgs/AppleIIgs.hpp"
@ -29,6 +30,7 @@
// Sources for construction options.
#include "../../Analyser/Static/Acorn/Target.hpp"
#include "../../Analyser/Static/Amiga/Target.hpp"
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
#include "../../Analyser/Static/AppleII/Target.hpp"
#include "../../Analyser/Static/AppleIIgs/Target.hpp"
@ -54,6 +56,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<::name::Machine>(name::Machine::m(target, rom_fetcher)); break;
#define Bind(m) BindD(m, m)
switch(target->machine) {
Bind(Amiga)
Bind(AmstradCPC)
BindD(Apple::II, AppleII)
BindD(Apple::IIgs, AppleIIgs)
@ -123,6 +126,7 @@ Machine::DynamicMachine *Machine::MachineForTargets(const Analyser::Static::Targ
std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) {
switch(machine) {
case Analyser::Machine::Amiga: return "Amiga";
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
case Analyser::Machine::AppleII: return "AppleII";
case Analyser::Machine::AppleIIgs: return "AppleIIgs";
@ -145,6 +149,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
switch(machine) {
case Analyser::Machine::Amiga: return "Amiga";
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
case Analyser::Machine::AppleII: return "Apple II";
case Analyser::Machine::AppleIIgs: return "Apple IIgs";
@ -177,6 +182,7 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
}
if(type == Type::Any || type == Type::DoesntRequireMedia) {
AddName(Amiga);
AddName(AmstradCPC);
AddName(AppleII);
AddName(AppleIIgs);
@ -228,6 +234,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Name), new Analyser::Static::TargetNamespace::Target));
#define Add(Name) AddMapped(Name, Name)
Add(Amiga);
Add(AmstradCPC);
Add(AppleII);
Add(AppleIIgs);

View File

@ -28,8 +28,10 @@ void PackBigEndian16(const std::vector<uint8_t> &source, uint8_t *target);
/*!
Copies the bytes from @c source into @c target, interpreting them
as big-endian 16-bit data. @c target will be resized to the proper size
exactly to contain the contents of @c source.
as big-endian 16-bit data and writing them as host-endian 16-bit data.
@c target will be resized to the proper size exactly to contain the contents
of @c source.
*/
void PackBigEndian16(const std::vector<uint8_t> &source, std::vector<uint16_t> &target);

View File

@ -407,6 +407,9 @@ Description::Description(Name name) {
case Name::AmigaA500Kickstart31:
*this = Description(name, "Amiga", "the A500/A600/A2000 Kickstart 3.1 ROM", "Kickstart-v3.1-rev40.63-1993-Commodore-A500-A600-A2000.rom", 512*1024, 0xfc24ae0du);
break;
case Name::AmigaDiagROM121:
*this = Description(name, "Amiga", "DiagROM 1.2.1", "16bit.bin", 512*1024, 0xf2ac0a3b);
break;
case Name::AppleIIEnhancedE: *this = Description(name, "AppleII", "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942u); break;
case Name::AppleIIe: *this = Description(name, "AppleII", "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18du); break;

View File

@ -42,6 +42,8 @@ enum Name {
AmigaA600Kickstart205,
AmigaA500Kickstart31,
AmigaDiagROM121,
// Amstrad CPC.
AMSDOS,
CPC464Firmware, CPC464BASIC,

37
Numeric/BitSpread.hpp Normal file
View File

@ -0,0 +1,37 @@
//
// BitSpread.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/10/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef BitSpread_hpp
#define BitSpread_hpp
namespace Numeric {
/// @returns The bits of @c input with a 0 bit inserted between each and
/// keeping the least-significant bit in its original position.
///
/// i.e. if @c input is abcdefgh then the result is 0a0b0c0d0e0f0g0h
constexpr uint16_t spread_bits(uint8_t input) {
uint16_t result = uint16_t(input); // 0000 0000 abcd efgh
result = (result | (result << 4)) & 0x0f0f; // 0000 abcd 0000 efgh
result = (result | (result << 2)) & 0x3333; // 00ab 00cd 00ef 00gh
return (result | (result << 1)) & 0x5555; // 0a0b 0c0d 0e0f 0g0h
}
/// Performs the opposite action to @c spread_bits; given the 16-bit input
/// @c abcd @c efgh @c ijkl @c mnop, returns the byte value @c bdfhjlnp
/// i.e. every other bit is retained, keeping the least-significant bit in place.
constexpr uint8_t unspread_bits(uint16_t input) {
input &= 0x5555; // 0a0b 0c0d 0e0f 0g0h
input = (input | (input >> 1)) & 0x3333; // 00ab 00cd 00ef 00gh
input = (input | (input >> 2)) & 0x0f0f; // 0000 abcd 0000 efgh
return uint8_t(input | (input >> 4)); // 0000 0000 abcd efgh
}
}
#endif /* BitSpread_hpp */

View File

@ -163,6 +163,8 @@
4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */; };
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */; };
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */; };
4B1A1B1E27320FBC00119335 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1A1B1C27320FBB00119335 /* Disk.cpp */; };
4B1A1B1F27320FBC00119335 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1A1B1C27320FBB00119335 /* Disk.cpp */; };
4B1B58F6246CC4E8009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; };
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58F4246CC4E8009C171E /* State.cpp */; };
4B1B58FF246E19FD009C171E /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1B58FD246E19FD009C171E /* State.cpp */; };
@ -179,6 +181,8 @@
4B1EC716255398B000A1F44B /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1EC714255398B000A1F44B /* Sound.cpp */; };
4B1EC717255398B000A1F44B /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1EC714255398B000A1F44B /* Sound.cpp */; };
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
4B2130E2273A7A0A008A77B4 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2130E0273A7A0A008A77B4 /* Audio.cpp */; };
4B2130E3273A7A0A008A77B4 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2130E0273A7A0A008A77B4 /* Audio.cpp */; };
4B228CD524D773B40077EF25 /* CSScanTarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B228CD424D773B30077EF25 /* CSScanTarget.mm */; };
4B228CD924DA12C60077EF25 /* CSScanTargetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B228CD824DA12C60077EF25 /* CSScanTargetView.m */; };
4B228CDB24DA41890077EF25 /* ScanTarget.metal in Sources */ = {isa = PBXBuildFile; fileRef = 4B228CDA24DA41880077EF25 /* ScanTarget.metal */; };
@ -286,6 +290,7 @@
4B670AB12401CB8400D4E002 /* z80docflags.tap in Resources */ = {isa = PBXBuildFile; fileRef = 4B670A9A2401CB8400D4E002 /* z80docflags.tap */; };
4B680CE223A5553100451D43 /* 68000ComparativeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */; };
4B680CE423A555CA00451D43 /* 68000 Comparative Tests in Resources */ = {isa = PBXBuildFile; fileRef = 4B680CE323A555CA00451D43 /* 68000 Comparative Tests */; };
4B683B012727BE700043E541 /* Amiga Blitter Tests in Resources */ = {isa = PBXBuildFile; fileRef = 4B683B002727BE6F0043E541 /* Amiga Blitter Tests */; };
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
@ -428,6 +433,12 @@
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
4B7C681627517A59001671EC /* Sprites.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681427517A59001671EC /* Sprites.cpp */; };
4B7C681727517A59001671EC /* Sprites.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681427517A59001671EC /* Sprites.cpp */; };
4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C6818275196E8001671EC /* MouseJoystick.cpp */; };
4B7C681B275196E8001671EC /* MouseJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C6818275196E8001671EC /* MouseJoystick.cpp */; };
4B7C681E2751A104001671EC /* Bitplanes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681C2751A104001671EC /* Bitplanes.cpp */; };
4B7C681F2751A104001671EC /* Bitplanes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681C2751A104001671EC /* Bitplanes.cpp */; };
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
@ -577,6 +588,12 @@
4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */; };
4B9D0C4D22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9D0C4C22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm */; };
4B9D0C4F22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */; };
4B9EC0E226AA27BA0060A31F /* Blitter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E126AA27BA0060A31F /* Blitter.cpp */; };
4B9EC0E326AA27BA0060A31F /* Blitter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E126AA27BA0060A31F /* Blitter.cpp */; };
4B9EC0E626AA4A660060A31F /* Chipset.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E426AA4A660060A31F /* Chipset.cpp */; };
4B9EC0E726AA4A660060A31F /* Chipset.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E426AA4A660060A31F /* Chipset.cpp */; };
4B9EC0EA26B384080060A31F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E826B384080060A31F /* Keyboard.cpp */; };
4B9EC0EB26B384080060A31F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9EC0E826B384080060A31F /* Keyboard.cpp */; };
4B9F11C92272375400701480 /* qltrace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11C82272375400701480 /* qltrace.txt.gz */; };
4B9F11CA2272433900701480 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B9F11CC22729B3600701480 /* OPCLOGR2.BIN in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */; };
@ -887,6 +904,12 @@
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
4BC080CA26A238CC00D03FD8 /* AmigaADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080C826A238CC00D03FD8 /* AmigaADF.cpp */; };
4BC080CB26A238CC00D03FD8 /* AmigaADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080C826A238CC00D03FD8 /* AmigaADF.cpp */; };
4BC080D026A257A200D03FD8 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080CF26A257A200D03FD8 /* StaticAnalyser.cpp */; };
4BC080D126A257A200D03FD8 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080CF26A257A200D03FD8 /* StaticAnalyser.cpp */; };
4BC080D926A25ADA00D03FD8 /* Amiga.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080D826A25ADA00D03FD8 /* Amiga.cpp */; };
4BC080DA26A25ADA00D03FD8 /* Amiga.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC080D826A25ADA00D03FD8 /* Amiga.cpp */; };
4BC0CB282446BC7B00A79DBB /* OPLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */; };
4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
@ -900,6 +923,10 @@
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
4BC6236D26F4235400F83DFE /* Copper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6236C26F4235400F83DFE /* Copper.cpp */; };
4BC6236E26F4235400F83DFE /* Copper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6236C26F4235400F83DFE /* Copper.cpp */; };
4BC6236F26F426B400F83DFE /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6237126F94BCB00F83DFE /* MintermTests.mm */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
@ -989,6 +1016,7 @@
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; };
4BF701A026FFD32300996424 /* AmigaBlitterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */; };
4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; };
4BF8D4D5251C11DD00BBE21B /* 65816Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */; };
4BF8D4D6251C11DD00BBE21B /* 65816Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */; };
@ -1143,6 +1171,7 @@
4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = KonamiWithSCC.hpp; sourceTree = "<group>"; };
4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StringSerialiser.cpp; sourceTree = "<group>"; };
4B17B58A20A8A9D9007CCA8F /* StringSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StringSerialiser.hpp; sourceTree = "<group>"; };
4B1A1B1C27320FBB00119335 /* Disk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Disk.cpp; sourceTree = "<group>"; };
4B1B58F4246CC4E8009C171E /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4B1B58F5246CC4E8009C171E /* State.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4B1B58FD246E19FD009C171E /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
@ -1159,6 +1188,8 @@
4B1EC714255398B000A1F44B /* Sound.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = "<group>"; };
4B1EC715255398B000A1F44B /* Sound.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sound.hpp; sourceTree = "<group>"; };
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
4B2130E0273A7A0A008A77B4 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = "<group>"; };
4B2130E1273A7A0A008A77B4 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
4B228CD424D773B30077EF25 /* CSScanTarget.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSScanTarget.mm; sourceTree = "<group>"; };
4B228CD624D773CA0077EF25 /* CSScanTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSScanTarget.h; sourceTree = "<group>"; };
4B228CD724DA12C50077EF25 /* CSScanTargetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSScanTargetView.h; sourceTree = "<group>"; };
@ -1341,6 +1372,7 @@
4B670A9A2401CB8400D4E002 /* z80docflags.tap */ = {isa = PBXFileReference; lastKnownFileType = file; path = z80docflags.tap; sourceTree = "<group>"; };
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = "<group>"; };
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = "<group>"; };
4B683B002727BE6F0043E541 /* Amiga Blitter Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Amiga Blitter Tests"; sourceTree = "<group>"; };
4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = "<group>"; };
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
@ -1399,6 +1431,12 @@
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
4B7BA03E23D55E7900B98D9E /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRC.hpp; sourceTree = "<group>"; };
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LFSR.hpp; sourceTree = "<group>"; };
4B7C681427517A59001671EC /* Sprites.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Sprites.cpp; sourceTree = "<group>"; };
4B7C681527517A59001671EC /* Sprites.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sprites.hpp; sourceTree = "<group>"; };
4B7C6818275196E8001671EC /* MouseJoystick.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MouseJoystick.cpp; sourceTree = "<group>"; };
4B7C6819275196E8001671EC /* MouseJoystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseJoystick.hpp; sourceTree = "<group>"; };
4B7C681C2751A104001671EC /* Bitplanes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Bitplanes.cpp; sourceTree = "<group>"; };
4B7C681D2751A104001671EC /* Bitplanes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Bitplanes.hpp; sourceTree = "<group>"; };
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
@ -1569,11 +1607,18 @@
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSXStaticAnalyserTests.mm; sourceTree = "<group>"; };
4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = "<group>"; };
4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VSyncPredictor.hpp; sourceTree = "<group>"; };
4B99EBD026BF2D9F00CA924D /* DeferredValue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredValue.hpp; sourceTree = "<group>"; };
4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = "<group>"; };
4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = "<group>"; };
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BCDTests.mm; sourceTree = "<group>"; };
4B9D0C4C22C7DA1A00DE1AD3 /* 68000ControlFlowTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ControlFlowTests.mm; sourceTree = "<group>"; };
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000RollShiftTests.mm; sourceTree = "<group>"; };
4B9EC0E026AA260C0060A31F /* Blitter.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Blitter.hpp; sourceTree = "<group>"; };
4B9EC0E126AA27BA0060A31F /* Blitter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Blitter.cpp; sourceTree = "<group>"; };
4B9EC0E426AA4A660060A31F /* Chipset.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Chipset.cpp; sourceTree = "<group>"; };
4B9EC0E526AA4A660060A31F /* Chipset.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Chipset.hpp; sourceTree = "<group>"; };
4B9EC0E826B384080060A31F /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B9EC0E926B384080060A31F /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B9F11C82272375400701480 /* qltrace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = qltrace.txt.gz; sourceTree = "<group>"; };
4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = OPCLOGR2.BIN; path = "68000 Coverage/OPCLOGR2.BIN"; sourceTree = "<group>"; };
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = "<group>"; };
@ -1903,6 +1948,16 @@
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; };
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
4BC080C826A238CC00D03FD8 /* AmigaADF.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AmigaADF.cpp; sourceTree = "<group>"; };
4BC080C926A238CC00D03FD8 /* AmigaADF.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AmigaADF.hpp; sourceTree = "<group>"; };
4BC080CD26A257A200D03FD8 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BC080CE26A257A200D03FD8 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BC080CF26A257A200D03FD8 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4BC080D726A25ADA00D03FD8 /* Amiga.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Amiga.hpp; sourceTree = "<group>"; };
4BC080D826A25ADA00D03FD8 /* Amiga.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Amiga.cpp; sourceTree = "<group>"; };
4BC080DE26A481C100D03FD8 /* 6526.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6526.hpp; sourceTree = "<group>"; };
4BC080E026A481C100D03FD8 /* 6526Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6526Implementation.hpp; sourceTree = "<group>"; };
4BC080E126A48BCC00D03FD8 /* 6526Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6526Storage.hpp; sourceTree = "<group>"; };
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OPLTests.mm; sourceTree = "<group>"; };
4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BC1316E2346DE5000E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
@ -1928,6 +1983,11 @@
4BC57CD82436A62900FBC404 /* State.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000MoveTests.mm; sourceTree = "<group>"; };
4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; };
4BC6236A26F178DA00F83DFE /* DMADevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMADevice.hpp; sourceTree = "<group>"; };
4BC6236B26F4224300F83DFE /* Copper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Copper.hpp; sourceTree = "<group>"; };
4BC6236C26F4235400F83DFE /* Copper.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Copper.cpp; sourceTree = "<group>"; };
4BC6237026F94A5B00F83DFE /* Minterms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Minterms.hpp; sourceTree = "<group>"; };
4BC6237126F94BCB00F83DFE /* MintermTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MintermTests.mm; sourceTree = "<group>"; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
@ -1963,6 +2023,9 @@
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Oric.hpp; sourceTree = "<group>"; };
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
4BD0FBC2233706A200148981 /* CSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSApplication.m; sourceTree = "<group>"; };
4BD1552E270B14AC00410C6E /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = "<group>"; };
4BD155312716362A00410C6E /* BitSpread.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BitSpread.hpp; sourceTree = "<group>"; };
4BD1553227178E8000410C6E /* Flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Flags.hpp; sourceTree = "<group>"; };
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
@ -2063,6 +2126,7 @@
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AmigaBlitterTests.mm; sourceTree = "<group>"; };
4BF8D4CD251C0C9C00BBE21B /* 65816.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816.hpp; sourceTree = "<group>"; };
4BF8D4D3251C0D9F00BBE21B /* 65816Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816Storage.hpp; sourceTree = "<group>"; };
4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 65816Storage.cpp; sourceTree = "<group>"; };
@ -2321,6 +2385,7 @@
4B1414631B588A1100E04248 /* Test Binaries */ = {
isa = PBXGroup;
children = (
4B683B002727BE6F0043E541 /* Amiga Blitter Tests */,
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */,
4B9252CD1E74D28200B76AF1 /* Atari ROMs */,
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */,
@ -2715,6 +2780,7 @@
children = (
4B80CD74256CA15E00176FCC /* 2MG.cpp */,
4B45188D1F75FD1B00926311 /* AcornADF.cpp */,
4BC080C826A238CC00D03FD8 /* AmigaADF.cpp */,
4B0333AD2094081A0050B93D /* AppleDSK.cpp */,
4B45188F1F75FD1B00926311 /* CPCDSK.cpp */,
4B4518911F75FD1B00926311 /* D64.cpp */,
@ -2733,6 +2799,7 @@
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */,
4B80CD75256CA15E00176FCC /* 2MG.hpp */,
4B45188E1F75FD1B00926311 /* AcornADF.hpp */,
4BC080C926A238CC00D03FD8 /* AmigaADF.hpp */,
4B0333AE2094081A0050B93D /* AppleDSK.hpp */,
4B4518901F75FD1B00926311 /* CPCDSK.hpp */,
4B4518921F75FD1B00926311 /* D64.hpp */,
@ -3103,6 +3170,7 @@
4B7BA03C23D55E7900B98D9E /* Numeric */ = {
isa = PBXGroup;
children = (
4BD155312716362A00410C6E /* BitSpread.hpp */,
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
@ -3234,6 +3302,7 @@
4B894517201967B4007DE474 /* StaticAnalyser.cpp */,
4B8944EA201967B4007DE474 /* StaticAnalyser.hpp */,
4B8944EB201967B4007DE474 /* Acorn */,
4BC080CC26A257A200D03FD8 /* Amiga */,
4B894514201967B4007DE474 /* AmstradCPC */,
4B15A9FE20824C9F005E6C8D /* AppleII */,
4BE211FB253FC80800435408 /* AppleIIgs */,
@ -4051,6 +4120,7 @@
children = (
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
@ -4069,6 +4139,7 @@
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */,
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */,
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */,
4BC6237126F94BCB00F83DFE /* MintermTests.mm */,
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */,
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
@ -4130,6 +4201,7 @@
4B046DC31CFE651500E9E45E /* ScanProducer.hpp */,
4B8DD375263481BB00B3C866 /* StateProducer.hpp */,
4BC57CD32434282000FBC404 /* TimedMachine.hpp */,
4BC080D626A25ADA00D03FD8 /* Amiga */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4BCE0048227CE8CA000CA200 /* Apple */,
4B0ACC0423775819008902D0 /* Atari */,
@ -4226,6 +4298,64 @@
path = "Joystick Manager";
sourceTree = "<group>";
};
4BC080CC26A257A200D03FD8 /* Amiga */ = {
isa = PBXGroup;
children = (
4BC080CD26A257A200D03FD8 /* StaticAnalyser.hpp */,
4BC080CE26A257A200D03FD8 /* Target.hpp */,
4BC080CF26A257A200D03FD8 /* StaticAnalyser.cpp */,
);
path = Amiga;
sourceTree = "<group>";
};
4BC080D626A25ADA00D03FD8 /* Amiga */ = {
isa = PBXGroup;
children = (
4BC080D826A25ADA00D03FD8 /* Amiga.cpp */,
4B2130E0273A7A0A008A77B4 /* Audio.cpp */,
4B7C681C2751A104001671EC /* Bitplanes.cpp */,
4B9EC0E126AA27BA0060A31F /* Blitter.cpp */,
4B9EC0E426AA4A660060A31F /* Chipset.cpp */,
4BC6236C26F4235400F83DFE /* Copper.cpp */,
4B1A1B1C27320FBB00119335 /* Disk.cpp */,
4B9EC0E826B384080060A31F /* Keyboard.cpp */,
4B7C6818275196E8001671EC /* MouseJoystick.cpp */,
4B7C681427517A59001671EC /* Sprites.cpp */,
4BC080D726A25ADA00D03FD8 /* Amiga.hpp */,
4B2130E1273A7A0A008A77B4 /* Audio.hpp */,
4B7C681D2751A104001671EC /* Bitplanes.hpp */,
4B9EC0E026AA260C0060A31F /* Blitter.hpp */,
4B9EC0E526AA4A660060A31F /* Chipset.hpp */,
4BC6236B26F4224300F83DFE /* Copper.hpp */,
4BC6236A26F178DA00F83DFE /* DMADevice.hpp */,
4BD1553227178E8000410C6E /* Flags.hpp */,
4B9EC0E926B384080060A31F /* Keyboard.hpp */,
4BD1552E270B14AC00410C6E /* MemoryMap.hpp */,
4BC6237026F94A5B00F83DFE /* Minterms.hpp */,
4B7C6819275196E8001671EC /* MouseJoystick.hpp */,
4B7C681527517A59001671EC /* Sprites.hpp */,
);
path = Amiga;
sourceTree = "<group>";
};
4BC080DD26A481C100D03FD8 /* 6526 */ = {
isa = PBXGroup;
children = (
4BC080DE26A481C100D03FD8 /* 6526.hpp */,
4BC080DF26A481C100D03FD8 /* Implementation */,
);
path = 6526;
sourceTree = "<group>";
};
4BC080DF26A481C100D03FD8 /* Implementation */ = {
isa = PBXGroup;
children = (
4BC080E026A481C100D03FD8 /* 6526Implementation.hpp */,
4BC080E126A48BCC00D03FD8 /* 6526Storage.hpp */,
);
path = Implementation;
sourceTree = "<group>";
};
4BC1316C2346DE5000E4FF3D /* Atari2600 */ = {
isa = PBXGroup;
children = (
@ -4285,6 +4415,7 @@
4BD468F81D8DF4290084958B /* 1770 */,
4BDACBE922FFA5B50045EF7E /* 5380 */,
4BC9DF4B1D04691600F44158 /* 6522 */,
4BC080DD26A481C100D03FD8 /* 6526 */,
4B1E85791D174DEC001EF87D /* 6532 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
4BE845221F2FF7F400A5EA22 /* 6845 */,
@ -4652,6 +4783,7 @@
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */,
4B449C942063389900A095C8 /* TimeTypes.hpp */,
4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */,
4B99EBD026BF2D9F00CA924D /* DeferredValue.hpp */,
);
name = ClockReceiver;
path = ../../ClockReceiver;
@ -5112,6 +5244,7 @@
4BB299BC1B587D8400A49093 /* rraax in Resources */,
4BB299B21B587D8400A49093 /* rolax in Resources */,
4B8DF6342550D91600F3433C /* CPURET-trace_compare.log in Resources */,
4B683B012727BE700043E541 /* Amiga Blitter Tests in Resources */,
4BB299481B587D8400A49093 /* dcmz in Resources */,
4B8DF6492550D91600F3433C /* CPUCMP.sfc in Resources */,
4BB2996A1B587D8400A49093 /* jmpi in Resources */,
@ -5200,6 +5333,7 @@
4B0E04FB1FC9FA3100F43484 /* 9918.cpp in Sources */,
4B1B88C9202E469400B67DFF /* MultiJoystickMachine.cpp in Sources */,
4BCE1DF225D4C3FA00AE7A2B /* Bus.cpp in Sources */,
4BC080DA26A25ADA00D03FD8 /* Amiga.cpp in Sources */,
4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */,
4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
4B0ACC3123775819008902D0 /* TIASound.cpp in Sources */,
@ -5210,6 +5344,7 @@
4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */,
4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */,
4B9EC0EB26B384080060A31F /* Keyboard.cpp in Sources */,
4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */,
@ -5227,6 +5362,7 @@
4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */,
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */,
4B7C681727517A59001671EC /* Sprites.cpp in Sources */,
4B2E86D025D8D8C70024F1E9 /* Keyboard.cpp in Sources */,
4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
@ -5258,6 +5394,7 @@
4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */,
4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */,
4B8318B722D3E54D006DB630 /* Video.cpp in Sources */,
4B7C681F2751A104001671EC /* Bitplanes.cpp in Sources */,
4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */,
4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
4B1B88C1202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */,
@ -5268,6 +5405,7 @@
4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */,
4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */,
4BD424E62193B5830097291A /* Shader.cpp in Sources */,
4BC080CB26A238CC00D03FD8 /* AmigaADF.cpp in Sources */,
4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */,
4B055A9E1FAE85DA0060FFFF /* G64.cpp in Sources */,
@ -5292,7 +5430,9 @@
4B6AAEAE230E40250078E864 /* Target.cpp in Sources */,
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */,
4B9EC0E726AA4A660060A31F /* Chipset.cpp in Sources */,
4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */,
4B9EC0E326AA27BA0060A31F /* Blitter.cpp in Sources */,
4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */,
4B2E86E325DC95150024F1E9 /* Joystick.cpp in Sources */,
4BEBFB4E2002C4BF000708CC /* FAT12.cpp in Sources */,
@ -5318,6 +5458,7 @@
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
4B25B5F925BD083C00362C84 /* DiskIIDrive.cpp in Sources */,
4B7C681B275196E8001671EC /* MouseJoystick.cpp in Sources */,
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
4B2E86B825D7490E0024F1E9 /* ReactiveDevice.cpp in Sources */,
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
@ -5335,8 +5476,10 @@
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */,
4B1A1B1F27320FBC00119335 /* Disk.cpp in Sources */,
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */,
4BC23A2D2467600F001A6030 /* OPLL.cpp in Sources */,
4BC6236E26F4235400F83DFE /* Copper.cpp in Sources */,
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */,
4B1EC717255398B000A1F44B /* Sound.cpp in Sources */,
4BB8616F24E22DC500A00E03 /* BufferingScanTarget.cpp in Sources */,
@ -5355,6 +5498,7 @@
4BE0A3EF237BB170002AB46F /* ST.cpp in Sources */,
4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
4B2130E3273A7A0A008A77B4 /* Audio.cpp in Sources */,
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */,
4BE2121A253FCE9C00435408 /* AppleIIgs.cpp in Sources */,
4B051CAD26783E2000CA44E8 /* Nick.cpp in Sources */,
@ -5369,6 +5513,7 @@
4B894525201967B4007DE474 /* Tape.cpp in Sources */,
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
4BC6236F26F426B400F83DFE /* FAT.cpp in Sources */,
4B0F1BFD260300D900B85C66 /* ZXSpectrum.cpp in Sources */,
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
@ -5381,6 +5526,7 @@
4BB244D622AABAF600BE20E5 /* z8530.cpp in Sources */,
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */,
4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */,
4BC080D126A257A200D03FD8 /* StaticAnalyser.cpp in Sources */,
4BD424E82193B5830097291A /* Rectangle.cpp in Sources */,
4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */,
4B8318BA22D3E579006DB630 /* MacintoshIMG.cpp in Sources */,
@ -5469,18 +5615,23 @@
4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
4B2E86BE25D74F160024F1E9 /* Mouse.cpp in Sources */,
4B2130E2273A7A0A008A77B4 /* Audio.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */,
4B9EC0E626AA4A660060A31F /* Chipset.cpp in Sources */,
4BB8616E24E22DC500A00E03 /* BufferingScanTarget.cpp in Sources */,
4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */,
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */,
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
4B0ACC2C23775819008902D0 /* IntelligentKeyboard.cpp in Sources */,
4BC080CA26A238CC00D03FD8 /* AmigaADF.cpp in Sources */,
4B1A1B1E27320FBC00119335 /* Disk.cpp in Sources */,
4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */,
4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */,
4B0ACC2A23775819008902D0 /* Video.cpp in Sources */,
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
4BC6236D26F4235400F83DFE /* Copper.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
4B051C95266EF50200CA44E8 /* AppleIIController.swift in Sources */,
@ -5515,15 +5666,18 @@
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
4B0F1C232605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */,
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B1B58FF246E19FD009C171E /* State.cpp in Sources */,
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
4B9EC0EA26B384080060A31F /* Keyboard.cpp in Sources */,
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
4BC57CD92436A62900FBC404 /* State.cpp in Sources */,
4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */,
4B4518831F75E91A00926311 /* PCMTrack.cpp in Sources */,
4B8DF4F9254E36AE00F3433C /* Video.cpp in Sources */,
4B0ACC3223775819008902D0 /* Atari2600.cpp in Sources */,
4B7C681E2751A104001671EC /* Bitplanes.cpp in Sources */,
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */,
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
@ -5555,6 +5709,7 @@
4BCD634922D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */,
4B89452A201967B4007DE474 /* File.cpp in Sources */,
4BC080D026A257A200D03FD8 /* StaticAnalyser.cpp in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */,
4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */,
@ -5570,6 +5725,7 @@
4B1497881EE4A1DA00CE2596 /* ZX80O81P.cpp in Sources */,
4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
4BC080D926A25ADA00D03FD8 /* Amiga.cpp in Sources */,
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
@ -5585,6 +5741,7 @@
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
4B2E86CF25D8D8C70024F1E9 /* Keyboard.cpp in Sources */,
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
4B7C681627517A59001671EC /* Sprites.cpp in Sources */,
4B0ACC02237756ED008902D0 /* Line.cpp in Sources */,
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
@ -5610,6 +5767,7 @@
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4BEBFB4D2002C4BF000708CC /* FAT12.cpp in Sources */,
4B9EC0E226AA27BA0060A31F /* Blitter.cpp in Sources */,
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */,
@ -5803,11 +5961,13 @@
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */,
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
4BC6237226F94BCB00F83DFE /* MintermTests.mm in Sources */,
4B778F3923A5F11C0000D260 /* Shifter.cpp in Sources */,
4BEE4BD425A26E2B00011BD2 /* x86DecoderTests.mm in Sources */,
4B778F3623A5F1040000D260 /* Target.cpp in Sources */,
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
4B778F3D23A5F1750000D260 /* ncr5380.cpp in Sources */,
4BF701A026FFD32300996424 /* AmigaBlitterTests.mm in Sources */,
4B778F6323A5F3630000D260 /* Tape.cpp in Sources */,
4B778EF523A5DB440000D260 /* StaticAnalyser.cpp in Sources */,
4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */,
@ -6129,6 +6289,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DV3346VVUN;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -6179,6 +6340,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DV3346VVUN;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19162" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19162"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -14,17 +14,17 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<visualEffectView hidden="YES" wantsLayer="YES" appearanceType="vibrantDark" blendingMode="withinWindow" material="HUDWindow" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="131"/>
<rect key="frame" x="0.0" y="0.0" width="200" height="261"/>
<subviews>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ySY-ir-hzb" userLabel="First indicator">
<rect key="frame" x="8" y="105" width="16" height="18"/>
<rect key="frame" x="8" y="235" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="ySY-ir-hzb" secondAttribute="height" multiplier="1:1" id="UX0-hT-7Td"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="DhQ-Di-tRT"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tah-UQ-vdf">
<rect key="frame" x="30" y="107" width="59" height="16"/>
<rect key="frame" x="30" y="237" width="59" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 1" id="a5P-Ci-RzC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -32,14 +32,14 @@
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ncQ-wN-C61" userLabel="Second indicator">
<rect key="frame" x="8" y="81" width="16" height="18"/>
<rect key="frame" x="8" y="211" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="ncQ-wN-C61" secondAttribute="height" multiplier="1:1" id="176-v3-mVW"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="jlb-bk-FPd"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="14O-Lq-Npx">
<rect key="frame" x="30" y="83" width="61" height="16"/>
<rect key="frame" x="30" y="213" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 2" id="NE1-CO-pGI">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -47,14 +47,14 @@
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0rV-Th-Zwt" userLabel="Third indicator">
<rect key="frame" x="8" y="57" width="16" height="18"/>
<rect key="frame" x="8" y="187" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="0rV-Th-Zwt" secondAttribute="height" multiplier="1:1" id="Ai8-b3-Nn5"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="CJy-Jn-eCL"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acy-tT-OFH">
<rect key="frame" x="30" y="59" width="61" height="16"/>
<rect key="frame" x="30" y="189" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 3" id="FSR-y6-7WE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -62,44 +62,131 @@
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bvH-EJ-TYb" userLabel="Fourth indicator">
<rect key="frame" x="8" y="33" width="16" height="18"/>
<rect key="frame" x="8" y="163" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="bvH-EJ-TYb" secondAttribute="height" multiplier="1:1" id="cKc-q1-2Q4"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="eoN-hl-30l"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R0g-Oa-VB5">
<rect key="frame" x="30" y="35" width="62" height="16"/>
<rect key="frame" x="30" y="165" width="62" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 4" id="aGr-cd-jC0">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pn9-7S-9kS" userLabel="Fourth indicator">
<rect key="frame" x="8" y="139" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="pn9-7S-9kS" secondAttribute="height" multiplier="1:1" id="yUR-uX-jII"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="iCv-RT-Jg8"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nDC-Fi-3fv">
<rect key="frame" x="30" y="141" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 5" id="b74-7q-OOK">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bf9-wO-fc9" userLabel="Fourth indicator">
<rect key="frame" x="8" y="115" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="Bf9-wO-fc9" secondAttribute="height" multiplier="1:1" id="K3C-ua-qwN"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="NyB-3g-1td"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DMC-js-ACH">
<rect key="frame" x="30" y="117" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 6" id="X6z-Td-IZP">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BEi-xM-JeY" userLabel="Fourth indicator">
<rect key="frame" x="8" y="91" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="BEi-xM-JeY" secondAttribute="height" multiplier="1:1" id="OrY-a0-81h"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="ZWa-VF-jLa"/>
</levelIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Q3c-5Q-sZh">
<rect key="frame" x="30" y="93" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 7" id="3MD-ZQ-jTk">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6ff-xA-HJ2">
<rect key="frame" x="30" y="69" width="61" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Activity 8" id="5EX-Qj-ovC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<levelIndicator verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Nnc-64-Nyi" userLabel="Fourth indicator">
<rect key="frame" x="8" y="67" width="16" height="18"/>
<constraints>
<constraint firstAttribute="width" secondItem="Nnc-64-Nyi" secondAttribute="height" multiplier="1:1" id="UIo-c7-UFR"/>
</constraints>
<levelIndicatorCell key="cell" alignment="left" maxValue="1" warningValue="2" criticalValue="2" levelIndicatorStyle="continuousCapacity" id="FcS-7N-VDW"/>
</levelIndicator>
</subviews>
<constraints>
<constraint firstItem="14O-Lq-Npx" firstAttribute="centerY" secondItem="ncQ-wN-C61" secondAttribute="centerY" id="0Ht-U2-sPg"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="top" secondItem="0rV-Th-Zwt" secondAttribute="bottom" constant="8" symbolic="YES" id="0xw-qA-6vP"/>
<constraint firstItem="Nnc-64-Nyi" firstAttribute="width" secondItem="Nnc-64-Nyi" secondAttribute="height" multiplier="1:1" id="5Oi-JS-jcm"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="14O-Lq-Npx" secondAttribute="trailing" constant="8" id="5eo-XI-a3W"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="6ff-xA-HJ2" secondAttribute="trailing" constant="8" id="5vx-2i-7ZX"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="centerY" secondItem="ySY-ir-hzb" secondAttribute="centerY" id="6Hn-ts-mTi"/>
<constraint firstItem="pn9-7S-9kS" firstAttribute="width" secondItem="pn9-7S-9kS" secondAttribute="height" multiplier="1:1" id="7f0-Op-CDm"/>
<constraint firstItem="Nnc-64-Nyi" firstAttribute="width" secondItem="Nnc-64-Nyi" secondAttribute="height" multiplier="1:1" id="ALZ-2x-OdL"/>
<constraint firstItem="pn9-7S-9kS" firstAttribute="top" secondItem="bvH-EJ-TYb" secondAttribute="bottom" constant="8" symbolic="YES" id="ALk-8e-nCA"/>
<constraint firstItem="Bf9-wO-fc9" firstAttribute="width" secondItem="Bf9-wO-fc9" secondAttribute="height" multiplier="1:1" id="DT1-FT-Ilb"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="leading" secondItem="bvH-EJ-TYb" secondAttribute="trailing" constant="8" symbolic="YES" id="Dgy-JI-nA1"/>
<constraint firstItem="Nnc-64-Nyi" firstAttribute="top" secondItem="BEi-xM-JeY" secondAttribute="bottom" constant="8" symbolic="YES" id="GAa-X8-Mys"/>
<constraint firstItem="R0g-Oa-VB5" firstAttribute="centerY" secondItem="bvH-EJ-TYb" secondAttribute="centerY" id="Gfq-mB-Y1z"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="centerY" secondItem="0rV-Th-Zwt" secondAttribute="centerY" id="ImF-rK-oOr"/>
<constraint firstItem="Acy-tT-OFH" firstAttribute="leading" secondItem="0rV-Th-Zwt" secondAttribute="trailing" constant="8" symbolic="YES" id="JSU-pZ-l9Q"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="KMh-EO-rxE"/>
<constraint firstItem="nDC-Fi-3fv" firstAttribute="centerY" secondItem="pn9-7S-9kS" secondAttribute="centerY" id="Mea-LA-Xx8"/>
<constraint firstItem="Nnc-64-Nyi" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="NA2-Ni-jh8"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="top" secondItem="ncQ-wN-C61" secondAttribute="bottom" constant="8" symbolic="YES" id="Q2g-yM-nlJ"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="QUI-Hc-Bcl"/>
<constraint firstItem="BEi-xM-JeY" firstAttribute="width" secondItem="BEi-xM-JeY" secondAttribute="height" multiplier="1:1" id="RNt-qR-jIr"/>
<constraint firstItem="nDC-Fi-3fv" firstAttribute="leading" secondItem="pn9-7S-9kS" secondAttribute="trailing" constant="8" symbolic="YES" id="RWP-AR-rBQ"/>
<constraint firstItem="BEi-xM-JeY" firstAttribute="top" secondItem="Bf9-wO-fc9" secondAttribute="bottom" constant="8" symbolic="YES" id="SfL-e8-tGf"/>
<constraint firstItem="6ff-xA-HJ2" firstAttribute="leading" secondItem="Nnc-64-Nyi" secondAttribute="trailing" constant="8" symbolic="YES" id="Td2-d9-JUG"/>
<constraint firstItem="Bf9-wO-fc9" firstAttribute="width" secondItem="Bf9-wO-fc9" secondAttribute="height" multiplier="1:1" id="WOL-cc-xiX"/>
<constraint firstItem="BEi-xM-JeY" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="WpG-KW-b5w"/>
<constraint firstItem="pn9-7S-9kS" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="YgV-sN-mDf"/>
<constraint firstItem="6ff-xA-HJ2" firstAttribute="centerY" secondItem="Nnc-64-Nyi" secondAttribute="centerY" id="atz-8g-UGI"/>
<constraint firstItem="0rV-Th-Zwt" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="bKh-4L-mqj"/>
<constraint firstItem="DMC-js-ACH" firstAttribute="leading" secondItem="Bf9-wO-fc9" secondAttribute="trailing" constant="8" symbolic="YES" id="c1E-R8-Qqu"/>
<constraint firstItem="bvH-EJ-TYb" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="cPA-Ls-fLj"/>
<constraint firstItem="BEi-xM-JeY" firstAttribute="width" secondItem="BEi-xM-JeY" secondAttribute="height" multiplier="1:1" id="f23-rG-rct"/>
<constraint firstItem="Bf9-wO-fc9" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="8" id="fBh-Va-Ijc"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Tah-UQ-vdf" secondAttribute="trailing" constant="8" id="igX-7U-TeE"/>
<constraint firstItem="Q3c-5Q-sZh" firstAttribute="centerY" secondItem="BEi-xM-JeY" secondAttribute="centerY" id="jY4-r7-doR"/>
<constraint firstItem="14O-Lq-Npx" firstAttribute="leading" secondItem="ncQ-wN-C61" secondAttribute="trailing" constant="8" symbolic="YES" id="jjP-qH-Pqg"/>
<constraint firstItem="Tah-UQ-vdf" firstAttribute="leading" secondItem="ySY-ir-hzb" secondAttribute="trailing" constant="8" symbolic="YES" id="lux-Nz-K7E"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Q3c-5Q-sZh" secondAttribute="trailing" constant="8" id="lvS-LP-64Q"/>
<constraint firstItem="Q3c-5Q-sZh" firstAttribute="leading" secondItem="BEi-xM-JeY" secondAttribute="trailing" constant="8" symbolic="YES" id="lzK-6c-BDP"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Acy-tT-OFH" secondAttribute="trailing" constant="8" id="mEe-VT-dNr"/>
<constraint firstItem="ncQ-wN-C61" firstAttribute="top" secondItem="ySY-ir-hzb" secondAttribute="bottom" constant="8" symbolic="YES" id="mSc-jj-amw"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nDC-Fi-3fv" secondAttribute="trailing" constant="8" id="oyS-8r-4bB"/>
<constraint firstItem="DMC-js-ACH" firstAttribute="centerY" secondItem="Bf9-wO-fc9" secondAttribute="centerY" id="rIJ-Cy-hBa"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="R0g-Oa-VB5" secondAttribute="trailing" constant="8" id="sR8-Ph-suC"/>
<constraint firstItem="Bf9-wO-fc9" firstAttribute="top" secondItem="pn9-7S-9kS" secondAttribute="bottom" constant="8" symbolic="YES" id="wRg-AH-BSH"/>
<constraint firstItem="ySY-ir-hzb" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="8" id="wbj-48-DYq"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="DMC-js-ACH" secondAttribute="trailing" constant="8" id="wgz-n0-sN7"/>
</constraints>
<point key="canvasLocation" x="57" y="80"/>
<point key="canvasLocation" x="57" y="144.5"/>
</visualEffectView>
</objects>
</document>

View File

@ -8,5 +8,17 @@
#include "ROMMachine.hpp"
/// @returns a ROM fetcher that will check: (i) this application's support directory; and
/// (ii) this application's bundle for any requested ROMs, in that order.
///
/// All ROMs are found to be missing when the ROM fetcher is used will be added
/// to @c missing.
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing = nullptr);
BOOL CSInstallROM(NSURL *);
/// Loads the binary file located at @c url and then tests for whether it matches anything
/// known to the ROM catalogue. If so then a copy of the ROM will be retained where it
/// can later be found by the ROM fetcher returned by @c CSROMFetcher.
///
/// @returns @c true if the file was loaded successfully and matches something in
/// the library; @c false otherwise.
BOOL CSInstallROM(NSURL *url);

View File

@ -12,6 +12,10 @@ NS_ASSUME_NONNULL_BEGIN
@class CSMachine;
typedef NS_ENUM(NSInteger, CSMachineAmigaModel) {
CSMachineAmigaModelA500,
};
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
CSMachineAppleIIModelAppleII,
CSMachineAppleIIModelAppleIIPlus,
@ -120,6 +124,7 @@ typedef int Kilobytes;
- (nullable instancetype)initWithFileAtURL:(NSURL *)url;
- (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model;
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model;
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController;
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize;

View File

@ -14,6 +14,7 @@
#include "StaticAnalyser.hpp"
#include "../../../../../Analyser/Static/Acorn/Target.hpp"
#include "../../../../../Analyser/Static/Amiga/Target.hpp"
#include "../../../../../Analyser/Static/AmstradCPC/Target.hpp"
#include "../../../../../Analyser/Static/AppleII/Target.hpp"
#include "../../../../../Analyser/Static/AppleIIgs/Target.hpp"
@ -49,6 +50,16 @@
// MARK: - Machine-based Initialisers
- (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model {
self = [super init];
if(self) {
using Target = Analyser::Static::Amiga::Target;
auto target = std::make_unique<Target>();
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model {
self = [super init];
if(self) {

View File

@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="353"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="353"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -62,6 +62,65 @@ Gw
<rect key="frame" x="154" y="57" width="420" height="242"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Amiga" identifier="amiga" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="g7g-ZA-Uge">
<rect key="frame" x="18" y="187" width="364" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="At present only a 512k Amiga 500 is supported." id="Isk-qc-yKa">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="g7g-ZA-Uge" secondAttribute="trailing" constant="20" symbolic="YES" id="aGA-6p-c9w"/>
<constraint firstItem="g7g-ZA-Uge" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="20" symbolic="YES" id="xzI-rP-iIw"/>
<constraint firstItem="g7g-ZA-Uge" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="20" symbolic="YES" id="ya2-No-rfZ"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="ZGX-Fz-lFd">
<view key="view" id="afR-Xr-omP">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2zv-Zo-rmO">
<rect key="frame" x="67" y="179" width="96" height="25"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="LgZ-9j-YQl" id="yH2-Vm-hiD">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="FdB-a4-Cox">
<items>
<menuItem title="CPC464" tag="464" id="VSM-l2-mWa"/>
<menuItem title="CPC664" tag="664" id="h83-cG-bYO"/>
<menuItem title="CPC6128" state="on" tag="6128" id="LgZ-9j-YQl"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c3g-96-b3x">
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="53v-92-jmf">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="2zv-Zo-rmO" firstAttribute="leading" secondItem="c3g-96-b3x" secondAttribute="trailing" constant="8" symbolic="YES" id="KDE-Qm-2op"/>
<constraint firstItem="c3g-96-b3x" firstAttribute="centerY" secondItem="2zv-Zo-rmO" secondAttribute="centerY" id="Pzy-Q7-Sod"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2zv-Zo-rmO" secondAttribute="trailing" constant="20" symbolic="YES" id="YZZ-ed-aQe"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="2zv-Zo-rmO" secondAttribute="bottom" constant="20" symbolic="YES" id="iwS-Cw-5lf"/>
<constraint firstItem="c3g-96-b3x" firstAttribute="leading" secondItem="afR-Xr-omP" secondAttribute="leading" constant="20" symbolic="YES" id="o2l-EW-YUv"/>
<constraint firstItem="2zv-Zo-rmO" firstAttribute="top" secondItem="afR-Xr-omP" secondAttribute="top" constant="20" symbolic="YES" id="tZb-Zu-Eb0"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="7" width="400" height="222"/>
@ -193,44 +252,6 @@ Gw
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="7" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
<rect key="frame" x="67" y="179" width="96" height="25"/>
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="r3D-C2-Ruq">
<items>
<menuItem title="CPC464" tag="464" id="5kZ-XF-RFl"/>
<menuItem title="CPC664" tag="664" id="Sct-ZX-Qp1"/>
<menuItem title="CPC6128" state="on" tag="6128" id="klh-ZE-Agp"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
<rect key="frame" x="18" y="185" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="20" symbolic="YES" id="4AF-5C-2IF"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="20" symbolic="YES" id="Wof-5h-gfD"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="20" symbolic="YES" id="c92-uU-NRr"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="20" symbolic="YES" id="enU-LN-Nep"/>
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" symbolic="YES" id="mA8-US-ndo"/>
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Atari ST" identifier="atarist" id="a6Y-mx-yFn">
<view key="view" id="nnv-Wi-7hc">
<rect key="frame" x="10" y="7" width="400" height="223"/>
@ -421,6 +442,7 @@ Gw
<constraints>
<constraint firstItem="dzd-tH-BjX" firstAttribute="centerY" secondItem="hIr-GH-7xi" secondAttribute="centerY" id="3TV-RU-Kgh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="hIr-GH-7xi" secondAttribute="trailing" constant="20" symbolic="YES" id="44v-9O-y7L"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Lfl-5c-b8j" secondAttribute="trailing" constant="20" symbolic="YES" id="5tb-fZ-HpY"/>
<constraint firstItem="frx-nk-c3P" firstAttribute="centerY" secondItem="PhH-bu-pb5" secondAttribute="centerY" id="6Wc-aR-wuL"/>
<constraint firstItem="dzd-tH-BjX" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="7RZ-Om-TAa"/>
<constraint firstItem="ykc-W1-YaS" firstAttribute="centerY" secondItem="nen-Za-7zH" secondAttribute="centerY" id="CLa-6E-8BB"/>
@ -889,7 +911,7 @@ Gw
<outlet property="appleIIModelButton" destination="jli-ac-Sij" id="Jm3-f7-C17"/>
<outlet property="appleIIgsMemorySizeButton" destination="nQa-YS-utT" id="pTV-XL-zX3"/>
<outlet property="appleIIgsModelButton" destination="gcS-uy-mzl" id="Jcc-jC-cV1"/>
<outlet property="cpcModelTypeButton" destination="00d-sg-Krh" id="VyV-b1-A6x"/>
<outlet property="cpcModelTypeButton" destination="2zv-Zo-rmO" id="F4C-b2-eS0"/>
<outlet property="electronADFSButton" destination="945-wU-JOH" id="Fjm-W8-kvh"/>
<outlet property="electronAP6Button" destination="cG2-Ph-S3Z" id="vkq-1J-KBG"/>
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>

View File

@ -227,6 +227,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
switch machineSelector.selectedTabViewItem!.identifier as! String {
case "amiga":
return CSStaticAnalyser(amigaModel: .A500)
case "appleii":
var model: CSMachineAppleIIModel = .appleII
switch appleIIModelButton.selectedTag() {

View File

@ -880,14 +880,27 @@
- (void)testDIVSException {
// DIVS.W #0, D1
self.machine->set_initial_stack_pointer(0);
const uint32_t initial_sp = 0x5000;
self.machine->set_initial_stack_pointer(initial_sp);
[self performDIVS:0x0 d1:0x1fffffff];
// Check register state.
const auto state = self.machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x1fffffff);
XCTAssertEqual(state.supervisor_stack_pointer, 0xfffffffa);
XCTAssertEqual(state.supervisor_stack_pointer, initial_sp - 6);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
XCTAssertEqual(42, self.machine->get_cycle_count());
// Check stack contents; should be PC.l, PC.h and status register.
// Assumed: the program counter on the stack is that of the
// failing instrustion.
const uint16_t pc_h = *self.machine->ram_at(initial_sp-4);
const uint16_t pc_l = *self.machine->ram_at(initial_sp-2);
// const uint16_t status = *self.machine->ram_at(initial_sp);
const uint32_t initial_pc = self.machine->initial_pc();
XCTAssertEqual(pc_l, initial_pc & 0xffff);
XCTAssertEqual(pc_h, initial_pc >> 16);
// XCTAssertEqual(status, 9);
}
@end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,210 @@
[
["bltafwm", 8191],
["bltalwm", 65528],
["bltcon1", 0],
["bltbdat", 65535],
["bltadat", 65535],
["bltamod", 0],
["bltdmod", 76],
["bltcmod", 76],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 1492],
["bltdpth", 1],
["bltdptl", 1492],
["bltsize", 642],
["cread", 67028, 59391],
["write", 67028, 63488],
["cread", 67030, 65511],
["write", 67030, 31],
["cread", 67108, 58880],
["write", 67108, 63999],
["cread", 67110, 2023],
["write", 67110, 63519],
["cread", 67188, 58880],
["write", 67188, 63999],
["cread", 67190, 103],
["write", 67190, 65439],
["cread", 67268, 26119],
["write", 67268, 31224],
["cread", 67270, 65127],
["write", 67270, 415],
["cread", 67348, 26119],
["write", 67348, 31224],
["cread", 67350, 65127],
["write", 67350, 415],
["cread", 67428, 26119],
["write", 67428, 31224],
["cread", 67430, 65127],
["write", 67430, 415],
["cread", 67508, 26119],
["write", 67508, 31224],
["cread", 67510, 65127],
["write", 67510, 415],
["cread", 67588, 26599],
["write", 67588, 30744],
["cread", 67590, 65127],
["write", 67590, 415],
["cread", 67668, 26592],
["write", 67668, 30751],
["cread", 67670, 103],
["write", 67670, 65439],
["cread", 67748, 59391],
["write", 67748, 63488],
["cread", 67750, 65511],
["write", 67750, 31],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 21972],
["bltdpth", 1],
["bltdptl", 21972],
["bltsize", 642],
["cread", 87508, 0],
["write", 87508, 8191],
["cread", 87510, 0],
["write", 87510, 65528],
["cread", 87588, 511],
["write", 87588, 7680],
["cread", 87590, 63488],
["write", 87590, 2040],
["cread", 87668, 480],
["write", 87668, 7711],
["cread", 87670, 0],
["write", 87670, 65528],
["cread", 87748, 33248],
["write", 87748, 40479],
["cread", 87750, 0],
["write", 87750, 65528],
["cread", 87828, 33248],
["write", 87828, 40479],
["cread", 87830, 0],
["write", 87830, 65528],
["cread", 87908, 33248],
["write", 87908, 40479],
["cread", 87910, 0],
["write", 87910, 65528],
["cread", 87988, 33248],
["write", 87988, 40479],
["cread", 87990, 0],
["write", 87990, 65528],
["cread", 88068, 32768],
["write", 88068, 40959],
["cread", 88070, 0],
["write", 88070, 65528],
["cread", 88148, 32768],
["write", 88148, 40959],
["cread", 88150, 0],
["write", 88150, 65528],
["cread", 88228, 0],
["write", 88228, 8191],
["cread", 88230, 0],
["write", 88230, 65528],
["bltafwm", 8191],
["bltalwm", 65528],
["bltcon1", 0],
["bltbdat", 65535],
["bltadat", 65535],
["bltamod", 0],
["bltdmod", 76],
["bltcmod", 76],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 1492],
["bltdpth", 1],
["bltdptl", 1492],
["bltsize", 642],
["cread", 67028, 63488],
["write", 67028, 59391],
["cread", 67030, 31],
["write", 67030, 65511],
["cread", 67108, 63999],
["write", 67108, 58880],
["cread", 67110, 63519],
["write", 67110, 2023],
["cread", 67188, 63999],
["write", 67188, 58880],
["cread", 67190, 65439],
["write", 67190, 103],
["cread", 67268, 31224],
["write", 67268, 26119],
["cread", 67270, 415],
["write", 67270, 65127],
["cread", 67348, 31224],
["write", 67348, 26119],
["cread", 67350, 415],
["write", 67350, 65127],
["cread", 67428, 31224],
["write", 67428, 26119],
["cread", 67430, 415],
["write", 67430, 65127],
["cread", 67508, 31224],
["write", 67508, 26119],
["cread", 67510, 415],
["write", 67510, 65127],
["cread", 67588, 30744],
["write", 67588, 26599],
["cread", 67590, 415],
["write", 67590, 65127],
["cread", 67668, 30751],
["write", 67668, 26592],
["cread", 67670, 65439],
["write", 67670, 103],
["cread", 67748, 63488],
["write", 67748, 59391],
["cread", 67750, 31],
["write", 67750, 65511],
["bltapth", 0],
["bltaptl", 47616],
["bltcon0", 874],
["bltcpth", 1],
["bltcptl", 21972],
["bltdpth", 1],
["bltdptl", 21972],
["bltsize", 642],
["cread", 87508, 8191],
["write", 87508, 0],
["cread", 87510, 65528],
["write", 87510, 0],
["cread", 87588, 7680],
["write", 87588, 511],
["cread", 87590, 2040],
["write", 87590, 63488],
["cread", 87668, 7711],
["write", 87668, 480],
["cread", 87670, 65528],
["write", 87670, 0],
["cread", 87748, 40479],
["write", 87748, 33248],
["cread", 87750, 65528],
["write", 87750, 0],
["cread", 87828, 40479],
["write", 87828, 33248],
["cread", 87830, 65528],
["write", 87830, 0],
["cread", 87908, 40479],
["write", 87908, 33248],
["cread", 87910, 65528],
["write", 87910, 0],
["cread", 87988, 40479],
["write", 87988, 33248],
["cread", 87990, 65528],
["write", 87990, 0],
["cread", 88068, 40959],
["write", 88068, 32768],
["cread", 88070, 65528],
["write", 88070, 0],
["cread", 88148, 40959],
["write", 88148, 32768],
["cread", 88150, 65528],
["write", 88150, 0],
["cread", 88228, 8191],
["write", 88228, 0],
["cread", 88230, 65528],
["write", 88230, 0]
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,785 @@
[
["bltafwm", 65535],
["bltalwm", 65535],
["bltbmod", 0],
["bltamod", 0],
["bltdmod", 0],
["bltcdat", 21845],
["bltapth", 0],
["bltaptl", 27983],
["bltbpth", 0],
["bltbptl", 28495],
["bltdpth", 0],
["bltdptl", 24127],
["bltcon0", 7640],
["bltcon1", 2],
["bltsize", 2056],
["aread", 27982, 43690],
["bread", 28494, 43690],
["write", 24126, 0],
["aread", 27980, 43690],
["bread", 28492, 43690],
["write", 24124, 0],
["aread", 27978, 43690],
["bread", 28490, 43690],
["write", 24122, 0],
["aread", 27976, 43690],
["bread", 28488, 43690],
["write", 24120, 0],
["aread", 27974, 43690],
["bread", 28486, 43690],
["write", 24118, 0],
["aread", 27972, 43690],
["bread", 28484, 43690],
["write", 24116, 0],
["aread", 27970, 43690],
["bread", 28482, 43690],
["write", 24114, 0],
["aread", 27968, 43690],
["bread", 28480, 43690],
["write", 24112, 0],
["aread", 27966, 43690],
["bread", 28478, 43690],
["write", 24110, 0],
["aread", 27964, 43690],
["bread", 28476, 43690],
["write", 24108, 0],
["aread", 27962, 43690],
["bread", 28474, 43690],
["write", 24106, 0],
["aread", 27960, 43690],
["bread", 28472, 43690],
["write", 24104, 0],
["aread", 27958, 43690],
["bread", 28470, 43690],
["write", 24102, 0],
["aread", 27956, 43690],
["bread", 28468, 43690],
["write", 24100, 0],
["aread", 27954, 43690],
["bread", 28466, 43690],
["write", 24098, 0],
["aread", 27952, 43690],
["bread", 28464, 43690],
["write", 24096, 0],
["aread", 27950, 43690],
["bread", 28462, 43690],
["write", 24094, 0],
["aread", 27948, 43690],
["bread", 28460, 43690],
["write", 24092, 0],
["aread", 27946, 43690],
["bread", 28458, 43690],
["write", 24090, 0],
["aread", 27944, 43690],
["bread", 28456, 43690],
["write", 24088, 0],
["aread", 27942, 43690],
["bread", 28454, 43690],
["write", 24086, 0],
["aread", 27940, 43690],
["bread", 28452, 43690],
["write", 24084, 0],
["aread", 27938, 43690],
["bread", 28450, 43690],
["write", 24082, 0],
["aread", 27936, 43690],
["bread", 28448, 43690],
["write", 24080, 0],
["aread", 27934, 43690],
["bread", 28446, 43690],
["write", 24078, 0],
["aread", 27932, 43690],
["bread", 28444, 43690],
["write", 24076, 0],
["aread", 27930, 43690],
["bread", 28442, 43690],
["write", 24074, 0],
["aread", 27928, 43690],
["bread", 28440, 43690],
["write", 24072, 0],
["aread", 27926, 43690],
["bread", 28438, 43690],
["write", 24070, 0],
["aread", 27924, 43690],
["bread", 28436, 43690],
["write", 24068, 0],
["aread", 27922, 43690],
["bread", 28434, 43690],
["write", 24066, 0],
["aread", 27920, 43690],
["bread", 28432, 43690],
["write", 24064, 0],
["aread", 27918, 43690],
["bread", 28430, 43690],
["write", 24062, 0],
["aread", 27916, 43690],
["bread", 28428, 43690],
["write", 24060, 0],
["aread", 27914, 43690],
["bread", 28426, 43690],
["write", 24058, 0],
["aread", 27912, 43690],
["bread", 28424, 43690],
["write", 24056, 0],
["aread", 27910, 43690],
["bread", 28422, 43690],
["write", 24054, 0],
["aread", 27908, 43690],
["bread", 28420, 43690],
["write", 24052, 0],
["aread", 27906, 43690],
["bread", 28418, 43690],
["write", 24050, 0],
["aread", 27904, 43690],
["bread", 28416, 43690],
["write", 24048, 0],
["aread", 27902, 43690],
["bread", 28414, 43690],
["write", 24046, 0],
["aread", 27900, 43690],
["bread", 28412, 43690],
["write", 24044, 0],
["aread", 27898, 43690],
["bread", 28410, 43690],
["write", 24042, 0],
["aread", 27896, 43690],
["bread", 28408, 43690],
["write", 24040, 0],
["aread", 27894, 43690],
["bread", 28406, 43690],
["write", 24038, 0],
["aread", 27892, 43690],
["bread", 28404, 43690],
["write", 24036, 0],
["aread", 27890, 43690],
["bread", 28402, 43690],
["write", 24034, 0],
["aread", 27888, 43690],
["bread", 28400, 43690],
["write", 24032, 0],
["aread", 27886, 43690],
["bread", 28398, 43690],
["write", 24030, 0],
["aread", 27884, 43690],
["bread", 28396, 43690],
["write", 24028, 0],
["aread", 27882, 43690],
["bread", 28394, 43690],
["write", 24026, 0],
["aread", 27880, 43690],
["bread", 28392, 43690],
["write", 24024, 0],
["aread", 27878, 43690],
["bread", 28390, 43690],
["write", 24022, 0],
["aread", 27876, 43690],
["bread", 28388, 43690],
["write", 24020, 0],
["aread", 27874, 43690],
["bread", 28386, 43690],
["write", 24018, 0],
["aread", 27872, 43690],
["bread", 28384, 43690],
["write", 24016, 0],
["aread", 27870, 43690],
["bread", 28382, 43690],
["write", 24014, 0],
["aread", 27868, 43690],
["bread", 28380, 43690],
["write", 24012, 0],
["aread", 27866, 43690],
["bread", 28378, 43690],
["write", 24010, 0],
["aread", 27864, 43690],
["bread", 28376, 43690],
["write", 24008, 0],
["aread", 27862, 43690],
["bread", 28374, 43690],
["write", 24006, 0],
["aread", 27860, 43690],
["bread", 28372, 43690],
["write", 24004, 0],
["aread", 27858, 43690],
["bread", 28370, 43690],
["write", 24002, 0],
["aread", 27856, 43690],
["bread", 28368, 43690],
["write", 24000, 0],
["aread", 27854, 43690],
["bread", 28366, 43690],
["write", 23998, 0],
["aread", 27852, 43690],
["bread", 28364, 43690],
["write", 23996, 0],
["aread", 27850, 43690],
["bread", 28362, 43690],
["write", 23994, 0],
["aread", 27848, 43690],
["bread", 28360, 43690],
["write", 23992, 0],
["aread", 27846, 43690],
["bread", 28358, 43690],
["write", 23990, 0],
["aread", 27844, 43690],
["bread", 28356, 43690],
["write", 23988, 0],
["aread", 27842, 43690],
["bread", 28354, 43690],
["write", 23986, 0],
["aread", 27840, 43690],
["bread", 28352, 43690],
["write", 23984, 0],
["aread", 27838, 43690],
["bread", 28350, 43690],
["write", 23982, 0],
["aread", 27836, 43690],
["bread", 28348, 43690],
["write", 23980, 0],
["aread", 27834, 43690],
["bread", 28346, 43690],
["write", 23978, 0],
["aread", 27832, 43690],
["bread", 28344, 43690],
["write", 23976, 0],
["aread", 27830, 43690],
["bread", 28342, 43690],
["write", 23974, 0],
["aread", 27828, 43690],
["bread", 28340, 43690],
["write", 23972, 0],
["aread", 27826, 43690],
["bread", 28338, 43690],
["write", 23970, 0],
["aread", 27824, 43690],
["bread", 28336, 43690],
["write", 23968, 0],
["aread", 27822, 43690],
["bread", 28334, 43690],
["write", 23966, 0],
["aread", 27820, 43690],
["bread", 28332, 43690],
["write", 23964, 0],
["aread", 27818, 43690],
["bread", 28330, 43690],
["write", 23962, 0],
["aread", 27816, 43690],
["bread", 28328, 43690],
["write", 23960, 0],
["aread", 27814, 43690],
["bread", 28326, 43690],
["write", 23958, 0],
["aread", 27812, 43690],
["bread", 28324, 43690],
["write", 23956, 0],
["aread", 27810, 43690],
["bread", 28322, 43690],
["write", 23954, 0],
["aread", 27808, 43690],
["bread", 28320, 43690],
["write", 23952, 0],
["aread", 27806, 43690],
["bread", 28318, 43690],
["write", 23950, 0],
["aread", 27804, 43690],
["bread", 28316, 43690],
["write", 23948, 0],
["aread", 27802, 43690],
["bread", 28314, 43690],
["write", 23946, 0],
["aread", 27800, 43690],
["bread", 28312, 43690],
["write", 23944, 0],
["aread", 27798, 43690],
["bread", 28310, 43690],
["write", 23942, 0],
["aread", 27796, 43690],
["bread", 28308, 43690],
["write", 23940, 0],
["aread", 27794, 43690],
["bread", 28306, 43690],
["write", 23938, 0],
["aread", 27792, 43690],
["bread", 28304, 43690],
["write", 23936, 0],
["aread", 27790, 43690],
["bread", 28302, 43690],
["write", 23934, 0],
["aread", 27788, 43690],
["bread", 28300, 43690],
["write", 23932, 0],
["aread", 27786, 43690],
["bread", 28298, 43690],
["write", 23930, 0],
["aread", 27784, 43690],
["bread", 28296, 43690],
["write", 23928, 0],
["aread", 27782, 43690],
["bread", 28294, 43690],
["write", 23926, 0],
["aread", 27780, 43690],
["bread", 28292, 43690],
["write", 23924, 0],
["aread", 27778, 43690],
["bread", 28290, 43690],
["write", 23922, 0],
["aread", 27776, 43690],
["bread", 28288, 43690],
["write", 23920, 0],
["aread", 27774, 43690],
["bread", 28286, 43690],
["write", 23918, 0],
["aread", 27772, 43690],
["bread", 28284, 43690],
["write", 23916, 0],
["aread", 27770, 43690],
["bread", 28282, 43690],
["write", 23914, 0],
["aread", 27768, 43690],
["bread", 28280, 43690],
["write", 23912, 0],
["aread", 27766, 43690],
["bread", 28278, 43690],
["write", 23910, 0],
["aread", 27764, 43690],
["bread", 28276, 43690],
["write", 23908, 0],
["aread", 27762, 43690],
["bread", 28274, 43690],
["write", 23906, 0],
["aread", 27760, 43690],
["bread", 28272, 43690],
["write", 23904, 0],
["aread", 27758, 43690],
["bread", 28270, 43690],
["write", 23902, 0],
["aread", 27756, 43690],
["bread", 28268, 43690],
["write", 23900, 0],
["aread", 27754, 43690],
["bread", 28266, 43690],
["write", 23898, 0],
["aread", 27752, 43690],
["bread", 28264, 43690],
["write", 23896, 0],
["aread", 27750, 43690],
["bread", 28262, 43690],
["write", 23894, 0],
["aread", 27748, 43690],
["bread", 28260, 43690],
["write", 23892, 0],
["aread", 27746, 43690],
["bread", 28258, 43690],
["write", 23890, 0],
["aread", 27744, 43690],
["bread", 28256, 43690],
["write", 23888, 0],
["aread", 27742, 43690],
["bread", 28254, 43690],
["write", 23886, 0],
["aread", 27740, 43690],
["bread", 28252, 43690],
["write", 23884, 0],
["aread", 27738, 43690],
["bread", 28250, 43690],
["write", 23882, 0],
["aread", 27736, 43690],
["bread", 28248, 43690],
["write", 23880, 0],
["aread", 27734, 43690],
["bread", 28246, 43690],
["write", 23878, 0],
["aread", 27732, 43690],
["bread", 28244, 43690],
["write", 23876, 0],
["aread", 27730, 43690],
["bread", 28242, 43690],
["write", 23874, 0],
["aread", 27728, 43690],
["bread", 28240, 43690],
["write", 23872, 0],
["aread", 27726, 43690],
["bread", 28238, 43690],
["write", 23870, 0],
["aread", 27724, 43690],
["bread", 28236, 43690],
["write", 23868, 0],
["aread", 27722, 43690],
["bread", 28234, 43690],
["write", 23866, 0],
["aread", 27720, 43690],
["bread", 28232, 43690],
["write", 23864, 0],
["aread", 27718, 43690],
["bread", 28230, 43690],
["write", 23862, 0],
["aread", 27716, 43690],
["bread", 28228, 43690],
["write", 23860, 0],
["aread", 27714, 43690],
["bread", 28226, 43690],
["write", 23858, 0],
["aread", 27712, 43690],
["bread", 28224, 43690],
["write", 23856, 0],
["aread", 27710, 43690],
["bread", 28222, 43690],
["write", 23854, 0],
["aread", 27708, 43690],
["bread", 28220, 43690],
["write", 23852, 0],
["aread", 27706, 43690],
["bread", 28218, 43690],
["write", 23850, 0],
["aread", 27704, 43690],
["bread", 28216, 43690],
["write", 23848, 0],
["aread", 27702, 43690],
["bread", 28214, 43690],
["write", 23846, 0],
["aread", 27700, 43690],
["bread", 28212, 43690],
["write", 23844, 0],
["aread", 27698, 43690],
["bread", 28210, 43690],
["write", 23842, 0],
["aread", 27696, 43690],
["bread", 28208, 43690],
["write", 23840, 0],
["aread", 27694, 43690],
["bread", 28206, 43690],
["write", 23838, 0],
["aread", 27692, 43690],
["bread", 28204, 43690],
["write", 23836, 0],
["aread", 27690, 43690],
["bread", 28202, 43690],
["write", 23834, 0],
["aread", 27688, 43690],
["bread", 28200, 43690],
["write", 23832, 0],
["aread", 27686, 43690],
["bread", 28198, 43690],
["write", 23830, 0],
["aread", 27684, 43690],
["bread", 28196, 43690],
["write", 23828, 0],
["aread", 27682, 43690],
["bread", 28194, 43690],
["write", 23826, 0],
["aread", 27680, 43690],
["bread", 28192, 43690],
["write", 23824, 0],
["aread", 27678, 43690],
["bread", 28190, 43690],
["write", 23822, 0],
["aread", 27676, 43690],
["bread", 28188, 43690],
["write", 23820, 0],
["aread", 27674, 43690],
["bread", 28186, 43690],
["write", 23818, 0],
["aread", 27672, 43690],
["bread", 28184, 43690],
["write", 23816, 0],
["aread", 27670, 43690],
["bread", 28182, 43690],
["write", 23814, 0],
["aread", 27668, 43690],
["bread", 28180, 43690],
["write", 23812, 0],
["aread", 27666, 43690],
["bread", 28178, 43690],
["write", 23810, 0],
["aread", 27664, 43690],
["bread", 28176, 43690],
["write", 23808, 0],
["aread", 27662, 43690],
["bread", 28174, 43690],
["write", 23806, 0],
["aread", 27660, 43690],
["bread", 28172, 43690],
["write", 23804, 0],
["aread", 27658, 43690],
["bread", 28170, 43690],
["write", 23802, 0],
["aread", 27656, 43690],
["bread", 28168, 43690],
["write", 23800, 0],
["aread", 27654, 43690],
["bread", 28166, 43690],
["write", 23798, 0],
["aread", 27652, 43690],
["bread", 28164, 43690],
["write", 23796, 0],
["aread", 27650, 43690],
["bread", 28162, 43690],
["write", 23794, 0],
["aread", 27648, 43690],
["bread", 28160, 43690],
["write", 23792, 0],
["aread", 27646, 43690],
["bread", 28158, 43690],
["write", 23790, 0],
["aread", 27644, 43690],
["bread", 28156, 43690],
["write", 23788, 0],
["aread", 27642, 43690],
["bread", 28154, 43690],
["write", 23786, 0],
["aread", 27640, 43690],
["bread", 28152, 43690],
["write", 23784, 0],
["aread", 27638, 43690],
["bread", 28150, 43690],
["write", 23782, 0],
["aread", 27636, 43690],
["bread", 28148, 43690],
["write", 23780, 0],
["aread", 27634, 43690],
["bread", 28146, 43690],
["write", 23778, 0],
["aread", 27632, 43690],
["bread", 28144, 43690],
["write", 23776, 0],
["aread", 27630, 43690],
["bread", 28142, 43690],
["write", 23774, 0],
["aread", 27628, 43690],
["bread", 28140, 43690],
["write", 23772, 0],
["aread", 27626, 43690],
["bread", 28138, 43690],
["write", 23770, 0],
["aread", 27624, 43690],
["bread", 28136, 43690],
["write", 23768, 0],
["aread", 27622, 43690],
["bread", 28134, 43690],
["write", 23766, 0],
["aread", 27620, 43690],
["bread", 28132, 43690],
["write", 23764, 0],
["aread", 27618, 43690],
["bread", 28130, 43690],
["write", 23762, 0],
["aread", 27616, 43690],
["bread", 28128, 43690],
["write", 23760, 0],
["aread", 27614, 43690],
["bread", 28126, 43690],
["write", 23758, 0],
["aread", 27612, 43690],
["bread", 28124, 43690],
["write", 23756, 0],
["aread", 27610, 43690],
["bread", 28122, 43690],
["write", 23754, 0],
["aread", 27608, 43690],
["bread", 28120, 43690],
["write", 23752, 0],
["aread", 27606, 43690],
["bread", 28118, 43690],
["write", 23750, 0],
["aread", 27604, 43690],
["bread", 28116, 43690],
["write", 23748, 0],
["aread", 27602, 43690],
["bread", 28114, 43690],
["write", 23746, 0],
["aread", 27600, 43690],
["bread", 28112, 43690],
["write", 23744, 0],
["aread", 27598, 43690],
["bread", 28110, 43690],
["write", 23742, 0],
["aread", 27596, 43690],
["bread", 28108, 43690],
["write", 23740, 0],
["aread", 27594, 43690],
["bread", 28106, 43690],
["write", 23738, 0],
["aread", 27592, 43690],
["bread", 28104, 43690],
["write", 23736, 0],
["aread", 27590, 43690],
["bread", 28102, 43690],
["write", 23734, 0],
["aread", 27588, 43690],
["bread", 28100, 43690],
["write", 23732, 0],
["aread", 27586, 43690],
["bread", 28098, 43690],
["write", 23730, 0],
["aread", 27584, 43690],
["bread", 28096, 43690],
["write", 23728, 0],
["aread", 27582, 43690],
["bread", 28094, 43690],
["write", 23726, 0],
["aread", 27580, 43690],
["bread", 28092, 43690],
["write", 23724, 0],
["aread", 27578, 43690],
["bread", 28090, 43690],
["write", 23722, 0],
["aread", 27576, 43690],
["bread", 28088, 43690],
["write", 23720, 0],
["aread", 27574, 43690],
["bread", 28086, 43690],
["write", 23718, 0],
["aread", 27572, 43690],
["bread", 28084, 43690],
["write", 23716, 0],
["aread", 27570, 43690],
["bread", 28082, 43690],
["write", 23714, 0],
["aread", 27568, 43690],
["bread", 28080, 43690],
["write", 23712, 0],
["aread", 27566, 43690],
["bread", 28078, 43690],
["write", 23710, 0],
["aread", 27564, 43690],
["bread", 28076, 43690],
["write", 23708, 0],
["aread", 27562, 43690],
["bread", 28074, 43690],
["write", 23706, 0],
["aread", 27560, 43690],
["bread", 28072, 43690],
["write", 23704, 0],
["aread", 27558, 43690],
["bread", 28070, 43690],
["write", 23702, 0],
["aread", 27556, 43690],
["bread", 28068, 43690],
["write", 23700, 0],
["aread", 27554, 43690],
["bread", 28066, 43690],
["write", 23698, 0],
["aread", 27552, 43690],
["bread", 28064, 43690],
["write", 23696, 0],
["aread", 27550, 43690],
["bread", 28062, 43690],
["write", 23694, 0],
["aread", 27548, 43690],
["bread", 28060, 43690],
["write", 23692, 0],
["aread", 27546, 43690],
["bread", 28058, 43690],
["write", 23690, 0],
["aread", 27544, 43690],
["bread", 28056, 43690],
["write", 23688, 0],
["aread", 27542, 43690],
["bread", 28054, 43690],
["write", 23686, 0],
["aread", 27540, 43690],
["bread", 28052, 43690],
["write", 23684, 0],
["aread", 27538, 43690],
["bread", 28050, 43690],
["write", 23682, 0],
["aread", 27536, 43690],
["bread", 28048, 43690],
["write", 23680, 0],
["aread", 27534, 43690],
["bread", 28046, 43690],
["write", 23678, 0],
["aread", 27532, 43690],
["bread", 28044, 43690],
["write", 23676, 0],
["aread", 27530, 43690],
["bread", 28042, 43690],
["write", 23674, 0],
["aread", 27528, 43690],
["bread", 28040, 43690],
["write", 23672, 0],
["aread", 27526, 43690],
["bread", 28038, 43690],
["write", 23670, 0],
["aread", 27524, 43690],
["bread", 28036, 43690],
["write", 23668, 0],
["aread", 27522, 43690],
["bread", 28034, 43690],
["write", 23666, 0],
["aread", 27520, 5290],
["bread", 28032, 20778],
["write", 23664, 30976],
["aread", 27518, 4753],
["bread", 28030, 18770],
["write", 23662, 24946],
["aread", 27516, 37137],
["bread", 28028, 19026],
["write", 23660, 25202],
["aread", 27514, 5268],
["bread", 28026, 17481],
["write", 23658, 27753],
["aread", 27512, 4373],
["bread", 28024, 20772],
["write", 23656, 29486],
["aread", 27510, 4757],
["bread", 28022, 17477],
["write", 23654, 25711],
["aread", 27508, 4693],
["bread", 28020, 19026],
["write", 23652, 24826],
["aread", 27506, 37461],
["bread", 28018, 21077],
["write", 23650, 28927],
["aread", 27504, 42258],
["bread", 28016, 17493],
["write", 23648, 20085],
["aread", 27502, 4778],
["bread", 28014, 21162],
["write", 23646, 28672],
["aread", 27500, 43689],
["bread", 28012, 43668],
["write", 23644, 22],
["aread", 27498, 37524],
["bread", 28010, 43594],
["write", 23642, 8296],
["aread", 27496, 4778],
["bread", 28008, 43594],
["write", 23640, 8256],
["aread", 27494, 37157],
["bread", 28006, 17706],
["write", 23638, 26378],
["aread", 27492, 42314],
["bread", 28004, 19114],
["write", 23636, 19072],
["aread", 27490, 21842],
["bread", 28002, 21802],
["write", 23634, 65440],
["aread", 27488, 42325],
["bread", 28000, 17572],
["write", 23632, 20142],
["aread", 27486, 10916],
["bread", 27998, 43666],
["write", 23630, 24],
["aread", 27484, 43349],
["bread", 27996, 18770],
["write", 23628, 17402],
["aread", 27482, 43282],
["bread", 27994, 43346],
["write", 23626, 880],
["aread", 27480, 43690],
["bread", 27992, 10922],
["write", 23624, 0],
["aread", 27478, 42276],
["bread", 27990, 42257],
["write", 23622, 3865],
["aread", 27476, 19090],
["bread", 27988, 19114],
["write", 23620, 49184],
["aread", 27474, 10538],
["bread", 27986, 20778],
["write", 23618, 21248],
["aread", 27472, 43685],
["bread", 27984, 17477],
["write", 23616, 17487]
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,274 @@
//
// AmigaBlitterTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 25/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "Blitter.hpp"
#include <unordered_map>
#include <vector>
namespace Amiga {
/// An empty stub to satisfy Amiga::Blitter's inheritance from Amiga::DMADevice;
struct Chipset {
// Hyper ugliness: make a gross assumption about the effect of
// the only call the Blitter will make into the Chipset, i.e.
// that it will write something but do nothing more.
//
// Bonus ugliness: assume the real Chipset struct is 1kb in
// size, at most.
uint8_t _[1024];
};
};
namespace {
using WriteVector = std::vector<std::pair<uint32_t, uint16_t>>;
}
@interface AmigaBlitterTests: XCTestCase
@end
@implementation AmigaBlitterTests
- (BOOL)verifyWrites:(WriteVector &)writes blitter:(Amiga::Blitter &)blitter ram:(uint16_t *)ram approximateLocation:(NSInteger)approximateLocation {
// Run for however much time the Blitter wants.
while(blitter.get_status() & 0x4000) {
blitter.advance_dma();
}
// Some blits will write the same address twice
// (e.g. by virtue of an appropriate modulo), but
// this unit test is currently able to verify the
// final result only. So count number of accesses per
// address up front in order only to count the
// final ones below.
std::unordered_map<int, int> access_counts;
for(const auto &write: writes) {
++access_counts[write.first];
}
for(const auto &write: writes) {
auto &count = access_counts[write.first];
--count;
if(count) continue;
XCTAssertEqual(ram[write.first >> 1], write.second, @"Didn't find %04x at address %08x; found %04x instead, somewhere before line %ld", write.second, write.first, ram[write.first >> 1], (long)approximateLocation);
// For now, indicate only the first failure.
if(ram[write.first >> 1] != write.second) {
return NO;
}
}
writes.clear();
return YES;
}
- (void)testCase:(NSString *)name {
uint16_t ram[256 * 1024]{};
Amiga::Chipset nonChipset;
Amiga::Blitter blitter(nonChipset, ram, 256 * 1024);
NSURL *const traceURL = [[NSBundle bundleForClass:[self class]] URLForResource:name withExtension:@"json" subdirectory:@"Amiga Blitter Tests"];
NSData *const traceData = [NSData dataWithContentsOfURL:traceURL];
NSArray *const trace = [NSJSONSerialization JSONObjectWithData:traceData options:0 error:nil];
// Step 1 in developing my version of the Blitter is to make sure that I understand
// the logic; as a result the first implementation is going to be a magical thing that
// completes all Blits in a single cycle.
//
// Therefore I've had to bodge my way around the trace's record of reads and writes by
// accumulating all writes into a blob and checking them en massse at the end of a blit
// (as detected by any register work in between memory accesses, since Kickstart 1.3
// doesn't do anything off-book).
enum class State {
AwaitingWrites,
LoggingWrites
} state = State::AwaitingWrites;
WriteVector writes;
BOOL hasFailed = NO;
NSInteger arrayEntry = -1;
for(NSArray *const event in trace) {
++arrayEntry;
if(hasFailed) break;
NSString *const type = event[0];
const NSInteger param1 = [event[1] integerValue];
if([type isEqualToString:@"cread"] || [type isEqualToString:@"bread"] || [type isEqualToString:@"aread"]) {
XCTAssert(param1 < sizeof(ram) - 1);
ram[param1 >> 1] = [event[2] integerValue];
state = State::LoggingWrites;
continue;
}
if([type isEqualToString:@"write"]) {
const uint16_t value = uint16_t([event[2] integerValue]);
if(writes.empty() || writes.back().first != param1) {
writes.push_back(std::make_pair(uint32_t(param1), value));
} else {
writes.back().second = value;
}
state = State::LoggingWrites;
continue;
}
// Hackaround for testing my magical all-at-once Blitter is here.
if(state == State::LoggingWrites) {
if(![self verifyWrites:writes blitter:blitter ram:ram approximateLocation:arrayEntry]) {
hasFailed = YES;
break;
}
state = State::AwaitingWrites;
}
// Hack ends here.
if([type isEqualToString:@"bltcon0"]) {
blitter.set_control(0, param1);
continue;
}
if([type isEqualToString:@"bltcon1"]) {
blitter.set_control(1, param1);
continue;
}
if([type isEqualToString:@"bltsize"]) {
blitter.set_size(param1);
continue;
}
if([type isEqualToString:@"bltafwm"]) {
blitter.set_first_word_mask(param1);
continue;
}
if([type isEqualToString:@"bltalwm"]) {
blitter.set_last_word_mask(param1);
continue;
}
if([type isEqualToString:@"bltadat"]) {
blitter.set_data(0, param1);
continue;
}
if([type isEqualToString:@"bltbdat"]) {
blitter.set_data(1, param1);
continue;
}
if([type isEqualToString:@"bltcdat"]) {
blitter.set_data(2, param1);
continue;
}
if([type isEqualToString:@"bltamod"]) {
blitter.set_modulo<0>(param1);
continue;
}
if([type isEqualToString:@"bltbmod"]) {
blitter.set_modulo<1>(param1);
continue;
}
if([type isEqualToString:@"bltcmod"]) {
blitter.set_modulo<2>(param1);
continue;
}
if([type isEqualToString:@"bltdmod"]) {
blitter.set_modulo<3>(param1);
continue;
}
if([type isEqualToString:@"bltaptl"]) {
blitter.set_pointer<0, 0>(param1);
continue;
}
if([type isEqualToString:@"bltbptl"]) {
blitter.set_pointer<1, 0>(param1);
continue;
}
if([type isEqualToString:@"bltcptl"]) {
blitter.set_pointer<2, 0>(param1);
continue;
}
if([type isEqualToString:@"bltdptl"]) {
blitter.set_pointer<3, 0>(param1);
continue;
}
if([type isEqualToString:@"bltapth"]) {
blitter.set_pointer<0, 16>(param1);
continue;
}
if([type isEqualToString:@"bltbpth"]) {
blitter.set_pointer<1, 16>(param1);
continue;
}
if([type isEqualToString:@"bltcpth"]) {
blitter.set_pointer<2, 16>(param1);
continue;
}
if([type isEqualToString:@"bltdpth"]) {
blitter.set_pointer<3, 16>(param1);
continue;
}
NSLog(@"Unhandled type: %@", type);
XCTAssert(false);
break;
}
// Check the final set of writes.
if(!hasFailed) {
[self verifyWrites:writes blitter:blitter ram:ram approximateLocation:-1];
}
}
- (void)testGadgetToggle {
[self testCase:@"gadget toggle"];
}
- (void)testIconHighlight {
[self testCase:@"icon highlight"];
}
- (void)testKickstart13BootLogo {
[self testCase:@"kickstart13 boot logo"];
}
- (void)testSectorDecode {
[self testCase:@"sector decode"];
}
- (void)testWindowDrag {
[self testCase:@"window drag"];
}
- (void)testWindowResize {
[self testCase:@"window resize"];
}
- (void)testRAMDiskOpen {
[self testCase:@"RAM disk open"];
}
- (void)testSpots {
[self testCase:@"spots"];
}
- (void)testClock {
[self testCase:@"clock"];
}
- (void)testInclusiveFills {
[self testCase:@"inclusive fills"];
}
@end

View File

@ -35,6 +35,7 @@ extern const uint8_t CSTestMachine6502JamOpcode;
- (nonnull instancetype)init NS_UNAVAILABLE;
- (nonnull instancetype)initWithProcessor:(CSTestMachine6502Processor)processor hasCIAs:(BOOL)hasCIAs;
- (nonnull instancetype)initWithProcessor:(CSTestMachine6502Processor)processor;
- (void)setData:(nonnull NSData *)data atAddress:(uint32_t)startAddress;

View File

@ -39,25 +39,29 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg)
#pragma mark - Lifecycle
- (instancetype)initWithProcessor:(CSTestMachine6502Processor)processor {
- (instancetype)initWithProcessor:(CSTestMachine6502Processor)processor hasCIAs:(BOOL)hasCIAs {
self = [super init];
if(self) {
switch(processor) {
case CSTestMachine6502Processor6502:
_processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::T6502);
_processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::T6502, hasCIAs);
break;
case CSTestMachine6502Processor65C02:
_processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::TWDC65C02);
_processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::TWDC65C02, hasCIAs);
break;
case CSTestMachine6502Processor65816:
_processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::TWDC65816);
_processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::TWDC65816, hasCIAs);
}
}
return self;
}
- (nonnull instancetype)initWithProcessor:(CSTestMachine6502Processor)processor {
return [self initWithProcessor:processor hasCIAs:NO];
}
- (void)dealloc {
delete _processor;
}

View File

@ -0,0 +1,57 @@
//
// MintermTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 20/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include <cstdint>
#include "Minterms.hpp"
namespace {
/// Implements the Amiga minterm algorithm in a bit-by-bit form.
template <typename IntT> IntT slow_minterm(IntT a, IntT b, IntT c, uint8_t minterm) {
constexpr int top_shift = sizeof(IntT) * 8 - 1;
IntT result = 0;
for(int i = 0; i < 8*sizeof(IntT); i++) {
int index = ((c&1) << 0) | ((b&1) << 1) | ((a&1) << 2);
result >>= 1;
result |= ((minterm >> index) & 1) << top_shift;
a >>= 1;
b >>= 1;
c >>= 1;
}
return result;
}
}
@interface MintermTests: XCTestCase
@end
@implementation MintermTests
- (void)testAll {
// These three are selected just to ensure that all combinations of
// bit pattern exist in the input.
constexpr uint8_t a = 0xaa;
constexpr uint8_t b = 0xcc;
constexpr uint8_t c = 0xf0;
for(int minterm = 0; minterm < 256; minterm++) {
const uint8_t slow = slow_minterm(a, b, c, minterm);
const uint8_t fast = Amiga::apply_minterm(a, b, c, minterm);
XCTAssertEqual(slow, fast, "Mismatch found between naive and fast implementations for %02x", minterm);
}
}
@end

View File

@ -140,10 +140,10 @@ namespace {
};
[self decode:sequence];
// addic r23,r14,-4187
// lfs f23,1926(r14)
// lwz r6,21804(r14)
// lbz r6,-4054(r21)
// addic r23,r14,-4187
// lfs f23,1926(r14)
// lwz r6,21804(r14)
// lbz r6,-4054(r21)
[self assert:instructions[0] operation:Operation::addic rD:23 rA:14 simm:-4187];
[self assert:instructions[1] operation:Operation::lfs frD:23 rA:14 d:1926];
[self assert:instructions[2] operation:Operation::lwz rD:6 rA:14 d:21804];
@ -152,7 +152,7 @@ namespace {
// .long 0xf8c2e801
// .long 0xe83d5cdf
// .long 0x7fa51fbb
// lha r22,-22352(r14)
// lha r22,-22352(r14)
[self assertUndefined:instructions[4]];
[self assertUndefined:instructions[5]];
[self assertUndefined:instructions[6]];
@ -167,47 +167,47 @@ namespace {
// I used checks the reserved bits. For now: don't test.
// XCTAssertEqual(instructions[10].operation, Operation::Undefined);
// stfdu f27,22125(r7)
// stfdu f27,22125(r7)
// .long 0xfb29a33e
// addic r9,r12,-16557
// andi. r15,r3,6677
// addic r9,r12,-16557
// andi. r15,r3,6677
[self assert:instructions[11] operation:Operation::stfdu frS:27 rA:7 d:22125];
[self assertUndefined:instructions[12]];
[self assert:instructions[13] operation:Operation::addic rD:9 rA:12 simm:-16557];
[self assert:instructions[14] operation:Operation::andi_ rA:15 rS:3 uimm:6677];
// lha r3,28689(r27)
// subfic r8,r7,2316
// lfdu f16,-27810(r4)
// oris r14,r1,17994
// lha r3,28689(r27)
// subfic r8,r7,2316
// lfdu f16,-27810(r4)
// oris r14,r1,17994
[self assert:instructions[15] operation:Operation::lha rD:3 rA:27 d:28689];
[self assert:instructions[16] operation:Operation::subfic rD:8 rA:7 simm:2316];
[self assert:instructions[17] operation:Operation::lfdu frD:16 rA:4 d:-27810];
[self assert:instructions[18] operation:Operation::oris rA:14 rS:1 uimm:17994];
// stw r24,-28998(r30)
// stw r24,-28998(r30)
// .long 0xee396d5b
// bl 0x01011890 [disassmebled at address 0x54]
// addic r14,r12,-21862
// bl 0x01011890 [disassmebled at address 0x54]
// addic r14,r12,-21862
[self assert:instructions[19] operation:Operation::stw rS:24 rA:30 d:-28998];
[self assertUndefined:instructions[20]];
[self assert:instructions[21] operation:Operation::bx li:0x01011890 - 0x54 lk:TRUE aa:FALSE];
[self assert:instructions[22] operation:Operation::addic rD:14 rA:12 simm:-21862];
// .long 0x42b61a86 [10101]
// mulli r22,r20,29981
// lwzu r21,30436(r15)
// mulli r22,r20,29981
// lwzu r21,30436(r15)
// .long 0x151405a9
[self assertUndefined:instructions[23]];
[self assert:instructions[24] operation:Operation::mulli rD:22 rA:20 simm:29981];
[self assert:instructions[25] operation:Operation::lwzu rD:21 rA:15 d:30436];
[self assertUndefined:instructions[26]];
// lfd f16,-16363(r10)
// ori r29,r6,8093
// lfd f16,-16363(r10)
// ori r29,r6,8093
// .long 0xecff44f6
// .long 0xf2c1110e
// stb r21,25915(r6)
// stb r21,25915(r6)
[self assert:instructions[27] operation:Operation::lfd frD:16 rA:10 d:-16363];
[self assert:instructions[28] operation:Operation::ori rA:29 rS:6 uimm:8093];
[self assertUndefined:instructions[29]];
@ -228,35 +228,35 @@ namespace {
};
[self decode:sequence];
// stw r1,11694(r5)
// andis. r9,r1,60948
// ori r9,r12,13756
// stfsu f22,19184(r9)
// stw r1,11694(r5)
// andis. r9,r1,60948
// ori r9,r12,13756
// stfsu f22,19184(r9)
[self assert:instructions[0] operation:Operation::stw rS:1 rA:5 d:11694];
[self assert:instructions[1] operation:Operation::andis_ rA:9 rS:1 uimm:60948];
[self assert:instructions[2] operation:Operation::ori rA:9 rS:12 uimm:13756];
[self assert:instructions[3] operation:Operation::stfsu frS:22 rA:9 d:19184];
// lmw r16,10591(r29)
// oris r30,r4,14441
// xoris r15,r15,29740
// rlwnm r4,r3,r25,23,7
// lmw r16,10591(r29)
// oris r30,r4,14441
// xoris r15,r15,29740
// rlwnm r4,r3,r25,23,7
[self assert:instructions[4] operation:Operation::lmw rD:16 rA:29 d:10591];
[self assert:instructions[5] operation:Operation::oris rA:30 rS:4 uimm:14441];
[self assert:instructions[6] operation:Operation::xoris rA:15 rS:15 uimm:29740];
[self assert:instructions[7] operation:Operation::rlwnmx rA:4 rS:3 rB:25 mb:23 me:7 rc:FALSE];
// andis. r13,r17,23022
// rlwinm. r28,r18,16,6,4
// lfsu f30,-24703(r5)
// lfdu f16,22525(r17)
// andis. r13,r17,23022
// rlwinm. r28,r18,16,6,4
// lfsu f30,-24703(r5)
// lfdu f16,22525(r17)
[self assert:instructions[8] operation:Operation::andis_ rA:13 rS:17 uimm:23022];
[self assert:instructions[9] operation:Operation::rlwinmx rA:28 rS:18 rB:16 mb:6 me:4 rc:TRUE];
[self assert:instructions[10] operation:Operation::lfsu frD:30 rA:5 d:-24703];
[self assert:instructions[11] operation:Operation::lfdu frD:16 rA:17 d:22525];
// lfd f3,-4263(r10)
// lwz r9,22658(r18)
// lfd f3,-4263(r10)
// lwz r9,22658(r18)
// .long 0x1336fad6
// .long 0xe1ddfa2b
[self assert:instructions[12] operation:Operation::lfd frD:3 rA:10 d:-4263];
@ -266,34 +266,34 @@ namespace {
// .long 0x18c60357
// .long 0x4c122cb5
// lfdu f5,-2231(r17)
// stfd f30,-5181(r28)
// lfdu f5,-2231(r17)
// stfd f30,-5181(r28)
[self assertUndefined:instructions[16]];
[self assertUndefined:instructions[17]];
[self assert:instructions[18] operation:Operation::lfdu frD:5 rA:17 d:-2231];
[self assert:instructions[19] operation:Operation::stfd frD:30 rA:28 d:-5181];
// twi 30,r6,391
// twi 30,r6,391
// .long 0x117eb911
// lwz r1,19523(r19)
// lwz r1,19523(r19)
// .long 0xe65371e8
[self assert:instructions[20] operation:Operation::twi to:30 rA:6 simm:391];
[self assertUndefined:instructions[21]];
[self assert:instructions[22] operation:Operation::lwz rD:1 rA:19 d:19523];
[self assertUndefined:instructions[23]];
// lhz r2,-14003(r7)
// lhz r2,-14003(r7)
// .long 0xe671dd0b
// .long 0xe07992bb
// xori r19,r17,12264
// xori r19,r17,12264
[self assert:instructions[24] operation:Operation::lhz rD:2 rA:7 d:-14003];
[self assertUndefined:instructions[25]];
[self assertUndefined:instructions[26]];
[self assert:instructions[27] operation:Operation::xori rA:19 rS:17 uimm:12264];
// .long 0xfc361c6b
// rlwnm r11,r20,r11,8,20
// sth r21,18978(r22)
// rlwnm r11,r20,r11,8,20
// sth r21,18978(r22)
// .long 0x45dd156
// [self assertUndefined:instructions[28]]; // Disabled due to reserved field; I'm decoding this as faddx.
[self assert:instructions[29] operation:Operation::rlwnmx rA:11 rS:20 rB:11 mb:8 me:20 rc:FALSE];
@ -314,26 +314,26 @@ namespace {
};
[self decode:sequence];
// stmw r5,13600(r15)
// stmw r5,13600(r15)
// .long 0xfa9df12d
// lfsu f17,-4150(r17)
// lhz r31,-3063(r7)
// lfsu f17,-4150(r17)
// lhz r31,-3063(r7)
[self assert:instructions[0] operation:Operation::stmw rS:5 rA:15 d:13600];
[self assertUndefined:instructions[1]];
[self assert:instructions[2] operation:Operation::lfsu frD:17 rA:17 d:-4150];
[self assert:instructions[3] operation:Operation::lhz rD:31 rA:7 d:-3063];
// addis r14,r28,-23949
// addis r7,r27,9037
// rlwinm. r29,r8,24,12,18
// lbzu r0,3895(r26)
// addis r14,r28,-23949
// addis r7,r27,9037
// rlwinm. r29,r8,24,12,18
// lbzu r0,3895(r26)
[self assert:instructions[4] operation:Operation::addis rD:14 rA:28 simm:-23949];
[self assert:instructions[5] operation:Operation::addis rD:7 rA:27 simm:9037];
[self assert:instructions[6] operation:Operation::rlwinmx rA:29 rS:8 rB:24 mb:12 me:18 rc:TRUE];
[self assert:instructions[7] operation:Operation::lbzu rD:0 rA:26 d:3895];
// rlmi. r28,r25,r21,6,13
// lfdu f16,-13282(r8)
// rlmi. r28,r25,r21,6,13
// lfdu f16,-13282(r8)
// .long 0x7b1dfd3a
// .long 0xf19aee7c
[self assert:instructions[8] operation:Operation::rlmix rA:28 rS:25 rB:21 mb:6 me:13 rc:TRUE];
@ -341,9 +341,9 @@ namespace {
[self assertUndefined:instructions[10]];
[self assertUndefined:instructions[11]];
// rlwimi. r8,r22,10,11,20
// lfsu f20,-16191(r1)
// stfs f29,-603(r17)
// rlwimi. r8,r22,10,11,20
// lfsu f20,-16191(r1)
// stfs f29,-603(r17)
// .long 0xe2b401cb
[self assert:instructions[12] operation:Operation::rlwimix rA:8 rS:22 rB:10 mb:11 me:20 rc:TRUE];
[self assert:instructions[13] operation:Operation::lfsu frD:20 rA:1 d:-16191];
@ -351,36 +351,36 @@ namespace {
[self assertUndefined:instructions[15]];
// .long 0x433cb83d
// rlwinm. r1,r2,5,29,0
// rlwimi r13,r25,12,9,5
// rlwinm. r1,r2,5,29,0
// rlwimi r13,r25,12,9,5
// .long 0xb3117c5
[self assertUndefined:instructions[16]];
[self assert:instructions[17] operation:Operation::rlwinmx rA:1 rS:2 rB:5 mb:29 me:0 rc:TRUE];
[self assert:instructions[18] operation:Operation::rlwimix rA:13 rS:25 rB:12 mb:9 me:5 rc:FALSE];
[self assertUndefined:instructions[19]];
// stb r4,17594(r1)
// lfsu f29,27344(r9)
// cmpli cr0,1,r19,5236
// rlwinm. r0,r17,24,13,19
// stb r4,17594(r1)
// lfsu f29,27344(r9)
// cmpli cr0,1,r19,5236
// rlwinm. r0,r17,24,13,19
[self assert:instructions[20] operation:Operation::stb rS:4 rA:1 d:17594];
[self assert:instructions[21] operation:Operation::lfsu frD:29 rA:9 d:27344];
[self assert:instructions[22] operation:Operation::cmpli crfD:0 l:TRUE rA:19 uimm:5236];
[self assert:instructions[23] operation:Operation::rlwinmx rA:0 rS:17 rB:24 mb:13 me:19 rc:TRUE];
// lha r24,9735(r10)
// lha r24,9735(r10)
// .long 0xe826acf4
// beq+ cr5,0xffffffffffff91bc [at address 0x68]
// oris r17,r3,53407
// beq+ cr5,0xffffffffffff91bc [at address 0x68]
// oris r17,r3,53407
[self assert:instructions[24] operation:Operation::lha rD:24 rA:10 d:9735];
[self assertUndefined:instructions[25]];
[self assert:instructions[26] operation:Operation::bcx bo:12 bi:22 bd:0xffff91bc - 0x68 lk:FALSE aa:FALSE];
[self assert:instructions[27] operation:Operation::oris rA:17 rS:3 uimm:53407];
// xori r5,r17,61519
// bl 0xfffffffffe1599e0 [at address 0x74]
// dozi r25,r12,-26897
// lha r25,29097(r17)
// xori r5,r17,61519
// bl 0xfffffffffe1599e0 [at address 0x74]
// dozi r25,r12,-26897
// lha r25,29097(r17)
[self assert:instructions[28] operation:Operation::xori rA:5 rS:17 uimm:61519];
[self assert:instructions[29] operation:Operation::bx li:0xfe1599e0 - 0x74 lk:TRUE aa:FALSE];
[self assert:instructions[30] operation:Operation::dozi rD:25 rA:12 simm:-26897];

View File

@ -35,6 +35,10 @@ class RAM68000: public CPU::MC68000::BusHandler {
set_processor_state(state);
}
uint32_t initial_pc() const {
return 0x1000;
}
void set_program(const std::vector<uint16_t> &program) {
memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t));

View File

@ -9,6 +9,14 @@
import XCTest
import Foundation
// Unused Lorenz tests:
//
// cpuport (tests the 6510 IO ports, I assume);
// cputiming (unclear what this times against, probably requires VIC-II delays?);
// mmu (presumably requires C64 paging?)
// mmufetch (as above)
// nmi
class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler {
// MARK: - 6502 Tests
@ -226,6 +234,87 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler {
/* The NOP tests also don't apply; the 65816 has only one, well-defined NOP. */
// MARK: - CIA Tests
func testNMI() {
// TODO: Requires serial register.
runTest("nmi", suffixes: [""], processor: .processor6502)
}
func testCIA1TA() {
runTest("cia1ta", suffixes: [""], processor: .processor6502)
}
func testCIA1TB() {
runTest("cia1tb", suffixes: [""], processor: .processor6502)
}
func testCIA1TAB() {
// Tests Port B timer output. TODO.
runTest("cia1tab", suffixes: [""], processor: .processor6502)
}
func testCIA1TB123() {
// Tests 6526 TOD clock. TODO.
runTest("cia1tb123", suffixes: [""], processor: .processor6502)
}
func testCIA2TA() {
runTest("cia2ta", suffixes: [""], processor: .processor6502)
}
func testCIA2TB() {
runTest("cia2tb", suffixes: [""], processor: .processor6502)
}
func testCIA2TB123() {
runTest("cia1tb123", suffixes: [""], processor: .processor6502)
}
func testCIA1PB6() {
runTest("cia1pb6", suffixes: [""], processor: .processor6502)
}
func testCIA1PB7() {
runTest("cia1pb7", suffixes: [""], processor: .processor6502)
}
func testCIA2PB6() {
runTest("cia2pb6", suffixes: [""], processor: .processor6502)
}
func testCIA2PB7() {
runTest("cia2pb7", suffixes: [""], processor: .processor6502)
}
func testCIALoadTH() {
runTest("loadth", suffixes: [""], processor: .processor6502)
}
func testCIACntPhi2() {
runTest("cnto2", suffixes: [""], processor: .processor6502)
}
func testCIAICR() {
runTest("icr01", suffixes: [""], processor: .processor6502)
}
func testCIAIMR() {
runTest("imr", suffixes: [""], processor: .processor6502)
}
func testCIAFLIPOS() {
runTest("flipos", suffixes: [""], processor: .processor6502)
}
func testCIAOneShot() {
runTest("oneshot", suffixes: [""], processor: .processor6502)
}
func testCIACNTDefault() {
runTest("cntdef", suffixes: [""], processor: .processor6502)
}
// MARK: - Collections
func testTransfers(processor: CSTestMachine6502Processor) {
@ -284,10 +373,13 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler {
fileprivate func runTest(_ name: String, processor: CSTestMachine6502Processor) {
var machine: CSTestMachine6502!
if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: nil) {
if let testData = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
let bundle = Bundle(for: type(of: self))
let mainBundle = Bundle.main
if let testFilename = bundle.url(forResource: name, withExtension: nil),
let kernelFilename = mainBundle.url(forResource: "kernal.901227-02", withExtension: "bin", subdirectory: "ROMImages/Commodore64") {
if let testData = try? Data(contentsOf: testFilename), let kernelData = try? Data(contentsOf: kernelFilename) {
machine = CSTestMachine6502(processor: processor)
machine = CSTestMachine6502(processor: processor, hasCIAs: true)
machine.trapHandler = self
output = ""
@ -296,9 +388,18 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler {
let contents = testData.subdata(in: 2 ..< testData.count)
machine.setData(contents, atAddress: loadAddress)
machine.setData(kernelData, atAddress: 0xe000)
// Cf. http://www.softwolves.com/arkiv/cbm-hackers/7/7114.html for the steps being taken here.
// Signal in-border, set up NMI and IRQ vectors as defaults.
machine.setValue(0xff, forAddress: 0xd011)
machine.setValue(0x66, forAddress: 0x0316)
machine.setValue(0xfe, forAddress: 0x0317)
machine.setValue(0x31, forAddress: 0x0314)
machine.setValue(0xea, forAddress: 0x0315)
// Initialise memory locations as instructed.
machine.setValue(0x00, forAddress: 0x0002)
machine.setValue(0x00, forAddress: 0xa002)
@ -345,7 +446,7 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler {
}
if machine == nil {
NSException(name: NSExceptionName(rawValue: "Failed Test"), reason: "Couldn't load file \(name)", userInfo: nil).raise()
NSException(name: NSExceptionName(rawValue: "Failed Test"), reason: "Couldn't load kernel, or file \(name)", userInfo: nil).raise()
}
while machine.value(for: .lastOperationAddress) != 0xe16f && !machine.isJammed {

View File

@ -153,29 +153,29 @@ namespace {
// 63 instructions are expected.
XCTAssertEqual(instructions.size(), 63);
// sub $0xea77,%ax
// jb 0x00000001
// dec %bx
// mov $0x28,%ch
// sub $0xea77,%ax
// jb 0x00000001
// dec %bx
// mov $0x28,%ch
[self assert:instructions[0] operation:Operation::SUB size:2 operand:0xea77 destination:Source::AX];
[self assert:instructions[1] operation:Operation::JB displacement:0xfffc];
[self assert:instructions[2] operation:Operation::DEC size:2 source:Source::BX destination:Source::BX];
[self assert:instructions[3] operation:Operation::MOV size:1 operand:0x28 destination:Source::CH];
// ret
// lret $0x4826
// lret $0x4826
// [[ omitted: gs insw (%dx),%es:(%di) ]]
// jnp 0xffffffaf
// ret $0x4265
// jnp 0xffffffaf
// ret $0x4265
[self assert:instructions[4] operation:Operation::RETN];
[self assert:instructions[5] operation:Operation::RETF operand:0x4826];
[self assert:instructions[6] operation:Operation::JNP displacement:0xff9f];
[self assert:instructions[7] operation:Operation::RETN operand:0x4265];
// dec %si
// out %ax,(%dx)
// jo 0x00000037
// xchg %ax,%sp
// dec %si
// out %ax,(%dx)
// jo 0x00000037
// xchg %ax,%sp
[self assert:instructions[8] operation:Operation::DEC size:2 source:Source::SI destination:Source::SI];
[self assert:instructions[9] operation:Operation::OUT size:2 source:Source::AX destination:Source::DX];
[self assert:instructions[10] operation:Operation::JO displacement:0x20];
@ -183,7 +183,7 @@ namespace {
// ODA has:
// c4 (bad)
// d4 93 aam $0x93
// d4 93 aam $0x93
//
// That assumes that upon discovering that the d4 doesn't make a valid LES,
// it can become an instruction byte. I'm not persuaded. So I'm taking:
@ -193,58 +193,58 @@ namespace {
[self assert:instructions[12] operation:Operation::Invalid];
[self assert:instructions[13] operation:Operation::XCHG size:2 source:Source::AX destination:Source::BX];
// inc %bx
// cmp $0x8e,%al
// [[ omitted: push $0x65 ]]
// sbb 0x45(%bx,%si),%bh
// adc %bh,0x3c(%bx)
// inc %bx
// cmp $0x8e,%al
// [[ omitted: push $0x65 ]]
// sbb 0x45(%bx,%si),%bh
// adc %bh,0x3c(%bx)
[self assert:instructions[14] operation:Operation::INC size:2 source:Source::BX destination:Source::BX];
[self assert:instructions[15] operation:Operation::CMP size:1 operand:0x8e destination:Source::AL];
[self assert:instructions[16] operation:Operation::SBB size:1 source:Source::IndBXPlusSI destination:Source::BH displacement:0x45];
[self assert:instructions[17] operation:Operation::ADC size:1 source:Source::BH destination:Source::IndBX displacement:0x3c];
// sbb %bx,0x16(%bp,%si)
// xor %sp,0x2c(%si)
// out %ax,$0xc6
// jge 0xffffffe0
// sbb %bx,0x16(%bp,%si)
// xor %sp,0x2c(%si)
// out %ax,$0xc6
// jge 0xffffffe0
[self assert:instructions[18] operation:Operation::SBB size:2 source:Source::BX destination:Source::IndBPPlusSI displacement:0x16];
[self assert:instructions[19] operation:Operation::XOR size:2 source:Source::SP destination:Source::IndSI displacement:0x2c];
[self assert:instructions[20] operation:Operation::OUT size:2 source:Source::AX destination:Source::DirectAddress operand:0xc6];
[self assert:instructions[21] operation:Operation::JNL displacement:0xffb0];
// mov $0x49,%ch
// [[ omitted: addr32 popa ]]
// mov $0xcbc0,%dx
// adc $0x7e,%al
// jno 0x0000000b
// mov $0x49,%ch
// [[ omitted: addr32 popa ]]
// mov $0xcbc0,%dx
// adc $0x7e,%al
// jno 0x0000000b
[self assert:instructions[22] operation:Operation::MOV size:1 operand:0x49 destination:Source::CH];
[self assert:instructions[23] operation:Operation::MOV size:2 operand:0xcbc0 destination:Source::DX];
[self assert:instructions[24] operation:Operation::ADC size:1 operand:0x7e destination:Source::AL];
[self assert:instructions[25] operation:Operation::JNO displacement:0xffd0];
// push %ax
// js 0x0000007b
// add (%di),%bx
// in $0xc9,%ax
// push %ax
// js 0x0000007b
// add (%di),%bx
// in $0xc9,%ax
[self assert:instructions[26] operation:Operation::PUSH size:2 source:Source::AX];
[self assert:instructions[27] operation:Operation::JS displacement:0x3d];
[self assert:instructions[28] operation:Operation::ADD size:2 source:Source::IndDI destination:Source::BX];
[self assert:instructions[29] operation:Operation::IN size:2 source:Source::DirectAddress destination:Source::AX operand:0xc9];
// xchg %ax,%di
// xchg %ax,%di
// ret
// fwait
// out %al,$0xd3
// out %al,$0xd3
[self assert:instructions[30] operation:Operation::XCHG size:2 source:Source::AX destination:Source::DI];
[self assert:instructions[31] operation:Operation::RETN];
[self assert:instructions[32] operation:Operation::WAIT];
[self assert:instructions[33] operation:Operation::OUT size:1 source:Source::AL destination:Source::DirectAddress operand:0xd3];
// [[ omitted: insb (%dx),%es:(%di) ]]
// pop %ax
// dec %bp
// jbe 0xffffffcc
// inc %sp
// [[ omitted: insb (%dx),%es:(%di) ]]
// pop %ax
// dec %bp
// jbe 0xffffffcc
// inc %sp
[self assert:instructions[34] operation:Operation::POP size:2 destination:Source::AX];
[self assert:instructions[35] operation:Operation::DEC size:2 source:Source::BP destination:Source::BP];
[self assert:instructions[36] operation:Operation::JBE displacement:0xff80];
@ -252,68 +252,68 @@ namespace {
// (bad)
// lahf
// movsw %ds:(%si),%es:(%di)
// mov $0x12a1,%bp
// movsw %ds:(%si),%es:(%di)
// mov $0x12a1,%bp
[self assert:instructions[38] operation:Operation::Invalid];
[self assert:instructions[39] operation:Operation::LAHF];
[self assert:instructions[40] operation:Operation::MOVS size:2];
[self assert:instructions[41] operation:Operation::MOV size:2 operand:0x12a1 destination:Source::BP];
// lds (%bx,%di),%bp
// lds (%bx,%di),%bp
// [[ omitted: leave ]]
// sahf
// fdiv %st(3),%st
// fdiv %st(3),%st
// iret
[self assert:instructions[42] operation:Operation::LDS size:2];
[self assert:instructions[43] operation:Operation::SAHF];
[self assert:instructions[44] operation:Operation::ESC];
[self assert:instructions[45] operation:Operation::IRET];
// xchg %ax,%dx
// cmp %bx,-0x70(%di)
// adc $0xb8c3,%ax
// lods %ds:(%si),%ax
// xchg %ax,%dx
// cmp %bx,-0x70(%di)
// adc $0xb8c3,%ax
// lods %ds:(%si),%ax
[self assert:instructions[46] operation:Operation::XCHG size:2 source:Source::AX destination:Source::DX];
[self assert:instructions[47] operation:Operation::CMP size:2 source:Source::BX destination:Source::IndDI displacement:0xff90];
[self assert:instructions[48] operation:Operation::ADC size:2 operand:0xb8c3 destination:Source::AX];
[self assert:instructions[49] operation:Operation::LODS size:2];
// call 0x0000172d
// dec %dx
// mov $0x9e,%al
// call 0x0000172d
// dec %dx
// mov $0x9e,%al
// stc
[self assert:instructions[50] operation:Operation::CALLD operand:0x16c8];
[self assert:instructions[51] operation:Operation::DEC size:2 source:Source::DX destination:Source::DX];
[self assert:instructions[52] operation:Operation::MOV size:1 operand:0x9e destination:Source::AL];
[self assert:instructions[53] operation:Operation::STC];
// mov $0xea56,%di
// dec %si
// mov $0xea56,%di
// dec %si
// std
// in $0x5a,%al
// in $0x5a,%al
[self assert:instructions[54] operation:Operation::MOV size:2 operand:0xea56 destination:Source::DI];
[self assert:instructions[55] operation:Operation::DEC size:2 source:Source::SI destination:Source::SI];
[self assert:instructions[56] operation:Operation::STD];
[self assert:instructions[57] operation:Operation::IN size:1 source:Source::DirectAddress destination:Source::AL operand:0x5a];
// and 0x5b2c(%bp,%si),%bp
// sub %dl,%dl
// negw 0x18(%bx)
// xchg %dl,0x6425(%bx,%si)
// and 0x5b2c(%bp,%si),%bp
// sub %dl,%dl
// negw 0x18(%bx)
// xchg %dl,0x6425(%bx,%si)
[self assert:instructions[58] operation:Operation::AND size:2 source:Source::IndBPPlusSI destination:Source::BP displacement:0x5b2c];
[self assert:instructions[59] operation:Operation::SUB size:1 source:Source::DL destination:Source::DL];
[self assert:instructions[60] operation:Operation::NEG size:2 source:Source::IndBX destination:Source::IndBX displacement:0x18];
[self assert:instructions[61] operation:Operation::XCHG size:1 source:Source::IndBXPlusSI destination:Source::DL displacement:0x6425];
// mov $0xc3,%bh
// mov $0xc3,%bh
[self assert:instructions[62] operation:Operation::MOV size:1 operand:0xc3 destination:Source::BH];
}
- (void)test83 {
[self decode:{
0x83, 0x10, 0x80, // adcw $0xff80,(%bx,%si)
0x83, 0x3b, 0x04, // cmpw $0x4,(%bp,%di)
0x83, 0x2f, 0x09, // subw $0x9,(%bx)
0x83, 0x10, 0x80, // adcw $0xff80,(%bx,%si)
0x83, 0x3b, 0x04, // cmpw $0x4,(%bp,%di)
0x83, 0x2f, 0x09, // subw $0x9,(%bx)
}];
XCTAssertEqual(instructions.size(), 3);

View File

@ -38,6 +38,7 @@ SOURCES += \
\
$$SRC/Analyser/Static/*.cpp \
$$SRC/Analyser/Static/Acorn/*.cpp \
$$SRC/Analyser/Static/Amiga/*.cpp \
$$SRC/Analyser/Static/AmstradCPC/*.cpp \
$$SRC/Analyser/Static/AppleII/*.cpp \
$$SRC/Analyser/Static/AppleIIgs/*.cpp \
@ -81,6 +82,7 @@ SOURCES += \
$$SRC/InstructionSets/x86/*.cpp \
\
$$SRC/Machines/*.cpp \
$$SRC/Machines/Amiga/*.cpp \
$$SRC/Machines/AmstradCPC/*.cpp \
$$SRC/Machines/Apple/ADB/*.cpp \
$$SRC/Machines/Apple/AppleII/*.cpp \
@ -159,6 +161,7 @@ HEADERS += \
\
$$SRC/Analyser/Static/*.hpp \
$$SRC/Analyser/Static/Acorn/*.hpp \
$$SRC/Analyser/Static/Amiga/*.hpp \
$$SRC/Analyser/Static/AmstradCPC/*.hpp \
$$SRC/Analyser/Static/AppleII/*.hpp \
$$SRC/Analyser/Static/AppleIIgs/*.hpp \
@ -212,6 +215,7 @@ HEADERS += \
$$SRC/InstructionSets/x86/*.hpp \
\
$$SRC/Machines/*.hpp \
$$SRC/Machines/Amiga/*.hpp \
$$SRC/Machines/AmstradCPC/*.hpp \
$$SRC/Machines/Apple/ADB/*.hpp \
$$SRC/Machines/Apple/AppleII/*.hpp \

View File

@ -22,6 +22,7 @@ SOURCES += glob.glob('../../Analyser/Dynamic/MultiMachine/Implementation/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Acorn/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Amiga/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AmstradCPC/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AppleII/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AppleIIgs/*.cpp')
@ -68,6 +69,7 @@ SOURCES += glob.glob('../../InstructionSets/PowerPC/*.cpp')
SOURCES += glob.glob('../../InstructionSets/x86/*.cpp')
SOURCES += glob.glob('../../Machines/*.cpp')
SOURCES += glob.glob('../../Machines/Amiga/*.cpp')
SOURCES += glob.glob('../../Machines/AmstradCPC/*.cpp')
SOURCES += glob.glob('../../Machines/Apple/ADB/*.cpp')
SOURCES += glob.glob('../../Machines/Apple/AppleII/*.cpp')

View File

@ -15,9 +15,10 @@
#include "../../../Concurrency/AsyncTaskQueue.hpp"
#include <algorithm>
#include <mutex>
#include <cstring>
#include <cassert>
#include <cmath>
#include <cstring>
#include <mutex>
namespace Outputs {
namespace Speaker {
@ -293,7 +294,7 @@ template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_s
friend BaseT;
using BaseT::process;
std::atomic<uint16_t> scale_ = 32767;
std::atomic<int> scale_ = 65536;
int get_scale() {
return scale_;
}
@ -305,13 +306,14 @@ template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_s
}
void get_samples(size_t length, int16_t *target) {
memcpy(target, buffer_, length);
buffer_ += length;
const auto word_length = length * (1 + is_stereo);
memcpy(target, buffer_, word_length * sizeof(int16_t));
buffer_ += word_length;
}
public:
void set_output_volume(float volume) final {
scale_.store(uint16_t(std::clamp(volume * 65535.0f, 0.0f, 65535.0f)));
scale_.store(int(std::clamp(volume * 65536.0f, 0.0f, 65536.0f)));
}
bool get_is_stereo() final {
@ -320,10 +322,16 @@ template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_s
/*!
Filters and posts onward the provided buffer, on the calling thread.
@param buffer The source for samples.
@param length The number of samples provided; in mono this will be the number of int16_ts
it is safe to read from @c buffer, and in stereo it will be half the number it is a count
of the number of time points at which audio was sampled.
*/
void push(const int16_t *buffer, size_t length) {
buffer_ = buffer;
process(length);
assert(buffer_ == buffer + (length * (1 + is_stereo)));
}
};

View File

@ -11,56 +11,102 @@
#include <algorithm>
#include <cstring>
//#define BE_NOISY
#include "../../../Components/6526/6526.hpp"
//#define BE_NOISY
using namespace CPU::MOS6502;
namespace {
static constexpr bool LogAllReads = false;
static constexpr bool LogAllWrites = false;
static constexpr bool LogCIAAccesses = true;
static constexpr bool LogProgramCounter = false;
using Type = CPU::MOS6502Esque::Type;
template <Type type> class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
template <Type type, bool has_cias> class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler {
public:
ConcreteAllRAMProcessor(size_t memory_size) :
AllRAMProcessor(memory_size),
mos6502_(*this) {
mos6502_(*this),
cia1_(cia1_handler_),
cia2_(cia2_handler_) {
mos6502_.set_power_on(false);
}
inline Cycles perform_bus_operation(BusOperation operation, uint32_t address, uint8_t *value) {
timestamp_ += Cycles(1);
if constexpr (has_cias) {
cia1_.run_for(HalfCycles(2));
cia2_.run_for(HalfCycles(2));
}
if(isAccessOperation(operation)) {
if(operation == BusOperation::ReadOpcode) {
#ifdef BE_NOISY
printf("[%04x] %02x a:%04x x:%04x y:%04x p:%02x s:%02x\n", address, memory_[address],
mos6502_.get_value_of_register(Register::A),
mos6502_.get_value_of_register(Register::X),
mos6502_.get_value_of_register(Register::Y),
mos6502_.get_value_of_register(Register::Flags) & 0xff,
mos6502_.get_value_of_register(Register::StackPointer) & 0xff);
#endif
if constexpr (LogProgramCounter) {
printf("[%04x] %02x a:%04x x:%04x y:%04x p:%02x s:%02x\n", address, memory_[address],
mos6502_.get_value_of_register(Register::A),
mos6502_.get_value_of_register(Register::X),
mos6502_.get_value_of_register(Register::Y),
mos6502_.get_value_of_register(Register::Flags) & 0xff,
mos6502_.get_value_of_register(Register::StackPointer) & 0xff);
}
check_address_for_trap(address);
--instructions_;
}
if(isReadOperation(operation)) {
*value = memory_[address];
#ifdef BE_NOISY
// if((address&0xff00) == 0x100) {
printf("%04x -> %02x\n", address, *value);
// }
#endif
if constexpr (has_cias) {
if((address & 0xff00) == 0xdc00) {
*value = cia1_.read(address);
if constexpr (LogCIAAccesses) {
printf("[%d] CIA1: %04x -> %02x\n", timestamp_.as<int>(), address, *value);
}
} else if((address & 0xff00) == 0xdd00) {
*value = cia2_.read(address);
if constexpr (LogCIAAccesses) {
printf("[%d] CIA2: %04x -> %02x\n", timestamp_.as<int>(), address, *value);
}
}
}
if constexpr (LogAllReads) {
// if((address&0xff00) == 0x100) {
printf("%04x -> %02x\n", address, *value);
// }
}
} else {
memory_[address] = *value;
#ifdef BE_NOISY
// if((address&0xff00) == 0x100) {
printf("%04x <- %02x\n", address, *value);
// }
#endif
if constexpr (has_cias) {
if((address & 0xff00) == 0xdc00) {
cia1_.write(address, *value);
if constexpr (LogCIAAccesses) {
printf("[%d] CIA1: %04x <- %02x\n", timestamp_.as<int>(), address, *value);
}
} else if((address & 0xff00) == 0xdd00) {
cia2_.write(address, *value);
if constexpr (LogCIAAccesses) {
printf("[%d] CIA2: %04x <- %02x\n", timestamp_.as<int>(), address, *value);
}
}
}
if constexpr (LogAllWrites) {
// if((address&0xff00) == 0x100) {
printf("%04x <- %02x\n", address, *value);
// }
}
}
}
mos6502_.set_irq_line(cia1_.get_interrupt_line());
mos6502_.set_nmi_line(cia2_.get_interrupt_line());
return Cycles(1);
}
@ -98,12 +144,21 @@ template <Type type> class ConcreteAllRAMProcessor: public AllRAMProcessor, publ
private:
CPU::MOS6502Esque::Processor<type, ConcreteAllRAMProcessor, false> mos6502_;
int instructions_ = 0;
class PortHandler: public MOS::MOS6526::PortHandler {};
PortHandler cia1_handler_, cia2_handler_;
MOS::MOS6526::MOS6526<PortHandler, MOS::MOS6526::Personality::P6526> cia1_, cia2_;
};
}
AllRAMProcessor *AllRAMProcessor::Processor(Type type) {
#define Bind(p) case p: return new ConcreteAllRAMProcessor<p>(type == Type::TWDC65816 ? 16*1024*1024 : 64*1024);
AllRAMProcessor *AllRAMProcessor::Processor(Type type, bool has_cias) {
#define Bind(p) \
case p: \
if(has_cias) return new ConcreteAllRAMProcessor<p, true>(type == Type::TWDC65816 ? 16*1024*1024 : 64*1024); \
else return new ConcreteAllRAMProcessor<p, false>(type == Type::TWDC65816 ? 16*1024*1024 : 64*1024); \
switch(type) {
default:
Bind(Type::T6502)

View File

@ -18,7 +18,7 @@ namespace MOS6502 {
class AllRAMProcessor:
public ::CPU::AllRAMProcessor {
public:
static AllRAMProcessor *Processor(CPU::MOS6502Esque::Type type);
static AllRAMProcessor *Processor(CPU::MOS6502Esque::Type type, bool has_cias = false);
virtual ~AllRAMProcessor() {}
virtual void run_for(const Cycles cycles) = 0;
@ -29,6 +29,8 @@ class AllRAMProcessor:
virtual uint16_t get_value_of_register(Register r) = 0;
virtual void set_value_of_register(Register r, uint16_t value) = 0;
protected:
AllRAMProcessor(size_t memory_size) : ::CPU::AllRAMProcessor(memory_size) {}
};

View File

@ -0,0 +1,7 @@
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
kernal.901227-02.bin; the Commodore 64 kernel.
NB: this file is used only for a particular set of unit tests. This is not currently a Commodore 64 emulator.

View File

@ -28,7 +28,8 @@ ClockingHint::Preference Controller::preferred_clocking() const {
// Nominate RealTime clocking if any drive currently wants any clocking whatsoever.
// Otherwise, ::None will do.
for(auto &drive: drives_) {
if(drive.preferred_clocking() != ClockingHint::Preference::None) {
const auto preferred_clocking = drive->preferred_clocking();
if(preferred_clocking != ClockingHint::Preference::None) {
return ClockingHint::Preference::RealTime;
}
}
@ -40,7 +41,7 @@ ClockingHint::Preference Controller::preferred_clocking() const {
void Controller::run_for(const Cycles cycles) {
for(auto &drive: drives_) {
drive.run_for(cycles);
drive->run_for(cycles);
}
empty_drive_.run_for(cycles);
}
@ -109,7 +110,7 @@ void Controller::set_drive(int index_mask) {
index_mask >>= 1;
++index;
}
drive_ = &drives_[index];
drive_ = drives_[index].get();
}
get_drive().set_event_delegate(this);

View File

@ -29,9 +29,9 @@ namespace Disk {
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
*/
class Controller:
public Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
private Drive::EventDelegate,
private ClockingHint::Observer {
protected:
/*!
Constructs a @c Controller that will be run at @c clock_rate.
@ -60,8 +60,8 @@ class Controller:
Adds a new drive to the drive list, returning its index.
*/
template<typename... Args> size_t emplace_drive(Args&&... args) {
drives_.emplace_back(std::forward<Args>(args)...);
drives_.back().set_clocking_hint_observer(this);
drives_.emplace_back(new Drive(std::forward<Args>(args)...));
drives_.back()->set_clocking_hint_observer(this);
return drives_.size() - 1;
}
@ -70,8 +70,7 @@ class Controller:
*/
template<typename... Args> size_t emplace_drives(size_t count, Args&&... args) {
while(count--) {
drives_.emplace_back(std::forward<Args>(args)...);
drives_.back().set_clocking_hint_observer(this);
emplace_drive(std::forward<Args>(args)...);
}
return drives_.size() - 1;
}
@ -122,13 +121,13 @@ class Controller:
Drive &get_drive();
Drive &get_drive(size_t index) {
return drives_[index];
return *drives_[index];
}
void for_all_drives(const std::function<void(Drive &, size_t)> &func) {
size_t index = 0;
for(auto &drive: drives_) {
func(drive, index);
func(*drive, index);
++index;
}
}
@ -149,7 +148,7 @@ class Controller:
friend DigitalPhaseLockedLoop<Controller>;
Drive empty_drive_;
std::vector<Drive> drives_;
std::vector<std::unique_ptr<Drive>> drives_;
Drive *drive_;
int drive_selection_mask_ = 0xff;

View File

@ -0,0 +1,187 @@
//
// AmigaADF.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "AmigaADF.hpp"
#include "../../Encodings/MFM/Constants.hpp"
#include "../../Encodings/MFM/Encoder.hpp"
#include "../../../../Numeric/BitSpread.hpp"
#include "../../Track/PCMTrack.hpp"
#include <type_traits>
using namespace Storage::Disk;
namespace {
/// Builds a buffer containing the bytes between @c begin and @c end split up so that the nibbles in the first half of the buffer
/// consist of the odd bits of the source bytes — b1, b3, b5 and b7 — ordered so that most-significant nibbles come before
/// least-significant ones, and the second half of the buffer contains the even bits.
///
/// Nibbles are written to @c first; it is assumed that an even number of source bytes have been supplied.
template <typename IteratorT, class OutputIt> void encode_block(IteratorT begin, IteratorT end, OutputIt first) {
// Parse 1: combine odd bits.
auto cursor = begin;
while(cursor != end) {
auto source = uint8_t(
((*cursor & 0x02) << 3) |
((*cursor & 0x08) << 2) |
((*cursor & 0x20) << 1) |
((*cursor & 0x80) << 0)
);
++cursor;
source |=
((*cursor & 0x02) >> 1) |
((*cursor & 0x08) >> 2) |
((*cursor & 0x20) >> 3) |
((*cursor & 0x80) >> 4);
++cursor;
*first = source;
++first;
}
// Parse 2: combine even bits.
cursor = begin;
while(cursor != end) {
auto source = uint8_t(
((*cursor & 0x01) << 4) |
((*cursor & 0x04) << 3) |
((*cursor & 0x10) << 2) |
((*cursor & 0x40) << 1)
);
++cursor;
source |=
((*cursor & 0x01) >> 0) |
((*cursor & 0x04) >> 1) |
((*cursor & 0x10) >> 2) |
((*cursor & 0x40) >> 3);
++cursor;
*first = source;
++first;
}
}
/// Construsts the Amiga-style checksum of the bytes between @c begin and @c end, which is a 32-bit exclusive OR of the source data
/// with each byte converted into a 16-bit word by inserting a 0 bit between every data bit, and then combined into 32-bit words in
/// big endian order.
template <typename IteratorT> auto checksum(IteratorT begin, IteratorT end) {
uint16_t checksum[2]{};
int offset = 0;
while(begin != end) {
const uint8_t value = *begin;
++begin;
// Do a clockless MFM encode.
const auto spread = Numeric::spread_bits(value);
checksum[offset] ^= spread;
offset ^= 1;
}
return std::array<uint8_t, 4>{
uint8_t(checksum[0] >> 8),
uint8_t(checksum[0]),
uint8_t(checksum[1] >> 8),
uint8_t(checksum[1]),
};
}
/// Obtains the Amiga-style checksum of the data between @c begin and @c end, then odd-even encodes it and writes
/// it out to @c encoder.
template <typename IteratorT> void write_checksum(IteratorT begin, IteratorT end, std::unique_ptr<Storage::Encodings::MFM::Encoder> &encoder) {
// Believe it or not, this appears to be the actual checksum algorithm on the Amiga:
//
// (1) calculate the XOR checksum of the MFM-encoded data, read as 32-bit words;
// (2) throw away the clock bits;
// (3) Take the resulting 32-bit value and perform an odd-even MFM encoding on those.
const auto raw_checksum = checksum(begin, end);
std::decay_t<decltype(raw_checksum)> encoded_checksum{};
encode_block(raw_checksum.begin(), raw_checksum.end(), encoded_checksum.begin());
encoder->add_bytes(encoded_checksum.begin(), encoded_checksum.end());
}
}
AmigaADF::AmigaADF(const std::string &file_name) :
file_(file_name) {
// Dumb validation only for now: a size check.
if(file_.stats().st_size != 901120) throw Error::InvalidFormat;
}
HeadPosition AmigaADF::get_maximum_head_position() {
return HeadPosition(80);
}
int AmigaADF::get_head_count() {
return 2;
}
std::shared_ptr<Track> AmigaADF::get_track_at_position(Track::Address address) {
using namespace Storage::Encodings;
// Create an MFM encoder.
Storage::Disk::PCMSegment encoded_segment;
encoded_segment.data.reserve(102'400); // i.e. 0x1900 bytes.
auto encoder = MFM::GetMFMEncoder(encoded_segment.data);
// Grab the unencoded track.
file_.seek(get_file_offset_for_position(address), SEEK_SET);
const std::vector<uint8_t> track_data = file_.read(512 * 11);
// Eleven sectors are then encoded.
for(size_t s = 0; s < 11; s++) {
// Two bytes of 0x00 act as an inter-sector gap.
encoder->add_byte(0);
encoder->add_byte(0);
// Add additional sync.
encoder->output_short(MFM::MFMSync);
encoder->output_short(MFM::MFMSync);
// Encode and write the header.
const uint8_t header[4] = {
0xff, // Amiga v1.0 format.
uint8_t(address.position.as_int() * 2 + address.head), // Track.
uint8_t(s), // Sector.
uint8_t(11 - s), // Sectors remaining.
};
std::array<uint8_t, 4> encoded_header;
encode_block(std::begin(header), std::end(header), std::begin(encoded_header));
encoder->add_bytes(std::begin(encoded_header), std::end(encoded_header));
// Write the sector label.
const std::array<uint8_t, 16> os_recovery{};
encoder->add_bytes(os_recovery.begin(), os_recovery.end());
// Encode the data.
std::array<uint8_t, 512> encoded_data;
encode_block(&track_data[s * 512], &track_data[(s + 1) * 512], std::begin(encoded_data));
// Write checksums.
write_checksum(std::begin(encoded_header), std::end(encoded_header), encoder);
write_checksum(std::begin(encoded_data), std::end(encoded_data), encoder);
// Write data.
encoder->add_bytes(std::begin(encoded_data), std::end(encoded_data));
}
// Throw in an '830-byte' gap (that's in MFM, I think — 830 bytes prior to decoding).
// Cf. https://www.techtravels.org/2007/01/syncing-to-the-0x4489-0x4489/#comment-295
for(int c = 0; c < 415; c++) {
encoder->add_byte(0xff);
}
return std::make_shared<Storage::Disk::PCMTrack>(std::move(encoded_segment));
}
long AmigaADF::get_file_offset_for_position(Track::Address address) {
return (address.position.as_int() * 2 + address.head) * 512 * 11;
}

View File

@ -0,0 +1,47 @@
//
// AmigaADF.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef AmigaADF_hpp
#define AmigaADF_hpp
#include "MFMSectorDump.hpp"
#include <string>
namespace Storage {
namespace Disk {
/*!
Provides a @c DiskImage containing an Amiga ADF, which is an MFM sector contents dump,
but the Amiga doesn't use IBM-style sector demarcation.
*/
class AmigaADF: public DiskImage {
public:
/*!
Construct an @c AmigaADF containing content from the file with name @c file_name.
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
@throws Error::InvalidFormat if the file doesn't appear to contain an .ADF format image.
*/
AmigaADF(const std::string &file_name);
// implemented to satisfy @c Disk
HeadPosition get_maximum_head_position() final;
int get_head_count() final;
std::shared_ptr<Track> get_track_at_position(Track::Address address) final;
private:
Storage::FileHolder file_;
long get_file_offset_for_position(Track::Address address);
};
}
}
#endif /* AmigaADF_hpp */

View File

@ -85,7 +85,7 @@ void Drive::step(HeadPosition offset) {
return;
}
if(ready_type_ == ReadyType::IBMRDY) {
if(disk_ && ready_type_ == ReadyType::IBMRDY) {
is_ready_ = true;
}

View File

@ -29,7 +29,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
ShugartRDY,
/// Indicates that RDY will go active when the motor is on and two index holes have passed; it will go inactive when the disk is ejected.
ShugartModifiedRDY,
/// Indicates that RDY will go active when the head steps; it will go inactive when the disk is ejected.
/// Indicates that RDY will go active when the head steps if a disk is present; it will go inactive when the disk is ejected.
IBMRDY,
};
@ -37,6 +37,15 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
Drive(int input_clock_rate, int number_of_heads, ReadyType rdy_type = ReadyType::ShugartRDY);
virtual ~Drive();
// TODO: Disallow copying.
//
// GCC 10 has an issue with the way the DiskII constructs its drive array if these are both
// deleted, despite not using the copy constructor.
//
// This seems to be fixed in GCC 11, so reenable this delete when possible.
// Drive(const Drive &) = delete;
void operator=(const Drive &) = delete;
/*!
Replaces whatever is in the drive with @c disk. Supply @c nullptr to eject any current disk and leave none inserted.
*/

View File

@ -11,6 +11,7 @@
#include "Constants.hpp"
#include "../../Track/PCMTrack.hpp"
#include "../../../../Numeric/CRC.hpp"
#include "../../../../Numeric/BitSpread.hpp"
#include <cassert>
#include <set>
@ -29,32 +30,11 @@ class MFMEncoder: public Encoder {
void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) final {
crc_generator_.add(input);
const uint16_t spread_value =
uint16_t(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7)
);
const uint16_t spread_value = Numeric::spread_bits(input);
const uint16_t spread_mask = Numeric::spread_bits(fuzzy_mask);
const uint16_t or_bits = uint16_t((spread_value << 1) | (spread_value >> 1) | (last_output_ << 15));
const uint16_t output = spread_value | ((~or_bits) & 0xaaaa);
const uint16_t spread_mask =
uint16_t(
((fuzzy_mask & 0x01) << 0) |
((fuzzy_mask & 0x02) << 1) |
((fuzzy_mask & 0x04) << 2) |
((fuzzy_mask & 0x08) << 3) |
((fuzzy_mask & 0x10) << 4) |
((fuzzy_mask & 0x20) << 5) |
((fuzzy_mask & 0x40) << 6) |
((fuzzy_mask & 0x80) << 7)
);
output_short(output, spread_mask);
}
@ -108,27 +88,8 @@ class FMEncoder: public Encoder {
void add_byte(uint8_t input, uint8_t fuzzy_mask = 0) final {
crc_generator_.add(input);
output_short(
uint16_t(
((input & 0x01) << 0) |
((input & 0x02) << 1) |
((input & 0x04) << 2) |
((input & 0x08) << 3) |
((input & 0x10) << 4) |
((input & 0x20) << 5) |
((input & 0x40) << 6) |
((input & 0x80) << 7) |
0xaaaa
),
uint16_t(
((fuzzy_mask & 0x01) << 0) |
((fuzzy_mask & 0x02) << 1) |
((fuzzy_mask & 0x04) << 2) |
((fuzzy_mask & 0x08) << 3) |
((fuzzy_mask & 0x10) << 4) |
((fuzzy_mask & 0x20) << 5) |
((fuzzy_mask & 0x40) << 6) |
((fuzzy_mask & 0x80) << 7)
)
Numeric::spread_bits(input) | 0xaaaa,
Numeric::spread_bits(fuzzy_mask)
);
}

View File

@ -55,6 +55,17 @@ class Encoder {
virtual void add_deleted_data_address_mark() = 0;
virtual void output_short(uint16_t value, uint16_t fuzzy_mask = 0);
template <typename IteratorT> void add_bytes(IteratorT begin, IteratorT end) {
while(begin != end) {
add_byte(*begin);
++begin;
}
}
template <typename ContainerT> void add_bytes(const ContainerT &container) {
write(std::begin(container), std::end(container));
}
/// Outputs the CRC for all data since the last address mask; if @c incorrectly is @c true then outputs an incorrect CRC.
void add_crc(bool incorrectly);

View File

@ -9,6 +9,8 @@
#include "Shifter.hpp"
#include "Constants.hpp"
#include "../../../../Numeric/BitSpread.hpp"
using namespace Storage::Encodings::MFM;
Shifter::Shifter() : owned_crc_generator_(new CRC::CCITT()), crc_generator_(owned_crc_generator_.get()) {}
@ -122,13 +124,5 @@ void Shifter::add_input_bit(int value) {
}
uint8_t Shifter::get_byte() const {
return uint8_t(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
return Numeric::unspread_bits(uint16_t(shift_register_));
}

View File

@ -23,25 +23,26 @@ enum Type: IntType {
AtariST = 1 << 4,
AcornAtom = 1 << 5,
AcornElectron = 1 << 6,
BBCMaster = 1 << 7,
BBCModelA = 1 << 8,
BBCModelB = 1 << 9,
Coleco = 1 << 10,
Commodore = 1 << 11,
DiskII = 1 << 12,
Enterprise = 1 << 13,
Sega = 1 << 14,
Macintosh = 1 << 15,
MSX = 1 << 16,
Oric = 1 << 17,
ZX80 = 1 << 18,
ZX81 = 1 << 19,
ZXSpectrum = 1 << 20,
Amiga = 1 << 7,
BBCMaster = 1 << 8,
BBCModelA = 1 << 9,
BBCModelB = 1 << 10,
Coleco = 1 << 11,
Commodore = 1 << 12,
DiskII = 1 << 13,
Enterprise = 1 << 14,
Sega = 1 << 15,
Macintosh = 1 << 16,
MSX = 1 << 17,
Oric = 1 << 18,
ZX80 = 1 << 19,
ZX81 = 1 << 20,
ZXSpectrum = 1 << 21,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,
AllCartridge = Atari2600 | AcornElectron | Coleco | MSX,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX | ZXSpectrum | Macintosh | AtariST | DiskII,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX | ZXSpectrum | Macintosh | AtariST | DiskII | Amiga,
AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX8081 | MSX | ZXSpectrum,
};