CIRCT 24.0.0git
Loading...
Searching...
No Matches
ConvertToArcs.cpp
Go to the documentation of this file.
1//===- ConvertToArcs.cpp --------------------------------------------------===//
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
17#include "mlir/IR/PatternMatch.h"
18#include "mlir/Pass/Pass.h"
19#include "mlir/Transforms/DialectConversion.h"
20#include "mlir/Transforms/RegionUtils.h"
21#include "llvm/Support/Debug.h"
22
23#define DEBUG_TYPE "convert-to-arcs"
24
25using namespace circt;
26using namespace arc;
27using namespace hw;
29using mlir::ConversionConfig;
30
31static bool isArcBreakingOp(Operation *op) {
32 if (isa<TapOp>(op))
33 return false;
34 return op->hasTrait<OpTrait::ConstantLike>() ||
35 isa<hw::InstanceOp, seq::CompRegOp, MemoryOp, MemoryReadPortOp,
36 ClockedOpInterface, seq::InitialOp, seq::ClockGateOp,
37 sim::DPICallOp, llhd::ProbeOp>(op) ||
38 op->getNumResults() > 1 || op->getNumRegions() > 0 ||
39 !mlir::isMemoryEffectFree(op);
40}
41
42static LogicalResult convertInitialValue(seq::CompRegOp reg,
43 SmallVectorImpl<Value> &values) {
44 if (!reg.getInitialValue())
45 return values.push_back({}), success();
46
47 // Use from_immutable cast to convert the seq.immutable type to the reg's
48 // type.
49 OpBuilder builder(reg);
50 auto init = seq::FromImmutableOp::create(builder, reg.getLoc(), reg.getType(),
51 reg.getInitialValue());
52
53 values.push_back(init);
54 return success();
55}
56
57//===----------------------------------------------------------------------===//
58// Conversion
59//===----------------------------------------------------------------------===//
60
61namespace {
62struct Converter {
63 LogicalResult run(ModuleOp module);
64 LogicalResult runOnModule(HWModuleOp module);
65 LogicalResult analyzeFanIn();
66 void extractArcs(HWModuleOp module);
67 LogicalResult absorbRegs(HWModuleOp module);
68
69 /// The global namespace used to create unique definition names.
70 Namespace globalNamespace;
71
72 /// All arc-breaking operations in the current module.
73 SmallVector<Operation *> arcBreakers;
74 SmallDenseMap<Operation *, unsigned> arcBreakerIndices;
75
76 /// A post-order traversal of the operations in the current module.
77 SmallVector<Operation *> postOrder;
78
79 /// The set of arc-breaking ops an operation in the current module
80 /// contributes to, represented as a bit mask.
81 MapVector<Operation *, APInt> faninMasks;
82
83 /// The sets of operations that contribute to the same arc-breaking ops.
84 MapVector<APInt, DenseSet<Operation *>> faninMaskGroups;
85
86 /// The arc uses generated by `extractArcs`.
87 SmallVector<mlir::CallOpInterface> arcUses;
88
89 /// Whether registers should be made observable by assigning their arcs a
90 /// "name" attribute.
91 bool tapRegisters;
92};
93} // namespace
94
95LogicalResult Converter::run(ModuleOp module) {
96 for (auto &op : module.getOps())
97 if (auto sym = dyn_cast<mlir::SymbolOpInterface>(&op))
98 globalNamespace.newName(sym.getName());
99 for (auto module : module.getOps<HWModuleOp>())
100 if (failed(runOnModule(module)))
101 return failure();
102 return success();
103}
104
105LogicalResult Converter::runOnModule(HWModuleOp module) {
106 // Find all arc-breaking operations in this module and assign them an index.
107 arcBreakers.clear();
108 arcBreakerIndices.clear();
109 for (Operation &op : *module.getBodyBlock()) {
110 if (isa<seq::InitialOp>(&op))
111 continue;
112 if (!isArcBreakingOp(&op) && !isa<hw::OutputOp>(&op))
113 continue;
114 arcBreakerIndices[&op] = arcBreakers.size();
115 arcBreakers.push_back(&op);
116 }
117 // Skip modules with only `OutputOp`.
118 if (module.getBodyBlock()->without_terminator().empty() &&
119 isa<hw::OutputOp>(module.getBodyBlock()->getTerminator()))
120 return success();
121 LLVM_DEBUG(llvm::dbgs() << "Analyzing " << module.getModuleNameAttr() << " ("
122 << arcBreakers.size() << " breakers)\n");
123
124 // For each operation, figure out the set of breaker ops it contributes to,
125 // in the form of a bit mask. Then group operations together that contribute
126 // to the same set of breaker ops.
127 if (failed(analyzeFanIn()))
128 return failure();
129
130 // Extract the fanin mask groups into separate combinational arcs and
131 // combine them with the registers in the design.
132 extractArcs(module);
133 if (failed(absorbRegs(module)))
134 return failure();
135
136 return success();
137}
138
139LogicalResult Converter::analyzeFanIn() {
140 SmallVector<std::tuple<Operation *, SmallVector<Value, 2>>> worklist;
141 SetVector<Value> seenOperands;
142 auto addToWorklist = [&](Operation *op) {
143 seenOperands.clear();
144 for (auto operand : op->getOperands())
145 seenOperands.insert(operand);
146 mlir::getUsedValuesDefinedAbove(op->getRegions(), seenOperands);
147 worklist.emplace_back(op, seenOperands.getArrayRef());
148 };
149
150 // Seed the worklist and fanin masks with the arc breaking operations.
151 faninMasks.clear();
152 for (auto *op : arcBreakers) {
153 unsigned index = arcBreakerIndices.lookup(op);
154 auto mask = APInt::getOneBitSet(arcBreakers.size(), index);
155 faninMasks[op] = mask;
156 addToWorklist(op);
157 }
158
159 // Establish a post-order among the operations.
160 DenseSet<Operation *> seen;
161 DenseSet<Operation *> finished;
162 postOrder.clear();
163 while (!worklist.empty()) {
164 auto &[op, operands] = worklist.back();
165 if (operands.empty()) {
166 if (!isArcBreakingOp(op) && !isa<hw::OutputOp>(op))
167 postOrder.push_back(op);
168 finished.insert(op);
169 seen.erase(op);
170 worklist.pop_back();
171 continue;
172 }
173 auto operand = operands.pop_back_val(); // advance to next operand
174 auto *definingOp = operand.getDefiningOp();
175 if (!definingOp || isArcBreakingOp(definingOp) ||
176 finished.contains(definingOp))
177 continue;
178 if (!seen.insert(definingOp).second) {
179 definingOp->emitError("combinational loop detected");
180 return failure();
181 }
182 addToWorklist(definingOp);
183 }
184 LLVM_DEBUG(llvm::dbgs() << "- Sorted " << postOrder.size() << " ops\n");
185
186 // Compute fanin masks in reverse post-order, which will compute the mask
187 // for an operation's uses before it computes it for the operation itself.
188 // This allows us to compute the set of arc breakers an operation
189 // contributes to in one pass.
190 for (auto *op : llvm::reverse(postOrder)) {
191 auto mask = APInt::getZero(arcBreakers.size());
192 for (auto *user : op->getUsers()) {
193 while (user->getParentOp() != op->getParentOp())
194 user = user->getParentOp();
195 auto *it = faninMasks.find(user);
196 if (it != faninMasks.end())
197 mask |= it->second;
198 }
199
200 auto duplicateOp = faninMasks.insert({op, mask});
201 (void)duplicateOp;
202 assert(duplicateOp.second && "duplicate op in order");
203 }
204
205 // Group the operations by their fan-in mask.
206 faninMaskGroups.clear();
207 for (auto [op, mask] : faninMasks)
208 if (!isArcBreakingOp(op) && !isa<hw::OutputOp>(op))
209 faninMaskGroups[mask].insert(op);
210 LLVM_DEBUG(llvm::dbgs() << "- Found " << faninMaskGroups.size()
211 << " fanin mask groups\n");
212
213 return success();
214}
215
216void Converter::extractArcs(HWModuleOp module) {
217 DenseMap<Value, Value> valueMapping;
218 SmallVector<Value> inputs;
219 SmallVector<Value> outputs;
220 SmallVector<Type> inputTypes;
221 SmallVector<Type> outputTypes;
222 SmallVector<std::pair<OpOperand *, unsigned>> externalUses;
223
224 arcUses.clear();
225 for (auto &group : faninMaskGroups) {
226 auto &opSet = group.second;
227 OpBuilder builder(module);
228
229 auto block = std::make_unique<Block>();
230 builder.setInsertionPointToStart(block.get());
231 valueMapping.clear();
232 inputs.clear();
233 outputs.clear();
234 inputTypes.clear();
235 outputTypes.clear();
236 externalUses.clear();
237
238 Operation *lastOp = nullptr;
239 // TODO: Remove the elements from the post order as we go.
240 for (auto *op : postOrder) {
241 if (!opSet.contains(op))
242 continue;
243 lastOp = op;
244 op->remove();
245 builder.insert(op);
246 for (auto &operand : op->getOpOperands()) {
247 if (opSet.contains(operand.get().getDefiningOp()))
248 continue;
249 auto &mapped = valueMapping[operand.get()];
250 if (!mapped) {
251 mapped = block->addArgument(operand.get().getType(),
252 operand.get().getLoc());
253 inputs.push_back(operand.get());
254 inputTypes.push_back(mapped.getType());
255 }
256 operand.set(mapped);
257 }
258 for (auto result : op->getResults()) {
259 bool anyExternal = false;
260 for (auto &use : result.getUses()) {
261 if (!opSet.contains(use.getOwner())) {
262 anyExternal = true;
263 externalUses.push_back({&use, outputs.size()});
264 }
265 }
266 if (anyExternal) {
267 outputs.push_back(result);
268 outputTypes.push_back(result.getType());
269 }
270 }
271 }
272 assert(lastOp);
273 arc::OutputOp::create(builder, lastOp->getLoc(), outputs);
274
275 // Create the arc definition.
276 builder.setInsertionPoint(module);
277 auto defOp =
278 DefineOp::create(builder, lastOp->getLoc(),
279 builder.getStringAttr(globalNamespace.newName(
280 module.getModuleName() + "_arc")),
281 builder.getFunctionType(inputTypes, outputTypes));
282 defOp.getBody().push_back(block.release());
283
284 // Create the call to the arc definition to replace the operations that
285 // we have just extracted.
286 builder.setInsertionPoint(module.getBodyBlock()->getTerminator());
287 auto arcOp = CallOp::create(builder, lastOp->getLoc(), defOp, inputs);
288 arcUses.push_back(arcOp);
289 for (auto [use, resultIdx] : externalUses)
290 use->set(arcOp.getResult(resultIdx));
291 }
292}
293
294LogicalResult Converter::absorbRegs(HWModuleOp module) {
295 // Handle the trivial cases where all of an arc's results are used by
296 // exactly one register each.
297 unsigned outIdx = 0;
298 unsigned numTrivialRegs = 0;
299 for (auto callOp : arcUses) {
300 auto stateOp = dyn_cast<StateOp>(callOp.getOperation());
301 Value clock = stateOp ? stateOp.getClock() : Value{};
302 Value reset;
303 SmallVector<Value> initialValues;
304 SmallVector<seq::CompRegOp> absorbedRegs;
305 SmallVector<Attribute> absorbedNames(callOp->getNumResults(), {});
306 if (auto names = callOp->getAttrOfType<ArrayAttr>("names"))
307 absorbedNames.assign(names.getValue().begin(), names.getValue().end());
308
309 // Go through all every arc result and collect the single register that uses
310 // it. If a result has multiple uses or is used by something other than a
311 // register, skip the arc for now and handle it later.
312 bool isTrivial = true;
313 for (auto result : callOp->getResults()) {
314 if (!result.hasOneUse()) {
315 isTrivial = false;
316 break;
317 }
318 auto regOp = dyn_cast<seq::CompRegOp>(result.use_begin()->getOwner());
319 if (!regOp || regOp.getInput() != result ||
320 (clock && clock != regOp.getClk())) {
321 isTrivial = false;
322 break;
323 }
324
325 clock = regOp.getClk();
326 reset = regOp.getReset();
327
328 // Check that if the register has a reset, it is to a constant zero
329 if (reset) {
330 Value resetValue = regOp.getResetValue();
331 Operation *op = resetValue.getDefiningOp();
332 if (!op)
333 return regOp->emitOpError(
334 "is reset by an input; not supported by ConvertToArcs");
335 if (auto constant = dyn_cast<hw::ConstantOp>(op)) {
336 if (constant.getValue() != 0)
337 return regOp->emitOpError("is reset to a constant non-zero value; "
338 "not supported by ConvertToArcs");
339 } else {
340 return regOp->emitOpError("is reset to a value that is not clearly "
341 "constant; not supported by ConvertToArcs");
342 }
343 }
344
345 if (failed(convertInitialValue(regOp, initialValues)))
346 return failure();
347
348 absorbedRegs.push_back(regOp);
349 // If we absorb a register into the arc, the arc effectively produces that
350 // register's value. So if the register had a name, ensure that we assign
351 // that name to the arc's output.
352 absorbedNames[result.getResultNumber()] = regOp.getNameAttr();
353 }
354
355 // If this wasn't a trivial case keep the arc around for a second iteration.
356 if (!isTrivial) {
357 arcUses[outIdx++] = callOp;
358 continue;
359 }
360 ++numTrivialRegs;
361
362 // Set the arc's clock to the clock of the registers we've absorbed, bump
363 // the latency up by one to account for the registers, add the reset if
364 // present and update the output names. Then replace the registers.
365
366 auto arc = dyn_cast<StateOp>(callOp.getOperation());
367 if (arc) {
368 arc.getClockMutable().assign(clock);
369 arc.setLatency(arc.getLatency() + 1);
370 } else {
371 mlir::IRRewriter rewriter(module->getContext());
372 rewriter.setInsertionPoint(callOp);
373 arc = rewriter.replaceOpWithNewOp<StateOp>(
374 callOp.getOperation(),
375 llvm::cast<SymbolRefAttr>(callOp.getCallableForCallee()),
376 callOp->getResultTypes(), clock, Value{}, 1, callOp.getArgOperands());
377 }
378
379 if (reset) {
380 if (arc.getReset())
381 return arc.emitError(
382 "StateOp tried to infer reset from CompReg, but already "
383 "had a reset.");
384 arc.getResetMutable().assign(reset);
385 }
386
387 bool onlyDefaultInitializers =
388 llvm::all_of(initialValues, [](auto val) -> bool { return !val; });
389
390 if (!onlyDefaultInitializers) {
391 if (!arc.getInitials().empty()) {
392 return arc.emitError(
393 "StateOp tried to infer initial values from CompReg, but already "
394 "had an initial value.");
395 }
396 // Create 0 constants for default initialization
397 for (unsigned i = 0; i < initialValues.size(); ++i) {
398 if (!initialValues[i]) {
399 OpBuilder zeroBuilder(arc);
400 initialValues[i] = zeroBuilder.createOrFold<hw::ConstantOp>(
401 arc.getLoc(),
402 zeroBuilder.getIntegerAttr(arc.getResult(i).getType(), 0));
403 }
404 }
405 arc.getInitialsMutable().assign(initialValues);
406 }
407
408 if (tapRegisters && llvm::any_of(absorbedNames, [](auto name) {
409 return !cast<StringAttr>(name).getValue().empty();
410 }))
411 arc->setAttr("names", ArrayAttr::get(module.getContext(), absorbedNames));
412 for (auto [arcResult, reg] : llvm::zip(arc.getResults(), absorbedRegs)) {
413 auto it = arcBreakerIndices.find(reg);
414 arcBreakers[it->second] = {};
415 arcBreakerIndices.erase(it);
416 reg.replaceAllUsesWith(arcResult);
417 reg.erase();
418 }
419 }
420 if (numTrivialRegs > 0)
421 LLVM_DEBUG(llvm::dbgs() << "- Trivially converted " << numTrivialRegs
422 << " regs to arcs\n");
423 arcUses.truncate(outIdx);
424
425 // Group the remaining registers by their clock, their reset and the operation
426 // they use as input. This will allow us to generally collapse registers
427 // derived from the same arc into one shuffling arc.
428 MapVector<std::tuple<Value, Value, Operation *>, SmallVector<seq::CompRegOp>>
429 regsByInput;
430 for (auto *op : arcBreakers)
431 if (auto regOp = dyn_cast_or_null<seq::CompRegOp>(op)) {
432 regsByInput[{regOp.getClk(), regOp.getReset(),
433 regOp.getInput().getDefiningOp()}]
434 .push_back(regOp);
435 }
436
437 unsigned numMappedRegs = 0;
438 // The reset values in the `regsByInput` keys may be results of registers
439 // that are themselves converted in this loop. Defer replacing and erasing the
440 // registers until all shuffling arcs exist, so that no key refers to an
441 // erased value when it is assigned as a reset operand.
442 SmallVector<std::pair<seq::CompRegOp, Value>> regReplacements;
443 for (auto [clockAndResetAndOp, regOps] : regsByInput) {
444 numMappedRegs += regOps.size();
445 OpBuilder builder(module);
446 auto block = std::make_unique<Block>();
447 builder.setInsertionPointToStart(block.get());
448
449 SmallVector<Value> inputs;
450 SmallVector<Value> outputs;
451 SmallVector<Attribute> names;
452 SmallVector<Type> types;
453 SmallVector<Value> initialValues;
454 SmallDenseMap<Value, unsigned> mapping;
455 SmallVector<unsigned> regToOutputMapping;
456 for (auto regOp : regOps) {
457 auto it = mapping.find(regOp.getInput());
458 if (it == mapping.end()) {
459 it = mapping.insert({regOp.getInput(), inputs.size()}).first;
460 inputs.push_back(regOp.getInput());
461 types.push_back(regOp.getType());
462 outputs.push_back(block->addArgument(regOp.getType(), regOp.getLoc()));
463 names.push_back(regOp->getAttrOfType<StringAttr>("name"));
464 if (failed(convertInitialValue(regOp, initialValues)))
465 return failure();
466 }
467 regToOutputMapping.push_back(it->second);
468 }
469
470 auto loc = regOps.back().getLoc();
471 arc::OutputOp::create(builder, loc, outputs);
472
473 builder.setInsertionPoint(module);
474 auto defOp = DefineOp::create(builder, loc,
475 builder.getStringAttr(globalNamespace.newName(
476 module.getModuleName() + "_arc")),
477 builder.getFunctionType(types, types));
478 defOp.getBody().push_back(block.release());
479
480 builder.setInsertionPoint(module.getBodyBlock()->getTerminator());
481
482 bool onlyDefaultInitializers =
483 llvm::all_of(initialValues, [](auto val) -> bool { return !val; });
484
485 if (onlyDefaultInitializers)
486 initialValues.clear();
487 else
488 for (unsigned i = 0; i < initialValues.size(); ++i) {
489 if (!initialValues[i])
490 initialValues[i] = builder.createOrFold<hw::ConstantOp>(
491 loc, builder.getIntegerAttr(types[i], 0));
492 }
493
494 auto arcOp =
495 StateOp::create(builder, loc, defOp, std::get<0>(clockAndResetAndOp),
496 /*enable=*/Value{}, 1, inputs, initialValues);
497 auto reset = std::get<1>(clockAndResetAndOp);
498 if (reset)
499 arcOp.getResetMutable().assign(reset);
500 if (tapRegisters && llvm::any_of(names, [](auto name) {
501 return !cast<StringAttr>(name).getValue().empty();
502 }))
503 arcOp->setAttr("names", builder.getArrayAttr(names));
504 for (auto [reg, resultIdx] : llvm::zip(regOps, regToOutputMapping))
505 regReplacements.push_back({reg, arcOp.getResult(resultIdx)});
506 }
507 for (auto [reg, arcResult] : regReplacements)
508 reg.replaceAllUsesWith(arcResult);
509 for (auto [reg, arcResult] : regReplacements)
510 reg.erase();
511
512 if (numMappedRegs > 0)
513 LLVM_DEBUG(llvm::dbgs() << "- Mapped " << numMappedRegs << " regs to "
514 << regsByInput.size() << " shuffling arcs\n");
515
516 return success();
517}
518
519//===----------------------------------------------------------------------===//
520// LLHD Conversion
521//===----------------------------------------------------------------------===//
522
523/// `llhd.combinational` -> `arc.execute`
524static LogicalResult convert(llhd::CombinationalOp op,
525 llhd::CombinationalOp::Adaptor adaptor,
526 ConversionPatternRewriter &rewriter,
527 const TypeConverter &converter) {
528 // Convert the result types.
529 SmallVector<Type> resultTypes;
530 if (failed(converter.convertTypes(op.getResultTypes(), resultTypes)))
531 return failure();
532
533 // Collect the SSA values defined outside but used inside the body region.
534 auto cloneIntoBody = [](Operation *op) {
535 return op->hasTrait<OpTrait::ConstantLike>();
536 };
537 auto operands =
538 mlir::makeRegionIsolatedFromAbove(rewriter, op.getBody(), cloneIntoBody);
539
540 // Create a replacement `arc.execute` op.
541 auto executeOp =
542 ExecuteOp::create(rewriter, op.getLoc(), resultTypes, operands);
543 executeOp.getBody().takeBody(op.getBody());
544 rewriter.replaceOp(op, executeOp.getResults());
545 return success();
546}
547
548/// `llhd.yield` -> `arc.output`
549static LogicalResult convert(llhd::YieldOp op, llhd::YieldOp::Adaptor adaptor,
550 ConversionPatternRewriter &rewriter) {
551 rewriter.replaceOpWithNewOp<arc::OutputOp>(op, adaptor.getOperands());
552 return success();
553}
554
555//===----------------------------------------------------------------------===//
556// Pass Infrastructure
557//===----------------------------------------------------------------------===//
558
559namespace circt {
560#define GEN_PASS_DEF_CONVERTTOARCSPASS
561#include "circt/Conversion/Passes.h.inc"
562} // namespace circt
563
564namespace {
565struct ConvertToArcsPass
566 : public circt::impl::ConvertToArcsPassBase<ConvertToArcsPass> {
567 using ConvertToArcsPassBase::ConvertToArcsPassBase;
568 void runOnOperation() override;
569};
570} // namespace
571
572void ConvertToArcsPass::runOnOperation() {
573 // Pass-through type converter; this pass does not change any types.
574 TypeConverter converter;
575 converter.addConversion([](Type type) { return type; });
576
577 // Gather the conversion patterns.
578 ConversionPatternSet patterns(&getContext(), converter);
579 patterns.add<llhd::CombinationalOp>(convert);
580 patterns.add<llhd::YieldOp>(convert);
581
582 // `llhd.combinational` and `llhd.yield` are the only LLHD ops rewritten by
583 // this pass; all other ops are left untouched for subsequent lowering
584 // passes to handle.
585 ConversionTarget target(getContext());
586 target.addIllegalOp<llhd::CombinationalOp, llhd::YieldOp>();
587 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
588
589 // Disable pattern rollback to use the faster one-shot dialect conversion.
590 ConversionConfig config;
591 config.allowPatternRollback = false;
592
593 // Apply the dialect conversion patterns.
594 if (failed(applyPartialConversion(getOperation(), target, std::move(patterns),
595 config))) {
596 emitError(getOperation().getLoc()) << "conversion to arcs failed";
597 return signalPassFailure();
598 }
599
600 // Outline operations into arcs.
601 Converter outliner;
602 outliner.tapRegisters = tapRegisters;
603 if (failed(outliner.run(getOperation())))
604 return signalPassFailure();
605}
assert(baseType &&"element must be base type")
static LogicalResult convertInitialValue(seq::CompRegOp reg, SmallVectorImpl< Value > &values)
static bool isArcBreakingOp(Operation *op)
static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor, ConversionPatternRewriter &rewriter, const TypeConverter &converter)
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:193
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:86
Definition arc.py:1
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
Definition hw.py:1
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21