11#include "mlir/IR/Threading.h"
12#include "mlir/Transforms/DialectConversion.h"
13#include "llvm/ADT/DenseSet.h"
14#include "llvm/Support/Debug.h"
23#define DEBUG_TYPE "lower-seq-firreg"
26 [](
const Operation *op) ->
bool {
27 return (isa<comb::MuxOp, ArrayGetOp, ArrayCreateOp>(op));
32 return llvm::any_of(regOp.getResult().getUsers(), [&](Operation *user) {
33 if (!OpUserInfo::opAllowsReachability(user))
35 buildReachabilityFrom(user);
36 return reachableMuxes[user].contains(muxOp);
48 if (
visited.contains(startNode))
53 llvm::SmallVector<OpUserInfo, 16> stk;
55 stk.emplace_back(startNode);
57 while (!stk.empty()) {
58 auto &info = stk.back();
59 Operation *currentNode = info.op;
62 if (info.getAndSetUnvisited())
65 if (info.userIter != info.userEnd) {
66 Operation *child = *info.userIter;
69 stk.emplace_back(child);
75 for (
auto *childOp : llvm::make_filter_range(
84 iter->getSecond().end());
92 const std::function<
void()> &trueSide,
93 const std::function<
void()> &falseSide) {
94 auto op =
ifCache.lookup({builder.getBlock(), cond});
99 builder.create<sv::IfOp>(cond.getLoc(), cond, trueSide, falseSide);
100 ifCache.insert({{builder.getBlock(), cond}, newIfOp});
102 OpBuilder::InsertionGuard guard(builder);
103 builder.setInsertionPointToEnd(op.getThenBlock());
105 builder.setInsertionPointToEnd(op.getElseBlock());
112 bool disableRegRandomization,
113 bool emitSeparateAlwaysBlocks)
114 : typeConverter(typeConverter), module(module),
115 disableRegRandomization(disableRegRandomization),
116 emitSeparateAlwaysBlocks(emitSeparateAlwaysBlocks) {
123 auto regs =
module.getOps<seq::FirRegOp>();
128 SmallVector<RegLowerInfo> randomInit, presetInit;
129 llvm::MapVector<Value, SmallVector<RegLowerInfo>> asyncResets;
130 for (
auto reg : llvm::make_early_inc_range(regs)) {
133 presetInit.push_back(svReg);
135 randomInit.push_back(svReg);
137 if (svReg.asyncResetSignal)
138 asyncResets[svReg.asyncResetSignal].emplace_back(svReg);
146 for (
auto reg : randomInit)
147 if (
reg.randStart >= 0)
148 maxBit = std::max(maxBit, (uint64_t)
reg.randStart +
reg.width);
150 for (
auto &
reg : randomInit) {
151 if (
reg.randStart == -1) {
152 reg.randStart = maxBit;
169 if (randomInit.empty() && presetInit.empty() && asyncResets.empty())
174 auto loc =
module.getLoc();
175 MLIRContext *context =
module.getContext();
176 auto randInitRef = sv::MacroIdentAttr::get(context,
"RANDOMIZE_REG_INIT");
179 ImplicitLocOpBuilder::atBlockTerminator(loc, module.getBodyBlock());
181 builder.create<
sv::IfDefOp>(
"ENABLE_INITIAL_REG_", [&] {
182 builder.create<sv::OrderedOutputOp>([&] {
183 builder.create<
sv::IfDefOp>(
"FIRRTL_BEFORE_INITIAL", [&] {
184 builder.create<sv::VerbatimOp>(
"`FIRRTL_BEFORE_INITIAL");
187 builder.create<sv::InitialOp>([&] {
188 if (!randomInit.empty()) {
189 builder.create<sv::IfDefProceduralOp>(
"INIT_RANDOM_PROLOG_", [&] {
190 builder.create<sv::VerbatimOp>(
"`INIT_RANDOM_PROLOG_");
192 builder.create<sv::IfDefProceduralOp>(randInitRef, [&] {
194 SmallVector<Value> randValues;
195 auto numRandomCalls = (maxBit + 31) / 32;
196 auto logic = builder.create<sv::LogicOp>(
198 hw::UnpackedArrayType::get(builder.getIntegerType(32),
203 auto inducionVariableWidth = llvm::Log2_64_Ceil(numRandomCalls + 1);
204 auto arrayIndexWith = llvm::Log2_64_Ceil(numRandomCalls);
208 loc, APInt(inducionVariableWidth, numRandomCalls));
211 auto forLoop = builder.create<sv::ForOp>(
212 loc, lb, ub, step,
"i", [&](BlockArgument iter) {
213 auto rhs = builder.create<sv::MacroRefExprSEOp>(
214 loc, builder.getIntegerType(32),
"RANDOM");
215 Value iterValue = iter;
216 if (!iter.getType().isInteger(arrayIndexWith))
218 loc, iterValue, 0, arrayIndexWith);
219 auto lhs = builder.
create<sv::ArrayIndexInOutOp>(loc, logic,
221 builder.create<sv::BPAssignOp>(loc, lhs, rhs);
223 builder.setInsertionPointAfter(forLoop);
224 for (uint64_t x = 0; x < numRandomCalls; ++x) {
225 auto lhs = builder.create<sv::ArrayIndexInOutOp>(
228 randValues.push_back(lhs.getResult());
232 for (
auto &svReg : randomInit)
237 if (!presetInit.empty()) {
238 for (
auto &svReg : presetInit) {
239 auto loc = svReg.reg.getLoc();
240 auto elemTy = svReg.reg.getType().getElementType();
244 if (cst.getType() == elemTy)
249 builder.
create<sv::BPAssignOp>(loc, svReg.reg, rhs);
253 if (!asyncResets.empty()) {
257 for (
auto &reset : asyncResets) {
261 builder.create<sv::IfOp>(reset.first, [&] {
262 for (
auto &
reg : reset.second)
263 builder.create<sv::BPAssignOp>(
reg.reg.getLoc(),
reg.reg,
264 reg.asyncResetValue);
270 builder.create<
sv::IfDefOp>(
"FIRRTL_AFTER_INITIAL", [&] {
271 builder.create<sv::VerbatimOp>(
"`FIRRTL_AFTER_INITIAL");
276 module->removeAttr("firrtl.random_init_width");
292 return c1.getType() == c2.getType() &&
293 c1.getValue() == c2.getValue() &&
303 if (!andOp || !andOp.getTwoState()) {
304 llvm::SetVector<Value> ret;
309 return llvm::SetVector<Value>(andOp.getOperands().begin(),
310 andOp.getOperands().end());
314 auto constantIndex = value.template getDefiningOp<hw::ConstantOp>();
316 return constantIndex.getValue();
325std::optional<std::tuple<Value, Value, Value>>
329 SmallVector<Value> muxConditions;
332 SmallVector<Value> reverseOpValues(llvm::reverse(nextRegValue.getOperands()));
333 if (!llvm::all_of(llvm::enumerate(reverseOpValues), [&](
auto idxAndValue) {
335 auto [i, value] = idxAndValue;
336 auto mux = value.template getDefiningOp<comb::MuxOp>();
338 if (!mux || !mux.getTwoState())
341 if (trueVal && trueVal != mux.getTrueValue())
344 trueVal = mux.getTrueValue();
345 muxConditions.push_back(mux.getCond());
349 mux.getFalseValue().template getDefiningOp<hw::ArrayGetOp>();
358 llvm::SetVector<Value> commonConditions =
360 for (
auto condition : ArrayRef(muxConditions).drop_front()) {
362 commonConditions.remove_if([&](
auto v) {
return !cond.contains(v); });
365 for (
auto [idx, condition] : llvm::enumerate(muxConditions)) {
369 extractedConditions.remove_if(
370 [&](
auto v) {
return commonConditions.contains(v); });
371 if (extractedConditions.size() != 1)
375 (*extractedConditions.begin()).getDefiningOp<comb::ICmpOp>();
376 if (!indexCompare || !indexCompare.getTwoState() ||
377 indexCompare.getPredicate() != comb::ICmpPredicate::eq)
380 if (indexValue && indexValue != indexCompare.getLhs())
383 indexValue = indexCompare.getLhs();
388 OpBuilder::InsertionGuard guard(builder);
389 builder.setInsertionPointAfterValue(
reg);
390 Value commonConditionValue;
391 if (commonConditions.empty())
394 commonConditionValue = builder.createOrFold<
comb::AndOp>(
395 reg.getLoc(), builder.getI1Type(), commonConditions.takeVector(),
true);
396 return std::make_tuple(commonConditionValue, indexValue, trueVal);
402 constexpr size_t limit = 1024;
414 auto firReg = term.getDefiningOp<seq::FirRegOp>();
416 std::deque<std::tuple<Block *, Value, Value, Value>> worklist;
417 auto addToWorklist = [&](Value
reg, Value term, Value next) {
418 worklist.emplace_back(builder.getBlock(),
reg, term, next);
421 auto getArrayIndex = [&](Value
reg, Value idx) {
423 OpBuilder::InsertionGuard guard(builder);
424 builder.setInsertionPointAfterValue(
reg);
425 return builder.create<sv::ArrayIndexInOutOp>(
reg.getLoc(),
reg, idx);
428 SmallVector<Value, 8> opsToDelete;
429 addToWorklist(
reg, term, next);
430 while (!worklist.empty()) {
431 OpBuilder::InsertionGuard guard(builder);
433 Value
reg, term, next;
434 std::tie(block,
reg, term, next) = worklist.front();
435 worklist.pop_front();
437 builder.setInsertionPointToEnd(block);
444 if (mux && mux.getTwoState() &&
446 if (counter >= limit) {
447 builder.create<sv::PAssignOp>(term.getLoc(),
reg, next);
451 builder, mux.getCond(),
452 [&]() { addToWorklist(reg, term, mux.getTrueValue()); },
453 [&]() { addToWorklist(reg, term, mux.getFalseValue()); });
461 if (
auto matchResultOpt =
463 Value cond, index, trueValue;
464 std::tie(cond, index, trueValue) = *matchResultOpt;
468 Value nextReg = getArrayIndex(
reg, index);
474 opsToDelete.push_back(termElement);
475 addToWorklist(nextReg, termElement, trueValue);
484 for (
auto [idx, value] : llvm::enumerate(array.getOperands())) {
485 idx = array.getOperands().size() - idx - 1;
489 APInt(std::max(1u, llvm::Log2_64_Ceil(array.getOperands().size())),
494 index = getArrayIndex(
reg, idxVal);
501 opsToDelete.push_back(termElement);
502 addToWorklist(index, termElement, value);
507 builder.create<sv::PAssignOp>(term.getLoc(),
reg, next);
510 while (!opsToDelete.empty()) {
511 auto value = opsToDelete.pop_back_val();
512 assert(value.use_empty());
513 value.getDefiningOp()->erase();
518 Location loc =
reg.getLoc();
521 ImplicitLocOpBuilder builder(
reg.getLoc(),
reg);
522 RegLowerInfo svReg{
nullptr,
reg.getPresetAttr(),
nullptr,
nullptr, -1, 0};
523 svReg.reg = builder.create<
sv::RegOp>(loc, regTy,
reg.getNameAttr());
526 if (
auto attr =
reg->getAttrOfType<IntegerAttr>(
"firrtl.random_init_start"))
527 svReg.randStart = attr.getUInt();
530 reg->removeAttr(
"firrtl.random_init_start");
533 svReg.reg->setDialectAttrs(
reg->getDialectAttrs());
535 if (
auto innerSymAttr =
reg.getInnerSymAttr())
536 svReg.reg.setInnerSymAttr(innerSymAttr);
540 if (
reg.hasReset()) {
542 module.getBodyBlock(), sv::EventControl::AtPosEdge,
reg.getClk(),
546 if (reg.getIsAsync() && areEquivalentValues(reg, reg.getNext()))
547 b.create<sv::PAssignOp>(reg.getLoc(), svReg.reg, reg);
549 createTree(b, svReg.reg, reg, reg.getNext());
551 reg.getIsAsync() ? sv::ResetType::AsyncReset :
sv::ResetType::SyncReset,
552 sv::EventControl::AtPosEdge,
reg.getReset(),
553 [&](OpBuilder &builder) {
554 builder.create<sv::PAssignOp>(loc, svReg.reg,
reg.getResetValue());
556 if (
reg.getIsAsync()) {
557 svReg.asyncResetSignal =
reg.getReset();
558 svReg.asyncResetValue =
reg.getResetValue();
562 module.getBodyBlock(), sv::EventControl::AtPosEdge,
reg.getClk(),
563 [&](OpBuilder &b) { createTree(b, svReg.reg, reg, reg.getNext()); });
566 reg.replaceAllUsesWith(regVal.getResult());
577 OpBuilder &builder, Value
reg,
580 auto type = cast<sv::InOutType>(
reg.getType()).getElementType();
581 if (
auto intTy = hw::type_dyn_cast<IntegerType>(type)) {
583 pos -= intTy.getWidth();
584 auto elem = builder.createOrFold<
comb::ExtractOp>(loc, randomSource, pos,
586 builder.
create<sv::BPAssignOp>(loc,
reg, elem);
587 }
else if (
auto array = hw::type_dyn_cast<hw::ArrayType>(type)) {
588 for (
unsigned i = 0, e = array.getNumElements(); i < e; ++i) {
591 loc, builder, builder.create<sv::ArrayIndexInOutOp>(loc,
reg, index),
594 }
else if (
auto structType = hw::type_dyn_cast<hw::StructType>(type)) {
595 for (
auto e : structType.getElements())
598 builder.create<sv::StructFieldInOutOp>(loc,
reg, e.name),
601 assert(
false &&
"unsupported type");
607 ArrayRef<Value> rands) {
608 auto loc =
reg.reg.getLoc();
609 SmallVector<Value> nibbles;
613 uint64_t width =
reg.width;
614 uint64_t offset =
reg.randStart;
616 auto index = offset / 32;
617 auto start = offset % 32;
618 auto nwidth = std::min(32 - start, width);
622 nibbles.push_back(elem);
627 unsigned pos =
reg.width;
633 Block *block, sv::EventControl clockEdge, Value clock,
634 const std::function<
void(OpBuilder &)> &body, sv::ResetType resetStyle,
635 sv::EventControl resetEdge, Value reset,
636 const std::function<
void(OpBuilder &)> &resetBody) {
637 auto loc = clock.getLoc();
638 auto builder = ImplicitLocOpBuilder::atBlockTerminator(loc, block);
640 resetStyle, resetEdge, reset};
642 sv::AlwaysOp alwaysOp;
650 assert(resetStyle != sv::ResetType::NoReset);
663 auto createIfOp = [&]() {
666 insideIfOp = builder.create<sv::IfOp>(
667 reset, []() {}, []() {});
669 if (resetStyle == sv::ResetType::AsyncReset) {
670 sv::EventControl events[] = {clockEdge, resetEdge};
671 Value clocks[] = {clock, reset};
673 alwaysOp = builder.create<sv::AlwaysOp>(events, clocks, [&]() {
674 if (resetEdge == sv::EventControl::AtNegEdge)
675 llvm_unreachable(
"negative edge for reset is not expected");
679 alwaysOp = builder.create<sv::AlwaysOp>(clockEdge, clock, createIfOp);
683 alwaysOp = builder.create<sv::AlwaysOp>(clockEdge, clock);
684 insideIfOp =
nullptr;
689 assert(insideIfOp &&
"reset body must be initialized before");
691 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getThenBlock());
692 resetBody(resetBuilder);
695 ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getElseBlock());
699 ImplicitLocOpBuilder::atBlockEnd(loc, alwaysOp.getBodyBlock());
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.
static bool areEquivalentValues(Value term, Value next)
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
FirRegLowering(TypeConverter &typeConverter, hw::HWModuleOp module, bool disableRegRandomization=false, bool emitSeparateAlwaysBlocks=false)
llvm::SmallDenseMap< IfKeyType, sv::IfOp > ifCache
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 createTree(OpBuilder &builder, Value reg, Value term, Value next)
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={})
std::tuple< Block *, sv::EventControl, Value, sv::ResetType, sv::EventControl, Value > AlwaysKeyType
void initializeRegisterElements(Location loc, OpBuilder &builder, Value reg, Value rand, unsigned &pos)
TypeConverter & typeConverter
hw::HWModuleOp bool disableRegRandomization
bool emitSeparateAlwaysBlocks
llvm::SmallDenseMap< AlwaysKeyType, std::pair< sv::AlwaysOp, sv::IfOp > > alwaysBlocks
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.
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
static std::function< bool(const Operation *op)> opAllowsReachability