Livt packages

Livt.IO

Version 1.8.1

Memory, UART, I2C, SPI, and Ethernet input/output components for Livt hardware designs.

Published files (126)

src/uart/UartReceiver.lvt

namespace Livt.IO

/**
 * UartReceiver
 *
 * Receives compile-time-configured UART frames from `rx` and produces a
 * one-cycle `rx_dv` pulse when a valid byte has been decoded. Invalid parity
 * or stop bits reject the frame and pulse `rx_frame_error` instead.
 */
component UartReceiver<DATA_BITS: UartDataBits = UartDataBits.Eight,
	PARITY: UartParity = UartParity.None,
	STOP_BITS: UartStopBits = UartStopBits.One,
	BAUD: Frequency = 115200Hz>
{
	// Compile-time bit timing and the minimum width of the bit-period counter.
	const TICKS_PER_BIT: uint = this.context.TicksPer(BAUD)
	const CLOCK_COUNT_BITS: int = Bits.RequiredFor(TICKS_PER_BIT - 1)

	// Wire levels used to qualify frame boundaries.
	const START_BIT: logic = 0b0
	const STOP_BIT: logic = 0b1

	// Asynchronous input and its two-stage synchronization before sampling.
	rx: logic
	rx_tmp: logic
	rx_data: logic

	// Decoded payload and mutually exclusive valid/error event pulses.
	rx_byte: logic[8]
	rx_dv: logic
	rx_frame_error: logic

	/**
	 * Configures reception at a compile-time baud rate and frame format.
	 */
	new(rx: in logic, rx_dv: out logic, rx_byte: out logic[8], rx_frame_error: out logic)
	{
		this.rx = rx

		rx_dv = this.rx_dv
		rx_byte = this.rx_byte
		rx_frame_error = this.rx_frame_error
	}

	/**
	 * Returns the current receive-data-valid pulse.
	 */
	public fn IsDataValid() logic
	{
		return this.rx_dv
	}

	/**
	 * Returns the most recently received byte.
	 */
	public fn GetData() byte
	{
		return this.rx_byte as byte
	}

	/**
	 * Returns the current framing-error pulse.
	 */
	public fn IsFrameError() logic
	{
		return this.rx_frame_error
	}

	/**
	 * Synchronizes the asynchronous input before the receiver samples it.
	 */
	process DoubleRegisterData()
	{
		state {
			this.rx_tmp = this.rx
			this.rx_data = this.rx_tmp
		}
	}

	/**
	 * Samples the selected frame format and rejects invalid parity or stop bits.
	 */
	process Receiver()
	{
		var clock_count: logic[CLOCK_COUNT_BITS] = (0 as uint) as logic[CLOCK_COUNT_BITS]
		var bit_index: logic[3] = 0b000
		var parity: logic = 0b0

		state Idle {
			clock_count = (0 as uint) as logic[CLOCK_COUNT_BITS]
			bit_index = 0
			parity = 0b0
			this.rx_dv = 0
			this.rx_frame_error = 0

			if (this.rx_data == START_BIT) {
				goto Receiving
			} else {
				goto Idle
			}
		}

		state Receiving {
			if ((clock_count as uint) == TICKS_PER_BIT / 2) {
				if (this.rx_data == 0) {
					clock_count = (0 as uint) as logic[CLOCK_COUNT_BITS]
					this.rx_byte = 0
					goto DataBits
				} else {
					goto Idle
				}
			} else {
				clock_count = (((clock_count as uint) + 1) as int) as logic[CLOCK_COUNT_BITS]
				goto Receiving
			}
		}

		state DataBits {
			if ((clock_count as uint) < TICKS_PER_BIT - 1) {
				clock_count = (((clock_count as uint) + 1) as int) as logic[CLOCK_COUNT_BITS]
				goto DataBits
			} else {
				clock_count = (0 as uint) as logic[CLOCK_COUNT_BITS]
				this.rx_byte[bit_index as uint] = this.rx_data

				static if (PARITY != UartParity.None) {
					parity = parity ^ this.rx_data
				}

				static if (DATA_BITS == UartDataBits.Five) {
					if ((bit_index as uint) < 4) {
						bit_index = ((bit_index as uint) + 1) as logic[3];
						goto DataBits
					} else {
						bit_index = 0;

						static if (PARITY == UartParity.None) {
							goto StopBit
						} else {
							goto ParityBit
						}
					}
				} elif (DATA_BITS == UartDataBits.Six) {
					if ((bit_index as uint) < 5) {
						bit_index = ((bit_index as uint) + 1) as logic[3];
						goto DataBits
					} else {
						bit_index = 0;

						static if (PARITY == UartParity.None) {
							goto StopBit
						} else {
							goto ParityBit
						}
					}
				} elif (DATA_BITS == UartDataBits.Seven) {
					if ((bit_index as uint) < 6) {
						bit_index = ((bit_index as uint) + 1) as logic[3];
						goto DataBits
					} else {
						bit_index = 0;

						static if (PARITY == UartParity.None) {
							goto StopBit
						} else {
							goto ParityBit
						}
					}
				} else {
					if ((bit_index as uint) < 7) {
						bit_index = ((bit_index as uint) + 1) as logic[3];
						goto DataBits
					} else {
						bit_index = 0;

						static if (PARITY == UartParity.None) {
							goto StopBit
						} else {
							goto ParityBit
						}
					}
				}
			}
		}

		static if (PARITY != UartParity.None) {
			state ParityBit {
				if ((clock_count as uint) < TICKS_PER_BIT - 1) {
					clock_count = (((clock_count as uint) + 1) as int) as logic[CLOCK_COUNT_BITS]
					goto ParityBit
				} else {
					clock_count = (0 as uint) as logic[CLOCK_COUNT_BITS]

					static if (PARITY == UartParity.Even) {
						if (this.rx_data == parity) {
							goto StopBit
						} else {
							this.rx_frame_error = 1;
							goto WaitForIdle
						}
					} else {
						if (this.rx_data != parity) {
							goto StopBit
						} else {
							this.rx_frame_error = 1;
							goto WaitForIdle
						}
					}
				}
			}
		}

		state StopBit {
			if ((clock_count as uint) < TICKS_PER_BIT - 1) {
				clock_count = (((clock_count as uint) + 1) as int) as logic[CLOCK_COUNT_BITS]
				goto StopBit
			} else {
				clock_count = (0 as uint) as logic[CLOCK_COUNT_BITS]

				if (this.rx_data == STOP_BIT) {
					static if (STOP_BITS == UartStopBits.Two) {
						goto SecondStopBit
					} else {
						this.rx_dv = 1
						goto Cleanup
					}
				} else {
					this.rx_frame_error = 1
					goto WaitForIdle
				}
			}
		}

		static if (STOP_BITS == UartStopBits.Two) {
			state SecondStopBit {
				if ((clock_count as uint) < TICKS_PER_BIT - 1) {
					clock_count = (((clock_count as uint) + 1) as int) as logic[CLOCK_COUNT_BITS]
					goto SecondStopBit
				} else {
					clock_count = (0 as uint) as logic[CLOCK_COUNT_BITS]

					if (this.rx_data == STOP_BIT) {
						this.rx_dv = 1
						goto Cleanup
					} else {
						this.rx_frame_error = 1
						goto WaitForIdle
					}
				}
			}
		}

		state WaitForIdle {
			// After a framing error, wait for the line to return to idle-high before
			// accepting a new start bit. This prevents immediately re-arming on a low line.
			this.rx_frame_error = 0

			if (this.rx_data == STOP_BIT) {
				goto Idle
			} else {
				goto WaitForIdle
			}
		}

		state Cleanup {
			this.rx_dv = 0
			this.rx_frame_error = 0
			goto Idle
		}
	}
}