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"
23#define DEBUG_TYPE "convert-to-arcs"
29using llvm::SmallSetVector;
30using mlir::ConversionConfig;
35 return op->hasTrait<OpTrait::ConstantLike>() ||
37 ClockedOpInterface, seq::InitialOp, seq::ClockGateOp,
38 sim::DPICallOp>(op) ||
39 op->getNumResults() > 1 || op->getNumRegions() > 0 ||
40 !mlir::isMemoryEffectFree(op);
44 SmallVectorImpl<Value> &values) {
45 if (!reg.getInitialValue())
46 return values.push_back({}), success();
50 OpBuilder builder(reg);
51 auto init = seq::FromImmutableOp::create(builder, reg.getLoc(), reg.getType(),
52 reg.getInitialValue());
54 values.push_back(init);
64 LogicalResult
run(ModuleOp module);
66 LogicalResult analyzeFanIn();
74 SmallVector<Operation *> arcBreakers;
78 SmallVector<Operation *> postOrder;
82 MapVector<Operation *, APInt> faninMasks;
85 MapVector<APInt, DenseSet<Operation *>> faninMaskGroups;
88 SmallVector<mlir::CallOpInterface> arcUses;
96LogicalResult Converter::run(ModuleOp module) {
97 for (
auto &op : module.getOps())
99 op.getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName()))
100 globalNamespace.newName(sym.getValue());
101 for (
auto module : module.getOps<
HWModuleOp>())
102 if (failed(runOnModule(module)))
107LogicalResult Converter::runOnModule(
HWModuleOp module) {
110 arcBreakerIndices.clear();
112 if (isa<seq::InitialOp>(&op))
116 arcBreakerIndices[&op] = arcBreakers.size();
117 arcBreakers.push_back(&op);
120 if (module.getBodyBlock()->without_terminator().empty() &&
121 isa<hw::OutputOp>(module.getBodyBlock()->getTerminator()))
123 LLVM_DEBUG(llvm::dbgs() <<
"Analyzing " << module.getModuleNameAttr() <<
" ("
124 << arcBreakers.size() <<
" breakers)\n");
129 if (failed(analyzeFanIn()))
135 if (failed(absorbRegs(module)))
141LogicalResult Converter::analyzeFanIn() {
142 SmallVector<std::tuple<Operation *, SmallVector<Value, 2>>> worklist;
143 SetVector<Value> seenOperands;
144 auto addToWorklist = [&](Operation *op) {
145 seenOperands.clear();
146 for (
auto operand : op->getOperands())
147 seenOperands.insert(operand);
148 mlir::getUsedValuesDefinedAbove(op->getRegions(), seenOperands);
149 worklist.emplace_back(op, seenOperands.getArrayRef());
154 for (
auto *op : arcBreakers) {
155 unsigned index = arcBreakerIndices.lookup(op);
156 auto mask = APInt::getOneBitSet(arcBreakers.size(), index);
157 faninMasks[op] =
mask;
162 DenseSet<Operation *> seen;
163 DenseSet<Operation *> finished;
165 while (!worklist.empty()) {
166 auto &[op, operands] = worklist.back();
167 if (operands.empty()) {
169 postOrder.push_back(op);
175 auto operand = operands.pop_back_val();
176 auto *definingOp = operand.getDefiningOp();
178 finished.contains(definingOp))
180 if (!seen.insert(definingOp).second) {
181 definingOp->emitError(
"combinational loop detected");
184 addToWorklist(definingOp);
186 LLVM_DEBUG(llvm::dbgs() <<
"- Sorted " << postOrder.size() <<
" ops\n");
192 for (
auto *op :
llvm::reverse(postOrder)) {
193 auto mask = APInt::getZero(arcBreakers.size());
194 for (
auto *user : op->getUsers()) {
195 while (user->getParentOp() != op->getParentOp())
196 user = user->getParentOp();
197 auto it = faninMasks.find(user);
198 if (it != faninMasks.end())
202 auto duplicateOp = faninMasks.insert({op,
mask});
204 assert(duplicateOp.second &&
"duplicate op in order");
208 faninMaskGroups.clear();
209 for (
auto [op, mask] : faninMasks)
211 faninMaskGroups[
mask].insert(op);
212 LLVM_DEBUG(llvm::dbgs() <<
"- Found " << faninMaskGroups.size()
213 <<
" fanin mask groups\n");
218void Converter::extractArcs(
HWModuleOp module) {
219 DenseMap<Value, Value> valueMapping;
220 SmallVector<Value> inputs;
221 SmallVector<Value> outputs;
222 SmallVector<Type> inputTypes;
223 SmallVector<Type> outputTypes;
224 SmallVector<std::pair<OpOperand *, unsigned>> externalUses;
227 for (
auto &group : faninMaskGroups) {
228 auto &opSet = group.second;
229 OpBuilder builder(module);
231 auto block = std::make_unique<Block>();
232 builder.setInsertionPointToStart(block.get());
233 valueMapping.clear();
238 externalUses.clear();
240 Operation *lastOp =
nullptr;
242 for (
auto *op : postOrder) {
243 if (!opSet.contains(op))
248 for (
auto &operand : op->getOpOperands()) {
249 if (opSet.contains(operand.get().getDefiningOp()))
251 auto &mapped = valueMapping[operand.get()];
253 mapped = block->addArgument(operand.get().getType(),
254 operand.get().getLoc());
255 inputs.push_back(operand.get());
256 inputTypes.push_back(mapped.getType());
260 for (
auto result : op->getResults()) {
261 bool anyExternal =
false;
262 for (
auto &use : result.getUses()) {
263 if (!opSet.contains(use.getOwner())) {
265 externalUses.push_back({&use, outputs.size()});
269 outputs.push_back(result);
270 outputTypes.push_back(result.getType());
275 arc::OutputOp::create(builder, lastOp->getLoc(), outputs);
278 builder.setInsertionPoint(module);
280 DefineOp::create(builder, lastOp->getLoc(),
281 builder.getStringAttr(globalNamespace.newName(
282 module.getModuleName() +
"_arc")),
283 builder.getFunctionType(inputTypes, outputTypes));
284 defOp.getBody().push_back(block.release());
288 builder.setInsertionPoint(module.getBodyBlock()->getTerminator());
289 auto arcOp = CallOp::create(builder, lastOp->getLoc(), defOp, inputs);
290 arcUses.push_back(arcOp);
291 for (
auto [use, resultIdx] : externalUses)
292 use->set(arcOp.getResult(resultIdx));
296LogicalResult Converter::absorbRegs(
HWModuleOp module) {
300 unsigned numTrivialRegs = 0;
301 for (
auto callOp : arcUses) {
302 auto stateOp = dyn_cast<StateOp>(callOp.getOperation());
303 Value clock = stateOp ? stateOp.getClock() : Value{};
305 SmallVector<Value> initialValues;
306 SmallVector<seq::CompRegOp> absorbedRegs;
307 SmallVector<Attribute> absorbedNames(callOp->getNumResults(), {});
308 if (
auto names = callOp->getAttrOfType<ArrayAttr>(
"names"))
309 absorbedNames.assign(names.getValue().begin(), names.getValue().end());
314 bool isTrivial =
true;
315 for (
auto result : callOp->getResults()) {
316 if (!result.hasOneUse()) {
320 auto regOp = dyn_cast<seq::CompRegOp>(result.use_begin()->getOwner());
321 if (!regOp || regOp.getInput() != result ||
322 (clock && clock != regOp.getClk())) {
327 clock = regOp.getClk();
328 reset = regOp.getReset();
332 Value resetValue = regOp.getResetValue();
333 Operation *op = resetValue.getDefiningOp();
335 return regOp->emitOpError(
336 "is reset by an input; not supported by ConvertToArcs");
337 if (
auto constant = dyn_cast<hw::ConstantOp>(op)) {
338 if (constant.getValue() != 0)
339 return regOp->emitOpError(
"is reset to a constant non-zero value; "
340 "not supported by ConvertToArcs");
342 return regOp->emitOpError(
"is reset to a value that is not clearly "
343 "constant; not supported by ConvertToArcs");
350 absorbedRegs.push_back(regOp);
354 absorbedNames[result.getResultNumber()] = regOp.getNameAttr();
359 arcUses[outIdx++] = callOp;
368 auto arc = dyn_cast<StateOp>(callOp.getOperation());
370 arc.getClockMutable().assign(clock);
371 arc.setLatency(arc.getLatency() + 1);
373 mlir::IRRewriter rewriter(module->getContext());
374 rewriter.setInsertionPoint(callOp);
375 arc = rewriter.replaceOpWithNewOp<StateOp>(
376 callOp.getOperation(),
377 llvm::cast<SymbolRefAttr>(callOp.getCallableForCallee()),
378 callOp->getResultTypes(), clock, Value{}, 1, callOp.getArgOperands());
383 return arc.emitError(
384 "StateOp tried to infer reset from CompReg, but already "
386 arc.getResetMutable().assign(reset);
389 bool onlyDefaultInitializers =
390 llvm::all_of(initialValues, [](
auto val) ->
bool {
return !val; });
392 if (!onlyDefaultInitializers) {
393 if (!arc.getInitials().empty()) {
394 return arc.emitError(
395 "StateOp tried to infer initial values from CompReg, but already "
396 "had an initial value.");
399 for (
unsigned i = 0; i < initialValues.size(); ++i) {
400 if (!initialValues[i]) {
401 OpBuilder zeroBuilder(arc);
404 zeroBuilder.getIntegerAttr(arc.getResult(i).getType(), 0));
407 arc.getInitialsMutable().assign(initialValues);
410 if (tapRegisters && llvm::any_of(absorbedNames, [](
auto name) {
411 return !cast<StringAttr>(name).getValue().empty();
413 arc->setAttr(
"names", ArrayAttr::get(module.getContext(), absorbedNames));
414 for (
auto [arcResult, reg] :
llvm::zip(arc.getResults(), absorbedRegs)) {
415 auto it = arcBreakerIndices.find(reg);
416 arcBreakers[it->second] = {};
417 arcBreakerIndices.erase(it);
418 reg.replaceAllUsesWith(arcResult);
422 if (numTrivialRegs > 0)
423 LLVM_DEBUG(llvm::dbgs() <<
"- Trivially converted " << numTrivialRegs
424 <<
" regs to arcs\n");
425 arcUses.truncate(outIdx);
430 MapVector<std::tuple<Value, Value, Operation *>, SmallVector<seq::CompRegOp>>
432 for (
auto *op : arcBreakers)
433 if (auto regOp = dyn_cast_or_null<
seq::CompRegOp>(op)) {
434 regsByInput[{regOp.getClk(), regOp.getReset(),
435 regOp.getInput().getDefiningOp()}]
439 unsigned numMappedRegs = 0;
440 for (
auto [clockAndResetAndOp, regOps] : regsByInput) {
441 numMappedRegs += regOps.size();
442 OpBuilder builder(module);
443 auto block = std::make_unique<Block>();
444 builder.setInsertionPointToStart(block.get());
446 SmallVector<Value> inputs;
447 SmallVector<Value> outputs;
448 SmallVector<Attribute> names;
449 SmallVector<Type> types;
450 SmallVector<Value> initialValues;
452 SmallVector<unsigned> regToOutputMapping;
453 for (
auto regOp : regOps) {
454 auto it = mapping.find(regOp.getInput());
455 if (it == mapping.end()) {
456 it = mapping.insert({regOp.getInput(), inputs.size()}).first;
457 inputs.push_back(regOp.getInput());
458 types.push_back(regOp.getType());
459 outputs.push_back(block->addArgument(regOp.getType(), regOp.getLoc()));
460 names.push_back(regOp->getAttrOfType<StringAttr>(
"name"));
464 regToOutputMapping.push_back(it->second);
467 auto loc = regOps.back().getLoc();
468 arc::OutputOp::create(builder, loc, outputs);
470 builder.setInsertionPoint(module);
471 auto defOp = DefineOp::create(builder, loc,
472 builder.getStringAttr(globalNamespace.newName(
473 module.getModuleName() +
"_arc")),
474 builder.getFunctionType(types, types));
475 defOp.getBody().push_back(block.release());
477 builder.setInsertionPoint(module.getBodyBlock()->getTerminator());
479 bool onlyDefaultInitializers =
480 llvm::all_of(initialValues, [](
auto val) ->
bool {
return !val; });
482 if (onlyDefaultInitializers)
483 initialValues.clear();
485 for (
unsigned i = 0; i < initialValues.size(); ++i) {
486 if (!initialValues[i])
488 loc, builder.getIntegerAttr(types[i], 0));
492 StateOp::create(builder, loc, defOp, std::get<0>(clockAndResetAndOp),
493 Value{}, 1, inputs, initialValues);
494 auto reset = std::get<1>(clockAndResetAndOp);
496 arcOp.getResetMutable().assign(reset);
497 if (tapRegisters && llvm::any_of(names, [](
auto name) {
498 return !cast<StringAttr>(name).getValue().empty();
500 arcOp->setAttr(
"names", builder.getArrayAttr(names));
501 for (
auto [reg, resultIdx] :
llvm::zip(regOps, regToOutputMapping)) {
502 reg.replaceAllUsesWith(arcOp.getResult(resultIdx));
507 if (numMappedRegs > 0)
508 LLVM_DEBUG(llvm::dbgs() <<
"- Mapped " << numMappedRegs <<
" regs to "
509 << regsByInput.size() <<
" shuffling arcs\n");
519static LogicalResult
convert(llhd::CombinationalOp op,
520 llhd::CombinationalOp::Adaptor adaptor,
521 ConversionPatternRewriter &rewriter,
522 const TypeConverter &converter) {
524 SmallVector<Type> resultTypes;
525 if (failed(converter.convertTypes(op.getResultTypes(), resultTypes)))
529 auto cloneIntoBody = [](Operation *op) {
530 return op->hasTrait<OpTrait::ConstantLike>();
533 mlir::makeRegionIsolatedFromAbove(rewriter, op.getBody(), cloneIntoBody);
537 ExecuteOp::create(rewriter, op.getLoc(), resultTypes, operands);
538 executeOp.getBody().takeBody(op.getBody());
539 rewriter.replaceOp(op, executeOp.getResults());
544static LogicalResult
convert(llhd::YieldOp op, llhd::YieldOp::Adaptor adaptor,
545 ConversionPatternRewriter &rewriter) {
546 rewriter.replaceOpWithNewOp<arc::OutputOp>(op, adaptor.getOperands());
555#define GEN_PASS_DEF_CONVERTTOARCSPASS
556#include "circt/Conversion/Passes.h.inc"
560struct ConvertToArcsPass
561 :
public circt::impl::ConvertToArcsPassBase<ConvertToArcsPass> {
562 using ConvertToArcsPassBase::ConvertToArcsPassBase;
563 void runOnOperation()
override;
567void ConvertToArcsPass::runOnOperation() {
569 TypeConverter converter;
572 converter.addConversion([](Type type) -> std::optional<Type> {
573 if (isa<llhd::LLHDDialect>(type.getDialect()))
584 ConversionTarget target(getContext());
585 target.addIllegalDialect<llhd::LLHDDialect>();
586 target.markUnknownOpDynamicallyLegal(
587 [](Operation *op) {
return !isa<llhd::LLHDDialect>(op->getDialect()); });
590 ConversionConfig config;
591 config.allowPatternRollback =
false;
594 if (failed(applyPartialConversion(getOperation(), target, std::move(
patterns),
596 emitError(getOperation().
getLoc()) <<
"conversion to arcs failed";
597 return signalPassFailure();
602 outliner.tapRegisters = tapRegisters;
603 if (failed(outliner.run(getOperation())))
604 return signalPassFailure();
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)
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)