15 #include "mlir/IR/PatternMatch.h"
16 #include "mlir/Pass/Pass.h"
17 #include "llvm/Support/Debug.h"
19 #define DEBUG_TYPE "convert-to-arcs"
21 using namespace circt;
24 using llvm::MapVector;
27 return op->hasTrait<OpTrait::ConstantLike>() ||
29 ClockedOpInterface, seq::InitialOp, seq::ClockGateOp,
30 sim::DPICallOp>(op) ||
31 op->getNumResults() > 1;
35 SmallVectorImpl<Value> &values) {
36 if (!
reg.getInitialValue())
37 return values.push_back({}), success();
41 OpBuilder builder(
reg);
42 auto init = builder.create<seq::FromImmutableOp>(
reg.getLoc(),
reg.getType(),
43 reg.getInitialValue());
45 values.push_back(init);
55 LogicalResult
run(ModuleOp module);
57 LogicalResult analyzeFanIn();
65 SmallVector<Operation *> arcBreakers;
69 SmallVector<Operation *> postOrder;
73 MapVector<Operation *, APInt> faninMasks;
76 MapVector<APInt, DenseSet<Operation *>> faninMaskGroups;
79 SmallVector<mlir::CallOpInterface> arcUses;
88 for (
auto &op : module.getOps())
90 op.getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName()))
91 globalNamespace.newName(sym.getValue());
92 for (
auto module : module.getOps<
HWModuleOp>())
93 if (failed(runOnModule(module)))
98 LogicalResult Converter::runOnModule(
HWModuleOp module) {
101 arcBreakerIndices.clear();
102 for (Operation &op : *module.getBodyBlock()) {
103 if (isa<seq::InitialOp>(&op))
105 if (op.getNumRegions() > 0)
106 return op.emitOpError(
"has regions; not supported by ConvertToArcs");
109 arcBreakerIndices[&op] = arcBreakers.size();
110 arcBreakers.push_back(&op);
113 if (module.getBodyBlock()->without_terminator().empty() &&
114 isa<hw::OutputOp>(module.getBodyBlock()->getTerminator()))
116 LLVM_DEBUG(llvm::dbgs() <<
"Analyzing " << module.getModuleNameAttr() <<
" ("
117 << arcBreakers.size() <<
" breakers)\n");
122 if (failed(analyzeFanIn()))
128 if (failed(absorbRegs(module)))
134 LogicalResult Converter::analyzeFanIn() {
135 SmallVector<std::tuple<Operation *, unsigned>> worklist;
139 for (
auto *op : arcBreakers) {
140 unsigned index = arcBreakerIndices.lookup(op);
141 auto mask = APInt::getOneBitSet(arcBreakers.size(), index);
142 faninMasks[op] =
mask;
143 worklist.push_back({op, 0});
147 DenseSet<Operation *> seen;
148 DenseSet<Operation *> finished;
150 while (!worklist.empty()) {
151 auto &[op, operandIdx] = worklist.back();
152 if (operandIdx == op->getNumOperands()) {
154 postOrder.push_back(op);
160 auto operand = op->getOperand(operandIdx++);
161 auto *definingOp = operand.getDefiningOp();
163 finished.contains(definingOp))
165 if (!seen.insert(definingOp).second) {
166 definingOp->emitError(
"combinational loop detected");
169 worklist.push_back({definingOp, 0});
171 LLVM_DEBUG(llvm::dbgs() <<
"- Sorted " << postOrder.size() <<
" ops\n");
177 for (
auto *op : llvm::reverse(postOrder)) {
178 auto mask = APInt::getZero(arcBreakers.size());
179 for (
auto *user : op->getUsers()) {
180 auto it = faninMasks.find(user);
181 if (it != faninMasks.end())
185 auto duplicateOp = faninMasks.insert({op,
mask});
187 assert(duplicateOp.second &&
"duplicate op in order");
191 faninMaskGroups.clear();
192 for (
auto [op, mask] : faninMasks)
194 faninMaskGroups[
mask].insert(op);
195 LLVM_DEBUG(llvm::dbgs() <<
"- Found " << faninMaskGroups.size()
196 <<
" fanin mask groups\n");
201 void Converter::extractArcs(
HWModuleOp module) {
202 DenseMap<Value, Value> valueMapping;
203 SmallVector<Value> inputs;
204 SmallVector<Value> outputs;
205 SmallVector<Type> inputTypes;
206 SmallVector<Type> outputTypes;
207 SmallVector<std::pair<OpOperand *, unsigned>> externalUses;
210 for (
auto &group : faninMaskGroups) {
211 auto &opSet = group.second;
212 OpBuilder builder(module);
214 auto block = std::make_unique<Block>();
215 builder.setInsertionPointToStart(block.get());
216 valueMapping.clear();
221 externalUses.clear();
223 Operation *lastOp =
nullptr;
225 for (
auto *op : postOrder) {
226 if (!opSet.contains(op))
231 for (
auto &operand : op->getOpOperands()) {
232 if (opSet.contains(operand.get().getDefiningOp()))
234 auto &mapped = valueMapping[operand.get()];
236 mapped = block->addArgument(operand.get().getType(),
237 operand.get().getLoc());
238 inputs.push_back(operand.get());
239 inputTypes.push_back(mapped.getType());
243 for (
auto result : op->getResults()) {
244 bool anyExternal =
false;
245 for (
auto &use : result.getUses()) {
246 if (!opSet.contains(use.getOwner())) {
248 externalUses.push_back({&use, outputs.size()});
252 outputs.push_back(result);
253 outputTypes.push_back(result.getType());
258 builder.create<arc::OutputOp>(lastOp->getLoc(), outputs);
261 builder.setInsertionPoint(module);
262 auto defOp = builder.create<DefineOp>(
264 builder.getStringAttr(
265 globalNamespace.newName(module.getModuleName() +
"_arc")),
266 builder.getFunctionType(inputTypes, outputTypes));
267 defOp.getBody().push_back(block.release());
271 builder.setInsertionPoint(module.getBodyBlock()->getTerminator());
272 auto arcOp = builder.create<CallOp>(lastOp->getLoc(), defOp, inputs);
273 arcUses.push_back(arcOp);
274 for (
auto [use, resultIdx] : externalUses)
275 use->set(arcOp.getResult(resultIdx));
279 LogicalResult Converter::absorbRegs(
HWModuleOp module) {
283 unsigned numTrivialRegs = 0;
284 for (
auto callOp : arcUses) {
285 auto stateOp = dyn_cast<StateOp>(callOp.getOperation());
286 Value clock = stateOp ? stateOp.getClock() : Value{};
288 SmallVector<Value> initialValues;
289 SmallVector<seq::CompRegOp> absorbedRegs;
290 SmallVector<Attribute> absorbedNames(callOp->getNumResults(), {});
291 if (
auto names = callOp->getAttrOfType<ArrayAttr>(
"names"))
292 absorbedNames.assign(names.getValue().begin(), names.getValue().end());
297 bool isTrivial =
true;
298 for (
auto result : callOp->getResults()) {
299 if (!result.hasOneUse()) {
303 auto regOp = dyn_cast<seq::CompRegOp>(result.use_begin()->getOwner());
304 if (!regOp || regOp.getInput() != result ||
305 (clock && clock != regOp.getClk())) {
310 clock = regOp.getClk();
311 reset = regOp.getReset();
315 Value resetValue = regOp.getResetValue();
316 Operation *op = resetValue.getDefiningOp();
318 return regOp->emitOpError(
319 "is reset by an input; not supported by ConvertToArcs");
320 if (
auto constant = dyn_cast<hw::ConstantOp>(op)) {
321 if (constant.getValue() != 0)
322 return regOp->emitOpError(
"is reset to a constant non-zero value; "
323 "not supported by ConvertToArcs");
325 return regOp->emitOpError(
"is reset to a value that is not clearly "
326 "constant; not supported by ConvertToArcs");
333 absorbedRegs.push_back(regOp);
337 absorbedNames[result.getResultNumber()] = regOp.getNameAttr();
342 arcUses[outIdx++] = callOp;
351 auto arc = dyn_cast<StateOp>(callOp.getOperation());
353 arc.getClockMutable().assign(clock);
354 arc.setLatency(arc.getLatency() + 1);
356 mlir::IRRewriter rewriter(module->getContext());
357 rewriter.setInsertionPoint(callOp);
358 arc = rewriter.replaceOpWithNewOp<StateOp>(
359 callOp.getOperation(),
360 callOp.getCallableForCallee().get<SymbolRefAttr>(),
361 callOp->getResultTypes(), clock, Value{}, 1, callOp.getArgOperands());
366 return arc.emitError(
367 "StateOp tried to infer reset from CompReg, but already "
369 arc.getResetMutable().assign(reset);
372 bool onlyDefaultInitializers =
373 llvm::all_of(initialValues, [](
auto val) ->
bool {
return !val; });
375 if (!onlyDefaultInitializers) {
376 if (!arc.getInitials().empty()) {
377 return arc.emitError(
378 "StateOp tried to infer initial values from CompReg, but already "
379 "had an initial value.");
382 for (
unsigned i = 0; i < initialValues.size(); ++i) {
383 if (!initialValues[i]) {
384 OpBuilder zeroBuilder(arc);
387 zeroBuilder.getIntegerAttr(arc.getResult(i).getType(), 0));
390 arc.getInitialsMutable().assign(initialValues);
393 if (tapRegisters && llvm::any_of(absorbedNames, [](
auto name) {
394 return !cast<StringAttr>(name).getValue().empty();
396 arc->setAttr(
"names",
ArrayAttr::get(module.getContext(), absorbedNames));
397 for (
auto [arcResult,
reg] : llvm::zip(arc.getResults(), absorbedRegs)) {
398 auto it = arcBreakerIndices.find(
reg);
399 arcBreakers[it->second] = {};
400 arcBreakerIndices.erase(it);
401 reg.replaceAllUsesWith(arcResult);
405 if (numTrivialRegs > 0)
406 LLVM_DEBUG(llvm::dbgs() <<
"- Trivially converted " << numTrivialRegs
407 <<
" regs to arcs\n");
408 arcUses.truncate(outIdx);
413 MapVector<std::tuple<Value, Value, Operation *>, SmallVector<seq::CompRegOp>>
415 for (
auto *op : arcBreakers)
416 if (
auto regOp = dyn_cast_or_null<seq::CompRegOp>(op)) {
417 regsByInput[{regOp.getClk(), regOp.getReset(),
418 regOp.getInput().getDefiningOp()}]
422 unsigned numMappedRegs = 0;
423 for (
auto [clockAndResetAndOp, regOps] : regsByInput) {
424 numMappedRegs += regOps.size();
425 OpBuilder builder(module);
426 auto block = std::make_unique<Block>();
427 builder.setInsertionPointToStart(block.get());
429 SmallVector<Value> inputs;
430 SmallVector<Value> outputs;
431 SmallVector<Attribute> names;
432 SmallVector<Type> types;
433 SmallVector<Value> initialValues;
435 SmallVector<unsigned> regToOutputMapping;
436 for (
auto regOp : regOps) {
437 auto it = mapping.find(regOp.getInput());
438 if (it == mapping.end()) {
439 it = mapping.insert({regOp.getInput(), inputs.size()}).first;
440 inputs.push_back(regOp.getInput());
441 types.push_back(regOp.getType());
442 outputs.push_back(block->addArgument(regOp.getType(), regOp.getLoc()));
443 names.push_back(regOp->getAttrOfType<StringAttr>(
"name"));
447 regToOutputMapping.push_back(it->second);
450 auto loc = regOps.back().getLoc();
451 builder.create<arc::OutputOp>(loc, outputs);
453 builder.setInsertionPoint(module);
455 builder.create<DefineOp>(loc,
456 builder.getStringAttr(globalNamespace.newName(
457 module.getModuleName() +
"_arc")),
458 builder.getFunctionType(types, types));
459 defOp.getBody().push_back(block.release());
461 builder.setInsertionPoint(module.getBodyBlock()->getTerminator());
463 bool onlyDefaultInitializers =
464 llvm::all_of(initialValues, [](
auto val) ->
bool {
return !val; });
466 if (onlyDefaultInitializers)
467 initialValues.clear();
469 for (
unsigned i = 0; i < initialValues.size(); ++i) {
470 if (!initialValues[i])
472 loc, builder.getIntegerAttr(types[i], 0));
476 builder.
create<StateOp>(loc, defOp, std::get<0>(clockAndResetAndOp),
477 Value{}, 1, inputs, initialValues);
478 auto reset = std::get<1>(clockAndResetAndOp);
480 arcOp.getResetMutable().assign(reset);
481 if (tapRegisters && llvm::any_of(names, [](
auto name) {
482 return !cast<StringAttr>(name).getValue().empty();
484 arcOp->setAttr(
"names", builder.getArrayAttr(names));
485 for (
auto [
reg, resultIdx] : llvm::zip(regOps, regToOutputMapping)) {
486 reg.replaceAllUsesWith(arcOp.getResult(resultIdx));
491 if (numMappedRegs > 0)
492 LLVM_DEBUG(llvm::dbgs() <<
"- Mapped " << numMappedRegs <<
" regs to "
493 << regsByInput.size() <<
" shuffling arcs\n");
503 #define GEN_PASS_DEF_CONVERTTOARCS
504 #include "circt/Conversion/Passes.h.inc"
508 struct ConvertToArcsPass :
public impl::ConvertToArcsBase<ConvertToArcsPass> {
509 using ConvertToArcsBase::ConvertToArcsBase;
511 void runOnOperation()
override {
513 converter.tapRegisters = tapRegisters;
514 if (failed(converter.run(getOperation())))
520 std::unique_ptr<OperationPass<ModuleOp>>
522 return std::make_unique<ConvertToArcsPass>(options);
assert(baseType &&"element must be base type")
static LogicalResult convertInitialValue(seq::CompRegOp reg, SmallVectorImpl< Value > &values)
static bool isArcBreakingOp(Operation *op)
A namespace that is used to store existing names and generate new names in some scope within the IR.
def create(data_type, value)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< OperationPass< ModuleOp > > createConvertToArcsPass(const ConvertToArcsOptions &options={})
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)