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:
parent
ecfe68d70f
commit
941d9a46a2
@ -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>
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user