CIRCT  20.0.0git
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 
16 #include "circt/Dialect/DC/DCOps.h"
21 #include "circt/Dialect/HW/HWOps.h"
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 
35 namespace circt {
36 #define GEN_PASS_DEF_DCTOHW
37 #include "circt/Conversion/Passes.h.inc"
38 } // namespace circt
39 
40 using namespace mlir;
41 using namespace circt;
42 using namespace circt::dc;
43 using namespace circt::hw;
44 
45 using NameUniquer = std::function<std::string(Operation *)>;
46 
47 // NOLINTNEXTLINE(misc-no-recursion)
48 static 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)
67 static 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 
81 static 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 
97 namespace {
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.
101 struct 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.
109 class ESITypeConverter : public TypeConverter {
110 public:
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 
144 namespace {
145 
146 /// Input handshakes contain a resolved valid and (optional )data signal, and
147 /// a to-be-assigned ready signal.
148 struct 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.
157 struct 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
165 static void connect(InputHandshake &input, OutputHandshake &output) {
166  output.valid->setValue(input.valid);
167  input.ready->setValue(output.ready);
168 }
169 
170 template <typename T, typename TInner>
171 llvm::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.
180 struct 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.
218 struct 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 
423 static bool isZeroWidthType(Type type) {
424  if (auto intType = dyn_cast<IntegerType>(type))
425  return intType.getWidth() == 0;
426  return isa<NoneType>(type);
427 }
428 
429 static 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 
467 static 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.
475 static 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 
512 class ForkConversionPattern : public OpConversionPattern<ForkOp> {
513 public:
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 
550 class JoinConversionPattern : public OpConversionPattern<JoinOp> {
551 public:
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 
574 class SelectConversionPattern : public OpConversionPattern<SelectOp> {
575 public:
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 
638 class BranchConversionPattern : public OpConversionPattern<BranchOp> {
639 public:
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 
667 class MergeConversionPattern : public OpConversionPattern<MergeOp> {
668 public:
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 
707 class ToESIConversionPattern : public OpConversionPattern<ToESIOp> {
708  // Essentially a no-op, seeing as the type converter does the heavy
709  // lifting here.
710 public:
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 
721 class FromESIConversionPattern : public OpConversionPattern<FromESIOp> {
722  // Essentially a no-op, seeing as the type converter does the heavy
723  // lifting here.
724 public:
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 
735 class SinkConversionPattern : public OpConversionPattern<SinkOp> {
736 public:
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 
751 class SourceConversionPattern : public OpConversionPattern<SourceOp> {
752 public:
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 
766 class PackConversionPattern : public OpConversionPattern<PackOp> {
767 public:
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 
785 class UnpackConversionPattern : public OpConversionPattern<UnpackOp> {
786 public:
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 
812 class BufferConversionPattern : public OpConversionPattern<BufferOp> {
813 public:
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 
840 static 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.
844 static 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 
857 namespace {
858 class DCToHWPass : public circt::impl::DCToHWBase<DCToHWPass> {
859 public:
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 
914 std::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(ConversionPatternRewriter &rewriter, 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
def create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition: seq.py:157
def connect(destination, source)
Definition: support.py:39
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
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
mlir::Type innerType(mlir::Type type)
Definition: ESITypes.cpp:184
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createDCToHWPass()
Definition: DCToHW.cpp:914
def 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...