mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 20:16:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			144 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //
 | |
| //  SerialPort.hpp
 | |
| //  Clock Signal
 | |
| //
 | |
| //  Created by Thomas Harte on 12/10/2019.
 | |
| //  Copyright © 2019 Thomas Harte. All rights reserved.
 | |
| //
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <vector>
 | |
| #include "Storage/Storage.hpp"
 | |
| #include "ClockReceiver/ClockReceiver.hpp"
 | |
| #include "ClockReceiver/ForceInline.hpp"
 | |
| 
 | |
| namespace Serial {
 | |
| 
 | |
| /*!
 | |
| 	Models one of two connections, either:
 | |
| 
 | |
| 		(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:
 | |
| 	/// Sets the line to @c level instantaneously.
 | |
| 	void write(bool);
 | |
| 
 | |
| 	/// @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);
 | |
| 
 | |
| 	/// 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
 | |
| 	/// 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(HalfCycles cycles, int count, int levels);
 | |
| 
 | |
| 	/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
 | |
| 	/// either in LSB or MSB order as per the @c lsb_first template flag.
 | |
| 	template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
 | |
| 
 | |
| 	/// @returns the number of cycles until currently enqueued write data is exhausted.
 | |
| 	forceinline HalfCycles write_data_time_remaining() const {
 | |
| 		return HalfCycles(remaining_delays_);
 | |
| 	}
 | |
| 
 | |
| 	/// @returns the number of cycles left until it is guaranteed that a passive reader
 | |
| 	/// has received all currently-enqueued bits.
 | |
| 	forceinline HalfCycles transmission_data_time_remaining() const {
 | |
| 		return HalfCycles(remaining_delays_ + transmission_extra_);
 | |
| 	}
 | |
| 
 | |
| 	/// Advances the read position relative to the writer's clock rate.
 | |
| 	void advance_writer(HalfCycles);
 | |
| 
 | |
| 	/// Eliminates all future write states, leaving the output at whatever it is now.
 | |
| 	void reset_writing();
 | |
| 
 | |
| 	struct ReadDelegate {
 | |
| 		virtual bool serial_line_did_produce_bit(Line *, int bit) = 0;
 | |
| 	};
 | |
| 	/*!
 | |
| 		Sets a read delegate.
 | |
| 
 | |
| 		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 *, Storage::Time bit_length = Storage::Time());
 | |
| 
 | |
| private:
 | |
| 	struct Event {
 | |
| 		enum Type {
 | |
| 			Delay, SetHigh, SetLow
 | |
| 		} type;
 | |
| 		int delay;
 | |
| 	};
 | |
| 	std::vector<Event> events_;
 | |
| 	HalfCycles::IntType remaining_delays_ = 0;
 | |
| 	HalfCycles::IntType transmission_extra_ = 0;
 | |
| 	bool level_ = true;
 | |
| 	HalfCycles clock_rate_ = 0;
 | |
| 
 | |
| 	ReadDelegate *read_delegate_ = nullptr;
 | |
| 	Storage::Time read_delegate_bit_length_, time_left_in_bit_;
 | |
| 	int write_cycles_since_delegate_call_ = 0;
 | |
| 	enum class ReadDelegatePhase {
 | |
| 		WaitingForZero,
 | |
| 		Serialising
 | |
| 	} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
 | |
| 
 | |
| 	void update_delegate(bool level);
 | |
| 	HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
 | |
| 
 | |
| 	template <bool lsb_first, typename IntT> void
 | |
| 		write_internal(HalfCycles, int, IntT);
 | |
| };
 | |
| 
 | |
| /*!
 | |
| 	Defines an RS-232-esque srial port.
 | |
| */
 | |
| class Port {
 | |
| 	public:
 | |
| };
 | |
| 
 | |
| }
 |