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