Livt types describe values that will eventually become hardware: single signals, vectors, counters, byte streams, fixed tables, state variables, and test data. The type system is intentionally familiar, but every width and array dimension has hardware consequences.
This page introduces the core primitive types, fixed-size arrays, literals, casts, and operators. The goal is not to memorize every rule at once. The goal is to learn which type communicates the intent of a value most clearly.
Primitive Types
Livt provides keyword spellings for the primitive types used most often:
| Keyword | Meaning | Typical use |
|---|---|---|
bool |
Two-valued condition: true or false |
Decisions, function results, assertions |
logic |
Hardware logic value | Signals, ports, bit vectors |
byte |
Unsigned 8-bit value | Protocol data, buffers, encoded text |
int |
Signed 32-bit integer | Counters, loop variables, arithmetic |
uint |
Unsigned 32-bit integer | Non-negative counts and sizes |
string |
Text value | Simulation, constants, encoded byte data |
clock |
Clock signal | Sequential process context |
reset |
Reset signal | Sequential process context |
Prefer the keyword spelling in examples and application code. It is shorter and keeps the source focused on intent.
bool
Use bool for decisions:
var enabled: bool = true
var done: bool = false
if (enabled)
{
done = true
}
Comparisons produce bool:
var value: int = 7
var small: bool = value < 10
Keep bool separate from logic. A bool answers a language-level question: should this branch run, did this function succeed, did this assertion pass? A logic value represents a hardware signal and can have HDL-style values.
logic
Use logic for hardware signals:
var bit: logic = 0b1
var unknown: logic = 0bX
Common logic literals include:
0b0
0b1
0bU
0bX
0bZ
logic[N] is an N-bit vector:
var nibble: logic[4] = 0b1010
var word: logic[32] = 0x0000002A
Use logic when a value is part of the hardware signal model. Use bool when a value is a condition in Livt control flow.
byte
byte is an unsigned 8-bit value with range 0 through 255:
var zero: byte = 0x00
var letterA: byte = 0x41
var max: byte = 0xFF
Bytes are natural for packet data, UART payloads, memory contents, encoded text, and protocol fields. Hex literals are common because they make byte boundaries obvious.
When you need signed arithmetic, use int. A byte should usually mean raw unsigned data.
int and uint
Use int for signed integer arithmetic and loop counters:
var offset: int = -4
var index: int = 0
Use uint when negative values do not make sense:
var length: uint = 1500
In hardware-oriented code, prefer named constants for important limits and widths instead of repeating numeric literals.
clock and reset
clock and reset identify timing signals used by sequential processes:
component TimedCounter
{
public count: int
new(clk: clock, rst: reset)
{
this.context.clk = clk
this.context.rst = rst
}
process Count()
{
this.count = this.count + 1
}
}
Combinational functions and clockless processes do not need a clock or reset. Sequential processes do.
string
Strings are text values. They are especially useful in tests, simulation reports, and fixed text that should become byte data:
Simulation.Report("starting test")
A string can be encoded into bytes:
component TextConstants
{
const GREETING: byte[] = "Hello".Encode()
fn GetGreetingLength() int
{
return GREETING.Length()
}
}
Treat strings carefully in synthesizable code. Text is most often used at compile time, in constants, or in simulation-only APIs. When hardware needs to transmit text, encode it into byte data.
Fixed-Size Arrays
Hardware resources are statically allocated, so arrays usually have fixed sizes. The size is part of the type:
var payload: byte[64]
var table: int[16]
var matrix: byte[2, 3]
Array literals initialize fixed-size arrays:
var header: byte[4] = [0xDE, 0xAD, 0xBE, 0xEF]
var offsets: int[3] = [0, 14, 34]
Multi-dimensional arrays use nested literals:
var matrix: byte[2, 3] = [
[0x01, 0x02, 0x03],
[0x04, 0x05, 0x06]
]
Indexing is zero-based:
var first = header[0]
header[1] = 0xAA
matrix[1, 2] = 0xFF
Logic Vectors and Array Dimensions
logic[N] is a vector, not a list of N separate Livt values. Initialize it with a binary or hex literal:
var flags: logic[4] = 0b1010
Use bracket-list literals when the type is an array of elements:
var bytes: byte[3] = [0x10, 0x20, 0x30]
var rows: logic[4, 2] = [0b00, 0b01, 0b10, 0b11]
The distinction matters because it maps to different VHDL shapes. If you mean one vector, use logic[N]. If you mean several byte or vector elements, use an array type such as byte[N] or logic[A, B].
Type Inference
Livt can infer many local variable types from their initializer:
var value = 42
var ok = true
var marker = 0xFF
Use an explicit type when the width, signedness, or hardware shape matters:
var marker: byte = 0xFF
var flags: logic[8] = 0b00001111
For public fields, function parameters, constants, and interfaces, prefer explicit types. They are part of the component contract.
Casts
The as operator converts a value at a specific point:
var word: logic[32] = this.GetWord()
var high: byte = (word[31:24]) as byte
var sum: int = high as int
Use casts when the conversion is part of the design intent. Parentheses are often worth the extra characters when slicing or combining operators.
Arithmetic Operators
Arithmetic operators work on numeric values:
var a: int = 10
var b: int = 3
var sum = a + b
var difference = a - b
var product = a * b
var quotient = a / b
var remainder = a % b
In hardware, arithmetic is not free. Multiplication, division, and wide additions can affect resource use and timing. Use them when they express the design, but remember that the compiler must lower them into hardware.
Comparison and Equality
Comparison operators return bool:
var isDigit = value >= 0x30 && value <= 0x39
var isEmpty = count == 0
var changed = previous != current
Use comparisons to turn signal or numeric values into control-flow conditions. When checking logic, compare it explicitly:
if (this.valid == 0b1)
{
this.Accept()
}
Logical Operators
Logical operators work on bool values:
var validLength: bool = length > 0
var validChecksum: bool = checksum == 0
var accept: bool = validLength && validChecksum
&& and || short-circuit. The right-hand side is evaluated only when needed. That makes them useful for guarded checks:
if (index < length && payload[index] == 0x00)
{
return true
}
Bitwise Operators
Bitwise operators work on integer, byte, and logic-style values:
var flags: byte = 0xF0
var lowNibble = flags & 0x0F
var highNibble = flags & 0xF0
Common bitwise operators are:
| Operator | Meaning | |
|---|---|---|
& |
bitwise AND | |
| ` | ` | bitwise OR |
^ |
bitwise XOR | |
~ |
bitwise NOT | |
<< |
shift left | |
>> |
shift right |
Use bitwise operators for masks, flags, protocol fields, and compact status values.
Assignment Operators
Assignment updates a variable, field, or output parameter:
count = count + 1
this.acceptedCount = this.acceptedCount + 1
Compound assignments are shorthand:
count += 1
flags &= 0x0F
Increment and decrement are useful for counters and loops:
index++
remaining--
Precedence and Parentheses
Operators have precedence rules, but readable Livt code should not require the reader to remember all of them. Use parentheses when expressions combine several kinds of operators:
var inRange = (value >= 0x30) && (value <= 0x39)
var masked = (flags & 0x0F) == 0x05
Parentheses are especially helpful around casts, slices, bit masks, and combined conditions.
Summary
Choose types for intent:
boolfor decisions.logicfor hardware signals and vectors.bytefor unsigned 8-bit data.intanduintfor arithmetic and counts.stringfor simulation text and encoded byte constants.clockandresetfor sequential process context.- Fixed-size arrays for statically allocated collections.
Operators let you calculate, compare, mask, shift, and assign values. The syntax is familiar, but every expression still becomes hardware or simulation behavior. When in doubt, make width, signedness, and intent explicit.