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"
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 =
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, nextStateWireRead, clock, reset,
439  /*reset value=*/encoding->encode(machineOp.getInitialStateOp()),
440  "state_reg");
441 
442  llvm::DenseMap<VariableOp, sv::RegOp> variableNextStateWires;
443  for (auto variableOp : machineOp.front().getOps<fsm::VariableOp>()) {
444  auto initValueAttr = variableOp.getInitValueAttr().dyn_cast<IntegerAttr>();
445  if (!initValueAttr)
446  return variableOp.emitOpError() << "expected an integer attribute "
447  "for the initial value.";
448  Type varType = variableOp.getType();
449  auto varLoc = variableOp.getLoc();
450  auto varNextState = b.create<sv::RegOp>(
451  varLoc, varType, b.getStringAttr(variableOp.getName() + "_next"));
452  auto varResetVal = b.create<hw::ConstantOp>(varLoc, initValueAttr);
453  auto variableReg = b.create<seq::CompRegOp>(
454  varLoc, b.create<sv::ReadInOutOp>(varLoc, varNextState), clock, reset,
455  varResetVal, b.getStringAttr(variableOp.getName() + "_reg"));
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  size_t portIndex = 0;
487  for (auto &port : hwPortList) {
488  if (!port.isOutput())
489  continue;
490  auto outputPortType = port.type;
491  CaseMuxItem outputAssignment;
492  outputAssignment.wire = b.create<sv::RegOp>(
493  machineOp.getLoc(), outputPortType,
494  b.getStringAttr("output_" + std::to_string(portIndex)));
495  outputAssignment.select = stateReg;
496  for (auto &state : orderedStates)
497  outputAssignment.assignmentInState[state] = {
498  stateConvResults[state].outputs[portIndex]};
499 
500  outputCaseAssignments.push_back(outputAssignment);
501  ++portIndex;
502  }
503 
504  // Create next-state maps for the FSM variables.
505  llvm::DenseMap<VariableOp, CaseMuxItem> variableCaseMuxItems;
506  for (auto &[currentState, it] : stateToVariableUpdates) {
507  for (auto &[targetState, it2] : it) {
508  for (auto &[variableOp, targetValue] : it2) {
509  auto caseMuxItemIt = variableCaseMuxItems.find(variableOp);
510  if (caseMuxItemIt == variableCaseMuxItems.end()) {
511  // First time seeing this variable. Initialize the outer case
512  // statement. The outer case has a default assignment to the current
513  // value of the variable register.
514  variableCaseMuxItems[variableOp];
515  caseMuxItemIt = variableCaseMuxItems.find(variableOp);
516  assert(variableOp);
517  assert(variableNextStateWires.count(variableOp));
518  caseMuxItemIt->second.wire = variableNextStateWires[variableOp];
519  caseMuxItemIt->second.select = stateReg;
520  caseMuxItemIt->second.defaultValue =
521  variableToRegister[variableOp].getResult();
522  }
523 
524  if (!std::get_if<std::shared_ptr<CaseMuxItem>>(
525  &caseMuxItemIt->second.assignmentInState[currentState])) {
526  // Initialize the inner case statement. This is an inner case within
527  // the current state, switching on the next-state value.
528  CaseMuxItem innerCaseMuxItem;
529  innerCaseMuxItem.wire = caseMuxItemIt->second.wire;
530  innerCaseMuxItem.select = nextStateWireRead;
531  caseMuxItemIt->second.assignmentInState[currentState] = {
532  std::make_shared<CaseMuxItem>(innerCaseMuxItem)};
533  }
534 
535  // Append to the nested case mux for the variable, with a case select
536  // on the next-state signal.
537  // Append an assignment in the case that nextState == targetState.
538  auto &innerCaseMuxItem = std::get<std::shared_ptr<CaseMuxItem>>(
539  caseMuxItemIt->second.assignmentInState[currentState]);
540  innerCaseMuxItem->assignmentInState[targetState] = {targetValue};
541  }
542  }
543  }
544 
545  // Materialize the case mux.
546  llvm::SmallVector<CaseMuxItem, 4> nextStateCaseAssignments;
547  nextStateCaseAssignments.push_back(
548  CaseMuxItem{nextStateWire, stateReg, nextStateFromState});
549  for (auto &[_, caseMuxItem] : variableCaseMuxItems)
550  nextStateCaseAssignments.push_back(caseMuxItem);
551  nextStateCaseAssignments.append(outputCaseAssignments.begin(),
552  outputCaseAssignments.end());
553 
554  {
555  auto alwaysCombOp = b.create<sv::AlwaysCombOp>(loc);
556  OpBuilder::InsertionGuard g(b);
557  b.setInsertionPointToStart(alwaysCombOp.getBodyBlock());
558  buildStateCaseMux(nextStateCaseAssignments);
559  }
560 
561  // Replace variable values with their register counterparts.
562  for (auto &[variableOp, variableReg] : variableToRegister)
563  variableOp.getResult().replaceAllUsesWith(variableReg);
564 
565  // Assing output ports.
566  llvm::SmallVector<Value> outputPortAssignments;
567  for (auto outputAssignment : outputCaseAssignments)
568  outputPortAssignments.push_back(
569  b.create<sv::ReadInOutOp>(machineOp.getLoc(), outputAssignment.wire));
570 
571  // Delete the default created output op and replace it with the output
572  // muxes.
573  auto *oldOutputOp = hwModuleOp.getBodyBlock()->getTerminator();
574  b.create<hw::OutputOp>(loc, outputPortAssignments);
575  oldOutputOp->erase();
576 
577  // Erase the original machine op.
578  machineOp.erase();
579 
580  return success();
581 }
582 
584 MachineOpConverter::convertTransitions( // NOLINT(misc-no-recursion)
585  StateOp currentState, ArrayRef<TransitionOp> transitions) {
586  Value nextState;
587  if (transitions.empty()) {
588  // Base case
589  // State: transition to the current state.
590  nextState = encoding->encode(currentState);
591  } else {
592  // Recursive case - transition to a named state.
593  auto transition = cast<fsm::TransitionOp>(transitions.front());
594  nextState = encoding->encode(transition.getNextStateOp());
595 
596  // Action conversion
597  if (transition.hasAction()) {
598  // Move any ops from the action region to the general scope, excluding
599  // variable update ops.
600  auto actionMoveOpsRes =
601  moveOps(&transition.getAction().front(),
602  [](Operation *op) { return isa<fsm::UpdateOp>(op); });
603  if (failed(actionMoveOpsRes))
604  return failure();
605 
606  // Gather variable updates during the action.
607  DenseMap<fsm::VariableOp, Value> variableUpdates;
608  for (auto updateOp : transition.getAction().getOps<fsm::UpdateOp>()) {
609  VariableOp variableOp = updateOp.getVariableOp();
610  variableUpdates[variableOp] = updateOp.getValue();
611  }
612 
613  stateToVariableUpdates[currentState][transition.getNextStateOp()] =
614  variableUpdates;
615  }
616 
617  // Guard conversion
618  if (transition.hasGuard()) {
619  // Not always taken; recurse and mux between the targeted next state and
620  // the recursion result, selecting based on the provided guard.
621  auto guardOpRes = moveOps(&transition.getGuard().front());
622  if (failed(guardOpRes))
623  return failure();
624 
625  auto guardOp = cast<ReturnOp>(*guardOpRes);
626  assert(guardOp && "guard should be defined");
627  auto guard = guardOp.getOperand();
628  auto otherNextState =
629  convertTransitions(currentState, transitions.drop_front());
630  if (failed(otherNextState))
631  return failure();
632  comb::MuxOp nextStateMux = b.create<comb::MuxOp>(
633  transition.getLoc(), guard, nextState, *otherNextState, false);
634  nextState = nextStateMux;
635  }
636  }
637 
638  assert(nextState && "next state should be defined");
639  return nextState;
640 }
641 
643 MachineOpConverter::convertState(StateOp state) {
644  MachineOpConverter::StateConversionResult res;
645 
646  // 3.1) Convert the output region by moving the operations into the module
647  // scope and gathering the operands of the output op.
648  if (!state.getOutput().empty()) {
649  auto outputOpRes = moveOps(&state.getOutput().front());
650  if (failed(outputOpRes))
651  return failure();
652 
653  OutputOp outputOp = cast<fsm::OutputOp>(*outputOpRes);
654  res.outputs = outputOp.getOperands(); // 3.2
655  }
656 
657  auto transitions = llvm::SmallVector<TransitionOp>(
658  state.getTransitions().getOps<TransitionOp>());
659  // 3.3, 3.4) Convert the transitions and record the next-state value
660  // derived from the transitions being selected in a priority-encoded manner.
661  auto nextStateRes = convertTransitions(state, transitions);
662  if (failed(nextStateRes))
663  return failure();
664  res.nextState = *nextStateRes;
665  return res;
666 }
667 
668 struct FSMToSVPass : public ConvertFSMToSVBase<FSMToSVPass> {
669  void runOnOperation() override;
670 };
671 
672 void FSMToSVPass::runOnOperation() {
673  auto module = getOperation();
674  auto b = OpBuilder(module);
675  SmallVector<Operation *, 16> opToErase;
676 
677  // Create a typescope shared by all of the FSMs. This typescope will be
678  // emitted in a single separate file to avoid polluting each output file with
679  // typedefs.
680  StringAttr typeScopeFilename = b.getStringAttr("fsm_enum_typedefs.sv");
681  b.setInsertionPointToStart(module.getBody());
682  auto typeScope = b.create<hw::TypeScopeOp>(
683  module.getLoc(), b.getStringAttr("fsm_enum_typedecls"));
684  typeScope.getBodyRegion().push_back(new Block());
685  typeScope->setAttr(
686  "output_file",
687  hw::OutputFileAttr::get(typeScopeFilename,
688  /*excludeFromFileList*/ b.getBoolAttr(false),
689  /*includeReplicatedOps*/ b.getBoolAttr(false)));
690 
691  // Traverse all machines and convert.
692  for (auto machine : llvm::make_early_inc_range(module.getOps<MachineOp>())) {
693  MachineOpConverter converter(b, typeScope, machine);
694 
695  if (failed(converter.dispatch())) {
696  signalPassFailure();
697  return;
698  }
699  }
700 
701  // Traverse all machine instances and convert to hw instances.
702  llvm::SmallVector<HWInstanceOp> instances;
703  module.walk([&](HWInstanceOp instance) { instances.push_back(instance); });
704  for (auto instance : instances) {
705  auto fsmHWModule =
706  module.lookupSymbol<hw::HWModuleOp>(instance.getMachine());
707  assert(fsmHWModule &&
708  "FSM machine should have been converted to a hw.module");
709 
710  b.setInsertionPoint(instance);
711  llvm::SmallVector<Value, 4> operands;
712  llvm::transform(instance.getOperands(), std::back_inserter(operands),
713  [&](auto operand) { return operand; });
714  auto hwInstance = b.create<hw::InstanceOp>(
715  instance.getLoc(), fsmHWModule, b.getStringAttr(instance.getName()),
716  operands, nullptr);
717  instance.replaceAllUsesWith(hwInstance);
718  instance.erase();
719  }
720 
721  if (typeScope.getBodyBlock()->empty()) {
722  // If the typescope is empty (no FSMs were converted), erase it.
723  typeScope.erase();
724  } else {
725  // Else, add an include file to the top-level (will include typescope
726  // in all files).
727  b.setInsertionPointToStart(module.getBody());
728  b.create<sv::VerbatimOp>(
729  module.getLoc(), "`include \"" + typeScopeFilename.getValue() + "\"");
730  }
731 }
732 
733 } // end anonymous namespace
734 
735 std::unique_ptr<mlir::Pass> circt::createConvertFSMToSVPass() {
736  return std::make_unique<FSMToSVPass>();
737 }
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: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:53
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createConvertFSMToSVPass()
Definition: FSMToSV.cpp:735
Definition: fsm.py:1