Annotations

Annotations are metadata tags that may be attached to declarations. They are used to attach extra information to parts of a design, such as pin assignments, synthesis attributes, and documentation hints.

Annotations are not part of Virdant’s semantic model — they are preserved in the AST and may be inspected by tools, backends, and documentation generators.

Syntax

An annotation starts with @ followed by a name, optionally followed by a parenthesized argument list.

Annotation :=
      "@" Ident "(" ArgList? ")"
    | "@" Ident

ArgList := Expr ("," Expr)* ","?

The argument list may be empty, and each argument is a literal:

  • @name or @name() — a bare annotation with no arguments. Example: @keep, @keep()

  • @name(123) — an annotation with a natural number argument. Example: @pin(35)

  • @name("string") — an annotation with a string argument. Example: @pin("E3")

  • @name(true) — an annotation with a boolean argument (true or false). Example: @keep(true)

  • @name(a, b, ...) — an annotation with multiple comma-separated arguments. Example: @parameter("WIDTH", 8) (see Parameter Annotations below)

Multiple annotations may be stacked on a single declaration:

@fpga("ice40")
@part("up5k-sg48")
mod Top {
    // ...
}

Where Annotations Appear

Annotations may appear after the documentation comment (if any) and before the keyword of any annotatable declaration.

Annotatable declarations include:

  • Module definitions

  • Platform definitions and their ports (see Platforms)

  • Struct type definitions and their fields

  • Union type definitions and their variants (with params)

  • Enum type definitions and their variants

  • Builtin type definitions

  • Socket definitions and their channels (cosi / soci)

  • Component declarations (incoming, outgoing, wire, reg)

  • Module instances (mod … of …)

  • Socket instances (client socket / server socket)

  • Struct type fields

  • Union variant parameters (Param)

The grammar shows Annotations as a non-terminal in all of these productions:

Annotations := Annotation*

ModDef := DocString? Annotations "pub"? "ext"? "mod" Ident "{" ...
PlatformDef := DocString? Annotations "pub"? "platform" Ident "{" ...
StructDef := DocString? Annotations "pub"? "struct" "type" Ident "{" ...
StructDefStmt := DocString? Annotations Ident ":" Type
UnionDef := DocString? Annotations "pub"? "union" "type" Ident "{" ...
UnionDefStmt := DocString? Annotations Ident ParamList?
EnumDef := DocString? Annotations "pub"? "enum" "type" Ident "{" ...
EnumDefStmt := DocString? Annotations Ident "=" Expr
BuiltinDef := DocString? Annotations "pub"? "builtin" "type" Ident ...
SocketDef := DocString? Annotations "pub"? "socket" Ident "{" ...
SocketDefStmt := DocString? Annotations ("cosi"|"soci") Ident ":" Type
Param := DocString? Annotations Ident ":" Type

ModDefStmtComponent :=
    DocString? Annotations ("incoming"|"outgoing"|"wire"|"reg") ...
ModDefStmtInstance :=
    DocString? Annotations "mod" Ident "of" ...
ModDefStmtSocket :=
    DocString? Annotations ("client"|"server") "socket" ...

Parameter Annotations

Most annotations are pure metadata, but the @parameter annotation is interpreted by the Verilog backend: it passes Verilog module parameters through to instances of ext mod (black-box) modules. This is useful for width- or mode-configurable cells, such as vendor library blocks whose data width or operating mode is selected by a Verilog parameter.

Parameters are declared on the ext mod definition. Each @parameter annotation takes two arguments: the parameter name (a string) and an optional default value:

@parameter("WIDTH", 8)
@parameter("INIT", 0)
@parameter("MODE", "SYNC")
ext mod Cell {
    incoming clk : Clock
    outgoing data : Word[16]
}

Any declared parameter may be overridden per instance by attaching @parameter to the mod … of … statement. Instance-supplied values take precedence over the declared defaults, and parameter names supplied by an instance but not declared on the target module are appended to the instance’s parameter list as well. Instances that supply no parameters use the declared defaults:

pub mod Top {
    incoming clk : Clock
    outgoing a : Word[16]
    outgoing b : Word[16]

    @parameter("WIDTH", 4)
    mod u0 of Cell {
        it.clk := clk
        a := it.data
    }

    mod u1 of Cell {
        it.clk := clk
        b := it.data
    }
}

Argument values are rendered to Verilog as follows:

  • Natural numbers render in decimal: @parameter("WIDTH", 4) produces .WIDTH(4).

  • Strings render quoted: @parameter("MODE", "SYNC") produces .MODE("SYNC").

  • Booleans render as single-bit Verilog literals: @parameter("EXTRA", true) produces .EXTRA(1'b1) (false produces 1'b0).

The Verilog backend emits each instance with its merged parameter list — declared parameters first (instance override or declared default), then instance-supplied extras — as a Verilog parameter assignment. For the example above, u0 and u1 are emitted as:

Cell #(
    .WIDTH(4),
    .INIT (0),
    .MODE ("SYNC"),
) u0(
    ...
);

Cell #(
    .WIDTH(8),
    .INIT (0),
    .MODE ("SYNC"),
) u1(
    ...
);

Backends and tools may use these to extract metadata during code generation or documentation processing.