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