CIRCT  20.0.0git
FirRegLowering.cpp
Go to the documentation of this file.
1 //===- FirRegLowering.cpp - FirReg lowering utilities ---------------------===//
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 #include "FirRegLowering.h"
11 #include "mlir/IR/Threading.h"
12 #include "mlir/Transforms/DialectConversion.h"
13 #include "llvm/ADT/DenseSet.h"
14 #include "llvm/Support/Debug.h"
15 
16 using namespace circt;
17 using namespace hw;
18 using namespace seq;
19 using llvm::MapVector;
20 
21 #define DEBUG_TYPE "lower-seq-firreg"
22 
23 std::function<bool(const Operation *op)> OpUserInfo::opAllowsReachability =
24  [](const Operation *op) -> bool {
25  return (isa<comb::MuxOp, ArrayGetOp, ArrayCreateOp>(op));
26 };
27 
28 bool ReachableMuxes::isMuxReachableFrom(seq::FirRegOp regOp,
29  comb::MuxOp muxOp) {
30  return llvm::any_of(regOp.getResult().getUsers(), [&](Operation *user) {
31  if (!OpUserInfo::opAllowsReachability(user))
32  return false;
33  buildReachabilityFrom(user);
34  return reachableMuxes[user].contains(muxOp);
35  });
36 }
37 
38 void ReachableMuxes::buildReachabilityFrom(Operation *startNode) {
39  // This is a backward dataflow analysis.
40  // First build a graph rooted at the `startNode`. Every user of an operation
41  // that does not block the reachability is a child node. Then, the ops that
42  // are reachable from a node is computed as the union of the Reachability of
43  // all its child nodes.
44  // The dataflow can be expressed as, for all child in the Children(node)
45  // Reachability(node) = node + Union{Reachability(child)}
46  if (visited.contains(startNode))
47  return;
48 
49  // The stack to record enough information for an iterative post-order
50  // traversal.
51  llvm::SmallVector<OpUserInfo, 16> stk;
52 
53  stk.emplace_back(startNode);
54 
55  while (!stk.empty()) {
56  auto &info = stk.back();
57  Operation *currentNode = info.op;
58 
59  // Node is being visited for the first time.
60  if (info.getAndSetUnvisited())
61  visited.insert(currentNode);
62 
63  if (info.userIter != info.userEnd) {
64  Operation *child = *info.userIter;
65  ++info.userIter;
66  if (!visited.contains(child))
67  stk.emplace_back(child);
68 
69  } else { // All children of the node have been visited
70  // Any op is reachable from itself.
71  reachableMuxes[currentNode].insert(currentNode);
72 
73  for (auto *childOp : llvm::make_filter_range(
74  info.op->getUsers(), OpUserInfo::opAllowsReachability)) {
75  reachableMuxes[currentNode].insert(childOp);
76  // Propagate the reachability backwards from m to currentNode.
77  auto iter = reachableMuxes.find(childOp);
78  assert(iter != reachableMuxes.end());
79 
80  // Add all the mux that was reachable from childOp, to currentNode.
81  reachableMuxes[currentNode].insert(iter->getSecond().begin(),
82  iter->getSecond().end());
83  }
84  stk.pop_back();
85  }
86  }
87 }
88 
89 void FirRegLowering::addToIfBlock(OpBuilder &builder, Value cond,
90  const std::function<void()> &trueSide,
91  const std::function<void()> &falseSide) {
92  auto op = ifCache.lookup({builder.getBlock(), cond});
93  // Always build both sides of the if, in case we want to use an empty else
94  // later. This way we don't have to build a new if and replace it.
95  if (!op) {
96  auto newIfOp =
97  builder.create<sv::IfOp>(cond.getLoc(), cond, trueSide, falseSide);
98  ifCache.insert({{builder.getBlock(), cond}, newIfOp});
99  } else {
100  OpBuilder::InsertionGuard guard(builder);
101  builder.setInsertionPointToEnd(op.getThenBlock());
102  trueSide();
103  builder.setInsertionPointToEnd(op.getElseBlock());
104  falseSide();
105  }
106 }
107 
108 FirRegLowering::FirRegLowering(TypeConverter &typeConverter,
109  hw::HWModuleOp module,
110  bool disableRegRandomization,
111  bool emitSeparateAlwaysBlocks)
112  : typeConverter(typeConverter), module(module),
113  disableRegRandomization(disableRegRandomization),
114  emitSeparateAlwaysBlocks(emitSeparateAlwaysBlocks) {
115 
116  reachableMuxes = std::make_unique<ReachableMuxes>(module);
117 }
118 
120  // Find all registers to lower in the module.
121  auto regs = module.getOps<seq::FirRegOp>();
122  if (regs.empty())
123  return;
124 
125  // Lower the regs to SV regs. Group them by initializer and reset kind.
126  SmallVector<RegLowerInfo> randomInit, presetInit;
127  llvm::MapVector<Value, SmallVector<RegLowerInfo>> asyncResets;
128  for (auto reg : llvm::make_early_inc_range(regs)) {
129  auto svReg = lower(reg);
130  if (svReg.preset)
131  presetInit.push_back(svReg);
132  else if (!disableRegRandomization)
133  randomInit.push_back(svReg);
134 
135  if (svReg.asyncResetSignal)
136  asyncResets[svReg.asyncResetSignal].emplace_back(svReg);
137  }
138 
139  // Compute total width of random space. Place non-chisel registers at the end
140  // of the space. The Random space is unique to the initial block, due to
141  // verilog thread rules, so we can drop trailing random calls if they are
142  // unused.
143  uint64_t maxBit = 0;
144  for (auto reg : randomInit)
145  if (reg.randStart >= 0)
146  maxBit = std::max(maxBit, (uint64_t)reg.randStart + reg.width);
147 
148  for (auto &reg : randomInit) {
149  if (reg.randStart == -1) {
150  reg.randStart = maxBit;
151  maxBit += reg.width;
152  }
153  }
154 
155  // Create an initial block at the end of the module where random
156  // initialisation will be inserted. Create two builders into the two
157  // `ifdef` ops where the registers will be placed.
158  //
159  // `ifndef SYNTHESIS
160  // `ifdef RANDOMIZE_REG_INIT
161  // ... regBuilder ...
162  // `endif
163  // initial
164  // `INIT_RANDOM_PROLOG_
165  // ... initBuilder ..
166  // `endif
167  if (randomInit.empty() && presetInit.empty() && asyncResets.empty())
168  return;
169 
170  needsRandom = true;
171 
172  auto loc = module.getLoc();
173  MLIRContext *context = module.getContext();
174  auto randInitRef = sv::MacroIdentAttr::get(context, "RANDOMIZE_REG_INIT");
175 
176  auto builder =
177  ImplicitLocOpBuilder::atBlockTerminator(loc, module.getBodyBlock());
178 
179  builder.create<sv::IfDefOp>("ENABLE_INITIAL_REG_", [&] {
180  builder.create<sv::OrderedOutputOp>([&] {
181  builder.create<sv::IfDefOp>("FIRRTL_BEFORE_INITIAL", [&] {
182  builder.create<sv::VerbatimOp>("`FIRRTL_BEFORE_INITIAL");
183  });
184 
185  builder.create<sv::InitialOp>([&] {
186  if (!randomInit.empty()) {
187  builder.create<sv::IfDefProceduralOp>("INIT_RANDOM_PROLOG_", [&] {
188  builder.create<sv::VerbatimOp>("`INIT_RANDOM_PROLOG_");
189  });
190  builder.create<sv::IfDefProceduralOp>(randInitRef, [&] {
191  // Create randomization vector
192  SmallVector<Value> randValues;
193  auto numRandomCalls = (maxBit + 31) / 32;
194  auto logic = builder.create<sv::LogicOp>(
195  loc,
196  hw::UnpackedArrayType::get(builder.getIntegerType(32),
197  numRandomCalls),
198  "_RANDOM");
199  // Indvar's width must be equal to `ceil(log2(numRandomCalls +
200  // 1))` to avoid overflow.
201  auto inducionVariableWidth = llvm::Log2_64_Ceil(numRandomCalls + 1);
202  auto arrayIndexWith = llvm::Log2_64_Ceil(numRandomCalls);
203  auto lb =
204  getOrCreateConstant(loc, APInt::getZero(inducionVariableWidth));
205  auto ub = getOrCreateConstant(
206  loc, APInt(inducionVariableWidth, numRandomCalls));
207  auto step =
208  getOrCreateConstant(loc, APInt(inducionVariableWidth, 1));
209  auto forLoop = builder.create<sv::ForOp>(
210  loc, lb, ub, step, "i", [&](BlockArgument iter) {
211  auto rhs = builder.create<sv::MacroRefExprSEOp>(
212  loc, builder.getIntegerType(32), "RANDOM");
213  Value iterValue = iter;
214  if (!iter.getType().isInteger(arrayIndexWith))
215  iterValue = builder.create<comb::ExtractOp>(
216  loc, iterValue, 0, arrayIndexWith);
217  auto lhs = builder.create<sv::ArrayIndexInOutOp>(loc, logic,
218  iterValue);
219  builder.create<sv::BPAssignOp>(loc, lhs, rhs);
220  });
221  builder.setInsertionPointAfter(forLoop);
222  for (uint64_t x = 0; x < numRandomCalls; ++x) {
223  auto lhs = builder.create<sv::ArrayIndexInOutOp>(
224  loc, logic,
225  getOrCreateConstant(loc, APInt(arrayIndexWith, x)));
226  randValues.push_back(lhs.getResult());
227  }
228 
229  // Create initialisers for all registers.
230  for (auto &svReg : randomInit)
231  initialize(builder, svReg, randValues);
232  });
233  }
234 
235  if (!presetInit.empty()) {
236  for (auto &svReg : presetInit) {
237  auto loc = svReg.reg.getLoc();
238  auto elemTy = svReg.reg.getType().getElementType();
239  auto cst = getOrCreateConstant(loc, svReg.preset.getValue());
240 
241  Value rhs;
242  if (cst.getType() == elemTy)
243  rhs = cst;
244  else
245  rhs = builder.create<hw::BitcastOp>(loc, elemTy, cst);
246 
247  builder.create<sv::BPAssignOp>(loc, svReg.reg, rhs);
248  }
249  }
250 
251  if (!asyncResets.empty()) {
252  // If the register is async reset, we need to insert extra
253  // initialization in post-randomization so that we can set the
254  // reset value to register if the reset signal is enabled.
255  for (auto &reset : asyncResets) {
256  // if (reset) begin
257  // ..
258  // end
259  builder.create<sv::IfOp>(reset.first, [&] {
260  for (auto &reg : reset.second)
261  builder.create<sv::BPAssignOp>(reg.reg.getLoc(), reg.reg,
262  reg.asyncResetValue);
263  });
264  }
265  }
266  });
267 
268  builder.create<sv::IfDefOp>("FIRRTL_AFTER_INITIAL", [&] {
269  builder.create<sv::VerbatimOp>("`FIRRTL_AFTER_INITIAL");
270  });
271  });
272  });
273 
274  module->removeAttr("firrtl.random_init_width");
275 }
276 
277 // Return true if two arguments are equivalent, or if both of them are the same
278 // array indexing.
279 // NOLINTNEXTLINE(misc-no-recursion)
280 static bool areEquivalentValues(Value term, Value next) {
281  if (term == next)
282  return true;
283  // Check whether these values are equivalent array accesses with constant
284  // index. We have to check the equivalence recursively because they might not
285  // be CSEd.
286  if (auto t1 = term.getDefiningOp<hw::ArrayGetOp>())
287  if (auto t2 = next.getDefiningOp<hw::ArrayGetOp>())
288  if (auto c1 = t1.getIndex().getDefiningOp<hw::ConstantOp>())
289  if (auto c2 = t2.getIndex().getDefiningOp<hw::ConstantOp>())
290  return c1.getType() == c2.getType() &&
291  c1.getValue() == c2.getValue() &&
292  areEquivalentValues(t1.getInput(), t2.getInput());
293  // Otherwise, regard as different.
294  // TODO: Handle struct if necessary.
295  return false;
296 }
297 
298 static llvm::SetVector<Value> extractConditions(Value value) {
299  auto andOp = value.getDefiningOp<comb::AndOp>();
300  // If the value is not AndOp with a bin flag, use it as a condition.
301  if (!andOp || !andOp.getTwoState()) {
302  llvm::SetVector<Value> ret;
303  ret.insert(value);
304  return ret;
305  }
306 
307  return llvm::SetVector<Value>(andOp.getOperands().begin(),
308  andOp.getOperands().end());
309 }
310 
311 static std::optional<APInt> getConstantValue(Value value) {
312  auto constantIndex = value.template getDefiningOp<hw::ConstantOp>();
313  if (constantIndex)
314  return constantIndex.getValue();
315  return {};
316 }
317 
318 // Return a tuple <cond, idx, val> if the array register update can be
319 // represented with a dynamic index assignment:
320 // if (cond)
321 // reg[idx] <= val;
322 //
323 std::optional<std::tuple<Value, Value, Value>>
324 FirRegLowering::tryRestoringSubaccess(OpBuilder &builder, Value reg, Value term,
325  hw::ArrayCreateOp nextRegValue) {
326  Value trueVal;
327  SmallVector<Value> muxConditions;
328  // Compat fix for GCC12's libstdc++, cannot use
329  // llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
330  SmallVector<Value> reverseOpValues(llvm::reverse(nextRegValue.getOperands()));
331  if (!llvm::all_of(llvm::enumerate(reverseOpValues), [&](auto idxAndValue) {
332  // Check that `nextRegValue[i]` is `cond_i ? val : reg[i]`.
333  auto [i, value] = idxAndValue;
334  auto mux = value.template getDefiningOp<comb::MuxOp>();
335  // Ensure that mux has binary flag.
336  if (!mux || !mux.getTwoState())
337  return false;
338  // The next value must be same.
339  if (trueVal && trueVal != mux.getTrueValue())
340  return false;
341  if (!trueVal)
342  trueVal = mux.getTrueValue();
343  muxConditions.push_back(mux.getCond());
344  // Check that ith element is an element of the register we are
345  // currently lowering.
346  auto arrayGet =
347  mux.getFalseValue().template getDefiningOp<hw::ArrayGetOp>();
348  if (!arrayGet)
349  return false;
350  return areEquivalentValues(arrayGet.getInput(), term) &&
351  getConstantValue(arrayGet.getIndex()) == i;
352  }))
353  return {};
354 
355  // Extract common expressions among mux conditions.
356  llvm::SetVector<Value> commonConditions =
357  extractConditions(muxConditions.front());
358  for (auto condition : ArrayRef(muxConditions).drop_front()) {
359  auto cond = extractConditions(condition);
360  commonConditions.remove_if([&](auto v) { return !cond.contains(v); });
361  }
362  Value indexValue;
363  for (auto [idx, condition] : llvm::enumerate(muxConditions)) {
364  llvm::SetVector<Value> extractedConditions = extractConditions(condition);
365  // Remove common conditions and check the remaining condition is only an
366  // index comparision.
367  extractedConditions.remove_if(
368  [&](auto v) { return commonConditions.contains(v); });
369  if (extractedConditions.size() != 1)
370  return {};
371 
372  auto indexCompare =
373  (*extractedConditions.begin()).getDefiningOp<comb::ICmpOp>();
374  if (!indexCompare || !indexCompare.getTwoState() ||
375  indexCompare.getPredicate() != comb::ICmpPredicate::eq)
376  return {};
377  // `IndexValue` must be same.
378  if (indexValue && indexValue != indexCompare.getLhs())
379  return {};
380  if (!indexValue)
381  indexValue = indexCompare.getLhs();
382  if (getConstantValue(indexCompare.getRhs()) != idx)
383  return {};
384  }
385 
386  OpBuilder::InsertionGuard guard(builder);
387  builder.setInsertionPointAfterValue(reg);
388  Value commonConditionValue;
389  if (commonConditions.empty())
390  commonConditionValue = getOrCreateConstant(reg.getLoc(), APInt(1, 1));
391  else
392  commonConditionValue = builder.createOrFold<comb::AndOp>(
393  reg.getLoc(), builder.getI1Type(), commonConditions.takeVector(), true);
394  return std::make_tuple(commonConditionValue, indexValue, trueVal);
395 }
396 
397 void FirRegLowering::createTree(OpBuilder &builder, Value reg, Value term,
398  Value next) {
399  // Get the fanout from this register before we build the tree. While we are
400  // creating the tree of if/else statements from muxes, we only want to turn
401  // muxes that are on the register's fanout into if/else statements. This is
402  // required to get the correct enable inference. But other muxes in the tree
403  // should be left as ternary operators. This is desirable because we don't
404  // want to create if/else structure for logic unrelated to the register's
405  // enable.
406  auto firReg = term.getDefiningOp<seq::FirRegOp>();
407 
408  SmallVector<std::tuple<Block *, Value, Value, Value>> worklist;
409  auto addToWorklist = [&](Value reg, Value term, Value next) {
410  worklist.push_back({builder.getBlock(), reg, term, next});
411  };
412 
413  auto getArrayIndex = [&](Value reg, Value idx) {
414  // Create an array index op just after `reg`.
415  OpBuilder::InsertionGuard guard(builder);
416  builder.setInsertionPointAfterValue(reg);
417  return builder.create<sv::ArrayIndexInOutOp>(reg.getLoc(), reg, idx);
418  };
419 
420  SmallVector<Value, 8> opsToDelete;
421  addToWorklist(reg, term, next);
422  while (!worklist.empty()) {
423  OpBuilder::InsertionGuard guard(builder);
424  Block *block;
425  Value reg, term, next;
426  std::tie(block, reg, term, next) = worklist.pop_back_val();
427  builder.setInsertionPointToEnd(block);
428  if (areEquivalentValues(term, next))
429  continue;
430 
431  // If this is a two-state mux within the fanout from the register, we use
432  // if/else structure for proper enable inference.
433  auto mux = next.getDefiningOp<comb::MuxOp>();
434  if (mux && mux.getTwoState() &&
435  reachableMuxes->isMuxReachableFrom(firReg, mux)) {
436  addToIfBlock(
437  builder, mux.getCond(),
438  [&]() { addToWorklist(reg, term, mux.getTrueValue()); },
439  [&]() { addToWorklist(reg, term, mux.getFalseValue()); });
440  continue;
441  }
442  // If the next value is an array creation, split the value into
443  // invidial elements and construct trees recursively.
444  if (auto array = next.getDefiningOp<hw::ArrayCreateOp>()) {
445  // First, try restoring subaccess assignments.
446  if (auto matchResultOpt =
447  tryRestoringSubaccess(builder, reg, term, array)) {
448  Value cond, index, trueValue;
449  std::tie(cond, index, trueValue) = *matchResultOpt;
450  addToIfBlock(
451  builder, cond,
452  [&]() {
453  Value nextReg = getArrayIndex(reg, index);
454  // Create a value to use for equivalence checking in the
455  // recursive calls. Add the value to `opsToDelete` so that it can
456  // be deleted afterwards.
457  auto termElement =
458  builder.create<hw::ArrayGetOp>(term.getLoc(), term, index);
459  opsToDelete.push_back(termElement);
460  addToWorklist(nextReg, termElement, trueValue);
461  },
462  []() {});
464  continue;
465  }
466  // Compat fix for GCC12's libstdc++, cannot use
467  // llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
468  // SmallVector<Value> reverseOpValues(llvm::reverse(array.getOperands()));
469  for (auto [idx, value] : llvm::enumerate(array.getOperands())) {
470  idx = array.getOperands().size() - idx - 1;
471  // Create an index constant.
472  auto idxVal = getOrCreateConstant(
473  array.getLoc(),
474  APInt(std::max(1u, llvm::Log2_64_Ceil(array.getOperands().size())),
475  idx));
476 
477  auto &index = arrayIndexCache[{reg, idx}];
478  if (!index)
479  index = getArrayIndex(reg, idxVal);
480 
481  // Create a value to use for equivalence checking in the
482  // recursive calls. Add the value to `opsToDelete` so that it can
483  // be deleted afterwards.
484  auto termElement =
485  builder.create<hw::ArrayGetOp>(term.getLoc(), term, idxVal);
486  opsToDelete.push_back(termElement);
487  addToWorklist(index, termElement, value);
488  }
489  continue;
490  }
491 
492  builder.create<sv::PAssignOp>(term.getLoc(), reg, next);
493  }
494 
495  while (!opsToDelete.empty()) {
496  auto value = opsToDelete.pop_back_val();
497  assert(value.use_empty());
498  value.getDefiningOp()->erase();
499  }
500 }
501 
503  Location loc = reg.getLoc();
504  Type regTy = typeConverter.convertType(reg.getType());
505 
506  ImplicitLocOpBuilder builder(reg.getLoc(), reg);
507  RegLowerInfo svReg{nullptr, reg.getPresetAttr(), nullptr, nullptr, -1, 0};
508  svReg.reg = builder.create<sv::RegOp>(loc, regTy, reg.getNameAttr());
509  svReg.width = hw::getBitWidth(regTy);
510 
511  if (auto attr = reg->getAttrOfType<IntegerAttr>("firrtl.random_init_start"))
512  svReg.randStart = attr.getUInt();
513 
514  // Don't move these over
515  reg->removeAttr("firrtl.random_init_start");
516 
517  // Move Attributes
518  svReg.reg->setDialectAttrs(reg->getDialectAttrs());
519 
520  if (auto innerSymAttr = reg.getInnerSymAttr())
521  svReg.reg.setInnerSymAttr(innerSymAttr);
522 
523  auto regVal = builder.create<sv::ReadInOutOp>(loc, svReg.reg);
524 
525  if (reg.hasReset()) {
527  module.getBodyBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
528  [&](OpBuilder &b) {
529  // If this is an AsyncReset, ensure that we emit a self connect to
530  // avoid erroneously creating a latch construct.
531  if (reg.getIsAsync() && areEquivalentValues(reg, reg.getNext()))
532  b.create<sv::PAssignOp>(reg.getLoc(), svReg.reg, reg);
533  else
534  createTree(b, svReg.reg, reg, reg.getNext());
535  },
536  reg.getIsAsync() ? sv::ResetType::AsyncReset : sv::ResetType::SyncReset,
537  sv::EventControl::AtPosEdge, reg.getReset(),
538  [&](OpBuilder &builder) {
539  builder.create<sv::PAssignOp>(loc, svReg.reg, reg.getResetValue());
540  });
541  if (reg.getIsAsync()) {
542  svReg.asyncResetSignal = reg.getReset();
543  svReg.asyncResetValue = reg.getResetValue();
544  }
545  } else {
547  module.getBodyBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
548  [&](OpBuilder &b) { createTree(b, svReg.reg, reg, reg.getNext()); });
549  }
550 
551  reg.replaceAllUsesWith(regVal.getResult());
552  reg.erase();
553 
554  return svReg;
555 }
556 
557 // Initialize registers by assigning each element recursively instead of
558 // initializing entire registers. This is necessary as a workaround for
559 // verilator which allocates many local variables for concat op.
560 // NOLINTBEGIN(misc-no-recursion)
562  OpBuilder &builder, Value reg,
563  Value randomSource,
564  unsigned &pos) {
565  auto type = cast<sv::InOutType>(reg.getType()).getElementType();
566  if (auto intTy = hw::type_dyn_cast<IntegerType>(type)) {
567  // Use randomSource[pos-1:pos-width] as a random value.
568  pos -= intTy.getWidth();
569  auto elem = builder.createOrFold<comb::ExtractOp>(loc, randomSource, pos,
570  intTy.getWidth());
571  builder.create<sv::BPAssignOp>(loc, reg, elem);
572  } else if (auto array = hw::type_dyn_cast<hw::ArrayType>(type)) {
573  for (unsigned i = 0, e = array.getNumElements(); i < e; ++i) {
574  auto index = getOrCreateConstant(loc, APInt(llvm::Log2_64_Ceil(e), i));
576  loc, builder, builder.create<sv::ArrayIndexInOutOp>(loc, reg, index),
577  randomSource, pos);
578  }
579  } else if (auto structType = hw::type_dyn_cast<hw::StructType>(type)) {
580  for (auto e : structType.getElements())
582  loc, builder,
583  builder.create<sv::StructFieldInOutOp>(loc, reg, e.name),
584  randomSource, pos);
585  } else {
586  assert(false && "unsupported type");
587  }
588 }
589 // NOLINTEND(misc-no-recursion)
590 
591 void FirRegLowering::initialize(OpBuilder &builder, RegLowerInfo reg,
592  ArrayRef<Value> rands) {
593  auto loc = reg.reg.getLoc();
594  SmallVector<Value> nibbles;
595  if (reg.width == 0)
596  return;
597 
598  uint64_t width = reg.width;
599  uint64_t offset = reg.randStart;
600  while (width) {
601  auto index = offset / 32;
602  auto start = offset % 32;
603  auto nwidth = std::min(32 - start, width);
604  auto elemVal = builder.create<sv::ReadInOutOp>(loc, rands[index]);
605  auto elem =
606  builder.createOrFold<comb::ExtractOp>(loc, elemVal, start, nwidth);
607  nibbles.push_back(elem);
608  offset += nwidth;
609  width -= nwidth;
610  }
611  auto concat = builder.createOrFold<comb::ConcatOp>(loc, nibbles);
612  unsigned pos = reg.width;
613  // Initialize register elements.
614  initializeRegisterElements(loc, builder, reg.reg, concat, pos);
615 }
616 
618  Block *block, sv::EventControl clockEdge, Value clock,
619  const std::function<void(OpBuilder &)> &body, sv::ResetType resetStyle,
620  sv::EventControl resetEdge, Value reset,
621  const std::function<void(OpBuilder &)> &resetBody) {
622  auto loc = clock.getLoc();
623  auto builder = ImplicitLocOpBuilder::atBlockTerminator(loc, block);
624  AlwaysKeyType key{builder.getBlock(), clockEdge, clock,
625  resetStyle, resetEdge, reset};
626 
627  sv::AlwaysOp alwaysOp;
628  sv::IfOp insideIfOp;
630  std::tie(alwaysOp, insideIfOp) = alwaysBlocks[key];
631  }
632 
633  if (!alwaysOp) {
634  if (reset) {
635  assert(resetStyle != sv::ResetType::NoReset);
636  // Here, we want to create the following structure with sv.always and
637  // sv.if. If `reset` is async, we need to add `reset` to a sensitivity
638  // list.
639  //
640  // sv.always @(clockEdge or reset) {
641  // sv.if (reset) {
642  // resetBody
643  // } else {
644  // body
645  // }
646  // }
647 
648  auto createIfOp = [&]() {
649  // It is weird but intended. Here we want to create an empty sv.if
650  // with an else block.
651  insideIfOp = builder.create<sv::IfOp>(
652  reset, []() {}, []() {});
653  };
654  if (resetStyle == sv::ResetType::AsyncReset) {
655  sv::EventControl events[] = {clockEdge, resetEdge};
656  Value clocks[] = {clock, reset};
657 
658  alwaysOp = builder.create<sv::AlwaysOp>(events, clocks, [&]() {
659  if (resetEdge == sv::EventControl::AtNegEdge)
660  llvm_unreachable("negative edge for reset is not expected");
661  createIfOp();
662  });
663  } else {
664  alwaysOp = builder.create<sv::AlwaysOp>(clockEdge, clock, createIfOp);
665  }
666  } else {
667  assert(!resetBody);
668  alwaysOp = builder.create<sv::AlwaysOp>(clockEdge, clock);
669  insideIfOp = nullptr;
670  }
671  }
672 
673  if (reset) {
674  assert(insideIfOp && "reset body must be initialized before");
675  auto resetBuilder =
676  ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getThenBlock());
677  resetBody(resetBuilder);
678 
679  auto bodyBuilder =
680  ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getElseBlock());
681  body(bodyBuilder);
682  } else {
683  auto bodyBuilder =
684  ImplicitLocOpBuilder::atBlockEnd(loc, alwaysOp.getBodyBlock());
685  body(bodyBuilder);
686  }
687 
689  alwaysBlocks[key] = {alwaysOp, insideIfOp};
690  }
691 }
assert(baseType &&"element must be base type")
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 bool areEquivalentValues(Value term, Value next)
static llvm::SetVector< Value > extractConditions(Value value)
static std::optional< APInt > getConstantValue(Value value)
std::unique_ptr< ReachableMuxes > reachableMuxes
void initialize(OpBuilder &builder, RegLowerInfo reg, ArrayRef< Value > rands)
llvm::SmallDenseMap< std::pair< Value, unsigned >, Value > arrayIndexCache
FirRegLowering(TypeConverter &typeConverter, hw::HWModuleOp module, bool disableRegRandomization=false, bool emitSeparateAlwaysBlocks=false)
hw::HWModuleOp module
void addToIfBlock(OpBuilder &builder, Value cond, const std::function< void()> &trueSide, const std::function< void()> &falseSide)
std::optional< std::tuple< Value, Value, Value > > tryRestoringSubaccess(OpBuilder &builder, Value reg, Value term, hw::ArrayCreateOp nextRegValue)
void createTree(OpBuilder &builder, Value reg, Value term, Value next)
hw::ConstantOp getOrCreateConstant(Location loc, const APInt &value)
void addToAlwaysBlock(Block *block, sv::EventControl clockEdge, Value clock, const std::function< void(OpBuilder &)> &body, sv::ResetType resetStyle={}, sv::EventControl resetEdge={}, Value reset={}, const std::function< void(OpBuilder &)> &resetBody={})
std::tuple< Block *, sv::EventControl, Value, sv::ResetType, sv::EventControl, Value > AlwaysKeyType
void initializeRegisterElements(Location loc, OpBuilder &builder, Value reg, Value rand, unsigned &pos)
TypeConverter & typeConverter
llvm::SmallDenseMap< AlwaysKeyType, std::pair< sv::AlwaysOp, sv::IfOp > > alwaysBlocks
void buildReachabilityFrom(Operation *startNode)
bool isMuxReachableFrom(seq::FirRegOp regOp, comb::MuxOp muxOp)
def create(low_bit, result_type, input=None)
Definition: comb.py:187
def create(data_type, value)
Definition: hw.py:441
Definition: sv.py:15
Definition: sv.py:68
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition: HWTypes.cpp:110
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: hw.py:1
Definition: seq.py:1
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:21
static std::function< bool(const Operation *op)> opAllowsReachability