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