CIRCT  19.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.getCond(), op.instanceName(),
242  op.portName(op.getCond()), 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([&](UndefLibOp op) {
319  auto undef = b.create<sv::ConstantXOp>(op.getType());
320  wires.append({undef});
321  })
322  .Case([&](PadLibOp op) {
323  auto in =
324  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
325  auto srcWidth = in.getType().getIntOrFloatBitWidth();
326  auto destWidth = op.getOut().getType().getIntOrFloatBitWidth();
327  auto zero = b.create<hw::ConstantOp>(op.getLoc(),
328  APInt(destWidth - srcWidth, 0));
329  auto padded = wireOut(b.createOrFold<comb::ConcatOp>(zero, in),
330  op.instanceName(), op.portName(op.getOut()), b);
331  wires.append({in.getInput(), padded});
332  })
333  .Case([&](ExtSILibOp op) {
334  auto in =
335  wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
336  auto extsi = wireOut(
337  createOrFoldSExt(op.getLoc(), in, op.getOut().getType(), b),
338  op.instanceName(), op.portName(op.getOut()), b);
339  wires.append({in.getInput(), extsi});
340  })
341  .Default([](Operation *) { return SmallVector<Value>(); });
342  }
343 
344  template <typename OpTy, typename ResultTy>
345  void convertArithBinaryOp(OpTy op, SmallVectorImpl<Value> &wires,
346  ImplicitLocOpBuilder &b) const {
347  auto left =
348  wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
349  auto right =
350  wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
351 
352  auto add = b.create<ResultTy>(left, right, false);
353 
354  auto out = wireOut(add, op.instanceName(), op.portName(op.getOut()), b);
355  wires.append({left.getInput(), right.getInput(), out});
356  }
357 
358  template <typename OpTy>
359  void convertCompareBinaryOp(OpTy op, ICmpPredicate pred,
360  SmallVectorImpl<Value> &wires,
361  ImplicitLocOpBuilder &b) const {
362  auto left =
363  wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
364  auto right =
365  wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
366 
367  auto add = b.create<ICmpOp>(pred, left, right, false);
368 
369  auto out = wireOut(add, op.instanceName(), op.portName(op.getOut()), b);
370  wires.append({left.getInput(), right.getInput(), out});
371  }
372 
373  template <typename SrcOpTy, typename TargetOpTy>
374  void convertPipelineOp(SrcOpTy op, SmallVectorImpl<Value> &wires,
375  ImplicitLocOpBuilder &b) const {
376  auto clk =
377  wireIn(op.getClk(), op.instanceName(), op.portName(op.getClk()), b);
378  auto reset =
379  wireIn(op.getReset(), op.instanceName(), op.portName(op.getReset()), b);
380  auto go = wireIn(op.getGo(), op.instanceName(), op.portName(op.getGo()), b);
381  auto left =
382  wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
383  auto right =
384  wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
385  wires.append({clk.getInput(), reset.getInput(), go.getInput(),
386  left.getInput(), right.getInput()});
387 
388  auto doneReg = reg(go, clk, reset,
389  op.instanceName() + "_" + op.portName(op.getDone()), b);
390  auto done =
391  wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b);
392 
393  auto targetOp = b.create<TargetOpTy>(left, right, false);
394  for (auto &&[targetRes, sourceRes] :
395  llvm::zip(targetOp->getResults(), op.getOutputPorts())) {
396  auto portName = op.portName(sourceRes);
397  auto clockEn = b.create<AndOp>(go, createOrFoldNot(done, b));
398  auto resReg = regCe(targetRes, clk, clockEn, reset,
399  createName(op.instanceName(), portName), b);
400  wires.push_back(wireOut(resReg, op.instanceName(), portName, b));
401  }
402 
403  wires.push_back(done);
404  }
405 
406  ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName,
407  ImplicitLocOpBuilder &b) const {
408  auto wire = b.create<sv::WireOp>(source.getType(),
409  createName(instanceName, portName));
410  return b.create<ReadInOutOp>(wire);
411  }
412 
413  ReadInOutOp wireOut(Value source, StringRef instanceName, StringRef portName,
414  ImplicitLocOpBuilder &b) const {
415  auto wire = b.create<sv::WireOp>(source.getType(),
416  createName(instanceName, portName));
417  b.create<sv::AssignOp>(wire, source);
418  return b.create<ReadInOutOp>(wire);
419  }
420 
421  CompRegOp reg(Value source, Value clock, Value reset, const Twine &name,
422  ImplicitLocOpBuilder &b) const {
423  auto resetValue = b.create<hw::ConstantOp>(source.getType(), 0);
424  return b.create<CompRegOp>(source, clock, reset, resetValue, name.str());
425  }
426 
427  CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset,
428  const Twine &name,
429  ImplicitLocOpBuilder &b) const {
430  auto resetValue = b.create<hw::ConstantOp>(source.getType(), 0);
431  return b.create<CompRegClockEnabledOp>(source, clock, ce, reset, resetValue,
432  name.str());
433  }
434 
435  std::string createName(StringRef instanceName, StringRef portName) const {
436  std::string name = instanceName.str();
437  if (!portName.empty())
438  name += ("_" + portName).str();
439  return name;
440  }
441 };
442 
443 /// Pass entrypoint.
444 
445 namespace {
446 class CalyxToHWPass : public CalyxToHWBase<CalyxToHWPass> {
447 public:
448  void runOnOperation() override;
449 
450 private:
451  LogicalResult runOnModule(ModuleOp module);
452 };
453 } // end anonymous namespace
454 
455 void CalyxToHWPass::runOnOperation() {
456  ModuleOp mod = getOperation();
457  if (failed(runOnModule(mod)))
458  return signalPassFailure();
459 }
460 
461 LogicalResult CalyxToHWPass::runOnModule(ModuleOp module) {
462  MLIRContext &context = getContext();
463 
464  ConversionTarget target(context);
465  target.addIllegalDialect<CalyxDialect>();
466  target.addLegalDialect<HWDialect>();
467  target.addLegalDialect<CombDialect>();
468  target.addLegalDialect<SeqDialect>();
469  target.addLegalDialect<SVDialect>();
470 
471  RewritePatternSet patterns(&context);
472  patterns.add<ConvertComponentOp>(&context);
473  patterns.add<ConvertWiresOp>(&context);
474  patterns.add<ConvertControlOp>(&context);
475  patterns.add<ConvertCellOp>(&context);
476  patterns.add<ConvertAssignOp>(&context);
477 
478  return applyPartialConversion(module, target, std::move(patterns));
479 }
480 
481 std::unique_ptr<mlir::Pass> circt::createCalyxToHWPass() {
482  return std::make_unique<CalyxToHWPass>();
483 }
assert(baseType &&"element must be base type")
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
Builder builder
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:72
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:481
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:20
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:413
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:435
void convertCompareBinaryOp(OpTy op, ICmpPredicate pred, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:359
void convertArithBinaryOp(OpTy op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:345
CompRegOp reg(Value source, Value clock, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:421
CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:427
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:374
ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName, ImplicitLocOpBuilder &b) const
Definition: CalyxToHW.cpp:406
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.