2019-05-04 02:16:07 +00:00
|
|
|
|
//
|
|
|
|
|
// Macintosh.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 03/05/2019.
|
|
|
|
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "Macintosh.hpp"
|
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
#include <array>
|
|
|
|
|
|
2019-06-01 19:03:15 +00:00
|
|
|
|
#include "DeferredAudio.hpp"
|
2019-06-01 23:31:32 +00:00
|
|
|
|
#include "DriveSpeedAccumulator.hpp"
|
2019-05-08 16:34:26 +00:00
|
|
|
|
#include "Keyboard.hpp"
|
|
|
|
|
#include "Video.hpp"
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
2020-04-02 03:19:34 +00:00
|
|
|
|
#include "../../MachineTypes.hpp"
|
2019-08-02 20:26:23 +00:00
|
|
|
|
#include "../../../Activity/Source.hpp"
|
2020-03-16 03:48:53 +00:00
|
|
|
|
#include "../../../Configurable/Configurable.hpp"
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
2019-06-11 22:21:56 +00:00
|
|
|
|
#include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp"
|
2019-07-26 02:55:27 +00:00
|
|
|
|
#include "../../../Outputs/Log.hpp"
|
2019-06-11 22:21:56 +00:00
|
|
|
|
|
2019-07-29 01:49:54 +00:00
|
|
|
|
#include "../../../ClockReceiver/JustInTime.hpp"
|
2019-08-16 03:14:40 +00:00
|
|
|
|
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
2019-09-20 01:50:39 +00:00
|
|
|
|
#include "../../../Configurable/StandardOptions.hpp"
|
2019-07-29 01:49:54 +00:00
|
|
|
|
|
2019-07-11 02:39:56 +00:00
|
|
|
|
//#define LOG_TRACE
|
2019-05-28 19:17:03 +00:00
|
|
|
|
|
2019-08-12 00:55:20 +00:00
|
|
|
|
#include "../../../Components/5380/ncr5380.hpp"
|
2019-05-04 02:16:07 +00:00
|
|
|
|
#include "../../../Components/6522/6522.hpp"
|
2019-06-08 22:47:11 +00:00
|
|
|
|
#include "../../../Components/8530/z8530.hpp"
|
2020-10-30 01:03:02 +00:00
|
|
|
|
#include "../../../Components/AppleClock/AppleClock.hpp"
|
2019-05-06 01:55:34 +00:00
|
|
|
|
#include "../../../Components/DiskII/IWM.hpp"
|
2019-07-10 20:24:48 +00:00
|
|
|
|
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
2019-06-01 18:39:40 +00:00
|
|
|
|
#include "../../../Processors/68000/68000.hpp"
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
2019-08-25 21:03:41 +00:00
|
|
|
|
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
|
|
|
|
|
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
|
2019-08-26 03:03:54 +00:00
|
|
|
|
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
|
2019-08-25 21:03:41 +00:00
|
|
|
|
|
2019-06-03 18:50:36 +00:00
|
|
|
|
#include "../../../Analyser/Static/Macintosh/Target.hpp"
|
|
|
|
|
|
2019-05-04 02:39:09 +00:00
|
|
|
|
#include "../../Utility/MemoryPacker.hpp"
|
2019-06-13 17:35:16 +00:00
|
|
|
|
#include "../../Utility/MemoryFuzzer.hpp"
|
2019-05-04 02:16:07 +00:00
|
|
|
|
|
2019-05-08 04:12:19 +00:00
|
|
|
|
namespace {
|
|
|
|
|
|
2019-12-22 05:22:17 +00:00
|
|
|
|
constexpr int CLOCK_RATE = 7833600;
|
2019-05-08 04:12:19 +00:00
|
|
|
|
|
2021-09-10 23:56:20 +00:00
|
|
|
|
|
|
|
|
|
// Former default PRAM:
|
|
|
|
|
//
|
|
|
|
|
// 0xa8, 0x00, 0x00, 0x00,
|
|
|
|
|
// 0xcc, 0x0a, 0xcc, 0x0a,
|
|
|
|
|
// 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
// 0x00, 0x02, 0x63, 0x00,
|
|
|
|
|
// 0x03, 0x88, 0x00, 0x4c
|
|
|
|
|
|
2019-05-08 04:12:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 02:16:07 +00:00
|
|
|
|
namespace Apple {
|
|
|
|
|
namespace Macintosh {
|
|
|
|
|
|
2019-06-03 18:50:36 +00:00
|
|
|
|
template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
|
2019-05-04 02:16:07 +00:00
|
|
|
|
public Machine,
|
2020-04-02 03:19:34 +00:00
|
|
|
|
public MachineTypes::TimedMachine,
|
|
|
|
|
public MachineTypes::ScanProducer,
|
|
|
|
|
public MachineTypes::AudioProducer,
|
|
|
|
|
public MachineTypes::MediaTarget,
|
|
|
|
|
public MachineTypes::MouseMachine,
|
|
|
|
|
public MachineTypes::MappedKeyboardMachine,
|
2019-07-03 01:14:33 +00:00
|
|
|
|
public CPU::MC68000::BusHandler,
|
2019-08-02 20:26:23 +00:00
|
|
|
|
public Zilog::SCC::z8530::Delegate,
|
2019-08-08 01:39:23 +00:00
|
|
|
|
public Activity::Source,
|
2019-09-20 01:50:39 +00:00
|
|
|
|
public Configurable::Device,
|
2019-08-16 03:14:40 +00:00
|
|
|
|
public DriveSpeedAccumulator::Delegate,
|
|
|
|
|
public ClockingHint::Observer {
|
2019-05-04 02:16:07 +00:00
|
|
|
|
public:
|
2019-06-05 02:13:00 +00:00
|
|
|
|
using Target = Analyser::Static::Macintosh::Target;
|
|
|
|
|
|
|
|
|
|
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
2020-04-02 03:19:34 +00:00
|
|
|
|
MachineTypes::MappedKeyboardMachine({
|
2019-09-22 17:48:50 +00:00
|
|
|
|
Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift,
|
|
|
|
|
Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption,
|
|
|
|
|
Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta,
|
|
|
|
|
}),
|
2019-05-04 03:25:42 +00:00
|
|
|
|
mc68000_(*this),
|
2019-06-01 23:31:32 +00:00
|
|
|
|
iwm_(CLOCK_RATE),
|
2019-08-12 01:41:12 +00:00
|
|
|
|
video_(audio_, drive_speed_accumulator_),
|
2019-05-04 20:38:01 +00:00
|
|
|
|
via_(via_port_handler_),
|
2019-09-21 21:39:45 +00:00
|
|
|
|
via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_),
|
2019-09-02 17:00:01 +00:00
|
|
|
|
scsi_bus_(CLOCK_RATE * 2),
|
2019-08-25 21:03:41 +00:00
|
|
|
|
scsi_(scsi_bus_, CLOCK_RATE * 2),
|
|
|
|
|
hard_drive_(scsi_bus_, 6 /* SCSI ID */),
|
2019-06-05 01:41:54 +00:00
|
|
|
|
drives_{
|
|
|
|
|
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
|
|
|
|
|
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
|
2019-06-11 22:21:56 +00:00
|
|
|
|
},
|
|
|
|
|
mouse_(1) {
|
2019-05-04 02:16:07 +00:00
|
|
|
|
|
2019-06-03 18:50:36 +00:00
|
|
|
|
// Select a ROM name and determine the proper ROM and RAM sizes
|
|
|
|
|
// based on the machine model.
|
|
|
|
|
using Model = Analyser::Static::Macintosh::Target::Model;
|
2019-07-23 01:14:21 +00:00
|
|
|
|
const std::string machine_name = "Macintosh";
|
2019-06-03 18:50:36 +00:00
|
|
|
|
uint32_t ram_size, rom_size;
|
2021-06-04 01:55:59 +00:00
|
|
|
|
ROM::Name rom_name;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
switch(model) {
|
|
|
|
|
default:
|
|
|
|
|
case Model::Mac128k:
|
|
|
|
|
ram_size = 128*1024;
|
|
|
|
|
rom_size = 64*1024;
|
2021-06-04 01:55:59 +00:00
|
|
|
|
rom_name = ROM::Name::Macintosh128k;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
break;
|
|
|
|
|
case Model::Mac512k:
|
|
|
|
|
ram_size = 512*1024;
|
|
|
|
|
rom_size = 64*1024;
|
2021-06-04 01:55:59 +00:00
|
|
|
|
rom_name = ROM::Name::Macintosh512k;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
break;
|
|
|
|
|
case Model::Mac512ke:
|
2019-07-20 20:08:40 +00:00
|
|
|
|
case Model::MacPlus: {
|
2019-08-12 01:41:12 +00:00
|
|
|
|
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
rom_size = 128*1024;
|
2021-06-04 01:55:59 +00:00
|
|
|
|
rom_name = ROM::Name::MacintoshPlus;
|
2019-07-20 20:08:40 +00:00
|
|
|
|
} break;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
}
|
2020-02-14 02:14:13 +00:00
|
|
|
|
ram_mask_ = ram_size - 1;
|
|
|
|
|
rom_mask_ = rom_size - 1;
|
|
|
|
|
ram_.resize(ram_size);
|
|
|
|
|
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
|
2019-06-03 18:50:36 +00:00
|
|
|
|
|
2019-05-04 02:39:09 +00:00
|
|
|
|
// Grab a copy of the ROM and convert it into big-endian data.
|
2021-06-04 01:55:59 +00:00
|
|
|
|
ROM::Request request(rom_name);
|
2021-06-04 22:54:50 +00:00
|
|
|
|
auto roms = rom_fetcher(request);
|
2021-06-04 01:55:59 +00:00
|
|
|
|
if(!request.validate(roms)) {
|
2019-05-04 02:16:07 +00:00
|
|
|
|
throw ROMMachine::Error::MissingROMs;
|
|
|
|
|
}
|
2021-06-04 01:55:59 +00:00
|
|
|
|
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
2019-06-13 17:35:16 +00:00
|
|
|
|
// Randomise memory contents.
|
2019-08-12 01:41:12 +00:00
|
|
|
|
Memory::Fuzz(ram_);
|
2019-06-13 17:35:16 +00:00
|
|
|
|
|
2019-06-05 01:41:54 +00:00
|
|
|
|
// Attach the drives to the IWM.
|
2019-08-08 01:28:02 +00:00
|
|
|
|
iwm_->set_drive(0, &drives_[0]);
|
|
|
|
|
iwm_->set_drive(1, &drives_[1]);
|
2019-06-05 01:41:54 +00:00
|
|
|
|
|
2019-07-30 19:08:55 +00:00
|
|
|
|
// If they are 400kb drives, also attach them to the drive-speed accumulator.
|
2019-08-08 01:39:23 +00:00
|
|
|
|
if(!drives_[0].is_800k() || !drives_[1].is_800k()) {
|
|
|
|
|
drive_speed_accumulator_.set_delegate(this);
|
|
|
|
|
}
|
2019-07-30 19:08:55 +00:00
|
|
|
|
|
2019-07-24 03:13:03 +00:00
|
|
|
|
// Make sure interrupt changes from the SCC are observed.
|
|
|
|
|
scc_.set_delegate(this);
|
|
|
|
|
|
2019-08-16 03:14:40 +00:00
|
|
|
|
// Also watch for changes in clocking requirement from the SCSI chip.
|
2019-12-22 18:42:24 +00:00
|
|
|
|
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
2019-09-03 03:14:37 +00:00
|
|
|
|
scsi_bus_.set_clocking_hint_observer(this);
|
2019-08-16 03:14:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 03:25:42 +00:00
|
|
|
|
// The Mac runs at 7.8336mHz.
|
2019-05-08 04:12:19 +00:00
|
|
|
|
set_clock_rate(double(CLOCK_RATE));
|
2019-07-17 18:41:36 +00:00
|
|
|
|
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
|
2019-06-05 02:13:00 +00:00
|
|
|
|
|
|
|
|
|
// Insert any supplied media.
|
|
|
|
|
insert_media(target.media);
|
2019-08-03 01:30:04 +00:00
|
|
|
|
|
|
|
|
|
// Set the immutables of the memory map.
|
|
|
|
|
setup_memory_map();
|
2019-05-04 03:25:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 21:29:57 +00:00
|
|
|
|
~ConcreteMachine() {
|
|
|
|
|
audio_.queue.flush();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
2019-05-04 03:25:42 +00:00
|
|
|
|
video_.set_scan_target(scan_target);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-22 03:28:25 +00:00
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
|
|
|
|
return video_.get_scaled_scan_status();
|
2020-01-21 02:45:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
Outputs::Speaker::Speaker *get_speaker() final {
|
2019-06-01 18:39:40 +00:00
|
|
|
|
return &audio_.speaker;
|
2019-05-04 03:25:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
void run_for(const Cycles cycles) final {
|
2019-05-04 03:25:42 +00:00
|
|
|
|
mc68000_.run_for(cycles);
|
2019-05-04 02:16:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
using Microcycle = CPU::MC68000::Microcycle;
|
|
|
|
|
|
2020-05-30 04:37:06 +00:00
|
|
|
|
forceinline HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
2019-08-03 02:12:34 +00:00
|
|
|
|
// Advance time.
|
2019-08-04 01:46:45 +00:00
|
|
|
|
advance_time(cycle.length);
|
2019-05-08 04:12:19 +00:00
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
// A null cycle leaves nothing else to do.
|
2019-08-04 01:46:45 +00:00
|
|
|
|
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
|
|
|
|
|
|
2020-02-14 02:14:13 +00:00
|
|
|
|
// Grab the address.
|
|
|
|
|
auto address = cycle.host_endian_byte_address();
|
2019-05-04 18:23:37 +00:00
|
|
|
|
|
2019-07-09 23:49:06 +00:00
|
|
|
|
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
2020-02-14 02:14:13 +00:00
|
|
|
|
mc68000_.set_is_peripheral_address(address >= 0xe0'0000);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
|
|
|
|
|
// All code below deals only with reads and writes — cycles in which a
|
|
|
|
|
// data select is active. So quit now if this is not the active part of
|
2019-08-02 23:48:41 +00:00
|
|
|
|
// a read or write.
|
2019-08-03 19:38:36 +00:00
|
|
|
|
//
|
|
|
|
|
// The 68000 uses 6800-style autovectored interrupts, so the mere act of
|
|
|
|
|
// having set VPA above deals with those given that the generated address
|
|
|
|
|
// for interrupt acknowledge cycles always has all bits set except the
|
|
|
|
|
// lowest explicit address lines.
|
2019-08-04 01:46:45 +00:00
|
|
|
|
if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
|
2019-08-04 01:46:45 +00:00
|
|
|
|
// Grab the word-precision address being accessed.
|
2020-02-14 02:14:13 +00:00
|
|
|
|
uint8_t *memory_base = nullptr;
|
2019-08-04 01:46:45 +00:00
|
|
|
|
HalfCycles delay;
|
2020-02-14 02:14:13 +00:00
|
|
|
|
switch(memory_map_[address >> 17]) {
|
2019-08-03 02:12:34 +00:00
|
|
|
|
default: assert(false);
|
2019-08-03 01:30:04 +00:00
|
|
|
|
|
|
|
|
|
case BusDevice::Unassigned:
|
|
|
|
|
fill_unmapped(cycle);
|
|
|
|
|
return delay;
|
|
|
|
|
|
|
|
|
|
case BusDevice::VIA: {
|
|
|
|
|
if(*cycle.address & 1) {
|
|
|
|
|
fill_unmapped(cycle);
|
|
|
|
|
} else {
|
2020-02-14 02:14:13 +00:00
|
|
|
|
const int register_address = address >> 9;
|
2019-07-09 23:49:06 +00:00
|
|
|
|
|
|
|
|
|
// VIA accesses are via address 0xefe1fe + register*512,
|
|
|
|
|
// which at word precision is 0x77f0ff + register*256.
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2020-01-05 18:40:02 +00:00
|
|
|
|
cycle.value->halves.low = via_.read(register_address);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
} else {
|
2020-01-05 18:40:02 +00:00
|
|
|
|
via_.write(register_address, cycle.value->halves.low);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
|
|
|
|
}
|
|
|
|
|
} return delay;
|
|
|
|
|
|
|
|
|
|
case BusDevice::PhaseRead: {
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
|
|
|
|
cycle.value->halves.low = phase_ & 7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
|
|
|
|
} return delay;
|
|
|
|
|
|
|
|
|
|
case BusDevice::IWM: {
|
|
|
|
|
if(*cycle.address & 1) {
|
2020-02-14 02:14:13 +00:00
|
|
|
|
const int register_address = address >> 9;
|
2019-08-03 01:30:04 +00:00
|
|
|
|
|
2019-07-09 23:49:06 +00:00
|
|
|
|
// The IWM; this is a purely polled device, so can be run on demand.
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2019-08-08 01:28:02 +00:00
|
|
|
|
cycle.value->halves.low = iwm_->read(register_address);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
} else {
|
2019-08-08 01:28:02 +00:00
|
|
|
|
iwm_->write(register_address, cycle.value->halves.low);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
|
|
|
|
} else {
|
|
|
|
|
fill_unmapped(cycle);
|
|
|
|
|
}
|
|
|
|
|
} return delay;
|
2019-07-09 23:49:06 +00:00
|
|
|
|
|
2019-08-12 00:55:20 +00:00
|
|
|
|
case BusDevice::SCSI: {
|
2020-02-14 02:14:13 +00:00
|
|
|
|
const int register_address = address >> 4;
|
|
|
|
|
const bool dma_acknowledge = address & 0x200;
|
2019-08-12 00:55:20 +00:00
|
|
|
|
|
|
|
|
|
// Even accesses = read; odd = write.
|
|
|
|
|
if(*cycle.address & 1) {
|
|
|
|
|
// Odd access => this is a write. Data will be in the upper byte.
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2019-09-14 17:48:33 +00:00
|
|
|
|
scsi_.write(register_address, 0xff, dma_acknowledge);
|
2019-08-12 00:55:20 +00:00
|
|
|
|
} else {
|
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) {
|
2019-09-14 17:48:33 +00:00
|
|
|
|
scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge);
|
2019-08-12 00:55:20 +00:00
|
|
|
|
} else {
|
2019-09-14 17:48:33 +00:00
|
|
|
|
scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge);
|
2019-08-12 00:55:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Even access => this is a read.
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2019-09-14 17:48:33 +00:00
|
|
|
|
const auto result = scsi_.read(register_address, dma_acknowledge);
|
2019-08-12 00:55:20 +00:00
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) {
|
|
|
|
|
// Data is loaded on the top part of the bus only.
|
2019-08-12 01:41:12 +00:00
|
|
|
|
cycle.value->full = uint16_t((result << 8) | 0xff);
|
2019-08-12 00:55:20 +00:00
|
|
|
|
} else {
|
|
|
|
|
cycle.value->halves.low = result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} return delay;
|
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
case BusDevice::SCCReadResetPhase: {
|
|
|
|
|
// Any word access here adjusts phase.
|
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) {
|
|
|
|
|
adjust_phase();
|
|
|
|
|
} else {
|
|
|
|
|
// A0 = 1 => reset; A0 = 0 => read.
|
|
|
|
|
if(*cycle.address & 1) {
|
|
|
|
|
scc_.reset();
|
2019-05-07 21:16:22 +00:00
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
|
|
|
|
cycle.value->halves.low = 0xff;
|
|
|
|
|
}
|
2019-07-09 23:49:06 +00:00
|
|
|
|
} else {
|
2020-02-14 02:14:13 +00:00
|
|
|
|
const auto read = scc_.read(int(address >> 1));
|
2019-08-03 01:30:04 +00:00
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
|
|
|
|
cycle.value->halves.low = read;
|
|
|
|
|
}
|
2019-05-04 16:33:27 +00:00
|
|
|
|
}
|
2019-08-03 01:30:04 +00:00
|
|
|
|
}
|
|
|
|
|
} return delay;
|
2019-07-09 23:49:06 +00:00
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
case BusDevice::SCCWrite: {
|
|
|
|
|
// Any word access here adjusts phase.
|
2019-07-09 23:49:06 +00:00
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) {
|
2019-08-03 01:30:04 +00:00
|
|
|
|
adjust_phase();
|
2019-05-04 16:33:27 +00:00
|
|
|
|
} else {
|
2019-08-03 01:30:04 +00:00
|
|
|
|
if(*cycle.address & 1) {
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2020-02-14 02:14:13 +00:00
|
|
|
|
scc_.write(int(address >> 1), 0xff);
|
2019-08-03 01:30:04 +00:00
|
|
|
|
cycle.value->halves.low = 0xff;
|
|
|
|
|
} else {
|
2020-02-14 02:14:13 +00:00
|
|
|
|
scc_.write(int(address >> 1), cycle.value->halves.low);
|
2019-08-03 01:30:04 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fill_unmapped(cycle);
|
|
|
|
|
}
|
2019-05-04 16:33:27 +00:00
|
|
|
|
}
|
2019-08-03 01:30:04 +00:00
|
|
|
|
} return delay;
|
|
|
|
|
|
|
|
|
|
case BusDevice::RAM: {
|
|
|
|
|
// This is coupled with the Macintosh implementation of video; the magic
|
|
|
|
|
// constant should probably be factored into the Video class.
|
|
|
|
|
// It embodies knowledge of the fact that video (and audio) will always
|
2020-02-14 02:14:13 +00:00
|
|
|
|
// be fetched from the final $d900 bytes of memory.
|
2019-08-03 01:30:04 +00:00
|
|
|
|
// (And that ram_mask_ = ram size - 1).
|
2020-02-14 02:14:13 +00:00
|
|
|
|
if(address > ram_mask_ - 0xd900)
|
2019-08-03 01:30:04 +00:00
|
|
|
|
update_video();
|
|
|
|
|
|
2019-08-12 01:41:12 +00:00
|
|
|
|
memory_base = ram_.data();
|
2020-02-14 02:14:13 +00:00
|
|
|
|
address &= ram_mask_;
|
2019-08-04 01:46:45 +00:00
|
|
|
|
|
2019-09-21 21:25:20 +00:00
|
|
|
|
// Apply a delay due to video contention if applicable; scheme applied:
|
|
|
|
|
// only every other access slot is available during the period of video
|
|
|
|
|
// output. I believe this to be correct for the 128k, 512k and Plus.
|
|
|
|
|
// More research to do on other models.
|
|
|
|
|
if(video_is_outputting() && ram_subcycle_ < 8) {
|
|
|
|
|
delay = HalfCycles(8 - ram_subcycle_);
|
2019-08-04 01:46:45 +00:00
|
|
|
|
advance_time(delay);
|
|
|
|
|
}
|
2019-08-03 01:30:04 +00:00
|
|
|
|
} break;
|
|
|
|
|
|
|
|
|
|
case BusDevice::ROM: {
|
|
|
|
|
if(!(cycle.operation & Microcycle::Read)) return delay;
|
|
|
|
|
memory_base = rom_;
|
2020-02-14 02:14:13 +00:00
|
|
|
|
address &= rom_mask_;
|
2019-08-03 01:30:04 +00:00
|
|
|
|
} break;
|
2019-05-04 16:33:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM.
|
2019-08-03 19:38:36 +00:00
|
|
|
|
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
2019-07-09 23:49:06 +00:00
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Microcycle::SelectWord | Microcycle::Read:
|
2020-02-14 02:14:13 +00:00
|
|
|
|
cycle.value->full = *reinterpret_cast<uint16_t *>(&memory_base[address]);
|
2019-07-09 23:49:06 +00:00
|
|
|
|
break;
|
|
|
|
|
case Microcycle::SelectByte | Microcycle::Read:
|
2020-02-14 02:14:13 +00:00
|
|
|
|
cycle.value->halves.low = memory_base[address];
|
2019-07-09 23:49:06 +00:00
|
|
|
|
break;
|
|
|
|
|
case Microcycle::SelectWord:
|
2020-02-14 02:14:13 +00:00
|
|
|
|
*reinterpret_cast<uint16_t *>(&memory_base[address]) = cycle.value->full;
|
2019-07-09 23:49:06 +00:00
|
|
|
|
break;
|
|
|
|
|
case Microcycle::SelectByte:
|
2020-02-14 02:14:13 +00:00
|
|
|
|
memory_base[address] = cycle.value->halves.low;
|
2019-07-09 23:49:06 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return delay;
|
2019-05-04 03:40:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-05 02:27:58 +00:00
|
|
|
|
void flush() {
|
2019-06-01 18:39:40 +00:00
|
|
|
|
// Flush the video before the audio queue; in a Mac the
|
|
|
|
|
// video is responsible for providing part of the
|
|
|
|
|
// audio signal, so the two aren't as distinct as in
|
|
|
|
|
// most machines.
|
2019-07-09 22:08:07 +00:00
|
|
|
|
update_video();
|
2019-06-01 18:39:40 +00:00
|
|
|
|
|
|
|
|
|
// As above: flush audio after video.
|
2019-06-01 21:29:57 +00:00
|
|
|
|
via_.flush();
|
2019-06-01 19:18:27 +00:00
|
|
|
|
audio_.queue.perform();
|
2019-07-07 18:13:55 +00:00
|
|
|
|
|
2020-01-26 16:36:06 +00:00
|
|
|
|
// This avoids deferring IWM costs indefinitely, until
|
|
|
|
|
// they become artbitrarily large.
|
2019-07-07 18:13:55 +00:00
|
|
|
|
iwm_.flush();
|
2019-05-05 02:27:58 +00:00
|
|
|
|
}
|
2019-05-04 03:40:22 +00:00
|
|
|
|
|
2019-05-04 20:38:01 +00:00
|
|
|
|
void set_rom_is_overlay(bool rom_is_overlay) {
|
|
|
|
|
ROM_is_overlay_ = rom_is_overlay;
|
2019-08-03 01:30:04 +00:00
|
|
|
|
|
2019-08-03 02:26:40 +00:00
|
|
|
|
using Model = Analyser::Static::Macintosh::Target::Model;
|
|
|
|
|
switch(model) {
|
|
|
|
|
case Model::Mac128k:
|
|
|
|
|
case Model::Mac512k:
|
|
|
|
|
case Model::Mac512ke:
|
2019-08-12 00:55:20 +00:00
|
|
|
|
populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
2019-08-03 02:26:40 +00:00
|
|
|
|
// Addresses up to $80 0000 aren't affected by this bit.
|
|
|
|
|
if(rom_is_overlay) {
|
|
|
|
|
// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes.
|
2019-08-11 03:53:34 +00:00
|
|
|
|
for(int c = 0; c < 0x600000; c += 0x100000) {
|
|
|
|
|
map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM);
|
2019-08-03 02:26:40 +00:00
|
|
|
|
}
|
|
|
|
|
map_to(0x800000, BusDevice::RAM);
|
|
|
|
|
} else {
|
|
|
|
|
map_to(0x400000, BusDevice::RAM);
|
|
|
|
|
map_to(0x500000, BusDevice::ROM);
|
|
|
|
|
map_to(0x800000, BusDevice::Unassigned);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Model::MacPlus:
|
2019-08-12 00:55:20 +00:00
|
|
|
|
populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
2019-08-03 02:26:40 +00:00
|
|
|
|
// Addresses up to $80 0000 aren't affected by this bit.
|
|
|
|
|
if(rom_is_overlay) {
|
2019-08-11 03:53:34 +00:00
|
|
|
|
for(int c = 0; c < 0x580000; c += 0x20000) {
|
|
|
|
|
map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
|
|
|
|
|
}
|
2019-08-03 02:26:40 +00:00
|
|
|
|
map_to(0x600000, BusDevice::SCSI);
|
|
|
|
|
map_to(0x800000, BusDevice::RAM);
|
|
|
|
|
} else {
|
|
|
|
|
map_to(0x400000, BusDevice::RAM);
|
2019-08-11 03:53:34 +00:00
|
|
|
|
for(int c = 0x400000; c < 0x580000; c += 0x20000) {
|
|
|
|
|
map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
|
|
|
|
|
}
|
2019-08-03 02:26:40 +00:00
|
|
|
|
map_to(0x600000, BusDevice::SCSI);
|
|
|
|
|
map_to(0x800000, BusDevice::Unassigned);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-04 20:38:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-09 22:08:07 +00:00
|
|
|
|
bool video_is_outputting() {
|
|
|
|
|
return video_.is_outputting(time_since_video_update_);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 19:03:15 +00:00
|
|
|
|
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
2019-07-28 02:23:40 +00:00
|
|
|
|
update_video();
|
2019-06-01 19:03:15 +00:00
|
|
|
|
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
|
2019-05-06 03:05:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
bool insert_media(const Analyser::Static::Media &media) final {
|
2019-08-25 21:03:41 +00:00
|
|
|
|
if(media.disks.empty() && media.mass_storage_devices.empty())
|
2019-06-05 02:13:00 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// TODO: shouldn't allow disks to be replaced like this, as the Mac
|
|
|
|
|
// uses software eject. Will need to expand messaging ability of
|
|
|
|
|
// insert_media.
|
2019-08-25 21:03:41 +00:00
|
|
|
|
if(!media.disks.empty()) {
|
|
|
|
|
if(drives_[0].has_disk())
|
|
|
|
|
drives_[1].set_disk(media.disks[0]);
|
|
|
|
|
else
|
|
|
|
|
drives_[0].set_disk(media.disks[0]);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 02:19:23 +00:00
|
|
|
|
// TODO: allow this only at machine startup?
|
2019-08-25 21:03:41 +00:00
|
|
|
|
if(!media.mass_storage_devices.empty()) {
|
2019-08-26 03:03:54 +00:00
|
|
|
|
const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get());
|
|
|
|
|
if(volume) {
|
|
|
|
|
volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI);
|
|
|
|
|
}
|
2019-08-25 21:03:41 +00:00
|
|
|
|
hard_drive_->set_storage(media.mass_storage_devices.front());
|
|
|
|
|
}
|
2019-06-05 02:13:00 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-03 01:14:33 +00:00
|
|
|
|
// MARK: Keyboard input.
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
KeyboardMapper *get_keyboard_mapper() final {
|
2019-07-03 01:14:33 +00:00
|
|
|
|
return &keyboard_mapper_;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
void set_key_state(uint16_t key, bool is_pressed) final {
|
2019-07-03 01:14:33 +00:00
|
|
|
|
keyboard_.enqueue_key_state(key, is_pressed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: clear all keys.
|
|
|
|
|
|
2019-07-24 03:13:03 +00:00
|
|
|
|
// MARK: Interrupt updates.
|
|
|
|
|
|
2020-05-30 04:37:06 +00:00
|
|
|
|
void did_change_interrupt_status(Zilog::SCC::z8530 *, bool) final {
|
2019-07-24 03:13:03 +00:00
|
|
|
|
update_interrupt_input();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void update_interrupt_input() {
|
|
|
|
|
// Update interrupt input.
|
|
|
|
|
// TODO: does this really cascade like this?
|
|
|
|
|
if(scc_.get_interrupt_line()) {
|
|
|
|
|
mc68000_.set_interrupt_level(2);
|
|
|
|
|
} else if(via_.get_interrupt_line()) {
|
|
|
|
|
mc68000_.set_interrupt_level(1);
|
|
|
|
|
} else {
|
|
|
|
|
mc68000_.set_interrupt_level(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 20:26:23 +00:00
|
|
|
|
// MARK: - Activity Source
|
2020-01-24 03:57:51 +00:00
|
|
|
|
void set_activity_observer(Activity::Observer *observer) final {
|
2019-08-08 01:28:02 +00:00
|
|
|
|
iwm_->set_activity_observer(observer);
|
2019-09-19 23:31:22 +00:00
|
|
|
|
|
2019-12-22 18:42:24 +00:00
|
|
|
|
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
2019-09-19 23:31:22 +00:00
|
|
|
|
scsi_bus_.set_activity_observer(observer);
|
|
|
|
|
}
|
2019-08-02 20:26:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-20 01:50:39 +00:00
|
|
|
|
// MARK: - Configuration options.
|
2020-03-17 03:25:05 +00:00
|
|
|
|
std::unique_ptr<Reflection::Struct> get_options() final {
|
2020-03-19 01:50:02 +00:00
|
|
|
|
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
|
|
|
|
options->quickboot = quickboot_;
|
|
|
|
|
return options;
|
2019-09-20 01:50:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 01:50:02 +00:00
|
|
|
|
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
|
|
|
|
|
// TODO: should this really be a runtime option?
|
|
|
|
|
// It should probably be a construction option.
|
|
|
|
|
|
|
|
|
|
const auto options = dynamic_cast<Options *>(str.get());
|
|
|
|
|
quickboot_ = options->quickboot;
|
2021-03-07 03:34:35 +00:00
|
|
|
|
|
|
|
|
|
using Model = Analyser::Static::Macintosh::Target::Model;
|
|
|
|
|
const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus;
|
|
|
|
|
if(quickboot_ && is_plus_rom) {
|
2020-03-19 01:50:02 +00:00
|
|
|
|
// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
|
|
|
|
|
// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
|
|
|
|
|
ram_[0x02ae] = 0x40;
|
|
|
|
|
ram_[0x02af] = 0x00;
|
|
|
|
|
ram_[0x02b0] = 0x00;
|
|
|
|
|
ram_[0x02b1] = 0x00;
|
|
|
|
|
}
|
2019-09-20 01:50:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 02:16:07 +00:00
|
|
|
|
private:
|
2020-03-19 01:50:02 +00:00
|
|
|
|
bool quickboot_ = false;
|
|
|
|
|
|
2020-05-30 04:37:06 +00:00
|
|
|
|
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final {
|
2019-09-02 20:03:33 +00:00
|
|
|
|
scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None;
|
2019-08-16 03:14:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final {
|
2019-08-08 01:39:23 +00:00
|
|
|
|
iwm_.flush();
|
|
|
|
|
drives_[0].set_rotation_speed(speed);
|
|
|
|
|
drives_[1].set_rotation_speed(speed);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
forceinline void adjust_phase() {
|
|
|
|
|
++phase_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
forceinline void fill_unmapped(const Microcycle &cycle) {
|
|
|
|
|
if(!(cycle.operation & Microcycle::Read)) return;
|
2020-02-14 02:14:13 +00:00
|
|
|
|
cycle.set_value16(0xffff);
|
2019-08-03 01:30:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-02 23:48:41 +00:00
|
|
|
|
/// Advances all non-CPU components by @c duration half cycles.
|
2019-08-03 02:12:34 +00:00
|
|
|
|
forceinline void advance_time(HalfCycles duration) {
|
2019-08-02 23:48:41 +00:00
|
|
|
|
time_since_video_update_ += duration;
|
2019-08-08 01:28:02 +00:00
|
|
|
|
iwm_ += duration;
|
2019-10-30 02:36:29 +00:00
|
|
|
|
ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15;
|
2019-08-02 23:48:41 +00:00
|
|
|
|
|
|
|
|
|
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
|
|
|
|
|
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
|
|
|
|
|
// may occur here in order to provide VSYNC at a proper moment.
|
|
|
|
|
// Possibly route vsync.
|
|
|
|
|
if(time_since_video_update_ < time_until_video_event_) {
|
|
|
|
|
via_clock_ += duration;
|
|
|
|
|
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
|
|
|
|
} else {
|
|
|
|
|
auto via_time_base = time_since_video_update_ - duration;
|
|
|
|
|
auto via_cycles_outstanding = duration;
|
|
|
|
|
while(time_until_video_event_ < time_since_video_update_) {
|
|
|
|
|
const auto via_cycles = time_until_video_event_ - via_time_base;
|
|
|
|
|
via_time_base = HalfCycles(0);
|
|
|
|
|
via_cycles_outstanding -= via_cycles;
|
|
|
|
|
|
|
|
|
|
via_clock_ += via_cycles;
|
|
|
|
|
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
|
|
|
|
|
|
|
|
|
video_.run_for(time_until_video_event_);
|
|
|
|
|
time_since_video_update_ -= time_until_video_event_;
|
|
|
|
|
time_until_video_event_ = video_.get_next_sequence_point();
|
|
|
|
|
|
|
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
via_clock_ += via_cycles_outstanding;
|
|
|
|
|
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
|
|
|
|
|
// Its clock and data lines are connected to the VIA.
|
|
|
|
|
keyboard_clock_ += duration;
|
|
|
|
|
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
|
|
|
|
if(keyboard_ticks > HalfCycles(0)) {
|
|
|
|
|
keyboard_.run_for(keyboard_ticks);
|
|
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
|
|
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Feed mouse inputs within at most 1250 cycles of each other.
|
|
|
|
|
if(mouse_.has_steps()) {
|
|
|
|
|
time_since_mouse_update_ += duration;
|
|
|
|
|
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
|
|
|
|
|
if(mouse_ticks > HalfCycles(0)) {
|
|
|
|
|
mouse_.prepare_step();
|
|
|
|
|
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
|
|
|
|
|
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
|
|
|
|
// anything connected.
|
|
|
|
|
|
|
|
|
|
// Consider updating the real-time clock.
|
|
|
|
|
real_time_clock_ += duration;
|
2019-10-30 02:36:29 +00:00
|
|
|
|
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral();
|
2019-08-02 23:48:41 +00:00
|
|
|
|
while(ticks--) {
|
|
|
|
|
clock_.update();
|
|
|
|
|
// TODO: leave a delay between toggling the input rather than using this coupled hack.
|
|
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
|
|
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
|
|
|
|
|
}
|
2019-08-16 03:14:40 +00:00
|
|
|
|
|
|
|
|
|
// Update the SCSI if currently active.
|
2019-12-22 18:42:24 +00:00
|
|
|
|
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
2019-09-02 20:03:33 +00:00
|
|
|
|
if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration);
|
2019-08-16 03:14:40 +00:00
|
|
|
|
}
|
2019-08-02 23:48:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
forceinline void update_video() {
|
2019-07-29 19:38:41 +00:00
|
|
|
|
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
2019-07-09 22:08:07 +00:00
|
|
|
|
time_until_video_event_ = video_.get_next_sequence_point();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-24 03:57:51 +00:00
|
|
|
|
Inputs::Mouse &get_mouse() final {
|
2019-06-11 22:41:41 +00:00
|
|
|
|
return mouse_;
|
2019-06-11 22:21:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-04 19:37:07 +00:00
|
|
|
|
using IWMActor = JustInTimeActor<IWM>;
|
2019-10-13 22:19:39 +00:00
|
|
|
|
|
2019-05-04 20:38:01 +00:00
|
|
|
|
class VIAPortHandler: public MOS::MOS6522::PortHandler {
|
|
|
|
|
public:
|
2020-10-30 01:03:02 +00:00
|
|
|
|
VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) :
|
2019-09-21 21:39:45 +00:00
|
|
|
|
machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
|
2019-05-04 20:38:01 +00:00
|
|
|
|
|
2019-05-04 21:12:26 +00:00
|
|
|
|
using Port = MOS::MOS6522::Port;
|
|
|
|
|
using Line = MOS::MOS6522::Line;
|
|
|
|
|
|
2020-05-30 04:37:06 +00:00
|
|
|
|
void set_port_output(Port port, uint8_t value, uint8_t) {
|
2019-05-04 20:38:01 +00:00
|
|
|
|
/*
|
|
|
|
|
Peripheral lines: keyboard data, interrupt configuration.
|
|
|
|
|
(See p176 [/215])
|
|
|
|
|
*/
|
|
|
|
|
switch(port) {
|
2019-05-04 21:12:26 +00:00
|
|
|
|
case Port::A:
|
2019-05-04 20:38:01 +00:00
|
|
|
|
/*
|
|
|
|
|
Port A:
|
|
|
|
|
b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR)
|
|
|
|
|
b6: 0 = alternate screen buffer, 1 = main screen buffer
|
|
|
|
|
b5: floppy disk SEL state control (upper/lower head "among other things")
|
|
|
|
|
b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map
|
|
|
|
|
b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer
|
|
|
|
|
b2–b0: audio output volume
|
|
|
|
|
*/
|
2019-08-08 01:28:02 +00:00
|
|
|
|
iwm_->set_select(!!(value & 0x20));
|
2019-05-30 16:08:00 +00:00
|
|
|
|
|
2019-06-01 19:03:15 +00:00
|
|
|
|
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
|
2019-05-08 16:34:26 +00:00
|
|
|
|
machine_.set_rom_is_overlay(!!(value & 0x10));
|
2019-05-30 16:08:00 +00:00
|
|
|
|
|
2019-06-01 18:39:40 +00:00
|
|
|
|
audio_.flush();
|
|
|
|
|
audio_.audio.set_volume(value & 7);
|
2019-05-04 20:38:01 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2019-05-04 21:12:26 +00:00
|
|
|
|
case Port::B:
|
|
|
|
|
/*
|
|
|
|
|
Port B:
|
|
|
|
|
b7: 0 = sound enabled, 1 = sound disabled
|
|
|
|
|
b6: [input] 0 = video beam in visible portion of line, 1 = outside
|
|
|
|
|
b5: [input] mouse y2
|
|
|
|
|
b4: [input] mouse x2
|
|
|
|
|
b3: [input] 0 = mouse button down, 1 = up
|
|
|
|
|
b2: 0 = real-time clock enabled, 1 = disabled
|
|
|
|
|
b1: clock's data-clock line
|
|
|
|
|
b0: clock's serial data line
|
|
|
|
|
*/
|
2019-05-08 04:12:19 +00:00
|
|
|
|
if(value & 0x4) clock_.abort();
|
|
|
|
|
else clock_.set_input(!!(value & 0x2), !!(value & 0x1));
|
2019-05-30 16:08:00 +00:00
|
|
|
|
|
2019-06-01 18:39:40 +00:00
|
|
|
|
audio_.flush();
|
2019-06-18 14:34:31 +00:00
|
|
|
|
audio_.audio.set_enabled(!(value & 0x80));
|
2019-05-04 20:38:01 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 21:12:26 +00:00
|
|
|
|
uint8_t get_port_input(Port port) {
|
|
|
|
|
switch(port) {
|
|
|
|
|
case Port::A:
|
2019-05-07 01:32:10 +00:00
|
|
|
|
// printf("6522 r A\n");
|
2019-05-08 20:54:19 +00:00
|
|
|
|
return 0x00; // TODO: b7 = SCC wait/request
|
2019-05-04 21:12:26 +00:00
|
|
|
|
|
|
|
|
|
case Port::B:
|
2019-06-13 02:19:25 +00:00
|
|
|
|
return uint8_t(
|
2019-07-03 01:14:33 +00:00
|
|
|
|
((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) |
|
2019-06-13 02:19:25 +00:00
|
|
|
|
((mouse_.get_channel(0) & 2) << 3) |
|
|
|
|
|
((mouse_.get_channel(1) & 2) << 4) |
|
2019-05-08 20:54:19 +00:00
|
|
|
|
(clock_.get_data() ? 0x02 : 0x00) |
|
2019-07-09 22:08:07 +00:00
|
|
|
|
(machine_.video_is_outputting() ? 0x00 : 0x40)
|
2019-06-13 02:19:25 +00:00
|
|
|
|
);
|
2019-05-04 21:12:26 +00:00
|
|
|
|
}
|
2019-07-08 22:13:23 +00:00
|
|
|
|
|
|
|
|
|
// Should be unreachable.
|
|
|
|
|
return 0xff;
|
2019-05-04 21:12:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_control_line_output(Port port, Line line, bool value) {
|
2019-05-08 04:12:19 +00:00
|
|
|
|
/*
|
|
|
|
|
Keyboard wiring (I believe):
|
2019-05-08 19:07:03 +00:00
|
|
|
|
CB2 = data (input/output)
|
2019-05-08 16:34:26 +00:00
|
|
|
|
CB1 = clock (input)
|
2019-05-08 04:12:19 +00:00
|
|
|
|
|
|
|
|
|
CA2 is used for receiving RTC interrupts.
|
2019-06-27 21:59:03 +00:00
|
|
|
|
CA1 is used for receiving vsync.
|
2019-05-08 04:12:19 +00:00
|
|
|
|
*/
|
2019-07-07 18:13:55 +00:00
|
|
|
|
if(port == Port::B && line == Line::Two) {
|
|
|
|
|
keyboard_.set_input(value);
|
|
|
|
|
}
|
2019-07-26 02:55:27 +00:00
|
|
|
|
else LOG("Unhandled control line output: " << (port ? 'B' : 'A') << int(line));
|
2019-05-04 21:12:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 19:44:29 +00:00
|
|
|
|
void run_for(HalfCycles duration) {
|
2019-07-17 18:41:36 +00:00
|
|
|
|
// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
|
|
|
|
|
// divided-by-two clock the audio works on.
|
2019-10-30 02:36:29 +00:00
|
|
|
|
audio_.time_since_update += HalfCycles(duration.as_integral() * 5);
|
2019-06-01 19:44:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 21:29:57 +00:00
|
|
|
|
void flush() {
|
|
|
|
|
audio_.flush();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-30 04:37:06 +00:00
|
|
|
|
void set_interrupt_status(bool) {
|
2019-07-24 03:13:03 +00:00
|
|
|
|
machine_.update_interrupt_input();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 20:38:01 +00:00
|
|
|
|
private:
|
|
|
|
|
ConcreteMachine &machine_;
|
2020-10-30 01:03:02 +00:00
|
|
|
|
Apple::Clock::SerialClock &clock_;
|
2019-05-08 16:34:26 +00:00
|
|
|
|
Keyboard &keyboard_;
|
2019-06-01 19:03:15 +00:00
|
|
|
|
DeferredAudio &audio_;
|
2019-10-13 22:19:39 +00:00
|
|
|
|
IWMActor &iwm_;
|
2019-06-11 23:52:37 +00:00
|
|
|
|
Inputs::QuadratureMouse &mouse_;
|
2019-05-04 03:40:22 +00:00
|
|
|
|
};
|
|
|
|
|
|
2019-05-04 02:16:07 +00:00
|
|
|
|
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
|
2019-06-01 18:39:40 +00:00
|
|
|
|
|
2019-06-01 23:31:32 +00:00
|
|
|
|
DriveSpeedAccumulator drive_speed_accumulator_;
|
2019-10-13 22:19:39 +00:00
|
|
|
|
IWMActor iwm_;
|
2019-06-01 23:31:32 +00:00
|
|
|
|
|
2019-06-01 19:03:15 +00:00
|
|
|
|
DeferredAudio audio_;
|
2019-05-04 03:25:42 +00:00
|
|
|
|
Video video_;
|
2019-05-04 03:40:22 +00:00
|
|
|
|
|
2020-10-30 01:03:02 +00:00
|
|
|
|
Apple::Clock::SerialClock clock_;
|
2019-05-08 16:34:26 +00:00
|
|
|
|
Keyboard keyboard_;
|
2019-05-08 04:12:19 +00:00
|
|
|
|
|
2019-05-04 03:40:22 +00:00
|
|
|
|
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
|
|
|
|
|
VIAPortHandler via_port_handler_;
|
2019-05-05 22:12:25 +00:00
|
|
|
|
|
2019-06-08 22:47:11 +00:00
|
|
|
|
Zilog::SCC::z8530 scc_;
|
2019-08-25 21:03:41 +00:00
|
|
|
|
SCSI::Bus scsi_bus_;
|
2019-08-12 00:55:20 +00:00
|
|
|
|
NCR::NCR5380::NCR5380 scsi_;
|
2019-08-25 21:03:41 +00:00
|
|
|
|
SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
|
2019-09-02 20:03:33 +00:00
|
|
|
|
bool scsi_bus_is_clocked_ = false;
|
2019-06-08 22:47:11 +00:00
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
HalfCycles via_clock_;
|
2019-05-08 17:58:52 +00:00
|
|
|
|
HalfCycles real_time_clock_;
|
|
|
|
|
HalfCycles keyboard_clock_;
|
2019-07-09 22:08:07 +00:00
|
|
|
|
HalfCycles time_since_video_update_;
|
|
|
|
|
HalfCycles time_until_video_event_;
|
2019-06-12 21:51:50 +00:00
|
|
|
|
HalfCycles time_since_mouse_update_;
|
2019-05-04 03:40:22 +00:00
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
bool ROM_is_overlay_ = true;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
int phase_ = 1;
|
2019-08-03 02:12:34 +00:00
|
|
|
|
int ram_subcycle_ = 0;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
|
2019-07-10 20:24:48 +00:00
|
|
|
|
DoubleDensityDrive drives_[2];
|
2019-06-11 22:21:56 +00:00
|
|
|
|
Inputs::QuadratureMouse mouse_;
|
2019-06-05 01:41:54 +00:00
|
|
|
|
|
2019-07-03 01:14:33 +00:00
|
|
|
|
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
|
|
|
|
|
|
2019-08-03 01:30:04 +00:00
|
|
|
|
enum class BusDevice {
|
|
|
|
|
RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-11 03:53:34 +00:00
|
|
|
|
/// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording
|
2019-08-03 01:30:04 +00:00
|
|
|
|
/// which device is current mapped in each area. Keeping it in a table is a bit faster
|
|
|
|
|
/// than the multi-level address inspection that is otherwise required, as well as
|
|
|
|
|
/// simplifying slightly the handling of different models.
|
|
|
|
|
///
|
2019-08-11 03:53:34 +00:00
|
|
|
|
/// So: index with the top 7 bits of the 24-bit address.
|
|
|
|
|
BusDevice memory_map_[128];
|
2019-08-03 01:30:04 +00:00
|
|
|
|
|
|
|
|
|
void setup_memory_map() {
|
2019-08-12 00:55:20 +00:00
|
|
|
|
// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true;
|
|
|
|
|
// start by calling into set_rom_is_overlay to seed everything up to $800000.
|
|
|
|
|
set_rom_is_overlay(true);
|
|
|
|
|
|
|
|
|
|
populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) {
|
|
|
|
|
map_to(0x900000, BusDevice::Unassigned);
|
|
|
|
|
map_to(0xa00000, BusDevice::SCCReadResetPhase);
|
|
|
|
|
map_to(0xb00000, BusDevice::Unassigned);
|
|
|
|
|
map_to(0xc00000, BusDevice::SCCWrite);
|
|
|
|
|
map_to(0xd00000, BusDevice::Unassigned);
|
|
|
|
|
map_to(0xe00000, BusDevice::IWM);
|
|
|
|
|
map_to(0xe80000, BusDevice::Unassigned);
|
|
|
|
|
map_to(0xf00000, BusDevice::VIA);
|
|
|
|
|
map_to(0xf80000, BusDevice::PhaseRead);
|
|
|
|
|
map_to(0x1000000, BusDevice::Unassigned);
|
|
|
|
|
});
|
2019-08-03 01:30:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-12 00:55:20 +00:00
|
|
|
|
void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) {
|
2019-08-03 01:30:04 +00:00
|
|
|
|
// Define semantics for below; map_to will write from the current cursor position
|
|
|
|
|
// to the supplied 24-bit address, setting a particular mapped device.
|
2019-08-12 00:55:20 +00:00
|
|
|
|
int segment = start_address >> 17;
|
2019-08-03 01:30:04 +00:00
|
|
|
|
auto map_to = [&segment, this](int address, BusDevice device) {
|
2019-08-11 03:53:34 +00:00
|
|
|
|
for(; segment < address >> 17; ++segment) {
|
2019-08-03 01:30:04 +00:00
|
|
|
|
this->memory_map_[segment] = device;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
populator(map_to);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-03 18:50:36 +00:00
|
|
|
|
uint32_t ram_mask_ = 0;
|
|
|
|
|
uint32_t rom_mask_ = 0;
|
2020-02-14 02:14:13 +00:00
|
|
|
|
uint8_t rom_[128*1024];
|
|
|
|
|
std::vector<uint8_t> ram_;
|
2019-05-04 02:16:07 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using namespace Apple::Macintosh;
|
|
|
|
|
|
|
|
|
|
Machine *Machine::Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
2019-06-03 18:50:36 +00:00
|
|
|
|
auto *const mac_target = dynamic_cast<const Analyser::Static::Macintosh::Target *>(target);
|
|
|
|
|
|
|
|
|
|
using Model = Analyser::Static::Macintosh::Target::Model;
|
|
|
|
|
switch(mac_target->model) {
|
|
|
|
|
default:
|
2019-06-05 02:13:00 +00:00
|
|
|
|
case Model::Mac128k: return new ConcreteMachine<Model::Mac128k>(*mac_target, rom_fetcher);
|
|
|
|
|
case Model::Mac512k: return new ConcreteMachine<Model::Mac512k>(*mac_target, rom_fetcher);
|
|
|
|
|
case Model::Mac512ke: return new ConcreteMachine<Model::Mac512ke>(*mac_target, rom_fetcher);
|
|
|
|
|
case Model::MacPlus: return new ConcreteMachine<Model::MacPlus>(*mac_target, rom_fetcher);
|
2019-06-03 18:50:36 +00:00
|
|
|
|
}
|
2019-05-04 02:16:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Machine::~Machine() {}
|