CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
16using namespace circt;
17using namespace hw;
18using namespace seq;
19using llvm::MapVector;
20
21#define DEBUG_TYPE "lower-seq-firreg"
22
23std::function<bool(const Operation *op)> OpUserInfo::opAllowsReachability =
24 [](const Operation *op) -> bool {
25 return (isa<comb::MuxOp, ArrayGetOp, ArrayCreateOp>(op));
26};
27
28bool 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
38void 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
89void 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
108FirRegLowering::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)
280static 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
298static 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
311static 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//
323std::optional<std::tuple<Value, Value, Value>>
324FirRegLowering::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
397void 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)) {
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;
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
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 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