Livt packages

Livt.Collections

Version 1.4.2

Fixed-capacity FIFOs, queues, stacks, circular buffers, lists, and bit sets for Livt hardware.

Published files (62)

src/fifo/Fifo.lvt

namespace Livt.Collections

/**
 * Hardware-level single-clock FIFO for cycle-sensitive producer/consumer wiring.
 * Requests and combinational acceptance are sampled together on the clock edge.
 * Pop data is the old head; an empty FIFO does not bypass. Use Queue for
 * convenient application-level scheduled transactions over this storage core.
 */
component Fifo<T, CAPACITY: int = 64>
{
	static assert CAPACITY > 0, "FIFO capacity must be positive"

	// Capacity-derived ring-address and logical occupancy widths.
	const ADDRESS_BITS: int = Bits.RequiredFor(CAPACITY - 1)
	const COUNT_BITS: int = Bits.RequiredFor(CAPACITY)

	// Transfer requests sampled together at the active clock edge.
	clear: bool
	push: bool
	pushData: T
	pop: bool

	// Combinational pre-edge acceptance, data, and occupancy observations.
	public pushAccepted: out bool
	public popAccepted: out bool
	public popData: out T
	public count: out int
	public space: out int

	// Reset-free payload storage and narrow owner state.
	@UninitializedStorage
	storage: T[CAPACITY]

	// Ring positions and logical occupancy within the fixed capacity.
	head: logic[ADDRESS_BITS]
	tail: logic[ADDRESS_BITS]
	used: logic[COUNT_BITS]

	// Cached head avoids a second process reading storage; zero serves empty reads.
	front: T
	zero: T

	/**
	 * Creates empty storage in the inherited clock/reset context.
	 */
	new(clear: bool, push: bool, pushData: T, pop: bool,
		pushAccepted: out bool, popAccepted: out bool, popData: out T,
		count: out int, space: out int)
	{
		this.clear = clear
		this.push = push
		this.pushData = pushData
		this.pop = pop
		pushAccepted = this.pushAccepted
		popAccepted = this.popAccepted
		popData = this.popData
		count = this.count
		space = this.space
	}

	/**
	 * Reports transactions accepted at the next edge and the pre-edge head.
	 */
	process Status[]()
	{
		this.count = (this.used as uint) as int
		this.space = CAPACITY - ((this.used as uint) as int)
		this.popAccepted = this.context.rst == 0b0 && !this.clear && this.pop && (this.used as uint) > 0
		this.pushAccepted = this.context.rst == 0b0 &&
			!this.clear &&
			this.push &&
			((this.used as uint) < (CAPACITY as uint) ||
			(this.pop &&
			(this.used as uint) > 0))
		this.popData = this.zero

		if ((this.used as uint) > 0) {
			this.popData = this.front
		}
	}

	/**
	 * Owns all queue mutation. Clear suppresses transfers without clearing cells.
	 */
	process Update()
	{
		state Tick {
			var take = pop && (used as uint) > 0
			var put = push && ((used as uint) < (CAPACITY as uint) || take)

			if (clear) {
				head = 0
				tail = 0
				used = 0
			} else {
				if (take) {
					if ((used as uint) > 1) {
						if ((head as uint) == ((CAPACITY - 1) as uint)) {
							front = storage[0]
						} else {
							front = storage[(head as uint) + 1]
						}
					} elif (put) {
						// Replacing the sole entry (also the capacity-one full case)
						// exposes the newly accepted word after this edge.
						front = pushData
					}

					static if (CAPACITY > 1) {
						if ((head as uint) == ((CAPACITY - 1) as uint)) {
							head = 0
						} else {
							head = ((head as uint) + 1) as logic[ADDRESS_BITS]
						}
					}
				}

				if (put) {
					storage[tail as uint] = pushData

					// The first word is cached so combinational status never reads the
					// backing array from a second behavioural process.
					if (!take && (used as uint) == 0) {
						front = pushData
					}

					static if (CAPACITY > 1) {
						if ((tail as uint) == ((CAPACITY - 1) as uint)) {
							tail = 0
						} else {
							tail = ((tail as uint) + 1) as logic[ADDRESS_BITS]
						}
					}
				}

				if (put && !take) {
					used = ((used as uint) + 1) as logic[COUNT_BITS]
				}

				if (take && !put) {
					used = ((used as uint) - 1) as logic[COUNT_BITS]
				}
			}

			goto Tick
		}
	}
}