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