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