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 produces1'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.