mirror of https://github.com/llvm/circt.git
[CalyxToFSM] Add FSM-flow remove groups pass (#4600)
This commit adds a version of the Calyx remove-groups pass tailored for the FSM flow. Specifically, it will perform FSM outlining alongside group rewriting - which interacts with the FSM I/O (group go/done signals). Technically, we now (finally) have an end-to-end flow for SCF->Calyx->FSM->SV. The usability of the end-to-end flow is still fairly restricted, mainly due to: 1. the lack of support for lowering Calyx memory ops 2. The lack of support for lowering `calyx.par` ops in the FSM lowering (this shouldn't be too difficult). A fair amount of state is stored in attributes in between `materialize-calyx-to-fsm` and `calyx-remove-groups-fsm`. I'm debating with myself whether that's code smell or just a necessary evil given the approach taken. Nevertheless, I did this work because of being frustrated with the disconnection of Calyx from the rest of CIRCT, which renders it unuseable. Since I was the author of the FSM flow, I figured that i'd might aswell finish the end-to-end prototype before stepping somewhat away from Calyx-related development. I'm hoping that given this end-to-end prototype, there's enough existing infrastructure for others to pick up the torch and flesh out the missing pieces. Some additional notes: * Also makes `calyx.component` contain a graph region to make it a bit less of a pain to specify SSA dependencies between cells. * Also fixes various small bugs highlighted by this conversion.
This commit is contained in:
parent
47c0eade67
commit
e80520b7c9
|
@ -29,10 +29,21 @@ namespace calyxToFSM {
|
|||
// Entry and exit state names of the Calyx control program in the FSM.
|
||||
static constexpr std::string_view sEntryStateName = "fsm_entry";
|
||||
static constexpr std::string_view sExitStateName = "fsm_exit";
|
||||
static constexpr std::string_view sGroupDoneInputs =
|
||||
"calyx.fsm_group_done_inputs";
|
||||
static constexpr std::string_view sGroupGoOutputs =
|
||||
"calyx.fsm_group_go_outputs";
|
||||
static constexpr std::string_view sSSAInputIndices = "calyx.fsm_ssa_inputs";
|
||||
static constexpr std::string_view sFSMTopLevelGoIndex =
|
||||
"calyx.fsm_top_level_go";
|
||||
static constexpr std::string_view sFSMTopLevelDoneIndex =
|
||||
"calyx.fsm_top_level_done";
|
||||
|
||||
} // namespace calyxToFSM
|
||||
|
||||
std::unique_ptr<mlir::Pass> createCalyxToFSMPass();
|
||||
std::unique_ptr<mlir::Pass> createMaterializeCalyxToFSMPass();
|
||||
std::unique_ptr<mlir::Pass> createRemoveGroupsFromFSMPass();
|
||||
|
||||
} // namespace circt
|
||||
|
||||
|
|
|
@ -264,6 +264,76 @@ def MaterializeCalyxToFSM : Pass<"materialize-calyx-to-fsm", "calyx::ComponentOp
|
|||
}
|
||||
|
||||
|
||||
def CalyxRemoveGroupsFromFSM : Pass<"calyx-remove-groups-fsm", "calyx::ComponentOp"> {
|
||||
let summary = "Perform FSM outlining and group removal";
|
||||
let description = [{
|
||||
This pass will outline the FSM into module scope and replace any SSA value references
|
||||
from within the FSM body with additional inputs. Given this, the FSM
|
||||
is instantiated as a `fsm.hw_module` operation within the Calyx component.
|
||||
Using the FSM I/O (which is the group go/done signals), the `calyx.group`
|
||||
operations are removed from the component, with the group go and done signals
|
||||
being wired up to the FSM instance.
|
||||
Example:
|
||||
```mlir
|
||||
calyx.component {
|
||||
%reg, ... = calyx.register ... : i1
|
||||
calyx.wires {
|
||||
// Groups have explicit done signals, and assignments are not guarded
|
||||
// by a group go signal.
|
||||
calyx.group @A {
|
||||
...
|
||||
calyx.assign %reg = ...
|
||||
...
|
||||
calyx.group_done %foo ? %bar
|
||||
}
|
||||
}
|
||||
calyx.control {
|
||||
// Machine is defined inside the `calyx.control` operation and references
|
||||
// SSA values defined outside the machine.
|
||||
fsm.machine @control(%A_done : i1) -> (%A_go : i1) {
|
||||
...
|
||||
%0 = comb.not %reg // reference some SSA value defined outside the machine
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
into
|
||||
```mlir
|
||||
// The machine has been outlined into module scope, and no longer references
|
||||
// any SSA values defined outside the machine. It is now fully independent
|
||||
// from any notion of Calyx.
|
||||
fsm.machine @control(%A_done : i1, %reg : i1) -> (%A_go : i1) {
|
||||
...
|
||||
%0 = comb.not %reg // reference some SSA value defined outside the machine
|
||||
...
|
||||
}
|
||||
|
||||
calyx.component {
|
||||
%reg, ... = calyx.register ...
|
||||
// Done signals are now wires
|
||||
%A_done_in, %A_done_out = calyx.wire : i1
|
||||
// The FSM is now instantiated as an `fsm.hwinstance` module
|
||||
%A_go = fsm.hwinstance @control(%A_done_out, %reg) : ...
|
||||
calyx.wires {
|
||||
// Groups have been inlined, the group go signal is now a guard for
|
||||
// all assignments, and `calyx.group_done` operations have been
|
||||
// replaced by wire assignments.
|
||||
...
|
||||
calyx.assign %reg = %A_go ? ...
|
||||
...
|
||||
calyx.assign %A_done_in = %foo ? %bar
|
||||
}
|
||||
calyx.control {
|
||||
}
|
||||
}
|
||||
```
|
||||
}];
|
||||
let dependentDialects = ["fsm::FSMDialect", "comb::CombDialect", "hw::HWDialect"];
|
||||
let constructor = "circt::createRemoveGroupsFromFSMPass()";
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// FSMToSV
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#define CIRCT_DIALECT_CALYX_CALYXHELPERS_H
|
||||
|
||||
#include "circt/Dialect/Calyx/CalyxOps.h"
|
||||
#include "circt/Dialect/Comb/CombOps.h"
|
||||
#include "circt/Dialect/HW/HWOps.h"
|
||||
#include "circt/Support/LLVM.h"
|
||||
|
||||
|
@ -50,6 +51,22 @@ void addMandatoryComponentPorts(PatternRewriter &rewriter,
|
|||
// than zero. See: https://github.com/llvm/circt/issues/2660
|
||||
unsigned handleZeroWidth(int64_t dim);
|
||||
|
||||
/// Updates the guard of each assignment within a group with `op`.
|
||||
template <typename Op>
|
||||
static void updateGroupAssignmentGuards(OpBuilder &builder, GroupOp &group,
|
||||
Op &op) {
|
||||
group.walk([&](AssignOp assign) {
|
||||
if (assign.getGuard())
|
||||
// If the assignment is guarded already, take the bitwise & of the current
|
||||
// guard and the group's go signal.
|
||||
assign->setOperand(2, builder.create<comb::AndOp>(
|
||||
group.getLoc(), assign.getGuard(), op, false));
|
||||
else
|
||||
// Otherwise, just insert it as the guard.
|
||||
assign->insertOperands(2, {op});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace calyx
|
||||
} // namespace circt
|
||||
|
||||
|
|
|
@ -218,6 +218,14 @@ def SliceLibOp : UnaryLibraryOp<"slice"> {
|
|||
|
||||
def NotLibOp : UnaryLibraryOp<"not"> {}
|
||||
|
||||
def WireLibOp : UnaryLibraryOp<"wire", [SameTypeConstraint<"in", "out">]> {}
|
||||
def WireLibOp : UnaryLibraryOp<"wire", [SameTypeConstraint<"in", "out">]> {
|
||||
let builders = [
|
||||
OpBuilder<(ins "StringRef":$sym_name, "::mlir::Type":$wireType), [{
|
||||
llvm::SmallVector<mlir::Type> resultTypes = {wireType, wireType};
|
||||
$_state.addAttribute(mlir::SymbolTable::getSymbolAttrName(), $_builder.getStringAttr(sym_name));
|
||||
$_state.addTypes(resultTypes);
|
||||
}]>
|
||||
];
|
||||
}
|
||||
|
||||
def ExtSILibOp : UnaryLibraryOp<"extsi"> {}
|
||||
|
|
|
@ -39,6 +39,7 @@ def UndefinedOp : CalyxOp<"undef", [
|
|||
|
||||
def ComponentOp : CalyxOp<"component", [
|
||||
HasParent<"mlir::ModuleOp">,
|
||||
RegionKindInterface,
|
||||
SymbolTable, /* contains Cell names. */
|
||||
Symbol,
|
||||
FunctionOpInterface,
|
||||
|
@ -85,6 +86,9 @@ def ComponentOp : CalyxOp<"component", [
|
|||
using mlir::detail::FunctionOpInterfaceTrait<ComponentOp>::front;
|
||||
using mlir::detail::FunctionOpInterfaceTrait<ComponentOp>::getFunctionBody;
|
||||
|
||||
// Implement RegionKindInterface.
|
||||
static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph;}
|
||||
|
||||
/// Returns the argument types of this function.
|
||||
ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); }
|
||||
|
||||
|
|
|
@ -3,18 +3,15 @@
|
|||
// RUN: circt-opt %s \
|
||||
// RUN: --lower-scf-to-calyx -canonicalize \
|
||||
// RUN: --calyx-remove-comb-groups --canonicalize \
|
||||
// RUN: --calyx-go-insertion --canonicalize \
|
||||
// RUN: --lower-calyx-to-fsm --canonicalize \
|
||||
// RUN: --materialize-calyx-to-fsm
|
||||
// RUN: --materialize-calyx-to-fsm --canonicalize \
|
||||
// RUN: --calyx-remove-groups-fsm --canonicalize | FileCheck %s
|
||||
|
||||
// This is the end of the road (for now) for Calyx in CIRCT.
|
||||
// The materialized FSM now needs to be outlined from within the
|
||||
// calyx module, and within the Calyx module it can be instantiated
|
||||
// as any other HW component. The FSM will then be lowered through
|
||||
// the existing FSM-to-HW flow.
|
||||
// This is the end of the road for this example since there (as of writing)
|
||||
// does not yet exist a lowering for calyx.memory operations.
|
||||
|
||||
// CHECK: calyx.control {
|
||||
// CHECK: fsm.machine @control(
|
||||
// CHECK: fsm.machine @control
|
||||
// CHECK: calyx.component @main
|
||||
|
||||
func.func @main() {
|
||||
%c0 = arith.constant 0 : index
|
|
@ -0,0 +1,26 @@
|
|||
// This test lowers an SCF construct through Calyx, FSM and (TODO)
|
||||
// to RTL.
|
||||
// RUN: circt-opt %s \
|
||||
// RUN: --lower-scf-to-calyx -canonicalize \
|
||||
// RUN: --calyx-remove-comb-groups --canonicalize \
|
||||
// RUN: --lower-calyx-to-fsm --canonicalize \
|
||||
// RUN: --materialize-calyx-to-fsm --canonicalize \
|
||||
// RUN: --calyx-remove-groups-fsm --canonicalize \
|
||||
// RUN: --lower-calyx-to-hw --canonicalize \
|
||||
// RUN: --convert-fsm-to-sv | FileCheck %s
|
||||
|
||||
// TODO: ... simulate the hardware!
|
||||
|
||||
// CHECK: hw.module @control
|
||||
// CHECK: hw.module @main
|
||||
// CHECK: hw.instance "controller" @control
|
||||
func.func @main(%arg0 : i32, %arg1 : i32) -> i32 {
|
||||
%0 = arith.cmpi slt, %arg0, %arg1 : i32
|
||||
cf.cond_br %0, ^bb1, ^bb2
|
||||
^bb1:
|
||||
cf.br ^bb3(%arg0 : i32)
|
||||
^bb2:
|
||||
cf.br ^bb3(%arg1 : i32)
|
||||
^bb3(%1 : i32):
|
||||
return %1 : i32
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
add_circt_library(CIRCTCalyxToFSM
|
||||
CalyxToFSM.cpp
|
||||
MaterializeFSM.cpp
|
||||
RemoveGroupsFromFSM.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/CalyxToFSM
|
||||
|
|
|
@ -63,6 +63,9 @@ struct MaterializeCalyxToFSMPass
|
|||
|
||||
for (auto transition :
|
||||
stateOp.getTransitions().getOps<fsm::TransitionOp>()) {
|
||||
|
||||
if (!transition.hasGuard() && doneGuards.empty())
|
||||
continue;
|
||||
transition.ensureGuard(b);
|
||||
auto guardOp = transition.getGuardReturn();
|
||||
llvm::SmallVector<Value> guards;
|
||||
|
@ -110,14 +113,103 @@ struct MaterializeCalyxToFSMPass
|
|||
|
||||
/// Constant cache.
|
||||
DenseMap<APInt, Value> constants;
|
||||
|
||||
OpBuilder *b;
|
||||
|
||||
FSMStateNode *entryState;
|
||||
FSMStateNode *exitState;
|
||||
|
||||
// Walks the machine and gathers the set of referenced groups and SSA values.
|
||||
void walkMachine();
|
||||
|
||||
// Creates the top-level group go/done I/O for the machine.
|
||||
void materializeGroupIO();
|
||||
|
||||
// Add attributes to the machine op to indicate which in/out ports are
|
||||
// associated with group activations and which are additional inputs to the
|
||||
// FSM.
|
||||
void assignAttributes();
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
void MaterializeCalyxToFSMPass::walkMachine() {
|
||||
// Walk the states of the machine and gather the relation between states and
|
||||
// the groups which they enable as well as the set of all enabled states.
|
||||
for (auto stateOp : machineOp.getOps<fsm::StateOp>()) {
|
||||
for (auto enableOp : llvm::make_early_inc_range(
|
||||
stateOp.getOutput().getOps<calyx::EnableOp>())) {
|
||||
auto groupName = enableOp.getGroupNameAttr().getAttr();
|
||||
stateEnables[stateOp].insert(groupName);
|
||||
referencedGroups.insert(groupName);
|
||||
// Erase the enable op now that we've recorded the information.
|
||||
enableOp.erase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaterializeCalyxToFSMPass::materializeGroupIO() {
|
||||
// Materialize the top-level I/O ports of the fsm.machine. We add an in- and
|
||||
// output for every unique group referenced within the machine, as well as an
|
||||
// additional in- and output to represent the top-level "go" input and "done"
|
||||
// output ports.
|
||||
SmallVector<Type> ioTypes = SmallVector<Type>(
|
||||
referencedGroups.size() + /*top-level go/done*/ 1, b->getI1Type());
|
||||
size_t nGroups = ioTypes.size() - 1;
|
||||
machineOp.setType(b->getFunctionType(ioTypes, ioTypes));
|
||||
assert(machineOp.getBody().getNumArguments() == 0 &&
|
||||
"expected no inputs to the FSM");
|
||||
machineOp.getBody().addArguments(
|
||||
ioTypes, SmallVector<Location, 4>(ioTypes.size(), b->getUnknownLoc()));
|
||||
|
||||
// Build output assignments and transition guards in every state. We here
|
||||
// assume that the ordering of states in referencedGroups is fixed and
|
||||
// deterministic, since it is used as an analogue for port I/O ordering.
|
||||
for (auto stateOp : machineOp.getOps<fsm::StateOp>()) {
|
||||
assignStateOutputOperands(*b, stateOp,
|
||||
/*topLevelDone=*/false);
|
||||
assignStateTransitionGuard(*b, stateOp);
|
||||
}
|
||||
|
||||
// Assign top-level go guard in the transition state.
|
||||
size_t topLevelGoIdx = nGroups;
|
||||
assignStateTransitionGuard(*b, entryState->getState(),
|
||||
{machineOp.getArgument(topLevelGoIdx)});
|
||||
|
||||
// Assign top-level done in the exit state.
|
||||
assignStateOutputOperands(*b, exitState->getState(),
|
||||
/*topLevelDone=*/true);
|
||||
}
|
||||
|
||||
void MaterializeCalyxToFSMPass::assignAttributes() {
|
||||
// sGroupDoneInputs is a mapping from group name to the index of the
|
||||
// corresponding done input port.
|
||||
llvm::SmallVector<NamedAttribute> groupDoneInputs;
|
||||
for (size_t i = 0; i < referencedGroups.size(); ++i)
|
||||
groupDoneInputs.push_back({referencedGroups[i], b->getI64IntegerAttr(i)});
|
||||
machineOp->setAttr(calyxToFSM::sGroupDoneInputs,
|
||||
b->getDictionaryAttr(groupDoneInputs));
|
||||
|
||||
// sGroupGoOutputs is a mapping from group name to the index of the
|
||||
// corresponding go output port.
|
||||
llvm::SmallVector<NamedAttribute> groupGoOutputs;
|
||||
for (size_t i = 0; i < referencedGroups.size(); ++i)
|
||||
groupGoOutputs.push_back({referencedGroups[i], b->getI64IntegerAttr(i)});
|
||||
machineOp->setAttr(calyxToFSM::sGroupGoOutputs,
|
||||
b->getDictionaryAttr(groupGoOutputs));
|
||||
|
||||
// Assign top level go/done attributes
|
||||
machineOp->setAttr(calyxToFSM::sFSMTopLevelGoIndex,
|
||||
b->getI64IntegerAttr(referencedGroups.size()));
|
||||
machineOp->setAttr(calyxToFSM::sFSMTopLevelDoneIndex,
|
||||
b->getI64IntegerAttr(referencedGroups.size()));
|
||||
}
|
||||
|
||||
void MaterializeCalyxToFSMPass::runOnOperation() {
|
||||
ComponentOp component = getOperation();
|
||||
auto *ctx = &getContext();
|
||||
auto b = OpBuilder(ctx);
|
||||
auto builder = OpBuilder(ctx);
|
||||
b = &builder;
|
||||
auto controlOp = component.getControlOp();
|
||||
machineOp =
|
||||
dyn_cast_or_null<fsm::MachineOp>(controlOp.getBodyBlock()->front());
|
||||
|
@ -131,8 +223,8 @@ void MaterializeCalyxToFSMPass::runOnOperation() {
|
|||
|
||||
// Ensure a well-formed FSM.
|
||||
auto graph = FSMGraph(machineOp);
|
||||
auto *entryState = graph.lookup(b.getStringAttr(calyxToFSM::sEntryStateName));
|
||||
auto *exitState = graph.lookup(b.getStringAttr(calyxToFSM::sExitStateName));
|
||||
entryState = graph.lookup(b->getStringAttr(calyxToFSM::sEntryStateName));
|
||||
exitState = graph.lookup(b->getStringAttr(calyxToFSM::sExitStateName));
|
||||
|
||||
if (!(entryState && exitState)) {
|
||||
machineOp.emitOpError()
|
||||
|
@ -142,49 +234,9 @@ void MaterializeCalyxToFSMPass::runOnOperation() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Walk the states of the machine and gather the relation between states and
|
||||
// the groups which they enable as well as the set of all enabled states.
|
||||
for (auto stateOp : machineOp.getOps<fsm::StateOp>()) {
|
||||
for (auto enableOp : llvm::make_early_inc_range(
|
||||
stateOp.getOutput().getOps<calyx::EnableOp>())) {
|
||||
auto groupName = enableOp.getGroupNameAttr().getAttr();
|
||||
stateEnables[stateOp].insert(groupName);
|
||||
referencedGroups.insert(groupName);
|
||||
// Erase the enable op now that we've recorded the information.
|
||||
enableOp.erase();
|
||||
}
|
||||
}
|
||||
|
||||
// Materialize the top-level I/O ports of the fsm.machine. We add an in- and
|
||||
// output for every unique group referenced within the machine, as well as an
|
||||
// additional in- and output to represent the top-level "go" input and "done"
|
||||
// output ports.
|
||||
SmallVector<Type> ioTypes = SmallVector<Type>(
|
||||
referencedGroups.size() + /*top-level go/done*/ 1, b.getI1Type());
|
||||
size_t nGroups = ioTypes.size() - 1;
|
||||
machineOp.setType(b.getFunctionType(ioTypes, ioTypes));
|
||||
assert(machineOp.getBody().getNumArguments() == 0 &&
|
||||
"expected no inputs to the FSM");
|
||||
machineOp.getBody().addArguments(
|
||||
ioTypes, SmallVector<Location, 4>(ioTypes.size(), b.getUnknownLoc()));
|
||||
|
||||
// Build output assignments and transition guards in every state. We here
|
||||
// assume that the ordering of states in referencedGroups is fixed and
|
||||
// deterministic, since it is used as an analogue for port I/O ordering.
|
||||
for (auto stateOp : machineOp.getOps<fsm::StateOp>()) {
|
||||
assignStateOutputOperands(b, stateOp,
|
||||
/*topLevelDone=*/false);
|
||||
assignStateTransitionGuard(b, stateOp);
|
||||
}
|
||||
|
||||
// Assign top-level go guard in the transition state.
|
||||
size_t topLevelGoIdx = nGroups;
|
||||
assignStateTransitionGuard(b, entryState->getState(),
|
||||
{machineOp.getArgument(topLevelGoIdx)});
|
||||
|
||||
// Assign top-level done in the exit state.
|
||||
assignStateOutputOperands(b, exitState->getState(),
|
||||
/*topLevelDone=*/true);
|
||||
walkMachine();
|
||||
materializeGroupIO();
|
||||
assignAttributes();
|
||||
}
|
||||
|
||||
std::unique_ptr<mlir::Pass> circt::createMaterializeCalyxToFSMPass() {
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
//===- RemoveGroupsFromFSM.cpp - Remove Groups Pass -------------*- C++ -*-===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Contains the definitions of the Remove Groups pass.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "../PassDetail.h"
|
||||
#include "circt/Conversion/CalyxToFSM.h"
|
||||
#include "circt/Dialect/Calyx/CalyxHelpers.h"
|
||||
#include "circt/Dialect/Comb/CombOps.h"
|
||||
#include "circt/Dialect/FSM/FSMGraph.h"
|
||||
#include "circt/Support/BackedgeBuilder.h"
|
||||
#include "circt/Support/LLVM.h"
|
||||
#include "mlir/IR/BuiltinTypes.h"
|
||||
#include "mlir/IR/OperationSupport.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
using namespace circt;
|
||||
using namespace calyx;
|
||||
using namespace mlir;
|
||||
using namespace fsm;
|
||||
|
||||
namespace {
|
||||
|
||||
struct CalyxRemoveGroupsFromFSM
|
||||
: public CalyxRemoveGroupsFromFSMBase<CalyxRemoveGroupsFromFSM> {
|
||||
void runOnOperation() override;
|
||||
|
||||
// Outlines the `fsm.machine` operation from within the `calyx.control`
|
||||
// operation to the module scope, and instantiates the FSM. By doing so, we
|
||||
// record the association between FSM outputs and group go signals as well as
|
||||
// FSM inputs, which are backedges to the group done signals.
|
||||
LogicalResult outlineMachine();
|
||||
|
||||
/// Makes several modifications to the operations of a GroupOp:
|
||||
/// 1. Assign the 'done' signal of the component with the done_op of the top
|
||||
/// level control group.
|
||||
/// 2. Append the 'go' signal of the component to guard of each assignment.
|
||||
/// 3. Replace all uses of GroupGoOp with the respective guard, and delete the
|
||||
/// GroupGoOp.
|
||||
/// 4. Remove the GroupDoneOp.
|
||||
LogicalResult modifyGroupOperations();
|
||||
|
||||
/// Inlines each group in the WiresOp.
|
||||
void inlineGroups();
|
||||
|
||||
/// A handle to the machine under transformation.
|
||||
MachineOp machineOp;
|
||||
|
||||
// A handle to the component op under transformation.
|
||||
ComponentOp componentOp;
|
||||
|
||||
OpBuilder *b;
|
||||
BackedgeBuilder *bb;
|
||||
|
||||
// A mapping between group names and their 'go' inputs generated by the FSM.
|
||||
DenseMap<StringAttr, Value> groupGoSignals;
|
||||
|
||||
// A mapping between group names and their 'done' output wires sent to
|
||||
// the FSM.
|
||||
DenseMap<StringAttr, calyx::WireLibOp> groupDoneWires;
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
LogicalResult CalyxRemoveGroupsFromFSM::modifyGroupOperations() {
|
||||
auto loc = componentOp.getLoc();
|
||||
for (auto group : componentOp.getWiresOp().getOps<GroupOp>()) {
|
||||
auto groupGo = group.getGoOp();
|
||||
if (groupGo)
|
||||
return emitError(loc)
|
||||
<< "This pass does not need `calyx.group_go` operations.";
|
||||
|
||||
auto groupDone = group.getDoneOp();
|
||||
if (!groupDone)
|
||||
return emitError(loc) << "Group " << group.getSymName()
|
||||
<< " does not have a `calyx.group_done` operation";
|
||||
|
||||
// Update group assignments to guard with the group go signal.
|
||||
auto fsmGroupGo = groupGoSignals.find(group.getSymNameAttr());
|
||||
assert(fsmGroupGo != groupGoSignals.end() &&
|
||||
"Could not find FSM go signal for group");
|
||||
|
||||
updateGroupAssignmentGuards(*b, group, fsmGroupGo->second);
|
||||
|
||||
// Create a calyx wire for the group done signal, and assign it to the
|
||||
// expression of the group_done operation.
|
||||
auto doneWireIt = groupDoneWires.find(group.getSymNameAttr());
|
||||
assert(doneWireIt != groupDoneWires.end() &&
|
||||
"Could not find FSM done backedge for group");
|
||||
auto doneWire = doneWireIt->second;
|
||||
|
||||
b->setInsertionPointToEnd(componentOp.getWiresOp().getBodyBlock());
|
||||
b->create<calyx::AssignOp>(loc, doneWire.getIn(), groupDone.getSrc(),
|
||||
groupDone.getGuard());
|
||||
|
||||
groupDone.erase();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Inlines each group in the WiresOp.
|
||||
void CalyxRemoveGroupsFromFSM::inlineGroups() {
|
||||
auto &wiresRegion = componentOp.getWiresOp().getRegion();
|
||||
auto &wireBlocks = wiresRegion.getBlocks();
|
||||
auto lastBlock = wiresRegion.end();
|
||||
|
||||
// Inline the body of each group as a Block into the WiresOp.
|
||||
wiresRegion.walk([&](GroupOp group) {
|
||||
wireBlocks.splice(lastBlock, group.getRegion().getBlocks());
|
||||
group->erase();
|
||||
});
|
||||
|
||||
// Merge the operations of each Block into the first block of the WiresOp.
|
||||
auto firstBlock = wireBlocks.begin();
|
||||
for (auto it = firstBlock, e = lastBlock; it != e; ++it) {
|
||||
if (it == firstBlock)
|
||||
continue;
|
||||
firstBlock->getOperations().splice(firstBlock->end(), it->getOperations());
|
||||
}
|
||||
|
||||
// Erase the (now) empty blocks.
|
||||
while (&wiresRegion.front() != &wiresRegion.back())
|
||||
wiresRegion.back().erase();
|
||||
}
|
||||
|
||||
LogicalResult CalyxRemoveGroupsFromFSM::outlineMachine() {
|
||||
// Walk all operations within the machine and gather the SSA values which are
|
||||
// referenced in case they are not defined within the machine.
|
||||
// MapVector ensures determinism.
|
||||
llvm::MapVector<Value, SmallVector<Operation *>> referencedValues;
|
||||
machineOp.walk([&](Operation *op) {
|
||||
for (auto &operand : op->getOpOperands()) {
|
||||
if (auto barg = operand.get().dyn_cast<BlockArgument>()) {
|
||||
if (barg.getOwner()->getParentOp() == machineOp)
|
||||
continue;
|
||||
|
||||
// A block argument defined outside of the machineOp.
|
||||
referencedValues[operand.get()].push_back(op);
|
||||
} else {
|
||||
auto *defOp = operand.get().getDefiningOp();
|
||||
auto machineOpParent = defOp->getParentOfType<MachineOp>();
|
||||
if (machineOpParent && machineOpParent == machineOp)
|
||||
continue;
|
||||
|
||||
referencedValues[operand.get()].push_back(op);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add a new input to the machine for each referenced SSA value and replace
|
||||
// all uses of the value with the new input.
|
||||
DenseMap<Value, size_t> ssaInputIndices;
|
||||
auto machineOutputTypes = machineOp.getFunctionType().getResults();
|
||||
auto currentInputs = machineOp.getFunctionType().getInputs();
|
||||
llvm::SmallVector<Type> machineInputTypes(currentInputs);
|
||||
|
||||
for (auto &[value, users] : referencedValues) {
|
||||
ssaInputIndices[value] = machineOp.getBody().getNumArguments();
|
||||
auto t = value.getType();
|
||||
auto arg = machineOp.getBody().addArgument(t, b->getUnknownLoc());
|
||||
machineInputTypes.push_back(t);
|
||||
for (auto *user : users) {
|
||||
for (auto &operand : user->getOpOperands()) {
|
||||
if (operand.get() == value)
|
||||
operand.set(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the machineOp type.
|
||||
machineOp.setType(b->getFunctionType(machineInputTypes, machineOutputTypes));
|
||||
|
||||
// Move the machine to module scope
|
||||
machineOp->moveBefore(componentOp);
|
||||
size_t nMachineInputs = machineOp.getBody().getNumArguments();
|
||||
|
||||
// Create an fsm.hwinstance in the Calyx component scope with backedges for
|
||||
// the group done inputs.
|
||||
auto groupDoneInputsAttr =
|
||||
machineOp->getAttrOfType<DictionaryAttr>(calyxToFSM::sGroupDoneInputs);
|
||||
auto groupGoOutputsAttr =
|
||||
machineOp->getAttrOfType<DictionaryAttr>(calyxToFSM::sGroupGoOutputs);
|
||||
if (!groupDoneInputsAttr || !groupGoOutputsAttr)
|
||||
return emitError(machineOp.getLoc())
|
||||
<< "MachineOp does not have a " << calyxToFSM::sGroupDoneInputs
|
||||
<< " or " << calyxToFSM::sGroupGoOutputs
|
||||
<< " attribute. Was --materialize-calyx-to-fsm run before "
|
||||
"this pass?";
|
||||
|
||||
b->setInsertionPointToStart(&componentOp.getBody().front());
|
||||
|
||||
// Maintain a mapping between the FSM input index and the SSA value.
|
||||
// We do this to sanity check that all inputs occur in the expected order.
|
||||
DenseMap<size_t, Value> fsmInputMap;
|
||||
|
||||
// First we inspect the groupDoneInputsAttr map and create backedges.
|
||||
for (auto &namedAttr : groupDoneInputsAttr.getValue()) {
|
||||
auto name = namedAttr.getName();
|
||||
auto idx = namedAttr.getValue().cast<IntegerAttr>();
|
||||
auto inputIdx = idx.cast<IntegerAttr>().getInt();
|
||||
if (fsmInputMap.count(inputIdx))
|
||||
return emitError(machineOp.getLoc())
|
||||
<< "MachineOp has duplicate input index " << idx;
|
||||
|
||||
// Create a wire for the group done input.
|
||||
b->setInsertionPointToStart(&componentOp.getBody().front());
|
||||
auto groupDoneWire = b->create<calyx::WireLibOp>(
|
||||
componentOp.getLoc(), name.str() + "_done", b->getI1Type());
|
||||
fsmInputMap[inputIdx] = groupDoneWire.getOut();
|
||||
groupDoneWires[name] = groupDoneWire;
|
||||
}
|
||||
|
||||
// Then we inspect the top level go/done attributes.
|
||||
auto topLevelGoAttr =
|
||||
machineOp->getAttrOfType<IntegerAttr>(calyxToFSM::sFSMTopLevelGoIndex);
|
||||
if (!topLevelGoAttr)
|
||||
return emitError(machineOp.getLoc())
|
||||
<< "MachineOp does not have a " << calyxToFSM::sFSMTopLevelGoIndex
|
||||
<< " attribute.";
|
||||
fsmInputMap[topLevelGoAttr.getInt()] = componentOp.getGoPort();
|
||||
|
||||
auto topLevelDoneAttr =
|
||||
machineOp->getAttrOfType<IntegerAttr>(calyxToFSM::sFSMTopLevelDoneIndex);
|
||||
if (!topLevelDoneAttr)
|
||||
return emitError(machineOp.getLoc())
|
||||
<< "MachineOp does not have a " << calyxToFSM::sFSMTopLevelDoneIndex
|
||||
<< " attribute.";
|
||||
|
||||
// Then we inspect the external SSA values.
|
||||
for (auto [value, idx] : ssaInputIndices) {
|
||||
if (fsmInputMap.count(idx))
|
||||
return emitError(machineOp.getLoc())
|
||||
<< "MachineOp has duplicate input index " << idx;
|
||||
fsmInputMap[idx] = value;
|
||||
}
|
||||
|
||||
if (fsmInputMap.size() != nMachineInputs)
|
||||
return emitError(machineOp.getLoc())
|
||||
<< "MachineOp has " << nMachineInputs
|
||||
<< " inputs, but only recorded " << fsmInputMap.size()
|
||||
<< " inputs. This either means that --materialize-calyx-to-fsm "
|
||||
"failed or that there is a mismatch in the MachineOp attributes.";
|
||||
|
||||
// Convert the fsmInputMap to a list.
|
||||
llvm::SmallVector<Value> fsmInputs;
|
||||
for (size_t idx = 0; idx < nMachineInputs; ++idx) {
|
||||
auto it = fsmInputMap.find(idx);
|
||||
assert(it != fsmInputMap.end() && "Missing FSM input index");
|
||||
fsmInputs.push_back(it->second);
|
||||
}
|
||||
|
||||
// Instantiate the FSM.
|
||||
auto fsmInstance = b->create<fsm::HWInstanceOp>(
|
||||
machineOp.getLoc(), machineOutputTypes, b->getStringAttr("controller"),
|
||||
machineOp.getSymNameAttr(), fsmInputs, componentOp.getClkPort(),
|
||||
componentOp.getResetPort());
|
||||
|
||||
// Record the FSM output group go signals.
|
||||
for (auto namedAttr : groupGoOutputsAttr.getValue()) {
|
||||
auto name = namedAttr.getName();
|
||||
auto idx = namedAttr.getValue().cast<IntegerAttr>().getInt();
|
||||
groupGoSignals[name] = fsmInstance.getResult(idx);
|
||||
}
|
||||
|
||||
// Assign FSM top level done to the component done.
|
||||
b->setInsertionPointToEnd(componentOp.getWiresOp().getBodyBlock());
|
||||
b->create<calyx::AssignOp>(machineOp.getLoc(), componentOp.getDonePort(),
|
||||
fsmInstance.getResult(topLevelDoneAttr.getInt()));
|
||||
|
||||
return success();
|
||||
}
|
||||
|
||||
void CalyxRemoveGroupsFromFSM::runOnOperation() {
|
||||
componentOp = getOperation();
|
||||
auto *ctx = componentOp.getContext();
|
||||
auto builder = OpBuilder(ctx);
|
||||
builder.setInsertionPointToStart(&componentOp.getBody().front());
|
||||
auto backedgeBuilder = BackedgeBuilder(builder, componentOp.getLoc());
|
||||
b = &builder;
|
||||
bb = &backedgeBuilder;
|
||||
|
||||
// Locate the FSM machine in the control op..
|
||||
auto machineOps = componentOp.getControlOp().getOps<fsm::MachineOp>();
|
||||
if (std::distance(machineOps.begin(), machineOps.end()) != 1) {
|
||||
emitError(componentOp.getLoc())
|
||||
<< "Expected exactly one fsm.MachineOp in the control op";
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
machineOp = *machineOps.begin();
|
||||
|
||||
if (failed(outlineMachine()) || failed(modifyGroupOperations())) {
|
||||
signalPassFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
inlineGroups();
|
||||
}
|
||||
|
||||
std::unique_ptr<mlir::Pass> circt::createRemoveGroupsFromFSMPass() {
|
||||
return std::make_unique<CalyxRemoveGroupsFromFSM>();
|
||||
}
|
|
@ -614,7 +614,9 @@ MachineOpConverter::convertTransitions( // NOLINT(misc-no-recursion)
|
|||
if (failed(guardOpRes))
|
||||
return failure();
|
||||
|
||||
auto guard = cast<ReturnOp>(*guardOpRes).getOperand();
|
||||
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))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "PassDetails.h"
|
||||
#include "circt/Dialect/Calyx/CalyxHelpers.h"
|
||||
#include "circt/Dialect/Calyx/CalyxOps.h"
|
||||
#include "circt/Dialect/Calyx/CalyxPasses.h"
|
||||
#include "circt/Support/LLVM.h"
|
||||
|
|
|
@ -26,22 +26,6 @@ namespace calyx {
|
|||
#define GEN_PASS_CLASSES
|
||||
#include "circt/Dialect/Calyx/CalyxPasses.h.inc"
|
||||
|
||||
/// Updates the guard of each assignment within a group with `op`.
|
||||
template <typename Op>
|
||||
static void updateGroupAssignmentGuards(OpBuilder &builder, GroupOp &group,
|
||||
Op &op) {
|
||||
group.walk([&](AssignOp assign) {
|
||||
if (assign.getGuard())
|
||||
// If the assignment is guarded already, take the bitwise & of the current
|
||||
// guard and the group's go signal.
|
||||
assign->setOperand(2, builder.create<comb::AndOp>(
|
||||
group.getLoc(), assign.getGuard(), op, false));
|
||||
else
|
||||
// Otherwise, just insert it as the guard.
|
||||
assign->insertOperands(2, {op});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace calyx
|
||||
} // namespace circt
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "PassDetails.h"
|
||||
#include "circt/Dialect/Calyx/CalyxHelpers.h"
|
||||
#include "circt/Dialect/Calyx/CalyxOps.h"
|
||||
#include "circt/Dialect/Calyx/CalyxPasses.h"
|
||||
#include "circt/Support/LLVM.h"
|
||||
|
|
|
@ -111,13 +111,21 @@ void MachineOp::print(OpAsmPrinter &p) {
|
|||
getArgAttrsAttrName(), getResAttrsAttrName());
|
||||
}
|
||||
|
||||
static LogicalResult compareTypes(TypeRange rangeA, TypeRange rangeB) {
|
||||
static LogicalResult compareTypes(Location loc, TypeRange rangeA,
|
||||
TypeRange rangeB) {
|
||||
if (rangeA.size() != rangeB.size())
|
||||
return failure();
|
||||
return emitError(loc) << "mismatch in number of types compared ("
|
||||
<< rangeA.size() << " != " << rangeB.size() << ")";
|
||||
|
||||
for (auto zip : llvm::zip(rangeA, rangeB))
|
||||
if (std::get<0>(zip) != std::get<1>(zip))
|
||||
return failure();
|
||||
size_t index = 0;
|
||||
for (auto zip : llvm::zip(rangeA, rangeB)) {
|
||||
auto typeA = std::get<0>(zip);
|
||||
auto typeB = std::get<1>(zip);
|
||||
if (typeA != typeB)
|
||||
return emitError(loc) << "type mismatch at index " << index << " ("
|
||||
<< typeA << " != " << typeB << ")";
|
||||
++index;
|
||||
}
|
||||
|
||||
return success();
|
||||
}
|
||||
|
@ -130,7 +138,8 @@ LogicalResult MachineOp::verify() {
|
|||
// Verify that the argument list of the function and the arg list of the entry
|
||||
// block line up. The trait already verified that the number of arguments is
|
||||
// the same between the signature and the block.
|
||||
if (failed(compareTypes(getArgumentTypes(), front().getArgumentTypes())))
|
||||
if (failed(compareTypes(getLoc(), getArgumentTypes(),
|
||||
front().getArgumentTypes())))
|
||||
return emitOpError(
|
||||
"entry block argument types must match the machine input types");
|
||||
|
||||
|
@ -196,7 +205,7 @@ static LogicalResult verifyCallerTypes(OpType op) {
|
|||
return op.emitError("cannot find machine definition");
|
||||
|
||||
// Check operand types first.
|
||||
if (failed(compareTypes(machine.getArgumentTypes(),
|
||||
if (failed(compareTypes(op.getLoc(), machine.getArgumentTypes(),
|
||||
op.getInputs().getTypes()))) {
|
||||
auto diag =
|
||||
op.emitOpError("operand types must match the machine input types");
|
||||
|
@ -205,8 +214,8 @@ static LogicalResult verifyCallerTypes(OpType op) {
|
|||
}
|
||||
|
||||
// Check result types.
|
||||
if (failed(
|
||||
compareTypes(machine.getResultTypes(), op.getOutputs().getTypes()))) {
|
||||
if (failed(compareTypes(op.getLoc(), machine.getResultTypes(),
|
||||
op.getOutputs().getTypes()))) {
|
||||
auto diag =
|
||||
op.emitOpError("result types must match the machine output types");
|
||||
diag.attachNote(machine->getLoc()) << "original machine declared here";
|
||||
|
@ -326,7 +335,8 @@ LogicalResult OutputOp::verify() {
|
|||
// Verify that the result list of the machine and the operand list of the
|
||||
// OutputOp line up.
|
||||
auto machine = (*this)->getParentOfType<MachineOp>();
|
||||
if (failed(compareTypes(machine.getResultTypes(), getOperandTypes())))
|
||||
if (failed(
|
||||
compareTypes(getLoc(), machine.getResultTypes(), getOperandTypes())))
|
||||
return emitOpError("operand types must match the machine output types");
|
||||
|
||||
return success();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// RUN: circt-opt --split-input-file -pass-pipeline='builtin.module(calyx.component(lower-calyx-to-fsm))' %s | FileCheck %s
|
||||
|
||||
// CHECK: fsm.machine @control() attributes {compiledGroups = [@true, @false, @cond], initialState = "fsm_entry"} {
|
||||
// CHECK: fsm.machine @control()
|
||||
// CHECK-NEXT: fsm.state @fsm_entry output {
|
||||
// CHECK-NEXT: fsm.output
|
||||
// CHECK-NEXT: } transitions {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// RUN: circt-opt --split-input-file -pass-pipeline='builtin.module(calyx.component(lower-calyx-to-fsm))' %s | FileCheck %s
|
||||
|
||||
// CHECK: fsm.machine @control() attributes {compiledGroups = [@C, @B, @A], initialState = "fsm_entry"} {
|
||||
// CHECK: fsm.machine @control()
|
||||
// CHECK-NEXT: fsm.state @fsm_entry output {
|
||||
// CHECK-NEXT: fsm.output
|
||||
// CHECK-NEXT: } transitions {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// RUN: circt-opt --split-input-file -pass-pipeline='builtin.module(calyx.component(lower-calyx-to-fsm))' %s | FileCheck %s
|
||||
|
||||
// CHECK: fsm.machine @control() attributes {compiledGroups = [@do_add, @do_add, @do_add, @cond], initialState = "fsm_entry"} {
|
||||
// CHECK: fsm.machine @control()
|
||||
// CHECK-NEXT: fsm.state @fsm_entry output {
|
||||
// CHECK-NEXT: fsm.output
|
||||
// CHECK-NEXT: } transitions {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// RUN: circt-opt -pass-pipeline='builtin.module(calyx.component(materialize-calyx-to-fsm))' %s | FileCheck %s
|
||||
|
||||
// CHECK: fsm.machine @control(%[[A_DONE:.*]]: i1, %[[B_DONE:.*]]: i1, %[[COND_DONE:.*]]: i1, %[[TOP_LEVEL_GO:.*]]: i1) -> (i1, i1, i1, i1) attributes {compiledGroups = [@A, @B, @cond], initialState = "[[FSM_ENTRY:.*]]"} {
|
||||
// CHECK: fsm.machine @control(%[[A_DONE:.*]]: i1, %[[B_DONE:.*]]: i1, %[[COND_DONE:.*]]: i1, %[[TOP_LEVEL_GO:.*]]: i1) -> (i1, i1, i1, i1)
|
||||
// CHECK-NEXT: %[[C1:.*]] = hw.constant true
|
||||
// CHECK-NEXT: %[[C0:.*]] = hw.constant false
|
||||
// CHECK-NEXT: fsm.state @[[FSM_ENTRY]] output {
|
||||
// CHECK-NEXT: fsm.state @fsm_entry output {
|
||||
// CHECK-NEXT: fsm.output %[[C0]], %[[C0]], %[[C0]], %[[C0]] : i1, i1, i1, i1
|
||||
// CHECK-NEXT: } transitions {
|
||||
// CHECK-NEXT: fsm.transition @[[SEQ_0_COND:.*]] guard {
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
// RUN: circt-opt --split-input-file --calyx-remove-groups-fsm %s | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: fsm.machine @control(
|
||||
// CHECK-SAME: %[[VAL_0:.*]]: i1, %[[VAL_1:.*]]: i1, %[[VAL_2:.*]]: i1, %[[VAL_3:.*]]: i1, %[[VAL_4:.*]]: i8, %[[VAL_5:.*]]: i8) -> (i1, i1, i1, i1)
|
||||
// CHECK: %[[VAL_6:.*]] = hw.constant true
|
||||
// CHECK: %[[VAL_7:.*]] = hw.constant false
|
||||
// CHECK: fsm.state @fsm_entry output {
|
||||
// CHECK: fsm.output %[[VAL_7]], %[[VAL_7]], %[[VAL_7]], %[[VAL_7]] : i1, i1, i1, i1
|
||||
// CHECK: } transitions {
|
||||
// CHECK: fsm.transition @seq_0_A guard {
|
||||
// CHECK: %[[VAL_8:.*]] = comb.icmp eq %[[VAL_4]], %[[VAL_5]] : i8
|
||||
// CHECK: fsm.return %[[VAL_8]]
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
// CHECK: fsm.state @fsm_exit output {
|
||||
// CHECK: fsm.output %[[VAL_7]], %[[VAL_7]], %[[VAL_7]], %[[VAL_6]] : i1, i1, i1, i1
|
||||
// CHECK: }
|
||||
// CHECK: fsm.state @seq_2_C output {
|
||||
// CHECK: fsm.output %[[VAL_6]], %[[VAL_7]], %[[VAL_7]], %[[VAL_7]] : i1, i1, i1, i1
|
||||
// CHECK: } transitions {
|
||||
// CHECK: fsm.transition @fsm_exit guard {
|
||||
// CHECK: fsm.return %[[VAL_0]]
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
// CHECK: fsm.state @seq_1_B output {
|
||||
// CHECK: fsm.output %[[VAL_7]], %[[VAL_6]], %[[VAL_7]], %[[VAL_7]] : i1, i1, i1, i1
|
||||
// CHECK: } transitions {
|
||||
// CHECK: fsm.transition @seq_2_C guard {
|
||||
// CHECK: fsm.return %[[VAL_1]]
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
// CHECK: fsm.state @seq_0_A output {
|
||||
// CHECK: fsm.output %[[VAL_7]], %[[VAL_7]], %[[VAL_6]], %[[VAL_7]] : i1, i1, i1, i1
|
||||
// CHECK: } transitions {
|
||||
// CHECK: fsm.transition @seq_1_B guard {
|
||||
// CHECK: fsm.return %[[VAL_2]]
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
|
||||
// CHECK-LABEL: calyx.component @main(
|
||||
// CHECK-SAME: %[[VAL_0:.*]]: i1 {go},
|
||||
// CHECK-SAME: %[[VAL_1:.*]]: i1 {reset},
|
||||
// CHECK-SAME: %[[VAL_2:.*]]: i1 {clk}) -> (
|
||||
// CHECK-SAME: %[[VAL_3:.*]]: i1 {done}) {
|
||||
// CHECK: %[[VAL_4:.*]], %[[VAL_5:.*]] = calyx.std_wire @C_done : i1, i1
|
||||
// CHECK: %[[VAL_6:.*]]:4 = fsm.hw_instance "controller" @control(%[[VAL_5]], %[[VAL_7:.*]], %[[VAL_8:.*]], %[[VAL_0]], %[[VAL_9:.*]], %[[VAL_10:.*]]), clock %[[VAL_2]], reset %[[VAL_1]] : (i1, i1, i1, i1, i8, i8) -> (i1, i1, i1, i1)
|
||||
// CHECK: %[[VAL_11:.*]], %[[VAL_7]] = calyx.std_wire @B_done : i1, i1
|
||||
// CHECK: %[[VAL_12:.*]], %[[VAL_8]] = calyx.std_wire @A_done : i1, i1
|
||||
// CHECK: %[[VAL_13:.*]] = hw.constant 2 : i8
|
||||
// CHECK: %[[VAL_14:.*]] = hw.constant 1 : i8
|
||||
// CHECK: %[[VAL_15:.*]] = hw.constant 0 : i8
|
||||
// CHECK: %[[VAL_16:.*]] = hw.constant true
|
||||
// CHECK: %[[VAL_10]], %[[VAL_17:.*]], %[[VAL_18:.*]], %[[VAL_19:.*]], %[[VAL_20:.*]], %[[VAL_21:.*]] = calyx.register @a : i8, i1, i1, i1, i8, i1
|
||||
// CHECK: %[[VAL_9]], %[[VAL_22:.*]], %[[VAL_23:.*]], %[[VAL_24:.*]], %[[VAL_25:.*]], %[[VAL_26:.*]] = calyx.register @b : i8, i1, i1, i1, i8, i1
|
||||
// CHECK: %[[VAL_27:.*]], %[[VAL_28:.*]], %[[VAL_29:.*]], %[[VAL_30:.*]], %[[VAL_31:.*]], %[[VAL_32:.*]] = calyx.register @c : i8, i1, i1, i1, i8, i1
|
||||
// CHECK: calyx.wires {
|
||||
// CHECK: %[[VAL_33:.*]] = calyx.undef : i1
|
||||
// CHECK: calyx.assign %[[VAL_3]] = %[[VAL_6]]#3 : i1
|
||||
// CHECK: calyx.assign %[[VAL_12]] = %[[VAL_21]] : i1
|
||||
// CHECK: calyx.assign %[[VAL_11]] = %[[VAL_26]] : i1
|
||||
// CHECK: calyx.assign %[[VAL_4]] = %[[VAL_32]] : i1
|
||||
// CHECK: calyx.assign %[[VAL_10]] = %[[VAL_6]]#2 ? %[[VAL_15]] : i8
|
||||
// CHECK: calyx.assign %[[VAL_17]] = %[[VAL_6]]#2 ? %[[VAL_16]] : i1
|
||||
// CHECK: calyx.assign %[[VAL_9]] = %[[VAL_6]]#1 ? %[[VAL_14]] : i8
|
||||
// CHECK: calyx.assign %[[VAL_22]] = %[[VAL_6]]#1 ? %[[VAL_16]] : i1
|
||||
// CHECK: calyx.assign %[[VAL_27]] = %[[VAL_6]]#0 ? %[[VAL_13]] : i8
|
||||
// CHECK: calyx.assign %[[VAL_28]] = %[[VAL_6]]#0 ? %[[VAL_16]] : i1
|
||||
// CHECK: }
|
||||
// CHECK: calyx.control {
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
|
||||
calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%done: i1 {done}) {
|
||||
%c2_i8 = hw.constant 2 : i8
|
||||
%c1_i8 = hw.constant 1 : i8
|
||||
%c0_i8 = hw.constant 0 : i8
|
||||
%true = hw.constant true
|
||||
%a.in, %a.write_en, %a.clk, %a.reset, %a.out, %a.done = calyx.register @a : i8, i1, i1, i1, i8, i1
|
||||
%b.in, %b.write_en, %b.clk, %b.reset, %b.out, %b.done = calyx.register @b : i8, i1, i1, i1, i8, i1
|
||||
%c.in, %c.write_en, %c.clk, %c.reset, %c.out, %c.done = calyx.register @c : i8, i1, i1, i1, i8, i1
|
||||
calyx.wires {
|
||||
%0 = calyx.undef : i1
|
||||
calyx.group @A {
|
||||
calyx.assign %a.in = %c0_i8 : i8
|
||||
calyx.assign %a.write_en = %true : i1
|
||||
calyx.group_done %a.done : i1
|
||||
}
|
||||
calyx.group @B {
|
||||
calyx.assign %b.in = %c1_i8 : i8
|
||||
calyx.assign %b.write_en = %true : i1
|
||||
calyx.group_done %b.done : i1
|
||||
}
|
||||
calyx.group @C {
|
||||
calyx.assign %c.in = %c2_i8 : i8
|
||||
calyx.assign %c.write_en = %true : i1
|
||||
calyx.group_done %c.done : i1
|
||||
}
|
||||
}
|
||||
calyx.control {
|
||||
fsm.machine @control(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1) -> (i1, i1, i1, i1) attributes
|
||||
{compiledGroups = [@C, @B, @A],
|
||||
calyx.fsm_group_done_inputs = {A = 2 : i64, B = 1 : i64, C = 0 : i64},
|
||||
calyx.fsm_group_go_outputs = {A = 2 : i64, B = 1 : i64, C = 0 : i64},
|
||||
calyx.fsm_top_level_done = 3 : i64,
|
||||
calyx.fsm_top_level_go = 3 : i64,
|
||||
initialState = "fsm_entry"} {
|
||||
%true_0 = hw.constant true
|
||||
%false = hw.constant false
|
||||
fsm.state @fsm_entry output {
|
||||
fsm.output %false, %false, %false, %false : i1, i1, i1, i1
|
||||
} transitions {
|
||||
fsm.transition @seq_0_A guard {
|
||||
%0 = comb.icmp eq %b.in, %a.in : i8
|
||||
fsm.return %0
|
||||
}
|
||||
}
|
||||
fsm.state @fsm_exit output {
|
||||
fsm.output %false, %false, %false, %true_0 : i1, i1, i1, i1
|
||||
} transitions {
|
||||
}
|
||||
fsm.state @seq_2_C output {
|
||||
fsm.output %true_0, %false, %false, %false : i1, i1, i1, i1
|
||||
} transitions {
|
||||
fsm.transition @fsm_exit guard {
|
||||
fsm.return %arg0
|
||||
}
|
||||
}
|
||||
fsm.state @seq_1_B output {
|
||||
fsm.output %false, %true_0, %false, %false : i1, i1, i1, i1
|
||||
} transitions {
|
||||
fsm.transition @seq_2_C guard {
|
||||
fsm.return %arg1
|
||||
}
|
||||
}
|
||||
fsm.state @seq_0_A output {
|
||||
fsm.output %false, %false, %true_0, %false : i1, i1, i1, i1
|
||||
} transitions {
|
||||
fsm.transition @seq_1_B guard {
|
||||
fsm.return %arg2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ fsm.machine @foo(%arg0: i1) -> i1 attributes {initialState = "IDLE"} {
|
|||
|
||||
fsm.state @IDLE output {
|
||||
%true = arith.constant true
|
||||
// expected-error @+2 {{mismatch in number of types compared (1 != 2)}}
|
||||
// expected-error @+1 {{'fsm.output' op operand types must match the machine output types}}
|
||||
fsm.output %true, %true : i1, i1
|
||||
} transitions {
|
||||
|
|
Loading…
Reference in New Issue