Livt packages

Livt.Signal

Version 0.1.1

Q1.15 sample streams, filters, windows, rate conversion, FFTs, oscillators, and mixers for Livt.

Published files (80)

tests/ratechange/CicQ15Fixture.lvt

namespace Livt.Signal.Tests.RateChange

using Livt.Signal.RateChange
using Livt.Signal.Stream

/**
 * Emits a fixed Q1.15 vector while honoring valid/ready backpressure.
 */
component CicQ15VectorSource<COUNT: int>
{
	stream: IQ15SampleStream
	values: int[COUNT]
	index: int
	public accepted: int

	new(stream: flip IQ15SampleStream, values: const int[COUNT])
	{
		this.stream = stream
		this.values = values
	}

	process Send()
	{
		state Emit {
			stream.sample = values[index] as logic[16]
			stream.valid = true
			stream.first = index == 0
			stream.last = index == COUNT - 1
			goto AwaitAccept
		}

		state AwaitAccept {
			if (stream.ready) {
				stream.valid = false
				accepted++

				if (index == COUNT - 1) {
					goto Idle
				} else {
					index++
					goto Gap
				}
			}
		}

		state Gap {
			stream.valid = false
			stream.first = false
			stream.last = false
			goto Emit
		}

		state Idle {
			stream.valid = false
			stream.first = false
			stream.last = false
			goto Idle
		}
	}
}

/**
 * Retains a bounded CIC output vector and its frame markers.
 */
component CicQ15VectorObserver<COUNT: int>
{
	stream: IQ15SampleStream
	public ready: in bool
	public valid: out bool
	public accepted: int
	public firstSeen: bool
	public lastSeen: bool
	public capturedSample0: logic[16]
	public capturedSample1: logic[16]
	public capturedSample2: logic[16]
	public capturedSample3: logic[16]
	public capturedSample4: logic[16]
	public capturedSample5: logic[16]
	public capturedSample6: logic[16]
	public capturedSample7: logic[16]

	new(stream: IQ15SampleStream)
	{
		this.stream = stream
	}

	process Observe[]()
	{
		stream.ready = this.context.rst == 0b0 && ready
		valid = stream.valid
	}

	process Capture()
	{
		state {
			if (stream.valid && stream.ready) {
				if (accepted == 0) {
					capturedSample0 = stream.sample
				} elif (accepted == 1) {
					capturedSample1 = stream.sample
				} elif (accepted == 2) {
					capturedSample2 = stream.sample
				} elif (accepted == 3) {
					capturedSample3 = stream.sample
				} elif (accepted == 4) {
					capturedSample4 = stream.sample
				} elif (accepted == 5) {
					capturedSample5 = stream.sample
				} elif (accepted == 6) {
					capturedSample6 = stream.sample
				} elif (accepted == 7) {
					capturedSample7 = stream.sample
				}

				if (accepted == 0 && stream.first) {
					firstSeen = true
				}

				if (accepted == COUNT - 1 && stream.last) {
					lastSeen = true
				}

				accepted++
			}
		}
	}
}

/**
 * Fixed-vector decimator harness with observable alignment diagnostics.
 */
component CicDecimatorQ15Fixture<INPUT_COUNT: int, OUTPUT_COUNT: int,
	RATE: int = 2, STAGES: int = 1, DIFFERENTIAL_DELAY: int = 1,
	OUTPUT_SHIFT: int = 1>
{
	inputStream: IQ15SampleStream
	outputStream: IQ15SampleStream
	source: CicQ15VectorSource<INPUT_COUNT>
	decimator: CicDecimatorQ15<RATE, STAGES, DIFFERENTIAL_DELAY, OUTPUT_SHIFT>
	observer: CicQ15VectorObserver<OUTPUT_COUNT>

	public outputReady: in bool
	public outputValid: out bool
	public sourceAccepted: out int
	public accepted: out int
	public firstSeen: out bool
	public lastSeen: out bool
	public alignmentErrorSeen: bool
	public capturedSample0: out logic[16]
	public capturedSample1: out logic[16]
	public capturedSample2: out logic[16]
	public capturedSample3: out logic[16]

	new(values: const int[INPUT_COUNT])
	{
		source = new CicQ15VectorSource<INPUT_COUNT>(inputStream, values)
		decimator = new CicDecimatorQ15<RATE, STAGES, DIFFERENTIAL_DELAY,
			OUTPUT_SHIFT>(inputStream, outputStream)
		observer = new CicQ15VectorObserver<OUTPUT_COUNT>(outputStream)
	}

	process Connect[]()
	{
		observer.ready = outputReady
		outputValid = observer.valid
		sourceAccepted = source.accepted
		accepted = observer.accepted
		firstSeen = observer.firstSeen
		lastSeen = observer.lastSeen
		capturedSample0 = observer.capturedSample0
		capturedSample1 = observer.capturedSample1
		capturedSample2 = observer.capturedSample2
		capturedSample3 = observer.capturedSample3
	}

	process WatchAlignment()
	{
		state {
			if (decimator.frameAlignmentError) {
				alignmentErrorSeen = true
			}
		}
	}

	public fn GetAccumulatorBits() int
	{
		return decimator.ACCUMULATOR_BITS
	}

	public fn GetInputInitiationInterval() int
	{
		return decimator.INPUT_INITIATION_INTERVAL
	}

	public fn GetOutputInitiationInterval() int
	{
		return decimator.OUTPUT_INITIATION_INTERVAL
	}
}

/**
 * Fixed-vector interpolator harness with caller-controlled output capacity.
 */
component CicInterpolatorQ15Fixture<INPUT_COUNT: int, OUTPUT_COUNT: int,
	RATE: int = 2, STAGES: int = 1, DIFFERENTIAL_DELAY: int = 1,
	OUTPUT_SHIFT: int = 0>
{
	inputStream: IQ15SampleStream
	outputStream: IQ15SampleStream
	source: CicQ15VectorSource<INPUT_COUNT>
	interpolator: CicInterpolatorQ15<RATE, STAGES, DIFFERENTIAL_DELAY,
		OUTPUT_SHIFT>
	observer: CicQ15VectorObserver<OUTPUT_COUNT>

	public outputReady: in bool
	public outputValid: out bool
	public sourceAccepted: out int
	public accepted: out int
	public firstSeen: out bool
	public lastSeen: out bool
	public capturedSample0: out logic[16]
	public capturedSample1: out logic[16]
	public capturedSample2: out logic[16]
	public capturedSample3: out logic[16]
	public capturedSample4: out logic[16]
	public capturedSample5: out logic[16]

	new(values: const int[INPUT_COUNT])
	{
		source = new CicQ15VectorSource<INPUT_COUNT>(inputStream, values)
		interpolator = new CicInterpolatorQ15<RATE, STAGES,
			DIFFERENTIAL_DELAY, OUTPUT_SHIFT>(inputStream, outputStream)
		observer = new CicQ15VectorObserver<OUTPUT_COUNT>(outputStream)
	}

	process Connect[]()
	{
		observer.ready = outputReady
		outputValid = observer.valid
		sourceAccepted = source.accepted
		accepted = observer.accepted
		firstSeen = observer.firstSeen
		lastSeen = observer.lastSeen
		capturedSample0 = observer.capturedSample0
		capturedSample1 = observer.capturedSample1
		capturedSample2 = observer.capturedSample2
		capturedSample3 = observer.capturedSample3
		capturedSample4 = observer.capturedSample4
		capturedSample5 = observer.capturedSample5
	}

	public fn GetAccumulatorBits() int
	{
		return interpolator.ACCUMULATOR_BITS
	}

	public fn GetInputInitiationInterval() int
	{
		return interpolator.INPUT_INITIATION_INTERVAL
	}

	public fn GetOutputInitiationInterval() int
	{
		return interpolator.OUTPUT_INITIATION_INTERVAL
	}
}

/**
 * Instantiates the public CIC defaults so their elaborated contract is tested.
 */
component CicQ15DefaultContractFixture
{
	const DECIMATOR_VALUES: int[2] = [0, 0]
	const INTERPOLATOR_VALUES: int[1] = [0]

	decimatorInput: IQ15SampleStream
	decimatorOutput: IQ15SampleStream
	interpolatorInput: IQ15SampleStream
	interpolatorOutput: IQ15SampleStream
	decimatorSource: CicQ15VectorSource<2>
	decimatorObserver: CicQ15VectorObserver<1>
	interpolatorSource: CicQ15VectorSource<1>
	interpolatorObserver: CicQ15VectorObserver<2>
	decimator: CicDecimatorQ15<2>
	interpolator: CicInterpolatorQ15<2>

	new()
	{
		decimatorSource = new CicQ15VectorSource<2>(decimatorInput,
			DECIMATOR_VALUES)
		decimator = new CicDecimatorQ15<2>(decimatorInput, decimatorOutput)
		decimatorObserver = new CicQ15VectorObserver<1>(decimatorOutput)
		interpolatorSource = new CicQ15VectorSource<1>(interpolatorInput,
			INTERPOLATOR_VALUES)
		interpolator = new CicInterpolatorQ15<2>(interpolatorInput,
			interpolatorOutput)
		interpolatorObserver = new CicQ15VectorObserver<2>(interpolatorOutput)
	}

	process TieOff[]()
	{
		decimatorObserver.ready = true
		interpolatorObserver.ready = true
	}

	public fn GetDecimatorBits() int
	{
		return decimator.ACCUMULATOR_BITS
	}

	public fn GetDecimatorShift() int
	{
		return decimator.AUTOMATIC_OUTPUT_SHIFT
	}

	public fn GetInterpolatorBits() int
	{
		return interpolator.ACCUMULATOR_BITS
	}

	public fn GetInterpolatorShift() int
	{
		return interpolator.AUTOMATIC_OUTPUT_SHIFT
	}
}