CIRCT 22.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 "circt/Support/Utils.h"
12#include "mlir/IR/Threading.h"
13#include "mlir/Transforms/DialectConversion.h"
14#include "llvm/ADT/DenseSet.h"
15#include "llvm/Support/Debug.h"
16
17#include <deque>
18
19using namespace circt;
20using namespace hw;
21using namespace seq;
22using llvm::MapVector;
23
24#define DEBUG_TYPE "lower-seq-firreg"
25
26/// Immediately before the terminator, if present. Otherwise, the block's end.
27static Block::iterator getBlockEnd(Block *block) {
28 if (block->mightHaveTerminator())
29 return Block::iterator(block->getTerminator());
30 return block->end();
31}
32
33std::function<bool(const Operation *op)> OpUserInfo::opAllowsReachability =
34 [](const Operation *op) -> bool {
35 return (isa<comb::MuxOp, ArrayGetOp, ArrayCreateOp>(op));
36};
37
38bool ReachableMuxes::isMuxReachableFrom(seq::FirRegOp regOp,
39 comb::MuxOp muxOp) {
40 return llvm::any_of(regOp.getResult().getUsers(), [&](Operation *user) {
41 if (!OpUserInfo::opAllowsReachability(user))
42 return false;
43 buildReachabilityFrom(user);
44 return reachableMuxes[user].contains(muxOp);
45 });
46}
47
48void ReachableMuxes::buildReachabilityFrom(Operation *startNode) {
49 // This is a backward dataflow analysis.
50 // First build a graph rooted at the `startNode`. Every user of an operation
51 // that does not block the reachability is a child node. Then, the ops that
52 // are reachable from a node is computed as the union of the Reachability of
53 // all its child nodes.
54 // The dataflow can be expressed as, for all child in the Children(node)
55 // Reachability(node) = node + Union{Reachability(child)}
56 if (visited.contains(startNode))
57 return;
58
59 // The stack to record enough information for an iterative post-order
60 // traversal.
61 llvm::SmallVector<OpUserInfo, 16> stk;
62
63 stk.emplace_back(startNode);
64
65 while (!stk.empty()) {
66 auto &info = stk.back();
67 Operation *currentNode = info.op;
68
69 // Node is being visited for the first time.
70 if (info.getAndSetUnvisited())
71 visited.insert(currentNode);
72
73 if (info.userIter != info.userEnd) {
74 Operation *child = *info.userIter;
75 ++info.userIter;
76 if (!visited.contains(child))
77 stk.emplace_back(child);
78
79 } else { // All children of the node have been visited
80 // Any op is reachable from itself.
81 reachableMuxes[currentNode].insert(currentNode);
82
83 for (auto *childOp : llvm::make_filter_range(
84 info.op->getUsers(), OpUserInfo::opAllowsReachability)) {
85 reachableMuxes[currentNode].insert(childOp);
86 // Propagate the reachability backwards from m to currentNode.
87 auto iter = reachableMuxes.find(childOp);
88 assert(iter != reachableMuxes.end());
89
90 // Add all the mux that was reachable from childOp, to currentNode.
91 reachableMuxes[currentNode].insert(iter->getSecond().begin(),
92 iter->getSecond().end());
93 }
94 stk.pop_back();
95 }
96 }
97}
98
99void FirRegLowering::addToIfBlock(OpBuilder &builder, Value cond,
100 const std::function<void()> &trueSide,
101 const std::function<void()> &falseSide) {
102 auto op = ifCache.lookup({builder.getBlock(), cond});
103 // Always build both sides of the if, in case we want to use an empty else
104 // later. This way we don't have to build a new if and replace it.
105 if (!op) {
106 auto newIfOp =
107 sv::IfOp::create(builder, cond.getLoc(), cond, trueSide, falseSide);
108 ifCache.insert({{builder.getBlock(), cond}, newIfOp});
109 } else {
110 OpBuilder::InsertionGuard guard(builder);
111 builder.setInsertionPointToEnd(op.getThenBlock());
112 trueSide();
113 builder.setInsertionPointToEnd(op.getElseBlock());
114 falseSide();
115 }
116}
117
118/// Attach an inner-sym to field-id 0 of the given register, or use an existing
119/// inner-sym, if present.
120static StringAttr getInnerSymFor(InnerSymbolNamespace &innerSymNS,
121 seq::FirRegOp reg) {
122 auto attr = reg.getInnerSymAttr();
123
124 // If we have an inner sym attribute already, and if there exists a symbol for
125 // field-id 0, then just return that.
126 if (attr)
127 if (auto sym = attr.getSymIfExists(0))
128 return sym;
129
130 // Otherwise, we have to create a new inner sym.
131 auto *context = reg->getContext();
132
133 auto hint = reg.getName();
134
135 // Create our new property for field 0.
136 auto sym = StringAttr::get(context, innerSymNS.newName(hint));
137 auto property = hw::InnerSymPropertiesAttr::get(sym);
138
139 // Build the new list of inner sym properties. Since properties are sorted by
140 // field ID, our new property is first.
141 SmallVector<hw::InnerSymPropertiesAttr> properties = {property};
142 if (attr)
143 llvm::append_range(properties, attr.getProps());
144
145 // Build the new InnerSymAttr and attach it to the op.
146 attr = hw::InnerSymAttr::get(context, properties);
147 reg.setInnerSymAttr(attr);
148
149 // Return the name of the new inner sym.
150 return sym;
151}
152
153static InnerRefAttr getInnerRefTo(StringAttr mod, InnerSymbolNamespace &isns,
154 seq::FirRegOp reg) {
155 auto tgt = getInnerSymFor(isns, reg);
156 return hw::InnerRefAttr::get(mod, tgt);
157}
158
159namespace {
160/// A pair of a register, and an inner-ref attribute.
161struct BuriedFirReg {
162 FirRegOp reg;
163 InnerRefAttr ref;
164};
165} // namespace
166
167/// Locate the registers under the given HW module, which are not at the
168/// top-level of the module body. These registers will be initialized through an
169/// NLA. Put an inner symbol on each, and return a list of the buried registers
170/// and their inner-symbols.
171static std::vector<BuriedFirReg> getBuriedRegs(HWModuleOp module) {
172 auto name = SymbolTable::getSymbolName(module);
173 InnerSymbolNamespace isns(module);
174 std::vector<BuriedFirReg> result;
175 for (auto &op : *module.getBodyBlock()) {
176 for (auto &region : op.getRegions()) {
177 region.walk([&](FirRegOp reg) {
178 auto ref = getInnerRefTo(name, isns, reg);
179 result.push_back({reg, ref});
180 });
181 }
182 }
183 return result;
184}
185
186/// Locate all registers which are not at the top-level of their parent HW
187/// module. These registers will be initialized through an NLA. Put an inner
188/// symbol on each, and return a list of the buried registers and their
189/// inner-symbols.
190static std::vector<BuriedFirReg> getAllBuriedRegs(ModuleOp top) {
191 auto *context = top.getContext();
192 std::vector<BuriedFirReg> init;
193 auto ms = top.getOps<HWModuleOp>();
194 const std::vector<HWModuleOp> modules(ms.begin(), ms.end());
195 const auto reduce =
196 [](std::vector<BuriedFirReg> acc,
197 std::vector<BuriedFirReg> &&xs) -> std::vector<BuriedFirReg> {
198 acc.insert(acc.end(), xs.begin(), xs.end());
199 return acc;
200 };
201 return transformReduce(context, modules, init, reduce, getBuriedRegs);
202}
203
204/// Construct a hierarchical path op that targets the given register.
205static hw::HierPathOp getHierPathTo(OpBuilder &builder, Namespace &ns,
206 BuriedFirReg entry) {
207 auto modName = entry.ref.getModule().getValue();
208 auto symName = entry.ref.getName().getValue();
209 auto name = ns.newName(Twine(modName) + "_" + symName);
210
211 // Insert the HierPathOp immediately before the parent HWModuleOp, for style.
212 OpBuilder::InsertionGuard guard(builder);
213 builder.setInsertionPoint(entry.reg->getParentOfType<HWModuleOp>());
214
215 auto path = builder.getArrayAttr({entry.ref});
216 return hw::HierPathOp::create(builder, entry.reg.getLoc(), name, path);
217}
218
220 auto builder = OpBuilder::atBlockBegin(top.getBody());
221 PathTable result;
222 Namespace ns;
223 ns.add(top);
224 for (auto entry : getAllBuriedRegs(top))
225 result[entry.reg] = getHierPathTo(builder, ns, entry);
226 return result;
227}
228
229FirRegLowering::FirRegLowering(TypeConverter &typeConverter,
230 hw::HWModuleOp module,
231 const PathTable &pathTable,
232 bool disableRegRandomization,
233 bool emitSeparateAlwaysBlocks)
234 : pathTable(pathTable), typeConverter(typeConverter), module(module),
235 disableRegRandomization(disableRegRandomization),
236 emitSeparateAlwaysBlocks(emitSeparateAlwaysBlocks) {
237 reachableMuxes = std::make_unique<ReachableMuxes>(module);
238}
239
241 lowerInBlock(module.getBodyBlock());
243 module->removeAttr("firrtl.random_init_width");
244}
245
246// NOLINTNEXTLINE(misc-no-recursion)
248 auto cond = ifDefOp.getCond();
249
250 conditions.emplace_back(RegCondition::IfDefThen, cond);
251 lowerInBlock(ifDefOp.getThenBlock());
252 conditions.pop_back();
253
254 if (ifDefOp.hasElse()) {
255 conditions.emplace_back(RegCondition::IfDefElse, cond);
256 lowerInBlock(ifDefOp.getElseBlock());
257 conditions.pop_back();
258 }
259}
260
261// NOLINTNEXTLINE(misc-no-recursion)
263 for (auto &op : llvm::make_early_inc_range(*block)) {
264 if (auto ifDefOp = dyn_cast<sv::IfDefOp>(op)) {
265 lowerUnderIfDef(ifDefOp);
266 continue;
267 }
268 if (auto regOp = dyn_cast<seq::FirRegOp>(op)) {
269 lowerReg(regOp);
270 continue;
271 }
272 for (auto &region : op.getRegions())
273 for (auto &block : region.getBlocks())
274 lowerInBlock(&block);
275 }
276}
277
278SmallVector<Value> FirRegLowering::createRandomizationVector(OpBuilder &builder,
279 Location loc) {
280 // Compute total width of random space. Place non-chisel registers at the end
281 // of the space. The Random space is unique to the initial block, due to
282 // verilog thread rules, so we can drop trailing random calls if they are
283 // unused.
284 uint64_t maxBit = 0;
285 for (auto reg : randomInitRegs)
286 if (reg.randStart >= 0)
287 maxBit = std::max(maxBit, (uint64_t)reg.randStart + reg.width);
288
289 for (auto &reg : randomInitRegs) {
290 if (reg.randStart == -1) {
291 reg.randStart = maxBit;
292 maxBit += reg.width;
293 }
294 }
295
296 // Create randomization vector
297 SmallVector<Value> randValues;
298 auto numRandomCalls = (maxBit + 31) / 32;
299 auto logic = sv::LogicOp::create(
300 builder, loc,
301 hw::UnpackedArrayType::get(builder.getIntegerType(32), numRandomCalls),
302 "_RANDOM");
303 // Indvar's width must be equal to `ceil(log2(numRandomCalls +
304 // 1))` to avoid overflow.
305 auto inducionVariableWidth = llvm::Log2_64_Ceil(numRandomCalls + 1);
306 auto arrayIndexWith = llvm::Log2_64_Ceil(numRandomCalls);
307 auto lb = getOrCreateConstant(loc, APInt::getZero(inducionVariableWidth));
308 auto ub =
309 getOrCreateConstant(loc, APInt(inducionVariableWidth, numRandomCalls));
310 auto step = getOrCreateConstant(loc, APInt(inducionVariableWidth, 1));
311 auto forLoop = sv::ForOp::create(
312 builder, loc, lb, ub, step, "i", [&](BlockArgument iter) {
313 auto rhs = sv::MacroRefExprSEOp::create(
314 builder, loc, builder.getIntegerType(32), "RANDOM");
315 Value iterValue = iter;
316 if (!iter.getType().isInteger(arrayIndexWith))
317 iterValue = comb::ExtractOp::create(builder, loc, iterValue, 0,
318 arrayIndexWith);
319 auto lhs =
320 sv::ArrayIndexInOutOp::create(builder, loc, logic, iterValue);
321 sv::BPAssignOp::create(builder, loc, lhs, rhs);
322 });
323 builder.setInsertionPointAfter(forLoop);
324 for (uint64_t x = 0; x < numRandomCalls; ++x) {
325 auto lhs = sv::ArrayIndexInOutOp::create(
326 builder, loc, logic,
327 getOrCreateConstant(loc, APInt(arrayIndexWith, x)));
328 randValues.push_back(lhs.getResult());
329 }
330
331 return randValues;
332}
333
334void FirRegLowering::createRandomInitialization(ImplicitLocOpBuilder &builder) {
335 auto randInitRef =
336 sv::MacroIdentAttr::get(builder.getContext(), "RANDOMIZE_REG_INIT");
337
338 if (!randomInitRegs.empty()) {
339 sv::IfDefProceduralOp::create(builder, "INIT_RANDOM_PROLOG_", [&] {
340 sv::VerbatimOp::create(builder, "`INIT_RANDOM_PROLOG_");
341 });
342
343 sv::IfDefProceduralOp::create(builder, randInitRef, [&] {
344 auto randValues = createRandomizationVector(builder, builder.getLoc());
345 for (auto &svReg : randomInitRegs)
346 initialize(builder, svReg, randValues);
347 });
348 }
349}
350
351void FirRegLowering::createPresetInitialization(ImplicitLocOpBuilder &builder) {
352 for (auto &svReg : presetInitRegs) {
353 auto loc = svReg.reg.getLoc();
354 auto elemTy = svReg.reg.getType().getElementType();
355 auto cst = getOrCreateConstant(loc, svReg.preset.getValue());
356
357 Value rhs;
358 if (cst.getType() == elemTy)
359 rhs = cst;
360 else
361 rhs = hw::BitcastOp::create(builder, loc, elemTy, cst);
362
363 sv::BPAssignOp::create(builder, loc, svReg.reg, rhs);
364 }
365}
366
367// If a register is async reset, we need to insert extra initialization in
368// post-randomization so that we can set the reset value to register if the
369// reset signal is enabled.
371 ImplicitLocOpBuilder &builder) {
372 for (auto &reset : asyncResets) {
373 // if (reset) begin
374 // ..
375 // end
376 sv::IfOp::create(builder, reset.first, [&] {
377 for (auto &reg : reset.second)
378 sv::BPAssignOp::create(builder, reg.reg.getLoc(), reg.reg,
379 reg.asyncResetValue);
380 });
381 }
382}
383
385 // Create an initial block at the end of the module where random
386 // initialisation will be inserted. Create two builders into the two
387 // `ifdef` ops where the registers will be placed.
388 //
389 // `ifndef SYNTHESIS
390 // `ifdef RANDOMIZE_REG_INIT
391 // ... regBuilder ...
392 // `endif
393 // initial
394 // `INIT_RANDOM_PROLOG_
395 // ... initBuilder ..
396 // `endif
397 if (randomInitRegs.empty() && presetInitRegs.empty() && asyncResets.empty())
398 return;
399
400 needsRandom = true;
401
402 auto loc = module.getLoc();
403 auto builder =
404 ImplicitLocOpBuilder::atBlockTerminator(loc, module.getBodyBlock());
405
406 sv::IfDefOp::create(builder, "ENABLE_INITIAL_REG_", [&] {
407 sv::OrderedOutputOp::create(builder, [&] {
408 sv::IfDefOp::create(builder, "FIRRTL_BEFORE_INITIAL", [&] {
409 sv::VerbatimOp::create(builder, "`FIRRTL_BEFORE_INITIAL");
410 });
411
412 sv::InitialOp::create(builder, [&] {
416 });
417
418 sv::IfDefOp::create(builder, "FIRRTL_AFTER_INITIAL", [&] {
419 sv::VerbatimOp::create(builder, "`FIRRTL_AFTER_INITIAL");
420 });
421 });
422 });
423}
424
425// Return true if two arguments are equivalent, or if both of them are the same
426// array indexing.
427// NOLINTNEXTLINE(misc-no-recursion)
428static bool areEquivalentValues(Value term, Value next) {
429 if (term == next)
430 return true;
431 // Check whether these values are equivalent array accesses with constant
432 // index. We have to check the equivalence recursively because they might not
433 // be CSEd.
434 if (auto t1 = term.getDefiningOp<hw::ArrayGetOp>())
435 if (auto t2 = next.getDefiningOp<hw::ArrayGetOp>())
436 if (auto c1 = t1.getIndex().getDefiningOp<hw::ConstantOp>())
437 if (auto c2 = t2.getIndex().getDefiningOp<hw::ConstantOp>())
438 return c1.getType() == c2.getType() &&
439 c1.getValue() == c2.getValue() &&
440 areEquivalentValues(t1.getInput(), t2.getInput());
441 // Otherwise, regard as different.
442 // TODO: Handle struct if necessary.
443 return false;
444}
445
446static llvm::SetVector<Value> extractConditions(Value value) {
447 auto andOp = value.getDefiningOp<comb::AndOp>();
448 // If the value is not AndOp with a bin flag, use it as a condition.
449 if (!andOp || !andOp.getTwoState()) {
450 llvm::SetVector<Value> ret;
451 ret.insert(value);
452 return ret;
453 }
454
455 return llvm::SetVector<Value>(andOp.getOperands().begin(),
456 andOp.getOperands().end());
457}
458
459static std::optional<APInt> getConstantValue(Value value) {
460 auto constantIndex = value.template getDefiningOp<hw::ConstantOp>();
461 if (constantIndex)
462 return constantIndex.getValue();
463 return {};
464}
465
466// Return a tuple <cond, idx, val> if the array register update can be
467// represented with a dynamic index assignment:
468// if (cond)
469// reg[idx] <= val;
470//
471std::optional<std::tuple<Value, Value, Value>>
472FirRegLowering::tryRestoringSubaccess(OpBuilder &builder, Value reg, Value term,
473 hw::ArrayCreateOp nextRegValue) {
474 Value trueVal;
475 SmallVector<Value> muxConditions;
476 // Compat fix for GCC12's libstdc++, cannot use
477 // llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
478 SmallVector<Value> reverseOpValues(llvm::reverse(nextRegValue.getOperands()));
479 if (!llvm::all_of(llvm::enumerate(reverseOpValues), [&](auto idxAndValue) {
480 // Check that `nextRegValue[i]` is `cond_i ? val : reg[i]`.
481 auto [i, value] = idxAndValue;
482 auto mux = value.template getDefiningOp<comb::MuxOp>();
483 // Ensure that mux has binary flag.
484 if (!mux || !mux.getTwoState())
485 return false;
486 // The next value must be same.
487 if (trueVal && trueVal != mux.getTrueValue())
488 return false;
489 if (!trueVal)
490 trueVal = mux.getTrueValue();
491 muxConditions.push_back(mux.getCond());
492 // Check that ith element is an element of the register we are
493 // currently lowering.
494 auto arrayGet =
495 mux.getFalseValue().template getDefiningOp<hw::ArrayGetOp>();
496 if (!arrayGet)
497 return false;
498 return areEquivalentValues(arrayGet.getInput(), term) &&
499 getConstantValue(arrayGet.getIndex()) == i;
500 }))
501 return {};
502
503 // Extract common expressions among mux conditions.
504 llvm::SetVector<Value> commonConditions =
505 extractConditions(muxConditions.front());
506 for (auto condition : ArrayRef(muxConditions).drop_front()) {
507 auto cond = extractConditions(condition);
508 commonConditions.remove_if([&](auto v) { return !cond.contains(v); });
509 }
510 Value indexValue;
511 for (auto [idx, condition] : llvm::enumerate(muxConditions)) {
512 llvm::SetVector<Value> extractedConditions = extractConditions(condition);
513 // Remove common conditions and check the remaining condition is only an
514 // index comparision.
515 extractedConditions.remove_if(
516 [&](auto v) { return commonConditions.contains(v); });
517 if (extractedConditions.size() != 1)
518 return {};
519
520 auto indexCompare =
521 (*extractedConditions.begin()).getDefiningOp<comb::ICmpOp>();
522 if (!indexCompare || !indexCompare.getTwoState() ||
523 indexCompare.getPredicate() != comb::ICmpPredicate::eq)
524 return {};
525 // `IndexValue` must be same.
526 if (indexValue && indexValue != indexCompare.getLhs())
527 return {};
528 if (!indexValue)
529 indexValue = indexCompare.getLhs();
530 if (getConstantValue(indexCompare.getRhs()) != idx)
531 return {};
532 }
533
534 OpBuilder::InsertionGuard guard(builder);
535 builder.setInsertionPointAfterValue(reg);
536 Value commonConditionValue;
537 if (commonConditions.empty())
538 commonConditionValue = getOrCreateConstant(reg.getLoc(), APInt(1, 1));
539 else
540 commonConditionValue = builder.createOrFold<comb::AndOp>(
541 reg.getLoc(), builder.getI1Type(), commonConditions.takeVector(), true);
542 return std::make_tuple(commonConditionValue, indexValue, trueVal);
543}
544
545void FirRegLowering::createTree(OpBuilder &builder, Value reg, Value term,
546 Value next) {
547 // If-then-else tree limit.
548 constexpr size_t limit = 1024;
549
550 // Count of emitted if-then-else ops.
551 size_t counter = 0;
552
553 // Get the fanout from this register before we build the tree. While we are
554 // creating the tree of if/else statements from muxes, we only want to turn
555 // muxes that are on the register's fanout into if/else statements. This is
556 // required to get the correct enable inference. But other muxes in the tree
557 // should be left as ternary operators. This is desirable because we don't
558 // want to create if/else structure for logic unrelated to the register's
559 // enable.
560 auto firReg = term.getDefiningOp<seq::FirRegOp>();
561
562 std::deque<std::tuple<Block *, Value, Value, Value>> worklist;
563 auto addToWorklist = [&](Value reg, Value term, Value next) {
564 worklist.emplace_back(builder.getBlock(), reg, term, next);
565 };
566
567 auto getArrayIndex = [&](Value reg, Value idx) {
568 // Create an array index op just after `reg`.
569 OpBuilder::InsertionGuard guard(builder);
570 builder.setInsertionPointAfterValue(reg);
571 return sv::ArrayIndexInOutOp::create(builder, reg.getLoc(), reg, idx);
572 };
573
574 SmallVector<Value, 8> opsToDelete;
575 addToWorklist(reg, term, next);
576 while (!worklist.empty()) {
577 OpBuilder::InsertionGuard guard(builder);
578 Block *block;
579 Value reg, term, next;
580 std::tie(block, reg, term, next) = worklist.front();
581 worklist.pop_front();
582
583 builder.setInsertionPointToEnd(block);
584 if (areEquivalentValues(term, next))
585 continue;
586
587 // If this is a two-state mux within the fanout from the register, we use
588 // if/else structure for proper enable inference.
589 auto mux = next.getDefiningOp<comb::MuxOp>();
590 if (mux && mux.getTwoState() &&
591 reachableMuxes->isMuxReachableFrom(firReg, mux)) {
592 if (counter >= limit) {
593 sv::PAssignOp::create(builder, term.getLoc(), reg, next);
594 continue;
595 }
597 builder, mux.getCond(),
598 [&]() { addToWorklist(reg, term, mux.getTrueValue()); },
599 [&]() { addToWorklist(reg, term, mux.getFalseValue()); });
600 ++counter;
601 continue;
602 }
603 // If the next value is an array creation, split the value into
604 // invidial elements and construct trees recursively.
605 if (auto array = next.getDefiningOp<hw::ArrayCreateOp>()) {
606 // First, try restoring subaccess assignments.
607 if (auto matchResultOpt =
608 tryRestoringSubaccess(builder, reg, term, array)) {
609 Value cond, index, trueValue;
610 std::tie(cond, index, trueValue) = *matchResultOpt;
612 builder, cond,
613 [&]() {
614 Value nextReg = getArrayIndex(reg, index);
615 // Create a value to use for equivalence checking in the
616 // recursive calls. Add the value to `opsToDelete` so that it can
617 // be deleted afterwards.
618 auto termElement =
619 hw::ArrayGetOp::create(builder, term.getLoc(), term, index);
620 opsToDelete.push_back(termElement);
621 addToWorklist(nextReg, termElement, trueValue);
622 },
623 []() {});
625 continue;
626 }
627 // Compat fix for GCC12's libstdc++, cannot use
628 // llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
629 // SmallVector<Value>
630 // reverseOpValues(llvm::reverse(array.getOperands()));
631 for (auto [idx, value] : llvm::enumerate(array.getOperands())) {
632 idx = array.getOperands().size() - idx - 1;
633 // Create an index constant.
634 auto idxVal = getOrCreateConstant(
635 array.getLoc(),
636 APInt(std::max(1u, llvm::Log2_64_Ceil(array.getOperands().size())),
637 idx));
638
639 auto &index = arrayIndexCache[{reg, idx}];
640 if (!index)
641 index = getArrayIndex(reg, idxVal);
642
643 // Create a value to use for equivalence checking in the
644 // recursive calls. Add the value to `opsToDelete` so that it can
645 // be deleted afterwards.
646 auto termElement =
647 hw::ArrayGetOp::create(builder, term.getLoc(), term, idxVal);
648 opsToDelete.push_back(termElement);
649 addToWorklist(index, termElement, value);
650 }
651 continue;
652 }
653
654 sv::PAssignOp::create(builder, term.getLoc(), reg, next);
655 }
656
657 while (!opsToDelete.empty()) {
658 auto value = opsToDelete.pop_back_val();
659 assert(value.use_empty());
660 value.getDefiningOp()->erase();
661 }
662}
663
665 Location loc = reg.getLoc();
666 Type regTy = typeConverter.convertType(reg.getType());
667
668 HierPathOp path;
669 auto lookup = pathTable.find(reg);
670 if (lookup != pathTable.end())
671 path = lookup->second;
672
673 ImplicitLocOpBuilder builder(reg.getLoc(), reg);
674 RegLowerInfo svReg{nullptr, path, reg.getPresetAttr(), nullptr, nullptr,
675 -1, 0};
676 svReg.reg = sv::RegOp::create(builder, loc, regTy, reg.getNameAttr());
677 svReg.width = hw::getBitWidth(regTy);
678
679 if (auto attr = reg->getAttrOfType<IntegerAttr>("firrtl.random_init_start"))
680 svReg.randStart = attr.getUInt();
681
682 // Don't move these over
683 reg->removeAttr("firrtl.random_init_start");
684
685 // Move Attributes
686 svReg.reg->setDialectAttrs(reg->getDialectAttrs());
687
688 if (auto innerSymAttr = reg.getInnerSymAttr())
689 svReg.reg.setInnerSymAttr(innerSymAttr);
690
691 auto regVal = sv::ReadInOutOp::create(builder, loc, svReg.reg);
692
693 if (reg.hasReset()) {
695 reg->getBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
696 [&](OpBuilder &b) {
697 // If this is an AsyncReset, ensure that we emit a self connect to
698 // avoid erroneously creating a latch construct.
699 if (reg.getIsAsync() && areEquivalentValues(reg, reg.getNext()))
700 sv::PAssignOp::create(b, reg.getLoc(), svReg.reg, reg);
701 else
702 createTree(b, svReg.reg, reg, reg.getNext());
703 },
704 reg.getIsAsync() ? sv::ResetType::AsyncReset : sv::ResetType::SyncReset,
705 sv::EventControl::AtPosEdge, reg.getReset(),
706 [&](OpBuilder &builder) {
707 sv::PAssignOp::create(builder, loc, svReg.reg, reg.getResetValue());
708 });
709 if (reg.getIsAsync()) {
710 svReg.asyncResetSignal = reg.getReset();
711 svReg.asyncResetValue = reg.getResetValue();
712 }
713 } else {
715 reg->getBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
716 [&](OpBuilder &b) { createTree(b, svReg.reg, reg, reg.getNext()); });
717 }
718
719 // Record information required later on to build the initialization code for
720 // this register. All initialization is grouped together in a single initial
721 // block at the back of the module.
722 if (svReg.preset)
723 presetInitRegs.push_back(svReg);
724 else if (!disableRegRandomization)
725 randomInitRegs.push_back(svReg);
726
727 if (svReg.asyncResetSignal)
728 asyncResets[svReg.asyncResetSignal].emplace_back(svReg);
729
730 // Remember the ifdef conditions surrounding this register, if present. We
731 // will need to place this register's initialization code under the same
732 // ifdef conditions.
733 if (!conditions.empty())
734 regConditionTable.emplace_or_assign(svReg.reg, conditions);
735
736 reg.replaceAllUsesWith(regVal.getResult());
737 reg.erase();
738}
739
740// Initialize registers by assigning each element recursively instead of
741// initializing entire registers. This is necessary as a workaround for
742// verilator which allocates many local variables for concat op.
743// NOLINTBEGIN(misc-no-recursion)
745 OpBuilder &builder, Value reg,
746 Value randomSource,
747 unsigned &pos) {
748 auto type = cast<sv::InOutType>(reg.getType()).getElementType();
749 if (auto intTy = hw::type_dyn_cast<IntegerType>(type)) {
750 // Use randomSource[pos-1:pos-width] as a random value.
751 pos -= intTy.getWidth();
752 auto elem = builder.createOrFold<comb::ExtractOp>(loc, randomSource, pos,
753 intTy.getWidth());
754 sv::BPAssignOp::create(builder, loc, reg, elem);
755 } else if (auto array = hw::type_dyn_cast<hw::ArrayType>(type)) {
756 for (unsigned i = 0, e = array.getNumElements(); i < e; ++i) {
757 auto index = getOrCreateConstant(loc, APInt(llvm::Log2_64_Ceil(e), i));
759 loc, builder, sv::ArrayIndexInOutOp::create(builder, loc, reg, index),
760 randomSource, pos);
761 }
762 } else if (auto structType = hw::type_dyn_cast<hw::StructType>(type)) {
763 for (auto e : structType.getElements())
765 loc, builder,
766 sv::StructFieldInOutOp::create(builder, loc, reg, e.name),
767 randomSource, pos);
768 } else {
769 assert(false && "unsupported type");
770 }
771}
772// NOLINTEND(misc-no-recursion)
773
775 // If there are no conditions, just return the current insertion point.
776 auto lookup = regConditionTable.find(reg);
777 if (lookup == regConditionTable.end())
778 return;
779
780 // Recreate the conditions under which the register was declared.
781 auto &conditions = lookup->second;
782 for (auto &condition : conditions) {
783 auto kind = condition.getKind();
784 if (kind == RegCondition::IfDefThen) {
785 auto ifDef = sv::IfDefProceduralOp::create(b, reg.getLoc(),
786 condition.getMacro(), []() {});
787 b.setInsertionPointToEnd(ifDef.getThenBlock());
788 continue;
789 }
790 if (kind == RegCondition::IfDefElse) {
791 auto ifDef = sv::IfDefProceduralOp::create(
792 b, reg.getLoc(), condition.getMacro(), []() {}, []() {});
793
794 b.setInsertionPointToEnd(ifDef.getElseBlock());
795 continue;
796 }
797 llvm_unreachable("unknown reg condition type");
798 }
799}
800
801static Value buildXMRTo(OpBuilder &builder, HierPathOp path, Location loc,
802 Type type) {
803 auto name = path.getSymNameAttr();
804 auto ref = mlir::FlatSymbolRefAttr::get(name);
805 return sv::XMRRefOp::create(builder, loc, type, ref);
806}
807
809 ArrayRef<Value> rands) {
810 auto loc = reg.reg.getLoc();
811 SmallVector<Value> nibbles;
812 if (reg.width == 0)
813 return;
814
815 OpBuilder::InsertionGuard guard(builder);
816
817 // If the register was defined under ifdefs, we have to guard the
818 // initialization code under the same ifdefs. The builder's insertion point
819 // will be left inside the guards.
820 buildRegConditions(builder, reg.reg);
821
822 // If the register is not located in the toplevel body of the module, we must
823 // refer to the register by (local) XMR, since the register will not dominate
824 // the initialization block.
825 Value target = reg.reg;
826 if (reg.path)
827 target = buildXMRTo(builder, reg.path, reg.reg.getLoc(), reg.reg.getType());
828
829 uint64_t width = reg.width;
830 uint64_t offset = reg.randStart;
831 while (width) {
832 auto index = offset / 32;
833 auto start = offset % 32;
834 auto nwidth = std::min(32 - start, width);
835 auto elemVal = sv::ReadInOutOp::create(builder, loc, rands[index]);
836 auto elem =
837 builder.createOrFold<comb::ExtractOp>(loc, elemVal, start, nwidth);
838 nibbles.push_back(elem);
839 offset += nwidth;
840 width -= nwidth;
841 }
842 auto concat = builder.createOrFold<comb::ConcatOp>(loc, nibbles);
843 unsigned pos = reg.width;
844 // Initialize register elements.
845 initializeRegisterElements(loc, builder, target, concat, pos);
846}
847
849 Block *block, sv::EventControl clockEdge, Value clock,
850 const std::function<void(OpBuilder &)> &body, sv::ResetType resetStyle,
851 sv::EventControl resetEdge, Value reset,
852 const std::function<void(OpBuilder &)> &resetBody) {
853 auto loc = clock.getLoc();
854 ImplicitLocOpBuilder builder(loc, block, getBlockEnd(block));
855 AlwaysKeyType key{builder.getBlock(), clockEdge, clock,
856 resetStyle, resetEdge, reset};
857
858 sv::AlwaysOp alwaysOp;
859 sv::IfOp insideIfOp;
861 std::tie(alwaysOp, insideIfOp) = alwaysBlocks[key];
862 }
863
864 if (!alwaysOp) {
865 if (reset) {
866 assert(resetStyle != sv::ResetType::NoReset);
867 // Here, we want to create the following structure with sv.always and
868 // sv.if. If `reset` is async, we need to add `reset` to a sensitivity
869 // list.
870 //
871 // sv.always @(clockEdge or reset) {
872 // sv.if (reset) {
873 // resetBody
874 // } else {
875 // body
876 // }
877 // }
878
879 auto createIfOp = [&]() {
880 // It is weird but intended. Here we want to create an empty sv.if
881 // with an else block.
882 insideIfOp = sv::IfOp::create(
883 builder, reset, []() {}, []() {});
884 };
885 if (resetStyle == sv::ResetType::AsyncReset) {
886 sv::EventControl events[] = {clockEdge, resetEdge};
887 Value clocks[] = {clock, reset};
888
889 alwaysOp = sv::AlwaysOp::create(builder, events, clocks, [&]() {
890 if (resetEdge == sv::EventControl::AtNegEdge)
891 llvm_unreachable("negative edge for reset is not expected");
892 createIfOp();
893 });
894 } else {
895 alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock, createIfOp);
896 }
897 } else {
898 assert(!resetBody);
899 alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock);
900 insideIfOp = nullptr;
901 }
902 }
903
904 if (reset) {
905 assert(insideIfOp && "reset body must be initialized before");
906 auto resetBuilder =
907 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getThenBlock());
908 resetBody(resetBuilder);
909
910 auto bodyBuilder =
911 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getElseBlock());
912 body(bodyBuilder);
913 } else {
914 auto bodyBuilder =
915 ImplicitLocOpBuilder::atBlockEnd(loc, alwaysOp.getBodyBlock());
916 body(bodyBuilder);
917 }
918
920 alwaysBlocks[key] = {alwaysOp, insideIfOp};
921 }
922}
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 InnerRefAttr getInnerRefTo(StringAttr mod, InnerSymbolNamespace &isns, seq::FirRegOp reg)
static StringAttr getInnerSymFor(InnerSymbolNamespace &innerSymNS, seq::FirRegOp reg)
Attach an inner-sym to field-id 0 of the given register, or use an existing inner-sym,...
static std::vector< BuriedFirReg > getAllBuriedRegs(ModuleOp top)
Locate all registers which are not at the top-level of their parent HW module.
static std::vector< BuriedFirReg > getBuriedRegs(HWModuleOp module)
Locate the registers under the given HW module, which are not at the top-level of the module body.
static Block::iterator getBlockEnd(Block *block)
Immediately before the terminator, if present. Otherwise, the block's end.
static Value buildXMRTo(OpBuilder &builder, HierPathOp path, Location loc, Type type)
static hw::HierPathOp getHierPathTo(OpBuilder &builder, Namespace &ns, BuriedFirReg entry)
Construct a hierarchical path op that targets the given register.
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
void createAsyncResetInitialization(ImplicitLocOpBuilder &builder)
llvm::SmallDenseMap< IfKeyType, sv::IfOp > ifCache
static PathTable createPaths(mlir::ModuleOp top)
When a register is buried under an ifdef op, the initialization code at the footer of the HW module w...
DenseMap< seq::FirRegOp, hw::HierPathOp > PathTable
A map sending registers to their paths.
void addToIfBlock(OpBuilder &builder, Value cond, const std::function< void()> &trueSide, const std::function< void()> &falseSide)
FirRegLowering(TypeConverter &typeConverter, hw::HWModuleOp module, const PathTable &pathTable, bool disableRegRandomization=false, bool emitSeparateAlwaysBlocks=false)
std::optional< std::tuple< Value, Value, Value > > tryRestoringSubaccess(OpBuilder &builder, Value reg, Value term, hw::ArrayCreateOp nextRegValue)
void createRandomInitialization(ImplicitLocOpBuilder &builder)
void lowerUnderIfDef(sv::IfDefOp ifDefOp)
void lowerInBlock(Block *block)
void buildRegConditions(OpBuilder &b, sv::RegOp reg)
Recreate the ifdefs under which reg was defined.
const PathTable & pathTable
void lowerReg(seq::FirRegOp reg)
SmallVector< Value > createRandomizationVector(OpBuilder &builder, Location loc)
std::vector< RegCondition > conditions
The ambient ifdef conditions we have encountered while lowering.
void createTree(OpBuilder &builder, Value reg, Value term, Value next)
void createPresetInitialization(ImplicitLocOpBuilder &builder)
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={})
SmallVector< RegLowerInfo > randomInitRegs
A list of registers discovered, bucketed by initialization style.
std::tuple< Block *, sv::EventControl, Value, sv::ResetType, sv::EventControl, Value > AlwaysKeyType
llvm::MapVector< Value, SmallVector< RegLowerInfo > > asyncResets
A map from async reset signal to the registers that use it.
void initializeRegisterElements(Location loc, OpBuilder &builder, Value reg, Value rand, unsigned &pos)
DenseMap< sv::RegOp, std::vector< RegCondition > > regConditionTable
A map from RegOps to the ifdef conditions under which they are defined.
TypeConverter & typeConverter
hw::HWModuleOp bool disableRegRandomization
SmallVector< RegLowerInfo > presetInitRegs
llvm::SmallDenseMap< AlwaysKeyType, std::pair< sv::AlwaysOp, sv::IfOp > > alwaysBlocks
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
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(array_value, idx)
Definition hw.py:450
create(data_type, value)
Definition hw.py:441
create(value)
Definition sv.py:106
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.
static ResultTy transformReduce(MLIRContext *context, IterTy begin, IterTy end, ResultTy init, ReduceFuncTy reduce, TransformFuncTy transform)
Wrapper for llvm::parallelTransformReduce that performs the transform_reduce serially when MLIR multi...
Definition Utils.h:40
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
@ IfDefThen
The register is under an ifdef "then" branch.
@ IfDefElse
The register is under an ifdef "else" branch.
static std::function< bool(const Operation *op)> opAllowsReachability