CIRCT  20.0.0git
CalyxToHW.cpp
Go to the documentation of this file.
1 //===- CalyxToHW.cpp - Translate Calyx into HW ----------------------------===//
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 Calyx to HW Conversion Pass Implementation.
10 //
11 //===----------------------------------------------------------------------===//
12 
18 #include "circt/Dialect/HW/HWOps.h"
21 #include "circt/Dialect/SV/SVOps.h"
23 #include "mlir/IR/ImplicitLocOpBuilder.h"
24 #include "mlir/Pass/Pass.h"
25 #include "mlir/Transforms/DialectConversion.h"
26 #include "llvm/ADT/TypeSwitch.h"
27 
28 namespace circt {
29 #define GEN_PASS_DEF_CALYXTOHW
30 #include "circt/Conversion/Passes.h.inc"
31 } // namespace circt
32 
33 using namespace mlir;
34 using namespace circt;
35 using namespace circt::calyx;
36 using namespace circt::comb;
37 using namespace circt::hw;
38 using namespace circt::seq;
39 using namespace circt::sv;
40 
41 /// ConversionPatterns.
42 
43 struct ConvertComponentOp : public OpConversionPattern<ComponentOp> {
44  using OpConversionPattern::OpConversionPattern;
45 
46  LogicalResult
47  matchAndRewrite(ComponentOp component, OpAdaptor adaptor,
48  ConversionPatternRewriter &rewriter) const override {
49  SmallVector<hw::PortInfo> hwInputInfo;
50  auto portInfo = component.getPortInfo();
51  for (auto [name, type, direction, _] : portInfo)
52  hwInputInfo.push_back({{name, type, hwDirection(direction)}});
53  ModulePortInfo hwPortInfo(hwInputInfo);
54 
55  SmallVector<Value> argValues;
56  auto hwMod = rewriter.create<HWModuleOp>(
57  component.getLoc(), component.getNameAttr(), hwPortInfo,
58  [&](OpBuilder &b, HWModulePortAccessor &ports) {
59  for (auto [name, type, direction, _] : portInfo) {
60  switch (direction) {
62  assert(ports.getInput(name).getType() == type);
63  argValues.push_back(ports.getInput(name));
64  break;
66  auto wire = b.create<sv::WireOp>(component.getLoc(), type, name);
67  auto wireRead =
68  b.create<sv::ReadInOutOp>(component.getLoc(), wire);
69  argValues.push_back(wireRead);
70  ports.setOutput(name, wireRead);
71  break;
72  }
73  }
74  });
75 
76  auto *outputOp = hwMod.getBodyBlock()->getTerminator();
77  rewriter.mergeBlocks(component.getBodyBlock(), hwMod.getBodyBlock(),
78  argValues);
79  outputOp->moveAfter(&hwMod.getBodyBlock()->back());
80  rewriter.eraseOp(component);
81  return success();
82  }
83 
84 private:
86  switch (dir) {
91  }
92  llvm_unreachable("unknown direction");
93  }
94 };
95 
96 struct ConvertWiresOp : public OpConversionPattern<WiresOp> {
97  using OpConversionPattern::OpConversionPattern;
98 
99  LogicalResult
100  matchAndRewrite(WiresOp wires, OpAdaptor adaptor,
101  ConversionPatternRewriter &rewriter) const override {
102  HWModuleOp hwMod = wires->getParentOfType<HWModuleOp>();
103  rewriter.inlineRegionBefore(wires.getBody(), hwMod.getBodyRegion(),
104  hwMod.getBodyRegion().end());
105  rewriter.eraseOp(wires);
106  rewriter.inlineBlockBefore(&hwMod.getBodyRegion().getBlocks().back(),
107  &hwMod.getBodyBlock()->back());
108  return success();
109  }
110 };
111 
112 struct ConvertControlOp : public OpConversionPattern<ControlOp> {
113  using OpConversionPattern::OpConversionPattern;
114 
115  LogicalResult
116  matchAndRewrite(ControlOp control, OpAdaptor adaptor,
117  ConversionPatternRewriter &rewriter) const override {
118  if (!control.getBodyBlock()->empty())
119  return control.emitOpError("calyx control must be structural");
120  rewriter.eraseOp(control);
121  return success();
122  }
123 };
124 
125 struct ConvertAssignOp : public OpConversionPattern<calyx::AssignOp> {
126  using OpConversionPattern::OpConversionPattern;
127 
128  LogicalResult
129  matchAndRewrite(calyx::AssignOp assign, OpAdaptor adaptor,
130  ConversionPatternRewriter &rewriter) const override {
131  Value src = adaptor.getSrc();
132  if (auto guard = adaptor.getGuard()) {
133  auto zero =
134  rewriter.create<hw::ConstantOp>(assign.getLoc(), src.getType(), 0);
135  src = rewriter.create<MuxOp>(assign.getLoc(), guard, src, zero);
136  for (Operation *destUser :
137  llvm::make_early_inc_range(assign.getDest().getUsers())) {
138  if (destUser == assign)
139  continue;
140  if (auto otherAssign = dyn_cast<calyx::AssignOp>(destUser)) {
141  src = rewriter.create<MuxOp>(assign.getLoc(), otherAssign.getGuard(),
142  otherAssign.getSrc(), src);
143  rewriter.eraseOp(destUser);
144  }
145  }
146  }
147 
148  // To make life easy in ConvertComponentOp, we read from the output wires so
149  // the dialect conversion block argument mapping would work without a type
150  // converter. This means assigns to ComponentOp outputs will try to assign
151  // to a read from a wire, so we need to map to the wire.
152  Value dest = adaptor.getDest();
153  if (auto readInOut = dyn_cast<ReadInOutOp>(dest.getDefiningOp()))
154  dest = readInOut.getInput();
155 
156  rewriter.replaceOpWithNewOp<sv::AssignOp>(assign, dest, src);
157 
158  return success();
159  }
160 };
161 
162 struct ConvertCellOp : public OpInterfaceConversionPattern<CellInterface> {
163  using OpInterfaceConversionPattern::OpInterfaceConversionPattern;
164 
165  LogicalResult
166  matchAndRewrite(CellInterface cell, ArrayRef<Value> operands,
167  ConversionPatternRewriter &rewriter) const override {
168  assert(operands.empty() && "calyx cells do not have operands");
169 
170  SmallVector<Value> wires;
171  ImplicitLocOpBuilder builder(cell.getLoc(), rewriter);
172  convertPrimitiveOp(cell, wires, builder);
173  if (wires.size() != cell.getPortInfo().size()) {
174  auto diag = cell.emitOpError("couldn't convert to core primitive");
175  for (Value wire : wires)
176  diag.attachNote() << "with wire: " << wire;
177  return diag;
178  }
179 
180  rewriter.replaceOp(cell, wires);
181 
182  return success();
183  }
184 
185 private:
186  void convertPrimitiveOp(Operation *op, SmallVectorImpl<Value> &wires,
187  ImplicitLocOpBuilder &b) const {
188  TypeSwitch<Operation *>(op)
189  // Comparison operations.
190  .Case([&](EqLibOp op) {
191  convertCompareBinaryOp(op, ICmpPredicate::eq, wires, b);
192  })
193  .Case([&](NeqLibOp op) {
194  convertCompareBinaryOp(op, ICmpPredicate::ne, wires, b);
195  })
196  .Case([&](LtLibOp op) {
197  convertCompareBinaryOp(op, ICmpPredicate::ult, wires, b);
198  })
199  .Case([&](LeLibOp op) {
200  convertCompareBinaryOp(op, ICmpPredicate::ule, wires, b);
201  })
202  .Case([&](GtLibOp op) {
203  convertCompareBinaryOp(op, ICmpPredicate::ugt, wires, b);
204  })
205  .Case([&](GeLibOp op) {
206  convertCompareBinaryOp(op, ICmpPredicate::uge, wires, b);
207  })
208  .Case([&](SltLibOp op) {
209  convertCompareBinaryOp(op, ICmpPredicate::slt, wires, b);
210  })
211  .Case([&](SleLibOp op) {
212  convertCompareBinaryOp(op, ICmpPredicate::sle, wires, b);
213  })
214  .Case([&](SgtLibOp op) {
215  convertCompareBinaryOp(op, ICmpPredicate::sgt, wires, b);
216  })
217  .Case([&](SgeLibOp op) {
218  convertCompareBinaryOp(op, ICmpPredicate::sge, wires, b);
219  })
220  // Combinational arithmetic and logical operations.
221  .Case([&](AddLibOp op) {
222  convertArithBinaryOp<AddLibOp, AddOp>(op, wires, b);
223  })
224  .Case([&](SubLibOp op) {
225  convertArithBinaryOp<SubLibOp, SubOp>(op, wires, b);
226  })
227  .Case([&](RshLibOp op) {
228  convertArithBinaryOp<RshLibOp, ShrUOp>(op, wires, b);
229  })
230  .Case([&](SrshLibOp op) {
231  convertArithBinaryOp<SrshLibOp, ShrSOp>(op, wires, b);
232  })
233  .Case([&](LshLibOp op) {
234  convertArithBinaryOp<LshLibOp, ShlOp>(op, wires, b);
235  })
236  .Case([&](AndLibOp op) {
237  convertArithBinaryOp<AndLibOp, AndOp>(op, wires, b);
238  })
239  .Case([&](OrLibOp op) {
240  convertArithBinaryOp<OrLibOp, OrOp>(op, wires, b);
241  })
242  .Case([&](XorLibOp op) {
243  convertArithBinaryOp<XorLibOp, XorOp>(op, wires, b);
244  })
245  .Case([&](MuxLibOp op) {
246  auto sel = wireIn(op.getCond(), op.instanceName(),
247  op.portName(op.getCond()), b);
248  auto tru = wireIn(op.getTru(), op.instanceName(),
249  op.portName(op.getTru()), b);
250  auto fal = wireIn(op.getFal(), op.instanceName(),
251  op.portName(op.getFal()), b);
252 
253  auto mux = b.create<MuxOp>(sel, tru, fal);
254 
255  auto out =
256  wireOut(mux, op.instanceName(), op.portName(op.getOut()), b);
257  wires.append({sel.getInput(), tru.getInput(), fal.getInput(), out});
258  })
259  // Pipelined arithmetic operations.
260  .Case([&](MultPipeLibOp op) {
261  convertPipelineOp<MultPipeLibOp, comb::MulOp>(op, wires, b);
262  })
263  .Case([&](DivUPipeLibOp op) {
264  convertPipelineOp<DivUPipeLibOp, comb::DivUOp>(op, wires, b);
265  })
266  .Case([&](DivSPipeLibOp op) {
267  convertPipelineOp<DivSPipeLibOp, comb::DivSOp>(op, wires, b);
268  })
269  .Case([&](RemSPipeLibOp op) {
270  convertPipelineOp<RemSPipeLibOp, comb::ModSOp>(op, wires, b);
271  })
272  .Case([&](RemUPipeLibOp op) {
273  convertPipelineOp<RemUPipeLibOp, comb::ModUOp>(op, wires, b);
274  })
275  // Sequential operations.
276  .Case([&](RegisterOp op) {
277  auto in =
278  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
279  auto writeEn = wireIn(op.getWriteEn(), op.instanceName(),
280  op.portName(op.getWriteEn()), b);
281  auto clk = wireIn(op.getClk(), op.instanceName(),
282  op.portName(op.getClk()), b);
283  auto reset = wireIn(op.getReset(), op.instanceName(),
284  op.portName(op.getReset()), b);
285  auto seqClk = b.create<seq::ToClockOp>(clk);
286  auto doneReg =
287  reg(writeEn, seqClk, reset, op.instanceName() + "_done_reg", b);
288  auto done =
289  wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b);
290  auto clockEn = b.create<AndOp>(writeEn, createOrFoldNot(done, b));
291  auto outReg =
292  regCe(in, seqClk, clockEn, reset, op.instanceName() + "_reg", b);
293  auto out = wireOut(outReg, op.instanceName(), "", b);
294  wires.append({in.getInput(), writeEn.getInput(), clk.getInput(),
295  reset.getInput(), out, done});
296  })
297  // Unary operqations.
298  .Case([&](SliceLibOp op) {
299  auto in =
300  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
301  auto outWidth = op.getOut().getType().getIntOrFloatBitWidth();
302 
303  auto extract = b.create<ExtractOp>(in, 0, outWidth);
304 
305  auto out =
306  wireOut(extract, op.instanceName(), op.portName(op.getOut()), b);
307  wires.append({in.getInput(), out});
308  })
309  .Case([&](NotLibOp op) {
310  auto in =
311  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
312 
313  auto notOp = comb::createOrFoldNot(in, b);
314 
315  auto out =
316  wireOut(notOp, op.instanceName(), op.portName(op.getOut()), b);
317  wires.append({in.getInput(), out});
318  })
319  .Case([&](WireLibOp op) {
320  auto wire = wireIn(op.getIn(), op.instanceName(), "", b);
321  wires.append({wire.getInput(), wire});
322  })
323  .Case([&](UndefLibOp op) {
324  auto undef = b.create<sv::ConstantXOp>(op.getType());
325  wires.append({undef});
326  })
327  .Case([&](PadLibOp op) {
328  auto in =
329  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
330  auto srcWidth = in.getType().getIntOrFloatBitWidth();
331  auto destWidth = op.getOut().getType().getIntOrFloatBitWidth();
332  auto zero = b.create<hw::ConstantOp>(op.getLoc(),
333  APInt(destWidth - srcWidth, 0));
334  auto padded = wireOut(b.createOrFold<comb::ConcatOp>(zero, in),
335  op.instanceName(), op.portName(op.getOut()), b);
336  wires.append({in.getInput(), padded});
337  })
338  .Case([&](ExtSILibOp op) {
339  auto in =
340  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
341  auto extsi = wireOut(
342  createOrFoldSExt(op.getLoc(), in, op.getOut().getType(), b),
343  op.instanceName(), op.portName(op.getOut()), b);
344  wires.append({in.getInput(), extsi});
345  })
346  .Default([](Operation *) { return SmallVector<Value>(); });
347  }
348 
349  template <typename OpTy, typename ResultTy>
350  void convertArithBinaryOp(OpTy op, SmallVectorImpl<Value> &wires,
351  ImplicitLocOpBuilder &b) const {
352  auto left =
353  wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
354  auto right =
355  wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
356 
357  auto add = b.create<ResultTy>(left, right, false);
358 
359  auto out = wireOut(add, op.instanceName(), op.portName(op.getOut()), b);
360  wires.append({left.getInput(), right.getInput(), out});
361  }
362 
363  template <typename OpTy>
364  void convertCompareBinaryOp(OpTy op, ICmpPredicate pred,
365  SmallVectorImpl<Value> &wires,
366  ImplicitLocOpBuilder &b) const {
367  auto left =
368  wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
369  auto right =
370  wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
371 
372  auto add = b.create<ICmpOp>(pred, left, right, false);
373 
374  auto out = wireOut(add, op.instanceName(), op.portName(op.getOut()), b);
375  wires.append({left.getInput(), right.getInput(), out});
376  }
377 
378  template <typename SrcOpTy, typename TargetOpTy>
379  void convertPipelineOp(SrcOpTy op, SmallVectorImpl<Value> &wires,
380  ImplicitLocOpBuilder &b) const {
381  auto clk =
382  wireIn(op.getClk(), op.instanceName(), op.portName(op.getClk()), b);
383  auto reset =
384  wireIn(op.getReset(), op.instanceName(), op.portName(op.getReset()), b);
385  auto go = wireIn(op.getGo(), op.instanceName(), op.portName(op.getGo()), b);
386  auto left =
387  wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
388  auto right =
389  wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
390  wires.append({clk.getInput(), reset.getInput(), go.getInput(),
391  left.getInput(), right.getInput()});
392 
393  auto doneReg = reg(go, clk, reset,
394  op.instanceName() + "_" + op.portName(op.getDone()), b);
395  auto done =
396  wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b);
397 
398  auto targetOp = b.create<TargetOpTy>(left, right, false);
399  for (auto &&[targetRes, sourceRes] :
400  llvm::zip(targetOp->getResults(), op.getOutputPorts())) {
401  auto portName = op.portName(sourceRes);
402  auto clockEn = b.create<AndOp>(go, createOrFoldNot(done, b));
403  auto resReg = regCe(targetRes, clk, clockEn, reset,
404  createName(op.instanceName(), portName), b);
405  wires.push_back(wireOut(resReg, op.instanceName(), portName, b));
406  }
407 
408  wires.push_back(done);
409  }
410 
411  ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName,
412  ImplicitLocOpBuilder &b) const {
413  auto wire = b.create<sv::WireOp>(source.getType(),
414  createName(instanceName, portName));
415  return b.create<ReadInOutOp>(wire);
416  }
417 
418  ReadInOutOp wireOut(Value source, StringRef instanceName, StringRef portName,
419  ImplicitLocOpBuilder &b) const {
420  auto wire = b.create<sv::WireOp>(source.getType(),
421  createName(instanceName, portName));
422  b.create<sv::AssignOp>(wire, source);
423  return b.create<ReadInOutOp>(wire);
424  }
425 
426  CompRegOp reg(Value source, Value clock, Value reset, const Twine &name,
427  ImplicitLocOpBuilder &b) const {
428  auto resetValue = b.create<hw::ConstantOp>(source.getType(), 0);
429  return b.create<CompRegOp>(source, clock, reset, resetValue, name.str());
430  }
431 
432  CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset,
433  const Twine &name,
434  ImplicitLocOpBuilder &b) const {
435  auto resetValue = b.create<hw::ConstantOp>(source.getType(), 0);
436  return b.create<CompRegClockEnabledOp>(source, clock, ce, reset, resetValue,
437  name.str());
438  }
439 
440  std::string createName(StringRef instanceName, StringRef portName) const {
441  std::string name = instanceName.str();
442  if (!portName.empty())
443  name += ("_" + portName).str();
444  return name;
445  }
446 };
447 
448 /// Pass entrypoint.
449 
450 namespace {
451 class CalyxToHWPass : public circt::impl::CalyxToHWBase<CalyxToHWPass> {
452 public:
453  void runOnOperation() override;
454 
455 private:
456  LogicalResult runOnModule(ModuleOp module);
457 };
458 } // end anonymous namespace
459 
460 void CalyxToHWPass::runOnOperation() {
461  ModuleOp mod = getOperation();
462  if (failed(runOnModule(mod)))
463  return signalPassFailure();
464 }
465 
466 LogicalResult CalyxToHWPass::runOnModule(ModuleOp module) {
467  MLIRContext &context = getContext();
468 
469  ConversionTarget target(context);
470  target.addIllegalDialect<CalyxDialect>();
471  target.addLegalDialect<HWDialect>();
472  target.addLegalDialect<CombDialect>();
473  target.addLegalDialect<SeqDialect>();
474  target.addLegalDialect<SVDialect>();
475 
476  RewritePatternSet patterns(&context);
477  patterns.add<ConvertComponentOp>(&context);
478  patterns.add<ConvertWiresOp>(&context);
479  patterns.add<ConvertControlOp>(&context);
480  patterns.add<ConvertCellOp>(&context);
481  patterns.add<ConvertAssignOp>(&context);
482 
483  return applyPartialConversion(module, target, std::move(patterns));
484 }
485 
486 std::unique_ptr<mlir::Pass> circt::createCalyxToHWPass() {
487  return std::make_unique<CalyxToHWPass>();
488 }
assert(baseType &&"element must be base type")
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
def create(data_type, value)
Definition: hw.py:393
def create(dest, src)
Definition: sv.py:98
Definition: sv.py:35
def create(data_type, name=None, sym_name=None)
Definition: sv.py:61
Direction
The direction of a Component or Cell port.
Definition: CalyxOps.h:76
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition: CombOps.cpp:48
Value createOrFoldSExt(Location loc, Value value, Type destTy, OpBuilder &builder)
Create a sign extension operation from a value of integer type to an equal or larger integer type.
Definition: CombOps.cpp:25
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createCalyxToHWPass()
Definition: CalyxToHW.cpp:486
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:21
LogicalResult matchAndRewrite(calyx::AssignOp assign, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Definition: CalyxToHW.cpp:129
ReadInOutOp wireOut(Value source, StringRef instanceName, StringRef portName, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:418
LogicalResult matchAndRewrite(CellInterface cell, ArrayRef< Value > operands, ConversionPatternRewriter &rewriter) const override
Definition: CalyxToHW.cpp:166
std::string createName(StringRef instanceName, StringRef portName) const
Definition: CalyxToHW.cpp:440
void convertCompareBinaryOp(OpTy op, ICmpPredicate pred, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:364
void convertArithBinaryOp(OpTy op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:350
CompRegOp reg(Value source, Value clock, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:426
CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:432
void convertPrimitiveOp(Operation *op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:186
void convertPipelineOp(SrcOpTy op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:379
ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:411
ConversionPatterns.
Definition: CalyxToHW.cpp:43
LogicalResult matchAndRewrite(ComponentOp component, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Definition: CalyxToHW.cpp:47
hw::ModulePort::Direction hwDirection(calyx::Direction dir) const
Definition: CalyxToHW.cpp:85
LogicalResult matchAndRewrite(ControlOp control, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Definition: CalyxToHW.cpp:116
LogicalResult matchAndRewrite(WiresOp wires, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Definition: CalyxToHW.cpp:100
This holds a decoded list of input/inout and output ports for a module or instance.