This chapter introduces the fundamental data types and operators used in Livt. Understanding these concepts is essential for writing effective code and manipulating data efficiently.
The primitive types are intentionally similar to those in many other programming languages, because Livt aims to stay simple. This also makes it easier to migrate existing software code into the FPGA domain.
Data Types
Boolean (bool)
Boolean is a type provided by the base library and Livt provides the bool keyword
to declare fields, parameters, and variables. A boolean represents exactly two values, and Livt provides the
literals true and false.
Booleans are used for decisions in control flow (for example in if and while) and
they are also the result of comparisons and equality checks.
It is important to distinguish bool from logic:
boolis strictly two-valued (true/false) and is the right choice for algorithmic conditions.logicis multi-valued and can representU,X,Z, … which is useful for modeling digital signals and buses.
In other words: use logic for signals, use bool for decisions.
var value: Boolean // Declaration using the Boolean type name
var value: bool // Declaration using the bool keyword
true // a literal representing true
false // a literal representing false
If a bool variable is declared without an initializer, it defaults to false.
Typical boolean operators are available:
!true // false
true && false // false
true || false // true
var enabled: bool = true
if (enabled)
{
// ...
}
var a: int = 3
var b: int = 5
var is_smaller: bool = a < b
Byte (byte)
Byte is a type provided by the base library and Livt provides the byte keyword to
declare fields, parameters, or variables. Byte represents a signed 8-bit integer
value ranging from -128 to 127.
Byte inherits from Int8, so both can be used interchangeably. Use byte
when you mean "a byte" and want that intent to be obvious; use Int8 when you want to be explicit
about size and signedness.
var value: Byte // base library type name
var value: Int8 // explicit signed 8-bit integer
var value: byte // primitive keyword
0
13
127 // max
-128 // min
Unsigned Byte (ubyte)
UnsignedByte represents an unsigned 8-bit integer ranging from 0 to 255.
Livt provides the ubyte keyword as the primitive spelling for this type.
UnsignedByte inherits from UInt8, so both names can be used interchangeably.
var value: UnsignedByte // base library type name
var value: UInt8 // explicit unsigned 8-bit integer
var value: ubyte // primitive keyword
0 // min
13
255 // max
Integer (int)
Integer represents a signed 32-bit integer ranging from -2,147,483,648 to
2,147,483,647. Livt provides the int keyword as the primitive spelling.
Integer inherits from Int32. The base library also provides Int8,
Int16, and Int64 for other widths.
var value: Integer // base library type name
var value: Int32 // explicit signed 32-bit integer
var value: int // primitive keyword
0
13
2_147_483_647 // max
-2_147_483_648 // min
Unsigned Integer (uint)
UnsignedInteger represents an unsigned 32-bit integer ranging from 0 to
4,294,967,295. Livt provides the uint keyword as the primitive spelling.
UnsignedInteger inherits from UInt32. The base library also provides
UInt8, UInt16, and UInt64.
var value: UnsignedInteger // base library type name
var value: UInt32 // explicit unsigned 32-bit integer
var value: uint // primitive keyword
0
13
4_294_967_295 // max
Logic (logic)
Logic is the fundamental signal type in Livt, used for parameters, fields, and variables.
Unlike bool, logic is a multi-valued logic type, similar to logic types
in HDL languages. This is useful to model uninitialized signals, unknown values, or tri-stated buses during simulation.
The following logic literals are supported:
0b0 // logic 0
0b1 // logic 1
0bU // uninitialized
0bX // unknown
0bZ // high impedance (tri-state)
0bL // weak 0
0bH // weak 1
Logic values can also be used in fixed-size arrays using the same binary literal style:
var a: Logic
var b: logic
var c: logic[4] = 0b1010
Clock (clock)
Clock is a special type used to represent clock signals in hardware designs. Livt requires
clocks to be declared explicitly so that the compiler can reason about clock domains and help prevent common
errors such as metastability issues with asynchronous clock crossings.
var my_clock: Clock // base library type name
var my_clock: clock // primitive keyword (preferred)
Clocks are typically passed into a component via its constructor and bound to its context:
component MyDesign
{
new(clk: clock)
{
this.context.clk = clk
}
}
Reset (reset)
Like clock, reset is a special type used to represent reset signals. Livt requires
resets to be declared explicitly so that the compiler can reason about reset domains and help prevent
synchronization and metastability errors.
var my_reset: Reset // base library type name
var my_reset: reset // primitive keyword (preferred)
component MyDesign
{
new(rst: reset)
{
this.context.rst = rst
}
}
String (string)
String is a type provided by the base library for handling text data. String literals are
written using double quotes. Strings in Livt are primarily used during simulation, testbenches, and debugging.
var greeting: String // base library type name
var greeting: string // primitive keyword (preferred)
var message = "Hello, World!"
The base library provides several useful string operations through the Livt.String namespace:
- Concatenation, slicing, searching — basic string manipulation operations
- Format / interpolation — formatted string generation, e.g.
"Value: {x}" - Conversion functions —
ToString,Parse,TryParsefor converting between strings and numeric types - Comparison operations —
Equals,Compare,StartsWith,EndsWith
A common use case is converting strings to byte arrays for transmission over communication interfaces.
The Encode() method converts a string to its ASCII byte representation, and Decode()
reverses this:
var message = "Hello, World!"
var bytes = message.Encode() // Convert to byte array
var decoded_message = bytes.Decode()
Fixed-Size Arrays
Livt supports fixed-size arrays for all primitive types. Arrays provide a way to group multiple values of the same type together, which is essential for hardware designs that work with buses, vectors, or collections of signals.
Array types are declared using square brackets with the size specified at compile time:
var data: logic[8] // 8-element logic array (1-dimensional)
var matrix: int[4, 4] // 4×4 integer array (2-dimensional)
var cube: bool[2, 3, 4] // 2×3×4 boolean array (3-dimensional)
Arrays can be initialized using literals:
var nibble: logic[4] = 0b1010 // Binary literal
var bytes: ubyte[3] = [0x12, 0x34, 0x56] // Explicit values
var flags: bool[4] = [true, false, true, false]
Individual elements are accessed using zero-based indexing:
var data: logic[8] = 0b11001010
var bit3 = data[3] // Access the 4th element (zero-indexed)
data[0] = 0b1 // Modify the first element
Multi-dimensional arrays use comma-separated indices:
var matrix: int[3, 3]
matrix[0, 0] = 42
matrix[1, 2] = matrix[0, 0] + 10
Important: Livt does not support dynamic arrays (resizable at runtime). All array sizes must be known at compile time. This constraint is essential for hardware synthesis, as FPGA resources are allocated statically.
Operators
Livt provides a comprehensive set of operators for working with data. The operator syntax is intentionally similar to C, Rust, and C# to make the language familiar to developers coming from software backgrounds.
Arithmetic Operators
Livt supports the standard arithmetic operators for numeric types:
var a: int = 10
var b: int = 3
var sum = a + b // 13 (addition)
var diff = a - b // 7 (subtraction)
var product = a * b // 30 (multiplication)
var quotient = a / b // 3 (integer division)
var remainder = a % b // 1 (modulo)
The unary minus operator negates a number. All arithmetic operators work exclusively on numeric types — attempting to apply them to non-numeric types results in a compile-time error.
var x: int = 42
var y = -x // -42
var invalid = "text" + 5 // Error: cannot add string and int
Comparison and Equality
Comparison operators evaluate two values and return a boolean result:
var a: int = 10
var b: int = 20
a < b // true (less than)
a <= b // true (less than or equal)
a > b // false (greater than)
a >= b // false (greater than or equal)
Equality operators work on any type:
1 == 2 // false
1 != 2 // true
0b1010 == 0b1010 // true
"hello" == "hello" // true
"hello" != "world" // true
314 == "pi" // false
Livt supports implicit conversions in limited, well-defined cases. For instance, comparing an int
with a byte will promote the byte to int before comparison.
Logical Operators
Logical operators combine boolean values and are essential for control flow:
var a: bool = true
var b: bool = false
a && b // false (logical AND)
a || b // true (logical OR)
!a // false (logical NOT)
These operators short-circuit: && stops evaluating if the left side is
false, and || stops if the left side is true. This is useful for safe
conditional checks:
var index: int = 5
var data: logic[8]
if (index < 8 && data[index] == 0b1)
{
// Safe: data[index] is only accessed if index is within bounds
}
Bit Operators
Bit operators perform bitwise operations on integer and logic types:
var a: logic[8] = 0b11001010
var b: logic[8] = 0b10101100
a | b // 0b11101110 (bitwise OR)
a & b // 0b10001000 (bitwise AND)
a ^ b // 0b01100110 (bitwise XOR)
~a // 0b00110101 (bitwise NOT/complement)
Shift operators move bits left or right:
var x: int = 8
x << 2 // 32 (shift left by 2 bits)
x >> 1 // 4 (shift right by 1 bit)
Compound Assignment
Compound assignment operators combine an arithmetic or bitwise operation with assignment:
var x: int = 10
x += 5 // x = x + 5 (x is now 15)
x -= 3 // x = x - 3 (x is now 12)
x *= 2 // x = x * 2 (x is now 24)
x /= 4 // x = x / 4 (x is now 6)
x %= 4 // x = x % 4 (x is now 2)
var flags: logic[8] = 0b00001111
flags |= 0b11110000 // flags = flags | 0b11110000
flags &= 0b10101010 // flags = flags & 0b10101010
flags ^= 0b01010101 // flags = flags ^ 0b01010101
Unary Operators
In addition to negation (-) and logical NOT (!), Livt supports increment and
decrement operators, which are particularly useful in loops and state machines:
var counter: int = 0
counter++ // Increment by 1 (counter is now 1)
counter-- // Decrement by 1 (counter is now 0)
for (var i: int = 0; i < 10; i++)
{
// Loop body
}
Precedence and Grouping
Operators have a defined precedence that determines the order of evaluation, from highest to lowest:
- Unary:
!,~,-,++,-- - Multiplicative:
*,/,% - Additive:
+,- - Shift:
<<,>> - Relational:
<,<=,>,>= - Equality:
==,!= - Bitwise AND:
& - Bitwise XOR:
^ - Bitwise OR:
| - Logical AND:
&& - Logical OR:
|| - Assignment:
=,+=,-=, etc.
Use parentheses to explicitly control evaluation order or improve readability:
var result = (a + b) * c // Add first, then multiply
var flag = (x > 0) && (y < 10) // Clear precedence
When in doubt, use parentheses. They make the code more explicit and easier to understand, especially for complex expressions.
Summary
This chapter introduced the fundamental data types and operators that form the foundation of Livt programming:
bool— two-valued boolean for algorithmic conditions and control flow.byte/ubyte— signed and unsigned 8-bit integers.int/uint— signed and unsigned 32-bit integers; the base library provides other widths (Int8,Int16,Int64, etc.).logic— multi-valued signal type for hardware design, supportingU,X,Z, and other values beyond binary.clock/reset— explicit timing control types that allow the compiler to reason about clock and reset domains.string— text handling, primarily for simulation and debugging.
Fixed-size arrays group values together, with all dimensions known at compile time to support hardware synthesis.
Livt's operator set — arithmetic, comparison, equality, logical, bitwise, compound assignment, and unary — follows familiar conventions from C-family languages, making it easy to write expressive code while maintaining the precision required for hardware design.
With these building blocks in place, you're ready to explore control flow structures in the next chapter, where you'll learn how to make decisions and create loops in your Livt programs.