circt/lib/Conversion/SeqToSV/SeqToSV.cpp

556 lines
19 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 "../PassDetail.h"
#include "FirMemLowering.h"
#include "FirRegLowering.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/HW/ConversionPatterns.h"
#include "circt/Dialect/HW/HWAttributes.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/DialectImplementation.h"
#include "mlir/IR/ImplicitLocOpBuilder.h"
#include "mlir/IR/Threading.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/IntervalMap.h"
#include "llvm/ADT/TypeSwitch.h"
#define DEBUG_TYPE "lower-seq-to-sv"
using namespace circt;
using namespace seq;
using hw::HWModuleOp;
using llvm::MapVector;
namespace {
#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;
};
} // anonymous namespace
namespace {
/// 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)
: OpConversionPattern<OpTy>(typeConverter, context),
lowerToAlwaysFF(lowerToAlwaysFF) {}
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 = rewriter.create<sv::RegOp>(loc, regTy, reg.getNameAttr(),
reg.getInnerSymAttr(),
reg.getPowerOnValue());
svReg->setDialectAttrs(reg->getDialectAttrs());
circt::sv::setSVAttributes(svReg, circt::sv::getSVAttributes(reg));
auto regVal = rewriter.create<sv::ReadInOutOp>(loc, svReg);
auto assignValue = [&] {
createAssign(rewriter, reg.getLoc(), svReg, reg);
};
auto assignReset = [&] {
rewriter.create<sv::PAssignOp>(loc, svReg, adaptor.getResetValue());
};
if (adaptor.getReset() && adaptor.getResetValue()) {
if (lowerToAlwaysFF) {
rewriter.create<sv::AlwaysFFOp>(
loc, sv::EventControl::AtPosEdge, adaptor.getClk(),
ResetType::SyncReset, sv::EventControl::AtPosEdge,
adaptor.getReset(), assignValue, assignReset);
} else {
rewriter.create<sv::AlwaysOp>(
loc, sv::EventControl::AtPosEdge, adaptor.getClk(), [&] {
rewriter.create<sv::IfOp>(loc, adaptor.getReset(), assignReset,
assignValue);
});
}
} else {
if (lowerToAlwaysFF) {
rewriter.create<sv::AlwaysFFOp>(loc, sv::EventControl::AtPosEdge,
adaptor.getClk(), assignValue);
} else {
rewriter.create<sv::AlwaysOp>(loc, sv::EventControl::AtPosEdge,
adaptor.getClk(), assignValue);
}
}
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;
};
/// Create the assign.
template <>
void CompRegLower<CompRegOp>::createAssign(ConversionPatternRewriter &rewriter,
Location loc, sv::RegOp svReg,
OpAdaptor reg) const {
rewriter.create<sv::PAssignOp>(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 {
rewriter.create<sv::IfOp>(loc, reg.getClockEnable(), [&]() {
rewriter.create<sv::PAssignOp>(loc, svReg, reg.getInput());
});
}
// 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 = rewriter.create<comb::OrOp>(loc, enable, te);
// Enable latch.
Value enableLatch = rewriter.create<sv::RegOp>(
loc, rewriter.getI1Type(), rewriter.getStringAttr("cg_en_latch"));
// Latch the enable signal using an always @* block.
rewriter.create<sv::AlwaysOp>(
loc, llvm::SmallVector<sv::EventControl>{}, llvm::SmallVector<Value>{},
[&]() {
rewriter.create<sv::IfOp>(
loc, comb::createOrFoldNot(loc, clk, rewriter), [&]() {
rewriter.create<sv::PAssignOp>(loc, enableLatch, enable);
});
});
// Create the gated clock signal.
rewriter.replaceOpWithNewOp<comb::AndOp>(
clockGate, clk, rewriter.create<sv::ReadInOutOp>(loc, enableLatch));
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::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) -> std::optional<mlir::Value> {
if (inputs.size() != 1)
return std::nullopt;
return inputs[0];
});
addSourceMaterialization(
[&](mlir::OpBuilder &builder, mlir::Type resultType,
mlir::ValueRange inputs,
mlir::Location loc) -> std::optional<mlir::Value> {
if (inputs.size() != 1)
return std::nullopt;
return inputs[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();
}
};
/// 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 = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
}
Value output = clockDiv.getClockIn();
SmallVector<Value> regs;
for (unsigned i = 0; i < clockDiv.getPow2(); ++i) {
Value reg = rewriter.create<sv::RegOp>(
loc, rewriter.getI1Type(),
rewriter.getStringAttr("clock_out_" + std::to_string(i)));
regs.push_back(reg);
rewriter.create<sv::AlwaysOp>(
loc, sv::EventControl::AtPosEdge, output, [&] {
Value outputVal = rewriter.create<sv::ReadInOutOp>(loc, reg);
Value inverted = rewriter.create<comb::XorOp>(loc, outputVal, one);
rewriter.create<sv::BPAssignOp>(loc, reg, inverted);
});
output = rewriter.create<sv::ReadInOutOp>(loc, reg);
}
if (!regs.empty()) {
Value zero = rewriter.create<hw::ConstantOp>(loc, APInt(1, 0));
rewriter.create<sv::InitialOp>(loc, [&] {
for (Value reg : regs) {
rewriter.create<sv::BPAssignOp>(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;
}
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;
for (auto &[config, memOps] : uniqueMems) {
// Create the `HWModuleGeneratedOp`s for each unique configuration.
auto genOp = memLowering.createMemoryModule(config, memOps);
// Group memories by their parent module for parallelism.
for (auto memOp : memOps) {
auto parent = memOp->getParentOfType<HWModuleOp>();
memsByModule[parent].emplace_back(&config, genOp, memOp);
}
}
// Lower memories and registers in modules in parallel.
bool needsRegRandomization = false;
mlir::parallelForEach(&getContext(), modules, [&](HWModuleOp module) {
SeqToSVTypeConverter typeConverter;
FirRegLowering regLowering(typeConverter, module, disableRegRandomization,
emitSeparateAlwaysBlocks);
regLowering.lower();
if (regLowering.needsRegRandomization())
needsRegRandomization = true;
numSubaccessRestored += regLowering.numSubaccessRestored;
if (auto *it = memsByModule.find(module); it != memsByModule.end())
memLowering.lowerMemoriesInModule(module, it->second);
});
// 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);
patterns.add<CompRegLower<CompRegClockEnabledOp>>(typeConverter, context,
lowerToAlwaysFF);
patterns.add<ClockCastLowering<seq::FromClockOp>>(typeConverter, context);
patterns.add<ClockCastLowering<seq::ToClockOp>>(typeConverter, context);
patterns.add<ClockGateLowering>(typeConverter, context);
patterns.add<ClockMuxLowering>(typeConverter, context);
patterns.add<ClockDividerLowering>(typeConverter, context);
patterns.add<ClockConstLowering>(typeConverter, context);
patterns.add<TypeConversionPattern>(typeConverter, context);
if (failed(applyPartialConversion(circuit, target, std::move(patterns))))
signalPassFailure();
bool hasRegRandomization = needsRegRandomization && !disableRegRandomization;
bool hasMemRandomization = !memsByModule.empty() && !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.
auto loc = UnknownLoc::get(context);
auto b = ImplicitLocOpBuilder::atBlockBegin(loc, circuit.getBody());
for (Operation &op : *circuit.getBody()) {
if (!isa<sv::VerbatimOp>(&op)) {
b.setInsertionPoint(&op);
break;
}
}
// 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.
StringSet<> emittedDecls;
auto emitDefine = [&](StringRef name, StringRef body, ArrayAttr args = {}) {
if (!emittedDecls.count(name)) {
emittedDecls.insert(name);
OpBuilder::InsertionGuard guard(b);
b.setInsertionPointToStart(circuit.getBody());
b.create<sv::MacroDeclOp>(name, args, StringAttr());
}
b.create<sv::MacroDefOp>(name, body);
};
auto emitGuardedDefine = [&](StringRef guard, StringRef defName,
StringRef defineTrue = "",
StringRef defineFalse = StringRef()) {
if (!defineFalse.data()) {
assert(defineTrue.data() && "didn't define anything");
b.create<sv::IfDefOp>(guard, [&]() { emitDefine(defName, defineTrue); });
} else {
b.create<sv::IfDefOp>(
guard,
[&]() {
if (defineTrue.data())
emitDefine(defName, defineTrue);
},
[&]() { emitDefine(defName, defineFalse); });
}
};
// Helper function to emit #ifndef guard.
auto emitGuard = [&](const char *guard, llvm::function_ref<void(void)> body) {
b.create<sv::IfDefOp>(
guard, []() {}, body);
};
b.create<sv::VerbatimOp>("// Standard header to adapt well known macros for "
"register randomization.");
bool needsRandom = true;
if (hasMemRandomization) {
emitGuard("RANDOMIZE",
[&]() { emitGuardedDefine("RANDOMIZE_MEM_INIT", "RANDOMIZE"); });
needsRandom = true;
}
if (hasRegRandomization) {
emitGuard("RANDOMIZE",
[&]() { emitGuardedDefine("RANDOMIZE_REG_INIT", "RANDOMIZE"); });
needsRandom = true;
}
if (needsRandom) {
b.create<sv::VerbatimOp>(
"\n// RANDOM may be set to an expression that produces a 32-bit "
"random unsigned value.");
emitGuardedDefine("RANDOM", "RANDOM", StringRef(), "$random");
b.create<sv::VerbatimOp>(
"\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(), "");
b.create<sv::VerbatimOp>(
"\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");
b.create<sv::VerbatimOp>(
"\n// Define INIT_RANDOM_PROLOG_ for use in our modules below.");
emitGuard("INIT_RANDOM_PROLOG_", [&]() {
b.create<sv::IfDefOp>(
"RANDOMIZE",
[&]() {
emitGuardedDefine("VERILATOR", "INIT_RANDOM_PROLOG_",
"`INIT_RANDOM",
"`INIT_RANDOM #`RANDOMIZE_DELAY begin end");
},
[&]() { emitDefine("INIT_RANDOM_PROLOG_", ""); });
});
b.create<sv::VerbatimOp>("\n// Include register initializers in init "
"blocks unless synthesis is set");
emitGuard("SYNTHESIS", [&] {
emitGuardedDefine("ENABLE_INITIAL_REG_", "ENABLE_INITIAL_REG_",
StringRef(), "");
});
b.create<sv::VerbatimOp>("\n// Include rmemory initializers in init "
"blocks unless synthesis is set");
emitGuard("SYNTHESIS", [&] {
emitGuardedDefine("ENABLE_INITIAL_MEM_", "ENABLE_INITIAL_MEM_",
StringRef(), "");
});
b.create<sv::VerbatimOp>("");
}
}
std::unique_ptr<Pass>
circt::createLowerSeqToSVPass(const LowerSeqToSVOptions &options) {
return std::make_unique<SeqToSVPass>(options);
}