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
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 : pathTable(pathTable), typeConverter(typeConverter), module(module),
242 disableRegRandomization(disableRegRandomization),
243 emitSeparateAlwaysBlocks(emitSeparateAlwaysBlocks) {
244 reachableMuxes = std::make_unique<ReachableMuxes>(module);
245}
246
248 lowerInBlock(module.getBodyBlock());
250 module->removeAttr("firrtl.random_init_width");
251}
252
253// NOLINTNEXTLINE(misc-no-recursion)
255 auto cond = ifDefOp.getCond();
256
257 conditions.emplace_back(RegCondition::IfDefThen, cond);
258 lowerInBlock(ifDefOp.getThenBlock());
259 conditions.pop_back();
260
261 if (ifDefOp.hasElse()) {
262 conditions.emplace_back(RegCondition::IfDefElse, cond);
263 lowerInBlock(ifDefOp.getElseBlock());
264 conditions.pop_back();
265 }
266}
267
268// NOLINTNEXTLINE(misc-no-recursion)
270 for (auto &op : llvm::make_early_inc_range(*block)) {
271 if (auto ifDefOp = dyn_cast<sv::IfDefOp>(op)) {
272 lowerUnderIfDef(ifDefOp);
273 continue;
274 }
275 if (auto regOp = dyn_cast<seq::FirRegOp>(op)) {
276 lowerReg(regOp);
277 continue;
278 }
279 for (auto &region : op.getRegions())
280 for (auto &block : region.getBlocks())
281 lowerInBlock(&block);
282 }
283}
284
285SmallVector<Value> FirRegLowering::createRandomizationVector(OpBuilder &builder,
286 Location loc) {
287 // Compute total width of random space. Place non-chisel registers at the end
288 // of the space. The Random space is unique to the initial block, due to
289 // verilog thread rules, so we can drop trailing random calls if they are
290 // unused.
291 uint64_t maxBit = 0;
292 for (auto reg : randomInitRegs)
293 if (reg.randStart >= 0)
294 maxBit = std::max(maxBit, (uint64_t)reg.randStart + reg.width);
295
296 for (auto &reg : randomInitRegs) {
297 if (reg.randStart == -1) {
298 reg.randStart = maxBit;
299 maxBit += reg.width;
300 }
301 }
302
303 // Create randomization vector
304 SmallVector<Value> randValues;
305 auto numRandomCalls = (maxBit + 31) / 32;
306 auto logic = sv::LogicOp::create(
307 builder, loc,
308 hw::UnpackedArrayType::get(builder.getIntegerType(32), numRandomCalls),
309 "_RANDOM");
310 // Indvar's width must be equal to `ceil(log2(numRandomCalls +
311 // 1))` to avoid overflow.
312 auto inducionVariableWidth = llvm::Log2_64_Ceil(numRandomCalls + 1);
313 auto arrayIndexWith = llvm::Log2_64_Ceil(numRandomCalls);
314 auto lb = getOrCreateConstant(loc, APInt::getZero(inducionVariableWidth));
315 auto ub =
316 getOrCreateConstant(loc, APInt(inducionVariableWidth, numRandomCalls));
317 auto step = getOrCreateConstant(loc, APInt(inducionVariableWidth, 1));
318 auto forLoop = sv::ForOp::create(
319 builder, loc, lb, ub, step, "i", [&](BlockArgument iter) {
320 auto rhs = sv::MacroRefExprSEOp::create(
321 builder, loc, builder.getIntegerType(32), "RANDOM");
322 Value iterValue = iter;
323 if (!iter.getType().isInteger(arrayIndexWith))
324 iterValue = comb::ExtractOp::create(builder, loc, iterValue, 0,
325 arrayIndexWith);
326 auto lhs =
327 sv::ArrayIndexInOutOp::create(builder, loc, logic, iterValue);
328 sv::BPAssignOp::create(builder, loc, lhs, rhs);
329 });
330 builder.setInsertionPointAfter(forLoop);
331 for (uint64_t x = 0; x < numRandomCalls; ++x) {
332 auto lhs = sv::ArrayIndexInOutOp::create(
333 builder, loc, logic,
334 getOrCreateConstant(loc, APInt(arrayIndexWith, x)));
335 randValues.push_back(lhs.getResult());
336 }
337
338 return randValues;
339}
340
341void FirRegLowering::createRandomInitialization(ImplicitLocOpBuilder &builder) {
342 auto randInitRef =
343 sv::MacroIdentAttr::get(builder.getContext(), "RANDOMIZE_REG_INIT");
344
345 if (!randomInitRegs.empty()) {
346 sv::IfDefProceduralOp::create(builder, "INIT_RANDOM_PROLOG_", [&] {
347 sv::VerbatimOp::create(builder, "`INIT_RANDOM_PROLOG_");
348 });
349
350 sv::IfDefProceduralOp::create(builder, randInitRef, [&] {
351 auto randValues = createRandomizationVector(builder, builder.getLoc());
352 for (auto &svReg : randomInitRegs)
353 initialize(builder, svReg, randValues);
354 });
355 }
356}
357
358void FirRegLowering::createPresetInitialization(ImplicitLocOpBuilder &builder) {
359 for (auto &svReg : presetInitRegs) {
360 OpBuilder::InsertionGuard guard(builder);
361
362 auto loc = svReg.reg.getLoc();
363 auto elemTy = svReg.reg.getType().getElementType();
364 auto cst = getOrCreateConstant(loc, svReg.preset.getValue());
365
366 Value rhs;
367 if (cst.getType() == elemTy)
368 rhs = cst;
369 else
370 rhs = hw::BitcastOp::create(builder, loc, elemTy, cst);
371
372 buildRegConditions(builder, svReg.reg);
373 Value target = svReg.reg;
374 if (svReg.path)
375 target = buildXMRTo(builder, svReg.path, svReg.reg.getLoc(),
376 svReg.reg.getType());
377
378 sv::BPAssignOp::create(builder, loc, target, rhs);
379 }
380}
381
382// If a register is async reset, we need to insert extra initialization in
383// post-randomization so that we can set the reset value to register if the
384// reset signal is enabled.
386 ImplicitLocOpBuilder &builder) {
387 for (auto &reset : asyncResets) {
388 OpBuilder::InsertionGuard guard(builder);
389
390 // if (reset) begin
391 // ..
392 // end
393 sv::IfOp::create(builder, reset.first, [&]() {
394 for (auto &reg : reset.second) {
395 OpBuilder::InsertionGuard guard(builder);
396 buildRegConditions(builder, reg.reg);
397 Value target = reg.reg;
398 if (reg.path)
399 target = buildXMRTo(builder, reg.path, reg.reg.getLoc(),
400 reg.reg.getType());
401 sv::BPAssignOp::create(builder, reg.reg.getLoc(), target,
402 reg.asyncResetValue);
403 }
404 });
405 }
406}
407
409 // Create an initial block at the end of the module where random
410 // initialisation will be inserted. Create two builders into the two
411 // `ifdef` ops where the registers will be placed.
412 //
413 // `ifndef SYNTHESIS
414 // `ifdef RANDOMIZE_REG_INIT
415 // ... regBuilder ...
416 // `endif
417 // initial
418 // `INIT_RANDOM_PROLOG_
419 // ... initBuilder ..
420 // `endif
421 if (randomInitRegs.empty() && presetInitRegs.empty() && asyncResets.empty())
422 return;
423
424 needsRandom = true;
425
426 auto loc = module.getLoc();
427 auto builder =
428 ImplicitLocOpBuilder::atBlockTerminator(loc, module.getBodyBlock());
429
430 sv::IfDefOp::create(builder, "ENABLE_INITIAL_REG_", [&] {
431 sv::OrderedOutputOp::create(builder, [&] {
432 sv::IfDefOp::create(builder, "FIRRTL_BEFORE_INITIAL", [&] {
433 sv::VerbatimOp::create(builder, "`FIRRTL_BEFORE_INITIAL");
434 });
435
436 sv::InitialOp::create(builder, [&] {
440 });
441
442 sv::IfDefOp::create(builder, "FIRRTL_AFTER_INITIAL", [&] {
443 sv::VerbatimOp::create(builder, "`FIRRTL_AFTER_INITIAL");
444 });
445 });
446 });
447}
448
449// Return true if two arguments are equivalent, or if both of them are the same
450// array indexing.
451// NOLINTNEXTLINE(misc-no-recursion)
452static bool areEquivalentValues(Value term, Value next) {
453 if (term == next)
454 return true;
455 // Check whether these values are equivalent array accesses with constant
456 // index. We have to check the equivalence recursively because they might not
457 // be CSEd.
458 if (auto t1 = term.getDefiningOp<hw::ArrayGetOp>())
459 if (auto t2 = next.getDefiningOp<hw::ArrayGetOp>())
460 if (auto c1 = t1.getIndex().getDefiningOp<hw::ConstantOp>())
461 if (auto c2 = t2.getIndex().getDefiningOp<hw::ConstantOp>())
462 return c1.getType() == c2.getType() &&
463 c1.getValue() == c2.getValue() &&
464 areEquivalentValues(t1.getInput(), t2.getInput());
465 // Otherwise, regard as different.
466 // TODO: Handle struct if necessary.
467 return false;
468}
469
470static llvm::SetVector<Value> extractConditions(Value value) {
471 auto andOp = value.getDefiningOp<comb::AndOp>();
472 // If the value is not AndOp with a bin flag, use it as a condition.
473 if (!andOp || !andOp.getTwoState()) {
474 llvm::SetVector<Value> ret;
475 ret.insert(value);
476 return ret;
477 }
478
479 return llvm::SetVector<Value>(andOp.getOperands().begin(),
480 andOp.getOperands().end());
481}
482
483static std::optional<APInt> getConstantValue(Value value) {
484 auto constantIndex = value.template getDefiningOp<hw::ConstantOp>();
485 if (constantIndex)
486 return constantIndex.getValue();
487 return {};
488}
489
490// Return a tuple <cond, idx, val> if the array register update can be
491// represented with a dynamic index assignment:
492// if (cond)
493// reg[idx] <= val;
494//
495std::optional<std::tuple<Value, Value, Value>>
496FirRegLowering::tryRestoringSubaccess(OpBuilder &builder, Value reg, Value term,
497 hw::ArrayCreateOp nextRegValue) {
498 Value trueVal;
499 SmallVector<Value> muxConditions;
500 // Compat fix for GCC12's libstdc++, cannot use
501 // llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
502 SmallVector<Value> reverseOpValues(llvm::reverse(nextRegValue.getOperands()));
503 if (!llvm::all_of(llvm::enumerate(reverseOpValues), [&](auto idxAndValue) {
504 // Check that `nextRegValue[i]` is `cond_i ? val : reg[i]`.
505 auto [i, value] = idxAndValue;
506 auto mux = value.template getDefiningOp<comb::MuxOp>();
507 // Ensure that mux has binary flag.
508 if (!mux || !mux.getTwoState())
509 return false;
510 // The next value must be same.
511 if (trueVal && trueVal != mux.getTrueValue())
512 return false;
513 if (!trueVal)
514 trueVal = mux.getTrueValue();
515 muxConditions.push_back(mux.getCond());
516 // Check that ith element is an element of the register we are
517 // currently lowering.
518 auto arrayGet =
519 mux.getFalseValue().template getDefiningOp<hw::ArrayGetOp>();
520 if (!arrayGet)
521 return false;
522 return areEquivalentValues(arrayGet.getInput(), term) &&
523 getConstantValue(arrayGet.getIndex()) == i;
524 }))
525 return {};
526
527 // Extract common expressions among mux conditions.
528 llvm::SetVector<Value> commonConditions =
529 extractConditions(muxConditions.front());
530 for (auto condition : ArrayRef(muxConditions).drop_front()) {
531 auto cond = extractConditions(condition);
532 commonConditions.remove_if([&](auto v) { return !cond.contains(v); });
533 }
534 Value indexValue;
535 for (auto [idx, condition] : llvm::enumerate(muxConditions)) {
536 llvm::SetVector<Value> extractedConditions = extractConditions(condition);
537 // Remove common conditions and check the remaining condition is only an
538 // index comparision.
539 extractedConditions.remove_if(
540 [&](auto v) { return commonConditions.contains(v); });
541 if (extractedConditions.size() != 1)
542 return {};
543
544 auto indexCompare =
545 (*extractedConditions.begin()).getDefiningOp<comb::ICmpOp>();
546 if (!indexCompare || !indexCompare.getTwoState() ||
547 indexCompare.getPredicate() != comb::ICmpPredicate::eq)
548 return {};
549 // `IndexValue` must be same.
550 if (indexValue && indexValue != indexCompare.getLhs())
551 return {};
552 if (!indexValue)
553 indexValue = indexCompare.getLhs();
554 if (getConstantValue(indexCompare.getRhs()) != idx)
555 return {};
556 }
557
558 OpBuilder::InsertionGuard guard(builder);
559 builder.setInsertionPointAfterValue(reg);
560 Value commonConditionValue;
561 if (commonConditions.empty())
562 commonConditionValue = getOrCreateConstant(reg.getLoc(), APInt(1, 1));
563 else
564 commonConditionValue = builder.createOrFold<comb::AndOp>(
565 reg.getLoc(), builder.getI1Type(), commonConditions.takeVector(), true);
566 return std::make_tuple(commonConditionValue, indexValue, trueVal);
567}
568
569void FirRegLowering::createTree(OpBuilder &builder, Value reg, Value term,
570 Value next) {
571 // If-then-else tree limit.
572 constexpr size_t limit = 1024;
573
574 // Count of emitted if-then-else ops.
575 size_t counter = 0;
576
577 // Get the fanout from this register before we build the tree. While we are
578 // creating the tree of if/else statements from muxes, we only want to turn
579 // muxes that are on the register's fanout into if/else statements. This is
580 // required to get the correct enable inference. But other muxes in the tree
581 // should be left as ternary operators. This is desirable because we don't
582 // want to create if/else structure for logic unrelated to the register's
583 // enable.
584 auto firReg = term.getDefiningOp<seq::FirRegOp>();
585
586 std::deque<std::tuple<Block *, Value, Value, Value>> worklist;
587 auto addToWorklist = [&](Value reg, Value term, Value next) {
588 worklist.emplace_back(builder.getBlock(), reg, term, next);
589 };
590
591 auto getArrayIndex = [&](Value reg, Value idx) {
592 // Create an array index op just after `reg`.
593 OpBuilder::InsertionGuard guard(builder);
594 builder.setInsertionPointAfterValue(reg);
595 return sv::ArrayIndexInOutOp::create(builder, reg.getLoc(), reg, idx);
596 };
597
598 SmallVector<Value, 8> opsToDelete;
599 addToWorklist(reg, term, next);
600 while (!worklist.empty()) {
601 OpBuilder::InsertionGuard guard(builder);
602 Block *block;
603 Value reg, term, next;
604 std::tie(block, reg, term, next) = worklist.front();
605 worklist.pop_front();
606
607 builder.setInsertionPointToEnd(block);
608 if (areEquivalentValues(term, next))
609 continue;
610
611 // If this is a two-state mux within the fanout from the register, we use
612 // if/else structure for proper enable inference.
613 auto mux = next.getDefiningOp<comb::MuxOp>();
614 if (mux && mux.getTwoState() &&
615 reachableMuxes->isMuxReachableFrom(firReg, mux)) {
616 if (counter >= limit) {
617 sv::PAssignOp::create(builder, term.getLoc(), reg, next);
618 continue;
619 }
621 builder, mux.getCond(),
622 [&]() { addToWorklist(reg, term, mux.getTrueValue()); },
623 [&]() { addToWorklist(reg, term, mux.getFalseValue()); });
624 ++counter;
625 continue;
626 }
627 // If the next value is an array creation, split the value into
628 // invidial elements and construct trees recursively.
629 if (auto array = next.getDefiningOp<hw::ArrayCreateOp>()) {
630 // First, try restoring subaccess assignments.
631 if (auto matchResultOpt =
632 tryRestoringSubaccess(builder, reg, term, array)) {
633 Value cond, index, trueValue;
634 std::tie(cond, index, trueValue) = *matchResultOpt;
636 builder, cond,
637 [&]() {
638 Value nextReg = getArrayIndex(reg, index);
639 // Create a value to use for equivalence checking in the
640 // recursive calls. Add the value to `opsToDelete` so that it can
641 // be deleted afterwards.
642 auto termElement =
643 hw::ArrayGetOp::create(builder, term.getLoc(), term, index);
644 opsToDelete.push_back(termElement);
645 addToWorklist(nextReg, termElement, trueValue);
646 },
647 []() {});
649 continue;
650 }
651 // Compat fix for GCC12's libstdc++, cannot use
652 // llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
653 // SmallVector<Value>
654 // reverseOpValues(llvm::reverse(array.getOperands()));
655 for (auto [idx, value] : llvm::enumerate(array.getOperands())) {
656 idx = array.getOperands().size() - idx - 1;
657 // Create an index constant.
658 auto idxVal = getOrCreateConstant(
659 array.getLoc(),
660 APInt(std::max(1u, llvm::Log2_64_Ceil(array.getOperands().size())),
661 idx));
662
663 auto &index = arrayIndexCache[{reg, idx}];
664 if (!index)
665 index = getArrayIndex(reg, idxVal);
666
667 // Create a value to use for equivalence checking in the
668 // recursive calls. Add the value to `opsToDelete` so that it can
669 // be deleted afterwards.
670 auto termElement =
671 hw::ArrayGetOp::create(builder, term.getLoc(), term, idxVal);
672 opsToDelete.push_back(termElement);
673 addToWorklist(index, termElement, value);
674 }
675 continue;
676 }
677
678 sv::PAssignOp::create(builder, term.getLoc(), reg, next);
679 }
680
681 while (!opsToDelete.empty()) {
682 auto value = opsToDelete.pop_back_val();
683 assert(value.use_empty());
684 value.getDefiningOp()->erase();
685 }
686}
687
689 Location loc = reg.getLoc();
690 Type regTy = typeConverter.convertType(reg.getType());
691
692 HierPathOp path;
693 auto lookup = pathTable.find(reg);
694 if (lookup != pathTable.end())
695 path = lookup->second;
696
697 ImplicitLocOpBuilder builder(reg.getLoc(), reg);
698 RegLowerInfo svReg{nullptr, path, reg.getPresetAttr(), nullptr, nullptr,
699 -1, 0};
700 svReg.reg = sv::RegOp::create(builder, loc, regTy, reg.getNameAttr());
701 svReg.width = hw::getBitWidth(regTy);
702
703 if (auto attr = reg->getAttrOfType<IntegerAttr>("firrtl.random_init_start"))
704 svReg.randStart = attr.getUInt();
705
706 // Don't move these over
707 reg->removeAttr("firrtl.random_init_start");
708
709 // Move Attributes
710 svReg.reg->setDialectAttrs(reg->getDialectAttrs());
711
712 if (auto innerSymAttr = reg.getInnerSymAttr())
713 svReg.reg.setInnerSymAttr(innerSymAttr);
714
715 auto regVal = sv::ReadInOutOp::create(builder, loc, svReg.reg);
716
717 if (reg.hasReset()) {
719 reg->getBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
720 [&](OpBuilder &b) {
721 // If this is an AsyncReset, ensure that we emit a self connect to
722 // avoid erroneously creating a latch construct.
723 if (reg.getIsAsync() && areEquivalentValues(reg, reg.getNext()))
724 sv::PAssignOp::create(b, reg.getLoc(), svReg.reg, reg);
725 else
726 createTree(b, svReg.reg, reg, reg.getNext());
727 },
728 reg.getIsAsync() ? sv::ResetType::AsyncReset : sv::ResetType::SyncReset,
729 sv::EventControl::AtPosEdge, reg.getReset(),
730 [&](OpBuilder &builder) {
731 sv::PAssignOp::create(builder, loc, svReg.reg, reg.getResetValue());
732 });
733 if (reg.getIsAsync()) {
734 svReg.asyncResetSignal = reg.getReset();
735 svReg.asyncResetValue = reg.getResetValue();
736 }
737 } else {
739 reg->getBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
740 [&](OpBuilder &b) { createTree(b, svReg.reg, reg, reg.getNext()); });
741 }
742
743 // Record information required later on to build the initialization code for
744 // this register. All initialization is grouped together in a single initial
745 // block at the back of the module.
746 if (svReg.preset)
747 presetInitRegs.push_back(svReg);
748 else if (!disableRegRandomization)
749 randomInitRegs.push_back(svReg);
750
751 if (svReg.asyncResetSignal)
752 asyncResets[svReg.asyncResetSignal].emplace_back(svReg);
753
754 // Remember the ifdef conditions surrounding this register, if present. We
755 // will need to place this register's initialization code under the same
756 // ifdef conditions.
757 if (!conditions.empty())
758 regConditionTable.emplace_or_assign(svReg.reg, conditions);
759
760 reg.replaceAllUsesWith(regVal.getResult());
761 reg.erase();
762}
763
764// Initialize registers by assigning each element recursively instead of
765// initializing entire registers. This is necessary as a workaround for
766// verilator which allocates many local variables for concat op.
767// NOLINTBEGIN(misc-no-recursion)
769 OpBuilder &builder, Value reg,
770 Value randomSource,
771 unsigned &pos) {
772 auto type = cast<sv::InOutType>(reg.getType()).getElementType();
773 if (auto intTy = hw::type_dyn_cast<IntegerType>(type)) {
774 // Use randomSource[pos-1:pos-width] as a random value.
775 pos -= intTy.getWidth();
776 auto elem = builder.createOrFold<comb::ExtractOp>(loc, randomSource, pos,
777 intTy.getWidth());
778 sv::BPAssignOp::create(builder, loc, reg, elem);
779 } else if (auto array = hw::type_dyn_cast<hw::ArrayType>(type)) {
780 for (unsigned i = 0, e = array.getNumElements(); i < e; ++i) {
781 auto index = getOrCreateConstant(loc, APInt(llvm::Log2_64_Ceil(e), i));
783 loc, builder, sv::ArrayIndexInOutOp::create(builder, loc, reg, index),
784 randomSource, pos);
785 }
786 } else if (auto structType = hw::type_dyn_cast<hw::StructType>(type)) {
787 for (auto e : structType.getElements())
789 loc, builder,
790 sv::StructFieldInOutOp::create(builder, loc, reg, e.name),
791 randomSource, pos);
792 } else {
793 assert(false && "unsupported type");
794 }
795}
796// NOLINTEND(misc-no-recursion)
797
799 // If there are no conditions, just return the current insertion point.
800 auto lookup = regConditionTable.find(reg);
801 if (lookup == regConditionTable.end())
802 return;
803
804 // Recreate the conditions under which the register was declared.
805 auto &conditions = lookup->second;
806 for (auto &condition : conditions) {
807 auto kind = condition.getKind();
808 if (kind == RegCondition::IfDefThen) {
809 auto ifDef = sv::IfDefProceduralOp::create(b, reg.getLoc(),
810 condition.getMacro(), []() {});
811 b.setInsertionPointToEnd(ifDef.getThenBlock());
812 continue;
813 }
814 if (kind == RegCondition::IfDefElse) {
815 auto ifDef = sv::IfDefProceduralOp::create(
816 b, reg.getLoc(), condition.getMacro(), []() {}, []() {});
817
818 b.setInsertionPointToEnd(ifDef.getElseBlock());
819 continue;
820 }
821 llvm_unreachable("unknown reg condition type");
822 }
823}
824
826 ArrayRef<Value> rands) {
827 auto loc = reg.reg.getLoc();
828 SmallVector<Value> nibbles;
829 if (reg.width == 0)
830 return;
831
832 OpBuilder::InsertionGuard guard(builder);
833
834 // If the register was defined under ifdefs, we have to guard the
835 // initialization code under the same ifdefs. The builder's insertion point
836 // will be left inside the guards.
837 buildRegConditions(builder, reg.reg);
838
839 // If the register is not located in the toplevel body of the module, we must
840 // refer to the register by (local) XMR, since the register will not dominate
841 // the initialization block.
842 Value target = reg.reg;
843 if (reg.path)
844 target = buildXMRTo(builder, reg.path, reg.reg.getLoc(), reg.reg.getType());
845
846 uint64_t width = reg.width;
847 uint64_t offset = reg.randStart;
848 while (width) {
849 auto index = offset / 32;
850 auto start = offset % 32;
851 auto nwidth = std::min(32 - start, width);
852 auto elemVal = sv::ReadInOutOp::create(builder, loc, rands[index]);
853 auto elem =
854 builder.createOrFold<comb::ExtractOp>(loc, elemVal, start, nwidth);
855 nibbles.push_back(elem);
856 offset += nwidth;
857 width -= nwidth;
858 }
859 auto concat = builder.createOrFold<comb::ConcatOp>(loc, nibbles);
860 unsigned pos = reg.width;
861 // Initialize register elements.
862 initializeRegisterElements(loc, builder, target, concat, pos);
863}
864
866 Block *block, sv::EventControl clockEdge, Value clock,
867 const std::function<void(OpBuilder &)> &body, sv::ResetType resetStyle,
868 sv::EventControl resetEdge, Value reset,
869 const std::function<void(OpBuilder &)> &resetBody) {
870 auto loc = clock.getLoc();
871 ImplicitLocOpBuilder builder(loc, block, getBlockEnd(block));
872 AlwaysKeyType key{builder.getBlock(), clockEdge, clock,
873 resetStyle, resetEdge, reset};
874
875 sv::AlwaysOp alwaysOp;
876 sv::IfOp insideIfOp;
878 std::tie(alwaysOp, insideIfOp) = alwaysBlocks[key];
879 }
880
881 if (!alwaysOp) {
882 if (reset) {
883 assert(resetStyle != sv::ResetType::NoReset);
884 // Here, we want to create the following structure with sv.always and
885 // sv.if. If `reset` is async, we need to add `reset` to a sensitivity
886 // list.
887 //
888 // sv.always @(clockEdge or reset) {
889 // sv.if (reset) {
890 // resetBody
891 // } else {
892 // body
893 // }
894 // }
895
896 auto createIfOp = [&]() {
897 // It is weird but intended. Here we want to create an empty sv.if
898 // with an else block.
899 insideIfOp = sv::IfOp::create(
900 builder, reset, []() {}, []() {});
901 };
902 if (resetStyle == sv::ResetType::AsyncReset) {
903 sv::EventControl events[] = {clockEdge, resetEdge};
904 Value clocks[] = {clock, reset};
905
906 alwaysOp = sv::AlwaysOp::create(builder, events, clocks, [&]() {
907 if (resetEdge == sv::EventControl::AtNegEdge)
908 llvm_unreachable("negative edge for reset is not expected");
909 createIfOp();
910 });
911 } else {
912 alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock, createIfOp);
913 }
914 } else {
915 assert(!resetBody);
916 alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock);
917 insideIfOp = nullptr;
918 }
919 }
920
921 if (reset) {
922 assert(insideIfOp && "reset body must be initialized before");
923 auto resetBuilder =
924 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getThenBlock());
925 resetBody(resetBuilder);
926
927 auto bodyBuilder =
928 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getElseBlock());
929 body(bodyBuilder);
930 } else {
931 auto bodyBuilder =
932 ImplicitLocOpBuilder::atBlockEnd(loc, alwaysOp.getBodyBlock());
933 body(bodyBuilder);
934 }
935
937 alwaysBlocks[key] = {alwaysOp, insideIfOp};
938 }
939}
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