CIRCT

Circuit IR Compilers and Tools

'pipeline' Dialect

Operation definition 

pipeline.pipeline (::circt::pipeline::PipelineOp) 

pipeline operation

Syntax:

operation ::= `pipeline.pipeline` `(` $inputs `)` `clock` $clock `reset` $reset attr-dict `:` functional-type($inputs, results) $body

The “pipeline.pipeline” operation represents a retimeable pipeline. The pipeline contains a single block representing a graph region. Pipeline stages are represented by pipeline.rt.register operations. Semantics of values crossing register boundaries are defined by lowering passes. The pipeline representation is centered around providing latency insensitive values (valid signals between stages). Such signals can either be removed (in the feedforward, statically scheduled case), used to control stalling (feedback, statically scheduled case) or in conjunction with handshake signals for dynamically scheduled pipelines. A pipelines’ latency sensitivity is based on the I/O of the pipeline - if any in- or output port is an ESI channel, all ports are expected to be ESI channels, and the pipeline is considered latency sensitive. The internal representation of the pipeline is agnostic to the latency insensitivity of the I/O. This is by design - allowing us a single source of truth for lowering either latency sensitive or latency insensitive pipelines.

A typical flow would go like this:

An untimed datapath is defined:

pipeline.pipeline(%in0 : i32, %in1 : i32) -> (i32) {
  ^bb0:(%arg0 : i32, %arg1: i32):
    %add0 = comb.add %arg0, %arg1 : i32
    %add1 = comb.add %add0, %arg0 : i32
    %add2 = comb.add %add1, %arg1 : i32
    pipeline.return %add2 : i32
}

The datapath is scheduled:

pipeline.pipeline(%in0 : i32, %in1 : i32) -> (i32) {
  ^bb0:(%arg0 : i32, %arg1: i32, %go : i1):
    %add0 = comb.add %arg0, %arg1 : i32

    %s0_valid = pipeline.stage when %go
    %add1 = comb.add %add0, %arg0 : i32

    %s1_valid = pipeline.stage when %g1
    %add2 = comb.add %add1, %arg1 : i32

    pipeline.return %add2 valid %s1_valid : i32
}

Stage-crossing dependencies are made explicit through registers.

pipeline.pipeline(%in0 : i32, %in1 : i32) -> (i32) {
  ^bb0:(%arg0 : i32, %arg1: i32):
    %add0 = comb.add %arg0, %arg1 : i32

    %s0_valid, %add0_r = pipeline.stage.register when %go regs (%add0: i32)
    %add1 = comb.add %add0_r, %arg0 : i32

    %s1_valid, %add1_r = pipeline.stage.register when %g1 regs (%add1: i32)
    %add2 = comb.add %add1_r, %arg1 : i32

    pipeline.return %add2 valid %s1_valid : i32
}

This representation can then be lowered to statically or dynamically scheduled pipelines.

Traits: AlwaysSpeculatableImplTrait, HasOnlyGraphRegion, IsolatedFromAbove, SingleBlockImplicitTerminator

Interfaces: ConditionallySpeculatable, NoMemoryEffect (MemoryEffectOpInterface), RegionKindInterface

Effects: MemoryEffects::Effect{}

Operands: 

OperandDescription
inputsany type
clock1-bit signless integer
reset1-bit signless integer

Results: 

ResultDescription
resultsany type

pipeline.register (::circt::pipeline::PipelineRegisterOp) 

Pipeline dialect pipeline register.

Syntax:

operation ::= `pipeline.register` $operands (`:` qualified(type($operands))^)? attr-dict

The pipeline.register terminates a pipeline stage and “registers” the values specified as operands. These values become the results of the stage.

Traits: HasParent<PipelineWhileOp, PipelineWhileStageOp>, Terminator

Operands: 

OperandDescription
operandsany type

pipeline.stage (::circt::pipeline::PipelineStageOp) 

Pipeline pipeline stage.

Syntax:

operation ::= `pipeline.stage` `when` $when attr-dict

The pipeline.stage operation represents a stage separating register in a pipeline. The stage does not define any explicit registers, but solely defines a cut of a dataflow graph based on its lexical position in the pipeline. Pipeline registers are made explicit through the register materialization pass, wherein this op is replaced by pipeline.stage.register operations.

Traits: HasParent

Interfaces: InferTypeOpInterface

Operands: 

OperandDescription
when1-bit signless integer

Results: 

ResultDescription
valid1-bit signless integer

pipeline.stage.register (::circt::pipeline::PipelineStageRegisterOp) 

Pipeline pipeline stage.

Syntax:

operation ::= `pipeline.stage.register` `when` $when (`regs` $regIns^)? attr-dict (`:` type($regIns)^)?

The pipeline.stage operation represents a stage separating register in a pipeline with materialized register values. pipeline.stage and pipeline.stage.register operations are not allowed to co-exist in the same pipeline body. This is because, once register values are materialized, all delays as well as knowledge about multicycle paths have been lowered away.

Traits: HasParent

Operands: 

OperandDescription
regInsany type
when1-bit signless integer

Results: 

ResultDescription
regOutsany type
valid1-bit signless integer

pipeline.terminator (::circt::pipeline::PipelineTerminatorOp) 

Pipeline dialect pipeline terminator.

Syntax:

operation ::= `pipeline.terminator` `iter_args` `(` $iter_args `)` `,`
              `results` `(` $results `)` `:`
              functional-type($iter_args, $results) attr-dict

The pipeline.terminator operation represents the terminator of a pipeline.while.

The results section accepts a variadic list of values which become the pipeline’s return values. These must be results of a stage, and their types must match the pipeline’s return types. The results need not be defined in the final stage, and it is up to lowering passes to preserve these values until the final stage is complete.

The iter_args section accepts a variadic list of values which become the next iteration’s iter_args. These may be the results of any stage, and their types must match the pipeline’s iter_args types.

Traits: AttrSizedOperandSegments, HasParent, Terminator

Operands: 

OperandDescription
iter_argsany type
resultsany type

pipeline.while (::circt::pipeline::PipelineWhileOp) 

Pipeline dialect pipeline while-loop.

The pipeline.while operation represents a statically scheduled pipeline stucture that executes while a condition is true. For more details, see: https://llvm.discourse.group/t/rfc-representing-pipelined-loops/4171.

A pipeline captures the result of scheduling, and is not generally safe to transform, besides lowering to hardware dialects. For more discussion about relaxing this, see: https://github.com/llvm/circt/issues/2204.

This is the top-level operation representing a high-level pipeline. It is not isolated from above, but could be if this is helpful. A pipeline contains two regions: condition and stages.

The pipeline may accept an optional iter_args, similar to the SCF dialect, for representing loop-carried values like induction variables or reductions. When the pipeline starts execution, the registers indicated as iter_args by pipeline.terminator should be initialized to the initial values specified in the iter_args section here. The iter_args relate to the initiation interval of the loop. The maximum distance in stages between where an iter_arg is used and where that iter_arg is registered must be less than the loop’s initiation interval. For example, with II=1, each iter_arg must be used and registered in the same stage.

The single-block condition region dictates the condition under which the pipeline should execute. It has a register terminator, and the pipeline initiates new iterations while the registered value is true : i1. It may access SSA values dominating the pipeline, as well as iter_args, which are block arguments. The body of the block may only contain “combinational” operations, which are currently defined to be simple arithmetic, comparisons, and selects from the Standard dialect.

The single-block stages region wraps pipeline.stage operations. It has a pipeline.terminator terminator, which can both return results from the pipeline and register iter_args. Stages may access SSA values dominating the pipeline, as well as iter_args, which are block arguments.

Attributes: 

AttributeMLIR TypeDescription
II::mlir::IntegerAttr64-bit signless integer attribute
tripCount::mlir::IntegerAttr64-bit signless integer attribute

Operands: 

OperandDescription
iterArgsany type

Results: 

ResultDescription
resultsany type

pipeline.while.stage (::circt::pipeline::PipelineWhileStageOp) 

Pipeline dialect while pipeline stage.

Syntax:

operation ::= `pipeline.while.stage` `start` `=` $start (`when` $when^)? $body (`:` qualified(type($results))^)? attr-dict

This operation has a single-block region which dictates the operations that may occur concurrently.

It has a start attribute, which indicates the start cycle for this stage.

It may have an optional when predicate, which supports conditional execution for each stage. This is in addition to the condition region that controls the execution of the whole pipeline. A stage with a when predicate should only execute when the predicate is true : i1, and push a bubble through the pipeline otherwise.

It has a register terminator, which passes the concurrently computed values forward to the next stage.

Any stage may access iter_args. If a stage accesses an iter_arg after the stage in which it is defined, it is up to lowering passes to preserve this value until the last stage that needs it.

Other than iter_args, stages may only access SSA values dominating the pipeline or SSA values computed by a previous stage. This ensures the stages capture the coarse-grained schedule of the pipeline and how values feed forward and backward.

Traits: HasParent

Attributes: 

AttributeMLIR TypeDescription
start::mlir::IntegerAttr64-bit signed integer attribute

Operands: 

OperandDescription
when1-bit signless integer

Results: 

ResultDescription
resultsany type

pipeline.return (::circt::pipeline::ReturnOp) 

Pipeline dialect return.

Syntax:

operation ::= `pipeline.return` ($outputs^)? `valid` $valid attr-dict (`:` type($outputs)^)?

The “return” operation represents a terminator of a pipeline.pipeline.

Traits: Terminator

Operands: 

OperandDescription
outputsany type
valid1-bit signless integer