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