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