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