1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-16 18:30:32 +00:00

Makes a better effort at exposition; better implements clocked line.

This commit is contained in:
Thomas Harte 2021-11-07 05:18:40 -08:00
parent ecfe68d70f
commit 941d9a46a2
2 changed files with 94 additions and 53 deletions

View File

@ -29,7 +29,9 @@ void Line<include_clock>::advance_writer(HalfCycles cycles) {
transmission_extra_ -= integral_cycles;
if(transmission_extra_ <= 0) {
transmission_extra_ = 0;
update_delegate(level_);
if constexpr (!include_clock) {
update_delegate(level_);
}
}
}
} else {
@ -42,12 +44,17 @@ void Line<include_clock>::advance_writer(HalfCycles cycles) {
auto iterator = events_.begin() + 1;
while(iterator != events_.end() && iterator->type != Event::Delay) {
level_ = iterator->type == Event::SetHigh;
if constexpr(include_clock) {
update_delegate(level_);
}
++iterator;
}
events_.erase(events_.begin(), iterator);
if(old_level != level_) {
update_delegate(old_level);
if constexpr (!include_clock) {
if(old_level != level_) {
update_delegate(old_level);
}
}
// Book enough extra time for the read delegate to be posted
@ -89,7 +96,7 @@ template <bool lsb_first, typename IntT> void Line<include_clock>::write_interna
bit = levels & 1;
levels >>= 1;
} else {
constexpr auto top_bit = std::numeric_limits<IntT>::max() - (std::numeric_limits<IntT>::max() >> 1);
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
bit = levels & top_bit;
levels <<= 1;
}
@ -123,9 +130,11 @@ bool Line<include_clock>::read() const {
template <bool include_clock>
void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
read_delegate_ = delegate;
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
if constexpr (include_clock) {
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
}
}
template <bool include_clock>
@ -134,33 +143,37 @@ void Line<include_clock>::update_delegate(bool level) {
// zero and this isn't zero.
if(!read_delegate_) return;
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
if constexpr (!include_clock) {
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
}
time_left_in_bit_ -= time_left;
} else {
read_delegate_->serial_line_did_produce_bit(this, level);
}
time_left_in_bit_ -= time_left;
}
template <bool include_clock>

View File

@ -17,25 +17,42 @@
namespace Serial {
/*!
@c Line connects a single reader and a single writer, allowing timestamped events to be
published and consumed, potentially with a clock conversion in between. It allows line
levels to be written and read in larger collections.
Models one of two connections, either:
It is assumed that the owner of the reader and writer will ensure that the reader will never
get ahead of the writer. If the writer posts events behind the reader they will simply be
given instanteous effect.
(i) a plain single-line serial; or
(ii) a two-line data + clock.
In both cases connects a single reader to a single writer.
When operating as a single-line serial connection:
Provides a mechanism for the writer to enqueue levels arbitrarily far
ahead of the current time, which are played back only as the
write queue advances. Permits the reader and writer to work at
different clock rates, and provides a virtual delegate protocol with
start bit detection.
Can alternatively be used by reader and/or writer only in immediate
mode, getting or setting the current level now, without the actor on
the other end having to have made the same decision.
When operating as a two-line connection:
Implies a clock over enqueued data and provides the reader with
all enqueued bits at appropriate times.
*/
template <bool include_clock> class Line {
public:
void set_writer_clock_rate(HalfCycles clock_rate);
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Sets the line to @c level.
/// Sets the line to @c level instantaneously.
void write(bool level);
/// @returns The instantaneous level of this line.
bool read() const;
/// Sets the denominator for the between levels for any data enqueued
/// via an @c write.
void set_writer_clock_rate(HalfCycles clock_rate);
/// 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. An additional gap of @c cycles
@ -59,25 +76,36 @@ template <bool include_clock> class Line {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
/// @returns The instantaneous level of this line.
bool read() const;
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
};
/*!
Sets a read delegate, which will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
Sets a read delegate.
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Single line serial connection:
The delegate will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Two-line clock + data connection:
The delegate will receive every bit that has been enqueued, spaced as nominated
by the writer. @c bit_length is ignored, as is the return result of
@c ReadDelegate::serial_line_did_produce_bit.
*/
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length = Storage::Time());
private:
struct Event {