1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-04 18:29:40 +00:00

Attempts mostly to implement 6850 output.

This commit is contained in:
Thomas Harte 2019-10-12 23:14:29 -04:00
parent 4bf81d3b90
commit 8b50a7d6e3
5 changed files with 197 additions and 48 deletions

View File

@ -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;
}
}

View File

@ -11,12 +11,15 @@
#include <cstdint>
#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);
};
}

View File

@ -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_;
}

View File

@ -9,19 +9,9 @@
#ifndef SerialPort_hpp
#define SerialPort_hpp
namespace Serial {
#include <vector>
/// 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<Event> events_;
int remaining_delays_ = 0;
bool level_ = false;
};
/*!

View File

@ -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);