diff --git a/Components/8530/z8530.cpp b/Components/8530/z8530.cpp index c902f2055..4d52363e0 100644 --- a/Components/8530/z8530.cpp +++ b/Components/8530/z8530.cpp @@ -8,34 +8,73 @@ #include "z8530.hpp" +#include "../../Outputs/Log.hpp" + using namespace Zilog::SCC; void z8530::reset() { + // TODO. +} + +bool z8530::get_interrupt_line() { + // TODO. + return false; } std::uint8_t z8530::read(int address) { - return channels_[address & 1].read(address & 2); + const auto result = channels_[address & 1].read(address & 2, pointer_); + pointer_ = 0; + return result; } void z8530::write(int address, std::uint8_t value) { - channels_[address & 1].write(address & 2, pointer_, value); + if(address & 2) { + // Write data register for channel. + } else { + // Write control register for channel. - /* - Control register 0, which includes the pointer bits, - is decoded here because there's only one set of pointer bits. - */ - if(!(address & 2)) { + // Most registers are per channel, but a couple are shared; sever + // them here. + switch(pointer_) { + default: + channels_[address & 1].write(address & 2, pointer_, value); + break; + + case 2: // Interrupt vector register; shared between both channels. + interrupt_vector_ = value; + LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value)); + break; + + case 9: // Master interrupt and reset register; also shared between both channels. + LOG("[SCC] TODO: master interrupt and reset register " << PADHEX(2) << int(value)); + break; + } + + // The pointer number resets to 0 after every access, but if it is zero + // then crib at least the next set of pointer bits (which, similarly, are shared + // between the two channels). if(pointer_) { pointer_ = 0; } else { + // The lowest three bits are the lowest three bits of the pointer. pointer_ = value & 7; + + // If the command part of the byte is a 'point high', also set the + // top bit of the pointer. + if(((value >> 3)&7) == 1) { + pointer_ |= 8; + } } } } -uint8_t z8530::Channel::read(bool data) { +uint8_t z8530::Channel::read(bool data, uint8_t pointer) { // If this is a data read, just return it. - if(data) return data_; + if(data) { + return data_; + } else { + LOG("[SCC] Unrecognised control read from register " << int(pointer)); + } // Otherwise, this is a control read... return 0x00; @@ -45,5 +84,100 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { if(data) { data_ = value; return; + } else { + switch(pointer) { + default: + LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer)); + break; + + case 0x0: // Write register 0 — CRC reset and other functions. + // Decode CRC reset instructions. + switch(value >> 6) { + default: /* Do nothing. */ break; + case 1: + LOG("[SCC] TODO: reset Rx CRC checker."); + break; + case 2: + LOG("[SCC] TODO: reset Tx CRC checker."); + break; + case 3: + LOG("[SCC] TODO: reset Tx underrun/EOM latch."); + break; + } + + // Decode command code. + switch((value >> 3)&7) { + default: /* Do nothing. */ break; + case 2: + LOG("[SCC] TODO: reset ext/status interrupts."); + break; + case 3: + LOG("[SCC] TODO: send abort (SDLC)."); + break; + case 4: + LOG("[SCC] TODO: enable interrupt on next Rx character."); + break; + case 5: + LOG("[SCC] TODO: reset Tx interrupt pending."); + break; + case 6: + LOG("[SCC] TODO: reset error."); + break; + case 7: + LOG("[SCC] TODO: reset highest IUS."); + break; + } + break; + + case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition. + transfer_interrupt_mask_ = value; + break; + + case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes. + // Bits 0 and 1 select parity mode. + if(!(value&1)) { + parity_ = Parity::Off; + } else { + parity_ = (value&2) ? Parity::Even : Parity::Odd; + } + + // Bits 2 and 3 select stop bits. + switch((value >> 2)&3) { + default: stop_bits_ = StopBits::Synchronous; break; + case 1: stop_bits_ = StopBits::OneBit; break; + case 2: stop_bits_ = StopBits::OneAndAHalfBits; break; + case 3: stop_bits_ = StopBits::TwoBits; break; + } + + // Bits 4 and 5 pick a sync mode. + switch((value >> 4)&3) { + default: sync_mode_ = Sync::Monosync; break; + case 1: sync_mode_ = Sync::Bisync; break; + case 2: sync_mode_ = Sync::SDLC; break; + case 3: sync_mode_ = Sync::External; break; + } + + // Bits 6 and 7 select a clock rate multiplier, unless synchronous + // mode is enabled (and this is ignored if sync mode is external). + if(stop_bits_ == StopBits::Synchronous) { + clock_rate_multiplier_ = 1; + } else { + switch((value >> 6)&3) { + default: clock_rate_multiplier_ = 1; break; + case 1: clock_rate_multiplier_ = 16; break; + case 2: clock_rate_multiplier_ = 32; break; + case 3: clock_rate_multiplier_ = 64; break; + } + } + break; + + case 0xf: // Write register 15 — External/Status Interrupt Control. + interrupt_mask_ = value; + break; + } } } + +void z8530::set_dcd(int port, bool level) { + // TODO. +} diff --git a/Components/8530/z8530.hpp b/Components/8530/z8530.hpp index bc6ad248b..991782652 100644 --- a/Components/8530/z8530.hpp +++ b/Components/8530/z8530.hpp @@ -19,9 +19,9 @@ namespace SCC { */ class z8530 { public: - void reset(); - /* + **Interface for emulated machine.** + Notes on addressing below: There's no inherent ordering of the two 'address' lines, @@ -30,20 +30,46 @@ class z8530 { A/B = A0 C/D = A1 */ - std::uint8_t read(int address); void write(int address, std::uint8_t value); + void reset(); + bool get_interrupt_line(); + + /* + **Interface for serial port input.** + */ + void set_dcd(int port, bool level); private: class Channel { public: - uint8_t read(bool data); + uint8_t read(bool data, uint8_t pointer); void write(bool data, uint8_t pointer, uint8_t value); private: uint8_t data_ = 0xff; + + enum class Parity { + Even, Odd, Off + } parity_ = Parity::Off; + + enum class StopBits { + Synchronous, OneBit, OneAndAHalfBits, TwoBits + } stop_bits_ = StopBits::Synchronous; + + enum class Sync { + Monosync, Bisync, SDLC, External + } sync_mode_ = Sync::Monosync; + + 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. + + bool dcd_ = false; } channels_[2]; uint8_t pointer_ = 0; + uint8_t interrupt_vector_ = 0; }; } diff --git a/Inputs/QuadratureMouse/QuadratureMouse.hpp b/Inputs/QuadratureMouse/QuadratureMouse.hpp index 462f7ad3d..ab6592bda 100644 --- a/Inputs/QuadratureMouse/QuadratureMouse.hpp +++ b/Inputs/QuadratureMouse/QuadratureMouse.hpp @@ -52,35 +52,33 @@ class QuadratureMouse: public Mouse { */ /*! - Removes a single step from the current accumulated mouse movement; - the step removed will henceforth be queriable via get_step. + Applies a single step from the current accumulated mouse movement, which + might involve the mouse moving right, or left, or not at all. */ void prepare_step() { for(int axis = 0; axis < 2; ++axis) { const int axis_value = axes_[axis]; - if(!axis_value) { - step_[axis] = 0; + if(!axis_value) continue; + + primaries_[axis] ^= 1; + secondaries_[axis] = primaries_[axis]; + if(axis_value > 0) { + -- axes_[axis]; } else { - if(axis_value > 0) { - -- axes_[axis]; - step_[axis] = 1; - } else { - ++ axes_[axis]; - step_[axis] = -1; - } + ++ axes_[axis]; + secondaries_[axis] ^= 1; } } } /*! - Gets and removes a single step from the current accumulated mouse - movement for @c axis — axis 0 is x, axis 1 is y. - - @returns 0 if no movement is outstanding, -1 if there is outstanding - negative movement, +1 if there is outstanding positive movement. + @returns the two quadrature channels — bit 0 is the 'primary' channel + (i.e. the one that can be monitored to observe velocity) and + bit 1 is the 'secondary' (i.e. that which can be queried to + observe direction). */ - int get_step(int axis) { - return step_[axis]; + int get_channel(int axis) { + return primaries_[axis] | (secondaries_[axis] << 1); } /*! @@ -94,8 +92,9 @@ class QuadratureMouse: public Mouse { int number_of_buttons_ = 0; std::atomic button_flags_; std::atomic axes_[2]; - int step_[2]; + int primaries_[2] = {0, 0}; + int secondaries_[2] = {0, 0}; }; } diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index eb03d1d8a..0d0627c32 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -142,14 +142,24 @@ template class ConcreteMachin // The keyboard also has a clock, albeit a very slow one. // Its clock and data lines are connected to the VIA. keyboard_clock_ += cycle.length; - auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000)); + 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()); } - // TODO: SCC is a divide-by-two. + // Feed mouse inputs within at most 100 cycles of each other. + time_since_mouse_update_ += cycle.length; + const auto mouse_ticks = keyboard_clock_.divide(HalfCycles(200)); + 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); + } + + // 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_ += cycle.length; @@ -166,7 +176,11 @@ template 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) ); + 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) { @@ -432,8 +446,8 @@ template class ConcreteMachin case Port::B: return (mouse_.get_button_mask() & 1) ? 0x00 : 0x08 | - (mouse_.get_step(1) > 0) ? 0x00 : 0x10 | - (mouse_.get_step(0) > 0) ? 0x00 : 0x20 | + ((mouse_.get_channel(1) & 2) << 3) | + ((mouse_.get_channel(0) & 2) << 4) | (clock_.get_data() ? 0x02 : 0x00) | (video_.is_outputting() ? 0x00 : 0x40); } @@ -492,6 +506,7 @@ template class ConcreteMachin HalfCycles video_clock_; // HalfCycles time_since_video_update_; HalfCycles time_since_iwm_update_; + HalfCycles time_since_mouse_update_; bool ROM_is_overlay_ = true; int phase_ = 1;