CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
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
28namespace circt {
29#define GEN_PASS_DEF_CALYXTOHW
30#include "circt/Conversion/Passes.h.inc"
31} // namespace circt
32
33using namespace mlir;
34using namespace circt;
35using namespace circt::calyx;
36using namespace circt::comb;
37using namespace circt::hw;
38using namespace circt::seq;
39using namespace circt::sv;
40
41/// ConversionPatterns.
42
43struct 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) {
61 case calyx::Direction::Input:
62 assert(ports.getInput(name).getType() == type);
63 argValues.push_back(ports.getInput(name));
64 break;
65 case calyx::Direction::Output:
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
84private:
86 switch (dir) {
87 case calyx::Direction::Input:
88 return hw::ModulePort::Direction::Input;
89 case calyx::Direction::Output:
90 return hw::ModulePort::Direction::Output;
91 }
92 llvm_unreachable("unknown direction");
93 }
94};
95
96struct 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
112struct 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
125struct 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
162struct 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
185private:
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
450namespace {
451class CalyxToHWPass : public circt::impl::CalyxToHWBase<CalyxToHWPass> {
452public:
453 void runOnOperation() override;
454
455private:
456 LogicalResult runOnModule(ModuleOp module);
457};
458} // end anonymous namespace
459
460void CalyxToHWPass::runOnOperation() {
461 ModuleOp mod = getOperation();
462 if (failed(runOnModule(mod)))
463 return signalPassFailure();
464}
465
466LogicalResult 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
486std::unique_ptr<mlir::Pass> circt::createCalyxToHWPass() {
487 return std::make_unique<CalyxToHWPass>();
488}
assert(baseType &&"element must be base type")
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:98
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.
std::unique_ptr< mlir::Pass > createCalyxToHWPass()
LogicalResult matchAndRewrite(calyx::AssignOp assign, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ReadInOutOp wireOut(Value source, StringRef instanceName, StringRef portName, ImplicitLocOpBuilder &b) const
LogicalResult matchAndRewrite(CellInterface cell, ArrayRef< Value > operands, ConversionPatternRewriter &rewriter) const override
std::string createName(StringRef instanceName, StringRef portName) const
void convertCompareBinaryOp(OpTy op, ICmpPredicate pred, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
void convertArithBinaryOp(OpTy op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
CompRegOp reg(Value source, Value clock, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const
CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const
void convertPrimitiveOp(Operation *op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
void convertPipelineOp(SrcOpTy op, SmallVectorImpl< Value > &wires, ImplicitLocOpBuilder &b) const
ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName, ImplicitLocOpBuilder &b) const
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
LogicalResult matchAndRewrite(WiresOp wires, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
This holds a decoded list of input/inout and output ports for a module or instance.