CIRCT  19.0.0git
HandshakeToDC.cpp
Go to the documentation of this file.
1 //===- HandshakeToDC.cpp - Translate Handshake into DC --------------------===//
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 Handshake to DC Conversion Pass Implementation.
10 //
11 //===----------------------------------------------------------------------===//
12 
16 #include "circt/Dialect/DC/DCOps.h"
18 #include "circt/Dialect/HW/HWOps.h"
23 #include "mlir/Dialect/Arith/IR/Arith.h"
24 #include "mlir/Pass/Pass.h"
25 #include "mlir/Pass/PassManager.h"
26 #include "mlir/Transforms/DialectConversion.h"
27 #include "llvm/Support/MathExtras.h"
28 #include <optional>
29 
30 namespace circt {
31 #define GEN_PASS_DEF_HANDSHAKETODC
32 #include "circt/Conversion/Passes.h.inc"
33 } // namespace circt
34 
35 using namespace mlir;
36 using namespace circt;
37 using namespace handshake;
38 using namespace dc;
39 using namespace hw;
40 using namespace handshaketodc;
41 
42 namespace {
43 
44 struct DCTuple {
45  DCTuple() = default;
46  DCTuple(Value token, Value data) : token(token), data(data) {}
47  DCTuple(dc::UnpackOp unpack)
48  : token(unpack.getToken()), data(unpack.getOutput()) {}
49  Value token;
50  Value data;
51 };
52 
53 // Unpack a !dc.value<...> into a DCTuple.
54 static DCTuple unpack(OpBuilder &b, Value v) {
55  if (isa<dc::ValueType>(v.getType()))
56  return DCTuple(b.create<dc::UnpackOp>(v.getLoc(), v));
57  assert(isa<dc::TokenType>(v.getType()) && "Expected a dc::TokenType");
58  return DCTuple(v, {});
59 }
60 
61 static Value pack(OpBuilder &b, Value token, Value data = {}) {
62  if (!data)
63  return token;
64  return b.create<dc::PackOp>(token.getLoc(), token, data);
65 }
66 
67 class DCTypeConverter : public TypeConverter {
68 public:
69  DCTypeConverter() {
70  addConversion([](Type type) -> Type {
71  if (isa<NoneType>(type))
72  return dc::TokenType::get(type.getContext());
73  return dc::ValueType::get(type.getContext(), type);
74  });
75  addConversion([](ValueType type) { return type; });
76  addConversion([](TokenType type) { return type; });
77 
78  addTargetMaterialization(
79  [](mlir::OpBuilder &builder, mlir::Type resultType,
80  mlir::ValueRange inputs,
81  mlir::Location loc) -> std::optional<mlir::Value> {
82  if (inputs.size() != 1)
83  return std::nullopt;
84 
85  // Materialize !dc.value<> -> !dc.token
86  if (isa<dc::TokenType>(resultType) &&
87  isa<dc::ValueType>(inputs.front().getType()))
88  return unpack(builder, inputs.front()).token;
89 
90  // Materialize !dc.token -> !dc.value<>
91  auto vt = dyn_cast<dc::ValueType>(resultType);
92  if (vt && !vt.getInnerType())
93  return pack(builder, inputs.front());
94 
95  return inputs[0];
96  });
97 
98  addSourceMaterialization(
99  [](mlir::OpBuilder &builder, mlir::Type resultType,
100  mlir::ValueRange inputs,
101  mlir::Location loc) -> std::optional<mlir::Value> {
102  if (inputs.size() != 1)
103  return std::nullopt;
104 
105  // Materialize !dc.value<> -> !dc.token
106  if (isa<dc::TokenType>(resultType) &&
107  isa<dc::ValueType>(inputs.front().getType()))
108  return unpack(builder, inputs.front()).token;
109 
110  // Materialize !dc.token -> !dc.value<>
111  auto vt = dyn_cast<dc::ValueType>(resultType);
112  if (vt && !vt.getInnerType())
113  return pack(builder, inputs.front());
114 
115  return inputs[0];
116  });
117  }
118 };
119 
120 template <typename OpTy>
121 class DCOpConversionPattern : public OpConversionPattern<OpTy> {
122 public:
124  using OpAdaptor = typename OpTy::Adaptor;
125 
126  DCOpConversionPattern(MLIRContext *context, TypeConverter &typeConverter,
127  ConvertedOps *convertedOps)
128  : OpConversionPattern<OpTy>(typeConverter, context),
129  convertedOps(convertedOps) {}
130  mutable ConvertedOps *convertedOps;
131 };
132 
133 class CondBranchConversionPattern
134  : public DCOpConversionPattern<handshake::ConditionalBranchOp> {
135 public:
136  using DCOpConversionPattern<
137  handshake::ConditionalBranchOp>::DCOpConversionPattern;
138  using OpAdaptor = typename handshake::ConditionalBranchOp::Adaptor;
139 
140  LogicalResult
141  matchAndRewrite(handshake::ConditionalBranchOp op, OpAdaptor adaptor,
142  ConversionPatternRewriter &rewriter) const override {
143  auto condition = unpack(rewriter, adaptor.getConditionOperand());
144  auto data = unpack(rewriter, adaptor.getDataOperand());
145 
146  // Join the token of the condition and the input.
147  auto join = rewriter.create<dc::JoinOp>(
148  op.getLoc(), ValueRange{condition.token, data.token});
149 
150  // Pack that together with the condition data.
151  auto packedCondition = pack(rewriter, join, condition.data);
152 
153  // Branch on the input data and the joined control input.
154  auto branch = rewriter.create<dc::BranchOp>(op.getLoc(), packedCondition);
155 
156  // Pack the branch output tokens with the input data, and replace the uses.
157  llvm::SmallVector<Value, 4> packed;
158  packed.push_back(pack(rewriter, branch.getTrueToken(), data.data));
159  packed.push_back(pack(rewriter, branch.getFalseToken(), data.data));
160 
161  rewriter.replaceOp(op, packed);
162  return success();
163  }
164 };
165 
166 class ForkOpConversionPattern
167  : public DCOpConversionPattern<handshake::ForkOp> {
168 public:
169  using DCOpConversionPattern<handshake::ForkOp>::DCOpConversionPattern;
170  using OpAdaptor = typename handshake::ForkOp::Adaptor;
171 
172  LogicalResult
173  matchAndRewrite(handshake::ForkOp op, OpAdaptor adaptor,
174  ConversionPatternRewriter &rewriter) const override {
175  auto input = unpack(rewriter, adaptor.getOperand());
176  auto forkOut = rewriter.create<dc::ForkOp>(op.getLoc(), input.token,
177  op.getNumResults());
178 
179  // Pack the fork result tokens with the input data, and replace the uses.
180  llvm::SmallVector<Value, 4> packed;
181  for (auto res : forkOut.getResults())
182  packed.push_back(pack(rewriter, res, input.data));
183 
184  rewriter.replaceOp(op, packed);
185  return success();
186  }
187 };
188 
189 class JoinOpConversion : public DCOpConversionPattern<handshake::JoinOp> {
190 public:
191  using DCOpConversionPattern<handshake::JoinOp>::DCOpConversionPattern;
192  using OpAdaptor = typename handshake::JoinOp::Adaptor;
193 
194  LogicalResult
195  matchAndRewrite(handshake::JoinOp op, OpAdaptor adaptor,
196  ConversionPatternRewriter &rewriter) const override {
197  llvm::SmallVector<Value, 4> inputTokens;
198  for (auto input : adaptor.getData())
199  inputTokens.push_back(unpack(rewriter, input).token);
200 
201  rewriter.replaceOpWithNewOp<dc::JoinOp>(op, inputTokens);
202  return success();
203  }
204 };
205 
206 class MergeOpConversion : public DCOpConversionPattern<handshake::MergeOp> {
207 public:
208  using DCOpConversionPattern<handshake::MergeOp>::DCOpConversionPattern;
209  using OpAdaptor = typename handshake::MergeOp::Adaptor;
210 
211  LogicalResult
212  matchAndRewrite(handshake::MergeOp op, OpAdaptor adaptor,
213  ConversionPatternRewriter &rewriter) const override {
214  if (op.getNumOperands() > 2)
215  return rewriter.notifyMatchFailure(op, "only two inputs supported");
216 
217  SmallVector<Value, 4> tokens, data;
218 
219  for (auto input : adaptor.getDataOperands()) {
220  auto up = unpack(rewriter, input);
221  tokens.push_back(up.token);
222  if (up.data)
223  data.push_back(up.data);
224  }
225 
226  // Control side
227  Value selectedIndex = rewriter.create<dc::MergeOp>(op.getLoc(), tokens);
228  auto selectedIndexUnpacked = unpack(rewriter, selectedIndex);
229  Value mergeOutput;
230 
231  if (!data.empty()) {
232  // Data-merge; mux the selected input.
233  auto dataMux = rewriter.create<arith::SelectOp>(
234  op.getLoc(), selectedIndexUnpacked.data, data[0], data[1]);
235  convertedOps->insert(dataMux);
236 
237  // Pack the data mux with the control token.
238  mergeOutput = pack(rewriter, selectedIndexUnpacked.token, dataMux);
239  } else {
240  // Control-only merge; throw away the index value of the dc.merge
241  // operation and only forward the dc.token.
242  mergeOutput = selectedIndexUnpacked.token;
243  }
244 
245  rewriter.replaceOp(op, mergeOutput);
246  return success();
247  }
248 };
249 
250 class ControlMergeOpConversion
251  : public DCOpConversionPattern<handshake::ControlMergeOp> {
252 public:
253  using DCOpConversionPattern<handshake::ControlMergeOp>::DCOpConversionPattern;
254 
255  using OpAdaptor = typename handshake::ControlMergeOp::Adaptor;
256 
257  LogicalResult
258  matchAndRewrite(handshake::ControlMergeOp op, OpAdaptor adaptor,
259  ConversionPatternRewriter &rewriter) const override {
260  if (op.getDataOperands().size() != 2)
261  return op.emitOpError("expected two data operands");
262 
263  llvm::SmallVector<Value> tokens, data;
264  for (auto input : adaptor.getDataOperands()) {
265  auto up = unpack(rewriter, input);
266  tokens.push_back(up.token);
267  if (up.data)
268  data.push_back(up.data);
269  }
270 
271  bool isIndexType = isa<IndexType>(op.getIndex().getType());
272 
273  // control-side
274  Value selectedIndex = rewriter.create<dc::MergeOp>(op.getLoc(), tokens);
275  auto mergeOpUnpacked = unpack(rewriter, selectedIndex);
276  auto selValue = mergeOpUnpacked.data;
277 
278  Value dataSide = selectedIndex;
279  if (!data.empty()) {
280  // Data side mux using the selected input.
281  auto dataMux = rewriter.create<arith::SelectOp>(op.getLoc(), selValue,
282  data[0], data[1]);
283  convertedOps->insert(dataMux);
284  // Pack the data mux with the control token.
285  auto packed = pack(rewriter, mergeOpUnpacked.token, dataMux);
286 
287  dataSide = packed;
288  }
289 
290  // if the original op used `index` as the select operand type, we need to
291  // index-cast the unpacked select operand
292  if (isIndexType) {
293  selValue = rewriter.create<arith::IndexCastOp>(
294  op.getLoc(), rewriter.getIndexType(), selValue);
295  convertedOps->insert(selValue.getDefiningOp());
296  selectedIndex = pack(rewriter, mergeOpUnpacked.token, selValue);
297  } else {
298  // The cmerge had a specific type defined for the index type. dc.merge
299  // provides an i1 operand for the selected index, so we need to cast it.
300  selValue = rewriter.create<arith::ExtUIOp>(
301  op.getLoc(), op.getIndex().getType(), selValue);
302  convertedOps->insert(selValue.getDefiningOp());
303  selectedIndex = pack(rewriter, mergeOpUnpacked.token, selValue);
304  }
305 
306  rewriter.replaceOp(op, {dataSide, selectedIndex});
307  return success();
308  }
309 };
310 
311 class SyncOpConversion : public DCOpConversionPattern<handshake::SyncOp> {
312 public:
313  using DCOpConversionPattern<handshake::SyncOp>::DCOpConversionPattern;
314  using OpAdaptor = typename handshake::SyncOp::Adaptor;
315 
316  LogicalResult
317  matchAndRewrite(handshake::SyncOp op, OpAdaptor adaptor,
318  ConversionPatternRewriter &rewriter) const override {
319  llvm::SmallVector<Value, 4> inputTokens;
320  for (auto input : adaptor.getOperands())
321  inputTokens.push_back(unpack(rewriter, input).token);
322 
323  auto syncToken = rewriter.create<dc::JoinOp>(op.getLoc(), inputTokens);
324 
325  // Wrap all outputs with the synchronization token
326  llvm::SmallVector<Value, 4> wrappedInputs;
327  for (auto input : adaptor.getOperands())
328  wrappedInputs.push_back(pack(rewriter, syncToken, input));
329 
330  rewriter.replaceOp(op, wrappedInputs);
331 
332  return success();
333  }
334 };
335 
336 class ConstantOpConversion
337  : public DCOpConversionPattern<handshake::ConstantOp> {
338 public:
339  using DCOpConversionPattern<handshake::ConstantOp>::DCOpConversionPattern;
340  using OpAdaptor = typename handshake::ConstantOp::Adaptor;
341 
342  LogicalResult
343  matchAndRewrite(handshake::ConstantOp op, OpAdaptor adaptor,
344  ConversionPatternRewriter &rewriter) const override {
345  // Wrap the constant with a token.
346  auto token = rewriter.create<dc::SourceOp>(op.getLoc());
347  auto cst =
348  rewriter.create<arith::ConstantOp>(op.getLoc(), adaptor.getValue());
349  convertedOps->insert(cst);
350  rewriter.replaceOp(op, pack(rewriter, token, cst));
351  return success();
352  }
353 };
354 
355 struct UnitRateConversionPattern : public ConversionPattern {
356 public:
357  UnitRateConversionPattern(MLIRContext *context, TypeConverter &converter,
358  ConvertedOps *joinedOps)
359  : ConversionPattern(converter, MatchAnyOpTypeTag(), 1, context),
360  joinedOps(joinedOps) {}
361  using ConversionPattern::ConversionPattern;
362 
363  // Generic pattern which replaces an operation by one of the same type, but
364  // with the in- and outputs synchronized through join semantics.
365  LogicalResult
366  matchAndRewrite(Operation *op, ArrayRef<Value> operands,
367  ConversionPatternRewriter &rewriter) const override {
368  llvm::SmallVector<Value> inputData;
369 
370  Value outToken;
371  if (operands.empty()) {
372  if (!op->hasTrait<OpTrait::ConstantLike>())
373  return op->emitOpError(
374  "no-operand operation which isn't constant-like. Too dangerous "
375  "to assume semantics - won't convert");
376 
377  // Constant-like operation; assume the token can be represented as a
378  // constant `dc.source`.
379  outToken = rewriter.create<dc::SourceOp>(op->getLoc());
380  } else {
381  llvm::SmallVector<Value> inputTokens;
382  for (auto input : operands) {
383  auto dct = unpack(rewriter, input);
384  inputData.push_back(dct.data);
385  inputTokens.push_back(dct.token);
386  }
387  // Join the tokens of the inputs.
388  assert(!inputTokens.empty() && "Expected at least one input token");
389  outToken = rewriter.create<dc::JoinOp>(op->getLoc(), inputTokens);
390  }
391 
392  // Patchwork to fix bad IR design in Handshake.
393  auto opName = op->getName();
394  if (opName.getStringRef() == "handshake.select") {
395  opName = OperationName("arith.select", getContext());
396  } else if (opName.getStringRef() == "handshake.constant") {
397  opName = OperationName("arith.constant", getContext());
398  }
399 
400  // Re-create the operation using the unpacked input data.
401  OperationState state(op->getLoc(), opName, inputData, op->getResultTypes(),
402  op->getAttrs(), op->getSuccessors());
403 
404  Operation *newOp = rewriter.create(state);
405  joinedOps->insert(newOp);
406 
407  // Pack the result token with the output data, and replace the uses.
408  llvm::SmallVector<Value> results;
409  for (auto result : newOp->getResults())
410  results.push_back(pack(rewriter, outToken, result));
411 
412  rewriter.replaceOp(op, results);
413 
414  return success();
415  }
416 
417  mutable ConvertedOps *joinedOps;
418 };
419 
420 class SinkOpConversionPattern
421  : public DCOpConversionPattern<handshake::SinkOp> {
422 public:
423  using DCOpConversionPattern<handshake::SinkOp>::DCOpConversionPattern;
424  using OpAdaptor = typename handshake::SinkOp::Adaptor;
425 
426  LogicalResult
427  matchAndRewrite(handshake::SinkOp op, OpAdaptor adaptor,
428  ConversionPatternRewriter &rewriter) const override {
429  auto input = unpack(rewriter, adaptor.getOperand());
430  rewriter.replaceOpWithNewOp<dc::SinkOp>(op, input.token);
431  return success();
432  }
433 };
434 
435 class SourceOpConversionPattern
436  : public DCOpConversionPattern<handshake::SourceOp> {
437 public:
438  using DCOpConversionPattern<handshake::SourceOp>::DCOpConversionPattern;
439  using OpAdaptor = typename handshake::SourceOp::Adaptor;
440 
441  LogicalResult
442  matchAndRewrite(handshake::SourceOp op, OpAdaptor adaptor,
443  ConversionPatternRewriter &rewriter) const override {
444  rewriter.replaceOpWithNewOp<dc::SourceOp>(op);
445  return success();
446  }
447 };
448 
449 class BufferOpConversion : public DCOpConversionPattern<handshake::BufferOp> {
450 public:
451  using DCOpConversionPattern<handshake::BufferOp>::DCOpConversionPattern;
452  using OpAdaptor = typename handshake::BufferOp::Adaptor;
453 
454  LogicalResult
455  matchAndRewrite(handshake::BufferOp op, OpAdaptor adaptor,
456  ConversionPatternRewriter &rewriter) const override {
457  rewriter.getI32IntegerAttr(1);
458  rewriter.replaceOpWithNewOp<dc::BufferOp>(
459  op, adaptor.getOperand(), static_cast<size_t>(op.getNumSlots()));
460  return success();
461  }
462 };
463 
464 class ReturnOpConversion : public OpConversionPattern<handshake::ReturnOp> {
465 public:
467  using OpAdaptor = typename handshake::ReturnOp::Adaptor;
468 
469  LogicalResult
470  matchAndRewrite(handshake::ReturnOp op, OpAdaptor adaptor,
471  ConversionPatternRewriter &rewriter) const override {
472  // Locate existing output op, Append operands to output op, and move to
473  // the end of the block.
474  auto hwModule = op->getParentOfType<hw::HWModuleOp>();
475  auto outputOp = *hwModule.getBodyBlock()->getOps<hw::OutputOp>().begin();
476  outputOp->setOperands(adaptor.getOperands());
477  outputOp->moveAfter(&hwModule.getBodyBlock()->back());
478  rewriter.eraseOp(op);
479  return success();
480  }
481 };
482 
483 class MuxOpConversionPattern : public DCOpConversionPattern<handshake::MuxOp> {
484 public:
485  using DCOpConversionPattern<handshake::MuxOp>::DCOpConversionPattern;
486  using OpAdaptor = typename handshake::MuxOp::Adaptor;
487 
488  LogicalResult
489  matchAndRewrite(handshake::MuxOp op, OpAdaptor adaptor,
490  ConversionPatternRewriter &rewriter) const override {
491  auto select = unpack(rewriter, adaptor.getSelectOperand());
492  auto selectData = select.data;
493  auto selectToken = select.token;
494  bool isIndexType = isa<IndexType>(selectData.getType());
495 
496  bool withData = !isa<NoneType>(op.getResult().getType());
497 
498  llvm::SmallVector<DCTuple> inputs;
499  for (auto input : adaptor.getDataOperands())
500  inputs.push_back(unpack(rewriter, input));
501 
502  Value dataMux;
503  Value controlMux = inputs.front().token;
504  // Convert the data-side mux to a sequence of arith.select operations.
505  // The data and control muxes are assumed one-hot and the base-case is set
506  // as the first input.
507  if (withData)
508  dataMux = inputs[0].data;
509 
510  llvm::SmallVector<Value> controlMuxInputs = {inputs.front().token};
511  for (auto [i, input] :
512  llvm::enumerate(llvm::make_range(inputs.begin() + 1, inputs.end()))) {
513  if (!withData)
514  continue;
515 
516  Value cmpIndex;
517  Value inputData = input.data;
518  Value inputControl = input.token;
519  if (isIndexType) {
520  cmpIndex = rewriter.create<arith::ConstantIndexOp>(op.getLoc(), i);
521  } else {
522  size_t width = cast<IntegerType>(selectData.getType()).getWidth();
523  cmpIndex = rewriter.create<arith::ConstantIntOp>(op.getLoc(), i, width);
524  }
525  auto inputSelected = rewriter.create<arith::CmpIOp>(
526  op.getLoc(), arith::CmpIPredicate::eq, selectData, cmpIndex);
527  dataMux = rewriter.create<arith::SelectOp>(op.getLoc(), inputSelected,
528  inputData, dataMux);
529 
530  // Legalize the newly created operations.
531  convertedOps->insert(cmpIndex.getDefiningOp());
532  convertedOps->insert(dataMux.getDefiningOp());
533  convertedOps->insert(inputSelected);
534 
535  // And similarly for the control mux, by muxing the input token with a
536  // select value that has it's control from the original select token +
537  // the inputSelected value.
538  auto inputSelectedControl = pack(rewriter, selectToken, inputSelected);
539  controlMux = rewriter.create<dc::SelectOp>(
540  op.getLoc(), inputSelectedControl, inputControl, controlMux);
541  convertedOps->insert(controlMux.getDefiningOp());
542  }
543 
544  // finally, pack the control and data side muxes into the output value.
545  rewriter.replaceOp(
546  op, pack(rewriter, controlMux, withData ? dataMux : Value{}));
547  return success();
548  }
549 };
550 
551 static hw::ModulePortInfo getModulePortInfoHS(const TypeConverter &tc,
552  handshake::FuncOp funcOp) {
553  SmallVector<hw::PortInfo> inputs, outputs;
554  auto *ctx = funcOp->getContext();
555  auto ft = funcOp.getFunctionType();
556 
557  // Add all inputs of funcOp.
558  for (auto [index, type] : llvm::enumerate(ft.getInputs())) {
559  inputs.push_back({{StringAttr::get(ctx, "in" + std::to_string(index)),
560  tc.convertType(type), hw::ModulePort::Direction::Input},
561  index,
562  {}});
563  }
564 
565  // Add all outputs of funcOp.
566  for (auto [index, type] : llvm::enumerate(ft.getResults())) {
567  outputs.push_back(
568  {{StringAttr::get(ctx, "out" + std::to_string(index)),
569  tc.convertType(type), hw::ModulePort::Direction::Output},
570  index,
571  {}});
572  }
573 
574  return hw::ModulePortInfo{inputs, outputs};
575 }
576 
577 class FuncOpConversion : public OpConversionPattern<handshake::FuncOp> {
578 public:
580  using OpAdaptor = typename handshake::FuncOp::Adaptor;
581 
582  // Replaces a handshake.func with a hw.module, converting the argument and
583  // result types using the provided type converter.
584  // @mortbopet: Not a fan of converting to hw here seeing as we don't
585  // necessarily have hardware semantics here. But, DC doesn't define a function
586  // operation, and there is no "func.graph_func" or any other generic function
587  // operation which is a graph region...
588  LogicalResult
589  matchAndRewrite(handshake::FuncOp op, OpAdaptor adaptor,
590  ConversionPatternRewriter &rewriter) const override {
591  ModulePortInfo ports = getModulePortInfoHS(*getTypeConverter(), op);
592 
593  if (op.isExternal()) {
594  rewriter.create<hw::HWModuleExternOp>(
595  op.getLoc(), rewriter.getStringAttr(op.getName()), ports);
596  } else {
597  auto hwModule = rewriter.create<hw::HWModuleOp>(
598  op.getLoc(), rewriter.getStringAttr(op.getName()), ports);
599 
600  auto &region = op->getRegions().front();
601 
602  Region &moduleRegion = hwModule->getRegions().front();
603  rewriter.mergeBlocks(&region.getBlocks().front(), hwModule.getBodyBlock(),
604  hwModule.getBodyBlock()->getArguments());
605  TypeConverter::SignatureConversion result(moduleRegion.getNumArguments());
606  (void)getTypeConverter()->convertSignatureArgs(
607  TypeRange(moduleRegion.getArgumentTypes()), result);
608  rewriter.applySignatureConversion(hwModule.getBodyBlock(), result);
609  }
610 
611  rewriter.eraseOp(op);
612  return success();
613  }
614 };
615 
616 class HandshakeToDCPass
617  : public circt::impl::HandshakeToDCBase<HandshakeToDCPass> {
618 public:
619  void runOnOperation() override {
620  mlir::ModuleOp mod = getOperation();
621  auto targetModifier = [](mlir::ConversionTarget &target) {
622  target.addLegalDialect<hw::HWDialect, func::FuncDialect>();
623  };
624 
625  auto patternBuilder = [&](TypeConverter &typeConverter,
626  handshaketodc::ConvertedOps &convertedOps,
627  RewritePatternSet &patterns) {
628  patterns.add<FuncOpConversion, ReturnOpConversion>(typeConverter,
629  mod.getContext());
630  };
631 
632  LogicalResult res = runHandshakeToDC(mod, patternBuilder, targetModifier);
633  if (failed(res))
634  signalPassFailure();
635  }
636 };
637 } // namespace
638 
639 std::unique_ptr<mlir::Pass> circt::createHandshakeToDCPass() {
640  return std::make_unique<HandshakeToDCPass>();
641 }
642 
644  mlir::Operation *op,
645  llvm::function_ref<void(TypeConverter &typeConverter,
646  handshaketodc::ConvertedOps &convertedOps,
647  RewritePatternSet &patterns)>
648  patternBuilder,
649  llvm::function_ref<void(mlir::ConversionTarget &)> configureTarget) {
650  // Maintain the set of operations which has been converted either through
651  // unit rate conversion, or as part of other conversions.
652  // Rationale:
653  // This is needed for all of the arith ops that get created as part of the
654  // handshake ops (e.g. arith.select for handshake.mux). There's a bit of a
655  // dilemma here seeing as all operations need to be converted/touched in a
656  // handshake.func - which is done so by UnitRateConversionPattern (when no
657  // other pattern applies). However, we obviously don't want to run said
658  // pattern on these newly created ops since they do not have handshake
659  // semantics.
660  handshaketodc::ConvertedOps convertedOps;
661  mlir::MLIRContext *ctx = op->getContext();
662  ConversionTarget target(*ctx);
663  target.addIllegalDialect<handshake::HandshakeDialect>();
664  target.addLegalDialect<dc::DCDialect>();
665  target.addLegalOp<mlir::ModuleOp>();
666 
667  // And any user-specified target adjustments
668  if (configureTarget)
669  configureTarget(target);
670 
671  // The various patterns will insert new operations into the module to
672  // facilitate the conversion - however, these operations must be
673  // distinguishable from already converted operations (which may be of the
674  // same type as the newly inserted operations). To do this, we mark all
675  // operations which have been converted as legal, and all other operations
676  // as illegal.
677  target.markUnknownOpDynamicallyLegal(
678  [&](Operation *op) { return convertedOps.contains(op); });
679 
680  DCTypeConverter typeConverter;
681  RewritePatternSet patterns(ctx);
682 
683  // Add handshake conversion patterns.
684  // Note: merge/control merge are not supported - these are non-deterministic
685  // operators and we do not care for them.
686  patterns.add<BufferOpConversion, CondBranchConversionPattern,
687  SinkOpConversionPattern, SourceOpConversionPattern,
688  MuxOpConversionPattern, ForkOpConversionPattern,
689  JoinOpConversion, MergeOpConversion, ControlMergeOpConversion,
690  ConstantOpConversion, SyncOpConversion>(ctx, typeConverter,
691  &convertedOps);
692 
693  // ALL other single-result operations are converted via the
694  // UnitRateConversionPattern.
695  patterns.add<UnitRateConversionPattern>(ctx, typeConverter, &convertedOps);
696 
697  // Build any user-specified patterns
698  patternBuilder(typeConverter, convertedOps, patterns);
699  return applyPartialConversion(op, target, std::move(patterns));
700 }
assert(baseType &&"element must be base type")
int32_t width
Definition: FIRRTL.cpp:36
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
DenseSet< Operation * > ConvertedOps
Definition: HandshakeToDC.h:33
LogicalResult runHandshakeToDC(mlir::Operation *op, llvm::function_ref< void(TypeConverter &typeConverter, ConvertedOps &convertedOps, RewritePatternSet &patterns)> patternBuilder, llvm::function_ref< void(mlir::ConversionTarget &)> configureTarget={})
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createHandshakeToDCPass()
Definition: hw.py:1
This holds a decoded list of input/inout and output ports for a module or instance.
Creates a new Calyx component for each FuncOp in the program.