Commit Graph

41 Commits

Author SHA1 Message Date
Morten Borup Petersen 4b2c78a13a
[Pipeline] Add cross-block canonicalization break op (#8517)
* [Pipeline] Add cross-block canonicalization break op

In participation of the upcoming removal of the cross-block canonicalization guards in the `comb` canonicalizers, we're introducing a `pipeline.src` operation to the pipeline dialect.

This operation is intended to alleviate the case of unintentional cross-block canonicalizations when a scheduled pipeline has not yet had its registers materialized.

This operation, `pipeline.src`, effectively acts as a canonicalization boundary, creating an in-block reference to a value that is defined in a predecessor block.

This means that we can keep our existing structure for unmaterialized, scheduled pipelines - values can be referenced from _any_ predecessor op through the `pipeline.src` operation, which still allows for said values to be easily moved across stages in a retiming scenario.
Upon register materialization, these `pipeline.src` operations are trivially removed, effectively becoming the block arguments of the pipeline stages.

* scheduling

* extra test

* some docs

* integration tests

* Review

---------

Co-authored-by: Morten Borup Petersen <mpetersen@microsoft.com>
2025-06-02 14:11:09 +02:00
Morten Borup Petersen 36106c2c5c
[Pipeline] Make `reset` signal optional (#8104)
* [Pipeline] Make `reset` signal optional

In some usecases, having a reset signal to a pipeline may do more harm than good in terms of synthetis/routing overhead. Instead, users can reset pipelines in other ways - e.g. by holder `go` low for `N stages` cycles. This, in turn, will set the pipeline stage enable registers to a deterministic state.

Allow for this by making the top-level `reset` signal optional.

* review

---------

Co-authored-by: Morten Borup Petersen <mpetersen@microsoft.com>
2025-01-23 14:15:50 +01:00
Morten Borup Petersen 91348ba8f1
[Pipeline] Verify >0 result types for LatencyOp (#6992) 2024-05-07 09:56:51 +02:00
Martin Erhart 594e3fb83f [Calyx][OM][Pipeline] Use free variants of isa/cast/dyn_cast
Refer to https://mlir.llvm.org/deprecation/
2024-04-28 16:53:58 +02:00
Andrew Lenharth 2654d56b93 [NFC] remove some warnings 2024-01-24 13:46:37 -06:00
Morten Borup Petersen 7654668842
[Ibis] Add `ibis-prepare-scheduling` pass (#6166)
Prepares `sblocks` for scheduling by:
1. Defining an operation `ibis.pipelineheader` that provides place-holders for clk/reset/go/stall.
2. places all operations inside a `pipeline.unscheduled` operation
3. feeds any block arguments that are trivially returned by the sblock, through the pipeline

Having this allows us to run any scheduling we want on the `pipeline.unscheduled` operation, regardless of the surroundings.

I was considering whether to include more of the control logic surrounding the pipeline operation into this PR. However, any performant control logic of the pipeline itself (which eventually need to interact with the ready/valid network that drives the `sblock`) needs information about the scheduled pipeline. Hence, prepare for scheduling, schedule, then create the control network based on scheduling results.
2023-09-28 13:27:09 +02:00
Morten Borup Petersen 290d917c02
[Pipeline] Remove internal clock, reset,stall signals. (#6196)
This also simplifies the stage enable signals, seeing as the entry enable signal will now always be the last argument of any mlir block inside the pipeline.
2023-09-25 16:25:59 +02:00
Nandor Licker 83c10b809e
[Seq] Convert `seq.clock_gate` to use the clock type exclusively (#6134)
This PR restricts the clock gate op to solely use the clock type.

Uses in other dialects, especially Pipeline, were adjusted.

To maintain canonicalization behaviour, a clock constant op along with an attribute is also introduced to represent constant clocks and to fold ops to them.
2023-09-18 12:36:04 +03:00
Morten Borup Petersen f3c9aa13ee
[Pipeline] Add non-stallable pipeline stages (#6018)
See rationale changes for an in-depth description of the why's and how's of this change.
2023-09-13 09:09:04 +02:00
Adam Izraelevitz ab842053ff
Bump LLVM: 2023/09/12 (#6114)
* Updated broken reference to mlir/Interfaces/FunctionInterfaces.td

* Fixed remaining issues from FunctionInterfaces inheriting from CallableOpInterface

* Rename constant_int_op_binder to constant_int_value_binder

* Bump llvm 9/12/2023
2023-09-12 16:11:24 -07:00
Morten Borup Petersen 55f3e4d5e5 [Pipeline] Use shared initializer list printer/parser function 2023-09-11 08:38:07 +00:00
Morten Borup Petersen 630dabd8fc
[Pipeline] valid -> enable (#6003)
Be a little bit more strict about naming here - the `i1` block argument of any given stage represents the stage **enable** signal - stage **valid** is reserved for the **output** signal/register of a stage, that is fed to its successor stage (as the successor stage enable signal).
2023-09-01 10:21:14 +02:00
Morten Borup Petersen 5c955b2f21
[Pipeline] Propagate names in `-pipeline-explicit-regs` (#5936)
Detect `sv.namehint` or OpAsmOpInterface's when routing values, and use these as the basis for register/passthrough names.

e.g.:
```mlir
hw.module @testNaming(%myArg : i32, %go : i1, %clk : i1, %rst : i1) -> (out: i32) {
  %out:2 = pipeline.scheduled(%A : i32 = %myArg) clock(%c = %clk) reset(%r = %rst) go(%g = %go) -> (out: i32) {
    %res = pipeline.latency 2 -> (i32) {
      %d = comb.add %A, %A : i32
      pipeline.latency.return %d : i32
    }  {"sv.namehint" = "foo"}
    pipeline.stage ^bb1
  ^bb1(%s1_valid : i1):
    pipeline.stage ^bb2
  ^bb2(%s2_valid : i1):
    %0 = comb.add %A, %res  {"sv.namehint" = "bar"} : i32
    pipeline.stage ^bb3
  ^bb3(%s3_valid : i1):
    pipeline.return %0 : i32
  }
  hw.output %out#0 : i32
}
```

eventually results in the following RTL:
```sv
module testNaming_p0(
  input  [31:0] A,
  input         enable,
                clk,
                rst,
  output [31:0] out,
  output        valid
);

  wire [31:0] _testNaming_p0_s1_A_out;
  wire [31:0] _testNaming_p0_s1_foo_out;
  wire        _testNaming_p0_s1_valid;
  wire [31:0] _testNaming_p0_s0_A_out;
  wire [31:0] _testNaming_p0_s0_foo_out;
  wire        _testNaming_p0_s0_valid;
  testNaming_p0_s0 testNaming_p0_s0 (
    .A       (A),
    .enable  (enable),
    .clk     (clk),
    .rst     (rst),
    .A_out   (_testNaming_p0_s0_A_out),
    .foo_out (_testNaming_p0_s0_foo_out),
    .valid   (_testNaming_p0_s0_valid)
  );
  testNaming_p0_s1 testNaming_p0_s1 (
    .A_in    (_testNaming_p0_s0_A_out),
    .foo_in  (_testNaming_p0_s0_foo_out),
    .enable  (_testNaming_p0_s0_valid),
    .clk     (clk),
    .rst     (rst),
    .A_out   (_testNaming_p0_s1_A_out),
    .foo_out (_testNaming_p0_s1_foo_out),
    .valid   (_testNaming_p0_s1_valid)
  );
  testNaming_p0_s2 testNaming_p0_s2 (
    .A_in    (_testNaming_p0_s1_A_out),
    .foo_in  (_testNaming_p0_s1_foo_out),
    .enable  (_testNaming_p0_s1_valid),
    .clk     (clk),
    .rst     (rst),
    .bar_out (out),
    .valid   (valid)
  );
endmodule

module testNaming_p0_s0(
  input  [31:0] A,
  input         enable,
                clk,
                rst,
  output [31:0] A_out,
                foo_out,
  output        valid
);

  reg [31:0] A_0;
  always_ff @(posedge clk)
    A_0 <= A;
  reg        stage0_valid;
  always_ff @(posedge clk) begin
    if (rst)
      stage0_valid <= 1'h0;
    else
      stage0_valid <= enable;
  end
  assign A_out = A_0;
  assign foo_out = {A[30:0], 1'h0};
  assign valid = stage0_valid;
endmodule

module testNaming_p0_s1(
  input  [31:0] A_in,
                foo_in,
  input         enable,
                clk,
                rst,
  output [31:0] A_out,
                foo_out,
  output        valid
);

  reg [31:0] A;
  always_ff @(posedge clk)
    A <= A_in;
  reg        stage1_valid;
  always_ff @(posedge clk) begin
    if (rst)
      stage1_valid <= 1'h0;
    else
      stage1_valid <= enable;
  end
  assign A_out = A;
  assign foo_out = foo_in;
  assign valid = stage1_valid;
endmodule

module testNaming_p0_s2(
  input  [31:0] A_in,
                foo_in,
  input         enable,
                clk,
                rst,
  output [31:0] bar_out,
  output        valid
);

  reg [31:0] bar;
  always_ff @(posedge clk)
    bar <= A_in + foo_in;
  reg        stage2_valid;
  always_ff @(posedge clk) begin
    if (rst)
      stage2_valid <= 1'h0;
    else
      stage2_valid <= enable;
  end
  assign bar_out = bar;
  assign valid = stage2_valid;
endmodule

module testNaming(
  input  [31:0] myArg,
  input         go,
                clk,
                rst,
  output [31:0] out
);

  testNaming_p0 testNaming_p0 (
    .A      (myArg),
    .enable (go),
    .clk    (clk),
    .rst    (rst),
    .out    (out),
    .valid  (/* unused */)
  );
endmodule
```
2023-08-25 09:27:17 +02:00
Robert Young 9429e9cb8a
Bump LLVM (#5825) 2023-08-11 09:40:48 -04:00
Morten Borup Petersen d933159ab4
[Pipeline] Remove `ext` input from pipelines (#5805)
Instead, remove `IsolatedFromAbove` from the pipeline, and define any used value defined outside of the pipeline as an external input. The motiviation for this is to reduce the headache introduced by having to explicitly modify the `ext` input list of a pipeline if more external inputs are to be added into a pipeline (or hoisted out of).
2023-08-08 12:56:53 -04:00
Morten Borup Petersen aecf3d3192
[Pipeline] Allow explicitly named registers and passthroughs (#5602)
Reflected in HW lowering, and used by e.g. the register materialization pass to carry register names through multiple stages.

Still a draft since i want to get peoples opinion on the HW lowering before i change tests.

Example lowering:

```mlir
hw.module @testNaming(%go: i1, %clk: i1, %rst: i1) -> (out: i1) {
  %0:2 = pipeline.scheduled "MyPipeline"(%a0 : i1 = %go) clock(%c = %clk) reset(%r = %rst) go(%g = %go) -> (out: i1) {
    // The register will be named after the namehint on the source operation.
    %add = comb.add %a0, %a0 : i1
    pipeline.stage ^bb1 regs("myNamed" = %add : i1, %add : i1) pass("myNamed" = %add : i1, %add : i1)

  ^bb1(%reg0 : i1, %reg1 : i1, %pass0 : i1, %pass1 : i1, %s1_valid: i1):
    pipeline.stage ^bb2 regs(%reg0 : i1, %reg1 : i1) pass(%pass0 : i1, %pass1 : i1)
  ^bb2(%reg01 : i1, %reg11 : i1, %pass01 : i1, %pass11 : i1, %s2_valid: i1):

    pipeline.return %reg01 : i1

  }
  hw.output %0#0 : i1
}

// Becomes
  hw.module @testNaming_MyPipeline(%a0: i1, %enable: i1, %clk: i1, %rst: i1) -> (out: i1, valid: i1) {
    %testNaming_MyPipeline_s0.myNamed, %testNaming_MyPipeline_s0.out1, %testNaming_MyPipeline_s0.myNamed_pass, %testNaming_MyPipeline_s0.pass1, %testNaming_MyPipeline_s0.valid = hw.instance "testNaming_MyPipeline_s0" @testNaming_MyPipeline_s0(a0: %a0: i1, enable: %enable: i1, clk: %clk: i1, rst: %rst: i1) -> (myNamed: i1, out1: i1, myNamed_pass: i1, pass1: i1, valid: i1)
    %testNaming_MyPipeline_s1.out0, %testNaming_MyPipeline_s1.out1, %testNaming_MyPipeline_s1.pass0, %testNaming_MyPipeline_s1.pass1, %testNaming_MyPipeline_s1.valid = hw.instance "testNaming_MyPipeline_s1" @testNaming_MyPipeline_s1(myNamed: %testNaming_MyPipeline_s0.myNamed: i1, out1: %testNaming_MyPipeline_s0.out1: i1, myNamed_pass: %testNaming_MyPipeline_s0.myNamed_pass: i1, pass1: %testNaming_MyPipeline_s0.pass1: i1, enable: %testNaming_MyPipeline_s0.valid: i1, clk: %clk: i1, rst: %rst: i1) -> (out0: i1, out1: i1, pass0: i1, pass1: i1, valid: i1)
    hw.output %testNaming_MyPipeline_s1.out0, %testNaming_MyPipeline_s1.out1 : i1, i1
  }
  hw.module @testNaming_MyPipeline_s0(%a0: i1, %enable: i1, %clk: i1, %rst: i1) -> (myNamed: i1, out1: i1, myNamed_pass: i1, pass1: i1, valid: i1) {
    %0 = comb.add %a0, %a0 : i1
    %s0_myNamed_reg = seq.compreg sym @s0_myNamed_reg %0, %clk : i1
    %s0_reg1 = seq.compreg sym @s0_reg1 %0, %clk : i1
    %false = hw.constant false
    %s0_valid = seq.compreg sym @s0_valid %enable, %clk, %rst, %false  : i1
    hw.output %s0_myNamed_reg, %s0_reg1, %0, %0, %s0_valid : i1, i1, i1, i1, i1
  }
  hw.module @testNaming_MyPipeline_s1(%myNamed: i1, %out1: i1, %myNamed_pass: i1, %pass1: i1, %enable: i1, %clk: i1, %rst: i1) -> (out0: i1, out1: i1, pass0: i1, pass1: i1, valid: i1) {
    %s1_reg0 = seq.compreg sym @s1_reg0 %myNamed, %clk : i1
    %s1_reg1 = seq.compreg sym @s1_reg1 %out1, %clk : i1
    %false = hw.constant false
    %s1_valid = seq.compreg sym @s1_valid %enable, %clk, %rst, %false  : i1
    hw.output %s1_reg0, %s1_reg1, %myNamed_pass, %pass1, %s1_valid : i1, i1, i1, i1, i1
  }
  hw.module @testNaming(%go: i1, %clk: i1, %rst: i1) -> (out: i1) {
    %testNaming_MyPipeline.out, %testNaming_MyPipeline.valid = hw.instance "testNaming_MyPipeline" @testNaming_MyPipeline(a0: %go: i1, enable: %go: i1, clk: %clk: i1, rst: %rst: i1) -> (out: i1, valid: i1)
    hw.output %testNaming_MyPipeline.out : i1
  }
```
Exports as
```sv
module testNaming_MyPipeline(
  input  a0,
         enable,
         clk,
         rst,
  output out,
         valid
);

  wire _testNaming_MyPipeline_s0_myNamed;
  wire _testNaming_MyPipeline_s0_out1;
  wire _testNaming_MyPipeline_s0_myNamed_pass;
  wire _testNaming_MyPipeline_s0_pass1;
  wire _testNaming_MyPipeline_s0_valid;
  testNaming_MyPipeline_s0 testNaming_MyPipeline_s0 (
    .a0           (a0),
    .enable       (enable),
    .clk          (clk),
    .rst          (rst),
    .myNamed      (_testNaming_MyPipeline_s0_myNamed),
    .out1         (_testNaming_MyPipeline_s0_out1),
    .myNamed_pass (_testNaming_MyPipeline_s0_myNamed_pass),
    .pass1        (_testNaming_MyPipeline_s0_pass1),
    .valid        (_testNaming_MyPipeline_s0_valid)
  );
  testNaming_MyPipeline_s1 testNaming_MyPipeline_s1 (
    .myNamed      (_testNaming_MyPipeline_s0_myNamed),
    .out1         (_testNaming_MyPipeline_s0_out1),
    .myNamed_pass (_testNaming_MyPipeline_s0_myNamed_pass),
    .pass1        (_testNaming_MyPipeline_s0_pass1),
    .enable       (_testNaming_MyPipeline_s0_valid),
    .clk          (clk),
    .rst          (rst),
    .out0         (out),
    .out1_0       (valid),
    .pass0        (/* unused */),
    .pass1_0      (/* unused */),
    .valid        (/* unused */)
  );
endmodule

module testNaming_MyPipeline_s0(
  input  a0,
         enable,
         clk,
         rst,
  output myNamed,
         out1,
         myNamed_pass,
         pass1,
         valid
);

  reg s0_myNamed_reg;
  reg s0_reg1;
  always_ff @(posedge clk) begin
    s0_myNamed_reg <= 1'h0;
    s0_reg1 <= 1'h0;
  end
  reg s0_valid;
  always_ff @(posedge clk) begin
    if (rst)
      s0_valid <= 1'h0;
    else
      s0_valid <= enable;
  end
  assign myNamed = s0_myNamed_reg;
  assign out1 = s0_reg1;
  assign myNamed_pass = 1'h0;
  assign pass1 = 1'h0;
  assign valid = s0_valid;
endmodule

module testNaming_MyPipeline_s1(
  input  myNamed,
         out1,
         myNamed_pass,
         pass1,
         enable,
         clk,
         rst,
  output out0,
         out1_0,
         pass0,
         pass1_0,
         valid
);

  reg s1_reg0;
  reg s1_reg1;
  always_ff @(posedge clk) begin
    s1_reg0 <= myNamed;
    s1_reg1 <= out1;
  end
  reg s1_valid;
  always_ff @(posedge clk) begin
    if (rst)
      s1_valid <= 1'h0;
    else
      s1_valid <= enable;
  end
  assign out0 = s1_reg0;
  assign out1_0 = s1_reg1;
  assign pass0 = myNamed_pass;
  assign pass1_0 = pass1;
  assign valid = s1_valid;
endmodule

module testNaming(
  input  go,
         clk,
         rst,
  output out
);

  testNaming_MyPipeline testNaming_MyPipeline (
    .a0     (go),
    .enable (go),
    .clk    (clk),
    .rst    (rst),
    .out    (out),
    .valid  (/* unused */)
  );
endmodule
```
2023-07-19 13:01:23 +02:00
blakep-msft b31bac8a12
Fix ordering of operands in ScheduledPipelineOp::build (#5585)
* Fix ordering of operands in ScheduledPipelineOp::build

* clang format

* Remove newlines and fixup schedule-linear-pipeline.mlir
2023-07-14 08:36:19 -07:00
Morten Borup Petersen 4387a07972
[Pipeline] Add missing name arg to builder (#5572) 2023-07-12 19:20:15 +02:00
Morten Borup Petersen 2d2aaab39f
[Pipeline] Refactor pipeline ops to include naming info (#5548)
The motivation for this refactor is to:
* remove the entry block - there's a bunch of different entry block arguments that symbolize different things, so we're asking for errors if these are printed as default block arguments.
* Add names to more things (pipeline, in and output ports).


Changes are:
* In the new format, there is no entry block (the entry block arguments is defined by the pipeline op). Furthermore, there is no `%s0_valid` signal, seeing as this is identical to the entry `%go` signal.
* Pipeline inputs and external inputs are expressed as an initializer list with type information. LHS is the name of the SSA value inside the pipeline, and RHS is the value that is passed into the pipeline.
*  Furthermore, also adds the ability to access clock, reset and stall signals from anywhere within the pipeline.
* Adds an (optional) name to the pipeline which can be used during lowering.
* Also makes the outputs named, which likewise can be used during lowering.
2023-07-12 09:44:34 +02:00
Morten Borup Petersen d62ef1a13d
[Pipeline] Fix `ScheduledPipelineOp` builder (#5540)
Also renames the data output field of the op to avoid aliasing with the built-in `getResultTypes()` that is the reason this bug went unnoticed.
2023-07-06 11:25:58 +02:00
Morten Borup Petersen 5d69c088d2
[Pipeline] Add per-register clock gating (#5489)
Also changes to that the `clock-gate-regs` options actually implements clock gates instead of a `seq.comp_reg.ce` operation. To cover all cases, i think there needs to be three kinds of gating implementations - clock gate, clock enable (`seq.compreg.ce`) and input muxing. The first and last are what we have now.
2023-07-03 09:13:26 +02:00
Morten Borup Petersen 34272a23d1
[NFC][Pipeline] Restructure pipeline dialect tablegen files (#5475)
for some reason, this also exposed a missing include in `circt-reduce.cpp`.
2023-06-27 10:31:05 +02:00
Morten Borup Petersen e290fcd526
[Pipeline] add 'go' signal to pipeline (#5455)
This commit adds an explicit 'go' signal to the pipeline abstraction.
This signal is used to indicate when the pipeline should start. The
signal will propagate through each stage as the stage valid signal.
As a result of this, each stage now has a `s#_valid : i1` signal as the
last value in its block argument list. This value can be used within
each stage for any operations which requires access to the pipeline
control circuitry.
Given that pipeline stage validity is now explicit within the block
arguments of a stage, a `pipeline.stage` operation no longer has an
`enable` signal. As a small 'bugfix' here, a `seq.clock_gate` is emitted
to gate the pipeline stage separating registers on the pipeline stage
valid signal.
2023-06-26 12:49:07 +02:00
Morten Borup Petersen 2c6732817e
[Pipeline] Add "ext" inputs to pipelines (#5347)
`ext` inputs are inputs which are accessible in any pipeline stage, and which will never be registered during register materialization. If you imagine a pipeline as signals going from left to right with registers in between, `ext` inputs are inputs that hook in from the top or bottom, into any pipeline stage.

In hardware (outlined) lowering, the external inputs are only provided to stages which actually reference them.

**Note**: The pipeline op signature is getting unruly, given that all inputs are bundled into the single `^bb0` definition. In a future commit, i plan to specialize the printer/parser for the pipeline op to mimick something like the `scf` ops, such that we have a bit more sane format.

```mlir
  %out:2 = pipeline.scheduled(%arg0) ext(%arg1 : i32) clock %clk reset %rst : (i32, i32) -> (i32, i32) {
    ^bb0(%a0 : i32, %ext0: i32):

// vs

%out:2 = pipeline.scheduled(%a0 : i32 = %arg0) ext(%ext0 = %arg1 : i32) clock %clk reset %rst -> (i32, i32) {
```
2023-06-20 11:06:57 +02:00
Morten Borup Petersen 336b71ffb6
[Pipeline] Add verifiers and fix register materialization pass (#5386)
Adds a bunch more verification to the `pipeline.scheduled` and `pipeline.latency` ops, as well as fixing the register materialization pass.

The register materialization pass has now been modified to  accomodate arbitrarily nested operations within each stage, which is a superset of the support required for `pipeline.latency`, in case operations in the inner body references values defined outside of its stage.
2023-06-19 13:43:56 +02:00
Morten Borup Petersen b1e12f3944
[Pipeline] Remove LI notion from pipelines (#5359)
Removes the notion that `pipeline` pipelines can be either latency insensitive or sensitive.
This is done because:
1. only latency sensitive lowerings are supported at the moment
2. there seems to be no clear way to infer backpressure through the pipeline without this being explicitly defined in the IR.

The latter is obviously the more grave concern. If/until this is eventually found out, we can add back latency-insensitivity. But until then, we should not advertise a feature that has no clear path of support.
2023-06-13 10:59:54 +02:00
Morten Borup Petersen 5dd674667a
[Pipeline] Add `pipeline.latency` operation (#5340)
The `pipeline.latency` operation represents an operation for wrapping
multi-cycle operations. The operation declares a single block
wherein any operation may be placed within. The operation is not
`IsolatedFromAbove` meaning that the operation can reference values
defined outside of the operation (subject to the materialization
phase of the parent pipeline).

This commit includes changes to the register materialization pass
(differentiating between `regs` and `pass` inputs to stages) and
pipeline HW lowering. Currently, `pipeline.latency` operations are
just inlined into the current insertion point.
2023-06-13 10:42:03 +02:00
Morten Borup Petersen 42711776fe
[Pipeline] Add optional `stall` signal to pipelines (#5278)
This signal is intended to connect to all stages within the pipeline, and is used to stall the entirety of the pipeline. It is lowering defined how stages choose to use this signal, although in the common case, a `stall` signal would typically connect to the clock-enable input of the stage-separating registers.

Future PRs will implement hardware lowerings.
2023-06-13 10:10:50 +02:00
Morten Borup Petersen d57b640d7b
[Pipeline] Refactor pipeline dialect to be block-based (#5332)
This commit refactors the pipeline dialect to be block-based. This brings a major representational change in the form of:
1. The pipeline is no longer defined by a lexical ordering of operations and insertion of `pipeline.stagesep` operations to separate stages. Instead, pipeline stages are defined by blocks.
2. Control flow between blocks are defined by `pipeline.stage` operations.
3. Like in the current version, the pipeline can exist in register dematerialized and materialized forms. In the dematerialized form, stages (`Block`s) have no arguments. In the materialized form, stages have arguments.
4. It is the `pipeline.stage` operations which infers whether to register a value or pass it directly (i.e. a wire) to the next stage.
5. Two top level operations exists:
  - `pipeline.unscheduled`: Unscheduled pipeline, essentially just a container of operations.
  - `pipeline.scheduled`: Scheduled pipeline, containing pipeline stages.

The motivation for this change is to improve the hierarchy of the IR, instead of relying on lexical ordering. This change also allows for more natural traversal of stages (`Block`s), as well as dataflow analysis of the pipeline, which now is analogous to control flow analysis. The only slight drawback of this change is that it slightly complicates adding new pipeline stages, seeing as one has to explicitly update the control flow of the pipeline. This is a minor drawback, seeing as this is also how things work in the software world, and is easily addressed by helper methods.

Likewise, this change also removes the (old) `pipeline.stage` operations, which mainly were introduced to facilitate lowering. This is no longer needed with the block-based pipeline, seeing as stage in- and outputs are clearly denoted by block inputs and `pipeline.stage` operations.

## Old representation:

```mlir
%out = pipeline.pipeline(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> (i32) {
  ^bb0(%a0 : i32, %a1: i32, %g : i1):
    %add0 = comb.add %a0, %a1 : i32
    %add1 = comb.add %add0, %a0 : i32
    %add2 = comb.add %add1, %add0 : i32
    pipeline.return %add2 valid %s1_valid : i32
}

// Schedules to
%out = pipeline.pipeline(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> (i32) {
^bb0(%a0 : i32, %a1: i32, %g : i1):
  %add0 = comb.add %a0, %a1 : i32

  %s0_valid = pipeline.stagesep enable %g
  %add1 = comb.add %add0, %a0 : i32 // %a0 is a block argument fed through a stage.

  %s1_valid = pipeline.stagesep enable %s0_valid
  %add2 = comb.add %add1, %add0 : i32 // %add0 crosses multiple stages.

  pipeline.return %add2 valid %s1_valid : i32
}

// materializes to
%0 = pipeline.pipeline(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> i32 {
^bb0(%a0: i32, %a1: i32, %g: i1):
  %1 = comb.add %a0, %a1 : i32

  %1_s0, %a0_s0, %valid = pipeline.stagesep.reg enable %g regs %1, %a0 : i32, i32
  %2 = comb.add %1_s0, %a0_s0 : i32

  %2_s1, %1_s1 %valid_3 = pipeline.stagesep.reg enable %valid regs %2, %1_s0 : i32, i32
  %3 = comb.add %2_s1, %1_s1 : i32 // %1 from the entry stage is chained through both stage 1 and 2.

  pipeline.return %3 valid %valid_3 : i32
}

// Lowers to
%0 = pipeline.pipeline(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> i32 {
^bb0(%a0: i32, %a1: i32, %arg2: i1):
  %outputs:2, %valid = pipeline.stage ins %a0, %a1 enable %g : (i32, i32, i1) -> (i32, i32) {
  ^bb0(%arg3: i32, %arg4: i32, %arg6: i1):
    %2 = comb.add %arg3, %arg4 : i32
    pipeline.stage.return regs %2, %arg3 valid %arg6 : (i32, i32)
  }
  %outputs_2:2, %valid_3 = pipeline.stage ins %outputs#0, %outputs#1 enable %valid : (i32, i32) -> (i32, i32) {
  ^bb0(%arg3: i32, %arg4: i32, %arg5: i1):
    %2 = comb.add %arg3, %arg4 : i32
    pipeline.stage.return regs %2, %arg3 valid %arg5 : (i32, i32)
  }
  %1 = comb.add %outputs_2#0, %outputs_2#1 : i32
  pipeline.return %1 valid %valid_3 : i32
}

```

## New representation

```mlir
%out = pipeline.unscheduled(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> (i32) {
  ^bb0(%a0 : i32, %a1: i32, %g : i1):
    %add0 = comb.add %a0, %a1 : i32
    %add1 = comb.add %add0, %a0 : i32
    %add2 = comb.add %add1, %add0 : i32
    pipeline.return %add2 valid %s1_valid : i32
}

// schedules to
%out = pipeline.scheduled(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> (i32) {
^bb0(%a0 : i32, %a1: i32, %go : i1):
  %add0 = comb.add %a0, %a1 : i32
  pipeline.stage ^bb1 enable %go

^bb1:
  %add1 = comb.add %add0, %a0 : i32 // %a0 is a block argument fed through a stage.
  pipeline.stage ^bb2 enable %go

^bb2:
  %add2 = comb.add %add1, %add0 : i32 // %add0 crosses multiple stages.
  pipeline.return %add2 enable %go : i32 // %go crosses multiple stages
}

// Materializes to
%0 = pipeline.scheduled(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> i32 {
^bb0(%a0: i32, %a1: i32, %go: i1):
  %1 = comb.add %a0, %a1 : i32
  pipeline.stage ^bb1 regs (%1, %a0, %go) pass () enable %go

^bb1(%1_s0 : i32, %a0_s0 : i32, %go_s0 : i1):
  %2 = comb.add %1_s0, %a0_s0 : i32
  pipeline.stage ^bb2 regs (%2, %1_s0, %go_s0) pass () enable %go_s0

^bb2(%2_s1 : i32, %1_s1 : i32, %go_s1 : i1):
  %3 = comb.add %2_s1, %1_s1 : i32 // %1 from the entry stage is chained through both stage 1 and 2.
  pipeline.return %3 valid %go_s1 : i32 // and likewise with %go
}

// which can be directly lowered to hardware
```
2023-06-12 10:42:40 +02:00
Morten Borup Petersen 464c687ed0
[Pipeline] Add `pipeline.stage` op and rename others accordingly (#5246)
This PR adds a new operation, `pipeline.stage` to the `Pipeline` dialect.
This operation is used to make stages explicit - that is, a region-defining
operation with inputs and outputs (much like the `loopschedule.pipeline.stage`).
The motivation for this is that once a `pipeline.pipeline` has had registers
materialized, a representational change to make the stages explicit allows
for much cleaner RTL lowering whenever stages are _not_ to be lowered to
hardware directly inline in the parent `hw.module`.

To (hopefully) avoid confusion, this commit also renames some existing
operations in the dialect:
* `pipeline.stage` becomes `pipeline.ss` - a *stage separating* operation.
  ... which is really what it is - it doesn't define a stage, it merely separates
  them.
* `pipeline.stage.regs` becomes `pipeline.ss.reg` - as above, it separates
  stages, but also defines the registers that are formed across the
  boundary.
2023-06-01 10:26:00 +02:00
Andrew Butt 80c76afb86
[LoopSchedule] Move PipelineWhile and Related Ops from Pipeline to LoopSchedule (#4947) 2023-04-18 11:56:07 -04:00
Will Dietz 1050a719d2
LLVM bump: move almost entirely to std::optional, fixes (#4470)
* LLVM.h: drop mlir::Optional.

It's an alias for std::optional now, just drop
instead of adding the templated using alias here as well
(which we'd have to remember to drop).

* Convert Optional -> std::optional.

Drop some unnecessary initializations, but keep them for fields for now.

Particularly:
StandardToHandshake.h: keep init for field, for omission in {} initializers.
FSMToSV: similarly keep the current initialization.
2022-12-22 18:57:38 -06:00
Mike Urbach d4aade214b
Bump LLVM to c4ca3a2c4b00033c393f694c1d92d28ff55c69cd. (#4048)
This included three renames:
* Arithmetic -> Arith
* FunctionOpInterface::getBody -> FunctionOpInterface::getBodyBlock
* MemRef dialect switching to prefixed accessors (memref -> getMemref, etc.)
2022-10-03 20:36:21 -06:00
Morten Borup Petersen 616649b4b6
[Pipeline] Add pipeline stage register materialization pass (#3876)
[Pipeline] Add pipeline stage register materialization pass

This commit adds an intermediate transformation to the Pipeline dialect
which is responsible for converting `pipeline.stage` to `pipeline.stage.register`
operations. The purpose of this transformation is to 'fix' where
registers needs to be placed in the pipeline, after all stages have been
defined and placed.

In short, the transformation will scan through the pipeline (in order,
top to bottom) and insert `pipeline.stage.register` operations in place
of `pipeline.stage` operations. Any operand used in any operation will
be analyzed to determine if it originates in between the last seen stage
and the operation itself. If not, this means that the operand crossed
a pipeline stage, and as such, the value will be routed through the
predecessor stage (`routeThroughStage`).
2022-09-16 15:03:58 +02:00
Mike Urbach 54a2b86d19 [Pipeline] Rename operands->outputs since it's is a reserved name.
The generated code will try to generate a `getOperands` method, but
that conflicts with a "keyword" in MLIR that is always
generated. Rename the arguments to generate a unique name and squash
the error.
2022-09-15 10:48:39 -06:00
Andrew Lenharth 1646243a27
[NFC] LLVM bump for 2022/09/14 (#3889)
Bump LLVM.
2022-09-15 08:49:33 -05:00
Morten Borup Petersen e7405f7be9
[Pipeline] Add `pipeline.pipeline` HW lowering (#3874)
This is a fairly straight-forward transformation since the brunt of the
work of detecting which values will be registered in a given pipeline
stage has already been performed by a prior pass.
This pass simply elaborates the `pipeline.stage.register` operations
into `seq.compreg` operations and stitches up the circuit.
2022-09-14 17:41:29 +02:00
Andrew Lenharth 4b67995d38 [NFC] eliminate some warnings 2022-09-13 11:24:53 -05:00
Morten Borup Petersen 0d82b4bb24
[Pipeline] Add retimeable pipeline operation (#3650)
The `pipeline.rtp` 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.

Alongside this, some old, unused code in the Pipeline dialect (StandardToPipeline) is pruned to remove a maintenance burden.

A typical flow would go like this:

An untimed datapath is defined:
```
pipeline.rtp(%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.rtp.return %add2 : i32
}
```
The datapath is scheduled (future pass):
```
pipeline.rtp(%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.rtp.return %add2 valid %s1_valid : i32
}
```

Stage-crossing dependencies are made explicit through registers (future pass):
```
pipeline.rtp(%in0 : i32, %in1 : i32) -> (i32) {
    ^bb0:(%arg0 : i32, %arg1: i32):
    %add0 = comb.add %arg0, %arg1 : i32

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

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

    pipeline.rtp.return %add2 valid %s1_valid : i32
}
```

This representation can then be lowered to statically or dynamically scheduled pipelines.
2022-08-23 19:48:22 +02:00
Morten Borup Petersen 5fc5b1d372
[Pipeline][NFC] Rename `pipeline.stage` -> `pipeline.while.stage` (#3758) 2022-08-22 10:20:00 +02:00
Morten Borup Petersen b3359685c7
[StaticLogic] Rename dialect to 'Pipeline' (#3648)
It is probably fair to conclude that naming this dialect `StaticLogic` has been a pain point for a while. This commit proposes a dialect renaming to `Pipeline`, for a couple of reaons:

1. So far, we've only been working with pipeline abstractions within this dialect.
2. Pipeline representations aren't necessarily statically scheduled - we plan on adding switches to select between latency sensitive and latency insensitive lowerings of pipelines.

This name change does not preclude renamings in the future if we want to fit more stuff into this dialect. Personally, i think it is prudent to maintain a dialect name which reflects what's actually being done within the dialect, as well as the (near/mid/"someone actually intends to work on this"-term) future plans for the dialect.
2022-08-04 10:58:40 +02:00