diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index 5e2571a5c..f86989c5d 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -13,22 +13,39 @@ using namespace Motorola::ACIA; +ACIA::ACIA() {} + uint8_t ACIA::read(int address) { if(address&1) { LOG("Read from receive register"); } else { LOG("Read status"); - return status_; + return + ((next_transmission_ == NoTransmission) ? 0x02 : 0x00) | + (data_carrier_detect.read() ? 0x04 : 0x00) | + (clear_to_send.read() ? 0x08 : 0x00) + ; + + // b0: receive data full. + // b1: transmit data empty. + // b2: DCD. + // b3: CTS. + // b4: framing error (i.e. no first stop bit where expected). + // b5: receiver overran. + // b6: parity error. + // b7: IRQ state. } return 0x00; } void ACIA::write(int address, uint8_t value) { if(address&1) { - LOG("Write to transmit register"); + next_transmission_ = value; + consider_transmission(); } else { if((value&3) == 3) { - LOG("Reset"); + transmit.reset_writing(); + request_to_send.reset_writing(); } else { switch(value & 3) { default: @@ -38,30 +55,77 @@ void ACIA::write(int address, uint8_t value) { } switch((value >> 2) & 7) { default: - case 0: word_size_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break; - case 1: word_size_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break; - case 2: word_size_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break; - case 3: word_size_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break; - case 4: word_size_ = 8; stop_bits_ = 2; parity_ = Parity::None; break; - case 5: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::None; break; - case 6: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break; - case 7: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break; + case 0: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break; + case 1: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break; + case 2: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break; + case 3: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break; + case 4: data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None; break; + case 5: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None; break; + case 6: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break; + case 7: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break; } switch((value >> 5) & 3) { - case 0: set_ready_to_transmit(false); transmit_interrupt_enabled_ = false; break; - case 1: set_ready_to_transmit(false); transmit_interrupt_enabled_ = true; break; - case 2: set_ready_to_transmit(true); transmit_interrupt_enabled_ = false; break; - case 3: set_ready_to_transmit(false); transmit_interrupt_enabled_ = false; break; /* TODO: transmit a break level on the transmit output. */ + case 0: request_to_send.write(false); transmit_interrupt_enabled_ = false; break; + case 1: request_to_send.write(false); transmit_interrupt_enabled_ = true; break; + case 2: request_to_send.write(true); transmit_interrupt_enabled_ = false; break; + case 3: + request_to_send.write(false); + transmit_interrupt_enabled_ = false; + transmit.reset_writing(); + transmit.write(false); + break; } receive_interrupt_enabled_ = value & 0x80; - LOG("Write to control register"); } } } -void ACIA::run_for(HalfCycles) { +void ACIA::run_for(HalfCycles length) { + // Transmission. + int transmit_advance = length.as_int(); + if(next_transmission_ != NoTransmission) { + const auto write_data_time_remaining = transmit.write_data_time_remaining(); + if(transmit_advance > write_data_time_remaining) { + transmit.flush_writing(); + transmit_advance -= write_data_time_remaining; + consider_transmission(); + } + } + transmit.advance_writer(transmit_advance); + + // Reception. } -void ACIA::set_ready_to_transmit(bool) { +void ACIA::consider_transmission() { + if(next_transmission_ != NoTransmission && !transmit.write_data_time_remaining()) { + // Establish start bit and [7 or 8] data bits. + if(data_bits_ == 7) next_transmission_ &= 0x7f; + int transmission = next_transmission_ << 1; + // Add a parity bit, if any. + int mask = 0x2 << data_bits_; + if(parity_ != Parity::None) { + next_transmission_ ^= next_transmission_ >> 4; + next_transmission_ ^= next_transmission_ >> 2; + next_transmission_ ^= next_transmission_ >> 1; + + if((next_transmission_&1) != (parity_ == Parity::Even)) { + transmission |= mask; + } + mask <<= 1; + } + + // Add stop bits. + for(int c = 0; c < stop_bits_; ++c) { + transmission |= mask; + mask <<= 1; + } + + // Output all that. + const int total_bits = 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None); + transmit.write(divider_, total_bits, transmission); + + // Mark the transmit register as empty again. + next_transmission_ = NoTransmission; + } } diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp index df8eafa0e..bb7aec9c2 100644 --- a/Components/6850/6850.hpp +++ b/Components/6850/6850.hpp @@ -11,12 +11,15 @@ #include #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../SerialPort/SerialPort.hpp" namespace Motorola { namespace ACIA { class ACIA { public: + ACIA(); + /*! Reads from the ACIA. @@ -37,17 +40,28 @@ class ACIA { void run_for(HalfCycles); + // Input lines. + Serial::Line receive; + Serial::Line clear_to_send; + Serial::Line data_carrier_detect; + + // Output lines. + Serial::Line transmit; + Serial::Line request_to_send; + private: int divider_ = 1; - uint8_t status_ = 0x00; enum class Parity { Even, Odd, None } parity_ = Parity::None; - int word_size_ = 7, stop_bits_ = 2; + int data_bits_ = 7, stop_bits_ = 2; + + static const int NoTransmission = 0x100; + int next_transmission_ = NoTransmission; + void consider_transmission(); + bool receive_interrupt_enabled_ = false; bool transmit_interrupt_enabled_ = false; - - void set_ready_to_transmit(bool); }; } diff --git a/Components/SerialPort/SerialPort.cpp b/Components/SerialPort/SerialPort.cpp index 4927e60ce..1b3c388fe 100644 --- a/Components/SerialPort/SerialPort.cpp +++ b/Components/SerialPort/SerialPort.cpp @@ -7,3 +7,68 @@ // #include "SerialPort.hpp" + +using namespace Serial; + +void Line::advance_writer(int cycles) { + remaining_delays_ = std::max(remaining_delays_ - cycles, 0); + while(!events_.empty()) { + if(events_.front().delay < cycles) { + cycles -= events_.front().delay; + auto iterator = events_.begin() + 1; + while(iterator != events_.end() && iterator->type != Event::Delay) { + level_ = iterator->type == Event::SetHigh; + ++iterator; + } + events_.erase(events_.begin(), iterator); + } else { + events_.front().delay -= cycles; + break; + } + } +} + +void Line::write(bool level) { + if(!events_.empty()) { + events_.emplace_back(); + events_.back().type = level ? Event::SetHigh : Event::SetLow; + } else { + level_ = level; + } +} + +void Line::write(int cycles, int count, int levels) { + remaining_delays_ += count*cycles; + + auto event = events_.size(); + events_.resize(events_.size() + size_t(count)*2); + while(count--) { + events_[event].type = Event::Delay; + events_[event].delay = cycles; + events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow; + event += 2; + } +} + +int Line::write_data_time_remaining() { + return remaining_delays_; +} + +void Line::reset_writing() { + events_.clear(); +} + +void Line::flush_writing() { + for(const auto &event : events_) { + switch(event.type) { + default: break; + case Event::SetHigh: level_ = true; break; + case Event::SetLow: level_ = false; break; + } + } + events_.clear(); +} + +bool Line::read() { + return level_; +} diff --git a/Components/SerialPort/SerialPort.hpp b/Components/SerialPort/SerialPort.hpp index e78061c25..055eba32d 100644 --- a/Components/SerialPort/SerialPort.hpp +++ b/Components/SerialPort/SerialPort.hpp @@ -9,19 +9,9 @@ #ifndef SerialPort_hpp #define SerialPort_hpp -namespace Serial { +#include -/// Signal is an amalgamation of the RS-232-esque signals and those available on the Macintosh -/// and therefore often associated with RS-422. -enum class Signal { - Receive, - Transmit, - ClearToSend, - RequestToSend, - DataCarrierDetect, - OutputHandshake, - InputHandshake -}; +namespace Serial { /*! @c Line connects a single reader and a single writer, allowing timestamped events to be @@ -34,29 +24,43 @@ enum class Signal { */ class Line { public: - void connect_reader(int clock_rate); - void disconnect_reader(); + /// Advances the read position by @c cycles relative to the writer's + /// clock rate. + void advance_writer(int cycles); - void connect_writer(int clock_rate); - void disconnect_writer(); + /// Sets the line to @c level. + void write(bool level); - /// Sets the line to @c level after @c cycles relative to the writer's - /// clock rate have elapsed from the final event currently posted. - void write(int cycles, bool level); - - /// Enqueues @c count level changes, the first occurring @c cycles + /// Enqueues @c count level changes, the first occurring immediately /// after the final event currently posted and each subsequent event - /// occurring @c cycles after the previous. The levels to output are + /// occurring @c cycles after the previous. An additional gap of @c cycles + /// is scheduled after the final output. The levels to output are /// taken from @c levels which is read from lsb to msb. @c cycles is /// relative to the writer's clock rate. void write(int cycles, int count, int levels); - /// Advances the read position by @c cycles relative to the reader's - /// clock rate. - void advance_reader(int cycles); + /// @returns the number of cycles until currently enqueued write data is exhausted. + int write_data_time_remaining(); - /// @returns The instantaneous level of this line at the current read position. + /// Eliminates all future write states, leaving the output at whatever it is now. + void reset_writing(); + + /// Applies all pending write changes instantly. + void flush_writing(); + + /// @returns The instantaneous level of this line. bool read(); + + private: + struct Event { + enum Type { + Delay, SetHigh, SetLow + } type; + int delay; + }; + std::vector events_; + int remaining_delays_ = 0; + bool level_ = false; }; /*! diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index e550d4ac4..1a4d829a1 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -39,6 +39,8 @@ class ConcreteMachine: public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : mc68000_(*this), + keyboard_acia_(), + midi_acia_(), ay_(audio_queue_), speaker_(ay_) { set_clock_rate(CLOCK_RATE);