Builder Library Reference

Top-Level Program Structure

Here's the general structure of a program that uses the builder to generate Calyx code.

# import the builder library
import calyx.builder as cb


# define `second_comp`
def add_second_comp(prog):
    # `second_comp` definition here


# method for defining `my_component` and adding it to a program
def add_my_component(prog, second_comp): 
    # add the component to the program
    my_component = prog.component("my_component")

    # Adding an instance of `second_comp` as a cell of `my_component`
    my_second_comp = my_component.cell("my_second_comp", second_comp)

    # adding a register cell (or other cells) to the component 
    my_reg = my_component.reg("my_reg", 32)

    # define a `my_component` group
    with my_component.group("my_group") as my_group:
      # assignments here 
      my_reg.write_en = 1

    # add the group to `my_component`'s control program
    my_component.control += my_group


# assemble the program
def build():
    prog = cb.Builder()
    my_second_comp = add_second_comp(prog)
    add_my_component(prog, my_second_comp)

    # return the generated program
    return prog.program


# emit the program
if __name__ == "__main__":
    build().emit()

Components

Defining Components

To define a component, call the Builder().component() method.

prog = cb.Builder()
prog.component("my_component")

Retrieving Components

To reference a component without an existing handle to it, use the Builder().get_component() method.

prog = cb.Builder()
prog.component("my_component")
# a few lines later 
my_component = prog.get_component("my_component")

Defining Component Inputs and Outputs

Components can be given input and output ports. Just specify the name of the port and its size.

my_component.input("my_input", 32)
my_component.output("my_output", 32)

To access the input and output ports of a component within the definition of a component, use the syntax my_component.this().port.

def add_my_component(prog):
    my_component = prog.component("my_component")
    my_component.output("my_output", 32)

    with my_component.group("my_group"):
        my_component.this().my_output = const(32, 1)

Note that it's possible to create a handle to input and output ports.

Multi-Component Designs

Calyx supports multi-component designs. The top-level example demonstrates how to construct multi-component designs using the library.

Defining Common Calyx Cells

Here's a snippet of code that adds a few common kinds of cells to a component:

my_component = prog.component("my_component")

# Registers: reg(name, bitwidth)
my_component.reg("my_reg", 32)

# Constants: const(name, bitwidth, value)
my_component.const("my_reg", 32, 42)

# Adders/Subtractors: [add|sub](name, size, signed=False)
# a signed adder 
my_component.add("my_add", 32, signed=True)
# a subtractor
my_component.sub("my_sub", 32)


# Comparators: [gt|lt|eq|neq|ge|le](name, size, signed=False)
my_component.gt("my_gt", 32)
# a signed le comparison
my_component.lt("my_lt", 32, signed=True)
my_component.eq("my_eq", 32)
my_component.neq("my_neq", 32)
my_component.ge("my_ge", 32)
my_component.le("my_le", 32)

# 1-D memory: 
# mem_d1(name, bitwidth, len, idx_size, is_external=False, is_ref=False)
my_component.mem_d1("my_mem", 32, 4, 32)
# An external memory
my_component.mem_d1("my_mem", 32, 4, 32, is_external=True)
# A memory by reference
my_component.mem_d1("my_mem", 32, 4, 32, is_ref=True)

If you're curious, you can read more about external memories or memories by reference.

Retrieving Cells

In order to reference a cell without a handle, use the Builder().get_cell() method.

# defining a register cell
my_component.reg("my_reg", 32)

# a few lines later 
my_reg = prog.get_cell("my_reg")

Wires

Guarded Assignments

Guarded assignments in the builder are syntactically similar to those in Calyx.

my_component = prog.component("my_component")

my_add = comp.add("my_add", 32)
my_reg = comp.reg("my_reg", 32)

with my_component.group("my_group"):
    # unconditional assignments
    add.left = const(32, 1)
    add.right = const(32, 41)
    my_reg.write_en = 1

    # a guarded assignment using @
    # in Calyx, this line would be:
    # my_reg.in = (add.left < add.right) ? add.out;
    my_reg.in = (add.left < add.right) @ add.out

Groups

Defining Groups

Groups are defined using the group() method for a component. To make a handle for a group, use the as syntax. Group handles are necessary in order to set done ports for groups.

It's possible to define a static delay for a group using the optional static_delay argument.

my_component = prog.component("my_component")

# a group definition + handle for the group
with my_component.group("my_group") as my_group:
    # assignments here

# a group with a static delay
with my_component.group("my_static_group", static_delay=1): 

Defining Combinational Groups

Combinational groups are defined similarly to groups, except with the comb_group method.

my_component = prog.component("my_component")

with my_component.comb_group("my_comb_group"):
    # assignments here

Retrieving Groups

If a group doesn't have a handle, it can be retrieved later with the Builder().get_group() method. It's possible to retrieve combinational groups as well as regular groups with this method.

prog = cb.Builder()
my_component = prog.component("my_component")

with my_component.group("my_group"):
    # group definition here

# a few lines later
my_group = prog.get_group("my_group")

with my_component.comb_group("my_comb_group"):
    # comb group definition here

my_comb_group = prog.get_group("my_comb_group")

Continuous Assignments

Continuous assignments are generated by using the syntax with comp.continuous.

my_component = prog.component("my_component")

my_output = my_component.output("my_output", 32)
my_reg = comp.reg("my_reg", 32)

with my_component.continuous:
    my_component.this().my_output = my_reg.out

Control Operators and Programs

A component's control program is defined by augmenting the list my_component.control. Control programs are constructed with control operators.

Group Enables

To enable a group, include it in a component's control program.

my_component.control += my_group

# using `get_group`
my_component.control += my_component.get_group("my_group")

seq

Control statements are sequenced in the order that they appear in a component's control program, represented by a Python list. Let's say we want to sequence the control statements A, B, and C.

my_component.control += [A, B, C]

par

For parallel compositions of control programs, use the par() function. Here's how to compose control programs A and B in parallel, and then sequence their composition with the control program C.

my_component.control += [par(A, B), C]

if

See the language reference for if.

# `if_(port, cond, body, else_body=None)`
my_if = if_(my_port, my_comb_group, my_true_group)

# with a nested if
my_other_if = if_(my_port, my_if)

# with a comb group to compute the value of my_port
my_if_comb = if_(my_port, my_comb_group, my_true_group)

# with an else body
my_if_else = if_(my_port, my_comb_group, my_true_group, my_false_group)

my_component.control += [my_if, my_other_if, my_if_comb, my_if_else]

while

See the language reference for while.

# while_(port, cond, body)
my_while = while_(my_port, my_body)

# with a comb group to compute the value of my_port
my_while = while_(my_port, my_comb_group, my_body)

invoke

See the language reference for invoke.

# invoke(cell, **kwargs)
my_invoke = invoke(my_cell, in_arg1=my_cell_arg1_reg.out, in_arg2=my_cell_arg2_reg.out)

Miscellaneous Tips + Tricks

Creating Handles

Handles allow components, groups, cells, control operators, and input/output ports to be referenced after their definition.

def add_my_component(prog):
    # Creating a handle to a component
    my_component = prog.component("my_component")

    # using the component handle
    my_component.reg("my_reg", 32)

    # Creating a handle to an input/output port
    my_input = component.input("my_input", 32)
    my_output = component.output("my_output", 32)

    # Creating a handle to a cell
    my_second_comp = my_component.cell("my_cell", my_second_comp)

    # Creating a handle to a group
    with my_component.group("my_group") as my_group:
        # assignments

    # Creating a handle to a control operator
    my_if = if_(my_second_comp.out_port, body=my_group)

    # using the group handle + control operator handle
    my_component.control += [my_group, my_if]

Importing Calyx Libraries

To generate imports for Calyx libraries, use the Builder.import_() method.

prog = cb.Builder()
prog.import_("primitives/binary_operators.futil")

Explictly Stating Widths with const

Usually, the builder library can automatically infer the width of a port. In cases where it can't, use the const(width, value) expression:

my_cell.my_port = const(32, 1)

High and Low Signals

The calyx.builder.HI or calyx.builder.LO are shorthand for one-bit high and low signals.

"""A one-bit low signal"""
LO = const(1, 0)
"""A one-bit high signal"""
HI = const(1, 1)