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