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/i2c/I2CRegisterSlave.lvt

namespace Livt.IO

/**
 * Register-file helper built on top of `I2CSlave`.
 *
 * The first byte written after an address match selects the register pointer.
 * Later write bytes store into the selected register and advance the pointer.
 * A read-address request loads the current register value and preloads the next
 * register value so ACKed multi-byte reads can auto-increment.
 */
component I2CRegisterSlave
{
	// Register-file address space.
	public const REGISTER_COUNT: int = 256

	// Owned byte-level target and register storage.
	slave: I2CSlave
	registers: byte[256]

	// Current register selection and first-write-byte interpretation.
	pointer: int
	pointerValid: bool
	expectingPointer: bool

	// Last application-visible register write and its notification.
	writtenRegister: byte
	writtenValue: byte
	hasWrittenRegister: bool

	/**
	 * Owns a byte-level I2C target on the borrowed bus and a 256-byte register bank. Starts
	 * with no valid pointer or write notification; address is masked to seven bits.
	 */
	new(bus: flip I2CBus, address: byte)
	{
		this.slave = new I2CSlave(bus, address)
		this.pointer = 0
		this.pointerValid = false
		this.expectingPointer = false
		this.writtenRegister = 0x00
		this.writtenValue = 0x00
		this.hasWrittenRegister = false
	}

	/**
	 * Writes the byte-addressed register without changing the bus pointer or generating a bus-
	 * write notification. Coordinate application writes with bus activity.
	 */
	public fn SetRegister(address: byte, value: byte)
	{
		var index: int = address as int

		state {
			this.registers[index] = value
		}
	}

	/**
	 * Returns the current byte at address without moving the pointer or clearing events.
	 */
	public fn GetRegister(address: byte) byte
	{
		var index: int = address as int
		return this.registers[index]
	}

	/**
	 * Selects address, marks the pointer valid, and preloads current/next transmit bytes.
	 * Coordinate with the bus service process.
	 */
	public fn SetPointer(address: byte)
	{
		var index: int = address as int

		state {
			this.pointer = index
			this.pointerValid = true
		}

		this.LoadTransmitWindow(index)
	}

	/**
	 * Returns the retained register pointer; HasPointer distinguishes a valid selection.
	 */
	public fn GetPointer() byte
	{
		return this.pointer as byte
	}

	/**
	 * Reports whether a pointer has been selected; this snapshot does not reserve it against
	 * bus activity.
	 */
	public fn HasPointer() bool
	{
		return this.pointerValid
	}

	/**
	 * Reports a retained bus register-write notification. Later writes can replace its
	 * address/value before it is cleared.
	 */
	public fn HasWrittenRegister() bool
	{
		return this.hasWrittenRegister
	}

	/**
	 * Returns the address of the latest notified bus write; valid with HasWrittenRegister.
	 */
	public fn GetWrittenRegister() byte
	{
		return this.writtenRegister
	}

	/**
	 * Returns the value of the latest notified bus write; valid with HasWrittenRegister.
	 */
	public fn GetWrittenValue() byte
	{
		return this.writtenValue
	}

	/**
	 * Clears the write notification without modifying registers or the retained address/value.
	 */
	public fn ClearWrittenRegister()
	{
		state {
			this.hasWrittenRegister = false
		}
	}

	/**
	 * Resets the pointer to zero and invalidates its selection without clearing register
	 * contents.
	 */
	public fn ClearPointer()
	{
		state {
			this.pointer = 0
			this.pointerValid = false
			this.expectingPointer = false
		}
	}

	/**
	 * Returns the byte at the retained pointer, including register zero after ClearPointer;
	 * does not establish pointer validity.
	 */
	public fn GetCurrentRegister() byte
	{
		return this.registers[this.pointer]
	}

	/**
	 * Treats the first byte after write addressing, or an invalid pointer, as the pointer.
	 * Otherwise stores the value, records a write event, and advances with wrap at 256. Used
	 * by the service process; serialize external calls with it.
	 */
	public fn AcceptWriteByte(value: byte)
	{
		if (this.pointerValid == false || this.expectingPointer == true) {
			var index: int = value as int

			state {
				this.pointer = index
				this.pointerValid = true
				this.expectingPointer = false
			}

			this.LoadTransmitWindow(index)
		} else {
			state {
				this.registers[this.pointer] = value
				this.writtenRegister = this.pointer as byte
				this.writtenValue = value
				this.hasWrittenRegister = true
			}

			this.AdvancePointer()
		}
	}

	/**
	 * Loads the current and following register into the target transmit window without
	 * advancing the pointer.
	 */
	public fn PrepareReadByte()
	{
		this.LoadTransmitWindow(this.pointer)
	}

	/**
	 * Advances the pointer and refills the following byte only after controller ACK. NACK
	 * leaves the pointer unchanged; used by the service process.
	 */
	public fn HandleTransmittedByte(acked: bool)
	{
		if (acked) {
			var nextPointer: int = this.NextIndex(this.pointer)
			var followingPointer: int = this.NextIndex(nextPointer)
			var followingValue: byte = this.registers[followingPointer]

			state {
				this.pointer = nextPointer
			}

			this.slave.SetNextTransmitByte(followingValue)
		}
	}

	/**
	 * Preloads current and next register bytes for index in 0..255, wrapping the next index at
	 * the bank end.
	 */
	fn LoadTransmitWindow(index: int)
	{
		var nextIndex: int = this.NextIndex(index)
		var transmitValue: byte = this.registers[index]
		var nextValue: byte = this.registers[nextIndex]
		this.slave.SetTransmitByte(transmitValue)
		this.slave.SetNextTransmitByte(nextValue)
	}

	/**
	 * Returns the following register index with wrap at 256; requires index in 0..255.
	 */
	fn NextIndex(index: int) int
	{
		if (index >= REGISTER_COUNT - 1) {
			return 0
		}

		return index + 1
	}

	/**
	 * Advances the current valid-range pointer with wrap at the register-bank boundary.
	 */
	fn AdvancePointer()
	{
		var nextPointer: int = this.NextIndex(this.pointer)

		state {
			this.pointer = nextPointer
		}
	}

	/**
	 * Consumes address, STOP, receive, and transmit events from the owned target. Maintains
	 * pointer selection and prefetch data; application access must be coordinated with bus
	 * activity.
	 */
	process ServiceSlave()
	{
		if (this.slave.HasAddressMatch()) {
			if (this.slave.WasWriteAddressed()) {
				state {
					this.expectingPointer = true
				}

				this.slave.ClearAddressMatch()
			} elif (this.slave.WasReadAddressed()) {
				this.PrepareReadByte()
				this.slave.ClearReadRequested()
				this.slave.ClearAddressMatch()
			}
		}

		if (this.slave.HasStopDetected()) {
			this.slave.ClearStopDetected()
			this.slave.ClearAddressMatch()
		}

		if (this.slave.HasReceivedByte()) {
			var value: byte = this.slave.GetReceivedByte()
			this.slave.ClearReceivedByte()
			this.AcceptWriteByte(value)
		}

		if (this.slave.HasTransmittedByte()) {
			var acked: bool = this.slave.WasTransmitAcked()
			this.HandleTransmittedByte(acked)
			this.slave.ClearTransmittedByte()

			if (acked == false) {
				this.slave.ClearAddressMatch()
			}
		}
	}
}