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