CIRCT 23.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(), type, name);
67 auto wireRead =
68 sv::ReadInOutOp::create(b, 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 hw::ConstantOp::create(rewriter, assign.getLoc(), src.getType(), 0);
135 src = MuxOp::create(rewriter, 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 = MuxOp::create(rewriter, 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 = MuxOp::create(b, 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 = seq::ToClockOp::create(b, 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 = AndOp::create(b, writeEn, createOrFoldNot(b, done));
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 = ExtractOp::create(b, 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(b, in);
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 = sv::ConstantXOp::create(b, 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 = hw::ConstantOp::create(b, 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(createOrFoldSExt(b, in, op.getOut().getType()),
342 op.instanceName(), op.portName(op.getOut()), b);
343 wires.append({in.getInput(), extsi});
344 })
345 .Default([](Operation *) { return SmallVector<Value>(); });
346 }
347
348 template <typename OpTy, typename ResultTy>
349 void convertArithBinaryOp(OpTy op, SmallVectorImpl<Value> &wires,
350 ImplicitLocOpBuilder &b) const {
351 auto left =
352 wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
353 auto right =
354 wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
355
356 auto add = ResultTy::create(b, left, right, false);
357
358 auto out = wireOut(add, op.instanceName(), op.portName(op.getOut()), b);
359 wires.append({left.getInput(), right.getInput(), out});
360 }
361
362 template <typename OpTy>
363 void convertCompareBinaryOp(OpTy op, ICmpPredicate pred,
364 SmallVectorImpl<Value> &wires,
365 ImplicitLocOpBuilder &b) const {
366 auto left =
367 wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
368 auto right =
369 wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
370
371 auto add = ICmpOp::create(b, pred, left, right, false);
372
373 auto out = wireOut(add, op.instanceName(), op.portName(op.getOut()), b);
374 wires.append({left.getInput(), right.getInput(), out});
375 }
376
377 template <typename SrcOpTy, typename TargetOpTy>
378 void convertPipelineOp(SrcOpTy op, SmallVectorImpl<Value> &wires,
379 ImplicitLocOpBuilder &b) const {
380 auto clk =
381 wireIn(op.getClk(), op.instanceName(), op.portName(op.getClk()), b);
382 auto reset =
383 wireIn(op.getReset(), op.instanceName(), op.portName(op.getReset()), b);
384 auto go = wireIn(op.getGo(), op.instanceName(), op.portName(op.getGo()), b);
385 auto left =
386 wireIn(op.getLeft(), op.instanceName(), op.portName(op.getLeft()), b);
387 auto right =
388 wireIn(op.getRight(), op.instanceName(), op.portName(op.getRight()), b);
389 wires.append({clk.getInput(), reset.getInput(), go.getInput(),
390 left.getInput(), right.getInput()});
391
392 // Convert i1 clock to seq.clock for register creation.
393 auto seqClk = seq::ToClockOp::create(b, clk);
394 auto doneReg = reg(go, seqClk, reset,
395 op.instanceName() + "_" + op.portName(op.getDone()), b);
396 auto done =
397 wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b);
398
399 auto targetOp = TargetOpTy::create(b, left, right, false);
400 for (auto &&[targetRes, sourceRes] :
401 llvm::zip(targetOp->getResults(), op.getOutputPorts())) {
402 auto portName = op.portName(sourceRes);
403 auto clockEn = AndOp::create(b, go, createOrFoldNot(b, done));
404 auto resReg = regCe(targetRes, seqClk, clockEn, reset,
405 createName(op.instanceName(), portName), b);
406 wires.push_back(wireOut(resReg, op.instanceName(), portName, b));
407 }
408
409 wires.push_back(done);
410 }
411
412 ReadInOutOp wireIn(Value source, StringRef instanceName, StringRef portName,
413 ImplicitLocOpBuilder &b) const {
414 auto wire = sv::WireOp::create(b, source.getType(),
415 createName(instanceName, portName));
416 return ReadInOutOp::create(b, wire);
417 }
418
419 ReadInOutOp wireOut(Value source, StringRef instanceName, StringRef portName,
420 ImplicitLocOpBuilder &b) const {
421 auto wire = sv::WireOp::create(b, source.getType(),
422 createName(instanceName, portName));
423 sv::AssignOp::create(b, wire, source);
424 return ReadInOutOp::create(b, wire);
425 }
426
427 CompRegOp reg(Value source, Value clock, Value reset, const Twine &name,
428 ImplicitLocOpBuilder &b) const {
429 auto resetValue = hw::ConstantOp::create(b, source.getType(), 0);
430 return CompRegOp::create(b, source, clock, reset, resetValue, name.str());
431 }
432
433 CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset,
434 const Twine &name,
435 ImplicitLocOpBuilder &b) const {
436 auto resetValue = hw::ConstantOp::create(b, source.getType(), 0);
437 return CompRegClockEnabledOp::create(b, source, clock, ce, reset,
438 resetValue, name.str());
439 }
440
441 std::string createName(StringRef instanceName, StringRef portName) const {
442 std::string name = instanceName.str();
443 if (!portName.empty())
444 name += ("_" + portName).str();
445 return name;
446 }
447};
448
449/// Pass entrypoint.
450
451namespace {
452class CalyxToHWPass : public circt::impl::CalyxToHWBase<CalyxToHWPass> {
453public:
454 void runOnOperation() override;
455
456private:
457 LogicalResult runOnModule(ModuleOp module);
458};
459} // end anonymous namespace
460
461void CalyxToHWPass::runOnOperation() {
462 ModuleOp mod = getOperation();
463 if (failed(runOnModule(mod)))
464 return signalPassFailure();
465}
466
467LogicalResult CalyxToHWPass::runOnModule(ModuleOp module) {
468 MLIRContext &context = getContext();
469
470 ConversionTarget target(context);
471 target.addIllegalDialect<CalyxDialect>();
472 target.addLegalDialect<HWDialect>();
473 target.addLegalDialect<CombDialect>();
474 target.addLegalDialect<SeqDialect>();
475 target.addLegalDialect<SVDialect>();
476
477 RewritePatternSet patterns(&context);
483
484 return applyPartialConversion(module, target, std::move(patterns));
485}
486
487std::unique_ptr<mlir::Pass> circt::createCalyxToHWPass() {
488 return std::make_unique<CalyxToHWPass>();
489}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:100
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:67
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.
Definition CombOps.cpp:44
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.