CIRCT

Circuit IR Compilers and Tools

'handshake' Dialect

This document also explains in a high-level manner how different components are organized, the principles behind them and the conventions we followed. The document assume that you have basic understanding of asynchronous digital circuits at the behavioral level abstraction.

Principle 

Handshake/dataflow IR is describes independent, unsynchronized processes communicating data through First-in First-out (FIFO) communication channels. This can be implemented in many ways, such as using synchronous logic, or with processors.

Choice of MLIR 

MLIR is a common infrastructure to build your own specific IR to target different architectures and needs. We use MLIR because of its extensibility. We can apply the various transformations and optimization of MLIR on this IR. We can also lower the std MLIR produced by different frontends to Handshake IR.

 TensorFlow     LLVM       Pytorch
      |           |           | 
 |-----------------------------------|    
 |   MLIR                            |
 |         -----------------         |
 |         | opt/transform |         |
 |         -----------------         |
 |                                   |
 |         -----------------         |
 |         | opt/transform |         |
 |         -----------------         |
 |                                   |
 |-----------------------------------|
    |        |        |             | 
   GPU      LLVM    Affine     **Dataflow**

IR Representation 

Simple Handshake IR snippet for add function look like this -

handshake.func @simple_addi(%arg0: index, %arg1: index, %arg2: none, ...) -> (index, none) {
        %0 = addi %arg0, %arg1 : index
        handshake.return %0, %arg2 : index, none
}

It accepts two input streams (modeled as MLIR operands) and produces one output stream (modeled as an MLIR result).

Conventions 

The Handshake dialect adopts the following conventions for IR:

  • The prefix for all Handshake types and operations are handshake..

Resources 

MLIR Handshake Dialect- slides by Stephen Neuendorffer (Xilinx) + Lana Josipović (EPFL)

Operation definitions 

handshake.branch (::circt::handshake::BranchOp) 

branch operation

The “handshake.branch” operation represents an unconditional branch. The single data input is propagated to the single successor. The input must be triggered by some predecessor to avoid continous triggering of a successor block.

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Operands: 

OperandDescription
dataOperandany type

Results: 

ResultDescription
dataResultany type

handshake.buffer (::circt::handshake::BufferOp) 

buffer operation

The “handshake.buffer” operation represents a buffer operation. $slots must be an unsigned integer larger than 0. $sequantial=True indicates a nontransparent buffer, while $sequantial=False indicates a transparent buffer.

Attributes: 

AttributeMLIR TypeDescription
sequential::mlir::BoolAttrbool attribute
control::mlir::BoolAttrbool attribute
slots::mlir::IntegerAttr32-bit signless integer attribute whose minimum value is 1

Operands: 

OperandDescription
«unnamed»any type

Results: 

ResultDescription
«unnamed»any type

handshake.conditional_branch (::circt::handshake::ConditionalBranchOp) 

conditional branch operation

The “handshake.cbranch” operation represents a conditional branch. The data input is propagated to one of the two outputs based on the condition input.

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Operands: 

OperandDescription
conditionOperand1-bit signless integer
dataOperandany type

Results: 

ResultDescription
trueResultany type
falseResultany type

handshake.constant (::circt::handshake::ConstantOp) 

constant operation

The “handshake.const” has a constant value.When triggered by its single input, it sends the constant value to its single successor.

Attributes: 

AttributeMLIR TypeDescription
value::mlir::Attributeany attribute

Operands: 

OperandDescription
«unnamed»any type

Results: 

ResultDescription
«unnamed»any type

handshake.control_merge (::circt::handshake::ControlMergeOp) 

control merge operation

The “handshake.control_merge” operation represents a (nondeterministic) control merge. Any input is propagated to the first output and the index of the propagated input is sent to the second output. The number of inputs corresponds to the number of predecessor blocks. ControlMerge is a control-only component(i.e., has no data but only bidirectional handshake).

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Operands: 

OperandDescription
dataOperandsany type

Results: 

ResultDescription
«unnamed»any type

handshake.end (::circt::handshake::EndOp) 

end operation

The “handshake.end” propagates the result of the appropriate return operation from one of its inputs to its single output after all memory accesses have completed. Currently not used(data returned through ReturnOp).

Operands: 

OperandDescription
controlany type
«unnamed»any type

handshake.fork (::circt::handshake::ForkOp) 

fork operation

The “handshake.fork” operation represents a fork operation. A single input is replicated to N outputs and distributed to each output as soon as the corresponding successor is available.

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Operands: 

OperandDescription
«unnamed»any type

Results: 

ResultDescription
«unnamed»any type

handshake.func (::circt::handshake::FuncOp) 

Handshake dialect function.

The “handshake.func” operation represents a handshaked function. This is almost exactly like a standard FuncOp, except that it has some extra verification conditions. In particular, each Value must only have a single use.

handshake.instance (::circt::handshake::InstanceOp) 

module instantiate operation

Syntax:

operation ::= `handshake.instance` $module `(` $operands `)` attr-dict `:` functional-type($operands, results)

The instance operation represents the instantiation of a module. This similar to a function call, except that different instances of the me module are guaranteed to have their own distinct state. The instantiated module is encoded as a symbol reference attribute named “module”.

Example:

%2 = handshake.instance @my_add(%0, %1) : (f32, f32) -> f32

Attributes: 

AttributeMLIR TypeDescription
module::mlir::FlatSymbolRefAttrflat symbol reference attribute

Operands: 

OperandDescription
operandsany type

Results: 

ResultDescription
«unnamed»any type

handshake.join (::circt::handshake::JoinOp) 

join operation

A control-only synchronizer. Produces a valid output when all inputs become available.

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Operands: 

OperandDescription
«unnamed»none type

Results: 

ResultDescription
«unnamed»none type

handshake.lazy_fork (::circt::handshake::LazyForkOp) 

lazy fork operation

The “handshake.lfork” operation represents a lazy fork operation. A single input is replicated to N outputs and distributed to each output when all successors are available.

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Operands: 

OperandDescription
«unnamed»any type

Results: 

ResultDescription
«unnamed»any type

handshake.load (::circt::handshake::LoadOp) 

load operation

Load memory port, sends load requests to MemoryOp. From dataflow predecessor, receives address indices and a control-only value which signals completion of all previous memory accesses which target the same memory. When all inputs are received, the load sends the address indices to MemoryOp. When the MemoryOp returns a piece of data, the load sends it to its dataflow successor.

Operands: address indices (from predecessor), data (from MemoryOp), control-only input. Results: data (to successor), address indices (to MemoryOp).

Operands: 

OperandDescription
«unnamed»any type
«unnamed»any type
«unnamed»none type

Results: 

ResultDescription
«unnamed»any type
addressResultsany type

handshake.memory (::circt::handshake::MemoryOp) 

memory

Each MemoryOp represents an independent memory or memory region (BRAM or external memory). It receives memory access requests from load and store operations. For every request, it returns data (for load) and a data-less token indicating completion. Operands: all stores (stdata1, staddr1, stdata2, staddr2, …), then all loads (ldaddr1, ldaddr2,…) Outputs: all load outputs, ordered the same as load addresses (lddata1, lddata2, …), followed by all none outputs, ordered as operands (stnone1, stnone2,…ldnone1, ldnone2,…)

Attributes: 

AttributeMLIR TypeDescription
ld_count::mlir::IntegerAttr32-bit signless integer attribute
st_count::mlir::IntegerAttr32-bit signless integer attribute
id::mlir::IntegerAttr32-bit signless integer attribute
type::mlir::TypeAttrmemref type attribute

Operands: 

OperandDescription
«unnamed»any type

Results: 

ResultDescription
«unnamed»any type

handshake.merge (::circt::handshake::MergeOp) 

merge operation

The “handshake.merge” operation represents a (nondeterministic) merge operation. Any input is propagated to the single output. The number of inputs corresponds to the number of predecessor blocks.

Operands: 

OperandDescription
dataOperandsany type

Results: 

ResultDescription
«unnamed»any type

handshake.mux (::circt::handshake::MuxOp) 

mux operation

The “handshake.mux” operation represents a(deterministic) merge operation. Operands: select, data0, data1, data2, …

The ‘select’ operand is received from ControlMerge of the same block and it represents the index of the data operand that the mux should propagate to its single output. The number of data inputs corresponds to the number of predecessor blocks.

Operands: 

OperandDescription
selectOperandany type
dataOperandsany type

Results: 

ResultDescription
«unnamed»any type

handshake.never (::circt::handshake::NeverOp) 

never operation

The “handshake.never” operation represents disconnected data source. The source never sets any ‘valid’ signal which will never trigger the successor at any point in time.

Results: 

ResultDescription
«unnamed»any type

handshake.return (::circt::handshake::ReturnOp) 

Handshake dialect return.

The “handshake.return” operation represents a handshaked function. This is almost exactly like a standard ReturnOp, except that it exists in a handshake.func. It has the same operands as standard ReturnOp which it replaces and an additional control - only operand(exit point of control - only network).

Operands: 

OperandDescription
operandsany type
controlnone type

handshake.sink (::circt::handshake::SinkOp) 

sink operation

The “handshake.sink” operation discards any data that arrives at its input.The sink has no successors and it can continuously consume data.

Operands: 

OperandDescription
«unnamed»any type

handshake.source (::circt::handshake::SourceOp) 

source operation

The “handshake.source” operation represents continuous data source. The source continously sets a ‘valid’ signal which the successor can consume at any point in time.

Results: 

ResultDescription
«unnamed»any type

handshake.start (::circt::handshake::StartOp) 

start operation

Triggers execution of the control - only network. Placed in entry ck. Currently not used( trigger given as function argument)

Attributes: 

AttributeMLIR TypeDescription
control::mlir::BoolAttrbool attribute

Results: 

ResultDescription
«unnamed»none type

handshake.store (::circt::handshake::StoreOp) 

store operation

Store memory port, sends store requests to MemoryOp. From dataflow predecessors, receives address indices, data, and a control-only value which signals completion of all previous memory accesses which target the same memory. When all inputs are received, the store sends the address and data to MemoryOp.

Operands: address indices, data, control-only input. Results: data and address indices (sent to MemoryOp).

Operands: 

OperandDescription
«unnamed»any type
«unnamed»any type
«unnamed»none type

Results: 

ResultDescription
«unnamed»any type
«unnamed»any type

handshake.terminator (::circt::handshake::TerminatorOp) 

handshake terminator operation

This op is used as a terminator in every block of the dataflow netlist (as a replacement for StandardOp branches). It has no functionality and can be removed in some subsequent pass, when the block structure is removed.

Successors: 

SuccessorDescription
destsany successor