CIRCT 24.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 = HWModuleOp::create(
57 rewriter, 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 = sv::WireOp::create(b, component.getLoc(),
67 sv::NetType::get(type), name);
68 auto wireRead =
69 sv::ReadInOutOp::create(b, component.getLoc(), wire);
70 argValues.push_back(wireRead);
71 ports.setOutput(name, wireRead);
72 break;
73 }
74 }
75 });
76
77 auto *outputOp = hwMod.getBodyBlock()->getTerminator();
78 rewriter.mergeBlocks(component.getBodyBlock(), hwMod.getBodyBlock(),
79 argValues);
80 outputOp->moveAfter(&hwMod.getBodyBlock()->back());
81 rewriter.eraseOp(component);
82 return success();
83 }
84
85private:
87 switch (dir) {
92 }
93 llvm_unreachable("unknown direction");
94 }
95};
96
97struct ConvertWiresOp : public OpConversionPattern<WiresOp> {
98 using OpConversionPattern::OpConversionPattern;
99
100 LogicalResult
101 matchAndRewrite(WiresOp wires, OpAdaptor adaptor,
102 ConversionPatternRewriter &rewriter) const override {
103 HWModuleOp hwMod = wires->getParentOfType<HWModuleOp>();
104 rewriter.inlineRegionBefore(wires.getBody(), hwMod.getBodyRegion(),
105 hwMod.getBodyRegion().end());
106 rewriter.eraseOp(wires);
107 rewriter.inlineBlockBefore(&hwMod.getBodyRegion().getBlocks().back(),
108 &hwMod.getBodyBlock()->back());
109 return success();
110 }
111};
112
113struct ConvertControlOp : public OpConversionPattern<ControlOp> {
114 using OpConversionPattern::OpConversionPattern;
115
116 LogicalResult
117 matchAndRewrite(ControlOp control, OpAdaptor adaptor,
118 ConversionPatternRewriter &rewriter) const override {
119 if (!control.getBodyBlock()->empty())
120 return control.emitOpError("calyx control must be structural");
121 rewriter.eraseOp(control);
122 return success();
123 }
124};
125
126struct ConvertAssignOp : public OpConversionPattern<calyx::AssignOp> {
127 using OpConversionPattern::OpConversionPattern;
128
129 LogicalResult
130 matchAndRewrite(calyx::AssignOp assign, OpAdaptor adaptor,
131 ConversionPatternRewriter &rewriter) const override {
132 Value src = adaptor.getSrc();
133 if (auto guard = adaptor.getGuard()) {
134 auto zero =
135 hw::ConstantOp::create(rewriter, assign.getLoc(), src.getType(), 0);
136 src = MuxOp::create(rewriter, assign.getLoc(), guard, src, zero);
137 for (Operation *destUser :
138 llvm::make_early_inc_range(assign.getDest().getUsers())) {
139 if (destUser == assign)
140 continue;
141 if (auto otherAssign = dyn_cast<calyx::AssignOp>(destUser)) {
142 src = MuxOp::create(rewriter, assign.getLoc(), otherAssign.getGuard(),
143 otherAssign.getSrc(), src);
144 rewriter.eraseOp(destUser);
145 }
146 }
147 }
148
149 // To make life easy in ConvertComponentOp, we read from the output wires so
150 // the dialect conversion block argument mapping would work without a type
151 // converter. This means assigns to ComponentOp outputs will try to assign
152 // to a read from a wire, so we need to map to the wire.
153 Value dest = adaptor.getDest();
154 if (auto readInOut = dyn_cast<ReadInOutOp>(dest.getDefiningOp()))
155 dest = readInOut.getInput();
156
157 rewriter.replaceOpWithNewOp<sv::AssignOp>(assign, dest, src);
158
159 return success();
160 }
161};
162
163struct ConvertCellOp : public OpInterfaceConversionPattern<CellInterface> {
164 using OpInterfaceConversionPattern::OpInterfaceConversionPattern;
165
166 LogicalResult
167 matchAndRewrite(CellInterface cell, ArrayRef<Value> operands,
168 ConversionPatternRewriter &rewriter) const override {
169 assert(operands.empty() && "calyx cells do not have operands");
170
171 SmallVector<Value> wires;
172 ImplicitLocOpBuilder builder(cell.getLoc(), rewriter);
173 convertPrimitiveOp(cell, wires, builder);
174 if (wires.size() != cell.getPortInfo().size()) {
175 auto diag = cell.emitOpError("couldn't convert to core primitive");
176 for (Value wire : wires)
177 diag.attachNote() << "with wire: " << wire;
178 return diag;
179 }
180
181 rewriter.replaceOp(cell, wires);
182
183 return success();
184 }
185
186private:
187 void convertPrimitiveOp(Operation *op, SmallVectorImpl<Value> &wires,
188 ImplicitLocOpBuilder &b) const {
189 TypeSwitch<Operation *>(op)
190 // Comparison operations.
191 .Case([&](EqLibOp op) {
192 convertCompareBinaryOp(op, ICmpPredicate::eq, wires, b);
193 })
194 .Case([&](NeqLibOp op) {
195 convertCompareBinaryOp(op, ICmpPredicate::ne, wires, b);
196 })
197 .Case([&](LtLibOp op) {
198 convertCompareBinaryOp(op, ICmpPredicate::ult, wires, b);
199 })
200 .Case([&](LeLibOp op) {
201 convertCompareBinaryOp(op, ICmpPredicate::ule, wires, b);
202 })
203 .Case([&](GtLibOp op) {
204 convertCompareBinaryOp(op, ICmpPredicate::ugt, wires, b);
205 })
206 .Case([&](GeLibOp op) {
207 convertCompareBinaryOp(op, ICmpPredicate::uge, wires, b);
208 })
209 .Case([&](SltLibOp op) {
210 convertCompareBinaryOp(op, ICmpPredicate::slt, wires, b);
211 })
212 .Case([&](SleLibOp op) {
213 convertCompareBinaryOp(op, ICmpPredicate::sle, wires, b);
214 })
215 .Case([&](SgtLibOp op) {
216 convertCompareBinaryOp(op, ICmpPredicate::sgt, wires, b);
217 })
218 .Case([&](SgeLibOp op) {
219 convertCompareBinaryOp(op, ICmpPredicate::sge, wires, b);
220 })
221 // Combinational arithmetic and logical operations.
222 .Case([&](AddLibOp op) {
224 })
225 .Case([&](SubLibOp op) {
227 })
228 .Case([&](RshLibOp op) {
230 })
231 .Case([&](SrshLibOp op) {
233 })
234 .Case([&](LshLibOp op) {
236 })
237 .Case([&](AndLibOp op) {
239 })
240 .Case([&](OrLibOp op) {
242 })
243 .Case([&](XorLibOp op) {
245 })
246 .Case([&](MuxLibOp op) {
247 auto sel = wireIn(op.getCond(), op.instanceName(),
248 op.portName(op.getCond()), b);
249 auto tru = wireIn(op.getTru(), op.instanceName(),
250 op.portName(op.getTru()), b);
251 auto fal = wireIn(op.getFal(), op.instanceName(),
252 op.portName(op.getFal()), b);
253
254 auto mux = MuxOp::create(b, sel, tru, fal);
255
256 auto out =
257 wireOut(mux, op.instanceName(), op.portName(op.getOut()), b);
258 wires.append({sel.getInput(), tru.getInput(), fal.getInput(), out});
259 })
260 // Pipelined arithmetic operations.
261 .Case([&](MultPipeLibOp op) {
263 })
264 .Case([&](DivUPipeLibOp op) {
266 })
267 .Case([&](DivSPipeLibOp op) {
269 })
270 .Case([&](RemSPipeLibOp op) {
272 })
273 .Case([&](RemUPipeLibOp op) {
275 })
276 // Sequential operations.
277 .Case([&](RegisterOp op) {
278 auto in =
279 wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
280 auto writeEn = wireIn(op.getWriteEn(), op.instanceName(),
281 op.portName(op.getWriteEn()), b);
282 auto clk = wireIn(op.getClk(), op.instanceName(),
283 op.portName(op.getClk()), b);
284 auto reset = wireIn(op.getReset(), op.instanceName(),
285 op.portName(op.getReset()), b);
286 auto seqClk = seq::ToClockOp::create(b, clk);
287 auto doneReg =
288 reg(writeEn, seqClk, reset, op.instanceName() + "_done_reg", b);
289 auto done =
290 wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b);
291 auto clockEn = AndOp::create(b, writeEn, createOrFoldNot(b, done));
292 auto outReg =
293 regCe(in, seqClk, clockEn, reset, op.instanceName() + "_reg", b);
294 auto out = wireOut(outReg, op.instanceName(), "", b);
295 wires.append({in.getInput(), writeEn.getInput(), clk.getInput(),
296 reset.getInput(), out, done});
297 })
298 // Unary operqations.
299 .Case([&](SliceLibOp op) {
300 auto in =
301 wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
302 auto outWidth = op.getOut().getType().getIntOrFloatBitWidth();
303
304 auto extract = ExtractOp::create(b, in, 0, outWidth);
305
306 auto out =
307 wireOut(extract, op.instanceName(), op.portName(op.getOut()), b);
308 wires.append({in.getInput(), out});
309 })
310 .Case([&](NotLibOp op) {
311 auto in =
312 wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
313
314 auto notOp = comb::createOrFoldNot(b, in);
315
316 auto out =
317 wireOut(notOp, op.instanceName(), op.portName(op.getOut()), b);
318 wires.append({in.getInput(), out});
319 })
320 .Case([&](WireLibOp op) {
321 auto wire = wireIn(op.getIn(), op.instanceName(), "", b);
322 wires.append({wire.getInput(), wire});
323 })
324 .Case([&](UndefLibOp op) {
325 auto undef = sv::ConstantXOp::create(b, op.getType());
326 wires.append({undef});
327 })
328 .Case([&](PadLibOp op) {
329 auto in =
330 wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
331 auto srcWidth = in.getType().getIntOrFloatBitWidth();
332 auto destWidth = op.getOut().getType().getIntOrFloatBitWidth();
333 auto zero = hw::ConstantOp::create(b, op.getLoc(),
334 APInt(destWidth - srcWidth, 0));
335 auto padded = wireOut(b.createOrFold<comb::ConcatOp>(zero, in),
336 op.instanceName(), op.portName(op.getOut()), b);
337 wires.append({in.getInput(), padded});
338 })
339 .Case([&](ExtSILibOp op) {
340 auto in =
341 wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b);
342 auto extsi = wireOut(createOrFoldSExt(b, in, op.getOut().getType()),
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 = ResultTy::create(b, 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 = ICmpOp::create(b, 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 // Convert i1 clock to seq.clock for register creation.
394 auto seqClk = seq::ToClockOp::create(b, clk);
395 auto doneReg = reg(go, seqClk, reset,
396 op.instanceName() + "_" + op.portName(op.getDone()), b);
397 auto done =
398 wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b);
399
400 auto targetOp = TargetOpTy::create(b, left, right, false);
401 for (auto &&[targetRes, sourceRes] :
402 llvm::zip(targetOp->getResults(), op.getOutputPorts())) {
403 auto portName = op.portName(sourceRes);
404 auto clockEn = AndOp::create(b, go, createOrFoldNot(b, done));
405 auto resReg = regCe(targetRes, seqClk, clockEn, reset,
406 createName(op.instanceName(), portName), b);
407 wires.push_back(wireOut(resReg, op.instanceName(), portName, b));
408 }
409
410 wires.push_back(done);
411 }
412
413 ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName,
414 ImplicitLocOpBuilder &b) const {
415 auto wire = sv::WireOp::create(b, sv::NetType::get(source.getType()),
416 createName(instanceName, portName));
417 return ReadInOutOp::create(b, wire);
418 }
419
420 ReadInOutOp wireOut(Value source, StringRef instanceName, StringRef portName,
421 ImplicitLocOpBuilder &b) const {
422 auto wire = sv::WireOp::create(b, sv::NetType::get(source.getType()),
423 createName(instanceName, portName));
424 sv::AssignOp::create(b, wire, source);
425 return ReadInOutOp::create(b, wire);
426 }
427
428 CompRegOp reg(Value source, Value clock, Value reset, const Twine &name,
429 ImplicitLocOpBuilder &b) const {
430 auto resetValue = hw::ConstantOp::create(b, source.getType(), 0);
431 return CompRegOp::create(b, source, clock, reset, resetValue, name.str());
432 }
433
434 CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset,
435 const Twine &name,
436 ImplicitLocOpBuilder &b) const {
437 auto resetValue = hw::ConstantOp::create(b, source.getType(), 0);
438 return CompRegClockEnabledOp::create(b, source, clock, ce, reset,
439 resetValue, name.str());
440 }
441
442 std::string createName(StringRef instanceName, StringRef portName) const {
443 std::string name = instanceName.str();
444 if (!portName.empty())
445 name += ("_" + portName).str();
446 return name;
447 }
448};
449
450/// Pass entrypoint.
451
452namespace {
453class CalyxToHWPass : public circt::impl::CalyxToHWBase<CalyxToHWPass> {
454public:
455 void runOnOperation() override;
456
457private:
458 LogicalResult runOnModule(ModuleOp module);
459};
460} // end anonymous namespace
461
462void CalyxToHWPass::runOnOperation() {
463 ModuleOp mod = getOperation();
464 if (failed(runOnModule(mod)))
465 return signalPassFailure();
466}
467
468LogicalResult CalyxToHWPass::runOnModule(ModuleOp module) {
469 MLIRContext &context = getContext();
470
471 ConversionTarget target(context);
472 target.addIllegalDialect<CalyxDialect>();
473 target.addLegalDialect<HWDialect>();
474 target.addLegalDialect<CombDialect>();
475 target.addLegalDialect<SeqDialect>();
476 target.addLegalDialect<SVDialect>();
477
478 RewritePatternSet patterns(&context);
479 patterns.add<ConvertComponentOp>(&context);
480 patterns.add<ConvertWiresOp>(&context);
481 patterns.add<ConvertControlOp>(&context);
482 patterns.add<ConvertCellOp>(&context);
483 patterns.add<ConvertAssignOp>(&context);
484
485 return applyPartialConversion(module, target, std::move(patterns));
486}
487
488std::unique_ptr<mlir::Pass> circt::createCalyxToHWPass() {
489 return std::make_unique<CalyxToHWPass>();
490}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
return success()
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:135
create(data_type, name=None, sym_name=None)
Definition sv.py:63
Direction
The direction of a Component or Cell port.
Definition CalyxOps.h:76
Value createOrFoldNot(OpBuilder &builder, Location loc, Value value, bool twoState=false)
Create a ``Not'' gate on a value.
Definition CombOps.cpp:112
Value createOrFoldSExt(OpBuilder &builder, Location loc, Value value, Type destTy)
Create a sign extension operation from a value of integer type to an equal or larger integer type.
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:86
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.