Grammar

This chapter describes the lexical elements of Virdant. These are the low-level building blocks from which all Virdant programs are constructed.

Tokenization

Virdant source files must be ASCII encoded, except for comments.

Whitespace

Whitespace in Virdant consists of spaces and newlines. Whitespace is used to separate tokens but is otherwise insignificant.

Virdant uses curly braces { and } to delimit blocks. Indentation is not semantically meaningful. Tabs are illegal characters. Use of 4 spaces for indentation is highly encouraged.

Comments

Virdant supports Line Comments and Docstrings

Line Comments

A line comment begins with // and extends to the end of the line. Line comments are ignored by the compiler and do not appear in generated documentation.

// This is a line comment

There are no multi-line comments.

Docstrings

Docstrings are comments that the compiler associates with declarations. They are used to generate documentation for modules, types, sockets, and functions.

A documentation comment begins with /// and extends to the end of the line. Multiple consecutive /// lines are combined into a single documentation string.

/// A simple passthrough module.
/// Takes an 8-bit value and immediately sends it back out.
mod Passthrough {
    incoming inp : Word[8]
    outgoing out : Word[8]

    out := inp
}

Documentation comments may appear before any top-level declaration: module definitions, struct types, union types, enum types, socket definitions, and function definitions.

They may also appear on module body statements such as component declarations, instance declarations, and port declarations.

The compiler collects these comments and makes them available to documentation generation tools like Sphinx.

Package Docstrings

Package-level documentation begins with //! and extends to the end of the line. This form of documentation comment is placed at the top of a package file and describes the purpose of the entire package.

//! This package contains the UART transmitter and receiver modules.

mod UartTx {
    // ...
}

Multiple consecutive //! lines are combined into a single package-level documentation string.

Identifiers

Identifiers in Virdant name things: modules, types, ports, wires, registers, instances, sockets, type variants, and functions.

Identifiers must begin with an uppercase or lowercase letter (A-Z or a-z) or an underscore (_). The remainder may contain letters, digits (0-9), and underscores.

ident: (a-z | A-Z | "_") (a-z | A-Z | 0-9 | "_")*

The following are valid identifiers:

  • counter

  • Clock

  • _internal

  • RgbColor

  • opcode3

Identifiers are case-sensitive: myReg and myreg are different names.

Keywords

The following words are reserved as keywords and may not be used as identifiers:

builtin  case     client   cosi     dontcare   dyn
else     enum     export   ext      false    import
incoming it       match    mod      of         on
outgoing reg      server   soci     socket     struct
true     type     union    unused   when       width
wire

Literals

Virdant provides several kinds of literal values.

Bit Literals

Bit literals represent the two possible values of the Bit type:

bit-lit ::=  "true" | "false"
wire a : Bit
a := true

wire b : Bit
b := false

Integer Literals

Integer literals represent numeric constants of type Word[n]. The width n is specified by appending w followed by the bit width:

word-lit ::=  Nat ( "w" Nat )?
nat      ::=  digit+

A literal without an explicit width requires type inference from context. When the width cannot be inferred, it is a compile-time error.

wire explicit : Word[4]
explicit := 0w4            // 4-bit value 0

wire inferred : Word[4]
inferred := 0              // Width inferred as 4

Integer literals may be written in several bases:

Decimal

42w8, 100_000

Binary

0b1010, 0b1010w4, 0b00_101_11

Hexadecimal

0xfe, 0xfew8, 0xcafe_babe

Underscores may be used as visual separators in integer literals. They are ignored by the compiler.

Warning

A literal written without the w annotation requires a surrounding context that determines the type. For example, if assigned to a Word[8] component, the literal 42 is inferred as 42w8.

If the context cannot determine the width, the compiler will report an error.

Dontcare

The dontcare keyword represents an unknown or undefined value. It is used when a signal’s value is irrelevant in a given context.

wire bit_x : Bit
bit_x := dontcare

wire word_x : Word[8]
word_x := dontcare

Assigning dontcare to a component tells the compiler and synthesis tools that the value may be anything. This can lead to smaller, more efficient hardware because the tools are free to choose the most convenient value for the don’t-care condition.

Hole Expressions

Holes are expressions which act as a placeholder for incomplete Virdant designs. They are functionally equivalent to dontcare, but they produce a warning which acts as a signal to the developer that they need to fill in the actual value later.

In Virdant, holes are written as ?.

reg state : Word[8] on clock {
    it <= ?
}

Holes typecheck as whatever type they are needed.