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