mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
Merge pull request #638 from TomHarte/SimplifiedBus
Simplifies code around Mac bus decoding.
This commit is contained in:
commit
80f6d665d9
@ -131,6 +131,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
|
|
||||||
// Insert any supplied media.
|
// Insert any supplied media.
|
||||||
insert_media(target.media);
|
insert_media(target.media);
|
||||||
|
|
||||||
|
// Set the immutables of the memory map.
|
||||||
|
setup_memory_map();
|
||||||
}
|
}
|
||||||
|
|
||||||
~ConcreteMachine() {
|
~ConcreteMachine() {
|
||||||
@ -152,95 +155,47 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
using Microcycle = CPU::MC68000::Microcycle;
|
using Microcycle = CPU::MC68000::Microcycle;
|
||||||
|
|
||||||
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
||||||
// TODO: pick a delay if this is a video-clashing memory fetch.
|
|
||||||
HalfCycles delay(0);
|
HalfCycles delay(0);
|
||||||
|
|
||||||
time_since_video_update_ += cycle.length;
|
// Grab the word-precision address being accessed.
|
||||||
iwm_.time_since_update += cycle.length;
|
uint32_t word_address = 0;
|
||||||
|
|
||||||
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
|
// Take a sneak peak and add a delay if this is a RAM access that would overlap with video.
|
||||||
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
|
if(cycle.data_select_active()) {
|
||||||
// may occur here in order to provide VSYNC at a proper moment.
|
word_address = cycle.active_operation_word_address();
|
||||||
// Possibly route vsync.
|
if(memory_map_[word_address >> 18] == BusDevice::RAM && ram_subcycle_ < 4) {
|
||||||
if(time_since_video_update_ < time_until_video_event_) {
|
delay = HalfCycles(4 - ram_subcycle_);
|
||||||
via_clock_ += cycle.length;
|
|
||||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
|
||||||
} else {
|
|
||||||
auto via_time_base = time_since_video_update_ - cycle.length;
|
|
||||||
auto via_cycles_outstanding = cycle.length;
|
|
||||||
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_ += cycle.length;
|
|
||||||
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_ += cycle.length;
|
|
||||||
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
|
// Advance time.
|
||||||
// anything connected.
|
advance_time(cycle.length + delay);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A null cycle leaves nothing else to do.
|
// A null cycle leaves nothing else to do.
|
||||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
|
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
|
||||||
|
|
||||||
auto word_address = cycle.active_operation_word_address();
|
|
||||||
|
|
||||||
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
||||||
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
||||||
|
|
||||||
// All code below deals only with reads and writes — cycles in which a
|
// 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
|
// data select is active. So quit now if this is not the active part of
|
||||||
// a read or write.
|
// a read or write.
|
||||||
if(!cycle.data_select_active()) return delay;
|
if(!cycle.data_select_active()) return delay;
|
||||||
|
|
||||||
// Check whether this access maps into the IO area; if so then
|
uint16_t *memory_base = nullptr;
|
||||||
// apply more complicated decoding logic.
|
switch(memory_map_[word_address >> 18]) {
|
||||||
if(word_address >= 0x400000) {
|
default: assert(false);
|
||||||
const int register_address = word_address >> 8;
|
|
||||||
|
case BusDevice::Unassigned:
|
||||||
|
fill_unmapped(cycle);
|
||||||
|
return delay;
|
||||||
|
|
||||||
|
case BusDevice::VIA: {
|
||||||
|
if(*cycle.address & 1) {
|
||||||
|
fill_unmapped(cycle);
|
||||||
|
} else {
|
||||||
|
const int register_address = word_address >> 8;
|
||||||
|
|
||||||
switch(word_address & 0x78f000) {
|
|
||||||
case 0x70f000:
|
|
||||||
// VIA accesses are via address 0xefe1fe + register*512,
|
// VIA accesses are via address 0xefe1fe + register*512,
|
||||||
// which at word precision is 0x77f0ff + register*256.
|
// which at word precision is 0x77f0ff + register*256.
|
||||||
if(cycle.operation & Microcycle::Read) {
|
if(cycle.operation & Microcycle::Read) {
|
||||||
@ -248,9 +203,23 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
} else {
|
} else {
|
||||||
via_.set_register(register_address, cycle.value->halves.low);
|
via_.set_register(register_address, cycle.value->halves.low);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x68f000:
|
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) {
|
||||||
|
const int register_address = word_address >> 8;
|
||||||
|
|
||||||
// The IWM; this is a purely polled device, so can be run on demand.
|
// The IWM; this is a purely polled device, so can be run on demand.
|
||||||
iwm_.flush();
|
iwm_.flush();
|
||||||
if(cycle.operation & Microcycle::Read) {
|
if(cycle.operation & Microcycle::Read) {
|
||||||
@ -258,101 +227,79 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
} else {
|
} else {
|
||||||
iwm_.iwm.write(register_address, cycle.value->halves.low);
|
iwm_.iwm.write(register_address, cycle.value->halves.low);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x780000:
|
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||||
// Phase read.
|
} else {
|
||||||
if(cycle.operation & Microcycle::Read) {
|
fill_unmapped(cycle);
|
||||||
cycle.value->halves.low = phase_ & 7;
|
}
|
||||||
}
|
} return delay;
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x480000: case 0x48f000:
|
case BusDevice::SCCReadResetPhase: {
|
||||||
case 0x580000: case 0x58f000:
|
// Any word access here adjusts phase.
|
||||||
// Any word access here adjusts phase.
|
if(cycle.operation & Microcycle::SelectWord) {
|
||||||
if(cycle.operation & Microcycle::SelectWord) {
|
adjust_phase();
|
||||||
++phase_;
|
} else {
|
||||||
|
// A0 = 1 => reset; A0 = 0 => read.
|
||||||
|
if(*cycle.address & 1) {
|
||||||
|
scc_.reset();
|
||||||
|
|
||||||
|
if(cycle.operation & Microcycle::Read) {
|
||||||
|
cycle.value->halves.low = 0xff;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if(word_address < 0x500000) {
|
const auto read = scc_.read(int(word_address));
|
||||||
// A0 = 1 => reset; A0 = 0 => read.
|
if(cycle.operation & Microcycle::Read) {
|
||||||
if(*cycle.address & 1) {
|
cycle.value->halves.low = read;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if(cycle.operation & Microcycle::Read) {
|
|
||||||
LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff));
|
|
||||||
cycle.value->halves.low = 0x00;
|
|
||||||
} else {
|
|
||||||
LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
|
||||||
|
|
||||||
return delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Having reached here, this is a RAM or ROM access.
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
uint16_t *memory_base;
|
|
||||||
if(
|
|
||||||
(!ROM_is_overlay_ && word_address < 0x200000) ||
|
|
||||||
(ROM_is_overlay_ && word_address >= 0x300000)
|
|
||||||
) {
|
|
||||||
memory_base = ram_;
|
|
||||||
word_address &= ram_mask_;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
|
|
||||||
// (And that ram_mask_ = ram size - 1).
|
|
||||||
// if(word_address > ram_mask_ - 0x6c80)
|
|
||||||
update_video();
|
|
||||||
} else {
|
|
||||||
memory_base = rom_;
|
|
||||||
word_address &= rom_mask_;
|
|
||||||
|
|
||||||
// Writes to ROM have no effect, and it doesn't mirror above 0x60000.
|
|
||||||
if(!(cycle.operation & Microcycle::Read)) return delay;
|
|
||||||
if(word_address >= 0x300000) {
|
|
||||||
if(cycle.operation & Microcycle::SelectWord) {
|
|
||||||
cycle.value->full = 0xffff;
|
|
||||||
} else {
|
|
||||||
cycle.value->halves.low = 0xff;
|
|
||||||
}
|
}
|
||||||
return delay;
|
} return delay;
|
||||||
}
|
|
||||||
|
case BusDevice::SCCWrite: {
|
||||||
|
// Any word access here adjusts phase.
|
||||||
|
if(cycle.operation & Microcycle::SelectWord) {
|
||||||
|
adjust_phase();
|
||||||
|
} else {
|
||||||
|
if(*cycle.address & 1) {
|
||||||
|
if(cycle.operation & Microcycle::Read) {
|
||||||
|
scc_.write(int(word_address), 0xff);
|
||||||
|
cycle.value->halves.low = 0xff;
|
||||||
|
} else {
|
||||||
|
scc_.write(int(word_address), cycle.value->halves.low);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fill_unmapped(cycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
|
||||||
|
// (And that ram_mask_ = ram size - 1).
|
||||||
|
if(word_address > ram_mask_ - 0x6c80)
|
||||||
|
update_video();
|
||||||
|
|
||||||
|
memory_base = ram_;
|
||||||
|
word_address &= ram_mask_;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case BusDevice::ROM: {
|
||||||
|
if(!(cycle.operation & Microcycle::Read)) return delay;
|
||||||
|
memory_base = rom_;
|
||||||
|
word_address &= rom_mask_;
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM.
|
||||||
|
// TODO: interrupt acknowledge cycles also end up here, which may suggest the 68000 is loading the address bus
|
||||||
|
// incorrectly during interrupt acknowledgment cycles. Check.
|
||||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
|
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Catches the deliberation set of operation to 0 above.
|
|
||||||
case 0: break;
|
|
||||||
|
|
||||||
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
|
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
|
||||||
// The Macintosh uses autovectored interrupts.
|
// The Macintosh uses autovectored interrupts.
|
||||||
mc68000_.set_is_peripheral_address(true);
|
mc68000_.set_is_peripheral_address(true);
|
||||||
@ -375,17 +322,6 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Normal memory map:
|
|
||||||
|
|
||||||
000000: RAM
|
|
||||||
400000: ROM
|
|
||||||
9FFFF8+: SCC read operations
|
|
||||||
BFFFF8+: SCC write operations
|
|
||||||
DFE1FF+: IWM
|
|
||||||
EFE1FE+: VIA
|
|
||||||
*/
|
|
||||||
|
|
||||||
return delay;
|
return delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,6 +342,48 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
|
|
||||||
void set_rom_is_overlay(bool rom_is_overlay) {
|
void set_rom_is_overlay(bool rom_is_overlay) {
|
||||||
ROM_is_overlay_ = rom_is_overlay;
|
ROM_is_overlay_ = rom_is_overlay;
|
||||||
|
|
||||||
|
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||||
|
switch(model) {
|
||||||
|
case Model::Mac128k:
|
||||||
|
case Model::Mac512k:
|
||||||
|
case Model::Mac512ke:
|
||||||
|
populate_memory_map([rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
||||||
|
// 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.
|
||||||
|
for(int c = 0; c <= 0x600000; c += 0x100000) {
|
||||||
|
map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned);
|
||||||
|
}
|
||||||
|
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:
|
||||||
|
populate_memory_map([rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
||||||
|
// Addresses up to $80 0000 aren't affected by this bit.
|
||||||
|
if(rom_is_overlay) {
|
||||||
|
map_to(0x100000, BusDevice::ROM);
|
||||||
|
map_to(0x400000, BusDevice::Unassigned);
|
||||||
|
map_to(0x500000, BusDevice::ROM);
|
||||||
|
map_to(0x580000, BusDevice::Unassigned);
|
||||||
|
map_to(0x600000, BusDevice::SCSI);
|
||||||
|
map_to(0x800000, BusDevice::RAM);
|
||||||
|
} else {
|
||||||
|
map_to(0x400000, BusDevice::RAM);
|
||||||
|
map_to(0x500000, BusDevice::ROM);
|
||||||
|
map_to(0x580000, BusDevice::Unassigned);
|
||||||
|
map_to(0x600000, BusDevice::SCSI);
|
||||||
|
map_to(0x800000, BusDevice::Unassigned);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool video_is_outputting() {
|
bool video_is_outputting() {
|
||||||
@ -468,7 +446,90 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void update_video() {
|
forceinline void adjust_phase() {
|
||||||
|
++phase_;
|
||||||
|
}
|
||||||
|
|
||||||
|
forceinline void fill_unmapped(const Microcycle &cycle) {
|
||||||
|
if(!(cycle.operation & Microcycle::Read)) return;
|
||||||
|
if(cycle.operation & Microcycle::SelectWord) {
|
||||||
|
cycle.value->full = 0xffff;
|
||||||
|
} else {
|
||||||
|
cycle.value->halves.low = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances all non-CPU components by @c duration half cycles.
|
||||||
|
forceinline void advance_time(HalfCycles duration) {
|
||||||
|
time_since_video_update_ += duration;
|
||||||
|
iwm_.time_since_update += duration;
|
||||||
|
ram_subcycle_ = (ram_subcycle_ + duration.as_int()) & 15;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forceinline void update_video() {
|
||||||
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
||||||
time_until_video_event_ = video_.get_next_sequence_point();
|
time_until_video_event_ = video_.get_next_sequence_point();
|
||||||
}
|
}
|
||||||
@ -627,12 +688,90 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
|
|
||||||
bool ROM_is_overlay_ = true;
|
bool ROM_is_overlay_ = true;
|
||||||
int phase_ = 1;
|
int phase_ = 1;
|
||||||
|
int ram_subcycle_ = 0;
|
||||||
|
|
||||||
DoubleDensityDrive drives_[2];
|
DoubleDensityDrive drives_[2];
|
||||||
Inputs::QuadratureMouse mouse_;
|
Inputs::QuadratureMouse mouse_;
|
||||||
|
|
||||||
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
|
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
|
||||||
|
|
||||||
|
enum class BusDevice {
|
||||||
|
RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Divides the 24-bit address space up into $80000 (i.e. 512kb) segments, recording
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// So: index with the top 5 bits of the 24-bit address.
|
||||||
|
BusDevice memory_map_[32];
|
||||||
|
|
||||||
|
void setup_memory_map() {
|
||||||
|
// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true.
|
||||||
|
|
||||||
|
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||||
|
switch(model) {
|
||||||
|
default: assert(false);
|
||||||
|
|
||||||
|
case Model::Mac128k:
|
||||||
|
case Model::Mac512k:
|
||||||
|
case Model::Mac512ke:
|
||||||
|
populate_memory_map([] (std::function<void(int target, BusDevice device)> map_to) {
|
||||||
|
// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes.
|
||||||
|
for(int c = 0; c <= 0x600000; c += 0x100000) {
|
||||||
|
map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned);
|
||||||
|
}
|
||||||
|
map_to(0x800000, BusDevice::RAM);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Model::MacPlus:
|
||||||
|
populate_memory_map([] (std::function<void(int target, BusDevice device)> map_to) {
|
||||||
|
map_to(0x100000, BusDevice::ROM);
|
||||||
|
map_to(0x400000, BusDevice::Unassigned);
|
||||||
|
map_to(0x500000, BusDevice::ROM);
|
||||||
|
map_to(0x580000, BusDevice::Unassigned);
|
||||||
|
map_to(0x600000, BusDevice::SCSI);
|
||||||
|
map_to(0x800000, BusDevice::RAM);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void populate_memory_map(std::function<void(std::function<void(int, BusDevice)>)> populator) {
|
||||||
|
// Define semantics for below; map_to will write from the current cursor position
|
||||||
|
// to the supplied 24-bit address, setting a particular mapped device.
|
||||||
|
int segment = 0;
|
||||||
|
auto map_to = [&segment, this](int address, BusDevice device) {
|
||||||
|
for(; segment < address >> 19; ++segment) {
|
||||||
|
this->memory_map_[segment] = device;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
populator(map_to);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t ram_mask_ = 0;
|
uint32_t ram_mask_ = 0;
|
||||||
uint32_t rom_mask_ = 0;
|
uint32_t rom_mask_ = 0;
|
||||||
uint16_t rom_[64*1024];
|
uint16_t rom_[64*1024];
|
||||||
|
Loading…
Reference in New Issue
Block a user