mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			219 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //
 | |
| //  SerialPort.cpp
 | |
| //  Clock Signal
 | |
| //
 | |
| //  Created by Thomas Harte on 12/10/2019.
 | |
| //  Copyright © 2019 Thomas Harte. All rights reserved.
 | |
| //
 | |
| 
 | |
| #include "Line.hpp"
 | |
| 
 | |
| #include <cassert>
 | |
| #include <limits>
 | |
| 
 | |
| using namespace Serial;
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::set_writer_clock_rate(const HalfCycles clock_rate) {
 | |
| 	clock_rate_ = clock_rate;
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::advance_writer(HalfCycles cycles) {
 | |
| 	if(cycles == HalfCycles(0)) return;
 | |
| 
 | |
| 	const auto integral_cycles = cycles.as_integral();
 | |
| 	remaining_delays_ = std::max(remaining_delays_ - integral_cycles, Cycles::IntType(0));
 | |
| 	if(events_.empty()) {
 | |
| 		write_cycles_since_delegate_call_ += integral_cycles;
 | |
| 		if(transmission_extra_) {
 | |
| 			transmission_extra_ -= integral_cycles;
 | |
| 			if(transmission_extra_ <= 0) {
 | |
| 				transmission_extra_ = 0;
 | |
| 				if constexpr (!include_clock) {
 | |
| 					update_delegate(level_);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		while(!events_.empty()) {
 | |
| 			if(events_.front().delay <= integral_cycles) {
 | |
| 				cycles -= events_.front().delay;
 | |
| 				write_cycles_since_delegate_call_ += events_.front().delay;
 | |
| 				const auto old_level = level_;
 | |
| 
 | |
| 				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 constexpr (!include_clock) {
 | |
| 					if(old_level != level_) {
 | |
| 						update_delegate(old_level);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// Book enough extra time for the read delegate to be posted
 | |
| 				// the final bit if one is attached.
 | |
| 				if(events_.empty()) {
 | |
| 					transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
 | |
| 				}
 | |
| 			} else {
 | |
| 				events_.front().delay -= integral_cycles;
 | |
| 				write_cycles_since_delegate_call_ += integral_cycles;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::write(const bool level) {
 | |
| 	if(!events_.empty()) {
 | |
| 		events_.emplace_back();
 | |
| 		events_.back().type = level ? Event::SetHigh : Event::SetLow;
 | |
| 	} else {
 | |
| 		level_ = level;
 | |
| 		transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(
 | |
| 	const HalfCycles cycles,
 | |
| 	int count,
 | |
| 	IntT levels
 | |
| ) {
 | |
| 	remaining_delays_ += count * cycles.as_integral();
 | |
| 
 | |
| 	auto event = events_.size();
 | |
| 	events_.resize(events_.size() + size_t(count)*2);
 | |
| 	while(count--) {
 | |
| 		events_[event].type = Event::Delay;
 | |
| 		events_[event].delay = int(cycles.as_integral());
 | |
| 		IntT bit;
 | |
| 		if constexpr (lsb_first) {
 | |
| 			bit = levels & 1;
 | |
| 			levels >>= 1;
 | |
| 		} else {
 | |
| 			static constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
 | |
| 			bit = levels & top_bit;
 | |
| 			levels <<= 1;
 | |
| 		}
 | |
| 
 | |
| 		events_[event+1].type = bit ? Event::SetHigh : Event::SetLow;
 | |
| 		event += 2;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::write(const HalfCycles cycles, const int count, const int levels) {
 | |
| 	write_internal<true, int>(cycles, count, levels);
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| template <bool lsb_first, typename IntT> void Line<include_clock>::write(const HalfCycles cycles, const IntT value) {
 | |
| 	write_internal<lsb_first, IntT>(cycles, 8 * sizeof(IntT), value);
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::reset_writing() {
 | |
| 	remaining_delays_ = 0;
 | |
| 	events_.clear();
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| bool Line<include_clock>::read() const {
 | |
| 	return level_;
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::set_read_delegate(
 | |
| 	ReadDelegate *const delegate,
 | |
| 	[[maybe_unused]] const Storage::Time bit_length
 | |
| ) {
 | |
| 	read_delegate_ = delegate;
 | |
| 	if constexpr (!include_clock) {
 | |
| 		assert(bit_length > Storage::Time(0));
 | |
| 		read_delegate_bit_length_ = bit_length;
 | |
| 		read_delegate_bit_length_.simplify();
 | |
| 		write_cycles_since_delegate_call_ = 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| void Line<include_clock>::update_delegate(const bool level) {
 | |
| 	// Exit early if there's no delegate, or if the delegate is waiting for
 | |
| 	// zero and this isn't zero.
 | |
| 	if(!read_delegate_) 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;
 | |
| 		while(time_left >= time_left_in_bit_) {
 | |
| 			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);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| template <bool include_clock>
 | |
| Cycles::IntType Line<include_clock>::minimum_write_cycles_for_read_delegate_bit() {
 | |
| 	if(!read_delegate_) return 0;
 | |
| 	return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).template get<int>();
 | |
| }
 | |
| 
 | |
| //
 | |
| // Explicitly instantiate the meaningful instances of templates above;
 | |
| // this class uses templates primarily to keep the interface compact and
 | |
| // to take advantage of constexpr functionality selection, not so as
 | |
| // to be generic.
 | |
| //
 | |
| 
 | |
| template class Serial::Line<true>;
 | |
| template class Serial::Line<false>;
 | |
| 
 | |
| template void Line<true>::write<true, uint8_t>(HalfCycles, uint8_t);
 | |
| template void Line<true>::write<false, uint8_t>(HalfCycles, uint8_t);
 | |
| template void Line<true>::write<true, uint16_t>(HalfCycles, uint16_t);
 | |
| template void Line<true>::write<false, uint16_t>(HalfCycles, uint16_t);
 | |
| template void Line<true>::write<true, uint32_t>(HalfCycles, uint32_t);
 | |
| template void Line<true>::write<false, uint32_t>(HalfCycles, uint32_t);
 | |
| template void Line<true>::write<true, uint64_t>(HalfCycles, uint64_t);
 | |
| template void Line<true>::write<false, uint64_t>(HalfCycles, uint64_t);
 | |
| 
 | |
| template void Line<false>::write<true, uint8_t>(HalfCycles, uint8_t);
 | |
| template void Line<false>::write<false, uint8_t>(HalfCycles, uint8_t);
 | |
| template void Line<false>::write<true, uint16_t>(HalfCycles, uint16_t);
 | |
| template void Line<false>::write<false, uint16_t>(HalfCycles, uint16_t);
 | |
| template void Line<false>::write<true, uint32_t>(HalfCycles, uint32_t);
 | |
| template void Line<false>::write<false, uint32_t>(HalfCycles, uint32_t);
 | |
| template void Line<false>::write<true, uint64_t>(HalfCycles, uint64_t);
 | |
| template void Line<false>::write<false, uint64_t>(HalfCycles, uint64_t);
 |