Language reference

Data Types & Operators

Primitive types, fixed-size arrays, literals, casts, and operators: the vocabulary for representing values in Livt.

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:

livt
var enabled: bool = true
var done: bool = false

if (enabled)
{
    done = true
}

Comparisons produce bool:

livt
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:

livt
var bit: logic = 0b1
var unknown: logic = 0bX

Common logic literals include:

livt
0b0
0b1
0bU
0bX
0bZ

logic[N] is an N-bit vector:

livt
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:

livt
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:

livt
var offset: int = -4
var index: int = 0

Use uint when negative values do not make sense:

livt
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:

livt
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:

livt
Simulation.Report("starting test")

A string can be encoded into bytes:

livt
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:

livt
var payload: byte[64]
var table: int[16]
var matrix: byte[2, 3]

Array literals initialize fixed-size arrays:

livt
var header: byte[4] = [0xDE, 0xAD, 0xBE, 0xEF]
var offsets: int[3] = [0, 14, 34]

Multi-dimensional arrays use nested literals:

livt
var matrix: byte[2, 3] = [
    [0x01, 0x02, 0x03],
    [0x04, 0x05, 0x06]
]

Indexing is zero-based:

livt
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:

livt
var flags: logic[4] = 0b1010

Use bracket-list literals when the type is an array of elements:

livt
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:

livt
var value = 42
var ok = true
var marker = 0xFF

Use an explicit type when the width, signedness, or hardware shape matters:

livt
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:

livt
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:

livt
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:

livt
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:

livt
if (this.valid == 0b1)
{
    this.Accept()
}

Logical Operators

Logical operators work on bool values:

livt
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:

livt
if (index < length && payload[index] == 0x00)
{
    return true
}

Bitwise Operators

Bitwise operators work on integer, byte, and logic-style values:

livt
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:

livt
count = count + 1
this.acceptedCount = this.acceptedCount + 1

Compound assignments are shorthand:

livt
count += 1
flags &= 0x0F

Increment and decrement are useful for counters and loops:

livt
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:

livt
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:

  • bool for decisions.
  • logic for hardware signals and vectors.
  • byte for unsigned 8-bit data.
  • int and uint for arithmetic and counts.
  • string for simulation text and encoded byte constants.
  • clock and reset for 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.