mirror of https://github.com/llvm/circt.git
861 lines
31 KiB
C++
861 lines
31 KiB
C++
//===- LowerSeqToSV.cpp - Seq to SV lowering ------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This transform translate Seq ops to SV.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "circt/Conversion/SeqToSV.h"
|
|
#include "FirMemLowering.h"
|
|
#include "FirRegLowering.h"
|
|
#include "circt/Dialect/Comb/CombOps.h"
|
|
#include "circt/Dialect/Emit/EmitOps.h"
|
|
#include "circt/Dialect/HW/ConversionPatterns.h"
|
|
#include "circt/Dialect/HW/HWOps.h"
|
|
#include "circt/Dialect/HW/HWTypes.h"
|
|
#include "circt/Dialect/SV/SVAttributes.h"
|
|
#include "circt/Dialect/SV/SVOps.h"
|
|
#include "circt/Dialect/Seq/SeqOps.h"
|
|
#include "circt/Support/Naming.h"
|
|
#include "mlir/IR/Builders.h"
|
|
#include "mlir/IR/ImplicitLocOpBuilder.h"
|
|
#include "mlir/IR/Threading.h"
|
|
#include "mlir/Pass/Pass.h"
|
|
#include "mlir/Transforms/DialectConversion.h"
|
|
|
|
#define DEBUG_TYPE "lower-seq-to-sv"
|
|
|
|
using namespace circt;
|
|
using namespace seq;
|
|
using hw::HWModuleOp;
|
|
using llvm::MapVector;
|
|
|
|
namespace circt {
|
|
#define GEN_PASS_DEF_LOWERSEQTOSV
|
|
#include "circt/Conversion/Passes.h.inc"
|
|
|
|
struct SeqToSVPass : public impl::LowerSeqToSVBase<SeqToSVPass> {
|
|
|
|
void runOnOperation() override;
|
|
|
|
using LowerSeqToSVBase<SeqToSVPass>::lowerToAlwaysFF;
|
|
using LowerSeqToSVBase<SeqToSVPass>::disableRegRandomization;
|
|
using LowerSeqToSVBase<SeqToSVPass>::emitSeparateAlwaysBlocks;
|
|
using LowerSeqToSVBase<SeqToSVPass>::LowerSeqToSVBase;
|
|
using LowerSeqToSVBase<SeqToSVPass>::numSubaccessRestored;
|
|
};
|
|
} // namespace circt
|
|
|
|
namespace {
|
|
struct ModuleLoweringState {
|
|
ModuleLoweringState(HWModuleOp module)
|
|
: immutableValueLowering(module), module(module) {}
|
|
|
|
struct ImmutableValueLowering {
|
|
ImmutableValueLowering(hw::HWModuleOp module) : module(module) {}
|
|
|
|
// Lower initial ops.
|
|
LogicalResult lower();
|
|
LogicalResult lower(seq::InitialOp initialOp);
|
|
|
|
Value
|
|
lookupImmutableValue(mlir::TypedValue<seq::ImmutableType> immut) const {
|
|
return mapping.lookup(immut);
|
|
}
|
|
|
|
sv::InitialOp getSVInitial() const { return svInitialOp; }
|
|
|
|
private:
|
|
sv::InitialOp svInitialOp = {};
|
|
// A mapping from a dummy immutable value to the actual initial value
|
|
// defined in SV initial op.
|
|
MapVector<mlir::TypedValue<seq::ImmutableType>, Value> mapping;
|
|
|
|
hw::HWModuleOp module;
|
|
} immutableValueLowering;
|
|
|
|
struct FragmentInfo {
|
|
bool needsRegFragment = false;
|
|
} fragment;
|
|
|
|
HWModuleOp module;
|
|
};
|
|
|
|
LogicalResult ModuleLoweringState::ImmutableValueLowering::lower() {
|
|
auto result = mergeInitialOps(module.getBodyBlock());
|
|
if (failed(result))
|
|
return failure();
|
|
|
|
auto initialOp = *result;
|
|
if (!initialOp)
|
|
return success();
|
|
|
|
return lower(initialOp);
|
|
}
|
|
|
|
LogicalResult
|
|
ModuleLoweringState::ImmutableValueLowering::lower(seq::InitialOp initialOp) {
|
|
OpBuilder builder = OpBuilder::atBlockBegin(module.getBodyBlock());
|
|
if (!svInitialOp)
|
|
svInitialOp = sv::InitialOp::create(builder, initialOp->getLoc());
|
|
// Initial ops are merged to single one and must not have operands.
|
|
assert(initialOp.getNumOperands() == 0 &&
|
|
"initial op should have no operands");
|
|
|
|
auto loc = initialOp.getLoc();
|
|
llvm::SmallVector<Value> results;
|
|
|
|
auto yieldOp = cast<seq::YieldOp>(initialOp.getBodyBlock()->getTerminator());
|
|
|
|
for (auto [result, operand] :
|
|
llvm::zip(initialOp.getResults(), yieldOp->getOperands())) {
|
|
auto placeholder =
|
|
mlir::UnrealizedConversionCastOp::create(
|
|
builder, loc, ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
|
|
->getResult(0);
|
|
result.replaceAllUsesWith(placeholder);
|
|
mapping.insert(
|
|
{cast<mlir::TypedValue<seq ::ImmutableType>>(placeholder), operand});
|
|
}
|
|
|
|
svInitialOp.getBodyBlock()->getOperations().splice(
|
|
svInitialOp.end(), initialOp.getBodyBlock()->getOperations());
|
|
|
|
assert(initialOp->use_empty());
|
|
initialOp.erase();
|
|
yieldOp->erase();
|
|
return success();
|
|
}
|
|
|
|
/// Lower CompRegOp to `sv.reg` and `sv.alwaysff`. Use a posedge clock and
|
|
/// synchronous reset.
|
|
template <typename OpTy>
|
|
class CompRegLower : public OpConversionPattern<OpTy> {
|
|
public:
|
|
CompRegLower(
|
|
TypeConverter &typeConverter, MLIRContext *context, bool lowerToAlwaysFF,
|
|
const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates)
|
|
: OpConversionPattern<OpTy>(typeConverter, context),
|
|
lowerToAlwaysFF(lowerToAlwaysFF),
|
|
moduleLoweringStates(moduleLoweringStates) {}
|
|
|
|
using OpAdaptor = typename OpConversionPattern<OpTy>::OpAdaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(OpTy reg, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = reg.getLoc();
|
|
|
|
auto regTy =
|
|
ConversionPattern::getTypeConverter()->convertType(reg.getType());
|
|
auto svReg = sv::RegOp::create(rewriter, loc, regTy, reg.getNameAttr(),
|
|
reg.getInnerSymAttr());
|
|
|
|
svReg->setDialectAttrs(reg->getDialectAttrs());
|
|
|
|
circt::sv::setSVAttributes(svReg, circt::sv::getSVAttributes(reg));
|
|
|
|
auto regVal = sv::ReadInOutOp::create(rewriter, loc, svReg);
|
|
|
|
auto assignValue = [&] {
|
|
createAssign(rewriter, reg.getLoc(), svReg, reg);
|
|
};
|
|
auto assignReset = [&] {
|
|
sv::PAssignOp::create(rewriter, loc, svReg, adaptor.getResetValue());
|
|
};
|
|
|
|
// Registers written in an `always_ff` process may not have any assignments
|
|
// outside of that process.
|
|
// For some tools this also prohibits inititalization.
|
|
bool mayLowerToAlwaysFF = lowerToAlwaysFF && !reg.getInitialValue();
|
|
|
|
if (adaptor.getReset() && adaptor.getResetValue()) {
|
|
if (mayLowerToAlwaysFF) {
|
|
sv::AlwaysFFOp::create(rewriter, loc, sv::EventControl::AtPosEdge,
|
|
adaptor.getClk(), sv::ResetType::SyncReset,
|
|
sv::EventControl::AtPosEdge, adaptor.getReset(),
|
|
assignValue, assignReset);
|
|
} else {
|
|
sv::AlwaysOp::create(
|
|
rewriter, loc, sv::EventControl::AtPosEdge, adaptor.getClk(), [&] {
|
|
sv::IfOp::create(rewriter, loc, adaptor.getReset(), assignReset,
|
|
assignValue);
|
|
});
|
|
}
|
|
} else {
|
|
if (mayLowerToAlwaysFF) {
|
|
sv::AlwaysFFOp::create(rewriter, loc, sv::EventControl::AtPosEdge,
|
|
adaptor.getClk(), assignValue);
|
|
} else {
|
|
sv::AlwaysOp::create(rewriter, loc, sv::EventControl::AtPosEdge,
|
|
adaptor.getClk(), assignValue);
|
|
}
|
|
}
|
|
|
|
// Lower initial values.
|
|
if (auto init = reg.getInitialValue()) {
|
|
auto module = reg->template getParentOfType<hw::HWModuleOp>();
|
|
const auto &initial =
|
|
moduleLoweringStates.find(module.getModuleNameAttr())
|
|
->second.immutableValueLowering;
|
|
|
|
Value initialValue = initial.lookupImmutableValue(init);
|
|
|
|
if (auto op = initialValue.getDefiningOp();
|
|
op && op->hasTrait<mlir::OpTrait::ConstantLike>()) {
|
|
auto clonedConstant = rewriter.clone(*op);
|
|
rewriter.moveOpBefore(clonedConstant, svReg);
|
|
svReg.getInitMutable().assign(clonedConstant->getResult(0));
|
|
} else {
|
|
OpBuilder::InsertionGuard guard(rewriter);
|
|
auto in = initial.getSVInitial();
|
|
rewriter.setInsertionPointToEnd(in.getBodyBlock());
|
|
sv::BPAssignOp::create(rewriter, reg->getLoc(), svReg, initialValue);
|
|
}
|
|
}
|
|
|
|
rewriter.replaceOp(reg, regVal);
|
|
return success();
|
|
}
|
|
|
|
// Helper to create an assignment based on the register type.
|
|
void createAssign(ConversionPatternRewriter &rewriter, Location loc,
|
|
sv::RegOp svReg, OpAdaptor reg) const;
|
|
|
|
private:
|
|
bool lowerToAlwaysFF;
|
|
const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates;
|
|
};
|
|
|
|
/// Create the assign.
|
|
template <>
|
|
void CompRegLower<CompRegOp>::createAssign(ConversionPatternRewriter &rewriter,
|
|
Location loc, sv::RegOp svReg,
|
|
OpAdaptor reg) const {
|
|
sv::PAssignOp::create(rewriter, loc, svReg, reg.getInput());
|
|
}
|
|
/// Create the assign inside of an if block.
|
|
template <>
|
|
void CompRegLower<CompRegClockEnabledOp>::createAssign(
|
|
ConversionPatternRewriter &rewriter, Location loc, sv::RegOp svReg,
|
|
OpAdaptor reg) const {
|
|
sv::IfOp::create(rewriter, loc, reg.getClockEnable(), [&]() {
|
|
sv::PAssignOp::create(rewriter, loc, svReg, reg.getInput());
|
|
});
|
|
}
|
|
|
|
/// Lower FromImmutable to `sv.reg` and `sv.initial`.
|
|
class FromImmutableLowering : public OpConversionPattern<FromImmutableOp> {
|
|
public:
|
|
FromImmutableLowering(
|
|
TypeConverter &typeConverter, MLIRContext *context,
|
|
const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates)
|
|
: OpConversionPattern<FromImmutableOp>(typeConverter, context),
|
|
moduleLoweringStates(moduleLoweringStates) {}
|
|
|
|
using OpAdaptor = typename OpConversionPattern<FromImmutableOp>::OpAdaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(FromImmutableOp fromImmutableOp, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = fromImmutableOp.getLoc();
|
|
|
|
auto regTy = ConversionPattern::getTypeConverter()->convertType(
|
|
fromImmutableOp.getType());
|
|
auto svReg = sv::RegOp::create(rewriter, loc, regTy);
|
|
|
|
auto regVal = sv::ReadInOutOp::create(rewriter, loc, svReg);
|
|
|
|
// Lower initial values.
|
|
auto module = fromImmutableOp->template getParentOfType<hw::HWModuleOp>();
|
|
const auto &initial = moduleLoweringStates.find(module.getModuleNameAttr())
|
|
->second.immutableValueLowering;
|
|
|
|
Value initialValue =
|
|
initial.lookupImmutableValue(fromImmutableOp.getInput());
|
|
|
|
OpBuilder::InsertionGuard guard(rewriter);
|
|
auto in = initial.getSVInitial();
|
|
rewriter.setInsertionPointToEnd(in.getBodyBlock());
|
|
sv::BPAssignOp::create(rewriter, fromImmutableOp->getLoc(), svReg,
|
|
initialValue);
|
|
|
|
rewriter.replaceOp(fromImmutableOp, regVal);
|
|
return success();
|
|
}
|
|
|
|
private:
|
|
const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates;
|
|
};
|
|
// Lower seq.clock_gate to a fairly standard clock gate implementation.
|
|
//
|
|
class ClockGateLowering : public OpConversionPattern<ClockGateOp> {
|
|
public:
|
|
using OpConversionPattern<ClockGateOp>::OpConversionPattern;
|
|
using OpAdaptor = typename OpConversionPattern<ClockGateOp>::OpAdaptor;
|
|
LogicalResult
|
|
matchAndRewrite(ClockGateOp clockGate, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto loc = clockGate.getLoc();
|
|
Value clk = adaptor.getInput();
|
|
|
|
// enable in
|
|
Value enable = adaptor.getEnable();
|
|
if (auto te = adaptor.getTestEnable())
|
|
enable = comb::OrOp::create(rewriter, loc, enable, te);
|
|
|
|
// Enable latch.
|
|
Value enableLatch =
|
|
sv::RegOp::create(rewriter, loc, rewriter.getI1Type(),
|
|
rewriter.getStringAttr("cg_en_latch"));
|
|
|
|
// Latch the enable signal using an always @* block.
|
|
sv::AlwaysOp::create(
|
|
rewriter, loc, llvm::SmallVector<sv::EventControl>{},
|
|
llvm::SmallVector<Value>{}, [&]() {
|
|
sv::IfOp::create(
|
|
rewriter, loc, comb::createOrFoldNot(loc, clk, rewriter), [&]() {
|
|
sv::PAssignOp::create(rewriter, loc, enableLatch, enable);
|
|
});
|
|
});
|
|
|
|
// Create the gated clock signal.
|
|
rewriter.replaceOpWithNewOp<comb::AndOp>(
|
|
clockGate, clk, sv::ReadInOutOp::create(rewriter, loc, enableLatch));
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// Lower seq.clock_inv to a regular inverter.
|
|
//
|
|
class ClockInverterLowering : public OpConversionPattern<ClockInverterOp> {
|
|
public:
|
|
using OpConversionPattern<ClockInverterOp>::OpConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(ClockInverterOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto loc = op.getLoc();
|
|
Value clk = adaptor.getInput();
|
|
|
|
StringAttr name = op->getAttrOfType<StringAttr>("sv.namehint");
|
|
Value one = hw::ConstantOp::create(rewriter, loc, APInt(1, 1));
|
|
auto newOp = rewriter.replaceOpWithNewOp<comb::XorOp>(op, clk, one);
|
|
if (name)
|
|
rewriter.modifyOpInPlace(newOp,
|
|
[&] { newOp->setAttr("sv.namehint", name); });
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// Lower seq.clock_mux to a `comb.mux` op
|
|
//
|
|
class ClockMuxLowering : public OpConversionPattern<ClockMuxOp> {
|
|
public:
|
|
using OpConversionPattern<ClockMuxOp>::OpConversionPattern;
|
|
using OpConversionPattern<ClockMuxOp>::OpAdaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(ClockMuxOp clockMux, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
rewriter.replaceOpWithNewOp<comb::MuxOp>(clockMux, adaptor.getCond(),
|
|
adaptor.getTrueClock(),
|
|
adaptor.getFalseClock(), true);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
/// Map `seq.clock` to `i1`.
|
|
struct SeqToSVTypeConverter : public TypeConverter {
|
|
SeqToSVTypeConverter() {
|
|
addConversion([&](Type type) { return type; });
|
|
addConversion([&](seq::ImmutableType type) { return type.getInnerType(); });
|
|
addConversion([&](seq::ClockType type) {
|
|
return IntegerType::get(type.getContext(), 1);
|
|
});
|
|
addConversion([&](hw::StructType structTy) {
|
|
bool changed = false;
|
|
|
|
SmallVector<hw::StructType::FieldInfo> newFields;
|
|
for (auto field : structTy.getElements()) {
|
|
auto &newField = newFields.emplace_back();
|
|
newField.name = field.name;
|
|
newField.type = convertType(field.type);
|
|
if (field.type != newField.type)
|
|
changed = true;
|
|
}
|
|
|
|
if (!changed)
|
|
return structTy;
|
|
|
|
return hw::StructType::get(structTy.getContext(), newFields);
|
|
});
|
|
addConversion([&](hw::ArrayType arrayTy) {
|
|
auto elementTy = arrayTy.getElementType();
|
|
auto newElementTy = convertType(elementTy);
|
|
if (elementTy != newElementTy)
|
|
return hw::ArrayType::get(newElementTy, arrayTy.getNumElements());
|
|
return arrayTy;
|
|
});
|
|
|
|
addTargetMaterialization([&](mlir::OpBuilder &builder,
|
|
mlir::Type resultType, mlir::ValueRange inputs,
|
|
mlir::Location loc) -> mlir::Value {
|
|
if (inputs.size() != 1)
|
|
return Value();
|
|
return mlir::UnrealizedConversionCastOp::create(builder, loc, resultType,
|
|
inputs[0])
|
|
->getResult(0);
|
|
});
|
|
|
|
addSourceMaterialization([&](mlir::OpBuilder &builder,
|
|
mlir::Type resultType, mlir::ValueRange inputs,
|
|
mlir::Location loc) -> mlir::Value {
|
|
if (inputs.size() != 1)
|
|
return Value();
|
|
return mlir::UnrealizedConversionCastOp::create(builder, loc, resultType,
|
|
inputs[0])
|
|
->getResult(0);
|
|
});
|
|
}
|
|
};
|
|
|
|
/// Eliminate no-op clock casts.
|
|
template <typename T>
|
|
class ClockCastLowering : public OpConversionPattern<T> {
|
|
public:
|
|
using OpConversionPattern<T>::OpConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(T op, typename T::Adaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
// If the cast had a better name than its input, propagate it.
|
|
if (Operation *inputOp = adaptor.getInput().getDefiningOp())
|
|
if (!isa<mlir::UnrealizedConversionCastOp>(inputOp))
|
|
if (auto name = chooseName(op, inputOp))
|
|
rewriter.modifyOpInPlace(
|
|
inputOp, [&] { inputOp->setAttr("sv.namehint", name); });
|
|
|
|
rewriter.replaceOp(op, adaptor.getInput());
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// Lower seq.const_clock to `hw.constant`
|
|
//
|
|
class ClockConstLowering : public OpConversionPattern<ConstClockOp> {
|
|
public:
|
|
using OpConversionPattern<ConstClockOp>::OpConversionPattern;
|
|
using OpConversionPattern<ConstClockOp>::OpAdaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(ConstClockOp clockConst, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
rewriter.replaceOpWithNewOp<hw::ConstantOp>(
|
|
clockConst, APInt(1, clockConst.getValue() == ClockConst::High));
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class AggregateConstantPattern
|
|
: public OpConversionPattern<hw::AggregateConstantOp> {
|
|
public:
|
|
using OpConversionPattern<hw::AggregateConstantOp>::OpConversionPattern;
|
|
using OpConversionPattern<hw::AggregateConstantOp>::OpAdaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(hw::AggregateConstantOp aggregateConstant, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto newType = typeConverter->convertType(aggregateConstant.getType());
|
|
auto newAttr = aggregateConstant.getFieldsAttr().replace(
|
|
[](seq::ClockConstAttr clockConst) {
|
|
return mlir::IntegerAttr::get(
|
|
mlir::IntegerType::get(clockConst.getContext(), 1),
|
|
APInt(1, clockConst.getValue() == ClockConst::High));
|
|
});
|
|
rewriter.replaceOpWithNewOp<hw::AggregateConstantOp>(
|
|
aggregateConstant, newType, cast<ArrayAttr>(newAttr));
|
|
return success();
|
|
}
|
|
};
|
|
|
|
/// Lower `seq.clock_div` to a behavioural clock divider
|
|
///
|
|
class ClockDividerLowering : public OpConversionPattern<ClockDividerOp> {
|
|
public:
|
|
using OpConversionPattern<ClockDividerOp>::OpConversionPattern;
|
|
using OpConversionPattern<ClockDividerOp>::OpAdaptor;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(ClockDividerOp clockDiv, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
Location loc = clockDiv.getLoc();
|
|
|
|
Value one;
|
|
if (clockDiv.getPow2()) {
|
|
one = hw::ConstantOp::create(rewriter, loc, APInt(1, 1));
|
|
}
|
|
|
|
Value output = clockDiv.getInput();
|
|
|
|
SmallVector<Value> regs;
|
|
for (unsigned i = 0; i < clockDiv.getPow2(); ++i) {
|
|
Value reg = sv::RegOp::create(
|
|
rewriter, loc, rewriter.getI1Type(),
|
|
rewriter.getStringAttr("clock_out_" + std::to_string(i)));
|
|
regs.push_back(reg);
|
|
|
|
sv::AlwaysOp::create(
|
|
rewriter, loc, sv::EventControl::AtPosEdge, output, [&] {
|
|
Value outputVal = sv::ReadInOutOp::create(rewriter, loc, reg);
|
|
Value inverted = comb::XorOp::create(rewriter, loc, outputVal, one);
|
|
sv::BPAssignOp::create(rewriter, loc, reg, inverted);
|
|
});
|
|
|
|
output = sv::ReadInOutOp::create(rewriter, loc, reg);
|
|
}
|
|
|
|
if (!regs.empty()) {
|
|
Value zero = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
|
|
sv::InitialOp::create(rewriter, loc, [&] {
|
|
for (Value reg : regs) {
|
|
sv::BPAssignOp::create(rewriter, loc, reg, zero);
|
|
}
|
|
});
|
|
}
|
|
|
|
rewriter.replaceOp(clockDiv, output);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// NOLINTBEGIN(misc-no-recursion)
|
|
static bool isLegalType(Type ty) {
|
|
if (hw::type_isa<ClockType>(ty))
|
|
return false;
|
|
|
|
if (auto arrayTy = hw::type_dyn_cast<hw::ArrayType>(ty))
|
|
return isLegalType(arrayTy.getElementType());
|
|
|
|
if (auto structTy = hw::type_dyn_cast<hw::StructType>(ty)) {
|
|
for (auto field : structTy.getElements())
|
|
if (!isLegalType(field.type))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// NOLINTEND(misc-no-recursion)
|
|
|
|
static bool isLegalOp(Operation *op) {
|
|
if (auto module = dyn_cast<hw::HWModuleLike>(op)) {
|
|
for (auto port : module.getHWModuleType().getPorts())
|
|
if (!isLegalType(port.type))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
if (auto hwAggregateConstantOp = dyn_cast<hw::AggregateConstantOp>(op)) {
|
|
bool foundClockAttr = false;
|
|
hwAggregateConstantOp.getFieldsAttr().walk(
|
|
[&](seq::ClockConstAttr attr) { foundClockAttr = true; });
|
|
if (foundClockAttr)
|
|
return false;
|
|
}
|
|
|
|
bool allOperandsLowered = llvm::all_of(
|
|
op->getOperands(), [](auto op) { return isLegalType(op.getType()); });
|
|
bool allResultsLowered = llvm::all_of(op->getResults(), [](auto result) {
|
|
return isLegalType(result.getType());
|
|
});
|
|
return allOperandsLowered && allResultsLowered;
|
|
}
|
|
|
|
void SeqToSVPass::runOnOperation() {
|
|
auto circuit = getOperation();
|
|
MLIRContext *context = &getContext();
|
|
|
|
auto modules = llvm::to_vector(circuit.getOps<HWModuleOp>());
|
|
|
|
FirMemLowering memLowering(circuit);
|
|
|
|
// Identify memories and group them by module.
|
|
auto uniqueMems = memLowering.collectMemories(modules);
|
|
MapVector<HWModuleOp, SmallVector<FirMemLowering::MemoryConfig>> memsByModule;
|
|
SmallVector<HWModuleGeneratedOp> generatedModules;
|
|
for (auto &[config, memOps] : uniqueMems) {
|
|
// Create the `HWModuleGeneratedOp`s for each unique configuration.
|
|
auto genOp = memLowering.createMemoryModule(config, memOps);
|
|
generatedModules.push_back(genOp);
|
|
|
|
// Group memories by their parent module for parallelism.
|
|
for (auto memOp : memOps) {
|
|
auto parent = memOp->getParentOfType<HWModuleOp>();
|
|
memsByModule[parent].emplace_back(&config, genOp, memOp);
|
|
}
|
|
}
|
|
|
|
// Any register that is "buried" inside an ifdef, will need a hierpath for
|
|
// building the initialization/randomization IR. Do that here. This path
|
|
// table will be used by the per-module FirRegLowering routine. This is done
|
|
// single-threaded to ensure symbol names are deterministic.
|
|
auto pathTable = FirRegLowering::createPaths(circuit);
|
|
|
|
// Lower memories and registers in modules in parallel.
|
|
std::atomic<bool> needsRegRandomization = false;
|
|
std::atomic<bool> needsMemRandomization = false;
|
|
|
|
MapVector<StringAttr, ModuleLoweringState> moduleLoweringStates;
|
|
for (auto module : circuit.getOps<HWModuleOp>())
|
|
moduleLoweringStates.try_emplace(module.getModuleNameAttr(),
|
|
ModuleLoweringState(module));
|
|
|
|
auto result = mlir::failableParallelForEach(
|
|
&getContext(), moduleLoweringStates, [&](auto &moduleAndState) {
|
|
auto &state = moduleAndState.second;
|
|
auto module = state.module;
|
|
SeqToSVTypeConverter typeConverter;
|
|
FirRegLowering regLowering(typeConverter, module, pathTable,
|
|
disableRegRandomization,
|
|
emitSeparateAlwaysBlocks);
|
|
regLowering.lower();
|
|
if (regLowering.needsRegRandomization()) {
|
|
if (!disableRegRandomization) {
|
|
state.fragment.needsRegFragment = true;
|
|
}
|
|
needsRegRandomization = true;
|
|
}
|
|
numSubaccessRestored += regLowering.numSubaccessRestored;
|
|
|
|
if (auto *it = memsByModule.find(module); it != memsByModule.end()) {
|
|
memLowering.lowerMemoriesInModule(module, it->second);
|
|
// Generated memories need register randomization since `HWMemSimImpl`
|
|
// may add registers.
|
|
needsMemRandomization = true;
|
|
needsRegRandomization = true;
|
|
}
|
|
return state.immutableValueLowering.lower();
|
|
});
|
|
|
|
if (failed(result))
|
|
return signalPassFailure();
|
|
|
|
auto randomInitFragmentName =
|
|
FlatSymbolRefAttr::get(context, "RANDOM_INIT_FRAGMENT");
|
|
auto randomInitRegFragmentName =
|
|
FlatSymbolRefAttr::get(context, "RANDOM_INIT_REG_FRAGMENT");
|
|
auto randomInitMemFragmentName =
|
|
FlatSymbolRefAttr::get(context, "RANDOM_INIT_MEM_FRAGMENT");
|
|
|
|
for (auto &[_, state] : moduleLoweringStates) {
|
|
const auto &info = state.fragment;
|
|
// Do not add fragments if not needed.
|
|
if (!info.needsRegFragment) {
|
|
continue;
|
|
}
|
|
|
|
SmallVector<Attribute> fragmentAttrs;
|
|
auto module = state.module;
|
|
if (auto others =
|
|
module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
|
|
fragmentAttrs = llvm::to_vector(others);
|
|
|
|
if (info.needsRegFragment) {
|
|
fragmentAttrs.push_back(randomInitRegFragmentName);
|
|
fragmentAttrs.push_back(randomInitFragmentName);
|
|
}
|
|
|
|
module->setAttr(emit::getFragmentsAttrName(),
|
|
ArrayAttr::get(context, fragmentAttrs));
|
|
}
|
|
|
|
// Set fragments for generated modules.
|
|
SmallVector<Attribute> genModFragments;
|
|
if (!disableRegRandomization)
|
|
genModFragments.push_back(randomInitRegFragmentName);
|
|
if (!disableMemRandomization)
|
|
genModFragments.push_back(randomInitMemFragmentName);
|
|
if (!genModFragments.empty()) {
|
|
genModFragments.push_back(randomInitFragmentName);
|
|
auto fragmentAttr = ArrayAttr::get(context, genModFragments);
|
|
for (auto genOp : generatedModules)
|
|
genOp->setAttr(emit::getFragmentsAttrName(), fragmentAttr);
|
|
}
|
|
|
|
// Mark all ops which can have clock types as illegal.
|
|
SeqToSVTypeConverter typeConverter;
|
|
ConversionTarget target(*context);
|
|
target.addIllegalDialect<SeqDialect>();
|
|
target.markUnknownOpDynamicallyLegal(isLegalOp);
|
|
|
|
RewritePatternSet patterns(context);
|
|
patterns.add<CompRegLower<CompRegOp>>(typeConverter, context, lowerToAlwaysFF,
|
|
moduleLoweringStates);
|
|
patterns.add<CompRegLower<CompRegClockEnabledOp>>(
|
|
typeConverter, context, lowerToAlwaysFF, moduleLoweringStates);
|
|
patterns.add<FromImmutableLowering>(typeConverter, context,
|
|
moduleLoweringStates);
|
|
patterns.add<ClockCastLowering<seq::FromClockOp>>(typeConverter, context);
|
|
patterns.add<ClockCastLowering<seq::ToClockOp>>(typeConverter, context);
|
|
patterns.add<ClockGateLowering>(typeConverter, context);
|
|
patterns.add<ClockInverterLowering>(typeConverter, context);
|
|
patterns.add<ClockMuxLowering>(typeConverter, context);
|
|
patterns.add<ClockDividerLowering>(typeConverter, context);
|
|
patterns.add<ClockConstLowering>(typeConverter, context);
|
|
patterns.add<TypeConversionPattern>(typeConverter, context);
|
|
patterns.add<AggregateConstantPattern>(typeConverter, context);
|
|
|
|
if (failed(applyPartialConversion(circuit, target, std::move(patterns))))
|
|
signalPassFailure();
|
|
|
|
auto loc = UnknownLoc::get(context);
|
|
auto b = ImplicitLocOpBuilder::atBlockBegin(loc, circuit.getBody());
|
|
if (needsRegRandomization || needsMemRandomization) {
|
|
sv::MacroDeclOp::create(b, "ENABLE_INITIAL_REG_");
|
|
sv::MacroDeclOp::create(b, "ENABLE_INITIAL_MEM_");
|
|
if (needsRegRandomization) {
|
|
sv::MacroDeclOp::create(b, "FIRRTL_BEFORE_INITIAL");
|
|
sv::MacroDeclOp::create(b, "FIRRTL_AFTER_INITIAL");
|
|
}
|
|
if (needsMemRandomization)
|
|
sv::MacroDeclOp::create(b, "RANDOMIZE_MEM_INIT");
|
|
sv::MacroDeclOp::create(b, "RANDOMIZE_REG_INIT");
|
|
sv::MacroDeclOp::create(b, "RANDOMIZE");
|
|
sv::MacroDeclOp::create(b, "RANDOMIZE_DELAY");
|
|
sv::MacroDeclOp::create(b, "RANDOM");
|
|
sv::MacroDeclOp::create(b, "INIT_RANDOM");
|
|
sv::MacroDeclOp::create(b, "INIT_RANDOM_PROLOG_");
|
|
}
|
|
|
|
bool hasRegRandomization = needsRegRandomization && !disableRegRandomization;
|
|
bool hasMemRandomization = needsMemRandomization && !disableMemRandomization;
|
|
if (!hasRegRandomization && !hasMemRandomization)
|
|
return;
|
|
|
|
// Build macros for FIRRTL-style register and memory initialization.
|
|
// Insert them at the start of the module, after any other verbatims.
|
|
for (Operation &op : *circuit.getBody()) {
|
|
if (!isa<sv::VerbatimOp, sv::IfDefOp>(&op)) {
|
|
b.setInsertionPoint(&op);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Create SYNTHESIS/VERILATOR macros if other passes have not done so already.
|
|
{
|
|
StringSet<> symbols;
|
|
for (auto sym : circuit.getOps<sv::MacroDeclOp>())
|
|
symbols.insert(sym.getName());
|
|
if (!symbols.count("SYNTHESIS"))
|
|
sv::MacroDeclOp::create(b, "SYNTHESIS");
|
|
if (!symbols.count("VERILATOR"))
|
|
sv::MacroDeclOp::create(b, "VERILATOR");
|
|
}
|
|
|
|
// TODO: We could have an operation for macros and uses of them, and
|
|
// even turn them into symbols so we can DCE unused macro definitions.
|
|
auto emitGuardedDefine = [&](StringRef guard, StringRef defName,
|
|
StringRef defineTrue = "",
|
|
StringRef defineFalse = StringRef()) {
|
|
if (!defineFalse.data()) {
|
|
assert(defineTrue.data() && "didn't define anything");
|
|
sv::IfDefOp::create(
|
|
b, guard, [&]() { sv::MacroDefOp::create(b, defName, defineTrue); });
|
|
} else {
|
|
sv::IfDefOp::create(
|
|
b, guard,
|
|
[&]() {
|
|
if (defineTrue.data())
|
|
sv::MacroDefOp::create(b, defName, defineTrue);
|
|
},
|
|
[&]() { sv::MacroDefOp::create(b, defName, defineFalse); });
|
|
}
|
|
};
|
|
|
|
// Helper function to emit #ifndef guard.
|
|
auto emitGuard = [&](const char *guard, llvm::function_ref<void(void)> body) {
|
|
sv::IfDefOp::create(
|
|
b, guard, []() {}, body);
|
|
};
|
|
|
|
emit::FragmentOp::create(b, randomInitFragmentName.getAttr(), [&] {
|
|
sv::VerbatimOp::create(b,
|
|
"// Standard header to adapt well known macros for "
|
|
"register randomization.");
|
|
|
|
sv::VerbatimOp::create(
|
|
b, "\n// RANDOM may be set to an expression that produces a 32-bit "
|
|
"random unsigned value.");
|
|
emitGuardedDefine("RANDOM", "RANDOM", StringRef(), "$random");
|
|
|
|
sv::VerbatimOp::create(
|
|
b, "\n// Users can define INIT_RANDOM as general code that gets "
|
|
"injected "
|
|
"into the\n// initializer block for modules with registers.");
|
|
emitGuardedDefine("INIT_RANDOM", "INIT_RANDOM", StringRef(), "");
|
|
|
|
sv::VerbatimOp::create(
|
|
b, "\n// If using random initialization, you can also define "
|
|
"RANDOMIZE_DELAY to\n// customize the delay used, otherwise 0.002 "
|
|
"is used.");
|
|
emitGuardedDefine("RANDOMIZE_DELAY", "RANDOMIZE_DELAY", StringRef(),
|
|
"0.002");
|
|
|
|
sv::VerbatimOp::create(
|
|
b, "\n// Define INIT_RANDOM_PROLOG_ for use in our modules below.");
|
|
emitGuard("INIT_RANDOM_PROLOG_", [&]() {
|
|
sv::IfDefOp::create(
|
|
b, "RANDOMIZE",
|
|
[&]() {
|
|
emitGuardedDefine("VERILATOR", "INIT_RANDOM_PROLOG_",
|
|
"`INIT_RANDOM",
|
|
"`INIT_RANDOM #`RANDOMIZE_DELAY begin end");
|
|
},
|
|
[&]() { sv::MacroDefOp::create(b, "INIT_RANDOM_PROLOG_", ""); });
|
|
});
|
|
});
|
|
|
|
if (hasMemRandomization) {
|
|
emit::FragmentOp::create(b, randomInitMemFragmentName.getAttr(), [&] {
|
|
sv::VerbatimOp::create(b, "\n// Include rmemory initializers in init "
|
|
"blocks unless synthesis is set");
|
|
emitGuard("RANDOMIZE", [&]() {
|
|
emitGuardedDefine("RANDOMIZE_MEM_INIT", "RANDOMIZE");
|
|
});
|
|
emitGuard("SYNTHESIS", [&] {
|
|
emitGuardedDefine("ENABLE_INITIAL_MEM_", "ENABLE_INITIAL_MEM_",
|
|
StringRef(), "");
|
|
});
|
|
sv::VerbatimOp::create(b, "");
|
|
});
|
|
}
|
|
|
|
if (hasRegRandomization) {
|
|
emit::FragmentOp::create(b, randomInitRegFragmentName.getAttr(), [&] {
|
|
sv::VerbatimOp::create(b, "\n// Include register initializers in init "
|
|
"blocks unless synthesis is set");
|
|
emitGuard("RANDOMIZE", [&]() {
|
|
emitGuardedDefine("RANDOMIZE_REG_INIT", "RANDOMIZE");
|
|
});
|
|
emitGuard("SYNTHESIS", [&] {
|
|
emitGuardedDefine("ENABLE_INITIAL_REG_", "ENABLE_INITIAL_REG_",
|
|
StringRef(), "");
|
|
});
|
|
sv::VerbatimOp::create(b, "");
|
|
});
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<Pass>
|
|
circt::createLowerSeqToSVPass(const LowerSeqToSVOptions &options) {
|
|
return std::make_unique<SeqToSVPass>(options);
|
|
}
|