A Compiler Infrastructure for Accelerator Generators


Calyx is a compiler infrastructure for languages that target hardware accelerators. Calyx’s control language simplifies encoding of high-level semantics. The Calyx compiler automatically optimizes and lowers programs into synthesizable hardware designs. Calyx has been integrated with the LLVM CIRCT infrastructure and is available as a dialect within it.

If you’re interested in building on Calyx, please say hello and let’s chat!

Frontends

CIRCT »

CIRCT is an LLVM incubator project building an open-source hardware toolchain. You can try compiling the Calyx dialect in your web browser with Compiler Explorer!

Dahlia »

Dahlia is an imperative, loop-based programming language for generating hardware accelerators.

Systolic Array »

Systolic arrays are the class of architectures that power AI chips like Google's TPUs.

TVM »

TVM is a compiler for machine learning frameworks that can optimize and target kernels to several different backends.

Language

Components

Components are the basic building block of Calyx programs. A component has three sections: cells, wires, and control. An empty component does nothing.

component main() -> () {
  cells { }
  wires { }
  control { }
}

Cells

The cells section instantiates sub-components like adders and registers. These sub-components can be used in this component to define behaviour.

component main() -> () {
  cells {
    // A 32-bit register
    c = std_reg(32);
  }
  wires { }
  control { }
}

Assignments & Guards

The wires section specifies connections to cells. They are continuously active by default and work like non-blocking assignments in RTL.

Guarded assignments have a boolean expression that controls when the assignment should be active.

component main() -> () {
  cells {
    // A 1-bit register
    b = std_reg(1);
    // A 32-bit register
    c = std_reg(32);
  }
  wires {
    // write a 1-bit constant to b
    b.in = 1'd1;
    b.write_en = 1'd1;

    // write 1 when b.out == 1
    c.in = b.out ? 32'd1;
    // write 2 when b.out == 0
    c.in = !b.out ? 32'd2;
  }
  control { }
}

Groups

A group is a collection of assignments that are only active when the group is run from the control program. The write_c[done] port is a special port defined on groups that signify when the group has finished.

component main() -> () {
  cells {
    // A 32-bit register
    c = std_reg(32);
  }
  wires {
    group write_c {
      c.in = 32'd1;
      c.write_en = 1'd1;
      // group is done when reg is done
      write_c[done] = c.done;
    }
  }
  control { }
}

Control

The control language specifies the order to run groups. The language has the normal imperative control flow operators: seq, if, and while. It also has par to express parallel composition.

component main() -> () {
  cells {
    a = std_reg(32);
    b = std_reg(32);
  }
  wires {
    group write_a { ... }
    group read_a { ... }
    group write_b { ... }
  }
  control {
    seq {
      write_a;
      par {
        read_a;
        write_b;
      }
    }
  }
}

Authors

Calyx's development is led by Rachit Nigam, Griffin Berlstein, Chris Gyurgyik, Samuel Thomas, and Adrian Sampson. An ever growing set of excellent folks have contributed code and ideas to Calyx. If you're interested on using or contributing to Calyx, please let us know!