CIRCT  19.0.0git
PipelineToHW.cpp
Go to the documentation of this file.
1 //===- PipelineToHW.cpp - Translate Pipeline into HW ----------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 
7 //===----------------------------------------------------------------------===//
8 //
9 // This is the main Pipeline to HW Conversion Pass Implementation.
10 //
11 //===----------------------------------------------------------------------===//
12 
15 #include "circt/Dialect/HW/HWOps.h"
18 #include "mlir/IR/Builders.h"
19 #include "mlir/Pass/Pass.h"
20 #include "llvm/ADT/TypeSwitch.h"
21 
22 namespace circt {
23 #define GEN_PASS_DEF_PIPELINETOHW
24 #include "circt/Conversion/Passes.h.inc"
25 } // namespace circt
26 
27 using namespace mlir;
28 using namespace circt;
29 using namespace pipeline;
30 
31 namespace {
32 // Base class for all pipeline lowerings.
33 class PipelineLowering {
34 public:
35  PipelineLowering(size_t pipelineID, ScheduledPipelineOp pipeline,
36  OpBuilder &builder, bool clockGateRegs,
37  bool enablePowerOnValues)
38  : pipelineID(pipelineID), pipeline(pipeline), builder(builder),
39  clockGateRegs(clockGateRegs), enablePowerOnValues(enablePowerOnValues) {
40  parentClk = pipeline.getClock();
41  parentRst = pipeline.getReset();
42  parentModule = pipeline->getParentOfType<hw::HWModuleOp>();
43  }
44  virtual ~PipelineLowering() = default;
45 
46  virtual LogicalResult run() = 0;
47 
48  // Arguments used for emitting the body of a stage module. These values must
49  // be within the scope of the stage module body.
50  struct StageArgs {
51  ValueRange data;
52  Value enable;
53  Value stall;
54  Value clock;
55  Value reset;
56  Value lnsEn;
57  };
58 
59  // Arguments used for returning the results from a stage. These values must
60  // be within the scope of the stage module body.
61  struct StageReturns {
62  llvm::SmallVector<Value> regs;
63  llvm::SmallVector<Value> passthroughs;
64  Value valid;
65 
66  // In case this was the last register in a non-stallable register chain, the
67  // register will also return its enable signal to be used for LNS of
68  // downstream stages.
69  Value lnsEn;
70  };
71 
72  virtual FailureOr<StageReturns>
73  lowerStage(Block *stage, StageArgs args, size_t stageIndex,
74  llvm::ArrayRef<Attribute> inputNames = {}) = 0;
75 
76  StageReturns emitStageBody(Block *stage, StageArgs args,
77  llvm::ArrayRef<Attribute> registerNames,
78  size_t stageIndex = -1) {
79  assert(args.enable && "enable not set");
80  auto *terminator = stage->getTerminator();
81 
82  // Move the stage operations into the current insertion point.
83  for (auto &op : llvm::make_early_inc_range(*stage)) {
84  if (&op == terminator)
85  continue;
86 
87  if (auto latencyOp = dyn_cast<LatencyOp>(op)) {
88  // For now, just directly emit the body of the latency op. The latency
89  // op is mainly used during register materialization. At a later stage,
90  // we may want to add some TCL-related things here to communicate
91  // multicycle paths.
92  Block *latencyOpBody = latencyOp.getBodyBlock();
93  for (auto &innerOp :
94  llvm::make_early_inc_range(latencyOpBody->without_terminator()))
95  innerOp.moveBefore(builder.getInsertionBlock(),
96  builder.getInsertionPoint());
97  latencyOp.replaceAllUsesWith(
98  latencyOpBody->getTerminator()->getOperands());
99  latencyOp.erase();
100  } else {
101  op.moveBefore(builder.getInsertionBlock(), builder.getInsertionPoint());
102  }
103  }
104 
105  auto loc = terminator->getLoc();
106  Value notStalled;
107  auto getOrSetNotStalled = [&]() {
108  if (!notStalled) {
109  notStalled = comb::createOrFoldNot(loc, args.stall, builder);
110  }
111  return notStalled;
112  };
113 
114  // Determine the stage kind. This will influence how the stage valid and
115  // enable signals are defined.
116  StageKind stageKind = pipeline.getStageKind(stageIndex);
117  Value stageValid;
118  StringAttr validSignalName =
119  builder.getStringAttr(getStagePrefix(stageIndex).strref() + "_valid");
120  switch (stageKind) {
121  case StageKind::Continuous:
122  LLVM_FALLTHROUGH;
123  case StageKind::NonStallable:
124  stageValid = args.enable;
125  break;
126  case StageKind::Stallable:
127  stageValid =
128  builder.create<comb::AndOp>(loc, args.enable, getOrSetNotStalled());
129  stageValid.getDefiningOp()->setAttr("sv.namehint", validSignalName);
130  break;
131  case StageKind::Runoff:
132  assert(args.lnsEn && "Expected an LNS signal if this was a runoff stage");
133  stageValid = builder.create<comb::AndOp>(
134  loc, args.enable,
135  builder.create<comb::OrOp>(loc, args.lnsEn, getOrSetNotStalled()));
136  stageValid.getDefiningOp()->setAttr("sv.namehint", validSignalName);
137  break;
138  }
139 
140  StageReturns rets;
141  auto stageOp = dyn_cast<StageOp>(terminator);
142  if (!stageOp) {
143  assert(isa<ReturnOp>(terminator) && "expected ReturnOp");
144  // This was the pipeline return op - the return op/last stage doesn't
145  // register its operands, hence, all return operands are passthrough
146  // and the valid signal is equal to the unregistered enable signal.
147  rets.passthroughs = terminator->getOperands();
148  rets.valid = stageValid;
149  return rets;
150  }
151 
152  assert(registerNames.size() == stageOp.getRegisters().size() &&
153  "register names and registers must be the same size");
154 
155  bool isStallablePipeline = stageKind != StageKind::Continuous;
156  Value notStalledClockGate;
157  if (this->clockGateRegs) {
158  // Create the top-level clock gate.
159  notStalledClockGate = builder.create<seq::ClockGateOp>(
160  loc, args.clock, stageValid, /*test_enable=*/Value(),
161  /*inner_sym=*/hw::InnerSymAttr());
162  }
163 
164  for (auto it : llvm::enumerate(stageOp.getRegisters())) {
165  auto regIdx = it.index();
166  auto regIn = it.value();
167 
168  StringAttr regName = cast<StringAttr>(registerNames[regIdx]);
169  Value dataReg;
170  if (this->clockGateRegs) {
171  // Use the clock gate instead of clock enable.
172  Value currClockGate = notStalledClockGate;
173  for (auto hierClockGateEnable : stageOp.getClockGatesForReg(regIdx)) {
174  // Create clock gates for any hierarchically nested clock gates.
175  currClockGate = builder.create<seq::ClockGateOp>(
176  loc, currClockGate, hierClockGateEnable,
177  /*test_enable=*/Value(),
178  /*inner_sym=*/hw::InnerSymAttr());
179  }
180  dataReg = builder.create<seq::CompRegOp>(stageOp->getLoc(), regIn,
181  currClockGate, regName);
182  } else {
183  // Only clock-enable the register if the pipeline is stallable.
184  // For non-stallable (continuous) pipelines, a data register can always
185  // be clocked.
186  if (isStallablePipeline) {
187  dataReg = builder.create<seq::CompRegClockEnabledOp>(
188  stageOp->getLoc(), regIn, args.clock, stageValid, regName);
189  } else {
190  dataReg = builder.create<seq::CompRegOp>(stageOp->getLoc(), regIn,
191  args.clock, regName);
192  }
193  }
194  rets.regs.push_back(dataReg);
195  }
196 
197  rets.valid = stageValid;
198  if (stageKind == StageKind::NonStallable)
199  rets.lnsEn = args.enable;
200 
201  rets.passthroughs = stageOp.getPassthroughs();
202  return rets;
203  }
204 
205  // A container carrying all-things stage output naming related.
206  // To avoid overloading 'output's to much (i'm trying to keep that
207  // reserved for "output" ports), this is named "egress".
208  struct StageEgressNames {
209  llvm::SmallVector<Attribute> regNames;
210  llvm::SmallVector<Attribute> outNames;
211  llvm::SmallVector<Attribute> inNames;
212  };
213 
214  // Returns a set of names for the output values of a given stage
215  // (registers and passthrough). If `withPipelinePrefix` is true, the names
216  // will be prefixed with the pipeline name.
217  void getStageEgressNames(size_t stageIndex, Operation *stageTerminator,
218  bool withPipelinePrefix,
219  StageEgressNames &egressNames) {
220  StringAttr pipelineName;
221  if (withPipelinePrefix)
222  pipelineName = getPipelineBaseName();
223 
224  if (auto stageOp = dyn_cast<StageOp>(stageTerminator)) {
225  // Registers...
226  std::string assignedRegName, assignedOutName, assignedInName;
227  for (size_t regi = 0; regi < stageOp.getRegisters().size(); ++regi) {
228  if (auto regName = stageOp.getRegisterName(regi)) {
229  assignedRegName = regName.str();
230  assignedOutName = assignedRegName + "_out";
231  assignedInName = assignedRegName + "_in";
232  } else {
233  assignedRegName =
234  ("stage" + Twine(stageIndex) + "_reg" + Twine(regi)).str();
235  assignedOutName = ("out" + Twine(regi)).str();
236  assignedInName = ("in" + Twine(regi)).str();
237  }
238 
239  if (pipelineName && !pipelineName.getValue().empty()) {
240  assignedRegName = pipelineName.str() + "_" + assignedRegName;
241  assignedOutName = pipelineName.str() + "_" + assignedOutName;
242  assignedInName = pipelineName.str() + "_" + assignedInName;
243  }
244 
245  egressNames.regNames.push_back(builder.getStringAttr(assignedRegName));
246  egressNames.outNames.push_back(builder.getStringAttr(assignedOutName));
247  egressNames.inNames.push_back(builder.getStringAttr(assignedInName));
248  }
249 
250  // Passthroughs
251  for (size_t passi = 0; passi < stageOp.getPassthroughs().size();
252  ++passi) {
253  if (auto passName = stageOp.getPassthroughName(passi)) {
254  assignedOutName = (passName.strref() + "_out").str();
255  assignedInName = (passName.strref() + "_in").str();
256  } else {
257  assignedOutName = ("pass" + Twine(passi)).str();
258  assignedInName = ("pass" + Twine(passi)).str();
259  }
260 
261  if (pipelineName && !pipelineName.getValue().empty()) {
262  assignedOutName = pipelineName.str() + "_" + assignedOutName;
263  assignedInName = pipelineName.str() + "_" + assignedInName;
264  }
265 
266  egressNames.outNames.push_back(builder.getStringAttr(assignedOutName));
267  egressNames.inNames.push_back(builder.getStringAttr(assignedInName));
268  }
269  } else {
270  // For the return op, we just inherit the names of the top-level
271  // pipeline as stage output names.
272  llvm::copy(pipeline.getOutputNames().getAsRange<StringAttr>(),
273  std::back_inserter(egressNames.outNames));
274  }
275  }
276 
277  // Returns a string to be used as a prefix for all stage registers.
278  virtual StringAttr getStagePrefix(size_t stageIdx) = 0;
279 
280 protected:
281  // Determine a reasonable name for the pipeline. This will affect naming
282  // of things such as stage registers.
283  StringAttr getPipelineBaseName() {
284  if (auto nameAttr = pipeline.getNameAttr())
285  return nameAttr;
286  return StringAttr::get(pipeline.getContext(), "p" + Twine(pipelineID));
287  }
288 
289  // Parent module clock.
290  Value parentClk;
291  // Parent module reset.
292  Value parentRst;
293  // ID of the current pipeline, used for naming.
294  size_t pipelineID;
295  // The current pipeline to be converted.
296  ScheduledPipelineOp pipeline;
297 
298  // The module wherein the pipeline resides.
299  hw::HWModuleOp parentModule;
300 
301  OpBuilder &builder;
302 
303  // If true, will use clock gating for registers instead of input muxing.
304  bool clockGateRegs;
305 
306  // If true, will add power-on values to the control registers of the design.
307  bool enablePowerOnValues;
308 
309  // Name of this pipeline - used for naming stages and registers.
310  // Implementation defined.
311  StringAttr pipelineName;
312 };
313 
314 class PipelineInlineLowering : public PipelineLowering {
315 public:
316  using PipelineLowering::PipelineLowering;
317 
318  StringAttr getStagePrefix(size_t stageIdx) override {
319  if (pipelineName && !pipelineName.getValue().empty())
320  return builder.getStringAttr(pipelineName.strref() + "_stage" +
321  Twine(stageIdx));
322  return builder.getStringAttr("stage" + Twine(stageIdx));
323  }
324 
325  LogicalResult run() override {
326  pipelineName = getPipelineBaseName();
327 
328  // Replace uses of the pipeline internal inputs with the pipeline inputs.
329  for (auto [outer, inner] :
330  llvm::zip(pipeline.getInputs(), pipeline.getInnerInputs()))
331  inner.replaceAllUsesWith(outer);
332 
333  // All operations should go directly before the pipeline op, into the
334  // parent module.
335  builder.setInsertionPoint(pipeline);
336  StageArgs args;
337  args.data = pipeline.getInnerInputs();
338  args.enable = pipeline.getGo();
339  args.clock = pipeline.getClock();
340  args.reset = pipeline.getReset();
341  args.stall = pipeline.getStall();
342  if (failed(lowerStage(pipeline.getEntryStage(), args, 0)))
343  return failure();
344 
345  pipeline.erase();
346  return success();
347  }
348 
349  /// NOLINTNEXTLINE(misc-no-recursion)
350  FailureOr<StageReturns>
351  lowerStage(Block *stage, StageArgs args, size_t stageIndex,
352  llvm::ArrayRef<Attribute> /*inputNames*/ = {}) override {
353  OpBuilder::InsertionGuard guard(builder);
354  Operation *terminator = stage->getTerminator();
355  Location loc = terminator->getLoc();
356 
357  if (stage != pipeline.getEntryStage()) {
358  // Replace the internal stage inputs with the provided arguments.
359  for (auto [vInput, vArg] :
360  llvm::zip(pipeline.getStageDataArgs(stage), args.data))
361  vInput.replaceAllUsesWith(vArg);
362  }
363 
364  // Build stage enable register. The enable register is always reset to 0.
365  // The stage enable register takes the previous-stage combinational valid
366  // output and determines whether this stage is active or not in the next
367  // cycle.
368  // A non-stallable stage always registers the incoming enable signal,
369  // whereas other stages register based on the current stall state.
370  StageKind stageKind = pipeline.getStageKind(stageIndex);
371  Value stageEnabled;
372  if (stageIndex == 0) {
373  stageEnabled = args.enable;
374  } else {
375  auto stageRegPrefix = getStagePrefix(stageIndex);
376  auto enableRegName = (stageRegPrefix.strref() + "_enable").str();
377  Value enableRegResetVal =
378  builder.create<hw::ConstantOp>(loc, APInt(1, 0, false)).getResult();
379 
380  switch (stageKind) {
381  case StageKind::Continuous:
382  LLVM_FALLTHROUGH;
383  case StageKind::NonStallable:
384  stageEnabled = builder.create<seq::CompRegOp>(
385  loc, args.enable, args.clock, args.reset, enableRegResetVal,
386  enableRegName);
387  break;
388  case StageKind::Stallable:
389  stageEnabled = builder.create<seq::CompRegClockEnabledOp>(
390  loc, args.enable, args.clock,
391  comb::createOrFoldNot(loc, args.stall, builder), args.reset,
392  enableRegResetVal, enableRegName);
393  break;
394  case StageKind::Runoff:
395  assert(args.lnsEn &&
396  "Expected an LNS signal if this was a runoff stage");
397  stageEnabled = builder.create<seq::CompRegClockEnabledOp>(
398  loc, args.enable, args.clock,
399  builder.create<comb::OrOp>(
400  loc, args.lnsEn,
401  comb::createOrFoldNot(loc, args.stall, builder)),
402  args.reset, enableRegResetVal, enableRegName);
403  break;
404  }
405 
406  if (enablePowerOnValues) {
407  llvm::TypeSwitch<Operation *, void>(stageEnabled.getDefiningOp())
408  .Case<seq::CompRegOp, seq::CompRegClockEnabledOp>([&](auto op) {
409  op.getPowerOnValueMutable().assign(enableRegResetVal);
410  });
411  }
412  }
413 
414  // Replace the stage valid signal.
415  args.enable = stageEnabled;
416  pipeline.getStageEnableSignal(stage).replaceAllUsesWith(stageEnabled);
417 
418  // Determine stage egress info.
419  auto nextStage = dyn_cast<StageOp>(terminator);
420  StageEgressNames egressNames;
421  if (nextStage)
422  getStageEgressNames(stageIndex, nextStage,
423  /*withPipelinePrefix=*/true, egressNames);
424 
425  // Move stage operations into the current module.
426  builder.setInsertionPoint(pipeline);
427  StageReturns stageRets =
428  emitStageBody(stage, args, egressNames.regNames, stageIndex);
429 
430  if (nextStage) {
431  // Lower the next stage.
432  SmallVector<Value> nextStageArgs;
433  llvm::append_range(nextStageArgs, stageRets.regs);
434  llvm::append_range(nextStageArgs, stageRets.passthroughs);
435  args.enable = stageRets.valid;
436  if (stageRets.lnsEn) {
437  // Swap the lnsEn signal if the current stage lowering generated an
438  // lnsEn.
439  args.lnsEn = stageRets.lnsEn;
440  }
441  args.data = nextStageArgs;
442  return lowerStage(nextStage.getNextStage(), args, stageIndex + 1);
443  }
444 
445  // Replace the pipeline results with the return op operands.
446  auto returnOp = cast<pipeline::ReturnOp>(stage->getTerminator());
447  llvm::SmallVector<Value> pipelineReturns;
448  llvm::append_range(pipelineReturns, returnOp.getInputs());
449  // The last stage valid signal is the 'done' output of the pipeline.
450  pipelineReturns.push_back(stageRets.valid);
451  pipeline.replaceAllUsesWith(pipelineReturns);
452  return stageRets;
453  }
454 };
455 } // namespace
456 
457 //===----------------------------------------------------------------------===//
458 // Pipeline to HW Conversion Pass
459 //===----------------------------------------------------------------------===//
460 
461 namespace {
462 struct PipelineToHWPass
463  : public circt::impl::PipelineToHWBase<PipelineToHWPass> {
464  using PipelineToHWBase::PipelineToHWBase;
465  void runOnOperation() override;
466 
467 private:
468  // Lowers pipelines within HWModules. This pass is currently expecting that
469  // Pipelines are always nested with HWModule's but could be written to be
470  // more generic.
471  void runOnHWModule(hw::HWModuleOp mod);
472 };
473 
474 void PipelineToHWPass::runOnOperation() {
475  for (auto hwMod : getOperation().getOps<hw::HWModuleOp>())
476  runOnHWModule(hwMod);
477 }
478 
479 void PipelineToHWPass::runOnHWModule(hw::HWModuleOp mod) {
480  OpBuilder builder(&getContext());
481  // Iterate over each pipeline op in the module and convert.
482  // Note: This pass matches on `hw::ModuleOp`s and not directly on the
483  // `ScheduledPipelineOp` due to the `ScheduledPipelineOp` being erased
484  // during this pass.
485  size_t pipelinesSeen = 0;
486  for (auto pipeline :
487  llvm::make_early_inc_range(mod.getOps<ScheduledPipelineOp>())) {
488  if (failed(PipelineInlineLowering(pipelinesSeen, pipeline, builder,
489  clockGateRegs, enablePowerOnValues)
490  .run())) {
491  signalPassFailure();
492  return;
493  }
494  ++pipelinesSeen;
495  }
496 }
497 
498 } // namespace
499 
500 std::unique_ptr<mlir::Pass>
501 circt::createPipelineToHWPass(const PipelineToHWOptions &options) {
502  return std::make_unique<PipelineToHWPass>(options);
503 }
assert(baseType &&"element must be base type")
def create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition: seq.py:164
def create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition: seq.py:137
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition: CombOps.cpp:48
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createPipelineToHWPass(const PipelineToHWOptions &options={})
Create an SCF to Calyx conversion pass.