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