mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-04 14:30:19 +00:00
Attempts mostly to implement 6850 output.
This commit is contained in:
parent
2bd7be13b5
commit
4e5b440145
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user