CIRCT  18.0.0git
FSMToSV.cpp
Go to the documentation of this file.
1 //===- FSMToSV.cpp - Convert FSM to HW and SV Dialect ---------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
10 #include "../PassDetail.h"
13 #include "circt/Dialect/HW/HWOps.h"
14 #include "circt/Dialect/SV/SVOps.h"
17 #include "mlir/Transforms/RegionUtils.h"
18 #include "llvm/ADT/TypeSwitch.h"
19 
20 #include <memory>
21 #include <variant>
22 
23 using namespace mlir;
24 using namespace circt;
25 using namespace fsm;
26 
27 /// Get the port info of a FSM machine. Clock and reset port are also added.
28 namespace {
29 struct ClkRstIdxs {
30  size_t clockIdx;
31  size_t resetIdx;
32 };
33 } // namespace
34 static ClkRstIdxs getMachinePortInfo(SmallVectorImpl<hw::PortInfo> &ports,
35  MachineOp machine, OpBuilder &b) {
36  // Get the port info of the machine inputs and outputs.
37  machine.getHWPortInfo(ports);
38  ClkRstIdxs specialPorts;
39 
40  // Add clock port.
41  hw::PortInfo clock;
42  clock.name = b.getStringAttr("clk");
44  clock.type = seq::ClockType::get(b.getContext());
45  clock.argNum = machine.getNumArguments();
46  ports.push_back(clock);
47  specialPorts.clockIdx = clock.argNum;
48 
49  // Add reset port.
50  hw::PortInfo reset;
51  reset.name = b.getStringAttr("rst");
53  reset.type = b.getI1Type();
54  reset.argNum = machine.getNumArguments() + 1;
55  ports.push_back(reset);
56  specialPorts.resetIdx = reset.argNum;
57 
58  return specialPorts;
59 }
60 
61 // Clones constants implicitly captured by the region, into the region.
62 static void cloneConstantsIntoRegion(Region &region, OpBuilder &builder) {
63  // Values implicitly captured by the region.
64  llvm::SetVector<Value> captures;
65  getUsedValuesDefinedAbove(region, region, captures);
66 
67  OpBuilder::InsertionGuard guard(builder);
68  builder.setInsertionPointToStart(&region.front());
69 
70  // Clone ConstantLike operations into the region.
71  for (auto &capture : captures) {
72  Operation *op = capture.getDefiningOp();
73  if (!op || !op->hasTrait<OpTrait::ConstantLike>())
74  continue;
75 
76  Operation *cloned = builder.clone(*op);
77  for (auto [orig, replacement] :
78  llvm::zip(op->getResults(), cloned->getResults()))
79  replaceAllUsesInRegionWith(orig, replacement, region);
80  }
81 }
82 
83 namespace {
84 
85 class StateEncoding {
86  // An class for handling state encoding. The class is designed to
87  // abstract away how states are selected in case patterns, referred to as
88  // values, and used as selection signals for muxes.
89 
90 public:
91  StateEncoding(OpBuilder &b, hw::TypeScopeOp typeScope, MachineOp machine,
92  hw::HWModuleOp hwModule);
93 
94  // Get the encoded value for a state.
95  Value encode(StateOp state);
96  // Get the state corresponding to an encoded value.
97  StateOp decode(Value value);
98 
99  // Returns the type which encodes the state values.
100  Type getStateType() { return stateType; }
101 
102  // Returns a case pattern which matches the provided state.
103  std::unique_ptr<sv::CasePattern> getCasePattern(StateOp state);
104 
105 protected:
106  // Creates a constant value in the module for the given encoded state
107  // and records the state value in the mappings. An inner symbol is
108  // attached to the wire to avoid it being optimized away.
109  // The constant can optionally be assigned behind a sv wire - doing so at this
110  // point ensures that constants don't end up behind "_GEN#" wires in the
111  // module.
112  void setEncoding(StateOp state, Value v, bool wire = false);
113 
114  // A mapping between a StateOp and its corresponding encoded value.
115  SmallDenseMap<StateOp, Value> stateToValue;
116 
117  // A mapping between an encoded value and its corresponding StateOp.
118  SmallDenseMap<Value, StateOp> valueToState;
119 
120  // A mapping between an encoded value and the source value in the IR.
121  SmallDenseMap<Value, Value> valueToSrcValue;
122 
123  // A typescope to emit the FSM enum type within.
124  hw::TypeScopeOp typeScope;
125 
126  // The enum type for the states.
127  Type stateType;
128 
129  OpBuilder &b;
130  MachineOp machine;
131  hw::HWModuleOp hwModule;
132 };
133 
134 StateEncoding::StateEncoding(OpBuilder &b, hw::TypeScopeOp typeScope,
135  MachineOp machine, hw::HWModuleOp hwModule)
136  : typeScope(typeScope), b(b), machine(machine), hwModule(hwModule) {
137  Location loc = machine.getLoc();
138  llvm::SmallVector<Attribute> stateNames;
139 
140  for (auto state : machine.getBody().getOps<StateOp>())
141  stateNames.push_back(b.getStringAttr(state.getName()));
142 
143  // Create an enum typedef for the states.
144  Type rawEnumType =
145  hw::EnumType::get(b.getContext(), b.getArrayAttr(stateNames));
146 
147  OpBuilder::InsertionGuard guard(b);
148  b.setInsertionPointToStart(&typeScope.getBodyRegion().front());
149  auto typedeclEnumType = b.create<hw::TypedeclOp>(
150  loc, b.getStringAttr(hwModule.getName() + "_state_t"),
151  TypeAttr::get(rawEnumType), nullptr);
152 
153  stateType = hw::TypeAliasType::get(
154  SymbolRefAttr::get(typeScope.getSymNameAttr(),
155  {FlatSymbolRefAttr::get(typedeclEnumType)}),
156  rawEnumType);
157 
158  // And create enum values for the states
159  b.setInsertionPointToStart(&hwModule.getBody().front());
160  for (auto state : machine.getBody().getOps<StateOp>()) {
161  auto fieldAttr = hw::EnumFieldAttr::get(
162  loc, b.getStringAttr(state.getName()), stateType);
163  auto enumConstantOp = b.create<hw::EnumConstantOp>(
164  loc, fieldAttr.getType().getValue(), fieldAttr);
165  setEncoding(state, enumConstantOp,
166  /*wire=*/true);
167  }
168 }
169 
170 // Get the encoded value for a state.
171 Value StateEncoding::encode(StateOp state) {
172  auto it = stateToValue.find(state);
173  assert(it != stateToValue.end() && "state not found");
174  return it->second;
175 }
176 // Get the state corresponding to an encoded value.
177 StateOp StateEncoding::decode(Value value) {
178  auto it = valueToState.find(value);
179  assert(it != valueToState.end() && "encoded state not found");
180  return it->second;
181 }
182 
183 // Returns a case pattern which matches the provided state.
184 std::unique_ptr<sv::CasePattern> StateEncoding::getCasePattern(StateOp state) {
185  // Get the field attribute for the state - fetch it through the encoding.
186  auto fieldAttr =
187  cast<hw::EnumConstantOp>(valueToSrcValue[encode(state)].getDefiningOp())
188  .getFieldAttr();
189  return std::make_unique<sv::CaseEnumPattern>(fieldAttr);
190 }
191 
192 void StateEncoding::setEncoding(StateOp state, Value v, bool wire) {
193  assert(stateToValue.find(state) == stateToValue.end() &&
194  "state already encoded");
195 
196  Value encodedValue;
197  if (wire) {
198  auto loc = machine.getLoc();
199  auto stateType = getStateType();
200  auto stateEncodingWire = b.create<sv::RegOp>(
201  loc, stateType, b.getStringAttr("to_" + state.getName()),
202  hw::InnerSymAttr::get(state.getNameAttr()));
203  b.create<sv::AssignOp>(loc, stateEncodingWire, v);
204  encodedValue = b.create<sv::ReadInOutOp>(loc, stateEncodingWire);
205  } else
206  encodedValue = v;
207  stateToValue[state] = encodedValue;
208  valueToState[encodedValue] = state;
209  valueToSrcValue[encodedValue] = v;
210 }
211 
212 class MachineOpConverter {
213 public:
214  MachineOpConverter(OpBuilder &builder, hw::TypeScopeOp typeScope,
215  MachineOp machineOp)
216  : machineOp(machineOp), typeScope(typeScope), b(builder) {}
217 
218  // Converts the machine op to a hardware module.
219  // 1. Creates a HWModuleOp for the machine op, with the same I/O as the FSM +
220  // clk/reset ports.
221  // 2. Creates a state register + encodings for the states visible in the
222  // machine.
223  // 3. Iterates over all states in the machine
224  // 3.1. Moves all `comb` logic into the body of the HW module
225  // 3.2. Records the SSA value(s) associated to the output ports in the state
226  // 3.3. iterates of the transitions of the state
227  // 3.3.1. Moves all `comb` logic in the transition guard/action regions to
228  // the body of the HW module.
229  // 3.3.2. Creates a case pattern for the transition guard
230  // 3.4. Creates a next-state value for the state based on the transition
231  // guards.
232  // 4. Assigns next-state values for the states in a case statement on the
233  // state reg.
234  // 5. Assigns the current-state outputs for the states in a case statement
235  // on the state reg.
236  LogicalResult dispatch();
237 
238 private:
239  struct StateConversionResult {
240  // Value of the next state output signal of the converted state.
241  Value nextState;
242  // Value of the output signals of the converted state.
243  llvm::SmallVector<Value> outputs;
244  };
245 
246  using StateConversionResults = DenseMap<StateOp, StateConversionResult>;
247 
248  // Converts a StateOp within this machine, and returns the value corresponding
249  // to the next-state output of the op.
250  FailureOr<StateConversionResult> convertState(StateOp state);
251 
252  // Converts the outgoing transitions of a state and returns the value
253  // corresponding to the next-state output of the op.
254  // Transitions are priority encoded in the order which they appear in the
255  // state transition region.
256  FailureOr<Value> convertTransitions(StateOp currentState,
257  ArrayRef<TransitionOp> transitions);
258 
259  // Moves operations from 'block' into module scope, failing if any op were
260  // deemed illegal. Returns the final op in the block if the op was a
261  // terminator. An optional 'exclude' filer can be provided to dynamically
262  // exclude some ops from being moved.
264  moveOps(Block *block,
265  llvm::function_ref<bool(Operation *)> exclude = nullptr);
266 
267  struct CaseMuxItem;
268  using StateCaseMapping =
269  llvm::SmallDenseMap<StateOp,
270  std::variant<Value, std::shared_ptr<CaseMuxItem>>>;
271  struct CaseMuxItem {
272  // The target wire to be assigned.
273  sv::RegOp wire;
274 
275  // The case select signal to be used.
276  Value select;
277 
278  // A mapping between a state and an assignment within that state.
279  // An assignment can either be a value or a nested CaseMuxItem. The latter
280  // case will create nested case statements.
281  StateCaseMapping assignmentInState;
282 
283  // An optional default value to be assigned before the case statement, if
284  // the case is not fully specified for all states.
285  std::optional<Value> defaultValue = {};
286  };
287 
288  // Build an SV-based case mux for the given assignments. Assignments are
289  // merged into the same case statement. Caller is expected to ensure that the
290  // insertion point is within an `always_...` block.
291  void buildStateCaseMux(llvm::MutableArrayRef<CaseMuxItem> assignments);
292 
293  // A handle to the state encoder for this machine.
294  std::unique_ptr<StateEncoding> encoding;
295 
296  // A deterministic ordering of the states in this machine.
297  llvm::SmallVector<StateOp> orderedStates;
298 
299  // A mapping from a fsm.variable op to its register.
301 
302  // A mapping from a state to variable updates performed during outgoing state
303  // transitions.
305  /*currentState*/ StateOp,
307  /*targetState*/ StateOp,
308  llvm::DenseMap</*targetVariable*/ VariableOp, /*targetValue*/ Value>>>
309  stateToVariableUpdates;
310 
311  // A handle to the MachineOp being converted.
312  MachineOp machineOp;
313 
314  // A handle to the HW ModuleOp being created.
315  hw::HWModuleOp hwModuleOp;
316 
317  // A handle to the state register of the machine.
318  seq::CompRegOp stateReg;
319 
320  // A typescope to emit the FSM enum type within.
321  hw::TypeScopeOp typeScope;
322 
323  OpBuilder &b;
324 };
325 
327 MachineOpConverter::moveOps(Block *block,
328  llvm::function_ref<bool(Operation *)> exclude) {
329  for (auto &op : llvm::make_early_inc_range(*block)) {
330  if (!isa<comb::CombDialect, hw::HWDialect, fsm::FSMDialect>(
331  op.getDialect()))
332  return op.emitOpError()
333  << "is unsupported (op from the "
334  << op.getDialect()->getNamespace() << " dialect).";
335 
336  if (exclude && exclude(&op))
337  continue;
338 
339  if (op.hasTrait<OpTrait::IsTerminator>())
340  return &op;
341 
342  op.moveBefore(hwModuleOp.getBodyBlock(), b.getInsertionPoint());
343  }
344  return nullptr;
345 }
346 
347 void MachineOpConverter::buildStateCaseMux(
348  llvm::MutableArrayRef<CaseMuxItem> assignments) {
349 
350  // Gather the select signal. All assignments are expected to use the same
351  // select signal.
352  Value select = assignments.front().select;
353  assert(llvm::all_of(
354  assignments,
355  [&](const CaseMuxItem &item) { return item.select == select; }) &&
356  "All assignments must use the same select signal.");
357 
358  sv::CaseOp caseMux;
359  // Default assignments.
360  for (auto &assignment : assignments) {
361  if (assignment.defaultValue)
362  b.create<sv::BPAssignOp>(assignment.wire.getLoc(), assignment.wire,
363  *assignment.defaultValue);
364  }
365 
366  // Case assignments.
367  caseMux = b.create<sv::CaseOp>(
368  machineOp.getLoc(), CaseStmtType::CaseStmt,
369  /*sv::ValidationQualifierTypeEnum::ValidationQualifierUnique, */ select,
370  /*numCases=*/machineOp.getNumStates() + 1, [&](size_t caseIdx) {
371  // Make Verilator happy for sized enums.
372  if (caseIdx == machineOp.getNumStates())
373  return std::unique_ptr<sv::CasePattern>(
374  new sv::CaseDefaultPattern(b.getContext()));
375  StateOp state = orderedStates[caseIdx];
376  return encoding->getCasePattern(state);
377  });
378 
379  // Create case assignments.
380  for (auto assignment : assignments) {
381  OpBuilder::InsertionGuard g(b);
382  for (auto [caseInfo, stateOp] :
383  llvm::zip(caseMux.getCases(), orderedStates)) {
384  auto assignmentInState = assignment.assignmentInState.find(stateOp);
385  if (assignmentInState == assignment.assignmentInState.end())
386  continue;
387  b.setInsertionPointToEnd(caseInfo.block);
388  if (auto v = std::get_if<Value>(&assignmentInState->second); v) {
389  b.create<sv::BPAssignOp>(machineOp.getLoc(), assignment.wire, *v);
390  } else {
391  // Nested case statement.
392  llvm::SmallVector<CaseMuxItem, 4> nestedAssignments;
393  nestedAssignments.push_back(
394  *std::get<std::shared_ptr<CaseMuxItem>>(assignmentInState->second));
395  buildStateCaseMux(nestedAssignments);
396  }
397  }
398  }
399 }
400 
401 LogicalResult MachineOpConverter::dispatch() {
402  b.setInsertionPoint(machineOp);
403  auto loc = machineOp.getLoc();
404  if (machineOp.getNumStates() < 2)
405  return machineOp.emitOpError() << "expected at least 2 states.";
406 
407  // Clone all referenced constants into the machine body - constants may have
408  // been moved to the machine parent due to the lack of IsolationFromAbove.
409  cloneConstantsIntoRegion(machineOp.getBody(), b);
410 
411  // 1) Get the port info of the machine and create a new HW module for it.
412  SmallVector<hw::PortInfo, 16> ports;
413  auto clkRstIdxs = getMachinePortInfo(ports, machineOp, b);
414  hwModuleOp = b.create<hw::HWModuleOp>(loc, machineOp.getSymNameAttr(), ports);
415  b.setInsertionPointToStart(hwModuleOp.getBodyBlock());
416 
417  // Replace all uses of the machine arguments with the arguments of the
418  // new created HW module.
419  for (auto args : llvm::zip(machineOp.getArguments(),
420  hwModuleOp.getBodyBlock()->getArguments())) {
421  auto machineArg = std::get<0>(args);
422  auto hwModuleArg = std::get<1>(args);
423  machineArg.replaceAllUsesWith(hwModuleArg);
424  }
425 
426  auto clock = hwModuleOp.getBodyBlock()->getArgument(clkRstIdxs.clockIdx);
427  auto reset = hwModuleOp.getBodyBlock()->getArgument(clkRstIdxs.resetIdx);
428 
429  // 2) Build state and variable registers.
430  encoding =
431  std::make_unique<StateEncoding>(b, typeScope, machineOp, hwModuleOp);
432  auto stateType = encoding->getStateType();
433 
434  auto nextStateWire =
435  b.create<sv::RegOp>(loc, stateType, b.getStringAttr("state_next"));
436  auto nextStateWireRead = b.create<sv::ReadInOutOp>(loc, nextStateWire);
437  stateReg = b.create<seq::CompRegOp>(
438  loc, stateType, nextStateWireRead, clock, "state_reg", reset,
439  /*reset value=*/encoding->encode(machineOp.getInitialStateOp()), nullptr);
440 
441  llvm::DenseMap<VariableOp, sv::RegOp> variableNextStateWires;
442  for (auto variableOp : machineOp.front().getOps<fsm::VariableOp>()) {
443  auto initValueAttr = variableOp.getInitValueAttr().dyn_cast<IntegerAttr>();
444  if (!initValueAttr)
445  return variableOp.emitOpError() << "expected an integer attribute "
446  "for the initial value.";
447  Type varType = variableOp.getType();
448  auto varLoc = variableOp.getLoc();
449  auto varNextState = b.create<sv::RegOp>(
450  varLoc, varType, b.getStringAttr(variableOp.getName() + "_next"));
451  auto varResetVal = b.create<hw::ConstantOp>(varLoc, initValueAttr);
452  auto variableReg = b.create<seq::CompRegOp>(
453  varLoc, varType, b.create<sv::ReadInOutOp>(varLoc, varNextState), clock,
454  b.getStringAttr(variableOp.getName() + "_reg"), reset, varResetVal,
455  nullptr);
456  variableToRegister[variableOp] = variableReg;
457  variableNextStateWires[variableOp] = varNextState;
458  // Postpone value replacement until all logic has been created.
459  // fsm::UpdateOp's require their target variables to refer to a
460  // fsm::VariableOp - if this is not the case, they'll throw an assert.
461  }
462 
463  // Move any operations at the machine-level scope, excluding state ops, which
464  // are handled separately.
465  if (failed(moveOps(&machineOp.front(), [](Operation *op) {
466  return isa<fsm::StateOp, fsm::VariableOp>(op);
467  })))
468  return failure();
469 
470  // 3) Convert states and record their next-state value assignments.
471  StateCaseMapping nextStateFromState;
472  StateConversionResults stateConvResults;
473  for (auto state : machineOp.getBody().getOps<StateOp>()) {
474  auto stateConvRes = convertState(state);
475  if (failed(stateConvRes))
476  return failure();
477 
478  stateConvResults[state] = *stateConvRes;
479  orderedStates.push_back(state);
480  nextStateFromState[state] = {stateConvRes->nextState};
481  }
482 
483  // 4/5) Create next-state assignments for each output.
484  llvm::SmallVector<CaseMuxItem, 4> outputCaseAssignments;
485  auto hwPortList = hwModuleOp.getPortList();
486  for (size_t portIndex = 0; portIndex < machineOp.getNumResults();
487  portIndex++) {
488  auto outputPort = hwPortList.atOutput(portIndex);
489  auto outputPortType = outputPort.type;
490  CaseMuxItem outputAssignment;
491  outputAssignment.wire = b.create<sv::RegOp>(
492  machineOp.getLoc(), outputPortType,
493  b.getStringAttr("output_" + std::to_string(portIndex)));
494  outputAssignment.select = stateReg;
495  for (auto &state : orderedStates)
496  outputAssignment.assignmentInState[state] = {
497  stateConvResults[state].outputs[portIndex]};
498 
499  outputCaseAssignments.push_back(outputAssignment);
500  }
501 
502  // Create next-state maps for the FSM variables.
503  llvm::DenseMap<VariableOp, CaseMuxItem> variableCaseMuxItems;
504  for (auto &[currentState, it] : stateToVariableUpdates) {
505  for (auto &[targetState, it2] : it) {
506  for (auto &[variableOp, targetValue] : it2) {
507  auto caseMuxItemIt = variableCaseMuxItems.find(variableOp);
508  if (caseMuxItemIt == variableCaseMuxItems.end()) {
509  // First time seeing this variable. Initialize the outer case
510  // statement. The outer case has a default assignment to the current
511  // value of the variable register.
512  variableCaseMuxItems[variableOp];
513  caseMuxItemIt = variableCaseMuxItems.find(variableOp);
514  assert(variableOp);
515  assert(variableNextStateWires.count(variableOp));
516  caseMuxItemIt->second.wire = variableNextStateWires[variableOp];
517  caseMuxItemIt->second.select = stateReg;
518  caseMuxItemIt->second.defaultValue =
519  variableToRegister[variableOp].getResult();
520  }
521 
522  if (!std::get_if<std::shared_ptr<CaseMuxItem>>(
523  &caseMuxItemIt->second.assignmentInState[currentState])) {
524  // Initialize the inner case statement. This is an inner case within
525  // the current state, switching on the next-state value.
526  CaseMuxItem innerCaseMuxItem;
527  innerCaseMuxItem.wire = caseMuxItemIt->second.wire;
528  innerCaseMuxItem.select = nextStateWireRead;
529  caseMuxItemIt->second.assignmentInState[currentState] = {
530  std::make_shared<CaseMuxItem>(innerCaseMuxItem)};
531  }
532 
533  // Append to the nested case mux for the variable, with a case select
534  // on the next-state signal.
535  // Append an assignment in the case that nextState == targetState.
536  auto &innerCaseMuxItem = std::get<std::shared_ptr<CaseMuxItem>>(
537  caseMuxItemIt->second.assignmentInState[currentState]);
538  innerCaseMuxItem->assignmentInState[targetState] = {targetValue};
539  }
540  }
541  }
542 
543  // Materialize the case mux.
544  llvm::SmallVector<CaseMuxItem, 4> nextStateCaseAssignments;
545  nextStateCaseAssignments.push_back(
546  CaseMuxItem{nextStateWire, stateReg, nextStateFromState});
547  for (auto &[_, caseMuxItem] : variableCaseMuxItems)
548  nextStateCaseAssignments.push_back(caseMuxItem);
549  nextStateCaseAssignments.append(outputCaseAssignments.begin(),
550  outputCaseAssignments.end());
551 
552  {
553  auto alwaysCombOp = b.create<sv::AlwaysCombOp>(loc);
554  OpBuilder::InsertionGuard g(b);
555  b.setInsertionPointToStart(alwaysCombOp.getBodyBlock());
556  buildStateCaseMux(nextStateCaseAssignments);
557  }
558 
559  // Replace variable values with their register counterparts.
560  for (auto &[variableOp, variableReg] : variableToRegister)
561  variableOp.getResult().replaceAllUsesWith(variableReg);
562 
563  // Assing output ports.
564  llvm::SmallVector<Value> outputPortAssignments;
565  for (auto outputAssignment : outputCaseAssignments)
566  outputPortAssignments.push_back(
567  b.create<sv::ReadInOutOp>(machineOp.getLoc(), outputAssignment.wire));
568 
569  // Delete the default created output op and replace it with the output
570  // muxes.
571  auto *oldOutputOp = hwModuleOp.getBodyBlock()->getTerminator();
572  b.create<hw::OutputOp>(loc, outputPortAssignments);
573  oldOutputOp->erase();
574 
575  // Erase the original machine op.
576  machineOp.erase();
577 
578  return success();
579 }
580 
582 MachineOpConverter::convertTransitions( // NOLINT(misc-no-recursion)
583  StateOp currentState, ArrayRef<TransitionOp> transitions) {
584  Value nextState;
585  if (transitions.empty()) {
586  // Base case
587  // State: transition to the current state.
588  nextState = encoding->encode(currentState);
589  } else {
590  // Recursive case - transition to a named state.
591  auto transition = cast<fsm::TransitionOp>(transitions.front());
592  nextState = encoding->encode(transition.getNextStateOp());
593 
594  // Action conversion
595  if (transition.hasAction()) {
596  // Move any ops from the action region to the general scope, excluding
597  // variable update ops.
598  auto actionMoveOpsRes =
599  moveOps(&transition.getAction().front(),
600  [](Operation *op) { return isa<fsm::UpdateOp>(op); });
601  if (failed(actionMoveOpsRes))
602  return failure();
603 
604  // Gather variable updates during the action.
605  DenseMap<fsm::VariableOp, Value> variableUpdates;
606  for (auto updateOp : transition.getAction().getOps<fsm::UpdateOp>()) {
607  VariableOp variableOp = updateOp.getVariableOp();
608  variableUpdates[variableOp] = updateOp.getValue();
609  }
610 
611  stateToVariableUpdates[currentState][transition.getNextStateOp()] =
612  variableUpdates;
613  }
614 
615  // Guard conversion
616  if (transition.hasGuard()) {
617  // Not always taken; recurse and mux between the targeted next state and
618  // the recursion result, selecting based on the provided guard.
619  auto guardOpRes = moveOps(&transition.getGuard().front());
620  if (failed(guardOpRes))
621  return failure();
622 
623  auto guardOp = cast<ReturnOp>(*guardOpRes);
624  assert(guardOp && "guard should be defined");
625  auto guard = guardOp.getOperand();
626  auto otherNextState =
627  convertTransitions(currentState, transitions.drop_front());
628  if (failed(otherNextState))
629  return failure();
630  comb::MuxOp nextStateMux = b.create<comb::MuxOp>(
631  transition.getLoc(), guard, nextState, *otherNextState, false);
632  nextState = nextStateMux;
633  }
634  }
635 
636  assert(nextState && "next state should be defined");
637  return nextState;
638 }
639 
641 MachineOpConverter::convertState(StateOp state) {
642  MachineOpConverter::StateConversionResult res;
643 
644  // 3.1) Convert the output region by moving the operations into the module
645  // scope and gathering the operands of the output op.
646  if (!state.getOutput().empty()) {
647  auto outputOpRes = moveOps(&state.getOutput().front());
648  if (failed(outputOpRes))
649  return failure();
650 
651  OutputOp outputOp = cast<fsm::OutputOp>(*outputOpRes);
652  res.outputs = outputOp.getOperands(); // 3.2
653  }
654 
655  auto transitions = llvm::SmallVector<TransitionOp>(
656  state.getTransitions().getOps<TransitionOp>());
657  // 3.3, 3.4) Convert the transitions and record the next-state value
658  // derived from the transitions being selected in a priority-encoded manner.
659  auto nextStateRes = convertTransitions(state, transitions);
660  if (failed(nextStateRes))
661  return failure();
662  res.nextState = *nextStateRes;
663  return res;
664 }
665 
666 struct FSMToSVPass : public ConvertFSMToSVBase<FSMToSVPass> {
667  void runOnOperation() override;
668 };
669 
670 void FSMToSVPass::runOnOperation() {
671  auto module = getOperation();
672  auto b = OpBuilder(module);
673  SmallVector<Operation *, 16> opToErase;
674 
675  // Create a typescope shared by all of the FSMs. This typescope will be
676  // emitted in a single separate file to avoid polluting each output file with
677  // typedefs.
678  StringAttr typeScopeFilename = b.getStringAttr("fsm_enum_typedefs.sv");
679  b.setInsertionPointToStart(module.getBody());
680  auto typeScope = b.create<hw::TypeScopeOp>(
681  module.getLoc(), b.getStringAttr("fsm_enum_typedecls"));
682  typeScope.getBodyRegion().push_back(new Block());
683  typeScope->setAttr(
684  "output_file",
685  hw::OutputFileAttr::get(typeScopeFilename,
686  /*excludeFromFileList*/ b.getBoolAttr(false),
687  /*includeReplicatedOps*/ b.getBoolAttr(false)));
688 
689  // Traverse all machines and convert.
690  for (auto machine : llvm::make_early_inc_range(module.getOps<MachineOp>())) {
691  MachineOpConverter converter(b, typeScope, machine);
692 
693  if (failed(converter.dispatch())) {
694  signalPassFailure();
695  return;
696  }
697  }
698 
699  // Traverse all machine instances and convert to hw instances.
700  llvm::SmallVector<HWInstanceOp> instances;
701  module.walk([&](HWInstanceOp instance) { instances.push_back(instance); });
702  for (auto instance : instances) {
703  auto fsmHWModule =
704  module.lookupSymbol<hw::HWModuleOp>(instance.getMachine());
705  assert(fsmHWModule &&
706  "FSM machine should have been converted to a hw.module");
707 
708  b.setInsertionPoint(instance);
709  llvm::SmallVector<Value, 4> operands;
710  llvm::transform(instance.getOperands(), std::back_inserter(operands),
711  [&](auto operand) { return operand; });
712  auto hwInstance = b.create<hw::InstanceOp>(
713  instance.getLoc(), fsmHWModule, b.getStringAttr(instance.getName()),
714  operands, nullptr);
715  instance.replaceAllUsesWith(hwInstance);
716  instance.erase();
717  }
718 
719  if (typeScope.getBodyBlock()->empty()) {
720  // If the typescope is empty (no FSMs were converted), erase it.
721  typeScope.erase();
722  } else {
723  // Else, add an include file to the top-level (will include typescope
724  // in all files).
725  b.setInsertionPointToStart(module.getBody());
726  b.create<sv::VerbatimOp>(
727  module.getLoc(), "`include \"" + typeScopeFilename.getValue() + "\"");
728  }
729 }
730 
731 } // end anonymous namespace
732 
733 std::unique_ptr<mlir::Pass> circt::createConvertFSMToSVPass() {
734  return std::make_unique<FSMToSVPass>();
735 }
assert(baseType &&"element must be base type")
static ClkRstIdxs getMachinePortInfo(SmallVectorImpl< hw::PortInfo > &ports, MachineOp machine, OpBuilder &b)
Definition: FSMToSV.cpp:34
static void cloneConstantsIntoRegion(Region &region, OpBuilder &builder)
Definition: FSMToSV.cpp:62
@ Input
Definition: HW.h:32
llvm::SmallVector< StringAttr > outputs
Builder builder
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
std::unique_ptr< mlir::Pass > createConvertFSMToSVPass()
Definition: FSMToSV.cpp:733
Definition: fsm.py:1