mirror of https://github.com/llvm/circt.git
385 lines
14 KiB
C++
385 lines
14 KiB
C++
//===- LowerSimToSV.cpp - Sim 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 translates Sim ops to SV.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "circt/Conversion/SimToSV.h"
|
|
#include "circt/Dialect/Comb/CombOps.h"
|
|
#include "circt/Dialect/Emit/EmitOps.h"
|
|
#include "circt/Dialect/HW/HWOps.h"
|
|
#include "circt/Dialect/SV/SVOps.h"
|
|
#include "circt/Dialect/Seq/SeqOps.h"
|
|
#include "circt/Dialect/Sim/SimDialect.h"
|
|
#include "circt/Dialect/Sim/SimOps.h"
|
|
#include "circt/Support/Namespace.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"
|
|
|
|
#define DEBUG_TYPE "lower-sim-to-sv"
|
|
|
|
namespace circt {
|
|
#define GEN_PASS_DEF_LOWERSIMTOSV
|
|
#include "circt/Conversion/Passes.h.inc"
|
|
} // namespace circt
|
|
|
|
using namespace circt;
|
|
using namespace sim;
|
|
|
|
namespace {
|
|
|
|
struct SimConversionState {
|
|
hw::HWModuleOp module;
|
|
bool usedSynthesisMacro = false;
|
|
SetVector<StringAttr> dpiCallees;
|
|
};
|
|
|
|
template <typename T>
|
|
struct SimConversionPattern : public OpConversionPattern<T> {
|
|
explicit SimConversionPattern(MLIRContext *context, SimConversionState &state)
|
|
: OpConversionPattern<T>(context), state(state) {}
|
|
|
|
SimConversionState &state;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Lower `sim.plusargs.test` to a standard SV implementation.
|
|
//
|
|
class PlusArgsTestLowering : public SimConversionPattern<PlusArgsTestOp> {
|
|
public:
|
|
using SimConversionPattern<PlusArgsTestOp>::SimConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto loc = op.getLoc();
|
|
auto resultType = rewriter.getIntegerType(1);
|
|
auto str = sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
|
|
auto reg = sv::RegOp::create(rewriter, loc, resultType,
|
|
rewriter.getStringAttr("_pargs"));
|
|
sv::InitialOp::create(rewriter, loc, [&] {
|
|
auto call = sv::SystemFunctionOp::create(
|
|
rewriter, loc, resultType, "test$plusargs", ArrayRef<Value>{str});
|
|
sv::BPAssignOp::create(rewriter, loc, reg, call);
|
|
});
|
|
|
|
rewriter.replaceOpWithNewOp<sv::ReadInOutOp>(op, reg);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// Lower `sim.plusargs.value` to a standard SV implementation.
|
|
//
|
|
class PlusArgsValueLowering : public SimConversionPattern<PlusArgsValueOp> {
|
|
public:
|
|
using SimConversionPattern<PlusArgsValueOp>::SimConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto loc = op.getLoc();
|
|
|
|
auto i1ty = rewriter.getIntegerType(1);
|
|
auto type = op.getResult().getType();
|
|
|
|
auto wirev = sv::WireOp::create(rewriter, loc, type,
|
|
rewriter.getStringAttr("_pargs_v"));
|
|
auto wiref = sv::WireOp::create(rewriter, loc, i1ty,
|
|
rewriter.getStringAttr("_pargs_f"));
|
|
|
|
state.usedSynthesisMacro = true;
|
|
sv::IfDefOp::create(
|
|
rewriter, loc, "SYNTHESIS",
|
|
[&]() {
|
|
auto cstFalse = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
|
|
auto cstZ = sv::ConstantZOp::create(rewriter, loc, type);
|
|
auto assignZ = sv::AssignOp::create(rewriter, loc, wirev, cstZ);
|
|
circt::sv::setSVAttributes(
|
|
assignZ,
|
|
sv::SVAttributeAttr::get(
|
|
rewriter.getContext(),
|
|
"This dummy assignment exists to avoid undriven lint "
|
|
"warnings (e.g., Verilator UNDRIVEN).",
|
|
/*emitAsComment=*/true));
|
|
sv::AssignOp::create(rewriter, loc, wiref, cstFalse);
|
|
},
|
|
[&]() {
|
|
auto i32ty = rewriter.getIntegerType(32);
|
|
auto regf = sv::RegOp::create(rewriter, loc, i32ty,
|
|
rewriter.getStringAttr("_found"));
|
|
auto regv = sv::RegOp::create(rewriter, loc, type,
|
|
rewriter.getStringAttr("_value"));
|
|
sv::InitialOp::create(rewriter, loc, [&] {
|
|
auto str =
|
|
sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
|
|
auto call = sv::SystemFunctionOp::create(
|
|
rewriter, loc, i32ty, "value$plusargs",
|
|
ArrayRef<Value>{str, regv});
|
|
sv::BPAssignOp::create(rewriter, loc, regf, call);
|
|
});
|
|
Value readRegF = sv::ReadInOutOp::create(rewriter, loc, regf);
|
|
Value readRegV = sv::ReadInOutOp::create(rewriter, loc, regv);
|
|
auto cstTrue = hw::ConstantOp::create(rewriter, loc, i32ty, 1);
|
|
// Squash any X coming from the regf to 0.
|
|
auto cmp = comb::ICmpOp::create(
|
|
rewriter, loc, comb::ICmpPredicate::ceq, readRegF, cstTrue);
|
|
sv::AssignOp::create(rewriter, loc, wiref, cmp);
|
|
sv::AssignOp::create(rewriter, loc, wirev, readRegV);
|
|
});
|
|
|
|
Value readf = sv::ReadInOutOp::create(rewriter, loc, wiref);
|
|
Value readv = sv::ReadInOutOp::create(rewriter, loc, wirev);
|
|
|
|
rewriter.replaceOp(op, {readf, readv});
|
|
return success();
|
|
}
|
|
};
|
|
|
|
template <typename FromOp, typename ToOp>
|
|
class SimulatorStopLowering : public SimConversionPattern<FromOp> {
|
|
public:
|
|
using SimConversionPattern<FromOp>::SimConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(FromOp op, typename FromOp::Adaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto loc = op.getLoc();
|
|
|
|
Value clockCast = seq::FromClockOp::create(rewriter, loc, adaptor.getClk());
|
|
|
|
this->state.usedSynthesisMacro = true;
|
|
sv::IfDefOp::create(
|
|
rewriter, loc, "SYNTHESIS", [&] {},
|
|
[&] {
|
|
sv::AlwaysOp::create(
|
|
rewriter, loc, sv::EventControl::AtPosEdge, clockCast, [&] {
|
|
sv::IfOp::create(rewriter, loc, adaptor.getCond(),
|
|
[&] { ToOp::create(rewriter, loc); });
|
|
});
|
|
});
|
|
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
}
|
|
};
|
|
|
|
class DPICallLowering : public SimConversionPattern<DPICallOp> {
|
|
public:
|
|
using SimConversionPattern<DPICallOp>::SimConversionPattern;
|
|
|
|
LogicalResult
|
|
matchAndRewrite(DPICallOp op, OpAdaptor adaptor,
|
|
ConversionPatternRewriter &rewriter) const final {
|
|
auto loc = op.getLoc();
|
|
// Record the callee.
|
|
state.dpiCallees.insert(op.getCalleeAttr().getAttr());
|
|
|
|
bool isClockedCall = !!op.getClock();
|
|
bool hasEnable = !!op.getEnable();
|
|
|
|
SmallVector<sv::RegOp> temporaries;
|
|
SmallVector<Value> reads;
|
|
for (auto [type, result] :
|
|
llvm::zip(op.getResultTypes(), op.getResults())) {
|
|
temporaries.push_back(sv::RegOp::create(rewriter, op.getLoc(), type));
|
|
reads.push_back(
|
|
sv::ReadInOutOp::create(rewriter, op.getLoc(), temporaries.back()));
|
|
}
|
|
|
|
auto emitCall = [&]() {
|
|
auto call = sv::FuncCallProceduralOp::create(
|
|
rewriter, op.getLoc(), op.getResultTypes(), op.getCalleeAttr(),
|
|
adaptor.getInputs());
|
|
for (auto [lhs, rhs] : llvm::zip(temporaries, call.getResults())) {
|
|
if (isClockedCall)
|
|
sv::PAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
|
|
else
|
|
sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
|
|
}
|
|
};
|
|
if (isClockedCall) {
|
|
Value clockCast =
|
|
seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
|
|
sv::AlwaysOp::create(
|
|
rewriter, loc,
|
|
ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
|
|
ArrayRef<Value>{clockCast}, [&]() {
|
|
if (!hasEnable)
|
|
return emitCall();
|
|
sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
|
|
emitCall);
|
|
});
|
|
} else {
|
|
// Unclocked call is lowered into always_comb.
|
|
// TODO: If there is a return value and no output argument, use an
|
|
// unclocked call op.
|
|
sv::AlwaysCombOp::create(rewriter, loc, [&]() {
|
|
if (!hasEnable)
|
|
return emitCall();
|
|
auto assignXToResults = [&] {
|
|
for (auto lhs : temporaries) {
|
|
auto xValue = sv::ConstantXOp::create(
|
|
rewriter, op.getLoc(), lhs.getType().getElementType());
|
|
sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, xValue);
|
|
}
|
|
};
|
|
sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(), emitCall,
|
|
assignXToResults);
|
|
});
|
|
}
|
|
|
|
rewriter.replaceOp(op, reads);
|
|
return success();
|
|
}
|
|
};
|
|
|
|
// A helper struct to lower DPI function/call.
|
|
struct LowerDPIFunc {
|
|
llvm::DenseMap<StringAttr, StringAttr> symbolToFragment;
|
|
circt::Namespace nameSpace;
|
|
LowerDPIFunc(mlir::ModuleOp module) { nameSpace.add(module); }
|
|
void lower(sim::DPIFuncOp func);
|
|
void addFragments(hw::HWModuleOp module,
|
|
ArrayRef<StringAttr> dpiCallees) const;
|
|
};
|
|
|
|
void LowerDPIFunc::lower(sim::DPIFuncOp func) {
|
|
ImplicitLocOpBuilder builder(func.getLoc(), func);
|
|
ArrayAttr inputLocsAttr, outputLocsAttr;
|
|
if (func.getArgumentLocs()) {
|
|
SmallVector<Attribute> inputLocs, outputLocs;
|
|
for (auto [port, loc] :
|
|
llvm::zip(func.getModuleType().getPorts(),
|
|
func.getArgumentLocsAttr().getAsRange<LocationAttr>())) {
|
|
(port.dir == hw::ModulePort::Output ? outputLocs : inputLocs)
|
|
.push_back(loc);
|
|
}
|
|
inputLocsAttr = builder.getArrayAttr(inputLocs);
|
|
outputLocsAttr = builder.getArrayAttr(outputLocs);
|
|
}
|
|
|
|
auto svFuncDecl =
|
|
sv::FuncOp::create(builder, func.getSymNameAttr(), func.getModuleType(),
|
|
func.getPerArgumentAttrsAttr(), inputLocsAttr,
|
|
outputLocsAttr, func.getVerilogNameAttr());
|
|
// DPI function is a declaration so it must be a private function.
|
|
svFuncDecl.setPrivate();
|
|
auto name = builder.getStringAttr(nameSpace.newName(
|
|
func.getSymNameAttr().getValue(), "dpi_import_fragument"));
|
|
|
|
// Add include guards to avoid duplicate declarations. See Issue 7458.
|
|
auto macroDecl = sv::MacroDeclOp::create(
|
|
builder, nameSpace.newName("__CIRCT_DPI_IMPORT",
|
|
func.getSymNameAttr().getValue().upper()));
|
|
emit::FragmentOp::create(builder, name, [&]() {
|
|
sv::IfDefOp::create(
|
|
builder, macroDecl.getSymNameAttr(), []() {},
|
|
[&]() {
|
|
sv::FuncDPIImportOp::create(builder, func.getSymNameAttr(),
|
|
StringAttr());
|
|
sv::MacroDefOp::create(builder, macroDecl.getSymNameAttr(), "");
|
|
});
|
|
});
|
|
|
|
symbolToFragment.insert({func.getSymNameAttr(), name});
|
|
func.erase();
|
|
}
|
|
|
|
void LowerDPIFunc::addFragments(hw::HWModuleOp module,
|
|
ArrayRef<StringAttr> dpiCallees) const {
|
|
llvm::SetVector<Attribute> fragments;
|
|
// Add existing emit fragments.
|
|
if (auto exstingFragments =
|
|
module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
|
|
for (auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
|
|
fragments.insert(fragment);
|
|
for (auto callee : dpiCallees) {
|
|
auto attr = symbolToFragment.at(callee);
|
|
fragments.insert(FlatSymbolRefAttr::get(attr));
|
|
}
|
|
if (!fragments.empty())
|
|
module->setAttr(
|
|
emit::getFragmentsAttrName(),
|
|
ArrayAttr::get(module.getContext(), fragments.takeVector()));
|
|
}
|
|
|
|
namespace {
|
|
struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
|
|
void runOnOperation() override {
|
|
auto circuit = getOperation();
|
|
MLIRContext *context = &getContext();
|
|
LowerDPIFunc lowerDPIFunc(circuit);
|
|
|
|
// Lower DPI functions.
|
|
for (auto func :
|
|
llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
|
|
lowerDPIFunc.lower(func);
|
|
|
|
std::atomic<bool> usedSynthesisMacro = false;
|
|
auto lowerModule = [&](hw::HWModuleOp module) {
|
|
SimConversionState state;
|
|
ConversionTarget target(*context);
|
|
target.addIllegalDialect<SimDialect>();
|
|
target.addLegalDialect<sv::SVDialect>();
|
|
target.addLegalDialect<hw::HWDialect>();
|
|
target.addLegalDialect<seq::SeqDialect>();
|
|
target.addLegalDialect<comb::CombDialect>();
|
|
|
|
RewritePatternSet patterns(context);
|
|
patterns.add<PlusArgsTestLowering>(context, state);
|
|
patterns.add<PlusArgsValueLowering>(context, state);
|
|
patterns.add<SimulatorStopLowering<sim::FinishOp, sv::FinishOp>>(context,
|
|
state);
|
|
patterns.add<SimulatorStopLowering<sim::FatalOp, sv::FatalOp>>(context,
|
|
state);
|
|
patterns.add<DPICallLowering>(context, state);
|
|
auto result = applyPartialConversion(module, target, std::move(patterns));
|
|
|
|
if (failed(result))
|
|
return result;
|
|
|
|
// Set the emit fragment.
|
|
lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector());
|
|
|
|
if (state.usedSynthesisMacro)
|
|
usedSynthesisMacro = true;
|
|
return result;
|
|
};
|
|
|
|
if (failed(mlir::failableParallelForEach(
|
|
context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
|
|
return signalPassFailure();
|
|
|
|
if (usedSynthesisMacro) {
|
|
Operation *op = circuit.lookupSymbol("SYNTHESIS");
|
|
if (op) {
|
|
if (!isa<sv::MacroDeclOp>(op)) {
|
|
op->emitOpError("should be a macro declaration");
|
|
return signalPassFailure();
|
|
}
|
|
} else {
|
|
auto builder = ImplicitLocOpBuilder::atBlockBegin(
|
|
UnknownLoc::get(context), circuit.getBody());
|
|
sv::MacroDeclOp::create(builder, "SYNTHESIS");
|
|
}
|
|
}
|
|
}
|
|
};
|
|
} // anonymous namespace
|
|
|
|
std::unique_ptr<Pass> circt::createLowerSimToSVPass() {
|
|
return std::make_unique<SimToSVPass>();
|
|
}
|