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