Livt packages

Eccelerators.Video

Version 1.0.1

Streaming pixel pipelines, filters, test patterns, and parallel camera capture for Livt FPGA applications.

Published files (22)

src/DvpRgb565Capture.lvt

namespace Eccelerators.Video

/**
 * Captures high-byte-first RGB565 on rising edges of the inherited PCLK context.
 * VSYNC is active high (blanking); HREF is high for active bytes. Board adapter
 * must supply the selected sensor mode's polarity/timing and reset synchronization.
 * No CDC is performed here. Connect the output to a qualified asynchronous FIFO.
 * The camera cannot stall: overflow/malformed geometry discards the rest of that
 * frame. The final pixel/END is withheld until VSYNC validates complete geometry,
 * so a partial frame cannot become a complete PC image. Resumes at the next frame.
 */
component DvpRgb565Capture<WIDTH: int = 320, HEIGHT: int = 240> : IPixelStream
{
	static assert WIDTH > 0 && WIDTH <= 4096 && HEIGHT > 0 && HEIGHT <= 4096, "Invalid DVP dimensions"

	// Physical inputs are sampled directly in PCLK, not synchronized bit-by-bit.
	public cameraData: in byte
	public href: in bool
	public vsync: in bool

	// Coherent counters in PCLK; snapshot/CDC is required before system-domain reads.
	public capturedFrames: out uint
	public droppedFrames: out uint
	public malformedFrames: out uint
	public overflowFrames: out uint

	// Frame/line ownership; initialization waits for an observed VSYNC pulse.
	previousSync: bool
	previousHref: bool
	active: bool
	x: int
	y: int
	halfPixel: bool
	highByte: byte
	sequence: uint
	identity: uint
	lastPixel: logic[16]
	lastPending: bool

	// Owned one-token output register and frame-level diagnostics.
	heldData: logic[16]
	heldValid: bool
	heldStart: bool
	heldLineEnd: bool
	heldEnd: bool
	heldId: uint
	captured: uint
	dropped: uint
	malformed: uint
	overflowed: uint

	/**
	 * Exposes each token and diagnostic without independent accessor registers.
	 */
	process Output[]()
	{
		this.data = this.heldData
		this.valid = this.heldValid
		this.frameStart = this.heldStart
		this.lineEnd = this.heldLineEnd
		this.frameEnd = this.heldEnd
		this.frameId = this.heldId
		this.capturedFrames = this.captured
		this.droppedFrames = this.dropped
		this.malformedFrames = this.malformed
		this.overflowFrames = this.overflowed
	}

	/**
	 * Samples one camera byte per edge. Only the final pixel waits for frame validation.
	 */
	process Capture()
	{
		state Tick {
			if (this.ready) {
				this.heldValid = false
			}

			if (this.previousSync && !this.vsync) {
				this.active = true
				this.x = 0
				this.y = 0
				this.halfPixel = false
				this.lastPending = false
				this.identity = this.sequence
				this.sequence++
			}

			if (this.active && this.previousHref && !this.href) {
				if (this.halfPixel || this.x != WIDTH || this.y >= HEIGHT) {
					this.active = false
					this.dropped++
					this.malformed++
				} else {
					this.y++
					this.x = 0
				}
			}

			if (this.active && this.vsync && !this.previousSync) {
				if (this.y != HEIGHT || this.x != 0 || this.halfPixel || !this.lastPending) {
					this.dropped++
					this.malformed++
				} elif (this.heldValid && !this.ready) {
					this.dropped++
					this.overflowed++
				} else {
					this.heldData = this.lastPixel
					this.heldValid = true
					this.heldStart = WIDTH == 1 && HEIGHT == 1
					this.heldLineEnd = true
					this.heldEnd = true
					this.heldId = this.identity
					this.captured++
				}

				this.active = false
			}

			if (this.active && !this.vsync && this.href) {
				if (this.x >= WIDTH || this.y >= HEIGHT) {
					this.active = false
					this.dropped++
					this.malformed++
				} elif (!this.halfPixel) {
					this.highByte = this.cameraData
					this.halfPixel = true
				} else {
					var pixel = (((this.highByte as uint) << 8) | (this.cameraData as uint)) as logic[16]
					this.halfPixel = false

					if (this.x == WIDTH - 1 && this.y == HEIGHT - 1) {
						this.lastPixel = pixel
						this.lastPending = true
					} elif (this.heldValid && !this.ready) {
						this.active = false
						this.dropped++
						this.overflowed++
					} else {
						this.heldData = pixel
						this.heldValid = true
						this.heldStart = this.x == 0 && this.y == 0
						this.heldLineEnd = this.x == WIDTH - 1
						this.heldEnd = false
						this.heldId = this.identity
					}

					this.x++
				}
			}

			this.previousSync = this.vsync
			this.previousHref = this.href
			goto Tick
		}
	}
}