[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.
This commit is contained in:
Morten Borup Petersen 2023-09-13 09:09:04 +02:00 committed by GitHub
parent 8f4abacef5
commit f3c9aa13ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 592 additions and 87 deletions

View File

@ -169,3 +169,64 @@ pipeline.stage ^bb4 regs(%out_s3 : i32)
^bb4(%out_s4 : i32):
foo.bar %out_s4 : i32
```
## Non-stallable Pipeline Stages
**Note:** the following is only valid for pipelines with a stall signal.
An option of the Pipeline abstraction presented in this dialect is the ability
to have _non-stallable stages_ (NS). NS stages are used whereever a pipeline
access resources that are not able to stop on a dime, and thus require a fixed
amount of cycles to complete.
Non-stallable stages are marked as an attribute of the pipeline operations,
wherein a bitvector is provided (by the user) to indicate which stage(s) are
non-stallable.
To see how non-stallable stages are implemented, consider the following. For
every stage, we define two signals - `S_{N,en}` is the signal that indicates
that the stage currently has valid contents (i.e. not a bubble). `S_{N,valid}`
is the signal that is used as a clock-enable for the output registers of a
stage.
Stages can be grouped into three distinct types based on how their valid signal
is defined: stallable stages, non-stallable stages and runoff stages.
<img title="Stage control" src="includes/img/Pipeline/stage_control.png"/>
1. Stallable stages are any stages which appear **before** the first
non-stallable stage in the pipeline.
2. Non-stallable stages are the stages explicitly marked as non-stallable by the
user.
3. Runoff stages and stages that appear **after** (and by extension, **between**
non-stallable stages). Runoff stages consider their own enablement wrt. the
stall signal, as well as the enablement of the **last non-stallable
register** (LNS) wrt. the runoff stage's position in the pipeline.
The purpose of the runoff stages is to ensure that they are able to pass through
as many pipeline cycles as there are upstream non-stallable stages, such that
the contents of the non-stallable stages is not discarded.
An important implication of this is that pipelines with non-stallable stages
**must** be connected to some buffer mechanism that is able to hold as many
pipeline output value cycles as there are non-stallable stages in the pipeline.
As an example, the following 6 stage pipeline will have the following valid
signals: <img title="Stage control" src="includes/img/Pipeline/ns_ex1.png"/>
### Example 1:
In this example, we have two NS stages followed by three runoff stages:
<img title="Stage control" src="includes/img/Pipeline/ns_ex2.png"/>
In this example we see that, as expected, two cycles are output from the
pipeline after the stall signal goes high, corresponding to the two NS stages.
### Example 2:
In this example, we have two NS stages, then one runoff stage, then one NS
stage, and finally one runoff stage:
<img title="Stage control" src="includes/img/Pipeline/ns_ex3.png"/>
In this example we see that, as expected, three cycles are output from the
pipeline after the stall signal goes high, corresponding to the three NS stages.

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -24,6 +24,24 @@ namespace pipeline {
class StageOp;
class ScheduledPipelineOp;
// StageKind defines the control semantics of a pipeline stages.
enum class StageKind {
// All stages in a pipeline without a stall signal is a continuous stage.
Continuous,
// Stallable stages are any stages which appear **before** the first
// non-stallable stage in the pipeline.
Stallable,
// Non-stallable stages are the stages explicitly marked as non-stallable by
// the user.
NonStallable,
// Runoff stages and stages that appear **after** (and by extension,
// **between** non-stallable stages). Runoff stages consider their own
// enablement wrt. the stall signal, as well as the enablement of the **last
// non-stallable register** (LNS) wrt. the runoff stage's position in the
// pipeline.
Runoff
};
namespace detail {
// Returns the set of values defined outside of the given region, and the

View File

@ -27,12 +27,6 @@ class PipelineBase<string mnemonic, list<Trait> traits = []> :
Pure,
RegionKindInterface,
AttrSizedOperandSegments])> {
let arguments = (ins
OptionalAttr<StrAttr>:$name, Variadic<AnyType>:$inputs, Optional<I1>:$stall,
I1:$clock, I1:$reset, I1:$go, StrArrayAttr:$inputNames,
StrArrayAttr:$outputNames
);
let results = (outs Variadic<AnyType>:$dataOutputs, I1:$done);
let hasCustomAssemblyFormat = 1;
@ -117,6 +111,11 @@ def UnscheduledPipelineOp : PipelineBase<"unscheduled", [
about the interface signals.
}];
let arguments = (ins
OptionalAttr<StrAttr>:$name, Variadic<AnyType>:$inputs, Optional<I1>:$stall,
I1:$clock, I1:$reset, I1:$go, StrArrayAttr:$inputNames,
StrArrayAttr:$outputNames
);
let regions = (region SizedRegion<1>: $body);
let extraModuleClassDeclaration = "";
}
@ -148,19 +147,48 @@ def ScheduledPipelineOp : PipelineBase<"scheduled", [
Any value defined outside the pipeline is considered an external input. An
external input will _not_ be registered.
The pipeline may optionally be provided with an array of bits `stallability`
which is used to determine which stages are stallable.
- If not provided and the pipeline has a stall signal, all stages are stallable.
- If provided, and the pipeline has a stall signal, the number of bits must
match the number of stages in the pipeline. Each bit represents a stage,
in the order of which the stages appear wrt. the `pipeline.stage` operations.
A bit set to 1 indicates that the stage is stallable, and 0 indicates that
the stage is not stallable.
The exit (non-registered) stage of a pipeline cannot be non-stallable, and
will always follow the stallability of the parent pipeline.
For more information about non-stallable stages, and how these are lowered,
please refer to the Pipeline dialect rationale.
}];
let arguments = (ins
OptionalAttr<StrAttr>:$name,
Variadic<AnyType>:$inputs,
Optional<I1>:$stall,
I1:$clock, I1:$reset, I1:$go,
StrArrayAttr:$inputNames, StrArrayAttr:$outputNames,
OptionalAttr<BoolArrayAttr>:$stallability
);
let regions = (region AnyRegion:$body);
let hasVerifier = 1;
let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins "TypeRange":$dataOutputs, "ValueRange":$inputs,
OpBuilder<(ins
"TypeRange":$dataOutputs,
"ValueRange":$inputs,
"ArrayAttr":$inputNames, "ArrayAttr":$outputNames,
"Value":$clock, "Value":$reset, "Value":$go, CArg<"Value", "{}">:$stall, CArg<"StringAttr", "{}">:$name)>
"Value":$clock, "Value":$reset, "Value":$go,
CArg<"Value", "{}">:$stall,
CArg<"StringAttr", "{}">:$name,
CArg<"ArrayAttr", "{}">:$stallability
)>
];
code extraModuleClassDeclaration = [{
static mlir::RegionKind getRegionKind(unsigned index) {
return mlir::RegionKind::SSACFG;
@ -171,6 +199,10 @@ def ScheduledPipelineOp : PipelineBase<"scheduled", [
return getRegion().getBlocks();
}
size_t getNumStages() {
return getStages().size();
}
void getAsmBlockArgumentNames(mlir::Region &region,
mlir::OpAsmSetValueNameFn setNameFn);
@ -227,6 +259,9 @@ def ScheduledPipelineOp : PipelineBase<"scheduled", [
return stage->getArguments().back();
return getInnerGo();
}
// Returns the stage kind of the given stage index.
StageKind getStageKind(size_t stageIdx);
}];
}

View File

@ -1,6 +1,6 @@
import glob, os
dir_path = os.path.dirname(os.path.realpath(__file__))
for pyfile in glob.glob(os.path.join(dir_path, "**", "*.py")):
for pyfile in glob.glob(os.path.join(dir_path, "**", "*.py"), recursive=True):
# remove dir from pyfile
config.excludes.add(os.path.basename(pyfile))

View File

@ -0,0 +1,87 @@
import cocotb
from cocotb.triggers import Timer
import cocotb.clock
# Value that will be adjusted and assigned to the dut input argument, every
# clock cycle. This is mainly to assist manual verification of the VCD trace/
v = 1
async def clock(dut):
global v
dut.clock.value = 0
await Timer(1, units='ns')
v += 1
dut.arg0.value = v
dut.clock.value = 1
await Timer(1, units='ns')
async def initDut(dut):
"""
Initializes a dut by adding a clock, setting initial valid and ready flags,
and performing a reset.
"""
# Reset
dut.reset.value = 1
await clock(dut)
dut.reset.value = 0
await clock(dut)
async def nonstallable_test(dut, stageStallability):
"""
Runs a test of a non-stallable pipeline.
Provided the number of stages in the pipeline and the indices of the non-stallable
stages, the test will:
1. for NStallCycles : range(0 to nStages - 1):
1. for fillCycles : range(0 to nStages - 1):
1. fill the pipeline with $fillCycles valid tokens
2. raise the stall signal
3. wait for NStallCycles
4. While doing so, check that len(nonStallableStageIdxs) dut.done assertions
occur
5. deassert stall
6. check that the expected amount of bubbles exit the pipeline
"""
nStages = len(stageStallability)
numNonstallableStages = sum(
[1 if not stallable else 0 for stallable in stageStallability])
for nStallCycles in range(0, nStages * 2):
for fillCycles in range(0, nStages + 1):
print(f"nStallCycles: {nStallCycles}, fillCycles: {fillCycles}")
# Reset the dut
dut.go.value = 0
dut.stall.value = 0
dut.arg0.value = 42
await initDut(dut)
dut.stall.value = 0
# Fill the pipeline with fillCycles valid tokens
dut.go.value = 1
for i in range(fillCycles):
await clock(dut)
dut.go.value = 0
nBufferedTokensExpected = min(numNonstallableStages, nStallCycles,
fillCycles)
nBubblesExpected = nBufferedTokensExpected
# Raise the stall signal. We now expect that numNonStallableStages dut.done
# assertions will occur in a row.
sequence = []
dut.stall.value = 1
for cycle in range(nStallCycles + nStages):
if cycle > nStallCycles:
dut.stall.value = 0
await Timer(1, units='ns')
sequence.append(int(dut.done))
await clock(dut)
# Check that exactly fill_cycles tokens exited the pipeline
nTokensExited = sum(sequence)
assert nTokensExited == fillCycles, f"Expected {fillCycles} tokens to exit the pipeline, but {nTokensExited} did"

View File

@ -0,0 +1,30 @@
// REQUIRES: iverilog,cocotb
// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw -lower-seq-to-sv -sv-trace-iverilog -export-verilog \
// RUN: -o %t.mlir > %t.sv
// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=nonstallable_test1 \
// RUN: --pythonModule=nonstallable_test1 --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s
// CHECK: ** TEST
// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0
hw.module @nonstallable_test1(%arg0: i32, %go: i1, %clock: i1, %reset: i1, %stall: i1) -> (out: i32, done: i1) {
%out, %done = pipeline.scheduled "nonstallable_test1"(%a0 : i32 = %arg0)
stall(%s = %stall) clock(%c = %clock) reset(%r = %reset) go(%g = %go)
{stallability = [true, false, false, true, true]} -> (out : i32) {
pipeline.stage ^bb1
^bb1(%s1_enable: i1):
pipeline.stage ^bb2
^bb2(%s2_enable: i1):
pipeline.stage ^bb3
^bb3(%s3_enable: i1):
pipeline.stage ^bb4
^bb4(%s4_enable: i1):
pipeline.stage ^bb5
^bb5(%s5_enable: i1):
pipeline.return %a0 : i32
}
hw.output %out, %done : i32, i1
}

View File

@ -0,0 +1,7 @@
import cocotb
from nonstallable_helper import nonstallable_test
@cocotb.test()
async def test1(dut):
await nonstallable_test(dut, [0, 1, 1, 0, 0])

View File

@ -0,0 +1,30 @@
// REQUIRES: iverilog,cocotb
// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw -lower-seq-to-sv -sv-trace-iverilog -export-verilog \
// RUN: -o %t.mlir > %t.sv
// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=nonstallable_test2 \
// RUN: --pythonModule=nonstallable_test2 --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s
// CHECK: ** TEST
// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0
hw.module @nonstallable_test2(%arg0: i32, %go: i1, %clock: i1, %reset: i1, %stall: i1) -> (out: i32, done : i1) {
%out, %done = pipeline.scheduled "nonstallable_test2"(%a0 : i32 = %arg0)
stall(%s = %stall) clock(%c = %clock) reset(%r = %reset) go(%g = %go)
{stallability = [true, false, true, false, true]} -> (out : i32) {
pipeline.stage ^bb1
^bb1(%s1_enable: i1):
pipeline.stage ^bb2
^bb2(%s2_enable: i1):
pipeline.stage ^bb3
^bb3(%s3_enable: i1):
pipeline.stage ^bb4
^bb4(%s4_enable: i1):
pipeline.stage ^bb5
^bb5(%s5_enable: i1):
pipeline.return %a0 : i32
}
hw.output %out, %done : i32, i1
}

View File

@ -0,0 +1,8 @@
import cocotb
from nonstallable_helper import nonstallable_test
@cocotb.test()
async def test1(dut):
await nonstallable_test(dut, [0, 1, 0, 1, 0])

View File

@ -46,6 +46,7 @@ public:
Value stall;
Value clock;
Value reset;
Value lnsEn;
};
// Arguments used for returning the results from a stage. These values must
@ -54,6 +55,11 @@ public:
llvm::SmallVector<Value> regs;
llvm::SmallVector<Value> passthroughs;
Value valid;
// In case this was the last register in a non-stallable register chain, the
// register will also return its enable signal to be used for LNS of
// downstream stages.
Value lnsEn;
};
virtual FailureOr<StageReturns>
@ -89,6 +95,41 @@ public:
}
}
auto loc = terminator->getLoc();
Value notStalled;
auto getOrSetNotStalled = [&]() {
if (!notStalled) {
notStalled = comb::createOrFoldNot(loc, args.stall, builder);
}
return notStalled;
};
// Determine the stage kind. This will influence how the stage valid and
// enable signals are defined.
StageKind stageKind = pipeline.getStageKind(stageIndex);
Value stageValid;
StringAttr validSignalName =
builder.getStringAttr(getStagePrefix(stageIndex).strref() + "_valid");
switch (stageKind) {
case StageKind::Continuous:
LLVM_FALLTHROUGH;
case StageKind::NonStallable:
stageValid = args.enable;
break;
case StageKind::Stallable:
stageValid =
builder.create<comb::AndOp>(loc, args.enable, getOrSetNotStalled());
stageValid.getDefiningOp()->setAttr("sv.namehint", validSignalName);
break;
case StageKind::Runoff:
assert(args.lnsEn && "Expected an LNS signal if this was a runoff stage");
stageValid = builder.create<comb::AndOp>(
loc, args.enable,
builder.create<comb::OrOp>(loc, args.lnsEn, getOrSetNotStalled()));
stageValid.getDefiningOp()->setAttr("sv.namehint", validSignalName);
break;
}
StageReturns rets;
auto stageOp = dyn_cast<StageOp>(terminator);
if (!stageOp) {
@ -97,32 +138,19 @@ public:
// register its operands, hence, all return operands are passthrough
// and the valid signal is equal to the unregistered enable signal.
rets.passthroughs = terminator->getOperands();
rets.valid = args.enable;
rets.valid = stageValid;
return rets;
}
assert(registerNames.size() == stageOp.getRegisters().size() &&
"register names and registers must be the same size");
// Build data registers.
auto stageRegPrefix = getStageRegPrefix(stageIndex);
auto loc = stageOp->getLoc();
// Build the clock enable signal: enable && !stall (if applicable)
Value stageValidAndNotStalled = args.enable;
Value notStalled;
bool hasStall = static_cast<bool>(args.stall);
if (hasStall) {
notStalled = comb::createOrFoldNot(loc, args.stall, builder);
stageValidAndNotStalled =
builder.create<comb::AndOp>(loc, stageValidAndNotStalled, notStalled);
}
bool isStallablePipeline = stageKind != StageKind::Continuous;
Value notStalledClockGate;
if (this->clockGateRegs) {
// Create the top-level clock gate.
notStalledClockGate = builder.create<seq::ClockGateOp>(
loc, args.clock, stageValidAndNotStalled, /*test_enable=*/Value(),
loc, args.clock, stageValid, /*test_enable=*/Value(),
/*inner_sym=*/hw::InnerSymAttr());
}
@ -138,18 +166,19 @@ public:
for (auto hierClockGateEnable : stageOp.getClockGatesForReg(regIdx)) {
// Create clock gates for any hierarchically nested clock gates.
currClockGate = builder.create<seq::ClockGateOp>(
loc, currClockGate, hierClockGateEnable, /*test_enable=*/Value(),
loc, currClockGate, hierClockGateEnable,
/*test_enable=*/Value(),
/*inner_sym=*/hw::InnerSymAttr());
}
dataReg = builder.create<seq::CompRegOp>(stageOp->getLoc(), regIn,
currClockGate, regName);
} else {
// Only clock-enable the register if the pipeline is stallable.
// For non-stallable pipelines, a data register can always be clocked.
if (hasStall) {
// For non-stallable (continuous) pipelines, a data register can always
// be clocked.
if (isStallablePipeline) {
dataReg = builder.create<seq::CompRegClockEnabledOp>(
stageOp->getLoc(), regIn, args.clock, stageValidAndNotStalled,
regName);
stageOp->getLoc(), regIn, args.clock, stageValid, regName);
} else {
dataReg = builder.create<seq::CompRegOp>(stageOp->getLoc(), regIn,
args.clock, regName);
@ -158,38 +187,26 @@ public:
rets.regs.push_back(dataReg);
}
// Build valid register. The valid register is always reset to 0, and
// clock enabled when not stalling.
auto validRegName = (stageRegPrefix.strref() + "_valid").str();
Value validRegResetVal =
builder.create<hw::ConstantOp>(terminator->getLoc(), APInt(1, 0, false))
.getResult();
if (hasStall) {
rets.valid = builder.create<seq::CompRegClockEnabledOp>(
loc, args.enable, args.clock, notStalled, args.reset,
validRegResetVal, validRegName);
} else {
rets.valid = builder.create<seq::CompRegOp>(loc, args.enable, args.clock,
args.reset, validRegResetVal,
validRegName);
}
rets.valid = stageValid;
if (stageKind == StageKind::NonStallable)
rets.lnsEn = args.enable;
rets.passthroughs = stageOp.getPassthroughs();
return rets;
}
// A container carrying all-things stage output naming related.
// To avoid overloading 'output's to much (i'm trying to keep that reserved
// for "output" ports), this is named "egress".
// To avoid overloading 'output's to much (i'm trying to keep that
// reserved for "output" ports), this is named "egress".
struct StageEgressNames {
llvm::SmallVector<Attribute> regNames;
llvm::SmallVector<Attribute> outNames;
llvm::SmallVector<Attribute> inNames;
};
// Returns a set of names for the output values of a given stage (registers
// and passthrough). If `withPipelinePrefix` is true, the names will be
// prefixed with the pipeline name.
// Returns a set of names for the output values of a given stage
// (registers and passthrough). If `withPipelinePrefix` is true, the names
// will be prefixed with the pipeline name.
void getStageEgressNames(size_t stageIndex, Operation *stageTerminator,
bool withPipelinePrefix,
StageEgressNames &egressNames) {
@ -243,15 +260,15 @@ public:
egressNames.inNames.push_back(builder.getStringAttr(assignedInName));
}
} else {
// For the return op, we just inherit the names of the top-level pipeline
// as stage output names.
// For the return op, we just inherit the names of the top-level
// pipeline as stage output names.
llvm::copy(pipeline.getOutputNames().getAsRange<StringAttr>(),
std::back_inserter(egressNames.outNames));
}
}
// Returns a string to be used as a prefix for all stage registers.
virtual StringAttr getStageRegPrefix(size_t stageIdx) = 0;
virtual StringAttr getStagePrefix(size_t stageIdx) = 0;
protected:
// Determine a reasonable name for the pipeline. This will affect naming
@ -288,7 +305,7 @@ class PipelineInlineLowering : public PipelineLowering {
public:
using PipelineLowering::PipelineLowering;
StringAttr getStageRegPrefix(size_t stageIdx) override {
StringAttr getStagePrefix(size_t stageIdx) override {
return builder.getStringAttr(pipelineName.strref() + "_stage" +
Twine(stageIdx));
}
@ -328,6 +345,8 @@ public:
lowerStage(Block *stage, StageArgs args, size_t stageIndex,
llvm::ArrayRef<Attribute> /*inputNames*/ = {}) override {
OpBuilder::InsertionGuard guard(builder);
Operation *terminator = stage->getTerminator();
Location loc = terminator->getLoc();
if (stage != pipeline.getEntryStage()) {
// Replace the internal stage inputs with the provided arguments.
@ -336,11 +355,55 @@ public:
vInput.replaceAllUsesWith(vArg);
}
// Build stage enable register. The enable register is always reset to 0.
// The stage enable register takes the previous-stage combinational valid
// output and determines whether this stage is active or not in the next
// cycle.
// A non-stallable stage always registers the incoming enable signal,
// whereas other stages register based on the current stall state.
StageKind stageKind = pipeline.getStageKind(stageIndex);
Value stageEnabled;
if (stageIndex == 0) {
stageEnabled = args.enable;
} else {
auto stageRegPrefix = getStagePrefix(stageIndex);
auto enableRegName = (stageRegPrefix.strref() + "_enable").str();
Value enableRegResetVal =
builder.create<hw::ConstantOp>(loc, APInt(1, 0, false)).getResult();
switch (stageKind) {
case StageKind::Continuous:
LLVM_FALLTHROUGH;
case StageKind::NonStallable:
stageEnabled = builder.create<seq::CompRegOp>(
loc, args.enable, args.clock, args.reset, enableRegResetVal,
enableRegName);
break;
case StageKind::Stallable:
stageEnabled = builder.create<seq::CompRegClockEnabledOp>(
loc, args.enable, args.clock,
comb::createOrFoldNot(loc, args.stall, builder), args.reset,
enableRegResetVal, enableRegName);
break;
case StageKind::Runoff:
assert(args.lnsEn &&
"Expected an LNS signal if this was a runoff stage");
stageEnabled = builder.create<seq::CompRegClockEnabledOp>(
loc, args.enable, args.clock,
builder.create<comb::OrOp>(
loc, args.lnsEn,
comb::createOrFoldNot(loc, args.stall, builder)),
args.reset, enableRegResetVal, enableRegName);
break;
}
}
// Replace the stage valid signal.
pipeline.getStageEnableSignal(stage).replaceAllUsesWith(args.enable);
args.enable = stageEnabled;
pipeline.getStageEnableSignal(stage).replaceAllUsesWith(stageEnabled);
// Determine stage egress info.
auto nextStage = dyn_cast<StageOp>(stage->getTerminator());
auto nextStage = dyn_cast<StageOp>(terminator);
StageEgressNames egressNames;
if (nextStage)
getStageEgressNames(stageIndex, nextStage,
@ -357,6 +420,11 @@ public:
llvm::append_range(nextStageArgs, stageRets.regs);
llvm::append_range(nextStageArgs, stageRets.passthroughs);
args.enable = stageRets.valid;
if (stageRets.lnsEn) {
// Swap the lnsEn signal if the current stage lowering generated an
// lnsEn.
args.lnsEn = stageRets.lnsEn;
}
args.data = nextStageArgs;
return lowerStage(nextStage.getNextStage(), args, stageIndex + 1);
}
@ -366,7 +434,7 @@ public:
llvm::SmallVector<Value> pipelineReturns;
llvm::append_range(pipelineReturns, returnOp.getInputs());
// The last stage valid signal is the 'done' output of the pipeline.
pipelineReturns.push_back(args.enable);
pipelineReturns.push_back(stageRets.valid);
pipeline.replaceAllUsesWith(pipelineReturns);
return stageRets;
}
@ -382,8 +450,8 @@ struct PipelineToHWPass : public PipelineToHWBase<PipelineToHWPass> {
private:
// Lowers pipelines within HWModules. This pass is currently expecting that
// Pipelines are always nested with HWModule's but could be written to be more
// generic.
// Pipelines are always nested with HWModule's but could be written to be
// more generic.
void runOnHWModule(hw::HWModuleOp mod);
};
@ -396,8 +464,8 @@ void PipelineToHWPass::runOnHWModule(hw::HWModuleOp mod) {
OpBuilder builder(&getContext());
// Iterate over each pipeline op in the module and convert.
// Note: This pass matches on `hw::ModuleOp`s and not directly on the
// `ScheduledPipelineOp` due to the `ScheduledPipelineOp` being erased during
// this pass.
// `ScheduledPipelineOp` due to the `ScheduledPipelineOp` being erased
// during this pass.
size_t pipelinesSeen = 0;
for (auto pipeline :
llvm::make_early_inc_range(mod.getOps<ScheduledPipelineOp>())) {

View File

@ -269,15 +269,15 @@ static void printPipelineOp(OpAsmPrinter &p, TPipelineOp op) {
printKeywordAssignment(p, "reset", op.getInnerReset(), op.getReset());
p << " ";
printKeywordAssignment(p, "go", op.getInnerGo(), op.getGo());
// Print the optional attribute dict.
p.printOptionalAttrDict(op->getAttrs(),
/*elidedAttrs=*/{"name", "operandSegmentSizes",
"outputNames", "inputNames"});
p << " -> ";
// Print the output list.
printOutputList(p, op.getDataOutputs().getTypes(), op.getOutputNames());
// Print the optional attribute dict.
p.printOptionalAttrDict(op->getAttrs(),
/*elidedAttrs=*/{"name", "operandSegmentSizes",
"outputNames", "inputNames"});
p << " ";
// Print the inner region, eliding the entry block arguments - we've already
@ -314,7 +314,7 @@ void ScheduledPipelineOp::build(OpBuilder &odsBuilder, OperationState &odsState,
TypeRange dataOutputs, ValueRange inputs,
ArrayAttr inputNames, ArrayAttr outputNames,
Value clock, Value reset, Value go, Value stall,
StringAttr name) {
StringAttr name, ArrayAttr stallability) {
odsState.addOperands(inputs);
if (stall)
odsState.addOperands(stall);
@ -359,6 +359,9 @@ void ScheduledPipelineOp::build(OpBuilder &odsBuilder, OperationState &odsState,
// entry stage valid signal.
entryBlock.addArgument(i1, odsState.location);
if (stallability)
odsState.addAttribute("stallability", stallability);
}
Block *ScheduledPipelineOp::addStage() {
@ -558,9 +561,61 @@ LogicalResult ScheduledPipelineOp::verify() {
}
}
if (auto stallability = getStallability()) {
// Only allow specifying stallability if there is a stall signal.
if (!hasStall())
return emitOpError("cannot specify stallability without a stall signal.");
// Ensure that the # of stages is equal to the length of the stallability
// array - the exit stage is never stallable.
size_t nRegisterStages = stages.size() - 1;
if (stallability->size() != nRegisterStages)
return emitOpError("stallability array must be the same length as the "
"number of stages. Pipeline has ")
<< nRegisterStages << " stages but array had "
<< stallability->size() << " elements.";
}
return success();
}
StageKind ScheduledPipelineOp::getStageKind(size_t stageIndex) {
size_t nStages = getNumStages();
assert(stageIndex < nStages && "invalid stage index");
if (!hasStall())
return StageKind::Continuous;
// There is a stall signal - also check whether stage-level stallability is
// specified.
std::optional<ArrayAttr> stallability = getStallability();
if (!stallability) {
// All stages are stallable.
return StageKind::Stallable;
}
if (stageIndex < stallability->size()) {
bool stageIsStallable =
(*stallability)[stageIndex].cast<BoolAttr>().getValue();
if (!stageIsStallable) {
// This is a non-stallable stage.
return StageKind::NonStallable;
}
}
// Walk backwards from this stage to see if any non-stallable stage exists.
// If so, this is a runoff stage.
// TODO: This should be a pre-computed property.
if (stageIndex == 0)
return StageKind::Stallable;
for (size_t i = stageIndex - 1; i > 0; --i) {
if (getStageKind(i) == StageKind::NonStallable)
return StageKind::Runoff;
}
return StageKind::Stallable;
}
//===----------------------------------------------------------------------===//
// ReturnOp
//===----------------------------------------------------------------------===//

View File

@ -15,15 +15,15 @@ hw.module @testBasic(%arg0: i1, %clk: i1, %rst: i1) -> (out: i1) {
// CHECK-SAME: %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i32, %[[VAL_2:.*]]: i1, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: i1) -> (out: i32, done: i1) {
// CHECK: %[[VAL_5:.*]] = comb.add %[[VAL_0]], %[[VAL_0]] : i32
// CHECK: %[[VAL_6:.*]] = hw.constant false
// CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_6]] : i1
// CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_6]] : i1
// CHECK: %[[VAL_8:.*]] = hw.constant false
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage1_valid %[[VAL_7]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage2_enable %[[VAL_7]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_10:.*]] = seq.compreg sym @p0_stage2_reg0 %[[VAL_5]], %[[VAL_3]] : i32
// CHECK: %[[VAL_11:.*]] = hw.constant false
// CHECK: %[[VAL_12:.*]] = seq.compreg sym @p0_stage2_valid %[[VAL_9]], %[[VAL_3]], %[[VAL_4]], %[[VAL_11]] : i1
// CHECK: %[[VAL_12:.*]] = seq.compreg sym @p0_stage3_enable %[[VAL_9]], %[[VAL_3]], %[[VAL_4]], %[[VAL_11]] : i1
// CHECK: %[[VAL_13:.*]] = seq.compreg sym @p0_stage3_reg0 %[[VAL_10]], %[[VAL_3]] : i32
// CHECK: %[[VAL_14:.*]] = hw.constant false
// CHECK: %[[VAL_15:.*]] = seq.compreg sym @p0_stage3_valid %[[VAL_12]], %[[VAL_3]], %[[VAL_4]], %[[VAL_14]] : i1
// CHECK: %[[VAL_15:.*]] = seq.compreg sym @p0_stage4_enable %[[VAL_12]], %[[VAL_3]], %[[VAL_4]], %[[VAL_14]] : i1
// CHECK: hw.output %[[VAL_13]], %[[VAL_15]] : i32, i1
// CHECK: }
hw.module @testLatency1(%arg0: i32, %arg1: i32, %go: i1, %clk: i1, %rst: i1) -> (out: i32, done: i1) {
@ -51,7 +51,7 @@ hw.module @testLatency1(%arg0: i32, %arg1: i32, %go: i1, %clk: i1, %rst: i1) ->
// CHECK: %[[VAL_6:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_5]], %[[VAL_3]] : i32
// CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage0_reg1 %[[VAL_0]], %[[VAL_3]] : i32
// CHECK: %[[VAL_8:.*]] = hw.constant false
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_6]], %[[VAL_7]] : i32
// CHECK: hw.output %[[VAL_10]], %[[VAL_9]] : i32, i1
// CHECK: }
@ -72,23 +72,23 @@ hw.module @testSingle(%arg0: i32, %arg1: i32, %go: i1, %clk: i1, %rst: i1) -> (o
// CHECK: %[[VAL_6:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_5]], %[[VAL_3]] : i32
// CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage0_reg1 %[[VAL_0]], %[[VAL_3]] : i32
// CHECK: %[[VAL_8:.*]] = hw.constant false
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_6]], %[[VAL_7]] : i32
// CHECK: %[[VAL_11:.*]] = seq.compreg sym @p0_stage1_reg0 %[[VAL_10]], %[[VAL_3]] : i32
// CHECK: %[[VAL_12:.*]] = seq.compreg sym @p0_stage1_reg1 %[[VAL_6]], %[[VAL_3]] : i32
// CHECK: %[[VAL_13:.*]] = hw.constant false
// CHECK: %[[VAL_14:.*]] = seq.compreg sym @p0_stage1_valid %[[VAL_9]], %[[VAL_3]], %[[VAL_4]], %[[VAL_13]] : i1
// CHECK: %[[VAL_14:.*]] = seq.compreg sym @p0_stage2_enable %[[VAL_9]], %[[VAL_3]], %[[VAL_4]], %[[VAL_13]] : i1
// CHECK: %[[VAL_15:.*]] = comb.mul %[[VAL_11]], %[[VAL_12]] : i32
// CHECK: %[[VAL_16:.*]] = comb.sub %[[VAL_15]], %[[VAL_1]] : i32
// CHECK: %[[VAL_17:.*]] = seq.compreg sym @p1_stage0_reg0 %[[VAL_16]], %[[VAL_3]] : i32
// CHECK: %[[VAL_18:.*]] = seq.compreg sym @p1_stage0_reg1 %[[VAL_15]], %[[VAL_3]] : i32
// CHECK: %[[VAL_19:.*]] = hw.constant false
// CHECK: %[[VAL_20:.*]] = seq.compreg sym @p1_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_19]] : i1
// CHECK: %[[VAL_20:.*]] = seq.compreg sym @p1_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_19]] : i1
// CHECK: %[[VAL_21:.*]] = comb.add %[[VAL_17]], %[[VAL_18]] : i32
// CHECK: %[[VAL_22:.*]] = seq.compreg sym @p1_stage1_reg0 %[[VAL_21]], %[[VAL_3]] : i32
// CHECK: %[[VAL_23:.*]] = seq.compreg sym @p1_stage1_reg1 %[[VAL_17]], %[[VAL_3]] : i32
// CHECK: %[[VAL_24:.*]] = hw.constant false
// CHECK: %[[VAL_25:.*]] = seq.compreg sym @p1_stage1_valid %[[VAL_20]], %[[VAL_3]], %[[VAL_4]], %[[VAL_24]] : i1
// CHECK: %[[VAL_25:.*]] = seq.compreg sym @p1_stage2_enable %[[VAL_20]], %[[VAL_3]], %[[VAL_4]], %[[VAL_24]] : i1
// CHECK: %[[VAL_26:.*]] = comb.mul %[[VAL_22]], %[[VAL_23]] : i32
// CHECK: hw.output %[[VAL_15]], %[[VAL_14]] : i32, i1
// CHECK: }
@ -124,11 +124,11 @@ hw.module @testMultiple(%arg0: i32, %arg1: i32, %go: i1, %clk: i1, %rst: i1) ->
// CHECK: %[[VAL_6:.*]] = comb.sub %[[VAL_0]], %[[VAL_0]] : i32
// CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_6]], %[[VAL_3]] : i32
// CHECK: %[[VAL_8:.*]] = hw.constant false
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_8]] : i1
// CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_7]], %[[VAL_1]] : i32
// CHECK: %[[VAL_11:.*]] = seq.compreg sym @p0_stage1_reg0 %[[VAL_10]], %[[VAL_3]] : i32
// CHECK: %[[VAL_12:.*]] = hw.constant false
// CHECK: %[[VAL_13:.*]] = seq.compreg sym @p0_stage1_valid %[[VAL_9]], %[[VAL_3]], %[[VAL_4]], %[[VAL_12]] : i1
// CHECK: %[[VAL_13:.*]] = seq.compreg sym @p0_stage2_enable %[[VAL_9]], %[[VAL_3]], %[[VAL_4]], %[[VAL_12]] : i1
// CHECK: hw.output %[[VAL_11]], %[[VAL_1]] : i32, i32
// CHECK: }
hw.module @testSingleWithExt(%arg0: i32, %ext1: i32, %go : i1, %clk: i1, %rst: i1) -> (out0: i32, out1: i32) {
@ -159,7 +159,7 @@ hw.module @testSingleWithExt(%arg0: i32, %ext1: i32, %go : i1, %clk: i1, %rst: i
// CHECK: sv.assign %[[VAL_5]], %[[VAL_8]] : i32
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_8]], %[[VAL_2]] : i32
// CHECK: %[[VAL_10:.*]] = hw.constant false
// CHECK: %[[VAL_11:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_1]], %[[VAL_2]], %[[VAL_3]], %[[VAL_10]] : i1
// CHECK: %[[VAL_11:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_1]], %[[VAL_2]], %[[VAL_3]], %[[VAL_10]] : i1
// CHECK: %[[VAL_12:.*]] = sv.wire : !hw.inout<i32>
// CHECK: %[[VAL_13:.*]] = sv.read_inout %[[VAL_12]] : !hw.inout<i32>
// CHECK: %[[VAL_14:.*]] = comb.add %[[VAL_13]], %[[VAL_9]] : i32
@ -167,7 +167,7 @@ hw.module @testSingleWithExt(%arg0: i32, %ext1: i32, %go : i1, %clk: i1, %rst: i
// CHECK: sv.assign %[[VAL_12]], %[[VAL_15]] : i32
// CHECK: %[[VAL_16:.*]] = seq.compreg sym @p0_stage1_reg0 %[[VAL_15]], %[[VAL_2]] : i32
// CHECK: %[[VAL_17:.*]] = hw.constant false
// CHECK: %[[VAL_18:.*]] = seq.compreg sym @p0_stage1_valid %[[VAL_11]], %[[VAL_2]], %[[VAL_3]], %[[VAL_17]] : i1
// CHECK: %[[VAL_18:.*]] = seq.compreg sym @p0_stage2_enable %[[VAL_11]], %[[VAL_2]], %[[VAL_3]], %[[VAL_17]] : i1
// CHECK: %[[VAL_19:.*]] = sv.wire : !hw.inout<i32>
// CHECK: %[[VAL_20:.*]] = sv.read_inout %[[VAL_19]] : !hw.inout<i32>
// CHECK: %[[VAL_21:.*]] = comb.add %[[VAL_20]], %[[VAL_16]] : i32
@ -211,11 +211,16 @@ hw.module @testControlUsage(%arg0: i32, %go : i1, %clk: i1, %rst: i1) -> (out0:
// CHECK-SAME: %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i1, %[[VAL_2:.*]]: i1, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: i1) -> (out0: i32, out1: i1) {
// CHECK: %[[VAL_5:.*]] = hw.constant true
// CHECK: %[[VAL_6:.*]] = comb.xor %[[VAL_2]], %[[VAL_5]] : i1
// CHECK: %[[VAL_7:.*]] = comb.and %[[VAL_1]], %[[VAL_6]] : i1
// CHECK: %[[VAL_8:.*]] = seq.compreg.ce sym @p0_stage0_reg0 %[[VAL_0]], %[[VAL_3]], %[[VAL_7]] : i32
// CHECK: %[[VAL_7:.*]] = comb.and %[[VAL_1]], %[[VAL_6]]
// CHECK: %[[VAL_8:.*]] = seq.compreg.ce sym @p0_stage0_reg0 %[[VAL_0]], %[[VAL_3]], %[[VAL_7]] : i32, i1
// CHECK: %[[VAL_9:.*]] = hw.constant false
// CHECK: %[[VAL_10:.*]] = seq.compreg.ce sym @p0_stage0_valid %[[VAL_1]], %[[VAL_3]], %[[VAL_6]], %[[VAL_4]], %[[VAL_9]] : i1
// CHECK: hw.output %[[VAL_8]], %[[VAL_10]] : i32, i1
// CHECK: %[[VAL_10:.*]] = hw.constant true
// CHECK: %[[VAL_11:.*]] = comb.xor %[[VAL_2]], %[[VAL_10]] : i1
// CHECK: %[[VAL_12:.*]] = seq.compreg.ce sym @p0_stage1_enable %[[VAL_7]], %[[VAL_3]], %[[VAL_11]], %[[VAL_4]], %[[VAL_9]] : i1, i1
// CHECK: %[[VAL_13:.*]] = hw.constant true
// CHECK: %[[VAL_14:.*]] = comb.xor %[[VAL_2]], %[[VAL_13]] : i1
// CHECK: %[[VAL_15:.*]] = comb.and %[[VAL_12]], %[[VAL_14]]
// CHECK: hw.output %[[VAL_8]], %[[VAL_15]] : i32, i1
// CHECK: }
hw.module @testWithStall(%arg0: i32, %go: i1, %stall : i1, %clk: i1, %rst: i1) -> (out0: i32, out1: i1) {
%0:2 = pipeline.scheduled(%a0 : i32 = %arg0) stall(%s = %stall) clock(%c = %clk) reset(%r = %rst) go(%g = %go) -> (out: i32) {
@ -225,3 +230,51 @@ hw.module @testWithStall(%arg0: i32, %go: i1, %stall : i1, %clk: i1, %rst: i1) -
}
hw.output %0#0, %0#1 : i32, i1
}
// -----
// CHECK-LABEL: hw.module @testStallability(
// CHECK-SAME: %[[VAL_0:.*]]: i32, %[[VAL_1:.*]]: i1, %[[VAL_2:.*]]: i1, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: i1) -> (out: i32) {
// CHECK: %[[VAL_5:.*]] = hw.constant true
// CHECK: %[[VAL_6:.*]] = comb.xor %[[VAL_4]], %[[VAL_5]] : i1
// CHECK: %[[VAL_7:.*]] = comb.and %[[VAL_1]], %[[VAL_6]]
// CHECK: %[[VAL_8:.*]] = seq.compreg.ce sym @MyPipeline_a0 %[[VAL_0]], %[[VAL_2]], %[[VAL_7]] : i32, i1
// CHECK: %[[VAL_9:.*]] = hw.constant false
// CHECK: %[[VAL_10:.*]] = seq.compreg sym @MyPipeline_stage1_enable %[[VAL_7]], %[[VAL_2]], %[[VAL_3]], %[[VAL_9]] : i1, i1
// CHECK: %[[VAL_11:.*]] = seq.compreg.ce sym @MyPipeline_a0 %[[VAL_8]], %[[VAL_2]], %[[VAL_10]] {name = "MyPipeline_a0"} : i32, i1
// CHECK: %[[VAL_12:.*]] = hw.constant false
// CHECK: %[[VAL_13:.*]] = hw.constant true
// CHECK: %[[VAL_14:.*]] = comb.xor %[[VAL_4]], %[[VAL_13]] : i1
// CHECK: %[[VAL_15:.*]] = comb.or %[[VAL_10]], %[[VAL_14]] : i1
// CHECK: %[[VAL_16:.*]] = seq.compreg.ce sym @MyPipeline_stage2_enable %[[VAL_10]], %[[VAL_2]], %[[VAL_15]], %[[VAL_3]], %[[VAL_12]] : i1, i1
// CHECK: %[[VAL_17:.*]] = hw.constant true
// CHECK: %[[VAL_18:.*]] = comb.xor %[[VAL_4]], %[[VAL_17]] : i1
// CHECK: %[[VAL_19:.*]] = comb.or %[[VAL_10]], %[[VAL_18]] : i1
// CHECK: %[[VAL_20:.*]] = comb.and %[[VAL_16]], %[[VAL_19]]
// CHECK: %[[VAL_21:.*]] = seq.compreg.ce sym @MyPipeline_a0 %[[VAL_11]], %[[VAL_2]], %[[VAL_20]] {name = "MyPipeline_a0"} : i32, i1
// CHECK: %[[VAL_22:.*]] = hw.constant false
// CHECK: %[[VAL_23:.*]] = hw.constant true
// CHECK: %[[VAL_24:.*]] = comb.xor %[[VAL_4]], %[[VAL_23]] : i1
// CHECK: %[[VAL_25:.*]] = comb.or %[[VAL_10]], %[[VAL_24]] : i1
// CHECK: %[[VAL_26:.*]] = seq.compreg.ce sym @MyPipeline_stage3_enable %[[VAL_20]], %[[VAL_2]], %[[VAL_25]], %[[VAL_3]], %[[VAL_22]] : i1, i1
// CHECK: %[[VAL_27:.*]] = hw.constant true
// CHECK: %[[VAL_28:.*]] = comb.xor %[[VAL_4]], %[[VAL_27]] : i1
// CHECK: %[[VAL_29:.*]] = comb.or %[[VAL_10]], %[[VAL_28]] : i1
// CHECK: %[[VAL_30:.*]] = comb.and %[[VAL_26]], %[[VAL_29]]
// CHECK: hw.output %[[VAL_21]] : i32
// CHECK: }
hw.module @testStallability(%arg0: i32, %go: i1, %clk: i1, %rst: i1, %stall: i1) -> (out: i32) {
%out, %done = pipeline.scheduled "MyPipeline"(%a0 : i32 = %arg0)
stall(%s = %stall) clock(%c = %clk) reset(%r = %rst) go(%g = %go)
{stallability = [true, false, true]} -> (out : i32) {
pipeline.stage ^bb1 regs("a0" = %a0 : i32)
^bb1(%a0_0: i32, %s1_enable: i1): // pred: ^bb0
pipeline.stage ^bb2 regs("a0" = %a0_0 : i32)
^bb2(%a0_1: i32, %s2_enable: i1): // pred: ^bb1
pipeline.stage ^bb3 regs("a0" = %a0_1 : i32)
^bb3(%a0_2: i32, %s3_enable: i1): // pred: ^bb2
pipeline.return %a0_2 : i32
}
hw.output %out : i32
}

View File

@ -8,7 +8,7 @@
// CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_5]], %[[VAL_6]] : i32
// CHECK: %[[VAL_8:.*]] = seq.compreg sym @p0_stage0_reg1 %[[VAL_0]], %[[VAL_6]] : i32
// CHECK: %[[VAL_9:.*]] = hw.constant false
// CHECK: %[[VAL_10:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_9]] : i1
// CHECK: %[[VAL_10:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_9]] : i1
// CHECK: %[[VAL_11:.*]] = comb.add %[[VAL_7]], %[[VAL_8]] : i32
// CHECK: hw.output %[[VAL_11]], %[[VAL_10]] : i32, i1
// CHECK: }

View File

@ -10,7 +10,7 @@
// CHECK: %[[VAL_8:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_5]], %[[VAL_3]] : i32
// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage0_reg1 %[[VAL_0]], %[[VAL_3]] : i32
// CHECK: %[[VAL_10:.*]] = hw.constant false
// CHECK: %[[VAL_11:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_10]] : i1
// CHECK: %[[VAL_11:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_10]] : i1
// CHECK: %[[VAL_12:.*]] = comb.add %[[VAL_8]], %[[VAL_9]] : i32
// CHECK: hw.output %[[VAL_12]], %[[VAL_11]] : i32, i1
// CHECK: }
@ -26,7 +26,7 @@
// CGATE: %[[VAL_11:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_5]], %[[VAL_10]] : i32
// CGATE: %[[VAL_12:.*]] = seq.compreg sym @p0_stage0_reg1 %[[VAL_0]], %[[VAL_8]] : i32
// CGATE: %[[VAL_13:.*]] = hw.constant false
// CGATE: %[[VAL_14:.*]] = seq.compreg sym @p0_stage0_valid %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_13]] : i1
// CGATE: %[[VAL_14:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]], %[[VAL_4]], %[[VAL_13]] : i1
// CGATE: %[[VAL_15:.*]] = comb.add %[[VAL_11]], %[[VAL_12]] : i32
// CGATE: hw.output %[[VAL_15]], %[[VAL_14]] : i32, i1
// CGATE: }

View File

@ -180,3 +180,39 @@ hw.module @invalid_clock_gate(%arg : i32, %go : i1, %clk : i1, %rst : i1) -> ()
}
hw.output
}
// -----
hw.module @noStallSignalWithStallability(%arg0 : i32, %go : i1, %clk : i1, %rst : i1) -> (out: i32) {
// expected-error @+1 {{'pipeline.scheduled' op cannot specify stallability without a stall signal.}}
%0:2 = pipeline.scheduled "MyPipeline"(%a0 : i32 = %arg0) clock(%c = %clk) reset(%r = %rst) go(%g = %go)
{stallability = [true, false, true]}
-> (out: i32) {
pipeline.stage ^bb1
^bb1(%s1_enable : i1):
pipeline.stage ^bb2
^bb2(%s2_enable : i1):
pipeline.stage ^bb3
^bb3(%s3_enable : i1):
pipeline.return %a0 : i32
}
hw.output %0 : i32
}
// -----
hw.module @incorrectStallabilitySize(%arg0 : i32, %go : i1, %clk : i1, %rst : i1, %stall : i1) -> (out: i32) {
// expected-error @+1 {{'pipeline.scheduled' op stallability array must be the same length as the number of stages. Pipeline has 3 stages but array had 2 elements.}}
%0:2 = pipeline.scheduled "MyPipeline"(%a0 : i32 = %arg0) stall(%s = %stall) clock(%c = %clk) reset(%r = %rst) go(%g = %go)
{stallability = [true, false]}
-> (out: i32) {
pipeline.stage ^bb1
^bb1(%s1_enable : i1):
pipeline.stage ^bb2
^bb2(%s2_enable : i1):
pipeline.stage ^bb3
^bb3(%s3_enable : i1):
pipeline.return %a0 : i32
}
hw.output %0 : i32
}

View File

@ -156,3 +156,20 @@ hw.module @withNames(%arg0 : i32, %arg1 : i32, %go : i1, %clk : i1, %rst : i1) -
}
hw.output %0 : i32
}
// CHECK-LABEL: hw.module @withStallability(
// CHECK: %out, %done = pipeline.scheduled "MyPipeline"(%a0 : i32 = %arg0) stall(%s = %stall) clock(%c = %clk) reset(%r = %rst) go(%g = %go) {stallability = [true, false, true]} -> (out : i32)
hw.module @withStallability(%arg0 : i32, %go : i1, %clk : i1, %rst : i1, %stall : i1) -> (out: i32) {
%0:2 = pipeline.scheduled "MyPipeline"(%a0 : i32 = %arg0) stall(%s = %stall) clock(%c = %clk) reset(%r = %rst) go(%g = %go)
{stallability = [true, false, true]}
-> (out: i32) {
pipeline.stage ^bb1
^bb1(%s1_enable : i1):
pipeline.stage ^bb2
^bb2(%s2_enable : i1):
pipeline.stage ^bb3
^bb3(%s3_enable : i1):
pipeline.return %a0 : i32
}
hw.output %0 : i32
}