12#include "mlir/IR/Threading.h"
13#include "mlir/Transforms/DialectConversion.h"
14#include "llvm/ADT/DenseSet.h"
15#include "llvm/Support/Debug.h"
24#define DEBUG_TYPE "lower-seq-firreg"
26static Value
buildXMRTo(OpBuilder &builder, HierPathOp path, Location loc,
28 auto name = path.getSymNameAttr();
29 auto ref = mlir::FlatSymbolRefAttr::get(name);
30 return sv::XMRRefOp::create(builder, loc, type, ref);
35 if (block->mightHaveTerminator())
36 return Block::iterator(block->getTerminator());
41 [](
const Operation *op) ->
bool {
42 return (isa<comb::MuxOp, ArrayGetOp, ArrayCreateOp>(op));
47 return llvm::any_of(regOp.getResult().getUsers(), [&](Operation *user) {
48 if (!OpUserInfo::opAllowsReachability(user))
50 buildReachabilityFrom(user);
51 return reachableMuxes[user].contains(muxOp);
63 if (
visited.contains(startNode))
68 llvm::SmallVector<OpUserInfo, 16> stk;
70 stk.emplace_back(startNode);
72 while (!stk.empty()) {
73 auto &info = stk.back();
74 Operation *currentNode = info.op;
77 if (info.getAndSetUnvisited())
80 if (info.userIter != info.userEnd) {
81 Operation *child = *info.userIter;
84 stk.emplace_back(child);
90 for (
auto *childOp : llvm::make_filter_range(
99 iter->getSecond().end());
107 const std::function<
void()> &trueSide,
108 const std::function<
void()> &falseSide) {
109 auto op =
ifCache.lookup({builder.getBlock(), cond});
114 sv::IfOp::create(builder, cond.getLoc(), cond, trueSide, falseSide);
115 ifCache.insert({{builder.getBlock(), cond}, newIfOp});
117 OpBuilder::InsertionGuard guard(builder);
118 builder.setInsertionPointToEnd(op.getThenBlock());
120 builder.setInsertionPointToEnd(op.getElseBlock());
129 auto attr =
reg.getInnerSymAttr();
134 if (
auto sym = attr.getSymIfExists(0))
140 auto hint =
reg.getName();
144 auto property = hw::InnerSymPropertiesAttr::get(sym);
148 SmallVector<hw::InnerSymPropertiesAttr> properties = {
property};
150 llvm::append_range(properties, attr.getProps());
153 attr = hw::InnerSymAttr::get(
context, properties);
154 reg.setInnerSymAttr(attr);
163 return hw::InnerRefAttr::get(mod, tgt);
179 auto name = SymbolTable::getSymbolName(module);
181 std::vector<BuriedFirReg> result;
182 for (
auto &op : *
module.getBodyBlock()) {
183 for (auto ®ion : op.getRegions()) {
184 region.walk([&](FirRegOp reg) {
185 auto ref = getInnerRefTo(name, isns, reg);
186 result.push_back({
reg, ref});
198 auto *
context = top.getContext();
199 std::vector<BuriedFirReg> init;
201 const std::vector<HWModuleOp> modules(ms.begin(), ms.end());
203 [](std::vector<BuriedFirReg> acc,
204 std::vector<BuriedFirReg> &&xs) -> std::vector<BuriedFirReg> {
205 acc.insert(acc.end(), xs.begin(), xs.end());
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);
219 OpBuilder::InsertionGuard guard(builder);
220 builder.setInsertionPoint(entry.reg->getParentOfType<
HWModuleOp>());
222 auto path = builder.getArrayAttr({entry.ref});
223 return hw::HierPathOp::create(builder, entry.reg.getLoc(), name, path);
227 auto builder = OpBuilder::atBlockBegin(top.getBody());
239 bool disableRegRandomization,
240 bool emitSeparateAlwaysBlocks,
241 bool emitPresetAsInlineInit)
242 : pathTable(pathTable), typeConverter(typeConverter), module(module),
243 disableRegRandomization(disableRegRandomization),
244 emitSeparateAlwaysBlocks(emitSeparateAlwaysBlocks),
245 emitPresetAsInlineInit(emitPresetAsInlineInit) {
252 module->removeAttr("firrtl.random_init_width");
257 auto cond = ifDefOp.getCond();
263 if (ifDefOp.hasElse()) {
272 for (
auto &op : llvm::make_early_inc_range(*block)) {
273 if (
auto ifDefOp = dyn_cast<sv::IfDefOp>(op)) {
277 if (
auto regOp = dyn_cast<seq::FirRegOp>(op)) {
281 for (
auto ®ion : op.getRegions())
282 for (
auto &block : region.getBlocks())
295 if (
reg.randStart >= 0)
296 maxBit = std::max(maxBit, (uint64_t)
reg.randStart +
reg.width);
299 if (
reg.randStart == -1) {
300 reg.randStart = maxBit;
306 SmallVector<Value> randValues;
307 auto numRandomCalls = (maxBit + 31) / 32;
308 auto logic = sv::LogicOp::create(
310 hw::UnpackedArrayType::get(builder.getIntegerType(32), numRandomCalls),
314 auto inducionVariableWidth = llvm::Log2_64_Ceil(numRandomCalls + 1);
315 auto arrayIndexWith = llvm::Log2_64_Ceil(numRandomCalls);
320 auto forLoop = sv::ForOp::create(
321 builder, loc, lb, ub, step,
"i", [&](BlockArgument iter) {
322 auto rhs = sv::MacroRefExprSEOp::create(
323 builder, loc, builder.getIntegerType(32),
"RANDOM");
324 Value iterValue = iter;
325 if (!iter.getType().isInteger(arrayIndexWith))
329 sv::ArrayIndexInOutOp::create(builder, loc, logic, iterValue);
330 sv::BPAssignOp::create(builder, loc, lhs, rhs);
332 builder.setInsertionPointAfter(forLoop);
333 for (uint64_t x = 0; x < numRandomCalls; ++x) {
334 auto lhs = sv::ArrayIndexInOutOp::create(
337 randValues.push_back(lhs.getResult());
345 sv::MacroIdentAttr::get(builder.getContext(),
"RANDOMIZE_REG_INIT");
348 sv::IfDefProceduralOp::create(builder,
"INIT_RANDOM_PROLOG_", [&] {
349 sv::VerbatimOp::create(builder,
"`INIT_RANDOM_PROLOG_");
352 sv::IfDefProceduralOp::create(builder, randInitRef, [&] {
362 OpBuilder::InsertionGuard guard(builder);
364 auto loc = svReg.reg.getLoc();
365 auto elemTy = svReg.reg.getType().getElementType();
369 if (cst.getType() == elemTy)
375 Value target = svReg.reg;
377 target =
buildXMRTo(builder, svReg.path, svReg.reg.getLoc(),
378 svReg.reg.getType());
380 sv::BPAssignOp::create(builder, loc, target, rhs);
388 ImplicitLocOpBuilder &builder) {
390 OpBuilder::InsertionGuard guard(builder);
395 sv::IfOp::create(builder, reset.first, [&]() {
396 for (auto ® : reset.second) {
397 OpBuilder::InsertionGuard guard(builder);
398 buildRegConditions(builder, reg.reg);
399 Value target = reg.reg;
401 target = buildXMRTo(builder, reg.path, reg.reg.getLoc(),
403 sv::BPAssignOp::create(builder, reg.reg.getLoc(), target,
404 reg.asyncResetValue);
428 auto loc =
module.getLoc();
430 ImplicitLocOpBuilder::atBlockTerminator(loc, module.getBodyBlock());
432 sv::IfDefOp::create(builder,
"ENABLE_INITIAL_REG_", [&] {
433 sv::OrderedOutputOp::create(builder, [&] {
434 sv::IfDefOp::create(builder,
"FIRRTL_BEFORE_INITIAL", [&] {
435 sv::VerbatimOp::create(builder,
"`FIRRTL_BEFORE_INITIAL");
438 sv::InitialOp::create(builder, [&] {
444 sv::IfDefOp::create(builder,
"FIRRTL_AFTER_INITIAL", [&] {
445 sv::VerbatimOp::create(builder,
"`FIRRTL_AFTER_INITIAL");
464 return c1.getType() == c2.getType() &&
465 c1.getValue() == c2.getValue() &&
475 if (!andOp || !andOp.getTwoState()) {
476 llvm::SetVector<Value> ret;
481 return llvm::SetVector<Value>(andOp.getOperands().begin(),
482 andOp.getOperands().end());
486 auto constantIndex = value.template getDefiningOp<hw::ConstantOp>();
488 return constantIndex.getValue();
497std::optional<std::tuple<Value, Value, Value>>
501 SmallVector<Value> muxConditions;
504 SmallVector<Value> reverseOpValues(llvm::reverse(nextRegValue.getOperands()));
505 if (!llvm::all_of(llvm::enumerate(reverseOpValues), [&](
auto idxAndValue) {
507 auto [i, value] = idxAndValue;
508 auto mux = value.template getDefiningOp<comb::MuxOp>();
510 if (!mux || !mux.getTwoState())
513 if (trueVal && trueVal != mux.getTrueValue())
516 trueVal = mux.getTrueValue();
517 muxConditions.push_back(mux.getCond());
521 mux.getFalseValue().template getDefiningOp<hw::ArrayGetOp>();
530 llvm::SetVector<Value> commonConditions =
532 for (
auto condition : ArrayRef(muxConditions).drop_front()) {
534 commonConditions.remove_if([&](
auto v) {
return !cond.contains(v); });
537 for (
auto [idx, condition] : llvm::enumerate(muxConditions)) {
541 extractedConditions.remove_if(
542 [&](
auto v) {
return commonConditions.contains(v); });
543 if (extractedConditions.size() != 1)
547 (*extractedConditions.begin()).getDefiningOp<comb::ICmpOp>();
548 if (!indexCompare || !indexCompare.getTwoState() ||
549 indexCompare.getPredicate() != comb::ICmpPredicate::eq)
552 if (indexValue && indexValue != indexCompare.getLhs())
555 indexValue = indexCompare.getLhs();
560 OpBuilder::InsertionGuard guard(builder);
561 builder.setInsertionPointAfterValue(
reg);
562 Value commonConditionValue;
563 if (commonConditions.empty())
566 commonConditionValue = builder.createOrFold<
comb::AndOp>(
567 reg.getLoc(), builder.getI1Type(), commonConditions.takeVector(),
true);
568 return std::make_tuple(commonConditionValue, indexValue, trueVal);
574 constexpr size_t limit = 1024;
586 auto firReg = term.getDefiningOp<seq::FirRegOp>();
588 std::deque<std::tuple<Block *, Value, Value, Value>> worklist;
589 auto addToWorklist = [&](Value
reg, Value term, Value next) {
590 worklist.emplace_back(builder.getBlock(),
reg, term, next);
593 auto getArrayIndex = [&](Value
reg, Value idx) {
595 OpBuilder::InsertionGuard guard(builder);
596 builder.setInsertionPointAfterValue(
reg);
597 return sv::ArrayIndexInOutOp::create(builder,
reg.getLoc(),
reg, idx);
600 SmallVector<Value, 8> opsToDelete;
601 addToWorklist(
reg, term, next);
602 while (!worklist.empty()) {
603 OpBuilder::InsertionGuard guard(builder);
605 Value
reg, term, next;
606 std::tie(block,
reg, term, next) = worklist.front();
607 worklist.pop_front();
609 builder.setInsertionPointToEnd(block);
616 if (mux && mux.getTwoState() &&
618 if (counter >= limit) {
619 sv::PAssignOp::create(builder, term.getLoc(),
reg, next);
623 builder, mux.getCond(),
624 [&]() { addToWorklist(reg, term, mux.getTrueValue()); },
625 [&]() { addToWorklist(reg, term, mux.getFalseValue()); });
633 if (
auto matchResultOpt =
635 Value cond, index, trueValue;
636 std::tie(cond, index, trueValue) = *matchResultOpt;
640 Value nextReg = getArrayIndex(
reg, index);
646 opsToDelete.push_back(termElement);
647 addToWorklist(nextReg, termElement, trueValue);
657 for (
auto [idx, value] : llvm::enumerate(array.getOperands())) {
658 idx = array.getOperands().size() - idx - 1;
662 APInt(std::max(1u, llvm::Log2_64_Ceil(array.getOperands().size())),
667 index = getArrayIndex(
reg, idxVal);
674 opsToDelete.push_back(termElement);
675 addToWorklist(index, termElement, value);
680 sv::PAssignOp::create(builder, term.getLoc(),
reg, next);
683 while (!opsToDelete.empty()) {
684 auto value = opsToDelete.pop_back_val();
685 assert(value.use_empty());
686 value.getDefiningOp()->erase();
691 Location loc =
reg.getLoc();
697 path = lookup->second;
699 ImplicitLocOpBuilder builder(
reg.getLoc(),
reg);
700 RegLowerInfo svReg{
nullptr, path,
reg.getPresetAttr(),
nullptr,
nullptr,
709 OpBuilder::InsertionGuard guard(builder);
710 builder.setInsertionPoint(
reg);
712 if (cst.getType() == regTy)
718 svReg.reg = sv::RegOp::create(builder, loc, regTy,
reg.getNameAttr(),
719 hw::InnerSymAttr(), initValue);
722 if (
auto attr =
reg->getAttrOfType<IntegerAttr>(
"firrtl.random_init_start"))
723 svReg.randStart = attr.getUInt();
726 reg->removeAttr(
"firrtl.random_init_start");
729 svReg.reg->setDialectAttrs(
reg->getDialectAttrs());
731 if (
auto innerSymAttr =
reg.getInnerSymAttr())
732 svReg.reg.setInnerSymAttr(innerSymAttr);
736 if (
reg.hasReset()) {
738 reg->getBlock(), sv::EventControl::AtPosEdge,
reg.getClk(),
742 if (reg.getIsAsync() && areEquivalentValues(reg, reg.getNext()))
743 sv::PAssignOp::create(b, reg.getLoc(), svReg.reg, reg);
745 createTree(b, svReg.reg, reg, reg.getNext());
747 reg.getIsAsync() ? sv::ResetType::AsyncReset : sv::ResetType::SyncReset,
748 sv::EventControl::AtPosEdge,
reg.getReset(),
749 [&](OpBuilder &builder) {
750 sv::PAssignOp::create(builder, loc, svReg.reg,
reg.getResetValue());
752 if (
reg.getIsAsync()) {
753 svReg.asyncResetSignal =
reg.getReset();
754 svReg.asyncResetValue =
reg.getResetValue();
758 reg->getBlock(), sv::EventControl::AtPosEdge,
reg.getClk(),
759 [&](OpBuilder &b) { createTree(b, svReg.reg, reg, reg.getNext()); });
771 if (svReg.asyncResetSignal)
772 asyncResets[svReg.asyncResetSignal].emplace_back(svReg);
785 Value replacement = regVal.getResult();
786 if (isa<seq::ClockType>(
reg.getType()) && !
reg.use_empty())
787 replacement = seq::ToClockOp::create(builder, loc, regVal.getResult());
788 reg.replaceAllUsesWith(replacement);
797 OpBuilder &builder, Value
reg,
800 auto type = cast<sv::InOutType>(
reg.getType()).getElementType();
801 if (
auto intTy = hw::type_dyn_cast<IntegerType>(type)) {
803 pos -= intTy.getWidth();
804 auto elem = builder.createOrFold<
comb::ExtractOp>(loc, randomSource, pos,
806 sv::BPAssignOp::create(builder, loc,
reg, elem);
807 }
else if (
auto array = hw::type_dyn_cast<hw::ArrayType>(type)) {
808 for (
unsigned i = 0, e = array.getNumElements(); i < e; ++i) {
811 loc, builder, sv::ArrayIndexInOutOp::create(builder, loc,
reg, index),
814 }
else if (
auto structType = hw::type_dyn_cast<hw::StructType>(type)) {
815 for (
auto e : structType.getElements())
818 sv::StructFieldInOutOp::create(builder, loc,
reg, e.name),
821 assert(
false &&
"unsupported type");
835 auto kind = condition.getKind();
837 auto ifDef = sv::IfDefProceduralOp::create(b,
reg.getLoc(),
838 condition.getMacro(), []() {});
839 b.setInsertionPointToEnd(ifDef.getThenBlock());
843 auto ifDef = sv::IfDefProceduralOp::create(
844 b,
reg.getLoc(), condition.getMacro(), []() {}, []() {});
846 b.setInsertionPointToEnd(ifDef.getElseBlock());
849 llvm_unreachable(
"unknown reg condition type");
854 ArrayRef<Value> rands) {
855 auto loc =
reg.reg.getLoc();
856 SmallVector<Value> nibbles;
860 OpBuilder::InsertionGuard guard(builder);
870 Value target =
reg.reg;
874 uint64_t width =
reg.width;
875 uint64_t offset =
reg.randStart;
877 auto index = offset / 32;
878 auto start = offset % 32;
879 auto nwidth = std::min(32 - start, width);
883 nibbles.push_back(elem);
888 unsigned pos =
reg.width;
894 Block *block, sv::EventControl clockEdge, Value clock,
895 const std::function<
void(OpBuilder &)> &body, sv::ResetType resetStyle,
896 sv::EventControl resetEdge, Value reset,
897 const std::function<
void(OpBuilder &)> &resetBody) {
898 auto loc = clock.getLoc();
899 ImplicitLocOpBuilder builder(loc, block,
getBlockEnd(block));
901 resetStyle, resetEdge, reset};
903 sv::AlwaysOp alwaysOp;
911 assert(resetStyle != sv::ResetType::NoReset);
924 auto createIfOp = [&]() {
927 insideIfOp = sv::IfOp::create(
928 builder, reset, []() {}, []() {});
930 if (resetStyle == sv::ResetType::AsyncReset) {
931 sv::EventControl events[] = {clockEdge, resetEdge};
932 Value clocks[] = {clock, reset};
934 alwaysOp = sv::AlwaysOp::create(builder, events, clocks, [&]() {
935 if (resetEdge == sv::EventControl::AtNegEdge)
936 llvm_unreachable(
"negative edge for reset is not expected");
940 alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock, createIfOp);
944 alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock);
945 insideIfOp =
nullptr;
950 assert(insideIfOp &&
"reset body must be initialized before");
952 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getThenBlock());
953 resetBody(resetBuilder);
956 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getElseBlock());
960 ImplicitLocOpBuilder::atBlockEnd(loc, alwaysOp.getBodyBlock());
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
bool emitPresetAsInlineInit
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 createInitialBlock()
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)
unsigned numSubaccessRestored
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
bool emitSeparateAlwaysBlocks
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.
void add(mlir::ModuleOp module)
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
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)
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
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...
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
@ 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