CIRCT 22.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 comb::AndOp::create(builder, 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 = comb::AndOp::create(
134 builder, loc, args.enable,
135 comb::OrOp::create(builder, 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 = seq::ClockGateOp::create(
160 builder, 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 = seq::ClockGateOp::create(
176 builder, loc, currClockGate, hierClockGateEnable,
177 /*test_enable=*/Value(),
178 /*inner_sym=*/hw::InnerSymAttr());
179 }
180 dataReg = seq::CompRegOp::create(builder, 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) {
188 builder, stageOp->getLoc(), regIn, args.clock, stageValid,
189 regName);
190 } else {
191 dataReg = seq::CompRegOp::create(builder, stageOp->getLoc(), regIn,
192 args.clock, regName);
193 }
194 }
195 rets.regs.push_back(dataReg);
196 }
197
198 rets.valid = stageValid;
199 if (stageKind == StageKind::NonStallable)
200 rets.lnsEn = args.enable;
201
202 rets.passthroughs = stageOp.getPassthroughs();
203 return rets;
204 }
205
206 // A container carrying all-things stage output naming related.
207 // To avoid overloading 'output's to much (i'm trying to keep that
208 // reserved for "output" ports), this is named "egress".
209 struct StageEgressNames {
210 llvm::SmallVector<Attribute> regNames;
211 llvm::SmallVector<Attribute> outNames;
212 llvm::SmallVector<Attribute> inNames;
213 };
214
215 // Returns a set of names for the output values of a given stage
216 // (registers and passthrough). If `withPipelinePrefix` is true, the names
217 // will be prefixed with the pipeline name.
218 void getStageEgressNames(size_t stageIndex, Operation *stageTerminator,
219 bool withPipelinePrefix,
220 StageEgressNames &egressNames) {
221 StringAttr pipelineName;
222 if (withPipelinePrefix)
223 pipelineName = getPipelineBaseName();
224
225 if (auto stageOp = dyn_cast<StageOp>(stageTerminator)) {
226 // Registers...
227 std::string assignedRegName, assignedOutName, assignedInName;
228 for (size_t regi = 0; regi < stageOp.getRegisters().size(); ++regi) {
229 if (auto regName = stageOp.getRegisterName(regi)) {
230 assignedRegName = regName.str();
231 assignedOutName = assignedRegName + "_out";
232 assignedInName = assignedRegName + "_in";
233 } else {
234 assignedRegName =
235 ("stage" + Twine(stageIndex) + "_reg" + Twine(regi)).str();
236 assignedOutName = ("out" + Twine(regi)).str();
237 assignedInName = ("in" + Twine(regi)).str();
238 }
239
240 if (pipelineName && !pipelineName.getValue().empty()) {
241 assignedRegName = pipelineName.str() + "_" + assignedRegName;
242 assignedOutName = pipelineName.str() + "_" + assignedOutName;
243 assignedInName = pipelineName.str() + "_" + assignedInName;
244 }
245
246 egressNames.regNames.push_back(builder.getStringAttr(assignedRegName));
247 egressNames.outNames.push_back(builder.getStringAttr(assignedOutName));
248 egressNames.inNames.push_back(builder.getStringAttr(assignedInName));
249 }
250
251 // Passthroughs
252 for (size_t passi = 0; passi < stageOp.getPassthroughs().size();
253 ++passi) {
254 if (auto passName = stageOp.getPassthroughName(passi)) {
255 assignedOutName = (passName.strref() + "_out").str();
256 assignedInName = (passName.strref() + "_in").str();
257 } else {
258 assignedOutName = ("pass" + Twine(passi)).str();
259 assignedInName = ("pass" + Twine(passi)).str();
260 }
261
262 if (pipelineName && !pipelineName.getValue().empty()) {
263 assignedOutName = pipelineName.str() + "_" + assignedOutName;
264 assignedInName = pipelineName.str() + "_" + assignedInName;
265 }
266
267 egressNames.outNames.push_back(builder.getStringAttr(assignedOutName));
268 egressNames.inNames.push_back(builder.getStringAttr(assignedInName));
269 }
270 } else {
271 // For the return op, we just inherit the names of the top-level
272 // pipeline as stage output names.
273 llvm::copy(pipeline.getOutputNames().getAsRange<StringAttr>(),
274 std::back_inserter(egressNames.outNames));
275 }
276 }
277
278 // Returns a string to be used as a prefix for all stage registers.
279 virtual StringAttr getStagePrefix(size_t stageIdx) = 0;
280
281protected:
282 // Determine a reasonable name for the pipeline. This will affect naming
283 // of things such as stage registers.
284 StringAttr getPipelineBaseName() {
285 if (auto nameAttr = pipeline.getNameAttr())
286 return nameAttr;
287 return StringAttr::get(pipeline.getContext(), "p" + Twine(pipelineID));
288 }
289
290 // Parent module clock.
291 Value parentClk;
292 // Parent module reset.
293 Value parentRst;
294 // ID of the current pipeline, used for naming.
295 size_t pipelineID;
296 // The current pipeline to be converted.
297 ScheduledPipelineOp pipeline;
298
299 // The module wherein the pipeline resides.
300 hw::HWModuleOp parentModule;
301
302 OpBuilder &builder;
303
304 // If true, will use clock gating for registers instead of input muxing.
305 bool clockGateRegs;
306
307 // If true, will add power-on values to the control registers of the design.
308 bool enablePowerOnValues;
309
310 // Name of this pipeline - used for naming stages and registers.
311 // Implementation defined.
312 StringAttr pipelineName;
313};
314
315class PipelineInlineLowering : public PipelineLowering {
316public:
317 using PipelineLowering::PipelineLowering;
318
319 StringAttr getStagePrefix(size_t stageIdx) override {
320 if (pipelineName && !pipelineName.getValue().empty())
321 return builder.getStringAttr(pipelineName.strref() + "_stage" +
322 Twine(stageIdx));
323 return builder.getStringAttr("stage" + Twine(stageIdx));
324 }
325
326 LogicalResult run() override {
327 pipelineName = getPipelineBaseName();
328
329 // Replace uses of the pipeline internal inputs with the pipeline inputs.
330 for (auto [outer, inner] :
331 llvm::zip(pipeline.getInputs(), pipeline.getInnerInputs()))
332 inner.replaceAllUsesWith(outer);
333
334 // All operations should go directly before the pipeline op, into the
335 // parent module.
336 builder.setInsertionPoint(pipeline);
337 StageArgs args;
338 args.data = pipeline.getInnerInputs();
339 args.enable = pipeline.getGo();
340 args.clock = pipeline.getClock();
341 args.reset = pipeline.getReset();
342 args.stall = pipeline.getStall();
343 if (failed(lowerStage(pipeline.getEntryStage(), args, 0)))
344 return failure();
345
346 pipeline.erase();
347 return success();
348 }
349
350 /// NOLINTNEXTLINE(misc-no-recursion)
351 FailureOr<StageReturns>
352 lowerStage(Block *stage, StageArgs args, size_t stageIndex,
353 llvm::ArrayRef<Attribute> /*inputNames*/ = {}) override {
354 OpBuilder::InsertionGuard guard(builder);
355 Operation *terminator = stage->getTerminator();
356 Location loc = terminator->getLoc();
357
358 if (stage != pipeline.getEntryStage()) {
359 // Replace the internal stage inputs with the provided arguments.
360 for (auto [vInput, vArg] :
361 llvm::zip(pipeline.getStageDataArgs(stage), args.data))
362 vInput.replaceAllUsesWith(vArg);
363 }
364
365 // Build stage enable register. The enable register is reset to 0 iff a
366 // reset signal is available. We here rely on the compreg builders, which
367 // accept reset signal/reset value mlir::Value's that are null.
368 //
369 // The stage enable register takes the
370 // previous-stage combinational valid output and determines whether this
371 // stage is active or not in the next cycle. A non-stallable stage always
372 // registers the incoming enable signal, whereas other stages register based
373 // on the current stall state.
374 StageKind stageKind = pipeline.getStageKind(stageIndex);
375 Value stageEnabled;
376 if (stageIndex == 0) {
377 stageEnabled = args.enable;
378 } else {
379 auto stageRegPrefix = getStagePrefix(stageIndex);
380 auto enableRegName = (stageRegPrefix.strref() + "_enable").str();
381
382 Value enableRegResetVal;
383 if (args.reset)
384 enableRegResetVal =
385 hw::ConstantOp::create(builder, loc, APInt(1, 0, false))
386 .getResult();
387
388 switch (stageKind) {
389 case StageKind::Continuous:
390 LLVM_FALLTHROUGH;
391 case StageKind::NonStallable:
392 stageEnabled = seq::CompRegOp::create(builder, loc, args.enable,
393 args.clock, args.reset,
394 enableRegResetVal, enableRegName);
395 break;
396 case StageKind::Stallable:
398 builder, loc, args.enable, args.clock,
399 comb::createOrFoldNot(loc, args.stall, builder), args.reset,
400 enableRegResetVal, enableRegName);
401 break;
402 case StageKind::Runoff:
403 assert(args.lnsEn &&
404 "Expected an LNS signal if this was a runoff stage");
406 builder, loc, args.enable, args.clock,
407 comb::OrOp::create(builder, loc, args.lnsEn,
408 comb::createOrFoldNot(loc, args.stall, builder)),
409 args.reset, enableRegResetVal, enableRegName);
410 break;
411 }
412
413 if (enablePowerOnValues) {
414 llvm::TypeSwitch<Operation *, void>(stageEnabled.getDefiningOp())
415 .Case<seq::CompRegOp, seq::CompRegClockEnabledOp>([&](auto op) {
416 op.getInitialValueMutable().assign(
418 builder, loc,
419 builder.getIntegerAttr(builder.getI1Type(),
420 APInt(1, 0, false))));
421 });
422 }
423 }
424
425 // Replace the stage valid signal.
426 args.enable = stageEnabled;
427 pipeline.getStageEnableSignal(stage).replaceAllUsesWith(stageEnabled);
428
429 // Determine stage egress info.
430 auto nextStage = dyn_cast<StageOp>(terminator);
431 StageEgressNames egressNames;
432 if (nextStage)
433 getStageEgressNames(stageIndex, nextStage,
434 /*withPipelinePrefix=*/true, egressNames);
435
436 // Move stage operations into the current module.
437 builder.setInsertionPoint(pipeline);
438 StageReturns stageRets =
439 emitStageBody(stage, args, egressNames.regNames, stageIndex);
440
441 if (nextStage) {
442 // Lower the next stage.
443 SmallVector<Value> nextStageArgs;
444 llvm::append_range(nextStageArgs, stageRets.regs);
445 llvm::append_range(nextStageArgs, stageRets.passthroughs);
446 args.enable = stageRets.valid;
447 if (stageRets.lnsEn) {
448 // Swap the lnsEn signal if the current stage lowering generated an
449 // lnsEn.
450 args.lnsEn = stageRets.lnsEn;
451 }
452 args.data = nextStageArgs;
453 return lowerStage(nextStage.getNextStage(), args, stageIndex + 1);
454 }
455
456 // Replace the pipeline results with the return op operands.
457 auto returnOp = cast<pipeline::ReturnOp>(stage->getTerminator());
458 llvm::SmallVector<Value> pipelineReturns;
459 llvm::append_range(pipelineReturns, returnOp.getInputs());
460 // The last stage valid signal is the 'done' output of the pipeline.
461 pipelineReturns.push_back(stageRets.valid);
462 pipeline.replaceAllUsesWith(pipelineReturns);
463 return stageRets;
464 }
465};
466} // namespace
467
468//===----------------------------------------------------------------------===//
469// Pipeline to HW Conversion Pass
470//===----------------------------------------------------------------------===//
471
472namespace {
473struct PipelineToHWPass
474 : public circt::impl::PipelineToHWBase<PipelineToHWPass> {
475 using PipelineToHWBase::PipelineToHWBase;
476 void runOnOperation() override;
477
478private:
479 // Lowers pipelines within HWModules. This pass is currently expecting that
480 // Pipelines are always nested with HWModule's but could be written to be
481 // more generic.
482 void runOnHWModule(hw::HWModuleOp mod);
483};
484
485void PipelineToHWPass::runOnOperation() {
486 for (auto hwMod : getOperation().getOps<hw::HWModuleOp>())
487 runOnHWModule(hwMod);
488}
489
490void PipelineToHWPass::runOnHWModule(hw::HWModuleOp mod) {
491 OpBuilder builder(&getContext());
492 // Iterate over each pipeline op in the module and convert.
493 // Note: This pass matches on `hw::ModuleOp`s and not directly on the
494 // `ScheduledPipelineOp` due to the `ScheduledPipelineOp` being erased
495 // during this pass.
496 size_t pipelinesSeen = 0;
497 for (auto pipeline :
498 llvm::make_early_inc_range(mod.getOps<ScheduledPipelineOp>())) {
499 if (failed(PipelineInlineLowering(pipelinesSeen, pipeline, builder,
500 clockGateRegs, enablePowerOnValues)
501 .run())) {
502 signalPassFailure();
503 return;
504 }
505 ++pipelinesSeen;
506 }
507}
508
509} // namespace
510
511std::unique_ptr<mlir::Pass>
512circt::createPipelineToHWPass(const PipelineToHWOptions &options) {
513 return std::make_unique<PipelineToHWPass>(options);
514}
assert(baseType &&"element must be base type")
create(data_type, value)
Definition hw.py:433
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:1159
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