circt/lib/Conversion/SeqToSV/FirRegLowering.cpp

923 lines
33 KiB
C++

//===- FirRegLowering.cpp - FirReg lowering utilities ---------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "FirRegLowering.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Support/Utils.h"
#include "mlir/IR/Threading.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/Debug.h"
#include <deque>
using namespace circt;
using namespace hw;
using namespace seq;
using llvm::MapVector;
#define DEBUG_TYPE "lower-seq-firreg"
/// Immediately before the terminator, if present. Otherwise, the block's end.
static Block::iterator getBlockEnd(Block *block) {
if (block->mightHaveTerminator())
return Block::iterator(block->getTerminator());
return block->end();
}
std::function<bool(const Operation *op)> OpUserInfo::opAllowsReachability =
[](const Operation *op) -> bool {
return (isa<comb::MuxOp, ArrayGetOp, ArrayCreateOp>(op));
};
bool ReachableMuxes::isMuxReachableFrom(seq::FirRegOp regOp,
comb::MuxOp muxOp) {
return llvm::any_of(regOp.getResult().getUsers(), [&](Operation *user) {
if (!OpUserInfo::opAllowsReachability(user))
return false;
buildReachabilityFrom(user);
return reachableMuxes[user].contains(muxOp);
});
}
void ReachableMuxes::buildReachabilityFrom(Operation *startNode) {
// This is a backward dataflow analysis.
// First build a graph rooted at the `startNode`. Every user of an operation
// that does not block the reachability is a child node. Then, the ops that
// are reachable from a node is computed as the union of the Reachability of
// all its child nodes.
// The dataflow can be expressed as, for all child in the Children(node)
// Reachability(node) = node + Union{Reachability(child)}
if (visited.contains(startNode))
return;
// The stack to record enough information for an iterative post-order
// traversal.
llvm::SmallVector<OpUserInfo, 16> stk;
stk.emplace_back(startNode);
while (!stk.empty()) {
auto &info = stk.back();
Operation *currentNode = info.op;
// Node is being visited for the first time.
if (info.getAndSetUnvisited())
visited.insert(currentNode);
if (info.userIter != info.userEnd) {
Operation *child = *info.userIter;
++info.userIter;
if (!visited.contains(child))
stk.emplace_back(child);
} else { // All children of the node have been visited
// Any op is reachable from itself.
reachableMuxes[currentNode].insert(currentNode);
for (auto *childOp : llvm::make_filter_range(
info.op->getUsers(), OpUserInfo::opAllowsReachability)) {
reachableMuxes[currentNode].insert(childOp);
// Propagate the reachability backwards from m to currentNode.
auto iter = reachableMuxes.find(childOp);
assert(iter != reachableMuxes.end());
// Add all the mux that was reachable from childOp, to currentNode.
reachableMuxes[currentNode].insert(iter->getSecond().begin(),
iter->getSecond().end());
}
stk.pop_back();
}
}
}
void FirRegLowering::addToIfBlock(OpBuilder &builder, Value cond,
const std::function<void()> &trueSide,
const std::function<void()> &falseSide) {
auto op = ifCache.lookup({builder.getBlock(), cond});
// Always build both sides of the if, in case we want to use an empty else
// later. This way we don't have to build a new if and replace it.
if (!op) {
auto newIfOp =
sv::IfOp::create(builder, cond.getLoc(), cond, trueSide, falseSide);
ifCache.insert({{builder.getBlock(), cond}, newIfOp});
} else {
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToEnd(op.getThenBlock());
trueSide();
builder.setInsertionPointToEnd(op.getElseBlock());
falseSide();
}
}
/// Attach an inner-sym to field-id 0 of the given register, or use an existing
/// inner-sym, if present.
static StringAttr getInnerSymFor(InnerSymbolNamespace &innerSymNS,
seq::FirRegOp reg) {
auto attr = reg.getInnerSymAttr();
// If we have an inner sym attribute already, and if there exists a symbol for
// field-id 0, then just return that.
if (attr)
if (auto sym = attr.getSymIfExists(0))
return sym;
// Otherwise, we have to create a new inner sym.
auto *context = reg->getContext();
auto hint = reg.getName();
// Create our new property for field 0.
auto sym = StringAttr::get(context, innerSymNS.newName(hint));
auto property = hw::InnerSymPropertiesAttr::get(sym);
// Build the new list of inner sym properties. Since properties are sorted by
// field ID, our new property is first.
SmallVector<hw::InnerSymPropertiesAttr> properties = {property};
if (attr)
llvm::append_range(properties, attr.getProps());
// Build the new InnerSymAttr and attach it to the op.
attr = hw::InnerSymAttr::get(context, properties);
reg.setInnerSymAttr(attr);
// Return the name of the new inner sym.
return sym;
}
static InnerRefAttr getInnerRefTo(StringAttr mod, InnerSymbolNamespace &isns,
seq::FirRegOp reg) {
auto tgt = getInnerSymFor(isns, reg);
return hw::InnerRefAttr::get(mod, tgt);
}
namespace {
/// A pair of a register, and an inner-ref attribute.
struct BuriedFirReg {
FirRegOp reg;
InnerRefAttr ref;
};
} // namespace
/// Locate the registers under the given HW module, which are not at the
/// top-level of the module body. These registers will be initialized through an
/// NLA. Put an inner symbol on each, and return a list of the buried registers
/// and their inner-symbols.
static std::vector<BuriedFirReg> getBuriedRegs(HWModuleOp module) {
auto name = SymbolTable::getSymbolName(module);
InnerSymbolNamespace isns(module);
std::vector<BuriedFirReg> result;
for (auto &op : *module.getBodyBlock()) {
for (auto &region : op.getRegions()) {
region.walk([&](FirRegOp reg) {
auto ref = getInnerRefTo(name, isns, reg);
result.push_back({reg, ref});
});
}
}
return result;
}
/// Locate all registers which are not at the top-level of their parent HW
/// module. These registers will be initialized through an NLA. Put an inner
/// symbol on each, and return a list of the buried registers and their
/// inner-symbols.
static std::vector<BuriedFirReg> getAllBuriedRegs(ModuleOp top) {
auto *context = top.getContext();
std::vector<BuriedFirReg> init;
auto ms = top.getOps<HWModuleOp>();
const std::vector<HWModuleOp> modules(ms.begin(), ms.end());
const auto reduce =
[](std::vector<BuriedFirReg> acc,
std::vector<BuriedFirReg> &&xs) -> std::vector<BuriedFirReg> {
acc.insert(acc.end(), xs.begin(), xs.end());
return acc;
};
return transformReduce(context, modules, init, reduce, getBuriedRegs);
}
/// Construct a hierarchical path op that targets the given register.
static hw::HierPathOp getHierPathTo(OpBuilder &builder, Namespace &ns,
BuriedFirReg entry) {
auto modName = entry.ref.getModule().getValue();
auto symName = entry.ref.getName().getValue();
auto name = ns.newName(Twine(modName) + "_" + symName);
// Insert the HierPathOp immediately before the parent HWModuleOp, for style.
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPoint(entry.reg->getParentOfType<HWModuleOp>());
auto path = builder.getArrayAttr({entry.ref});
return hw::HierPathOp::create(builder, entry.reg.getLoc(), name, path);
}
FirRegLowering::PathTable FirRegLowering::createPaths(mlir::ModuleOp top) {
auto builder = OpBuilder::atBlockBegin(top.getBody());
PathTable result;
Namespace ns;
ns.add(top);
for (auto entry : getAllBuriedRegs(top))
result[entry.reg] = getHierPathTo(builder, ns, entry);
return result;
}
FirRegLowering::FirRegLowering(TypeConverter &typeConverter,
hw::HWModuleOp module,
const PathTable &pathTable,
bool disableRegRandomization,
bool emitSeparateAlwaysBlocks)
: pathTable(pathTable), typeConverter(typeConverter), module(module),
disableRegRandomization(disableRegRandomization),
emitSeparateAlwaysBlocks(emitSeparateAlwaysBlocks) {
reachableMuxes = std::make_unique<ReachableMuxes>(module);
}
void FirRegLowering::lower() {
lowerInBlock(module.getBodyBlock());
createInitialBlock();
module->removeAttr("firrtl.random_init_width");
}
// NOLINTNEXTLINE(misc-no-recursion)
void FirRegLowering::lowerUnderIfDef(sv::IfDefOp ifDefOp) {
auto cond = ifDefOp.getCond();
conditions.emplace_back(RegCondition::IfDefThen, cond);
lowerInBlock(ifDefOp.getThenBlock());
conditions.pop_back();
if (ifDefOp.hasElse()) {
conditions.emplace_back(RegCondition::IfDefElse, cond);
lowerInBlock(ifDefOp.getElseBlock());
conditions.pop_back();
}
}
// NOLINTNEXTLINE(misc-no-recursion)
void FirRegLowering::lowerInBlock(Block *block) {
for (auto &op : llvm::make_early_inc_range(*block)) {
if (auto ifDefOp = dyn_cast<sv::IfDefOp>(op)) {
lowerUnderIfDef(ifDefOp);
continue;
}
if (auto regOp = dyn_cast<seq::FirRegOp>(op)) {
lowerReg(regOp);
continue;
}
for (auto &region : op.getRegions())
for (auto &block : region.getBlocks())
lowerInBlock(&block);
}
}
SmallVector<Value> FirRegLowering::createRandomizationVector(OpBuilder &builder,
Location loc) {
// Compute total width of random space. Place non-chisel registers at the end
// of the space. The Random space is unique to the initial block, due to
// verilog thread rules, so we can drop trailing random calls if they are
// unused.
uint64_t maxBit = 0;
for (auto reg : randomInitRegs)
if (reg.randStart >= 0)
maxBit = std::max(maxBit, (uint64_t)reg.randStart + reg.width);
for (auto &reg : randomInitRegs) {
if (reg.randStart == -1) {
reg.randStart = maxBit;
maxBit += reg.width;
}
}
// Create randomization vector
SmallVector<Value> randValues;
auto numRandomCalls = (maxBit + 31) / 32;
auto logic = sv::LogicOp::create(
builder, loc,
hw::UnpackedArrayType::get(builder.getIntegerType(32), numRandomCalls),
"_RANDOM");
// Indvar's width must be equal to `ceil(log2(numRandomCalls +
// 1))` to avoid overflow.
auto inducionVariableWidth = llvm::Log2_64_Ceil(numRandomCalls + 1);
auto arrayIndexWith = llvm::Log2_64_Ceil(numRandomCalls);
auto lb = getOrCreateConstant(loc, APInt::getZero(inducionVariableWidth));
auto ub =
getOrCreateConstant(loc, APInt(inducionVariableWidth, numRandomCalls));
auto step = getOrCreateConstant(loc, APInt(inducionVariableWidth, 1));
auto forLoop = sv::ForOp::create(
builder, loc, lb, ub, step, "i", [&](BlockArgument iter) {
auto rhs = sv::MacroRefExprSEOp::create(
builder, loc, builder.getIntegerType(32), "RANDOM");
Value iterValue = iter;
if (!iter.getType().isInteger(arrayIndexWith))
iterValue = comb::ExtractOp::create(builder, loc, iterValue, 0,
arrayIndexWith);
auto lhs =
sv::ArrayIndexInOutOp::create(builder, loc, logic, iterValue);
sv::BPAssignOp::create(builder, loc, lhs, rhs);
});
builder.setInsertionPointAfter(forLoop);
for (uint64_t x = 0; x < numRandomCalls; ++x) {
auto lhs = sv::ArrayIndexInOutOp::create(
builder, loc, logic,
getOrCreateConstant(loc, APInt(arrayIndexWith, x)));
randValues.push_back(lhs.getResult());
}
return randValues;
}
void FirRegLowering::createRandomInitialization(ImplicitLocOpBuilder &builder) {
auto randInitRef =
sv::MacroIdentAttr::get(builder.getContext(), "RANDOMIZE_REG_INIT");
if (!randomInitRegs.empty()) {
sv::IfDefProceduralOp::create(builder, "INIT_RANDOM_PROLOG_", [&] {
sv::VerbatimOp::create(builder, "`INIT_RANDOM_PROLOG_");
});
sv::IfDefProceduralOp::create(builder, randInitRef, [&] {
auto randValues = createRandomizationVector(builder, builder.getLoc());
for (auto &svReg : randomInitRegs)
initialize(builder, svReg, randValues);
});
}
}
void FirRegLowering::createPresetInitialization(ImplicitLocOpBuilder &builder) {
for (auto &svReg : presetInitRegs) {
auto loc = svReg.reg.getLoc();
auto elemTy = svReg.reg.getType().getElementType();
auto cst = getOrCreateConstant(loc, svReg.preset.getValue());
Value rhs;
if (cst.getType() == elemTy)
rhs = cst;
else
rhs = hw::BitcastOp::create(builder, loc, elemTy, cst);
sv::BPAssignOp::create(builder, loc, svReg.reg, rhs);
}
}
// If a register is async reset, we need to insert extra initialization in
// post-randomization so that we can set the reset value to register if the
// reset signal is enabled.
void FirRegLowering::createAsyncResetInitialization(
ImplicitLocOpBuilder &builder) {
for (auto &reset : asyncResets) {
// if (reset) begin
// ..
// end
sv::IfOp::create(builder, reset.first, [&] {
for (auto &reg : reset.second)
sv::BPAssignOp::create(builder, reg.reg.getLoc(), reg.reg,
reg.asyncResetValue);
});
}
}
void FirRegLowering::createInitialBlock() {
// Create an initial block at the end of the module where random
// initialisation will be inserted. Create two builders into the two
// `ifdef` ops where the registers will be placed.
//
// `ifndef SYNTHESIS
// `ifdef RANDOMIZE_REG_INIT
// ... regBuilder ...
// `endif
// initial
// `INIT_RANDOM_PROLOG_
// ... initBuilder ..
// `endif
if (randomInitRegs.empty() && presetInitRegs.empty() && asyncResets.empty())
return;
needsRandom = true;
auto loc = module.getLoc();
auto builder =
ImplicitLocOpBuilder::atBlockTerminator(loc, module.getBodyBlock());
sv::IfDefOp::create(builder, "ENABLE_INITIAL_REG_", [&] {
sv::OrderedOutputOp::create(builder, [&] {
sv::IfDefOp::create(builder, "FIRRTL_BEFORE_INITIAL", [&] {
sv::VerbatimOp::create(builder, "`FIRRTL_BEFORE_INITIAL");
});
sv::InitialOp::create(builder, [&] {
createRandomInitialization(builder);
createPresetInitialization(builder);
createAsyncResetInitialization(builder);
});
sv::IfDefOp::create(builder, "FIRRTL_AFTER_INITIAL", [&] {
sv::VerbatimOp::create(builder, "`FIRRTL_AFTER_INITIAL");
});
});
});
}
// Return true if two arguments are equivalent, or if both of them are the same
// array indexing.
// NOLINTNEXTLINE(misc-no-recursion)
static bool areEquivalentValues(Value term, Value next) {
if (term == next)
return true;
// Check whether these values are equivalent array accesses with constant
// index. We have to check the equivalence recursively because they might not
// be CSEd.
if (auto t1 = term.getDefiningOp<hw::ArrayGetOp>())
if (auto t2 = next.getDefiningOp<hw::ArrayGetOp>())
if (auto c1 = t1.getIndex().getDefiningOp<hw::ConstantOp>())
if (auto c2 = t2.getIndex().getDefiningOp<hw::ConstantOp>())
return c1.getType() == c2.getType() &&
c1.getValue() == c2.getValue() &&
areEquivalentValues(t1.getInput(), t2.getInput());
// Otherwise, regard as different.
// TODO: Handle struct if necessary.
return false;
}
static llvm::SetVector<Value> extractConditions(Value value) {
auto andOp = value.getDefiningOp<comb::AndOp>();
// If the value is not AndOp with a bin flag, use it as a condition.
if (!andOp || !andOp.getTwoState()) {
llvm::SetVector<Value> ret;
ret.insert(value);
return ret;
}
return llvm::SetVector<Value>(andOp.getOperands().begin(),
andOp.getOperands().end());
}
static std::optional<APInt> getConstantValue(Value value) {
auto constantIndex = value.template getDefiningOp<hw::ConstantOp>();
if (constantIndex)
return constantIndex.getValue();
return {};
}
// Return a tuple <cond, idx, val> if the array register update can be
// represented with a dynamic index assignment:
// if (cond)
// reg[idx] <= val;
//
std::optional<std::tuple<Value, Value, Value>>
FirRegLowering::tryRestoringSubaccess(OpBuilder &builder, Value reg, Value term,
hw::ArrayCreateOp nextRegValue) {
Value trueVal;
SmallVector<Value> muxConditions;
// Compat fix for GCC12's libstdc++, cannot use
// llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
SmallVector<Value> reverseOpValues(llvm::reverse(nextRegValue.getOperands()));
if (!llvm::all_of(llvm::enumerate(reverseOpValues), [&](auto idxAndValue) {
// Check that `nextRegValue[i]` is `cond_i ? val : reg[i]`.
auto [i, value] = idxAndValue;
auto mux = value.template getDefiningOp<comb::MuxOp>();
// Ensure that mux has binary flag.
if (!mux || !mux.getTwoState())
return false;
// The next value must be same.
if (trueVal && trueVal != mux.getTrueValue())
return false;
if (!trueVal)
trueVal = mux.getTrueValue();
muxConditions.push_back(mux.getCond());
// Check that ith element is an element of the register we are
// currently lowering.
auto arrayGet =
mux.getFalseValue().template getDefiningOp<hw::ArrayGetOp>();
if (!arrayGet)
return false;
return areEquivalentValues(arrayGet.getInput(), term) &&
getConstantValue(arrayGet.getIndex()) == i;
}))
return {};
// Extract common expressions among mux conditions.
llvm::SetVector<Value> commonConditions =
extractConditions(muxConditions.front());
for (auto condition : ArrayRef(muxConditions).drop_front()) {
auto cond = extractConditions(condition);
commonConditions.remove_if([&](auto v) { return !cond.contains(v); });
}
Value indexValue;
for (auto [idx, condition] : llvm::enumerate(muxConditions)) {
llvm::SetVector<Value> extractedConditions = extractConditions(condition);
// Remove common conditions and check the remaining condition is only an
// index comparision.
extractedConditions.remove_if(
[&](auto v) { return commonConditions.contains(v); });
if (extractedConditions.size() != 1)
return {};
auto indexCompare =
(*extractedConditions.begin()).getDefiningOp<comb::ICmpOp>();
if (!indexCompare || !indexCompare.getTwoState() ||
indexCompare.getPredicate() != comb::ICmpPredicate::eq)
return {};
// `IndexValue` must be same.
if (indexValue && indexValue != indexCompare.getLhs())
return {};
if (!indexValue)
indexValue = indexCompare.getLhs();
if (getConstantValue(indexCompare.getRhs()) != idx)
return {};
}
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointAfterValue(reg);
Value commonConditionValue;
if (commonConditions.empty())
commonConditionValue = getOrCreateConstant(reg.getLoc(), APInt(1, 1));
else
commonConditionValue = builder.createOrFold<comb::AndOp>(
reg.getLoc(), builder.getI1Type(), commonConditions.takeVector(), true);
return std::make_tuple(commonConditionValue, indexValue, trueVal);
}
void FirRegLowering::createTree(OpBuilder &builder, Value reg, Value term,
Value next) {
// If-then-else tree limit.
constexpr size_t limit = 1024;
// Count of emitted if-then-else ops.
size_t counter = 0;
// Get the fanout from this register before we build the tree. While we are
// creating the tree of if/else statements from muxes, we only want to turn
// muxes that are on the register's fanout into if/else statements. This is
// required to get the correct enable inference. But other muxes in the tree
// should be left as ternary operators. This is desirable because we don't
// want to create if/else structure for logic unrelated to the register's
// enable.
auto firReg = term.getDefiningOp<seq::FirRegOp>();
std::deque<std::tuple<Block *, Value, Value, Value>> worklist;
auto addToWorklist = [&](Value reg, Value term, Value next) {
worklist.emplace_back(builder.getBlock(), reg, term, next);
};
auto getArrayIndex = [&](Value reg, Value idx) {
// Create an array index op just after `reg`.
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointAfterValue(reg);
return sv::ArrayIndexInOutOp::create(builder, reg.getLoc(), reg, idx);
};
SmallVector<Value, 8> opsToDelete;
addToWorklist(reg, term, next);
while (!worklist.empty()) {
OpBuilder::InsertionGuard guard(builder);
Block *block;
Value reg, term, next;
std::tie(block, reg, term, next) = worklist.front();
worklist.pop_front();
builder.setInsertionPointToEnd(block);
if (areEquivalentValues(term, next))
continue;
// If this is a two-state mux within the fanout from the register, we use
// if/else structure for proper enable inference.
auto mux = next.getDefiningOp<comb::MuxOp>();
if (mux && mux.getTwoState() &&
reachableMuxes->isMuxReachableFrom(firReg, mux)) {
if (counter >= limit) {
sv::PAssignOp::create(builder, term.getLoc(), reg, next);
continue;
}
addToIfBlock(
builder, mux.getCond(),
[&]() { addToWorklist(reg, term, mux.getTrueValue()); },
[&]() { addToWorklist(reg, term, mux.getFalseValue()); });
++counter;
continue;
}
// If the next value is an array creation, split the value into
// invidial elements and construct trees recursively.
if (auto array = next.getDefiningOp<hw::ArrayCreateOp>()) {
// First, try restoring subaccess assignments.
if (auto matchResultOpt =
tryRestoringSubaccess(builder, reg, term, array)) {
Value cond, index, trueValue;
std::tie(cond, index, trueValue) = *matchResultOpt;
addToIfBlock(
builder, cond,
[&]() {
Value nextReg = getArrayIndex(reg, index);
// Create a value to use for equivalence checking in the
// recursive calls. Add the value to `opsToDelete` so that it can
// be deleted afterwards.
auto termElement =
hw::ArrayGetOp::create(builder, term.getLoc(), term, index);
opsToDelete.push_back(termElement);
addToWorklist(nextReg, termElement, trueValue);
},
[]() {});
++numSubaccessRestored;
continue;
}
// Compat fix for GCC12's libstdc++, cannot use
// llvm::enumerate(llvm::reverse(OperandRange)). See #4900.
// SmallVector<Value>
// reverseOpValues(llvm::reverse(array.getOperands()));
for (auto [idx, value] : llvm::enumerate(array.getOperands())) {
idx = array.getOperands().size() - idx - 1;
// Create an index constant.
auto idxVal = getOrCreateConstant(
array.getLoc(),
APInt(std::max(1u, llvm::Log2_64_Ceil(array.getOperands().size())),
idx));
auto &index = arrayIndexCache[{reg, idx}];
if (!index)
index = getArrayIndex(reg, idxVal);
// Create a value to use for equivalence checking in the
// recursive calls. Add the value to `opsToDelete` so that it can
// be deleted afterwards.
auto termElement =
hw::ArrayGetOp::create(builder, term.getLoc(), term, idxVal);
opsToDelete.push_back(termElement);
addToWorklist(index, termElement, value);
}
continue;
}
sv::PAssignOp::create(builder, term.getLoc(), reg, next);
}
while (!opsToDelete.empty()) {
auto value = opsToDelete.pop_back_val();
assert(value.use_empty());
value.getDefiningOp()->erase();
}
}
void FirRegLowering::lowerReg(FirRegOp reg) {
Location loc = reg.getLoc();
Type regTy = typeConverter.convertType(reg.getType());
HierPathOp path;
auto lookup = pathTable.find(reg);
if (lookup != pathTable.end())
path = lookup->second;
ImplicitLocOpBuilder builder(reg.getLoc(), reg);
RegLowerInfo svReg{nullptr, path, reg.getPresetAttr(), nullptr, nullptr,
-1, 0};
svReg.reg = sv::RegOp::create(builder, loc, regTy, reg.getNameAttr());
svReg.width = hw::getBitWidth(regTy);
if (auto attr = reg->getAttrOfType<IntegerAttr>("firrtl.random_init_start"))
svReg.randStart = attr.getUInt();
// Don't move these over
reg->removeAttr("firrtl.random_init_start");
// Move Attributes
svReg.reg->setDialectAttrs(reg->getDialectAttrs());
if (auto innerSymAttr = reg.getInnerSymAttr())
svReg.reg.setInnerSymAttr(innerSymAttr);
auto regVal = sv::ReadInOutOp::create(builder, loc, svReg.reg);
if (reg.hasReset()) {
addToAlwaysBlock(
reg->getBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
[&](OpBuilder &b) {
// If this is an AsyncReset, ensure that we emit a self connect to
// avoid erroneously creating a latch construct.
if (reg.getIsAsync() && areEquivalentValues(reg, reg.getNext()))
sv::PAssignOp::create(b, reg.getLoc(), svReg.reg, reg);
else
createTree(b, svReg.reg, reg, reg.getNext());
},
reg.getIsAsync() ? sv::ResetType::AsyncReset : sv::ResetType::SyncReset,
sv::EventControl::AtPosEdge, reg.getReset(),
[&](OpBuilder &builder) {
sv::PAssignOp::create(builder, loc, svReg.reg, reg.getResetValue());
});
if (reg.getIsAsync()) {
svReg.asyncResetSignal = reg.getReset();
svReg.asyncResetValue = reg.getResetValue();
}
} else {
addToAlwaysBlock(
reg->getBlock(), sv::EventControl::AtPosEdge, reg.getClk(),
[&](OpBuilder &b) { createTree(b, svReg.reg, reg, reg.getNext()); });
}
// Record information required later on to build the initialization code for
// this register. All initialization is grouped together in a single initial
// block at the back of the module.
if (svReg.preset)
presetInitRegs.push_back(svReg);
else if (!disableRegRandomization)
randomInitRegs.push_back(svReg);
if (svReg.asyncResetSignal)
asyncResets[svReg.asyncResetSignal].emplace_back(svReg);
// Remember the ifdef conditions surrounding this register, if present. We
// will need to place this register's initialization code under the same
// ifdef conditions.
if (!conditions.empty())
regConditionTable.emplace_or_assign(svReg.reg, conditions);
reg.replaceAllUsesWith(regVal.getResult());
reg.erase();
}
// Initialize registers by assigning each element recursively instead of
// initializing entire registers. This is necessary as a workaround for
// verilator which allocates many local variables for concat op.
// NOLINTBEGIN(misc-no-recursion)
void FirRegLowering::initializeRegisterElements(Location loc,
OpBuilder &builder, Value reg,
Value randomSource,
unsigned &pos) {
auto type = cast<sv::InOutType>(reg.getType()).getElementType();
if (auto intTy = hw::type_dyn_cast<IntegerType>(type)) {
// Use randomSource[pos-1:pos-width] as a random value.
pos -= intTy.getWidth();
auto elem = builder.createOrFold<comb::ExtractOp>(loc, randomSource, pos,
intTy.getWidth());
sv::BPAssignOp::create(builder, loc, reg, elem);
} else if (auto array = hw::type_dyn_cast<hw::ArrayType>(type)) {
for (unsigned i = 0, e = array.getNumElements(); i < e; ++i) {
auto index = getOrCreateConstant(loc, APInt(llvm::Log2_64_Ceil(e), i));
initializeRegisterElements(
loc, builder, sv::ArrayIndexInOutOp::create(builder, loc, reg, index),
randomSource, pos);
}
} else if (auto structType = hw::type_dyn_cast<hw::StructType>(type)) {
for (auto e : structType.getElements())
initializeRegisterElements(
loc, builder,
sv::StructFieldInOutOp::create(builder, loc, reg, e.name),
randomSource, pos);
} else {
assert(false && "unsupported type");
}
}
// NOLINTEND(misc-no-recursion)
void FirRegLowering::buildRegConditions(OpBuilder &b, sv::RegOp reg) {
// If there are no conditions, just return the current insertion point.
auto lookup = regConditionTable.find(reg);
if (lookup == regConditionTable.end())
return;
// Recreate the conditions under which the register was declared.
auto &conditions = lookup->second;
for (auto &condition : conditions) {
auto kind = condition.getKind();
if (kind == RegCondition::IfDefThen) {
auto ifDef = sv::IfDefProceduralOp::create(b, reg.getLoc(),
condition.getMacro(), []() {});
b.setInsertionPointToEnd(ifDef.getThenBlock());
continue;
}
if (kind == RegCondition::IfDefElse) {
auto ifDef = sv::IfDefProceduralOp::create(
b, reg.getLoc(), condition.getMacro(), []() {}, []() {});
b.setInsertionPointToEnd(ifDef.getElseBlock());
continue;
}
llvm_unreachable("unknown reg condition type");
}
}
static Value buildXMRTo(OpBuilder &builder, HierPathOp path, Location loc,
Type type) {
auto name = path.getSymNameAttr();
auto ref = mlir::FlatSymbolRefAttr::get(name);
return sv::XMRRefOp::create(builder, loc, type, ref);
}
void FirRegLowering::initialize(OpBuilder &builder, RegLowerInfo reg,
ArrayRef<Value> rands) {
auto loc = reg.reg.getLoc();
SmallVector<Value> nibbles;
if (reg.width == 0)
return;
OpBuilder::InsertionGuard guard(builder);
// If the register was defined under ifdefs, we have to guard the
// initialization code under the same ifdefs. The builder's insertion point
// will be left inside the guards.
buildRegConditions(builder, reg.reg);
// If the register is not located in the toplevel body of the module, we must
// refer to the register by (local) XMR, since the register will not dominate
// the initialization block.
Value target = reg.reg;
if (reg.path)
target = buildXMRTo(builder, reg.path, reg.reg.getLoc(), reg.reg.getType());
uint64_t width = reg.width;
uint64_t offset = reg.randStart;
while (width) {
auto index = offset / 32;
auto start = offset % 32;
auto nwidth = std::min(32 - start, width);
auto elemVal = sv::ReadInOutOp::create(builder, loc, rands[index]);
auto elem =
builder.createOrFold<comb::ExtractOp>(loc, elemVal, start, nwidth);
nibbles.push_back(elem);
offset += nwidth;
width -= nwidth;
}
auto concat = builder.createOrFold<comb::ConcatOp>(loc, nibbles);
unsigned pos = reg.width;
// Initialize register elements.
initializeRegisterElements(loc, builder, target, concat, pos);
}
void FirRegLowering::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) {
auto loc = clock.getLoc();
ImplicitLocOpBuilder builder(loc, block, getBlockEnd(block));
AlwaysKeyType key{builder.getBlock(), clockEdge, clock,
resetStyle, resetEdge, reset};
sv::AlwaysOp alwaysOp;
sv::IfOp insideIfOp;
if (!emitSeparateAlwaysBlocks) {
std::tie(alwaysOp, insideIfOp) = alwaysBlocks[key];
}
if (!alwaysOp) {
if (reset) {
assert(resetStyle != sv::ResetType::NoReset);
// Here, we want to create the following structure with sv.always and
// sv.if. If `reset` is async, we need to add `reset` to a sensitivity
// list.
//
// sv.always @(clockEdge or reset) {
// sv.if (reset) {
// resetBody
// } else {
// body
// }
// }
auto createIfOp = [&]() {
// It is weird but intended. Here we want to create an empty sv.if
// with an else block.
insideIfOp = sv::IfOp::create(
builder, reset, []() {}, []() {});
};
if (resetStyle == sv::ResetType::AsyncReset) {
sv::EventControl events[] = {clockEdge, resetEdge};
Value clocks[] = {clock, reset};
alwaysOp = sv::AlwaysOp::create(builder, events, clocks, [&]() {
if (resetEdge == sv::EventControl::AtNegEdge)
llvm_unreachable("negative edge for reset is not expected");
createIfOp();
});
} else {
alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock, createIfOp);
}
} else {
assert(!resetBody);
alwaysOp = sv::AlwaysOp::create(builder, clockEdge, clock);
insideIfOp = nullptr;
}
}
if (reset) {
assert(insideIfOp && "reset body must be initialized before");
auto resetBuilder =
ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getThenBlock());
resetBody(resetBuilder);
auto bodyBuilder =
ImplicitLocOpBuilder::atBlockEnd(loc, insideIfOp.getElseBlock());
body(bodyBuilder);
} else {
auto bodyBuilder =
ImplicitLocOpBuilder::atBlockEnd(loc, alwaysOp.getBodyBlock());
body(bodyBuilder);
}
if (!emitSeparateAlwaysBlocks) {
alwaysBlocks[key] = {alwaysOp, insideIfOp};
}
}