Language Constructs
Packages
Packages group related top-level declarations under a common namespace.
package VGA {
const SCREEN_WIDTH = 640
const SCREEN_HEIGHT = 480
module SyncPulses(
clk: clock,
) -> (
hsync: bool,
vsync: bool,
row: uint[clog2!(SCREEN_HEIGHT)],
col: uint[clog2!(SCREEN_WIDTH)],
) {
// ...
hsync = 1'b0
vsync = 1'b0
row = 9'd0
col = 10'd0
}
}
module Top(
clk: clock,
) -> (
hsync: bool,
vsync: bool,
vga_color: (r: u8, g: u8, b: u8),
) {
let pulses = VGA::SyncPulses(clk, hsync, vsync)
vga_color = (
r: pulses.row[7:0],
g: pulses.col[7:0],
b: 8'd127,
)
}
-
Members declared within a package are accessed using the
::operator. -
Imported files are implicitly wrapped in a package with the same name as the file.
Constant Declarations
Constants are named fixed values known at compile-time.
const BAUD_RATE = 115200
const CLOCK_FREQ = 100 * 1_000_000 // 100 MHz
const CYCLES_PER_BIT = CLOCK_FREQ / BAUD_RATE
module Top() -> () {}
Type Alias Declarations
Type aliases are used to give a name to a commonly used type.
type U8 = u8
type RGB = (r: U8, g: U8, b: U8)
module Top() -> () {}
Parameterised type aliases
Type aliases can be parameterised by providing a list of type parameters.
type Triplet[T] = (T, T, T)
module Top() -> () {
let triplet: Triplet[bool] = (true, false, false)
}
Module Declarations
-
Modules enable hierarchical design by encapsulating reusable functionality.
-
A module is defined by a name, an optional list of parameters, and a list of input and output ports.
module FullAdder(
a: bool,
b: bool,
carry_in: bool,
) -> (
sum: bool,
carry_out: bool,
) {
let xor1 = a xor b
sum = carry_in xor xor1
carry_out = (carry_in and xor1) or (a and b)
}
module Top() -> () {}
Parameterised Modules
Modules can be parameterised by specifying a list of parameters surrounded by [ and ] after the module name.
module Adder[N: Nat](
a: uint[N],
b: uint[N],
carry_in: bool,
) -> (
sum: uint[N],
carry_out: bool,
) {
let carry_chain: [N + 1]bool
carry_chain[0] = carry_in
let bits: [N]bool
for i in 0..<N {
FullAdder(
a: a[i],
b: b[i],
carry_in: carry_chain[i],
carry_out: carry_chain[i + 1],
sum: bits[i],
)
}
carry_out = carry_chain[N]
sum = uint!(bits)
}
module Top() -> () {}
Module instances
-
Module instances are first-class values that can be assigned to variables.
-
If a module is parameterised, all the parameters must be specified when creating an instance.
-
Parameter names can be omitted if the parameters are specified in the same order as the declaration.
module Adder[N: Nat](
a: uint[N],
b: uint[N],
carry_in: bool,
) -> (
sum: uint[N],
carry_out: bool,
) {
let total: uint[N + 1] = a + b + carry_in
sum = total[N - 1 : 0]
carry_out = total[N]
}
module Top(clk: clock) -> () {
let counter = Reg[u32](clk)
counter.d = counter.q + 1
let adder = Adder[32]( // or Adder[N: 32](..)
a: counter.q,
b: 32'1,
carry_in: 1'0,
)
}
-
A module instance need not assign all ports as the module's ports are accessible as fields of the instance using the
.operator. -
Ports whose values are local variables with the same name can omit the right-hand side of the assignment.
All three Reg instances in the following example are equivalent.
module Top(clk: clock, rst: bool) -> () {
let reg1 = Reg[u32](clk: clk, rst: rst)
let reg2 = Reg[u32](clk, rst)
let reg3 = Reg[u32]()
reg3.clk = clk
reg3.rst = rst
}
Let Bindings
- A let binding introduces a named value in the current scope.
- Let bindings must be defined inside a module declaration.
- The type of the binding is inferred from the right-hand side of the assignment if present otherwise, the type must be explicitly specified.
module Test() -> () {
let message1: [3]u8
message1 = "Yo!"
let message2 = "Hi!"
let message3: [3]u8 = "Hey"
}
Assignments
If a binding is assigned multiple times, only the last assignment is used.
module Test() -> () {
let a = true // this first assignment is ignored
a = false
}
Block Expressions
Block expressions group multiple statements in a new lexical scope and optionally return a value:
module Test(clk: clock) -> () {
let result = {
let a = 5
let b = 10
(a + b) // Last expression becomes the block's value
}
assert!(result == 15)
}