circt/lib/Conversion/FSMToSV/FSMToSV.cpp

762 lines
28 KiB
C++

//===- FSMToSV.cpp - Convert FSM to HW and SV Dialect ---------------------===//
//
// 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 "circt/Conversion/FSMToSV.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/Emit/EmitOps.h"
#include "circt/Dialect/FSM/FSMOps.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/SV/SVOps.h"
#include "circt/Dialect/Seq/SeqOps.h"
#include "circt/Support/BackedgeBuilder.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/RegionUtils.h"
#include "llvm/ADT/TypeSwitch.h"
#include <memory>
#include <variant>
namespace circt {
#define GEN_PASS_DEF_CONVERTFSMTOSV
#include "circt/Conversion/Passes.h.inc"
} // namespace circt
using namespace mlir;
using namespace circt;
using namespace fsm;
/// Get the port info of a FSM machine. Clock and reset port are also added.
namespace {
struct ClkRstIdxs {
size_t clockIdx;
size_t resetIdx;
};
} // namespace
static ClkRstIdxs getMachinePortInfo(SmallVectorImpl<hw::PortInfo> &ports,
MachineOp machine, OpBuilder &b) {
// Get the port info of the machine inputs and outputs.
machine.getHWPortInfo(ports);
ClkRstIdxs specialPorts;
// Add clock port.
hw::PortInfo clock;
clock.name = b.getStringAttr("clk");
clock.dir = hw::ModulePort::Direction::Input;
clock.type = seq::ClockType::get(b.getContext());
clock.argNum = machine.getNumArguments();
ports.push_back(clock);
specialPorts.clockIdx = clock.argNum;
// Add reset port.
hw::PortInfo reset;
reset.name = b.getStringAttr("rst");
reset.dir = hw::ModulePort::Direction::Input;
reset.type = b.getI1Type();
reset.argNum = machine.getNumArguments() + 1;
ports.push_back(reset);
specialPorts.resetIdx = reset.argNum;
return specialPorts;
}
// Clones constants implicitly captured by the region, into the region.
static void cloneConstantsIntoRegion(Region &region, OpBuilder &builder) {
// Values implicitly captured by the region.
llvm::SetVector<Value> captures;
getUsedValuesDefinedAbove(region, region, captures);
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(&region.front());
// Clone ConstantLike operations into the region.
for (auto &capture : captures) {
Operation *op = capture.getDefiningOp();
if (!op || !op->hasTrait<OpTrait::ConstantLike>())
continue;
Operation *cloned = builder.clone(*op);
for (auto [orig, replacement] :
llvm::zip(op->getResults(), cloned->getResults()))
replaceAllUsesInRegionWith(orig, replacement, region);
}
}
namespace {
class StateEncoding {
// An class for handling state encoding. The class is designed to
// abstract away how states are selected in case patterns, referred to as
// values, and used as selection signals for muxes.
public:
StateEncoding(OpBuilder &b, hw::TypeScopeOp typeScope, MachineOp machine,
hw::HWModuleOp hwModule);
// Get the encoded value for a state.
Value encode(StateOp state);
// Get the state corresponding to an encoded value.
StateOp decode(Value value);
// Returns the type which encodes the state values.
Type getStateType() { return stateType; }
// Returns a case pattern which matches the provided state.
std::unique_ptr<sv::CasePattern> getCasePattern(StateOp state);
protected:
// Creates a constant value in the module for the given encoded state
// and records the state value in the mappings. An inner symbol is
// attached to the wire to avoid it being optimized away.
// The constant can optionally be assigned behind a sv wire - doing so at this
// point ensures that constants don't end up behind "_GEN#" wires in the
// module.
void setEncoding(StateOp state, Value v, bool wire = false);
// A mapping between a StateOp and its corresponding encoded value.
SmallDenseMap<StateOp, Value> stateToValue;
// A mapping between an encoded value and its corresponding StateOp.
SmallDenseMap<Value, StateOp> valueToState;
// A mapping between an encoded value and the source value in the IR.
SmallDenseMap<Value, Value> valueToSrcValue;
// A typescope to emit the FSM enum type within.
hw::TypeScopeOp typeScope;
// The enum type for the states.
Type stateType;
OpBuilder &b;
MachineOp machine;
hw::HWModuleOp hwModule;
};
StateEncoding::StateEncoding(OpBuilder &b, hw::TypeScopeOp typeScope,
MachineOp machine, hw::HWModuleOp hwModule)
: typeScope(typeScope), b(b), machine(machine), hwModule(hwModule) {
Location loc = machine.getLoc();
llvm::SmallVector<Attribute> stateNames;
for (auto state : machine.getBody().getOps<StateOp>())
stateNames.push_back(b.getStringAttr(state.getName()));
// Create an enum typedef for the states.
Type rawEnumType =
hw::EnumType::get(b.getContext(), b.getArrayAttr(stateNames));
OpBuilder::InsertionGuard guard(b);
b.setInsertionPointToStart(&typeScope.getBodyRegion().front());
auto typedeclEnumType = hw::TypedeclOp::create(
b, loc, b.getStringAttr(hwModule.getName() + "_state_t"),
TypeAttr::get(rawEnumType), nullptr);
stateType = hw::TypeAliasType::get(
SymbolRefAttr::get(typeScope.getSymNameAttr(),
{FlatSymbolRefAttr::get(typedeclEnumType)}),
rawEnumType);
// And create enum values for the states
b.setInsertionPointToStart(&hwModule.getBody().front());
for (auto state : machine.getBody().getOps<StateOp>()) {
auto fieldAttr = hw::EnumFieldAttr::get(
loc, b.getStringAttr(state.getName()), stateType);
auto enumConstantOp = hw::EnumConstantOp::create(
b, loc, fieldAttr.getType().getValue(), fieldAttr);
setEncoding(state, enumConstantOp,
/*wire=*/true);
}
}
// Get the encoded value for a state.
Value StateEncoding::encode(StateOp state) {
auto it = stateToValue.find(state);
assert(it != stateToValue.end() && "state not found");
return it->second;
}
// Get the state corresponding to an encoded value.
StateOp StateEncoding::decode(Value value) {
auto it = valueToState.find(value);
assert(it != valueToState.end() && "encoded state not found");
return it->second;
}
// Returns a case pattern which matches the provided state.
std::unique_ptr<sv::CasePattern> StateEncoding::getCasePattern(StateOp state) {
// Get the field attribute for the state - fetch it through the encoding.
auto fieldAttr =
cast<hw::EnumConstantOp>(valueToSrcValue[encode(state)].getDefiningOp())
.getFieldAttr();
return std::make_unique<sv::CaseEnumPattern>(fieldAttr);
}
void StateEncoding::setEncoding(StateOp state, Value v, bool wire) {
assert(stateToValue.find(state) == stateToValue.end() &&
"state already encoded");
Value encodedValue;
if (wire) {
auto loc = machine.getLoc();
auto stateType = getStateType();
auto stateEncodingWire = sv::RegOp::create(
b, loc, stateType, b.getStringAttr("to_" + state.getName()),
hw::InnerSymAttr::get(state.getNameAttr()));
sv::AssignOp::create(b, loc, stateEncodingWire, v);
encodedValue = sv::ReadInOutOp::create(b, loc, stateEncodingWire);
} else
encodedValue = v;
stateToValue[state] = encodedValue;
valueToState[encodedValue] = state;
valueToSrcValue[encodedValue] = v;
}
class MachineOpConverter {
public:
MachineOpConverter(OpBuilder &builder, hw::TypeScopeOp typeScope,
MachineOp machineOp, FlatSymbolRefAttr headerName)
: machineOp(machineOp), typeScope(typeScope), b(builder),
headerName(headerName) {}
// Converts the machine op to a hardware module.
// 1. Creates a HWModuleOp for the machine op, with the same I/O as the FSM +
// clk/reset ports.
// 2. Creates a state register + encodings for the states visible in the
// machine.
// 3. Iterates over all states in the machine
// 3.1. Moves all `comb` logic into the body of the HW module
// 3.2. Records the SSA value(s) associated to the output ports in the state
// 3.3. iterates of the transitions of the state
// 3.3.1. Moves all `comb` logic in the transition guard/action regions to
// the body of the HW module.
// 3.3.2. Creates a case pattern for the transition guard
// 3.4. Creates a next-state value for the state based on the transition
// guards.
// 4. Assigns next-state values for the states in a case statement on the
// state reg.
// 5. Assigns the current-state outputs for the states in a case statement
// on the state reg.
LogicalResult dispatch();
private:
struct StateConversionResult {
// Value of the next state output signal of the converted state.
Value nextState;
// Value of the output signals of the converted state.
llvm::SmallVector<Value> outputs;
};
using StateConversionResults = DenseMap<StateOp, StateConversionResult>;
// Converts a StateOp within this machine, and returns the value corresponding
// to the next-state output of the op.
FailureOr<StateConversionResult> convertState(StateOp state);
// Converts the outgoing transitions of a state and returns the value
// corresponding to the next-state output of the op.
// Transitions are priority encoded in the order which they appear in the
// state transition region.
FailureOr<Value> convertTransitions(StateOp currentState,
ArrayRef<TransitionOp> transitions);
// Moves operations from 'block' into module scope, failing if any op were
// deemed illegal. Returns the final op in the block if the op was a
// terminator. An optional 'exclude' filer can be provided to dynamically
// exclude some ops from being moved.
FailureOr<Operation *>
moveOps(Block *block,
llvm::function_ref<bool(Operation *)> exclude = nullptr);
struct CaseMuxItem;
using StateCaseMapping =
llvm::SmallDenseMap<StateOp,
std::variant<Value, std::shared_ptr<CaseMuxItem>>>;
struct CaseMuxItem {
// The target wire to be assigned.
sv::RegOp wire;
// The case select signal to be used.
Value select;
// A mapping between a state and an assignment within that state.
// An assignment can either be a value or a nested CaseMuxItem. The latter
// case will create nested case statements.
StateCaseMapping assignmentInState;
// An optional default value to be assigned before the case statement, if
// the case is not fully specified for all states.
std::optional<Value> defaultValue = {};
};
// Build an SV-based case mux for the given assignments. Assignments are
// merged into the same case statement. Caller is expected to ensure that the
// insertion point is within an `always_...` block.
void buildStateCaseMux(llvm::MutableArrayRef<CaseMuxItem> assignments);
// A handle to the state encoder for this machine.
std::unique_ptr<StateEncoding> encoding;
// A deterministic ordering of the states in this machine.
llvm::SmallVector<StateOp> orderedStates;
// A mapping from a fsm.variable op to its register.
llvm::SmallDenseMap<VariableOp, seq::CompRegOp> variableToRegister;
// A mapping from a state to variable updates performed during outgoing state
// transitions.
llvm::SmallDenseMap<
/*currentState*/ StateOp,
llvm::SmallDenseMap<
/*targetState*/ StateOp,
llvm::DenseMap</*targetVariable*/ VariableOp, /*targetValue*/ Value>>>
stateToVariableUpdates;
// A handle to the MachineOp being converted.
MachineOp machineOp;
// A handle to the HW ModuleOp being created.
hw::HWModuleOp hwModuleOp;
// A handle to the state register of the machine.
seq::CompRegOp stateReg;
// A typescope to emit the FSM enum type within.
hw::TypeScopeOp typeScope;
OpBuilder &b;
// The name of the header which contains the type scope for the machine.
FlatSymbolRefAttr headerName;
};
FailureOr<Operation *>
MachineOpConverter::moveOps(Block *block,
llvm::function_ref<bool(Operation *)> exclude) {
for (auto &op : llvm::make_early_inc_range(*block)) {
if (!isa<comb::CombDialect, hw::HWDialect, fsm::FSMDialect>(
op.getDialect()))
return op.emitOpError()
<< "is unsupported (op from the "
<< op.getDialect()->getNamespace() << " dialect).";
if (exclude && exclude(&op))
continue;
if (op.hasTrait<OpTrait::IsTerminator>())
return &op;
op.moveBefore(hwModuleOp.getBodyBlock(), b.getInsertionPoint());
}
return nullptr;
}
void MachineOpConverter::buildStateCaseMux(
llvm::MutableArrayRef<CaseMuxItem> assignments) {
// Gather the select signal. All assignments are expected to use the same
// select signal.
Value select = assignments.front().select;
assert(llvm::all_of(
assignments,
[&](const CaseMuxItem &item) { return item.select == select; }) &&
"All assignments must use the same select signal.");
sv::CaseOp caseMux;
// Default assignments.
for (auto &assignment : assignments) {
if (assignment.defaultValue)
sv::BPAssignOp::create(b, assignment.wire.getLoc(), assignment.wire,
*assignment.defaultValue);
}
// Case assignments.
caseMux = sv::CaseOp::create(
b, machineOp.getLoc(), CaseStmtType::CaseStmt,
/*sv::ValidationQualifierTypeEnum::ValidationQualifierUnique, */ select,
/*numCases=*/machineOp.getNumStates() + 1, [&](size_t caseIdx) {
// Make Verilator happy for sized enums.
if (caseIdx == machineOp.getNumStates())
return std::unique_ptr<sv::CasePattern>(
new sv::CaseDefaultPattern(b.getContext()));
StateOp state = orderedStates[caseIdx];
return encoding->getCasePattern(state);
});
// Create case assignments.
for (auto assignment : assignments) {
OpBuilder::InsertionGuard g(b);
for (auto [caseInfo, stateOp] :
llvm::zip(caseMux.getCases(), orderedStates)) {
auto assignmentInState = assignment.assignmentInState.find(stateOp);
if (assignmentInState == assignment.assignmentInState.end())
continue;
b.setInsertionPointToEnd(caseInfo.block);
if (auto v = std::get_if<Value>(&assignmentInState->second); v) {
sv::BPAssignOp::create(b, machineOp.getLoc(), assignment.wire, *v);
} else {
// Nested case statement.
llvm::SmallVector<CaseMuxItem, 4> nestedAssignments;
nestedAssignments.push_back(
*std::get<std::shared_ptr<CaseMuxItem>>(assignmentInState->second));
buildStateCaseMux(nestedAssignments);
}
}
}
}
LogicalResult MachineOpConverter::dispatch() {
b.setInsertionPoint(machineOp);
auto loc = machineOp.getLoc();
if (machineOp.getNumStates() < 2)
return machineOp.emitOpError() << "expected at least 2 states.";
// Clone all referenced constants into the machine body - constants may have
// been moved to the machine parent due to the lack of IsolationFromAbove.
cloneConstantsIntoRegion(machineOp.getBody(), b);
// 1) Get the port info of the machine and create a new HW module for it.
SmallVector<hw::PortInfo, 16> ports;
auto clkRstIdxs = getMachinePortInfo(ports, machineOp, b);
hwModuleOp =
hw::HWModuleOp::create(b, loc, machineOp.getSymNameAttr(), ports);
hwModuleOp->setAttr(emit::getFragmentsAttrName(),
b.getArrayAttr({headerName}));
b.setInsertionPointToStart(hwModuleOp.getBodyBlock());
// Replace all uses of the machine arguments with the arguments of the
// new created HW module.
for (auto args : llvm::zip(machineOp.getArguments(),
hwModuleOp.getBodyBlock()->getArguments())) {
auto machineArg = std::get<0>(args);
auto hwModuleArg = std::get<1>(args);
machineArg.replaceAllUsesWith(hwModuleArg);
}
auto clock = hwModuleOp.getBodyBlock()->getArgument(clkRstIdxs.clockIdx);
auto reset = hwModuleOp.getBodyBlock()->getArgument(clkRstIdxs.resetIdx);
// 2) Build state and variable registers.
encoding =
std::make_unique<StateEncoding>(b, typeScope, machineOp, hwModuleOp);
auto stateType = encoding->getStateType();
auto nextStateWire =
sv::RegOp::create(b, loc, stateType, b.getStringAttr("state_next"));
auto nextStateWireRead = sv::ReadInOutOp::create(b, loc, nextStateWire);
stateReg = seq::CompRegOp::create(
b, loc, nextStateWireRead, clock, reset,
/*reset value=*/encoding->encode(machineOp.getInitialStateOp()),
"state_reg");
llvm::DenseMap<VariableOp, sv::RegOp> variableNextStateWires;
for (auto variableOp : machineOp.front().getOps<fsm::VariableOp>()) {
auto initValueAttr = dyn_cast<IntegerAttr>(variableOp.getInitValueAttr());
if (!initValueAttr)
return variableOp.emitOpError() << "expected an integer attribute "
"for the initial value.";
Type varType = variableOp.getType();
auto varLoc = variableOp.getLoc();
auto varNextState = sv::RegOp::create(
b, varLoc, varType, b.getStringAttr(variableOp.getName() + "_next"));
auto varResetVal = hw::ConstantOp::create(b, varLoc, initValueAttr);
auto variableReg = seq::CompRegOp::create(
b, varLoc, sv::ReadInOutOp::create(b, varLoc, varNextState), clock,
reset, varResetVal, b.getStringAttr(variableOp.getName() + "_reg"));
variableToRegister[variableOp] = variableReg;
variableNextStateWires[variableOp] = varNextState;
// Postpone value replacement until all logic has been created.
// fsm::UpdateOp's require their target variables to refer to a
// fsm::VariableOp - if this is not the case, they'll throw an assert.
}
// Move any operations at the machine-level scope, excluding state ops, which
// are handled separately.
if (failed(moveOps(&machineOp.front(), [](Operation *op) {
return isa<fsm::StateOp, fsm::VariableOp>(op);
})))
return failure();
// 3) Convert states and record their next-state value assignments.
StateCaseMapping nextStateFromState;
StateConversionResults stateConvResults;
for (auto state : machineOp.getBody().getOps<StateOp>()) {
auto stateConvRes = convertState(state);
if (failed(stateConvRes))
return failure();
stateConvResults[state] = *stateConvRes;
orderedStates.push_back(state);
nextStateFromState[state] = {stateConvRes->nextState};
}
// 4/5) Create next-state assignments for each output.
llvm::SmallVector<CaseMuxItem, 4> outputCaseAssignments;
auto hwPortList = hwModuleOp.getPortList();
size_t portIndex = 0;
for (auto &port : hwPortList) {
if (!port.isOutput())
continue;
auto outputPortType = port.type;
CaseMuxItem outputAssignment;
outputAssignment.wire = sv::RegOp::create(
b, machineOp.getLoc(), outputPortType,
b.getStringAttr("output_" + std::to_string(portIndex)));
outputAssignment.select = stateReg;
for (auto &state : orderedStates)
outputAssignment.assignmentInState[state] = {
stateConvResults[state].outputs[portIndex]};
outputCaseAssignments.push_back(outputAssignment);
++portIndex;
}
// Create next-state maps for the FSM variables.
llvm::DenseMap<VariableOp, CaseMuxItem> variableCaseMuxItems;
for (auto &[currentState, it] : stateToVariableUpdates) {
for (auto &[targetState, it2] : it) {
for (auto &[variableOp, targetValue] : it2) {
auto caseMuxItemIt = variableCaseMuxItems.find(variableOp);
if (caseMuxItemIt == variableCaseMuxItems.end()) {
// First time seeing this variable. Initialize the outer case
// statement. The outer case has a default assignment to the current
// value of the variable register.
variableCaseMuxItems[variableOp];
caseMuxItemIt = variableCaseMuxItems.find(variableOp);
assert(variableOp);
assert(variableNextStateWires.count(variableOp));
caseMuxItemIt->second.wire = variableNextStateWires[variableOp];
caseMuxItemIt->second.select = stateReg;
caseMuxItemIt->second.defaultValue =
variableToRegister[variableOp].getResult();
}
if (!std::get_if<std::shared_ptr<CaseMuxItem>>(
&caseMuxItemIt->second.assignmentInState[currentState])) {
// Initialize the inner case statement. This is an inner case within
// the current state, switching on the next-state value.
CaseMuxItem innerCaseMuxItem;
innerCaseMuxItem.wire = caseMuxItemIt->second.wire;
innerCaseMuxItem.select = nextStateWireRead;
caseMuxItemIt->second.assignmentInState[currentState] = {
std::make_shared<CaseMuxItem>(innerCaseMuxItem)};
}
// Append to the nested case mux for the variable, with a case select
// on the next-state signal.
// Append an assignment in the case that nextState == targetState.
auto &innerCaseMuxItem = std::get<std::shared_ptr<CaseMuxItem>>(
caseMuxItemIt->second.assignmentInState[currentState]);
innerCaseMuxItem->assignmentInState[targetState] = {targetValue};
}
}
}
// Materialize the case mux.
llvm::SmallVector<CaseMuxItem, 4> nextStateCaseAssignments;
nextStateCaseAssignments.push_back(
CaseMuxItem{nextStateWire, stateReg, nextStateFromState});
for (auto &[_, caseMuxItem] : variableCaseMuxItems)
nextStateCaseAssignments.push_back(caseMuxItem);
nextStateCaseAssignments.append(outputCaseAssignments.begin(),
outputCaseAssignments.end());
{
auto alwaysCombOp = sv::AlwaysCombOp::create(b, loc);
OpBuilder::InsertionGuard g(b);
b.setInsertionPointToStart(alwaysCombOp.getBodyBlock());
buildStateCaseMux(nextStateCaseAssignments);
}
// Replace variable values with their register counterparts.
for (auto &[variableOp, variableReg] : variableToRegister)
variableOp.getResult().replaceAllUsesWith(variableReg);
// Assing output ports.
llvm::SmallVector<Value> outputPortAssignments;
for (auto outputAssignment : outputCaseAssignments)
outputPortAssignments.push_back(
sv::ReadInOutOp::create(b, machineOp.getLoc(), outputAssignment.wire));
// Delete the default created output op and replace it with the output
// muxes.
auto *oldOutputOp = hwModuleOp.getBodyBlock()->getTerminator();
hw::OutputOp::create(b, loc, outputPortAssignments);
oldOutputOp->erase();
// Erase the original machine op.
machineOp.erase();
return success();
}
FailureOr<Value>
MachineOpConverter::convertTransitions( // NOLINT(misc-no-recursion)
StateOp currentState, ArrayRef<TransitionOp> transitions) {
Value nextState;
if (transitions.empty()) {
// Base case
// State: transition to the current state.
nextState = encoding->encode(currentState);
} else {
// Recursive case - transition to a named state.
auto transition = cast<fsm::TransitionOp>(transitions.front());
nextState = encoding->encode(transition.getNextStateOp());
// Action conversion
if (transition.hasAction()) {
// Move any ops from the action region to the general scope, excluding
// variable update ops.
auto actionMoveOpsRes =
moveOps(&transition.getAction().front(),
[](Operation *op) { return isa<fsm::UpdateOp>(op); });
if (failed(actionMoveOpsRes))
return failure();
// Gather variable updates during the action.
DenseMap<fsm::VariableOp, Value> variableUpdates;
for (auto updateOp : transition.getAction().getOps<fsm::UpdateOp>()) {
VariableOp variableOp = updateOp.getVariableOp();
variableUpdates[variableOp] = updateOp.getValue();
}
stateToVariableUpdates[currentState][transition.getNextStateOp()] =
variableUpdates;
}
// Guard conversion
if (transition.hasGuard()) {
// Not always taken; recurse and mux between the targeted next state and
// the recursion result, selecting based on the provided guard.
auto guardOpRes = moveOps(&transition.getGuard().front());
if (failed(guardOpRes))
return failure();
auto guardOp = cast<ReturnOp>(*guardOpRes);
assert(guardOp && "guard should be defined");
auto guard = guardOp.getOperand();
auto otherNextState =
convertTransitions(currentState, transitions.drop_front());
if (failed(otherNextState))
return failure();
comb::MuxOp nextStateMux = comb::MuxOp::create(
b, transition.getLoc(), guard, nextState, *otherNextState, false);
nextState = nextStateMux;
}
}
assert(nextState && "next state should be defined");
return nextState;
}
FailureOr<MachineOpConverter::StateConversionResult>
MachineOpConverter::convertState(StateOp state) {
MachineOpConverter::StateConversionResult res;
// 3.1) Convert the output region by moving the operations into the module
// scope and gathering the operands of the output op.
if (!state.getOutput().empty()) {
auto outputOpRes = moveOps(&state.getOutput().front());
if (failed(outputOpRes))
return failure();
OutputOp outputOp = cast<fsm::OutputOp>(*outputOpRes);
res.outputs = outputOp.getOperands(); // 3.2
}
SmallVector<TransitionOp> transitions;
for (auto &op : state.getTransitions().getOps()) {
if (auto transOp = dyn_cast<TransitionOp>(op)) {
transitions.push_back(transOp);
} else {
// Clone operations which are inside `transitions` region but outside
// `guard` region.
auto opClone = b.clone(op);
for (auto [i, res] : llvm::enumerate(op.getResults()))
res.replaceAllUsesWith(opClone->getResult(i));
}
}
// 3.3, 3.4) Convert the transitions and record the next-state value
// derived from the transitions being selected in a priority-encoded manner.
auto nextStateRes = convertTransitions(state, transitions);
if (failed(nextStateRes))
return failure();
res.nextState = *nextStateRes;
return res;
}
struct FSMToSVPass : public circt::impl::ConvertFSMToSVBase<FSMToSVPass> {
void runOnOperation() override;
};
void FSMToSVPass::runOnOperation() {
auto module = getOperation();
auto loc = module.getLoc();
auto b = OpBuilder(module);
// Identify the machines to lower, bail out if none exist.
auto machineOps = llvm::to_vector(module.getOps<MachineOp>());
if (machineOps.empty()) {
markAllAnalysesPreserved();
return;
}
// Create a typescope shared by all of the FSMs. This typescope will be
// emitted in a single separate file to avoid polluting each output file with
// typedefs.
b.setInsertionPointToStart(module.getBody());
hw::TypeScopeOp typeScope =
hw::TypeScopeOp::create(b, loc, b.getStringAttr("fsm_enum_typedecls"));
typeScope.getBodyRegion().push_back(new Block());
auto file = emit::FileOp::create(b, loc, "fsm_enum_typedefs.sv", [&] {
emit::RefOp::create(b, loc,
FlatSymbolRefAttr::get(typeScope.getSymNameAttr()));
});
auto fragment = emit::FragmentOp::create(b, loc, "FSM_ENUM_TYPEDEFS", [&] {
sv::VerbatimOp::create(b, loc, "`include \"" + file.getFileName() + "\"");
});
auto headerName = FlatSymbolRefAttr::get(fragment.getSymNameAttr());
// Traverse all machines and convert.
for (auto machineOp : machineOps) {
MachineOpConverter converter(b, typeScope, machineOp, headerName);
if (failed(converter.dispatch())) {
signalPassFailure();
return;
}
}
// Traverse all machine instances and convert to hw instances.
llvm::SmallVector<HWInstanceOp> instances;
module.walk([&](HWInstanceOp instance) { instances.push_back(instance); });
for (auto instance : instances) {
auto fsmHWModule =
module.lookupSymbol<hw::HWModuleOp>(instance.getMachine());
assert(fsmHWModule &&
"FSM machine should have been converted to a hw.module");
b.setInsertionPoint(instance);
llvm::SmallVector<Value, 4> operands;
llvm::transform(instance.getOperands(), std::back_inserter(operands),
[&](auto operand) { return operand; });
auto hwInstance = hw::InstanceOp::create(
b, instance.getLoc(), fsmHWModule, b.getStringAttr(instance.getName()),
operands, nullptr);
instance.replaceAllUsesWith(hwInstance);
instance.erase();
}
assert(!typeScope.getBodyBlock()->empty() && "missing type decls");
}
} // end anonymous namespace
std::unique_ptr<mlir::Pass> circt::createConvertFSMToSVPass() {
return std::make_unique<FSMToSVPass>();
}