CIRCT 20.0.0git
Loading...
Searching...
No Matches
DCToHW.cpp
Go to the documentation of this file.
1//===- DCToHW.cpp - Translate DC 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 DC to HW Conversion Pass Implementation.
10//
11//===----------------------------------------------------------------------===//
12
26#include "mlir/IR/ImplicitLocOpBuilder.h"
27#include "mlir/Pass/Pass.h"
28#include "mlir/Pass/PassManager.h"
29#include "mlir/Transforms/DialectConversion.h"
30#include "llvm/ADT/TypeSwitch.h"
31#include "llvm/Support/MathExtras.h"
32
33#include <optional>
34
35namespace circt {
36#define GEN_PASS_DEF_DCTOHW
37#include "circt/Conversion/Passes.h.inc"
38} // namespace circt
39
40using namespace mlir;
41using namespace circt;
42using namespace circt::dc;
43using namespace circt::hw;
44
45using NameUniquer = std::function<std::string(Operation *)>;
46
47// NOLINTNEXTLINE(misc-no-recursion)
48static Type tupleToStruct(TupleType tuple) {
49 auto *ctx = tuple.getContext();
50 mlir::SmallVector<hw::StructType::FieldInfo, 8> hwfields;
51 for (auto [i, innerType] : llvm::enumerate(tuple)) {
52 Type convertedInnerType = innerType;
53 if (auto tupleInnerType = dyn_cast<TupleType>(innerType))
54 convertedInnerType = tupleToStruct(tupleInnerType);
55 hwfields.push_back(
56 {StringAttr::get(ctx, "field" + Twine(i)), convertedInnerType});
57 }
58
59 return hw::StructType::get(ctx, hwfields);
60}
61
62/// Converts any type 't' into a `hw`-compatible type.
63/// tuple -> hw.struct
64/// none -> i0
65/// (tuple[...] | hw.struct)[...] -> (tuple | hw.struct)[toHwType(...)]
66// NOLINTNEXTLINE(misc-no-recursion)
67static Type toHWType(Type t) {
68 return TypeSwitch<Type, Type>(t)
69 .Case([](TupleType tt) { return toHWType(tupleToStruct(tt)); })
70 .Case([](hw::StructType st) {
71 llvm::SmallVector<hw::StructType::FieldInfo> structFields(
72 st.getElements());
73 for (auto &field : structFields)
74 field.type = toHWType(field.type);
75 return hw::StructType::get(st.getContext(), structFields);
76 })
77 .Case([](NoneType nt) { return IntegerType::get(nt.getContext(), 0); })
78 .Default([](Type t) { return t; });
79}
80
81static Type toESIHWType(Type t) {
82 Type outType =
83 llvm::TypeSwitch<Type, Type>(t)
84 .Case([](ValueType vt) {
85 return esi::ChannelType::get(vt.getContext(),
86 toHWType(vt.getInnerType()));
87 })
88 .Case([](TokenType tt) {
89 return esi::ChannelType::get(tt.getContext(),
90 IntegerType::get(tt.getContext(), 0));
91 })
92 .Default([](auto t) { return toHWType(t); });
93
94 return outType;
95}
96
97namespace {
98
99/// Shared state used by various functions; captured in a struct to reduce the
100/// number of arguments that we have to pass around.
101struct DCLoweringState {
102 ModuleOp parentModule;
103 NameUniquer nameUniquer;
104};
105
106/// A type converter is needed to perform the in-flight materialization of "raw"
107/// (non-ESI channel) types to their ESI channel correspondents. This comes into
108/// effect when backedges exist in the input IR.
109class ESITypeConverter : public TypeConverter {
110public:
111 ESITypeConverter() {
112 addConversion([](Type type) -> Type { return toESIHWType(type); });
113 addConversion([](esi::ChannelType t) -> Type { return t; });
114 addTargetMaterialization([](mlir::OpBuilder &builder, mlir::Type resultType,
115 mlir::ValueRange inputs,
116 mlir::Location loc) -> mlir::Value {
117 if (inputs.size() != 1)
118 return Value();
119
120 return builder
121 .create<UnrealizedConversionCastOp>(loc, resultType, inputs[0])
122 ->getResult(0);
123 });
124
125 addSourceMaterialization([](mlir::OpBuilder &builder, mlir::Type resultType,
126 mlir::ValueRange inputs,
127 mlir::Location loc) -> mlir::Value {
128 if (inputs.size() != 1)
129 return Value();
130
131 return builder
132 .create<UnrealizedConversionCastOp>(loc, resultType, inputs[0])
133 ->getResult(0);
134 });
135 }
136};
137
138} // namespace
139
140//===----------------------------------------------------------------------===//
141// HW Sub-module Related Functions
142//===----------------------------------------------------------------------===//
143
144namespace {
145
146/// Input handshakes contain a resolved valid and (optional )data signal, and
147/// a to-be-assigned ready signal.
148struct InputHandshake {
149 Value channel;
150 Value valid;
151 std::optional<Backedge> ready;
152 Value data;
153};
154
155/// Output handshakes contain a resolved ready, and to-be-assigned valid and
156/// (optional) data signals.
157struct OutputHandshake {
158 Value channel;
159 std::optional<Backedge> valid;
160 Value ready;
161 std::optional<Backedge> data;
162};
163
164/// Directly connect an input handshake to an output handshake
165static void connect(InputHandshake &input, OutputHandshake &output) {
166 output.valid->setValue(input.valid);
167 input.ready->setValue(output.ready);
168}
169
170template <typename T, typename TInner>
171llvm::SmallVector<T> extractValues(llvm::SmallVector<TInner> &container,
172 llvm::function_ref<T(TInner &)> extractor) {
173 llvm::SmallVector<T> result;
174 llvm::transform(container, std::back_inserter(result), extractor);
175 return result;
176}
177
178// Wraps a set of input and output handshakes with an API that provides
179// access to collections of the underlying values.
180struct UnwrappedIO {
181 llvm::SmallVector<InputHandshake> inputs;
182 llvm::SmallVector<OutputHandshake> outputs;
183
184 llvm::SmallVector<Value> getInputValids() {
185 return extractValues<Value, InputHandshake>(
186 inputs, [](auto &hs) { return hs.valid; });
187 }
188 llvm::SmallVector<std::optional<Backedge>> getInputReadys() {
189 return extractValues<std::optional<Backedge>, InputHandshake>(
190 inputs, [](auto &hs) { return hs.ready; });
191 }
192 llvm::SmallVector<std::optional<Backedge>> getOutputValids() {
193 return extractValues<std::optional<Backedge>, OutputHandshake>(
194 outputs, [](auto &hs) { return hs.valid; });
195 }
196 llvm::SmallVector<Value> getInputDatas() {
197 return extractValues<Value, InputHandshake>(
198 inputs, [](auto &hs) { return hs.data; });
199 }
200 llvm::SmallVector<Value> getOutputReadys() {
201 return extractValues<Value, OutputHandshake>(
202 outputs, [](auto &hs) { return hs.ready; });
203 }
204
205 llvm::SmallVector<Value> getOutputChannels() {
206 return extractValues<Value, OutputHandshake>(
207 outputs, [](auto &hs) { return hs.channel; });
208 }
209 llvm::SmallVector<std::optional<Backedge>> getOutputDatas() {
210 return extractValues<std::optional<Backedge>, OutputHandshake>(
211 outputs, [](auto &hs) { return hs.data; });
212 }
213};
214
215/// A class containing a bunch of syntactic sugar to reduce builder function
216/// verbosity.
217/// @todo: should be moved to support.
218struct RTLBuilder {
219 RTLBuilder(Location loc, OpBuilder &builder, Value clk = Value(),
220 Value rst = Value())
221 : b(builder), loc(loc), clk(clk), rst(rst) {}
222
223 Value constant(const APInt &apv, StringRef name = {}) {
224 // Cannot use zero-width APInt's in DenseMap's, see
225 // https://github.com/llvm/llvm-project/issues/58013
226 bool isZeroWidth = apv.getBitWidth() == 0;
227 if (!isZeroWidth) {
228 auto it = constants.find(apv);
229 if (it != constants.end())
230 return it->second;
231 }
232
233 auto cval = b.create<hw::ConstantOp>(loc, apv);
234 if (!isZeroWidth)
235 constants[apv] = cval;
236 return cval;
237 }
238
239 Value constant(unsigned width, int64_t value, StringRef name = {}) {
240 return constant(
241 APInt(width, value, /*isSigned=*/false, /*implicitTrunc=*/true));
242 }
243 std::pair<Value, Value> wrap(Value data, Value valid, StringRef name = {}) {
244 auto wrapOp = b.create<esi::WrapValidReadyOp>(loc, data, valid);
245 return {wrapOp.getResult(0), wrapOp.getResult(1)};
246 }
247 std::pair<Value, Value> unwrap(Value channel, Value ready,
248 StringRef name = {}) {
249 auto unwrapOp = b.create<esi::UnwrapValidReadyOp>(loc, channel, ready);
250 return {unwrapOp.getResult(0), unwrapOp.getResult(1)};
251 }
252
253 /// Various syntactic sugar functions.
254 Value reg(StringRef name, Value in, Value rstValue, Value clk = Value(),
255 Value rst = Value()) {
256 Value resolvedClk = clk ? clk : this->clk;
257 Value resolvedRst = rst ? rst : this->rst;
258 assert(resolvedClk &&
259 "No global clock provided to this RTLBuilder - a clock "
260 "signal must be provided to the reg(...) function.");
261 assert(resolvedRst &&
262 "No global reset provided to this RTLBuilder - a reset "
263 "signal must be provided to the reg(...) function.");
264
265 return b.create<seq::CompRegOp>(loc, in, resolvedClk, resolvedRst, rstValue,
266 name);
267 }
268
269 Value cmp(Value lhs, Value rhs, comb::ICmpPredicate predicate,
270 StringRef name = {}) {
271 return b.create<comb::ICmpOp>(loc, predicate, lhs, rhs);
272 }
273
274 Value buildNamedOp(llvm::function_ref<Value()> f, StringRef name) {
275 Value v = f();
276 StringAttr nameAttr;
277 Operation *op = v.getDefiningOp();
278 if (!name.empty()) {
279 op->setAttr("sv.namehint", b.getStringAttr(name));
280 nameAttr = b.getStringAttr(name);
281 }
282 return v;
283 }
284
285 /// Bitwise 'and'.
286 Value bitAnd(ValueRange values, StringRef name = {}) {
287 return buildNamedOp(
288 [&]() { return b.create<comb::AndOp>(loc, values, false); }, name);
289 }
290
291 // Bitwise 'or'.
292 Value bitOr(ValueRange values, StringRef name = {}) {
293 return buildNamedOp(
294 [&]() { return b.create<comb::OrOp>(loc, values, false); }, name);
295 }
296
297 /// Bitwise 'not'.
298 Value bitNot(Value value, StringRef name = {}) {
299 auto allOnes = constant(value.getType().getIntOrFloatBitWidth(), -1);
300 std::string inferedName;
301 if (!name.empty()) {
302 // Try to create a name from the input value.
303 if (auto valueName =
304 value.getDefiningOp()->getAttrOfType<StringAttr>("sv.namehint")) {
305 inferedName = ("not_" + valueName.getValue()).str();
306 name = inferedName;
307 }
308 }
309
310 return buildNamedOp(
311 [&]() { return b.create<comb::XorOp>(loc, value, allOnes); }, name);
312 }
313
314 Value shl(Value value, Value shift, StringRef name = {}) {
315 return buildNamedOp(
316 [&]() { return b.create<comb::ShlOp>(loc, value, shift); }, name);
317 }
318
319 Value concat(ValueRange values, StringRef name = {}) {
320 return buildNamedOp([&]() { return b.create<comb::ConcatOp>(loc, values); },
321 name);
322 }
323
324 llvm::SmallVector<Value> extractBits(Value v, StringRef name = {}) {
325 llvm::SmallVector<Value> bits;
326 for (unsigned i = 0, e = v.getType().getIntOrFloatBitWidth(); i != e; ++i)
327 bits.push_back(b.create<comb::ExtractOp>(loc, v, i, /*bitWidth=*/1));
328 return bits;
329 }
330
331 /// OR-reduction of the bits in 'v'.
332 Value reduceOr(Value v, StringRef name = {}) {
333 return buildNamedOp([&]() { return bitOr(extractBits(v)); }, name);
334 }
335
336 /// Extract bits v[hi:lo] (inclusive).
337 Value extract(Value v, unsigned lo, unsigned hi, StringRef name = {}) {
338 unsigned width = hi - lo + 1;
339 return buildNamedOp(
340 [&]() { return b.create<comb::ExtractOp>(loc, v, lo, width); }, name);
341 }
342
343 /// Truncates 'value' to its lower 'width' bits.
344 Value truncate(Value value, unsigned width, StringRef name = {}) {
345 return extract(value, 0, width - 1, name);
346 }
347
348 Value zext(Value value, unsigned outWidth, StringRef name = {}) {
349 unsigned inWidth = value.getType().getIntOrFloatBitWidth();
350 assert(inWidth <= outWidth && "zext: input width must be <= output width.");
351 if (inWidth == outWidth)
352 return value;
353 auto c0 = constant(outWidth - inWidth, 0);
354 return concat({c0, value}, name);
355 }
356
357 Value sext(Value value, unsigned outWidth, StringRef name = {}) {
358 return comb::createOrFoldSExt(loc, value, b.getIntegerType(outWidth), b);
359 }
360
361 /// Extracts a single bit v[bit].
362 Value bit(Value v, unsigned index, StringRef name = {}) {
363 return extract(v, index, index, name);
364 }
365
366 /// Creates a hw.array of the given values.
367 Value arrayCreate(ValueRange values, StringRef name = {}) {
368 return buildNamedOp(
369 [&]() { return b.create<hw::ArrayCreateOp>(loc, values); }, name);
370 }
371
372 /// Extract the 'index'th value from the input array.
373 Value arrayGet(Value array, Value index, StringRef name = {}) {
374 return buildNamedOp(
375 [&]() { return b.create<hw::ArrayGetOp>(loc, array, index); }, name);
376 }
377
378 /// Muxes a range of values.
379 /// The select signal is expected to be a decimal value which selects
380 /// starting from the lowest index of value.
381 Value mux(Value index, ValueRange values, StringRef name = {}) {
382 if (values.size() == 2) {
383 return buildNamedOp(
384 [&]() {
385 return b.create<comb::MuxOp>(loc, index, values[1], values[0]);
386 },
387 name);
388 }
389 return arrayGet(arrayCreate(values), index, name);
390 }
391
392 /// Muxes a range of values. The select signal is expected to be a 1-hot
393 /// encoded value.
394 Value oneHotMux(Value index, ValueRange inputs) {
395 // Confirm the select input can be a one-hot encoding for the inputs.
396 unsigned numInputs = inputs.size();
397 assert(numInputs == index.getType().getIntOrFloatBitWidth() &&
398 "mismatch between width of one-hot select input and the number of "
399 "inputs to be selected");
400
401 // Start the mux tree with zero value.
402 auto dataType = inputs[0].getType();
403 unsigned width =
404 isa<NoneType>(dataType) ? 0 : dataType.getIntOrFloatBitWidth();
405 Value muxValue = constant(width, 0);
406
407 // Iteratively chain together muxes from the high bit to the low bit.
408 for (size_t i = numInputs - 1; i != 0; --i) {
409 Value input = inputs[i];
410 Value selectBit = bit(index, i);
411 muxValue = mux(selectBit, {muxValue, input});
412 }
413
414 return muxValue;
415 }
416
417 OpBuilder &b;
418 Location loc;
419 Value clk, rst;
420 DenseMap<APInt, Value> constants;
421};
422
423static bool isZeroWidthType(Type type) {
424 if (auto intType = dyn_cast<IntegerType>(type))
425 return intType.getWidth() == 0;
426 return isa<NoneType>(type);
427}
428
429static UnwrappedIO unwrapIO(Location loc, ValueRange operands,
430 TypeRange results,
431 ConversionPatternRewriter &rewriter,
432 BackedgeBuilder &bb) {
433 RTLBuilder rtlb(loc, rewriter);
434 UnwrappedIO unwrapped;
435 for (auto in : operands) {
436 assert(isa<esi::ChannelType>(in.getType()));
437 auto ready = bb.get(rtlb.b.getI1Type());
438 auto [data, valid] = rtlb.unwrap(in, ready);
439 unwrapped.inputs.push_back(InputHandshake{in, valid, ready, data});
440 }
441 for (auto outputType : results) {
442 outputType = toESIHWType(outputType);
443 esi::ChannelType channelType = cast<esi::ChannelType>(outputType);
444 OutputHandshake hs;
445 Type innerType = channelType.getInner();
446 Value data;
447 if (isZeroWidthType(innerType)) {
448 // Feed the ESI wrap with an i0 constant.
449 data =
450 rewriter.create<hw::ConstantOp>(loc, rewriter.getIntegerType(0), 0);
451 } else {
452 // Create a backedge for the unresolved data.
453 auto dataBackedge = bb.get(innerType);
454 hs.data = dataBackedge;
455 data = dataBackedge;
456 }
457 auto valid = bb.get(rewriter.getI1Type());
458 auto [dataCh, ready] = rtlb.wrap(data, valid);
459 hs.valid = valid;
460 hs.ready = ready;
461 hs.channel = dataCh;
462 unwrapped.outputs.push_back(hs);
463 }
464 return unwrapped;
465}
466
467static UnwrappedIO unwrapIO(Operation *op, ValueRange operands,
468 ConversionPatternRewriter &rewriter,
469 BackedgeBuilder &bb) {
470 return unwrapIO(op->getLoc(), operands, op->getResultTypes(), rewriter, bb);
471}
472
473/// Locate the clock and reset values from the parent operation based on
474/// attributes assigned to the arguments.
475static FailureOr<std::pair<Value, Value>> getClockAndReset(Operation *op) {
476 auto *parent = op->getParentOp();
477 auto parentFuncOp = dyn_cast<HWModuleLike>(parent);
478 if (!parentFuncOp)
479 return parent->emitOpError("parent op does not implement HWModuleLike");
480
481 auto argAttrs = parentFuncOp.getAllInputAttrs();
482
483 std::optional<size_t> clockIdx, resetIdx;
484
485 for (auto [idx, battrs] : llvm::enumerate(argAttrs)) {
486 auto attrs = cast<DictionaryAttr>(battrs);
487 if (attrs.get("dc.clock")) {
488 if (clockIdx)
489 return parent->emitOpError(
490 "multiple arguments contains a 'dc.clock' attribute");
491 clockIdx = idx;
492 }
493
494 if (attrs.get("dc.reset")) {
495 if (resetIdx)
496 return parent->emitOpError(
497 "multiple arguments contains a 'dc.reset' attribute");
498 resetIdx = idx;
499 }
500 }
501
502 if (!clockIdx)
503 return parent->emitOpError("no argument contains a 'dc.clock' attribute");
504
505 if (!resetIdx)
506 return parent->emitOpError("no argument contains a 'dc.reset' attribute");
507
508 return {std::make_pair(parentFuncOp.getArgumentForInput(*clockIdx),
509 parentFuncOp.getArgumentForInput(*resetIdx))};
510}
511
512class ForkConversionPattern : public OpConversionPattern<ForkOp> {
513public:
515 LogicalResult
516 matchAndRewrite(ForkOp op, OpAdaptor operands,
517 ConversionPatternRewriter &rewriter) const override {
518 auto bb = BackedgeBuilder(rewriter, op.getLoc());
519 auto crRes = getClockAndReset(op);
520 if (failed(crRes))
521 return failure();
522 auto [clock, reset] = *crRes;
523 RTLBuilder rtlb(op.getLoc(), rewriter, clock, reset);
524 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
525
526 auto &input = io.inputs[0];
527
528 Value c0I1 = rtlb.constant(1, 0);
529 llvm::SmallVector<Value> doneWires;
530 for (auto [i, output] : llvm::enumerate(io.outputs)) {
531 Backedge doneBE = bb.get(rtlb.b.getI1Type());
532 Value emitted = rtlb.bitAnd({doneBE, rtlb.bitNot(*input.ready)});
533 Value emittedReg =
534 rtlb.reg("emitted_" + std::to_string(i), emitted, c0I1);
535 Value outValid = rtlb.bitAnd({rtlb.bitNot(emittedReg), input.valid});
536 output.valid->setValue(outValid);
537 Value validReady = rtlb.bitAnd({output.ready, outValid});
538 Value done =
539 rtlb.bitOr({validReady, emittedReg}, "done" + std::to_string(i));
540 doneBE.setValue(done);
541 doneWires.push_back(done);
542 }
543 input.ready->setValue(rtlb.bitAnd(doneWires, "allDone"));
544
545 rewriter.replaceOp(op, io.getOutputChannels());
546 return success();
547 }
548};
549
550class JoinConversionPattern : public OpConversionPattern<JoinOp> {
551public:
553
554 LogicalResult
555 matchAndRewrite(JoinOp op, OpAdaptor operands,
556 ConversionPatternRewriter &rewriter) const override {
557 auto bb = BackedgeBuilder(rewriter, op.getLoc());
558 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
559 RTLBuilder rtlb(op.getLoc(), rewriter);
560 auto &output = io.outputs[0];
561
562 Value allValid = rtlb.bitAnd(io.getInputValids());
563 output.valid->setValue(allValid);
564
565 auto validAndReady = rtlb.bitAnd({output.ready, allValid});
566 for (auto &input : io.inputs)
567 input.ready->setValue(validAndReady);
568
569 rewriter.replaceOp(op, io.outputs[0].channel);
570 return success();
571 }
572};
573
574class SelectConversionPattern : public OpConversionPattern<SelectOp> {
575public:
577
578 LogicalResult
579 matchAndRewrite(SelectOp op, OpAdaptor operands,
580 ConversionPatternRewriter &rewriter) const override {
581 auto bb = BackedgeBuilder(rewriter, op.getLoc());
582 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
583 RTLBuilder rtlb(op.getLoc(), rewriter);
584
585 // Extract select signal from the unwrapped IO.
586 auto select = io.inputs[0];
587 io.inputs.erase(io.inputs.begin());
588 buildMuxLogic(rtlb, io, select);
589
590 rewriter.replaceOp(op, io.outputs[0].channel);
591 return success();
592 }
593
594 // Builds mux logic for the given inputs and outputs.
595 // Note: it is assumed that the caller has removed the 'select' signal from
596 // the 'unwrapped' inputs and provide it as a separate argument.
597 void buildMuxLogic(RTLBuilder &rtlb, UnwrappedIO &unwrapped,
598 InputHandshake &select) const {
599
600 // ============================= Control logic =============================
601 size_t numInputs = unwrapped.inputs.size();
602 size_t selectWidth = llvm::Log2_64_Ceil(numInputs);
603 Value truncatedSelect =
604 select.data.getType().getIntOrFloatBitWidth() > selectWidth
605 ? rtlb.truncate(select.data, selectWidth)
606 : select.data;
607
608 // Decimal-to-1-hot decoder. 'shl' operands must be identical in size.
609 auto selectZext = rtlb.zext(truncatedSelect, numInputs);
610 auto select1h = rtlb.shl(rtlb.constant(numInputs, 1), selectZext);
611 auto &res = unwrapped.outputs[0];
612
613 // Mux input valid signals.
614 auto selectedInputValid =
615 rtlb.mux(truncatedSelect, unwrapped.getInputValids());
616 // Result is valid when the selected input and the select input is valid.
617 auto selAndInputValid = rtlb.bitAnd({selectedInputValid, select.valid});
618 res.valid->setValue(selAndInputValid);
619 auto resValidAndReady = rtlb.bitAnd({selAndInputValid, res.ready});
620
621 // Select is ready when result is valid and ready (result transacting).
622 select.ready->setValue(resValidAndReady);
623
624 // Assign each input ready signal if it is currently selected.
625 for (auto [inIdx, in] : llvm::enumerate(unwrapped.inputs)) {
626 // Extract the selection bit for this input.
627 auto isSelected = rtlb.bit(select1h, inIdx);
628
629 // '&' that with the result valid and ready, and assign to the input
630 // ready signal.
631 auto activeAndResultValidAndReady =
632 rtlb.bitAnd({isSelected, resValidAndReady});
633 in.ready->setValue(activeAndResultValidAndReady);
634 }
635 }
636};
637
638class BranchConversionPattern : public OpConversionPattern<BranchOp> {
639public:
640 using OpConversionPattern::OpConversionPattern;
641 LogicalResult
642 matchAndRewrite(BranchOp op, OpAdaptor operands,
643 ConversionPatternRewriter &rewriter) const override {
644 auto bb = BackedgeBuilder(rewriter, op.getLoc());
645 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
646 RTLBuilder rtlb(op.getLoc(), rewriter);
647 auto cond = io.inputs[0];
648 auto trueRes = io.outputs[0];
649 auto falseRes = io.outputs[1];
650
651 // Connect valid signal of both results.
652 trueRes.valid->setValue(rtlb.bitAnd({cond.data, cond.valid}));
653 falseRes.valid->setValue(rtlb.bitAnd({rtlb.bitNot(cond.data), cond.valid}));
654
655 // Connect ready signal of condition.
656 Value selectedResultReady =
657 rtlb.mux(cond.data, {falseRes.ready, trueRes.ready});
658 Value condReady = rtlb.bitAnd({selectedResultReady, cond.valid});
659 cond.ready->setValue(condReady);
660
661 rewriter.replaceOp(op,
662 SmallVector<Value>{trueRes.channel, falseRes.channel});
663 return success();
664 }
665};
666
667class MergeConversionPattern : public OpConversionPattern<MergeOp> {
668public:
669 using OpConversionPattern::OpConversionPattern;
670 LogicalResult
671 matchAndRewrite(MergeOp op, OpAdaptor operands,
672 ConversionPatternRewriter &rewriter) const override {
673 BackedgeBuilder bb(rewriter, op.getLoc());
674 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
675 auto output = io.outputs[0];
676 RTLBuilder rtlb(op.getLoc(), rewriter);
677
678 // A winner is found if any of the two input valids are high.
679 Value hasWin = rtlb.bitOr(io.getInputValids());
680
681 // The winning index is either 0b0 (first) or 0b1 (second), hence we can
682 // just use either of the input valids as win index signal. The op is
683 // defined to select inputs with priority first >> second, so use the first
684 // input.
685 Value winWasFirst = io.inputs[0].valid;
686 Value winWasSecond = rtlb.bitNot(winWasFirst);
687 Value winIndex = winWasSecond;
688
689 output.valid->setValue(hasWin);
690 output.data->setValue(winIndex);
691
692 // Create the logic to set the done wires for the result. The done wire is
693 // asserted when the output is valid and ready.
694 Value outValidAndReady = rtlb.bitAnd({hasWin, output.ready});
695
696 // Create the logic to assign the arg ready outputs. An argument is ready
697 // when the output is valid and ready, and the given input is selected.
698 io.inputs[0].ready->setValue(rtlb.bitAnd({outValidAndReady, winWasFirst}));
699 io.inputs[1].ready->setValue(rtlb.bitAnd({outValidAndReady, winWasSecond}));
700
701 rewriter.replaceOp(op, output.channel);
702
703 return success();
704 }
705};
706
707class ToESIConversionPattern : public OpConversionPattern<ToESIOp> {
708 // Essentially a no-op, seeing as the type converter does the heavy
709 // lifting here.
710public:
711 using OpConversionPattern::OpConversionPattern;
712
713 LogicalResult
714 matchAndRewrite(ToESIOp op, OpAdaptor operands,
715 ConversionPatternRewriter &rewriter) const override {
716 rewriter.replaceOp(op, operands.getOperands());
717 return success();
718 }
719};
720
721class FromESIConversionPattern : public OpConversionPattern<FromESIOp> {
722 // Essentially a no-op, seeing as the type converter does the heavy
723 // lifting here.
724public:
725 using OpConversionPattern::OpConversionPattern;
726
727 LogicalResult
728 matchAndRewrite(FromESIOp op, OpAdaptor operands,
729 ConversionPatternRewriter &rewriter) const override {
730 rewriter.replaceOp(op, operands.getOperands());
731 return success();
732 }
733};
734
735class SinkConversionPattern : public OpConversionPattern<SinkOp> {
736public:
738
739 LogicalResult
740 matchAndRewrite(SinkOp op, OpAdaptor operands,
741 ConversionPatternRewriter &rewriter) const override {
742 auto bb = BackedgeBuilder(rewriter, op.getLoc());
743 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
744 io.inputs[0].ready->setValue(
745 RTLBuilder(op.getLoc(), rewriter).constant(1, 1));
746 rewriter.eraseOp(op);
747 return success();
748 }
749};
750
751class SourceConversionPattern : public OpConversionPattern<SourceOp> {
752public:
754 LogicalResult
755 matchAndRewrite(SourceOp op, OpAdaptor operands,
756 ConversionPatternRewriter &rewriter) const override {
757 auto bb = BackedgeBuilder(rewriter, op.getLoc());
758 UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb);
759 RTLBuilder rtlb(op.getLoc(), rewriter);
760 io.outputs[0].valid->setValue(rtlb.constant(1, 1));
761 rewriter.replaceOp(op, io.outputs[0].channel);
762 return success();
763 }
764};
765
766class PackConversionPattern : public OpConversionPattern<PackOp> {
767public:
769 LogicalResult
770 matchAndRewrite(PackOp op, OpAdaptor operands,
771 ConversionPatternRewriter &rewriter) const override {
772 auto bb = BackedgeBuilder(rewriter, op.getLoc());
773 UnwrappedIO io = unwrapIO(op, llvm::SmallVector<Value>{operands.getToken()},
774 rewriter, bb);
775 RTLBuilder rtlb(op.getLoc(), rewriter);
776 auto &input = io.inputs[0];
777 auto &output = io.outputs[0];
778 output.data->setValue(operands.getInput());
779 connect(input, output);
780 rewriter.replaceOp(op, output.channel);
781 return success();
782 }
783};
784
785class UnpackConversionPattern : public OpConversionPattern<UnpackOp> {
786public:
788 LogicalResult
789 matchAndRewrite(UnpackOp op, OpAdaptor operands,
790 ConversionPatternRewriter &rewriter) const override {
791 auto bb = BackedgeBuilder(rewriter, op.getLoc());
792 UnwrappedIO io = unwrapIO(
793 op.getLoc(), llvm::SmallVector<Value>{operands.getInput()},
794 // Only generate an output channel for the token typed output.
795 llvm::SmallVector<Type>{op.getToken().getType()}, rewriter, bb);
796 RTLBuilder rtlb(op.getLoc(), rewriter);
797 auto &input = io.inputs[0];
798 auto &output = io.outputs[0];
799
800 llvm::SmallVector<Value> unpackedValues;
801 unpackedValues.push_back(input.data);
802
803 connect(input, output);
804 llvm::SmallVector<Value> outputs;
805 outputs.push_back(output.channel);
806 outputs.append(unpackedValues.begin(), unpackedValues.end());
807 rewriter.replaceOp(op, outputs);
808 return success();
809 }
810};
811
812class BufferConversionPattern : public OpConversionPattern<BufferOp> {
813public:
815
816 LogicalResult
817 matchAndRewrite(BufferOp op, OpAdaptor operands,
818 ConversionPatternRewriter &rewriter) const override {
819 if (op.getInitValuesAttr())
820 return rewriter.notifyMatchFailure(
821 op, "BufferOp with initial values not supported");
822
823 auto crRes = getClockAndReset(op);
824 if (failed(crRes))
825 return failure();
826 auto [clock, reset] = *crRes;
827
828 // ... esi.buffer should in theory provide a correct (latency-insensitive)
829 // implementation...
830 Type channelType = operands.getInput().getType();
831 rewriter.replaceOpWithNewOp<esi::ChannelBufferOp>(
832 op, channelType, clock, reset, operands.getInput(), op.getSizeAttr(),
833 nullptr);
834 return success();
835 };
836};
837
838} // namespace
839
840static bool isDCType(Type type) { return isa<TokenType, ValueType>(type); }
841
842/// Returns true if the given `op` is considered as legal - i.e. it does not
843/// contain any dc-typed values.
844static bool isLegalOp(Operation *op) {
845 if (auto funcOp = dyn_cast<HWModuleLike>(op))
846 return llvm::none_of(funcOp.getPortTypes(), isDCType);
847
848 bool operandsOK = llvm::none_of(op->getOperandTypes(), isDCType);
849 bool resultsOK = llvm::none_of(op->getResultTypes(), isDCType);
850 return operandsOK && resultsOK;
851}
852
853//===----------------------------------------------------------------------===//
854// HW Top-module Related Functions
855//===----------------------------------------------------------------------===//
856
857namespace {
858class DCToHWPass : public circt::impl::DCToHWBase<DCToHWPass> {
859public:
860 void runOnOperation() override {
861 Operation *parent = getOperation();
862
863 // Lowering to HW requires that every DC-typed value is used exactly once.
864 // Check whether this precondition is met, and if not, exit.
865 auto walkRes = parent->walk([&](Operation *op) {
866 for (auto res : op->getResults()) {
867 if (isa<dc::TokenType, dc::ValueType>(res.getType())) {
868 if (res.use_empty()) {
869 op->emitOpError() << "DCToHW: value " << res << " is unused.";
870 return WalkResult::interrupt();
871 }
872 if (!res.hasOneUse()) {
873 op->emitOpError()
874 << "DCToHW: value " << res << " has multiple uses.";
875 return WalkResult::interrupt();
876 }
877 }
878 }
879 return WalkResult::advance();
880 });
881
882 if (walkRes.wasInterrupted()) {
883 parent->emitOpError()
884 << "DCToHW: failed to verify that all values "
885 "are used exactly once. Remember to run the "
886 "fork/sink materialization pass before HW lowering.";
887 signalPassFailure();
888 return;
889 }
890
891 ESITypeConverter typeConverter;
892 ConversionTarget target(getContext());
893 target.markUnknownOpDynamicallyLegal(isLegalOp);
894
895 // All top-level logic of a handshake module will be the interconnectivity
896 // between instantiated modules.
897 target.addIllegalDialect<dc::DCDialect>();
898
899 RewritePatternSet patterns(parent->getContext());
900
901 patterns.insert<
902 ForkConversionPattern, JoinConversionPattern, SelectConversionPattern,
903 BranchConversionPattern, PackConversionPattern, UnpackConversionPattern,
904 BufferConversionPattern, SourceConversionPattern, SinkConversionPattern,
905 MergeConversionPattern, TypeConversionPattern, ToESIConversionPattern,
906 FromESIConversionPattern>(typeConverter, parent->getContext());
907
908 if (failed(applyPartialConversion(parent, target, std::move(patterns))))
909 signalPassFailure();
910 }
911};
912} // namespace
913
914std::unique_ptr<mlir::Pass> circt::createDCToHWPass() {
915 return std::make_unique<DCToHWPass>();
916}
assert(baseType &&"element must be base type")
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
static std::string valueName(Operation *scopeOp, Value v)
Convenience function for getting the SSA name of v under the scope of operation scopeOp.
Definition CalyxOps.cpp:121
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition CalyxOps.cpp:540
static SmallVector< Value > extractBits(OpBuilder &builder, Value val)
Definition CombToAIG.cpp:33
static Type toHWType(Type t)
Converts any type 't' into a hw-compatible type.
Definition DCToHW.cpp:67
static bool isDCType(Type type)
Definition DCToHW.cpp:840
static Type tupleToStruct(TupleType tuple)
Definition DCToHW.cpp:48
static bool isLegalOp(Operation *op)
Returns true if the given op is considered as legal - i.e.
Definition DCToHW.cpp:844
std::function< std::string(Operation *)> NameUniquer
Definition DCToHW.cpp:45
static Type toESIHWType(Type t)
Definition DCToHW.cpp:81
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:113
Instantiate one of these and use it to build typed backedges.
Backedge get(mlir::Type resultType, mlir::LocationAttr optionalLoc={})
Create a typed backedge.
Backedge is a wrapper class around a Value.
void setValue(mlir::Value)
Channels are the basic communication primitives.
Definition Types.h:63
const Type * getInner() const
Definition Types.h:66
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
connect(destination, source)
Definition support.py:39
mlir::Type innerType(mlir::Type type)
Definition ESITypes.cpp:227
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createDCToHWPass()
Definition DCToHW.cpp:914
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
Generic pattern which replaces an operation by one of the same operation name, but with converted att...