Modules
Modules are the primary building blocks of Virdant designs. A module defines a hardware block with ports, internal components, and behavioral statements.
Module Body
The body of a module definition is a sequence of statements that declare ports, components, instances, and control flow.
ModDefStmt :=
ModDefStmtComponent
| ModDefStmtDriver
| ModDefStmtInstance
| ModDefStmtSocket
| ModDefStmtMatch
| ModDefStmtUnused
The order of statements within a module body does not matter. Virdant processes the module body as a set of declarations, not as a sequence of imperative statements.
Ports
Ports are the interface between a module and the outside world. They are declared with a direction, a name, and a type.
ModDefStmtComponent :=
DocString Annotations "incoming" Ident ":" Type OnClause? ItBlock?
| DocString Annotations "outgoing" Ident ":" Type OnClause? ItBlock?
| DocString Annotations "outgoing" "wire" Ident ":" Type OnClause? ItBlock?
| DocString Annotations "outgoing" "reg" Ident ":" Type OnClause? ItBlock?
| DocString Annotations "wire" Ident ":" Type OnClause? ItBlock?
| DocString Annotations "reg" Ident ":" Type OnClause? ItBlock?
mod Passthrough {
incoming inp : Word[8]
outgoing out : Word[8]
out := inp
}
incoming ports receive values from outside the module. outgoing ports send values from the module to the outside.
Wires
A wire declaration creates an internal combinational signal.
wire sum : Word[8]
sum := a + b
A wire may optionally have an on clause and a block, making it a clocked wire. Wires may also be given an initial value through an it block.
wire w : Word[8] {
w := a + b
}
Registers
A reg declaration creates a sequential (stateful) element. Every register must have a clock associated with it via the on keyword.
reg counter : Word[8] on clock
counter <= counter + 1
Registers update their value on the rising edge of the associated clock. The value assigned to a register uses the <= assignment operator, indicating a non-blocking (clocked) assignment.
A register may have an associated block for additional statements:
reg counter : Word[8] on clock {
it <= when {
case reset => 0
else => it + 1
}
}
Inside the register’s block, the special identifier it refers to the register itself.
Assignments
Virdant provides three assignment operators for driving signals.
- := (combinational assignment)
Drives a combinational signal. The target is continuously driven by the expression.
out := a + b // combinational: out always equals a + b
- <= (sequential assignment)
Drives a register. The target updates on the next clock edge.
reg r : Word[8] on clock r <= a + b // sequential: r updates on next clock edge
- :=: (bidirectional connection)
Connects two signals bidirectionally. Both sides drive each other.
memory.mem :=: core.mem
Module Instances
Module instances allow you to compose larger designs from smaller modules.
ModDefStmtInstance :=
DocString Annotations "mod" Ident "of" Ofness ItBlock?
mod ha1 of HalfAdder
ha1.a := a
ha1.b := b
sum := ha1.sum
carry := ha1.carry
The mod keyword introduces an instance, followed by a name for the instance, the of keyword, and the type (module name) being instantiated.
Ports on the instance are accessed using dot notation: <instance>.<port>.
An instance may optionally have an it block for grouping connections:
mod ha1 of HalfAdder {
it.a := a
it.b := b
}
sum := ha1.sum
carry := ha1.carry
Socket Instances
Sockets provide a way to group related ports and connect them as a unit.
ModDefStmtSocket :=
DocString Annotations "client" "socket" Ident "of" Ofness ItBlock?
| DocString Annotations "server" "socket" Ident "of" Ofness ItBlock?
client socket mem of Mem {
it.addr := 0
unused it.data
}
A socket instance must specify its role: either client or server. The role determines the direction of the individual ports within the socket.
For more details on sockets, see Sockets.
Match Statements
Match statements enable pattern-based conditional logic in module bodies.
ModDefStmtMatch :=
"match" Expr "{" ModDefStmtMatchArm* "}"
ModDefStmtMatchArm :=
"case" Pat "=>" ModDefStmtBlock
| "else" "=>" ModDefStmtBlock
match state {
case #Idle => {
r <= 0
out := ready
}
case #Active => {
r <= r + 1
out := busy
}
else => {
r <= r
out := idle
}
}
The else arm catches any value not matched by the preceding case arms. If no else arm is present, the match must be exhaustive.
Unused Declarations
The unused keyword indicates that a signal is intentionally not used. This suppresses compiler warnings about unused signals.
ModDefStmtUnused :=
"unused" Path
unused mem.data
unused takes a path that may refer to a component, a port, or a field of an instance.
The it Identifier
Inside certain blocks (such as register blocks and instance blocks), the special identifier it refers to the enclosing declaration.
For a register it refers to the register itself. For an instance it refers to the instance.
reg counter : Word[8] on clock {
it <= when {
case reset => 0
else => it + 1
}
}
mod fifo of Fifo {
it.clock := clock
it.data := data
}
The it identifier is only available within the block of a declaration that provides it.
On Clause
The on clause associates a register with a clock signal.
reg r : Word[8] on clock
Every register must have exactly one on clause. The clock signal referenced in the on clause must be of type Clock.
The register latches its input value on the rising edge of the named clock. All registers driven by the same clock form a single clock domain.
Driver Blocks
Driver blocks are groups additional module body statements under a declaration. The keyword it inside the block refers to the enclosing declaration.
reg r : Word[8] on clock {
it <= 0
out := it
}
Blocks are optional. When present, they provide a scoped grouping for related assignments and connections.