Eccelerators.Cli is a transport-independent command-line interface for FPGA
applications written in Livt. It provides bounded terminal input, editing,
parsing, prompts, atomic output writes, and explicit backpressure. Applications
remain responsible for the physical byte transport and static command dispatch.
The first stable release is 1.0.0.
- 64-byte bounded command lines
- up to eight whitespace-separated arguments
- CR, LF, and CRLF line endings
- backspace and delete editing
- optional local echo
- scheduled
>prompts - 64-byte circular output FIFO
- atomic byte-array and CRLF line writes
- lossless input and output backpressure
- no dynamic allocation or command registration
All capacities and commands are fixed at synthesis time.
Add the package to a Livt project with:
[dependencies]
"Eccelerators.Cli" = "1.0.0"Import its public API with:
using Eccelerators.Cli
Cliis the application-facing facade.CliLineEditortracks bounded input length and line-ending state.CliParserstores a command snapshot and token spans.CliOutputqueues echo, responses, errors, and prompts atomically.
See DESIGN.md for ownership, data flow, and timing contracts.
An application owns one Cli and one byte transport. Its continuous process
services prompts, retries pending commands, drains output only after the
transport accepts a byte, and accepts input only when the CLI has capacity. The
transport operations below are pseudocode and must be replaced by the target
application's UART or other byte transport:
process Main()
{
this.cli.Service()
if (this.cli.HasCommand() == true) {
var completed: bool = this.DispatchCommand()
if (completed == true) { this.cli.CompleteCommand() }
}
if (this.cli.HasOutput() == true && transportCanWrite == true) {
var value: byte = this.cli.PeekOutput()
if (transportWrite(value) == true) { this.cli.ConsumeOutput() }
}
if (transportHasData == true && this.cli.CanAcceptByte() == true) {
this.cli.AcceptByte(transportRead())
}
}
The transport must not discard an input byte when CanAcceptByte() is false.
Likewise, call ConsumeOutput() only after the transport accepted the byte from
PeekOutput(). These two rules preserve data during backpressure.
The sibling livt-uart-cli-app repository provides a complete UART example.
Commands are statically dispatched by the application that owns Cli:
var status = "status".Encode()
var isStatus: bool = this.cli.CommandEquals(status)
if (isStatus == true) {
var message = "OK".Encode()
var written: bool = this.cli.TryWriteLine(message)
return written
}
Returning false keeps the command pending so the application can retry after
the output transport drains.
Input and lifecycle:
Service()CanAcceptByte()andAcceptByte(value)HasCommand()andCompleteCommand()Reset()
Parsing:
CommandEquals(expected)andGetCommandLength()GetArgumentCount()GetArgumentLength(index)andGetArgumentByte(index, offset)ArgumentEquals(index, expected)
Output:
CanWrite(length)TryWriteByte(value),TryWrite(data), andTryWriteLine(data)TryWriteArgumentsLine()HasOutput(),PeekOutput(), andConsumeOutput()GetOutputCount()
- Command lines contain at most 64 printable bytes.
- At most eight arguments are retained; excess arguments reject the line.
- Parsing is case-sensitive.
- Spaces and tabs separate arguments; quoting and escaping are not implemented.
- The output FIFO holds 64 bytes, so one atomic
TryWriteLine()payload can be at most 62 bytes when the FIFO is empty. - Applications must drain output while receiving echoed input.
- Dynamic command registration is intentionally outside the hardware model.
livt validate
livt testThe test suite covers complete FIFO ordering and wraparound, atomic writes, line endings, editing, overflow recovery, parsing, argument limits, prompts, enabled and disabled echo, input backpressure, and command completion.
MIT. See LICENSE.