mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-25 16:31:42 +00:00
By hook or by crook, mouse input now works.
This commit is contained in:
parent
ccd2cb44a2
commit
d6150645c0
@ -17,14 +17,57 @@ void z8530::reset() {
|
||||
}
|
||||
|
||||
bool z8530::get_interrupt_line() {
|
||||
// TODO.
|
||||
return false;
|
||||
return
|
||||
(master_interrupt_control_ & 0x8) &&
|
||||
(
|
||||
channels_[0].get_interrupt_line() ||
|
||||
channels_[1].get_interrupt_line()
|
||||
);
|
||||
}
|
||||
|
||||
std::uint8_t z8530::read(int address) {
|
||||
const auto result = channels_[address & 1].read(address & 2, pointer_);
|
||||
pointer_ = 0;
|
||||
return result;
|
||||
if(address & 2) {
|
||||
// Read data register for channel
|
||||
return 0x00;
|
||||
} else {
|
||||
// Read control register for channel.
|
||||
uint8_t result = 0;
|
||||
|
||||
switch(pointer_) {
|
||||
default:
|
||||
result = channels_[address & 1].read(address & 2, pointer_);
|
||||
break;
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
LOG("[SCC] Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
// Modify the vector if permitted.
|
||||
// if(master_interrupt_control_ & 1) {
|
||||
for(int port = 0; port < 2; ++port) {
|
||||
// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix.
|
||||
if(channels_[port].get_interrupt_line()) {
|
||||
const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4);
|
||||
const uint8_t mask = uint8_t(~(7 << shift));
|
||||
result = uint8_t(
|
||||
(result & mask) |
|
||||
((1 | ((port == 1) ? 4 : 0)) << shift)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pointer_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void z8530::write(int address, std::uint8_t value) {
|
||||
@ -47,6 +90,7 @@ void z8530::write(int address, std::uint8_t value) {
|
||||
|
||||
case 9: // Master interrupt and reset register; also shared between both channels.
|
||||
LOG("[SCC] TODO: master interrupt and reset register " << PADHEX(2) << int(value));
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -68,15 +112,31 @@ void z8530::write(int address, std::uint8_t value) {
|
||||
}
|
||||
}
|
||||
|
||||
void z8530::set_dcd(int port, bool level) {
|
||||
channels_[port].set_dcd(level);
|
||||
}
|
||||
|
||||
// MARK: - Channel implementations
|
||||
|
||||
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
|
||||
// If this is a data read, just return it.
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
LOG("[SCC] Unrecognised control read from register " << int(pointer));
|
||||
return 0x00;
|
||||
|
||||
case 0:
|
||||
return dcd_ ? 0x8 : 0x0;
|
||||
|
||||
case 0xf:
|
||||
return external_interrupt_status_;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, this is a control read...
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
@ -109,7 +169,9 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
LOG("[SCC] TODO: reset ext/status interrupts.");
|
||||
// LOG("[SCC] reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
LOG("[SCC] TODO: send abort (SDLC).");
|
||||
@ -130,7 +192,7 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
break;
|
||||
|
||||
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
|
||||
transfer_interrupt_mask_ = value;
|
||||
interrupt_mask_ = value;
|
||||
break;
|
||||
|
||||
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
|
||||
@ -172,12 +234,24 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
|
||||
break;
|
||||
|
||||
case 0xf: // Write register 15 — External/Status Interrupt Control.
|
||||
interrupt_mask_ = value;
|
||||
external_interrupt_mask_ = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void z8530::set_dcd(int port, bool level) {
|
||||
// TODO.
|
||||
void z8530::Channel::set_dcd(bool level) {
|
||||
if(dcd_ == level) return;
|
||||
dcd_ = level;
|
||||
|
||||
if(external_interrupt_mask_ & 0x8) {
|
||||
external_status_interrupt_ = true;
|
||||
external_interrupt_status_ |= 0x8;
|
||||
}
|
||||
}
|
||||
|
||||
bool z8530::Channel::get_interrupt_line() {
|
||||
return
|
||||
(interrupt_mask_ & 1) && external_status_interrupt_;
|
||||
// TODO: other potential causes of an interrupt.
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ class z8530 {
|
||||
public:
|
||||
uint8_t read(bool data, uint8_t pointer);
|
||||
void write(bool data, uint8_t pointer, uint8_t value);
|
||||
void set_dcd(bool level);
|
||||
bool get_interrupt_line();
|
||||
|
||||
private:
|
||||
uint8_t data_ = 0xff;
|
||||
@ -63,13 +65,20 @@ class z8530 {
|
||||
|
||||
int clock_rate_multiplier_ = 1;
|
||||
|
||||
uint8_t transfer_interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
|
||||
|
||||
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
|
||||
bool external_status_interrupt_ = false;
|
||||
uint8_t external_interrupt_status_ = 0;
|
||||
|
||||
bool dcd_ = false;
|
||||
} channels_[2];
|
||||
|
||||
uint8_t pointer_ = 0;
|
||||
|
||||
uint8_t interrupt_vector_ = 0;
|
||||
|
||||
uint8_t master_interrupt_control_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Apple;
|
||||
|
||||
@ -50,7 +50,7 @@ uint8_t IWM::read(int address) {
|
||||
|
||||
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
||||
default:
|
||||
printf("Invalid read\n");
|
||||
LOG("[IWM] Invalid read\n");
|
||||
return 0xff;
|
||||
|
||||
// "Read all 1s".
|
||||
@ -62,10 +62,9 @@ uint8_t IWM::read(int address) {
|
||||
const auto result = data_register_;
|
||||
|
||||
if(data_register_ & 0x80) {
|
||||
printf("[%02x] ", data_register_);
|
||||
data_register_ = 0;
|
||||
}
|
||||
printf("Reading data register\n");
|
||||
LOG("Reading data register: " << PADHEX(2) << int(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -82,13 +81,13 @@ uint8_t IWM::read(int address) {
|
||||
(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
|
||||
If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
|
||||
*/
|
||||
printf("Reading status (including [%d][%c%c%c%c] ", active_drive_, (state_ & CA2) ? '2' : '-', (state_ & CA1) ? '1' : '-', (state_ & CA0) ? '0' : '-', (state_ & SEL) ? 'S' : '-');
|
||||
LOG("Reading status (including [" << active_drive_ << "][" << ((state_ & CA2) ? '2' : '-') << ((state_ & CA1) ? '1' : '-') << ((state_ & CA0) ? '0' : '-') << ((state_ & SEL) ? 'S' : '-') << "]");
|
||||
|
||||
// Determine the SENSE input.
|
||||
uint8_t sense = 0x00;
|
||||
switch(state_ & (CA2 | CA1 | CA0 | SEL)) {
|
||||
default:
|
||||
printf("unknown)\n");
|
||||
LOG("unknown)");
|
||||
break;
|
||||
|
||||
// 4 = step finished (0 = done)
|
||||
@ -102,7 +101,7 @@ uint8_t IWM::read(int address) {
|
||||
// break;
|
||||
|
||||
case SEL: // Disk in place.
|
||||
printf("disk in place)\n");
|
||||
LOG("disk in place)");
|
||||
sense = drives_[active_drive_] && drives_[active_drive_]->has_disk() ? 0x00 : 0x80;
|
||||
break;
|
||||
|
||||
@ -111,24 +110,24 @@ uint8_t IWM::read(int address) {
|
||||
// break;
|
||||
//
|
||||
case CA0|SEL: // Disk locked (i.e. write-protect tab).
|
||||
printf("disk locked)\n");
|
||||
LOG("disk locked)");
|
||||
// sense = drives_[active_drive_] && drives_[active_drive_]->get_is_read_only() ? 0x00 : 0x80;
|
||||
sense = drives_[active_drive_] && drives_[active_drive_]->get_is_read_only() ? 0x80 : 0x00;
|
||||
break;
|
||||
|
||||
case CA1: // Disk motor running.
|
||||
printf("disk motor running)\n");
|
||||
LOG("disk motor running)");
|
||||
sense = drives_[active_drive_] && drives_[active_drive_]->get_motor_on() ? 0x00 : 0x80;
|
||||
break;
|
||||
|
||||
case CA1|SEL: // Head at track 0.
|
||||
printf("head at track 0)\n");
|
||||
LOG("head at track 0)");
|
||||
sense = drives_[active_drive_] && drives_[active_drive_]->get_is_track_zero() ? 0x00 : 0x80;
|
||||
break;
|
||||
|
||||
case CA1|CA0|SEL: // Tachometer (?)
|
||||
sense = drives_[active_drive_] && drives_[active_drive_]->get_tachometer() ? 0x00 : 0x80;
|
||||
printf("tachometer [%02x])\n", sense);
|
||||
LOG("tachometer " << PADHEX(2) << int(sense) << ")");
|
||||
break;
|
||||
|
||||
// case CA2: // Read data, lower head.
|
||||
@ -140,13 +139,13 @@ uint8_t IWM::read(int address) {
|
||||
// break;
|
||||
//
|
||||
case CA2|CA1: // Single- or double-sided drive.
|
||||
printf("single- or double-sided drive)\n");
|
||||
LOG("single- or double-sided drive)");
|
||||
sense = drives_[active_drive_] && (drives_[active_drive_]->get_head_count() == 1) ? 0x00 : 0x80;
|
||||
break;
|
||||
|
||||
case CA2|CA1|CA0: // Drive installed. (per the Mac Plus ROM)
|
||||
case CA2|CA1|CA0|SEL: // Drive installed. (per Inside Macintosh)
|
||||
printf("drive installed)\n");
|
||||
LOG("drive installed)");
|
||||
sense = drives_[active_drive_] ? 0x00 : 0x80;
|
||||
break;
|
||||
}
|
||||
@ -165,7 +164,7 @@ uint8_t IWM::read(int address) {
|
||||
bit 6: 1 = write state (cleared to 0 if a write underrun occurs).
|
||||
bit 7: 1 = write data buffer ready for data.
|
||||
*/
|
||||
printf("Reading write handshake\n");
|
||||
LOG("Reading write handshake");
|
||||
return 0x1f | 0x80 | 0x40;
|
||||
}
|
||||
|
||||
@ -200,11 +199,11 @@ void IWM::write(int address, uint8_t input) {
|
||||
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
|
||||
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
|
||||
}
|
||||
printf("IWM mode is now %02x\n", mode_);
|
||||
LOG("IWM mode is now " << PADHEX(2) << int(mode_));
|
||||
break;
|
||||
|
||||
case Q7|Q6|ENABLE: // Write data register.
|
||||
printf("Data register write\n");
|
||||
LOG("Data register write\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -237,23 +236,23 @@ void IWM::access(int address) {
|
||||
if(!(address & 1)) {
|
||||
switch(state_ & (CA1 | CA0 | SEL)) {
|
||||
default:
|
||||
printf("Unhandled LSTRB\n");
|
||||
LOG("Unhandled LSTRB");
|
||||
break;
|
||||
|
||||
case 0:
|
||||
printf("LSTRB Set stepping direction: %d\n", state_ & CA2);
|
||||
LOG("LSTRB Set stepping direction: " << int(state_ & CA2));
|
||||
break;
|
||||
|
||||
case CA0:
|
||||
printf("LSTRB Step\n");
|
||||
LOG("LSTRB Step");
|
||||
break;
|
||||
|
||||
case CA1:
|
||||
printf("LSTRB Motor on\n");
|
||||
LOG("LSTRB Motor on");
|
||||
break;
|
||||
|
||||
case CA1|CA0:
|
||||
printf("LSTRB Eject disk\n");
|
||||
LOG("LSTRB Eject disk");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -64,9 +64,9 @@ class QuadratureMouse: public Mouse {
|
||||
secondaries_[axis] = primaries_[axis];
|
||||
if(axis_value > 0) {
|
||||
-- axes_[axis];
|
||||
secondaries_[axis] ^= 1;
|
||||
} else {
|
||||
++ axes_[axis];
|
||||
secondaries_[axis] ^= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
via_clock_ += cycle.length;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
|
||||
// The keyboard also has a clock, albeit a very slow one.
|
||||
// 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));
|
||||
@ -149,13 +149,13 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
||||
}
|
||||
|
||||
// Feed mouse inputs within at most 100 cycles of each other.
|
||||
// Feed mouse inputs within at most 1250 cycles of each other.
|
||||
time_since_mouse_update_ += cycle.length;
|
||||
const auto mouse_ticks = keyboard_clock_.divide(HalfCycles(200));
|
||||
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(0) & 1);
|
||||
scc_.set_dcd(1, mouse_.get_channel(1) & 1);
|
||||
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
|
||||
@ -176,11 +176,18 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
|
||||
// Update interrupt input. TODO: move this into a VIA/etc delegate callback?
|
||||
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. */
|
||||
);
|
||||
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. */
|
||||
// );
|
||||
|
||||
// A null cycle leaves nothing else to do.
|
||||
if(cycle.operation) {
|
||||
@ -444,12 +451,13 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
return 0x00; // TODO: b7 = SCC wait/request
|
||||
|
||||
case Port::B:
|
||||
return
|
||||
return uint8_t(
|
||||
(mouse_.get_button_mask() & 1) ? 0x00 : 0x08 |
|
||||
((mouse_.get_channel(1) & 2) << 3) |
|
||||
((mouse_.get_channel(0) & 2) << 4) |
|
||||
((mouse_.get_channel(0) & 2) << 3) |
|
||||
((mouse_.get_channel(1) & 2) << 4) |
|
||||
(clock_.get_data() ? 0x02 : 0x00) |
|
||||
(video_.is_outputting() ? 0x00 : 0x40);
|
||||
(video_.is_outputting() ? 0x00 : 0x40)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@ -447,7 +447,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
auto mouse_machine = _machine->mouse_machine();
|
||||
if(mouse_machine) {
|
||||
@synchronized(self) {
|
||||
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
|
||||
mouse_machine->get_mouse().move(int(deltaX), -int(deltaY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
#ifdef LOG_TRACE
|
||||
// should_log = true;
|
||||
if(should_log) {
|
||||
printf("\nInterrupt\n");
|
||||
printf("\n\nInterrupt\n\n");
|
||||
}
|
||||
#endif
|
||||
active_program_ = nullptr;
|
||||
@ -287,8 +287,9 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
}
|
||||
|
||||
#ifdef LOG_TRACE
|
||||
should_log |= (program_counter_.full - 4) == 0x401A84;
|
||||
//4176b6
|
||||
should_log |= (program_counter_.full - 4) == 0x418A0A;//0x41806A;//180A2;
|
||||
// should_log |= (program_counter_.full - 4) == 0x418A0A;//0x41806A;//180A2;
|
||||
// should_log = ((program_counter_.full - 4) >= 0x417D9E) && ((program_counter_.full - 4) <= 0x419D96);
|
||||
#endif
|
||||
|
||||
@ -1885,7 +1886,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
// on the data bus as the accepted interrupt number during the interrupt
|
||||
// acknowledge cycle, with the low bit set since a real 68000 uses the lower
|
||||
// data strobe to collect the corresponding vector byte.
|
||||
accepted_interrupt_level_ = interrupt_level_ = bus_interrupt_level_;
|
||||
accepted_interrupt_level_ = interrupt_level_ = bus_interrupt_level_;
|
||||
effective_address_[0].full = 1 | uint32_t(accepted_interrupt_level_ << 1);
|
||||
|
||||
// Recede the program counter to where it would have been were there no
|
||||
|
Loading…
Reference in New Issue
Block a user