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