Livt packages

Livt.Bus

Version 2.0.1

Configurable Avalon, AXI4-Lite, and Wishbone memory-mapped masters, interfaces, bridges, and test components.

Published files (67)

src/wishbone/WishboneMaster.lvt

namespace Livt.Bus.Wishbone

using Livt.Bus

/**
 * Width-configurable Wishbone Classic single-transfer master.
 */
component WishboneMaster<AddressWidth: int = 32, DataWidth: int = 32, CheckAlignment: bool = true> : IWishboneMaster<Logic[AddressWidth], Logic[DataWidth], Logic[BYTE_COUNT]>, IMemoryMappedMaster<Logic[AddressWidth], Logic[DataWidth], Logic[BYTE_COUNT]>
{
	const BYTE_COUNT: int = DataWidth / 8
static assert DataWidth == 32 || DataWidth == 64 || DataWidth == 128, "DataWidth must be 32, 64 or 128"
static assert AddressWidth >= Bits.RequiredFor(DataWidth / 8 - 1) && AddressWidth <= 64, "AddressWidth cannot represent byte lanes or exceeds 64"

	/**
	 * Status returned after a Wishbone acknowledgement.
	 */
	public const RESPONSE_OKAY: Logic[2] = 0b00

	/**
	 * Status returned after a local Wishbone timeout.
	 */
	public const RESPONSE_SLVERR: Logic[2] = 0b10

	/**
	 * Byte-select value enabling all native data lanes.
	 */
	public const ALL_BYTES: Logic[BYTE_COUNT] = ~(0 as Logic[BYTE_COUNT])

	timeoutTicks: uint
	lastReadValue: Logic[DataWidth]
	lastResponse: Logic[2]
	lastTimedOut: bool
	commandAddress: Logic[AddressWidth]
	commandData: Logic[DataWidth]
	commandMask: Logic[BYTE_COUNT]
	commandIsRead: bool
	commandPending: bool
	transferComplete: bool
	request_adr: Logic[AddressWidth]
	request_datin: Logic[DataWidth]
	request_sel: Logic[BYTE_COUNT]
	request_cyc: logic
	request_stb: logic
	request_we: logic
	target_ack: logic
	target_datout: Logic[DataWidth]

	new()
	{
		this.timeoutTicks = this.context.TicksFor(1ms)
	}

	/**
	 * Exact per-stage deadline; acceptance wins at the boundary.
	 */
	override fn SetTimeoutTicks(timeoutTicks: uint)
	{
		this.timeoutTicks = timeoutTicks
	}

	override fn GetTimeoutTicks() uint
	{
		return this.timeoutTicks
	}

	override fn LastReadValue() Logic[DataWidth]
	{
		return this.lastReadValue
	}

	override fn LastResponse() Logic[2]
	{
		return this.lastResponse
	}

	override fn LastTimedOut() bool
	{
		return this.lastTimedOut
	}

	override fn TryWrite(address: Logic[AddressWidth], value: Logic[DataWidth], byteEnable: Logic[BYTE_COUNT]) bool
	{
		if (CheckAlignment) {
			assert (address & ((BYTE_COUNT - 1) as Logic[AddressWidth])) == (0 as Logic[AddressWidth])
		}

		var completed = this.ExecuteTransfer(address, value, byteEnable, false)
		return completed
	}

	override fn TryRead(address: Logic[AddressWidth]) bool
	{
		if (CheckAlignment) {
			assert (address & ((BYTE_COUNT - 1) as Logic[AddressWidth])) == (0 as Logic[AddressWidth])
		}

		var completed = this.ExecuteTransfer(address, 0 as Logic[DataWidth], ALL_BYTES, true)
		return completed
	}

	/**
	 * One read with explicit lane enables. Address and enables are transmitted unchanged.
	 */
	override fn TryReadMasked(address: Logic[AddressWidth], byteEnable: Logic[BYTE_COUNT]) bool
	{
		if (CheckAlignment) {
			assert (address & ((BYTE_COUNT - 1) as Logic[AddressWidth])) == (0 as Logic[AddressWidth])
		}

		var completed = this.ExecuteTransfer(address, 0 as Logic[DataWidth], byteEnable, true)
		return completed
	}

	/**
	 * Scheduled calls own the mailbox; one clocked process owns all bus outputs.
	 */
	private fn ExecuteTransfer(address: Logic[AddressWidth], value: Logic[DataWidth], mask: Logic[BYTE_COUNT], isRead: bool) bool
	{
		this.commandAddress = address
		this.commandData = value
		this.commandMask = mask
		this.commandIsRead = isRead
		this.commandPending = true

		state WaitForCompletion {
			if (this.transferComplete) {
				goto Release
			} else {
				goto WaitForCompletion
			}
		}

		state Release {
			this.commandPending = false;
			goto WaitForIdle
		}

		state WaitForIdle {
			if (this.transferComplete) {
				goto WaitForIdle
			} else {
				goto Finish
			}
		}

		state Finish {}

		return !this.lastTimedOut
	}

	/**
	 * Direct protocol wires: ready/valid/ack are never sampled by field accessors.
	 */
	process Connect[]()
	{
		this.adr = this.request_adr
		this.datin = this.request_datin
		this.sel = this.request_sel
		this.cyc = this.request_cyc
		this.stb = this.request_stb
		this.we = this.request_we
		this.target_ack = this.ack
		this.target_datout = this.datout
	}

	/**
	 * Classic single transfers terminate on ACK or deadline; no pipelining.
	 */
	process Transfer()
	{
		var elapsed: uint = 0
		var deadline: uint = 0

		state Idle {
			if (this.commandPending) {
				elapsed = 0
				deadline = this.timeoutTicks

				if (this.commandIsRead) {
					this.lastReadValue = 0
				}

				this.lastResponse = RESPONSE_OKAY
				this.lastTimedOut = false
				this.request_adr = this.commandAddress
				this.request_datin = this.commandData
				this.request_sel = this.commandMask
				this.request_cyc = 0b1
				this.request_stb = 0b1

				if (this.commandIsRead) {
					this.request_we = 0b0
				} else {
					this.request_we = 0b1
				}

				goto Active
			} else {
				goto Idle
			}
		}

		state Active {
			if (this.target_ack == 0b1) {
				if (this.commandIsRead) {
					this.lastReadValue = this.target_datout
				}

				this.request_cyc = 0b0
				this.request_stb = 0b0
				this.request_we = 0b0
				this.request_sel = 0 as Logic[BYTE_COUNT]
				this.transferComplete = true
				goto WaitForRelease
			} elif ((elapsed + 1) >= deadline) {
				this.lastTimedOut = true
				this.lastResponse = RESPONSE_SLVERR
				this.request_cyc = 0b0
				this.request_stb = 0b0
				this.request_we = 0b0
				this.request_sel = 0 as Logic[BYTE_COUNT]
				this.transferComplete = true
				goto WaitForRelease
			} else {
				elapsed = elapsed + 1;
				goto Active
			}
		}

		state WaitForRelease {
			if (this.commandPending) {
				goto WaitForRelease
			} else {
				this.transferComplete = false;
				goto Idle
			}
		}
	}
}