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/I2CSlave.lvt

namespace Livt.IO

/**
 * Byte-event I2C target peripheral for one 7-bit address.
 *
 * `SetTransmitByte` provides the current read byte. `SetNextTransmitByte`
 * queues the next byte to become current immediately after a master ACK, which
 * lets wrappers implement multi-byte reads without racing the next SCL edge.
 */
component I2CSlave
{
	// Borrowed bus attachment and the selected seven-bit target address.
	bus: I2CBus
	address: byte

	// Edge history and incoming address/data-bit assembly.
	prevScl: logic
	prevSda: logic
	bitCount: int
	shiftReg: byte

	// Last received byte and its retained notification.
	receivedByte: byte
	byteReceived: bool

	// Current and prefetched read payloads and the refill request.
	transmitByte: byte
	nextTransmitByte: byte
	hasNextTransmitByte: bool
	readRequested: bool

	// Address selection, transfer direction, and retained address/stop events.
	addressMatched: bool
	isRead: bool
	addressMatchEvent: bool
	readAddressed: bool
	writeAddressed: bool
	stopDetected: bool

	// Transmit completion, ACK phase tracking, and SDA drive state.
	transmittedByte: bool
	transmitAcked: bool
	ackHighSeen: bool
	ackLowSeen: bool
	readSdaDriveLow: logic

	// Startup and active-transaction tracking.
	initialized: bool
	busActive: bool

	/**
	 * Borrows the bus, masks address to seven bits, releases the lines, and clears event
	 * state. Transmit data must be supplied before the controller reads it.
	 */
	new(bus: flip I2CBus, address: byte)
	{
		this.bus = bus
		this.address = (address as int & 0x7F) as byte
		this.bus.scl_drive_low = 0b0
		this.bus.sda_drive_low = 0b0
		this.prevScl = 0b1
		this.prevSda = 0b1
		this.bitCount = 0
		this.shiftReg = 0x00
		this.receivedByte = 0x00
		this.byteReceived = false
		this.transmitByte = 0x00
		this.nextTransmitByte = 0x00
		this.hasNextTransmitByte = false
		this.readRequested = false
		this.addressMatched = false
		this.isRead = false
		this.addressMatchEvent = false
		this.readAddressed = false
		this.writeAddressed = false
		this.stopDetected = false
		this.transmittedByte = false
		this.transmitAcked = false
		this.ackHighSeen = false
		this.ackLowSeen = false
		this.readSdaDriveLow = 0b0
		this.initialized = false
		this.busActive = false
	}

	/**
	 * Reports a retained receive event. This single-byte slot is not a queue; service it
	 * before another byte arrives.
	 */
	public fn HasReceivedByte() bool
	{
		return this.byteReceived
	}

	/**
	 * Returns the latest received byte without consuming its event; use HasReceivedByte to
	 * check availability.
	 */
	public fn GetReceivedByte() byte
	{
		return this.receivedByte
	}

	/**
	 * Clears the receive notification while retaining the last byte value.
	 */
	public fn ClearReceivedByte()
	{
		this.byteReceived = false
	}

	/**
	 * Replaces the current outgoing byte. Supply it before the controller begins clocking that
	 * byte.
	 */
	public fn SetTransmitByte(value: byte)
	{
		this.transmitByte = value
	}

	/**
	 * Fills the one-byte prefetch slot promoted after a controller ACK. A later call replaces
	 * any unconsumed prefetched value.
	 */
	public fn SetNextTransmitByte(value: byte)
	{
		state {
			this.nextTransmitByte = value
			this.hasNextTransmitByte = true
		}
	}

	/**
	 * Invalidates the prefetched byte without changing the current transmit byte.
	 */
	public fn ClearNextTransmitByte()
	{
		this.hasNextTransmitByte = false
	}

	/**
	 * Reports the retained request for read data; service it and clear the event before
	 * relying on a later request.
	 */
	public fn IsReadRequested() bool
	{
		return this.readRequested
	}

	/**
	 * Clears the read-data request flag without changing transmit data.
	 */
	public fn ClearReadRequested()
	{
		this.readRequested = false
	}

	/**
	 * Reports a retained matching-address event; inspect the direction flags before clearing
	 * it.
	 */
	public fn HasAddressMatch() bool
	{
		return this.addressMatchEvent
	}

	/**
	 * Clears the address event and both retained direction flags without aborting the bus
	 * transaction.
	 */
	public fn ClearAddressMatch()
	{
		state {
			this.addressMatchEvent = false
			this.readAddressed = false
			this.writeAddressed = false
		}
	}

	/**
	 * Reports the retained read-direction address notification; inspect with HasAddressMatch.
	 */
	public fn WasReadAddressed() bool
	{
		return this.readAddressed
	}

	/**
	 * Reports the retained write-direction address notification; inspect with HasAddressMatch.
	 */
	public fn WasWriteAddressed() bool
	{
		return this.writeAddressed
	}

	/**
	 * Reports a retained STOP event; reading does not clear it.
	 */
	public fn HasStopDetected() bool
	{
		return this.stopDetected
	}

	/**
	 * Clears the retained STOP notification.
	 */
	public fn ClearStopDetected()
	{
		this.stopDetected = false
	}

	/**
	 * Reports a retained byte-transmit completion; inspect WasTransmitAcked before clearing
	 * it.
	 */
	public fn HasTransmittedByte() bool
	{
		return this.transmittedByte
	}

	/**
	 * Returns the controller ACK status for the retained transmit completion; valid with
	 * HasTransmittedByte.
	 */
	public fn WasTransmitAcked() bool
	{
		return this.transmitAcked
	}

	/**
	 * Clears the transmit-completion event and its retained ACK flag.
	 */
	public fn ClearTransmittedByte()
	{
		state {
			this.transmittedByte = false
			this.transmitAcked = false
		}
	}

	/**
	 * Tracks bus edges, accepts matching address/data bytes, and transmits current/prefetched
	 * bytes. Events are single retained slots; the caller must service them fast enough for
	 * the bus rate.
	 */
	process Main()
	{
		var oldScl: logic = 0b1
		var oldSda: logic = 0b1
		var sclNow: logic = 0b1
		var sdaNow: logic = 0b1
		var nextShift: int = 0
		var matchedAddress: byte = 0x00
		var rwBit: int = 0
		var byteValue: int = 0
		var shiftAmount: int = 0
		var bitValue: int = 0
		var readMode: bool = false

		state Idle {
			this.bus.scl_drive_low = 0b0
			this.bus.sda_drive_low = 0b0

			if (this.initialized == false) {
				this.initialized = true
				this.prevScl = this.bus.scl
				this.prevSda = this.bus.sda
				goto Idle
			}

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldSda == 0b1 && sdaNow == 0b0 && sclNow == 0b1) {
				this.busActive = true
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.isRead = false
				readMode = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				goto AddressBits
			} else {
				goto Idle
			}
		}

		state AddressBits {
			this.bus.scl_drive_low = 0b0
			this.bus.sda_drive_low = 0b0

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldSda == 0b1 && sdaNow == 0b0 && sclNow == 0b1) {
				this.busActive = true
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.isRead = false
				readMode = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				goto AddressBits
			} elif (oldSda == 0b0 && sdaNow == 0b1 && sclNow == 0b1 && this.busActive == true) {
				this.stopDetected = true
				this.busActive = false
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				goto Idle
			} elif (oldScl == 0b0 && sclNow == 0b1) {
				nextShift = (this.shiftReg as int) << 1

				if (sdaNow == 0b1) {
					nextShift = nextShift | 1
				}

				this.shiftReg = nextShift as byte

				if (this.bitCount == 7) {
					matchedAddress = (nextShift >> 1) as byte
					rwBit = nextShift & 1
					this.bitCount = 0

					if (matchedAddress == this.address) {
						this.addressMatched = true
						this.addressMatchEvent = true
						readMode = (rwBit == 1)
						this.isRead = readMode

						if (readMode == true) {
							this.readAddressed = true
							this.readRequested = true
						} else {
							this.writeAddressed = true
						}

						this.ackHighSeen = false
						this.ackLowSeen = false
						goto AddressAck
					} else {
						this.addressMatched = false
						goto Idle
					}
				} else {
					this.bitCount = this.bitCount + 1
					goto AddressBits
				}
			} else {
				goto AddressBits
			}
		}

		state AddressAck {
			this.bus.scl_drive_low = 0b0

			if (this.ackLowSeen == true) {
				this.bus.sda_drive_low = 0b1
			} else {
				this.bus.sda_drive_low = 0b0
			}

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldScl == 0b1 && sclNow == 0b0) {
				if (this.ackHighSeen == true) {
					this.bus.sda_drive_low = 0b0
					this.shiftReg = 0x00
					this.bitCount = 0
					this.ackHighSeen = false
					this.ackLowSeen = false

					if (readMode == true) {
						byteValue = this.transmitByte as int
						shiftAmount = 7 - this.bitCount
						bitValue = (byteValue >> shiftAmount) & 1

						if (bitValue == 0) {
							this.readSdaDriveLow = 0b1
							this.bus.sda_drive_low = 0b1
						} else {
							this.readSdaDriveLow = 0b0
							this.bus.sda_drive_low = 0b0
						}

						goto ReadDataBits
					} else {
						goto WriteDataBits
					}
				} else {
					this.ackLowSeen = true
					this.bus.sda_drive_low = 0b1
					goto AddressAck
				}
			} elif (oldScl == 0b0 && sclNow == 0b1) {
				if (this.ackLowSeen == true) {
					this.ackHighSeen = true
				}

				goto AddressAck
			} else {
				goto AddressAck
			}
		}

		state WriteDataBits {
			this.bus.scl_drive_low = 0b0
			this.bus.sda_drive_low = 0b0

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldSda == 0b1 && sdaNow == 0b0 && sclNow == 0b1) {
				this.busActive = true
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.isRead = false
				readMode = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				goto AddressBits
			} elif (oldSda == 0b0 && sdaNow == 0b1 && sclNow == 0b1 && this.busActive == true) {
				this.stopDetected = true
				this.busActive = false
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				readMode = false
				goto Idle
			} elif (oldScl == 0b0 && sclNow == 0b1) {
				nextShift = (this.shiftReg as int) << 1

				if (sdaNow == 0b1) {
					nextShift = nextShift | 1
				}

				this.shiftReg = nextShift as byte

				if (this.bitCount == 7) {
					this.receivedByte = nextShift as byte
					this.byteReceived = true
					this.bitCount = 0
					this.ackHighSeen = false
					this.ackLowSeen = false
					goto WriteAck
				} else {
					this.bitCount = this.bitCount + 1
					goto WriteDataBits
				}
			} else {
				goto WriteDataBits
			}
		}

		state WriteAck {
			this.bus.scl_drive_low = 0b0

			if (this.ackLowSeen == true) {
				this.bus.sda_drive_low = 0b1
			} else {
				this.bus.sda_drive_low = 0b0
			}

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldScl == 0b1 && sclNow == 0b0) {
				if (this.ackHighSeen == true) {
					this.bus.sda_drive_low = 0b0
					this.shiftReg = 0x00
					this.ackHighSeen = false
					this.ackLowSeen = false
					goto WriteDataBits
				} else {
					this.ackLowSeen = true
					this.bus.sda_drive_low = 0b1
					goto WriteAck
				}
			} elif (oldScl == 0b0 && sclNow == 0b1) {
				if (this.ackLowSeen == true) {
					this.ackHighSeen = true
				}

				goto WriteAck
			} else {
				goto WriteAck
			}
		}

		state ReadDataBits {
			this.bus.scl_drive_low = 0b0
			this.bus.sda_drive_low = this.readSdaDriveLow

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldSda == 0b1 && sdaNow == 0b0 && sclNow == 0b1) {
				this.busActive = true
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.isRead = false
				readMode = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				this.bus.sda_drive_low = 0b0
				goto AddressBits
			} elif (oldSda == 0b0 && sdaNow == 0b1 && sclNow == 0b1 && this.busActive == true) {
				this.stopDetected = true
				this.busActive = false
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				this.bus.sda_drive_low = 0b0
				readMode = false
				goto Idle
			} elif (oldScl == 0b1 && sclNow == 0b0) {
				byteValue = this.transmitByte as int
				shiftAmount = 7 - this.bitCount
				bitValue = (byteValue >> shiftAmount) & 1

				if (bitValue == 0) {
					this.readSdaDriveLow = 0b1
					this.bus.sda_drive_low = 0b1
				} else {
					this.readSdaDriveLow = 0b0
					this.bus.sda_drive_low = 0b0
				}

				goto ReadDataBits
			} elif (oldScl == 0b0 && sclNow == 0b1) {
				if (this.bitCount == 7) {
					this.bitCount = 0
					goto ReadRelease
				} else {
					this.bitCount = this.bitCount + 1
					goto ReadDataBits
				}
			} else {
				goto ReadDataBits
			}
		}

		state ReadRelease {
			this.bus.scl_drive_low = 0b0
			this.bus.sda_drive_low = this.readSdaDriveLow

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldScl == 0b1 && sclNow == 0b0) {
				this.readSdaDriveLow = 0b0
				this.bus.sda_drive_low = 0b0
				goto ReadAck
			} else {
				goto ReadRelease
			}
		}

		state ReadAck {
			this.bus.scl_drive_low = 0b0
			this.bus.sda_drive_low = 0b0

			oldScl = this.prevScl
			oldSda = this.prevSda
			sclNow = this.bus.scl
			sdaNow = this.bus.sda
			this.prevScl = sclNow
			this.prevSda = sdaNow

			if (oldSda == 0b1 && sdaNow == 0b0 && sclNow == 0b1) {
				this.busActive = true
				this.bitCount = 0
				this.shiftReg = 0x00
				this.addressMatched = false
				this.isRead = false
				readMode = false
				this.ackHighSeen = false
				this.ackLowSeen = false
				this.readSdaDriveLow = 0b0
				goto AddressBits
			} elif (oldScl == 0b0 && sclNow == 0b1) {
				this.transmittedByte = true
				this.transmitAcked = (sdaNow == 0b0)

				if (sdaNow == 0b0) {
					if (this.hasNextTransmitByte == true) {
						this.transmitByte = this.nextTransmitByte
						this.hasNextTransmitByte = false
					}

					this.bitCount = 0
					this.readSdaDriveLow = 0b0
					goto ReadDataBits
				} else {
					this.busActive = false
					this.addressMatched = false
					readMode = false
					goto Idle
				}
			} else {
				goto ReadAck
			}
		}
	}
}