Multi-Component Designs

Calyx designs can define and instantiate other Calyx components that themselves encode complex control programs.

As an example, we'll build a Calyx design that uses a simple Calyx component to save a value in a register and use it in a different component.

We define a new component called identity that has an input port in and an output port out.

component identity(in: 32) -> (out: 32) {
  cells {
    r = std_reg(32);
  }
  wires {
    group save {
      r.in = in;
      r.write_en = 1'd1;
      save[done] = r.done;
    }

    // This component always outputs the current value in r
    out = r.out;
  }
  control {
    save;
  }
}

The following line defines a continuous assignment, i.e., an assignment that is always kept active, regardless of the component's control program being active.

    // This component always outputs the current value in r
    out = r.out;

By defining this continuous assignment, we can execute our component and later observe any relevant values.

Next, we can instantiate this component in any other Calyx component. The following Calyx program instantiates the id component and uses it to save a value and observe it.

component main() -> () {
  cells {
    // Instantiate the identity element
    id = identity();
    current_value = std_reg(32);
  }
  wires {
    group run_id {
      // We want to "save" the value 10 inside the identity group.
      id.in = 32'd10;
      // All components have a magic "go" and "done" port added to them.
      // Execute the component.
      id.go = 1'd1;
      run_id[done] = id.done;
    }
    group use_id {
      // We want to "observe" the current value saved in id.
      // The out port on the `id` component always shows the last saved
      // element. We don't need to set the `go` because we're not executing
      // and control.
      current_value.in = id.out;
      current_value.write_en = 1'd1;
      use_id[done] = current_value.done;
    }
  }
  control {
    seq { run_id; use_id; }
  }
}

Our first group executes the component by setting the go signal for the component to high and placing the value 10 on the input port. The second group simply saves the value on the output port. Importantly, we don't have to set the go signal of the component to high because we don't need to save a new value into it. The component executes the two groups in-order.

To see the output from running this component, run the command:

fud e examples/futil/multi-component.futil --to vcd