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