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"
|
2019-05-08 04:12:19 +00:00
|
|
|
|
#include "RealTimeClock.hpp"
|
2019-06-05 01:41:54 +00:00
|
|
|
|
#include "SonyDrive.hpp"
|
2019-05-08 16:34:26 +00:00
|
|
|
|
#include "Video.hpp"
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
|
|
|
|
#include "../../CRTMachine.hpp"
|
2019-06-05 02:13:00 +00:00
|
|
|
|
#include "../../MediaTarget.hpp"
|
2019-06-11 22:21:56 +00:00
|
|
|
|
#include "../../MouseMachine.hpp"
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
2019-06-11 22:21:56 +00:00
|
|
|
|
#include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp"
|
|
|
|
|
|
2019-06-18 18:04:28 +00:00
|
|
|
|
//#define LOG_TRACE
|
2019-05-28 19:17:03 +00:00
|
|
|
|
|
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"
|
2019-05-06 01:55:34 +00:00
|
|
|
|
#include "../../../Components/DiskII/IWM.hpp"
|
2019-06-01 18:39:40 +00:00
|
|
|
|
#include "../../../Processors/68000/68000.hpp"
|
2019-05-04 03:25:42 +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 {
|
|
|
|
|
|
|
|
|
|
const int CLOCK_RATE = 7833600;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2019-05-04 03:25:42 +00:00
|
|
|
|
public CRTMachine::Machine,
|
2019-06-05 02:13:00 +00:00
|
|
|
|
public MediaTarget::Machine,
|
2019-06-11 22:21:56 +00:00
|
|
|
|
public MouseMachine::Machine,
|
2019-05-04 02:16:07 +00:00
|
|
|
|
public CPU::MC68000::BusHandler {
|
|
|
|
|
public:
|
2019-06-05 02:13:00 +00:00
|
|
|
|
using Target = Analyser::Static::Macintosh::Target;
|
|
|
|
|
|
|
|
|
|
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
2019-05-04 03:25:42 +00:00
|
|
|
|
mc68000_(*this),
|
2019-06-01 23:31:32 +00:00
|
|
|
|
iwm_(CLOCK_RATE),
|
2019-06-03 18:50:36 +00:00
|
|
|
|
video_(ram_, audio_, drive_speed_accumulator_),
|
2019-05-04 20:38:01 +00:00
|
|
|
|
via_(via_port_handler_),
|
2019-06-11 23:52:37 +00:00
|
|
|
|
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
|
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;
|
|
|
|
|
std::string rom_name;
|
|
|
|
|
uint32_t ram_size, rom_size;
|
|
|
|
|
switch(model) {
|
|
|
|
|
default:
|
|
|
|
|
case Model::Mac128k:
|
|
|
|
|
ram_size = 128*1024;
|
|
|
|
|
rom_size = 64*1024;
|
|
|
|
|
rom_name = "mac128k.rom";
|
|
|
|
|
break;
|
|
|
|
|
case Model::Mac512k:
|
|
|
|
|
ram_size = 512*1024;
|
|
|
|
|
rom_size = 64*1024;
|
|
|
|
|
rom_name = "mac512k.rom";
|
|
|
|
|
break;
|
|
|
|
|
case Model::Mac512ke:
|
|
|
|
|
case Model::MacPlus:
|
|
|
|
|
ram_size = 512*1024;
|
|
|
|
|
rom_size = 128*1024;
|
|
|
|
|
rom_name = "macplus.rom";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
ram_mask_ = (ram_size >> 1) - 1;
|
|
|
|
|
rom_mask_ = (rom_size >> 1) - 1;
|
|
|
|
|
video_.set_ram_mask(ram_mask_);
|
|
|
|
|
|
2019-05-04 02:39:09 +00:00
|
|
|
|
// Grab a copy of the ROM and convert it into big-endian data.
|
2019-06-03 18:50:36 +00:00
|
|
|
|
const auto roms = rom_fetcher("Macintosh", { rom_name });
|
2019-05-04 02:16:07 +00:00
|
|
|
|
if(!roms[0]) {
|
|
|
|
|
throw ROMMachine::Error::MissingROMs;
|
|
|
|
|
}
|
2019-06-03 18:50:36 +00:00
|
|
|
|
roms[0]->resize(rom_size);
|
|
|
|
|
Memory::PackBigEndian16(*roms[0], rom_);
|
2019-05-04 03:25:42 +00:00
|
|
|
|
|
2019-06-13 17:35:16 +00:00
|
|
|
|
// Randomise memory contents.
|
2019-06-13 22:41:38 +00:00
|
|
|
|
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
|
2019-06-13 17:35:16 +00:00
|
|
|
|
|
2019-06-05 01:41:54 +00:00
|
|
|
|
// Attach the drives to the IWM.
|
|
|
|
|
iwm_.iwm.set_drive(0, &drives_[0]);
|
2019-07-02 17:59:30 +00:00
|
|
|
|
// iwm_.iwm.set_drive(1, &drives_[1]);
|
2019-06-05 01:41:54 +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-06-01 19:03:15 +00:00
|
|
|
|
audio_.speaker.set_input_rate(float(CLOCK_RATE));
|
2019-06-05 02:13:00 +00:00
|
|
|
|
|
|
|
|
|
// Insert any supplied media.
|
|
|
|
|
insert_media(target.media);
|
2019-05-04 03:25:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 21:29:57 +00:00
|
|
|
|
~ConcreteMachine() {
|
|
|
|
|
audio_.queue.flush();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 03:25:42 +00:00
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
|
|
|
|
video_.set_scan_target(scan_target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Outputs::Speaker::Speaker *get_speaker() override {
|
2019-06-01 18:39:40 +00:00
|
|
|
|
return &audio_.speaker;
|
2019-05-04 03:25:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void run_for(const Cycles cycles) override {
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
2019-05-08 20:54:19 +00:00
|
|
|
|
// time_since_video_update_ += cycle.length;
|
2019-05-30 16:08:00 +00:00
|
|
|
|
iwm_.time_since_update += cycle.length;
|
2019-05-05 02:27:58 +00:00
|
|
|
|
|
2019-05-07 21:16:22 +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).
|
2019-05-04 16:33:27 +00:00
|
|
|
|
via_clock_ += cycle.length;
|
|
|
|
|
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
|
|
|
|
|
2019-06-13 02:19:25 +00:00
|
|
|
|
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
|
2019-05-08 17:58:52 +00:00
|
|
|
|
// Its clock and data lines are connected to the VIA.
|
|
|
|
|
keyboard_clock_ += cycle.length;
|
2019-06-12 21:51:50 +00:00
|
|
|
|
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
2019-05-08 17:58:52 +00:00
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-13 02:19:25 +00:00
|
|
|
|
// Feed mouse inputs within at most 1250 cycles of each other.
|
2019-06-12 21:51:50 +00:00
|
|
|
|
time_since_mouse_update_ += cycle.length;
|
2019-06-13 02:19:25 +00:00
|
|
|
|
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
|
2019-06-12 21:51:50 +00:00
|
|
|
|
if(mouse_ticks > HalfCycles(0)) {
|
|
|
|
|
mouse_.prepare_step();
|
2019-06-13 02:19:25 +00:00
|
|
|
|
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
|
|
|
|
|
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
|
2019-06-12 21:51:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
|
|
|
|
// anything connected.
|
2019-05-04 16:33:27 +00:00
|
|
|
|
|
2019-05-08 04:12:19 +00:00
|
|
|
|
// Consider updating the real-time clock.
|
|
|
|
|
real_time_clock_ += cycle.length;
|
|
|
|
|
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
|
|
|
|
|
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-05-08 20:54:19 +00:00
|
|
|
|
// Update the video. TODO: only on demand.
|
|
|
|
|
video_.run_for(cycle.length);
|
2019-06-02 17:39:25 +00:00
|
|
|
|
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
2019-05-08 20:54:19 +00:00
|
|
|
|
|
2019-05-08 04:12:19 +00:00
|
|
|
|
// Update interrupt input. TODO: move this into a VIA/etc delegate callback?
|
2019-06-13 02:19:25 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
// mc68000_.set_interrupt_level(
|
|
|
|
|
// (via_.get_interrupt_line() ? 1 : 0) |
|
|
|
|
|
// (scc_.get_interrupt_line() ? 2 : 0)
|
|
|
|
|
// /* TODO: to emulate a programmer's switch: have it set bit 2 when pressed. */
|
|
|
|
|
// );
|
2019-05-08 04:12:19 +00:00
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
// A null cycle leaves nothing else to do.
|
|
|
|
|
if(cycle.operation) {
|
|
|
|
|
auto word_address = cycle.word_address();
|
|
|
|
|
|
2019-05-07 21:16:22 +00:00
|
|
|
|
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
|
|
|
|
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
2019-05-06 18:10:13 +00:00
|
|
|
|
|
2019-05-04 16:33:27 +00:00
|
|
|
|
if(word_address >= 0x400000) {
|
2019-05-04 18:23:37 +00:00
|
|
|
|
if(cycle.data_select_active()) {
|
2019-05-04 21:12:26 +00:00
|
|
|
|
const int register_address = word_address >> 8;
|
|
|
|
|
|
2019-05-07 02:57:29 +00:00
|
|
|
|
switch(word_address & 0x78f000) {
|
|
|
|
|
case 0x70f000:
|
2019-05-04 20:38:01 +00:00
|
|
|
|
// VIA accesses are via address 0xefe1fe + register*512,
|
|
|
|
|
// which at word precision is 0x77f0ff + register*256.
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2019-05-04 21:12:26 +00:00
|
|
|
|
cycle.value->halves.low = via_.get_register(register_address);
|
2019-05-04 20:38:01 +00:00
|
|
|
|
} else {
|
2019-05-04 21:12:26 +00:00
|
|
|
|
via_.set_register(register_address, cycle.value->halves.low);
|
2019-05-04 20:38:01 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2019-05-07 02:57:29 +00:00
|
|
|
|
case 0x68f000:
|
2019-05-06 01:55:34 +00:00
|
|
|
|
// The IWM; this is a purely polled device, so can be run on demand.
|
2019-06-15 20:08:54 +00:00
|
|
|
|
#ifndef NDEBUG
|
|
|
|
|
// printf("[%06x]: ", mc68000_.get_state().program_counter);
|
|
|
|
|
#endif
|
2019-05-30 16:08:00 +00:00
|
|
|
|
iwm_.flush();
|
2019-05-05 22:12:25 +00:00
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2019-05-30 16:08:00 +00:00
|
|
|
|
cycle.value->halves.low = iwm_.iwm.read(register_address);
|
2019-05-05 22:12:25 +00:00
|
|
|
|
} else {
|
2019-05-30 16:08:00 +00:00
|
|
|
|
iwm_.iwm.write(register_address, cycle.value->halves.low);
|
2019-05-05 22:12:25 +00:00
|
|
|
|
}
|
2019-05-04 20:38:01 +00:00
|
|
|
|
break;
|
2019-05-04 18:23:37 +00:00
|
|
|
|
|
2019-06-03 18:50:36 +00:00
|
|
|
|
case 0x780000:
|
|
|
|
|
// Phase read.
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
|
|
|
|
cycle.value->halves.low = phase_ & 7;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x480000: case 0x48f000:
|
2019-06-08 22:47:11 +00:00
|
|
|
|
case 0x580000: case 0x58f000:
|
2019-06-03 18:50:36 +00:00
|
|
|
|
// Any word access here adjusts phase.
|
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) {
|
|
|
|
|
++phase_;
|
|
|
|
|
} else {
|
2019-06-08 22:47:11 +00:00
|
|
|
|
if(word_address < 0x500000) {
|
|
|
|
|
// A0 = 1 => reset; A0 = 0 => read.
|
|
|
|
|
if(*cycle.address & 1) {
|
|
|
|
|
scc_.reset();
|
|
|
|
|
} else {
|
|
|
|
|
const auto read = scc_.read(int(word_address));
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
|
|
|
|
cycle.value->halves.low = read;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if(*cycle.address & 1) {
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
|
|
|
|
scc_.write(int(word_address), 0xff);
|
|
|
|
|
} else {
|
|
|
|
|
scc_.write(int(word_address), cycle.value->halves.low);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-03 18:50:36 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2019-05-07 01:32:10 +00:00
|
|
|
|
default:
|
|
|
|
|
if(cycle.operation & Microcycle::Read) {
|
2019-06-03 18:50:36 +00:00
|
|
|
|
printf("Unrecognised read %06x\n", *cycle.address & 0xffffff);
|
2019-05-08 20:54:19 +00:00
|
|
|
|
cycle.value->halves.low = 0x00;
|
2019-06-03 18:50:36 +00:00
|
|
|
|
} else {
|
|
|
|
|
printf("Unrecognised write %06x\n", *cycle.address & 0xffffff);
|
2019-05-07 01:32:10 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-06-08 22:47:11 +00:00
|
|
|
|
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
2019-05-04 18:23:37 +00:00
|
|
|
|
}
|
2019-05-04 16:33:27 +00:00
|
|
|
|
} else {
|
|
|
|
|
if(cycle.data_select_active()) {
|
|
|
|
|
uint16_t *memory_base = nullptr;
|
2019-05-07 21:16:22 +00:00
|
|
|
|
auto operation = cycle.operation;
|
2019-05-04 18:23:37 +00:00
|
|
|
|
|
|
|
|
|
// When ROM overlay is enabled, the ROM begins at both $000000 and $400000,
|
|
|
|
|
// and RAM is available at $600000.
|
|
|
|
|
//
|
|
|
|
|
// Otherwise RAM is mapped at $000000 and ROM from $400000.
|
2019-05-04 21:29:30 +00:00
|
|
|
|
if(
|
2019-05-07 21:16:22 +00:00
|
|
|
|
(ROM_is_overlay_ && word_address >= 0x300000) ||
|
|
|
|
|
(!ROM_is_overlay_ && word_address < 0x200000)
|
2019-05-04 21:29:30 +00:00
|
|
|
|
) {
|
2019-06-03 18:50:36 +00:00
|
|
|
|
memory_base = ram_;
|
|
|
|
|
word_address &= ram_mask_;
|
2019-05-04 21:29:30 +00:00
|
|
|
|
} else {
|
2019-06-03 18:50:36 +00:00
|
|
|
|
memory_base = rom_;
|
|
|
|
|
word_address &= rom_mask_;
|
2019-05-07 21:16:22 +00:00
|
|
|
|
|
2019-05-08 19:07:03 +00:00
|
|
|
|
// Disallow writes to ROM; also it doesn't mirror above 0x60000, ever.
|
|
|
|
|
if(!(operation & Microcycle::Read) || word_address >= 0x300000) operation = 0;
|
2019-05-04 16:33:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 20:24:41 +00:00
|
|
|
|
const auto masked_operation = operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge);
|
|
|
|
|
switch(masked_operation) {
|
2019-05-07 21:16:22 +00:00
|
|
|
|
default:
|
|
|
|
|
break;
|
2019-05-04 21:29:30 +00:00
|
|
|
|
|
2019-05-28 20:24:41 +00:00
|
|
|
|
// Catches the deliberation set of operation to 0 above.
|
|
|
|
|
case 0: break;
|
|
|
|
|
|
|
|
|
|
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
|
|
|
|
|
// The Macintosh uses autovectored interrupts.
|
|
|
|
|
mc68000_.set_is_peripheral_address(true);
|
|
|
|
|
break;
|
|
|
|
|
|
2019-05-04 21:29:30 +00:00
|
|
|
|
case Microcycle::SelectWord | Microcycle::Read:
|
|
|
|
|
cycle.value->full = memory_base[word_address];
|
|
|
|
|
break;
|
|
|
|
|
case Microcycle::SelectByte | Microcycle::Read:
|
|
|
|
|
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
|
|
|
|
|
break;
|
|
|
|
|
case Microcycle::SelectWord:
|
|
|
|
|
memory_base[word_address] = cycle.value->full;
|
|
|
|
|
break;
|
|
|
|
|
case Microcycle::SelectByte:
|
|
|
|
|
memory_base[word_address] = uint16_t(
|
|
|
|
|
(cycle.value->halves.low << cycle.byte_shift()) |
|
2019-05-06 02:48:40 +00:00
|
|
|
|
(memory_base[word_address] & cycle.untouched_byte_mask())
|
2019-05-04 21:29:30 +00:00
|
|
|
|
);
|
|
|
|
|
break;
|
2019-05-04 16:33:27 +00:00
|
|
|
|
}
|
2019-06-18 14:34:31 +00:00
|
|
|
|
|
|
|
|
|
// if(!(operation & Microcycle::Read) && (word_address == (0x0000182e >> 1))) {
|
|
|
|
|
// printf("Write to 0000182e: %04x from %08x\n", cycle.value->full, mc68000_.get_state().program_counter);
|
|
|
|
|
// }
|
|
|
|
|
// if(
|
|
|
|
|
// (
|
|
|
|
|
// (word_address == (0x00000352 >> 1))
|
|
|
|
|
// || (word_address == (0x00000354 >> 1))
|
|
|
|
|
// || (word_address == (0x00005d16 >> 1))
|
|
|
|
|
// )
|
|
|
|
|
// ) {
|
|
|
|
|
// printf("%s %08x: %04x from around %08x\n", (operation & Microcycle::Read) ? "Read" : "Write", word_address << 1, memory_base[word_address], mc68000_.get_state().program_counter);
|
|
|
|
|
// }
|
2019-05-04 16:33:27 +00:00
|
|
|
|
} else {
|
2019-05-04 21:29:30 +00:00
|
|
|
|
// TODO: add delay if this is a RAM access and video blocks it momentarily.
|
|
|
|
|
// "Each [video] fetch took two cycles out of eight"
|
2019-05-04 16:33:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 03:55:28 +00:00
|
|
|
|
/*
|
|
|
|
|
Normal memory map:
|
|
|
|
|
|
|
|
|
|
000000: RAM
|
|
|
|
|
400000: ROM
|
|
|
|
|
9FFFF8+: SCC read operations
|
|
|
|
|
BFFFF8+: SCC write operations
|
|
|
|
|
DFE1FF+: IWM
|
|
|
|
|
EFE1FE+: VIA
|
|
|
|
|
*/
|
|
|
|
|
|
2019-05-04 03:40:22 +00:00
|
|
|
|
return HalfCycles(0);
|
|
|
|
|
}
|
|
|
|
|
|
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-05-08 20:54:19 +00:00
|
|
|
|
// video_.run_for(time_since_video_update_.flush());
|
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-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-06-01 19:03:15 +00:00
|
|
|
|
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
|
|
|
|
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
|
2019-05-06 03:05:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-05 02:13:00 +00:00
|
|
|
|
bool insert_media(const Analyser::Static::Media &media) override {
|
|
|
|
|
if(media.disks.empty())
|
|
|
|
|
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.
|
|
|
|
|
drives_[0].set_disk(media.disks[0]);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 02:16:07 +00:00
|
|
|
|
private:
|
2019-06-11 22:41:41 +00:00
|
|
|
|
Inputs::Mouse &get_mouse() override {
|
|
|
|
|
return mouse_;
|
2019-06-11 22:21:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 16:08:00 +00:00
|
|
|
|
struct IWM {
|
|
|
|
|
IWM(int clock_rate) : iwm(clock_rate) {}
|
|
|
|
|
|
|
|
|
|
Apple::IWM iwm;
|
|
|
|
|
HalfCycles time_since_update;
|
|
|
|
|
|
|
|
|
|
void flush() {
|
|
|
|
|
iwm.run_for(time_since_update.flush_cycles());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-04 20:38:01 +00:00
|
|
|
|
class VIAPortHandler: public MOS::MOS6522::PortHandler {
|
|
|
|
|
public:
|
2019-06-11 23:52:37 +00:00
|
|
|
|
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
|
|
|
|
|
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), 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;
|
|
|
|
|
|
|
|
|
|
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
|
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-05-30 16:08:00 +00:00
|
|
|
|
iwm_.flush();
|
2019-06-06 22:32:11 +00:00
|
|
|
|
iwm_.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-06-11 23:52:37 +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-06-13 02:19:25 +00:00
|
|
|
|
(video_.is_outputting() ? 0x00 : 0x40)
|
|
|
|
|
);
|
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-05-08 16:34:26 +00:00
|
|
|
|
if(port == Port::B && line == Line::Two) keyboard_.set_input(value);
|
2019-06-08 22:47:11 +00:00
|
|
|
|
else printf("Unhandled control line output: %c %d\n", 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) {
|
|
|
|
|
audio_.time_since_update += duration;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 21:29:57 +00:00
|
|
|
|
void flush() {
|
|
|
|
|
audio_.flush();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-04 20:38:01 +00:00
|
|
|
|
private:
|
|
|
|
|
ConcreteMachine &machine_;
|
2019-05-08 04:12:19 +00:00
|
|
|
|
RealTimeClock &clock_;
|
2019-05-08 16:34:26 +00:00
|
|
|
|
Keyboard &keyboard_;
|
2019-05-08 20:54:19 +00:00
|
|
|
|
Video &video_;
|
2019-06-01 19:03:15 +00:00
|
|
|
|
DeferredAudio &audio_;
|
2019-05-30 16:08:00 +00:00
|
|
|
|
IWM &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_;
|
|
|
|
|
IWM iwm_;
|
|
|
|
|
|
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
|
|
|
|
|
2019-05-08 04:12:19 +00:00
|
|
|
|
RealTimeClock 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-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-05-08 20:54:19 +00:00
|
|
|
|
HalfCycles video_clock_;
|
|
|
|
|
// HalfCycles time_since_video_update_;
|
2019-05-06 01:55:34 +00:00
|
|
|
|
HalfCycles time_since_iwm_update_;
|
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-06-05 01:41:54 +00:00
|
|
|
|
SonyDrive drives_[2];
|
2019-06-11 22:21:56 +00:00
|
|
|
|
Inputs::QuadratureMouse mouse_;
|
2019-06-05 01:41:54 +00:00
|
|
|
|
|
2019-06-03 18:50:36 +00:00
|
|
|
|
uint32_t ram_mask_ = 0;
|
|
|
|
|
uint32_t rom_mask_ = 0;
|
|
|
|
|
uint16_t rom_[64*1024];
|
|
|
|
|
uint16_t ram_[256*1024];
|
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() {}
|