CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
18#include "mlir/IR/Builders.h"
19#include "mlir/Pass/Pass.h"
20#include "llvm/ADT/TypeSwitch.h"
21
22namespace circt {
23#define GEN_PASS_DEF_PIPELINETOHW
24#include "circt/Conversion/Passes.h.inc"
25} // namespace circt
26
27using namespace mlir;
28using namespace circt;
29using namespace pipeline;
30
31namespace {
32// Base class for all pipeline lowerings.
33class PipelineLowering {
34public:
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
280protected:
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
314class PipelineInlineLowering : public PipelineLowering {
315public:
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 reset to 0 iff a
365 // reset signal is available. We here rely on the compreg builders, which
366 // accept reset signal/reset value mlir::Value's that are null.
367 //
368 // The stage enable register takes the
369 // previous-stage combinational valid output and determines whether this
370 // stage is active or not in the next cycle. A non-stallable stage always
371 // registers the incoming enable signal, whereas other stages register based
372 // on the current stall state.
373 StageKind stageKind = pipeline.getStageKind(stageIndex);
374 Value stageEnabled;
375 if (stageIndex == 0) {
376 stageEnabled = args.enable;
377 } else {
378 auto stageRegPrefix = getStagePrefix(stageIndex);
379 auto enableRegName = (stageRegPrefix.strref() + "_enable").str();
380
381 Value enableRegResetVal;
382 if (args.reset)
383 enableRegResetVal =
384 builder.create<hw::ConstantOp>(loc, APInt(1, 0, false)).getResult();
385
386 switch (stageKind) {
387 case StageKind::Continuous:
388 LLVM_FALLTHROUGH;
389 case StageKind::NonStallable:
390 stageEnabled = builder.create<seq::CompRegOp>(
391 loc, args.enable, args.clock, args.reset, enableRegResetVal,
392 enableRegName);
393 break;
394 case StageKind::Stallable:
395 stageEnabled = builder.create<seq::CompRegClockEnabledOp>(
396 loc, args.enable, args.clock,
397 comb::createOrFoldNot(loc, args.stall, builder), args.reset,
398 enableRegResetVal, enableRegName);
399 break;
400 case StageKind::Runoff:
401 assert(args.lnsEn &&
402 "Expected an LNS signal if this was a runoff stage");
403 stageEnabled = builder.create<seq::CompRegClockEnabledOp>(
404 loc, args.enable, args.clock,
405 builder.create<comb::OrOp>(
406 loc, args.lnsEn,
407 comb::createOrFoldNot(loc, args.stall, builder)),
408 args.reset, enableRegResetVal, enableRegName);
409 break;
410 }
411
412 if (enablePowerOnValues) {
413 llvm::TypeSwitch<Operation *, void>(stageEnabled.getDefiningOp())
414 .Case<seq::CompRegOp, seq::CompRegClockEnabledOp>([&](auto op) {
415 op.getInitialValueMutable().assign(
417 builder, loc,
418 builder.getIntegerAttr(builder.getI1Type(),
419 APInt(1, 0, false))));
420 });
421 }
422 }
423
424 // Replace the stage valid signal.
425 args.enable = stageEnabled;
426 pipeline.getStageEnableSignal(stage).replaceAllUsesWith(stageEnabled);
427
428 // Determine stage egress info.
429 auto nextStage = dyn_cast<StageOp>(terminator);
430 StageEgressNames egressNames;
431 if (nextStage)
432 getStageEgressNames(stageIndex, nextStage,
433 /*withPipelinePrefix=*/true, egressNames);
434
435 // Move stage operations into the current module.
436 builder.setInsertionPoint(pipeline);
437 StageReturns stageRets =
438 emitStageBody(stage, args, egressNames.regNames, stageIndex);
439
440 if (nextStage) {
441 // Lower the next stage.
442 SmallVector<Value> nextStageArgs;
443 llvm::append_range(nextStageArgs, stageRets.regs);
444 llvm::append_range(nextStageArgs, stageRets.passthroughs);
445 args.enable = stageRets.valid;
446 if (stageRets.lnsEn) {
447 // Swap the lnsEn signal if the current stage lowering generated an
448 // lnsEn.
449 args.lnsEn = stageRets.lnsEn;
450 }
451 args.data = nextStageArgs;
452 return lowerStage(nextStage.getNextStage(), args, stageIndex + 1);
453 }
454
455 // Replace the pipeline results with the return op operands.
456 auto returnOp = cast<pipeline::ReturnOp>(stage->getTerminator());
457 llvm::SmallVector<Value> pipelineReturns;
458 llvm::append_range(pipelineReturns, returnOp.getInputs());
459 // The last stage valid signal is the 'done' output of the pipeline.
460 pipelineReturns.push_back(stageRets.valid);
461 pipeline.replaceAllUsesWith(pipelineReturns);
462 return stageRets;
463 }
464};
465} // namespace
466
467//===----------------------------------------------------------------------===//
468// Pipeline to HW Conversion Pass
469//===----------------------------------------------------------------------===//
470
471namespace {
472struct PipelineToHWPass
473 : public circt::impl::PipelineToHWBase<PipelineToHWPass> {
474 using PipelineToHWBase::PipelineToHWBase;
475 void runOnOperation() override;
476
477private:
478 // Lowers pipelines within HWModules. This pass is currently expecting that
479 // Pipelines are always nested with HWModule's but could be written to be
480 // more generic.
481 void runOnHWModule(hw::HWModuleOp mod);
482};
483
484void PipelineToHWPass::runOnOperation() {
485 for (auto hwMod : getOperation().getOps<hw::HWModuleOp>())
486 runOnHWModule(hwMod);
487}
488
489void PipelineToHWPass::runOnHWModule(hw::HWModuleOp mod) {
490 OpBuilder builder(&getContext());
491 // Iterate over each pipeline op in the module and convert.
492 // Note: This pass matches on `hw::ModuleOp`s and not directly on the
493 // `ScheduledPipelineOp` due to the `ScheduledPipelineOp` being erased
494 // during this pass.
495 size_t pipelinesSeen = 0;
496 for (auto pipeline :
497 llvm::make_early_inc_range(mod.getOps<ScheduledPipelineOp>())) {
498 if (failed(PipelineInlineLowering(pipelinesSeen, pipeline, builder,
499 clockGateRegs, enablePowerOnValues)
500 .run())) {
501 signalPassFailure();
502 return;
503 }
504 ++pipelinesSeen;
505 }
506}
507
508} // namespace
509
510std::unique_ptr<mlir::Pass>
511circt::createPipelineToHWPass(const PipelineToHWOptions &options) {
512 return std::make_unique<PipelineToHWPass>(options);
513}
assert(baseType &&"element must be base type")
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:187
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
mlir::TypedValue< seq::ImmutableType > createConstantInitialValue(OpBuilder builder, Location loc, mlir::IntegerAttr attr)
Definition SeqOps.cpp:1065
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createPipelineToHWPass(const PipelineToHWOptions &options={})
Create an SCF to Calyx conversion pass.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:121
Definition hw.py:1