Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
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
24namespace circt {
25#define GEN_PASS_DEF_CONVERTFSMTOSV
26#include "circt/Conversion/Passes.h.inc"
27} // namespace circt
28
29using namespace mlir;
30using namespace circt;
31using namespace fsm;
32
33/// Get the port info of a FSM machine. Clock and reset port are also added.
34namespace {
35struct ClkRstIdxs {
36 size_t clockIdx;
37 size_t resetIdx;
38};
39} // namespace
40static 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");
49 clock.dir = hw::ModulePort::Direction::Input;
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");
58 reset.dir = hw::ModulePort::Direction::Input;
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.
68static 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
89namespace {
90
91class 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
96public:
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
111protected:
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.
122
123 // A mapping between an encoded value and its corresponding StateOp.
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
140StateEncoding::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 = hw::TypedeclOp::create(
156 b, 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 = hw::EnumConstantOp::create(
170 b, loc, fieldAttr.getType().getValue(), fieldAttr);
171 setEncoding(state, enumConstantOp,
172 /*wire=*/true);
173 }
174}
175
176// Get the encoded value for a state.
177Value 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.
183StateOp 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.
190std::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
198void 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 = sv::RegOp::create(
207 b, loc, stateType, b.getStringAttr("to_" + state.getName()),
208 hw::InnerSymAttr::get(state.getNameAttr()));
209 sv::AssignOp::create(b, loc, stateEncodingWire, v);
210 encodedValue = sv::ReadInOutOp::create(b, loc, stateEncodingWire);
211 } else
212 encodedValue = v;
213 stateToValue[state] = encodedValue;
214 valueToState[encodedValue] = state;
215 valueToSrcValue[encodedValue] = v;
216}
217
218class MachineOpConverter {
219public:
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
245private:
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
335FailureOr<Operation *>
336MachineOpConverter::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
356void 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 sv::BPAssignOp::create(b, assignment.wire.getLoc(), assignment.wire,
372 *assignment.defaultValue);
373 }
374
375 // Case assignments.
376 caseMux = sv::CaseOp::create(
377 b, 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 sv::BPAssignOp::create(b, 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
410LogicalResult 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 =
424 hw::HWModuleOp::create(b, loc, machineOp.getSymNameAttr(), ports);
425 hwModuleOp->setAttr(emit::getFragmentsAttrName(),
426 b.getArrayAttr({headerName}));
427 b.setInsertionPointToStart(hwModuleOp.getBodyBlock());
428
429 // Replace all uses of the machine arguments with the arguments of the
430 // new created HW module.
431 for (auto args : llvm::zip(machineOp.getArguments(),
432 hwModuleOp.getBodyBlock()->getArguments())) {
433 auto machineArg = std::get<0>(args);
434 auto hwModuleArg = std::get<1>(args);
435 machineArg.replaceAllUsesWith(hwModuleArg);
436 }
437
438 auto clock = hwModuleOp.getBodyBlock()->getArgument(clkRstIdxs.clockIdx);
439 auto reset = hwModuleOp.getBodyBlock()->getArgument(clkRstIdxs.resetIdx);
440
441 // 2) Build state and variable registers.
442 encoding =
443 std::make_unique<StateEncoding>(b, typeScope, machineOp, hwModuleOp);
444 auto stateType = encoding->getStateType();
445
446 auto nextStateWire =
447 sv::RegOp::create(b, loc, stateType, b.getStringAttr("state_next"));
448 auto nextStateWireRead = sv::ReadInOutOp::create(b, loc, nextStateWire);
449 stateReg = seq::CompRegOp::create(
450 b, loc, nextStateWireRead, clock, reset,
451 /*reset value=*/encoding->encode(machineOp.getInitialStateOp()),
452 "state_reg");
453
454 llvm::DenseMap<VariableOp, sv::RegOp> variableNextStateWires;
455 for (auto variableOp : machineOp.front().getOps<fsm::VariableOp>()) {
456 auto initValueAttr = dyn_cast<IntegerAttr>(variableOp.getInitValueAttr());
457 if (!initValueAttr)
458 return variableOp.emitOpError() << "expected an integer attribute "
459 "for the initial value.";
460 Type varType = variableOp.getType();
461 auto varLoc = variableOp.getLoc();
462 auto varNextState = sv::RegOp::create(
463 b, varLoc, varType, b.getStringAttr(variableOp.getName() + "_next"));
464 auto varResetVal = hw::ConstantOp::create(b, varLoc, initValueAttr);
465 auto variableReg = seq::CompRegOp::create(
466 b, varLoc, sv::ReadInOutOp::create(b, varLoc, varNextState), clock,
467 reset, varResetVal, b.getStringAttr(variableOp.getName() + "_reg"));
468 variableToRegister[variableOp] = variableReg;
469 variableNextStateWires[variableOp] = varNextState;
470 // Postpone value replacement until all logic has been created.
471 // fsm::UpdateOp's require their target variables to refer to a
472 // fsm::VariableOp - if this is not the case, they'll throw an assert.
473 }
474
475 // Move any operations at the machine-level scope, excluding state ops, which
476 // are handled separately.
477 if (failed(moveOps(&machineOp.front(), [](Operation *op) {
478 return isa<fsm::StateOp, fsm::VariableOp>(op);
479 })))
480 return failure();
481
482 // 3) Convert states and record their next-state value assignments.
483 StateCaseMapping nextStateFromState;
484 StateConversionResults stateConvResults;
485 for (auto state : machineOp.getBody().getOps<StateOp>()) {
486 auto stateConvRes = convertState(state);
487 if (failed(stateConvRes))
488 return failure();
489
490 stateConvResults[state] = *stateConvRes;
491 orderedStates.push_back(state);
492 nextStateFromState[state] = {stateConvRes->nextState};
493 }
494
495 // 4/5) Create next-state assignments for each output.
496 llvm::SmallVector<CaseMuxItem, 4> outputCaseAssignments;
497 auto hwPortList = hwModuleOp.getPortList();
498 size_t portIndex = 0;
499 for (auto &port : hwPortList) {
500 if (!port.isOutput())
501 continue;
502 auto outputPortType = port.type;
503 CaseMuxItem outputAssignment;
504 outputAssignment.wire = sv::RegOp::create(
505 b, machineOp.getLoc(), outputPortType,
506 b.getStringAttr("output_" + std::to_string(portIndex)));
507 outputAssignment.select = stateReg;
508 for (auto &state : orderedStates)
509 outputAssignment.assignmentInState[state] = {
510 stateConvResults[state].outputs[portIndex]};
511
512 outputCaseAssignments.push_back(outputAssignment);
513 ++portIndex;
514 }
515
516 // Create next-state maps for the FSM variables.
517 llvm::DenseMap<VariableOp, CaseMuxItem> variableCaseMuxItems;
518 for (auto &[currentState, it] : stateToVariableUpdates) {
519 for (auto &[targetState, it2] : it) {
520 for (auto &[variableOp, targetValue] : it2) {
521 auto caseMuxItemIt = variableCaseMuxItems.find(variableOp);
522 if (caseMuxItemIt == variableCaseMuxItems.end()) {
523 // First time seeing this variable. Initialize the outer case
524 // statement. The outer case has a default assignment to the current
525 // value of the variable register.
526 variableCaseMuxItems[variableOp];
527 caseMuxItemIt = variableCaseMuxItems.find(variableOp);
528 assert(variableOp);
529 assert(variableNextStateWires.count(variableOp));
530 caseMuxItemIt->second.wire = variableNextStateWires[variableOp];
531 caseMuxItemIt->second.select = stateReg;
532 caseMuxItemIt->second.defaultValue =
533 variableToRegister[variableOp].getResult();
534 }
535
536 if (!std::get_if<std::shared_ptr<CaseMuxItem>>(
537 &caseMuxItemIt->second.assignmentInState[currentState])) {
538 // Initialize the inner case statement. This is an inner case within
539 // the current state, switching on the next-state value.
540 CaseMuxItem innerCaseMuxItem;
541 innerCaseMuxItem.wire = caseMuxItemIt->second.wire;
542 innerCaseMuxItem.select = nextStateWireRead;
543 caseMuxItemIt->second.assignmentInState[currentState] = {
544 std::make_shared<CaseMuxItem>(innerCaseMuxItem)};
545 }
546
547 // Append to the nested case mux for the variable, with a case select
548 // on the next-state signal.
549 // Append an assignment in the case that nextState == targetState.
550 auto &innerCaseMuxItem = std::get<std::shared_ptr<CaseMuxItem>>(
551 caseMuxItemIt->second.assignmentInState[currentState]);
552 innerCaseMuxItem->assignmentInState[targetState] = {targetValue};
553 }
554 }
555 }
556
557 // Materialize the case mux.
558 llvm::SmallVector<CaseMuxItem, 4> nextStateCaseAssignments;
559 nextStateCaseAssignments.push_back(
560 CaseMuxItem{nextStateWire, stateReg, nextStateFromState});
561 for (auto &[_, caseMuxItem] : variableCaseMuxItems)
562 nextStateCaseAssignments.push_back(caseMuxItem);
563 nextStateCaseAssignments.append(outputCaseAssignments.begin(),
564 outputCaseAssignments.end());
565
566 {
567 auto alwaysCombOp = sv::AlwaysCombOp::create(b, loc);
568 OpBuilder::InsertionGuard g(b);
569 b.setInsertionPointToStart(alwaysCombOp.getBodyBlock());
570 buildStateCaseMux(nextStateCaseAssignments);
571 }
572
573 // Replace variable values with their register counterparts.
574 for (auto &[variableOp, variableReg] : variableToRegister)
575 variableOp.getResult().replaceAllUsesWith(variableReg);
576
577 // Assing output ports.
578 llvm::SmallVector<Value> outputPortAssignments;
579 for (auto outputAssignment : outputCaseAssignments)
580 outputPortAssignments.push_back(
581 sv::ReadInOutOp::create(b, machineOp.getLoc(), outputAssignment.wire));
582
583 // Delete the default created output op and replace it with the output
584 // muxes.
585 auto *oldOutputOp = hwModuleOp.getBodyBlock()->getTerminator();
586 hw::OutputOp::create(b, loc, outputPortAssignments);
587 oldOutputOp->erase();
588
589 // Erase the original machine op.
590 machineOp.erase();
591
592 return success();
593}
594
595FailureOr<Value>
596MachineOpConverter::convertTransitions( // NOLINT(misc-no-recursion)
597 StateOp currentState, ArrayRef<TransitionOp> transitions) {
598 Value nextState;
599 if (transitions.empty()) {
600 // Base case
601 // State: transition to the current state.
602 nextState = encoding->encode(currentState);
603 } else {
604 // Recursive case - transition to a named state.
605 auto transition = cast<fsm::TransitionOp>(transitions.front());
606 nextState = encoding->encode(transition.getNextStateOp());
607
608 // Action conversion
609 if (transition.hasAction()) {
610 // Move any ops from the action region to the general scope, excluding
611 // variable update ops.
612 auto actionMoveOpsRes =
613 moveOps(&transition.getAction().front(),
614 [](Operation *op) { return isa<fsm::UpdateOp>(op); });
615 if (failed(actionMoveOpsRes))
616 return failure();
617
618 // Gather variable updates during the action.
619 DenseMap<fsm::VariableOp, Value> variableUpdates;
620 for (auto updateOp : transition.getAction().getOps<fsm::UpdateOp>()) {
621 VariableOp variableOp = updateOp.getVariableOp();
622 variableUpdates[variableOp] = updateOp.getValue();
623 }
624
625 stateToVariableUpdates[currentState][transition.getNextStateOp()] =
626 variableUpdates;
627 }
628
629 // Guard conversion
630 if (transition.hasGuard()) {
631 // Not always taken; recurse and mux between the targeted next state and
632 // the recursion result, selecting based on the provided guard.
633 auto guardOpRes = moveOps(&transition.getGuard().front());
634 if (failed(guardOpRes))
635 return failure();
636
637 auto guardOp = cast<ReturnOp>(*guardOpRes);
638 assert(guardOp && "guard should be defined");
639 auto guard = guardOp.getOperand();
640 auto otherNextState =
641 convertTransitions(currentState, transitions.drop_front());
642 if (failed(otherNextState))
643 return failure();
644 comb::MuxOp nextStateMux = comb::MuxOp::create(
645 b, transition.getLoc(), guard, nextState, *otherNextState, false);
646 nextState = nextStateMux;
647 }
648 }
649
650 assert(nextState && "next state should be defined");
651 return nextState;
652}
653
654FailureOr<MachineOpConverter::StateConversionResult>
655MachineOpConverter::convertState(StateOp state) {
656 MachineOpConverter::StateConversionResult res;
657
658 // 3.1) Convert the output region by moving the operations into the module
659 // scope and gathering the operands of the output op.
660 if (!state.getOutput().empty()) {
661 auto outputOpRes = moveOps(&state.getOutput().front());
662 if (failed(outputOpRes))
663 return failure();
664
665 OutputOp outputOp = cast<fsm::OutputOp>(*outputOpRes);
666 res.outputs = outputOp.getOperands(); // 3.2
667 }
668
669 SmallVector<TransitionOp> transitions;
670 for (auto &op : state.getTransitions().getOps()) {
671 if (auto transOp = dyn_cast<TransitionOp>(op)) {
672 transitions.push_back(transOp);
673 } else {
674 // Clone operations which are inside `transitions` region but outside
675 // `guard` region.
676 auto opClone = b.clone(op);
677 for (auto [i, res] : llvm::enumerate(op.getResults()))
678 res.replaceAllUsesWith(opClone->getResult(i));
679 }
680 }
681 // 3.3, 3.4) Convert the transitions and record the next-state value
682 // derived from the transitions being selected in a priority-encoded manner.
683 auto nextStateRes = convertTransitions(state, transitions);
684 if (failed(nextStateRes))
685 return failure();
686 res.nextState = *nextStateRes;
687 return res;
688}
689
690struct FSMToSVPass : public circt::impl::ConvertFSMToSVBase<FSMToSVPass> {
691 void runOnOperation() override;
692};
693
694void FSMToSVPass::runOnOperation() {
695 auto module = getOperation();
696 auto loc = module.getLoc();
697 auto b = OpBuilder(module);
698
699 // Identify the machines to lower, bail out if none exist.
700 auto machineOps = llvm::to_vector(module.getOps<MachineOp>());
701 if (machineOps.empty()) {
702 markAllAnalysesPreserved();
703 return;
704 }
705
706 // Create a typescope shared by all of the FSMs. This typescope will be
707 // emitted in a single separate file to avoid polluting each output file with
708 // typedefs.
709 b.setInsertionPointToStart(module.getBody());
710 hw::TypeScopeOp typeScope =
711 hw::TypeScopeOp::create(b, loc, b.getStringAttr("fsm_enum_typedecls"));
712 typeScope.getBodyRegion().push_back(new Block());
713
714 auto file = emit::FileOp::create(b, loc, "fsm_enum_typedefs.sv", [&] {
715 emit::RefOp::create(b, loc,
716 FlatSymbolRefAttr::get(typeScope.getSymNameAttr()));
717 });
718 auto fragment = emit::FragmentOp::create(b, loc, "FSM_ENUM_TYPEDEFS", [&] {
719 sv::VerbatimOp::create(b, loc, "`include \"" + file.getFileName() + "\"");
720 });
721
722 auto headerName = FlatSymbolRefAttr::get(fragment.getSymNameAttr());
723
724 // Traverse all machines and convert.
725 for (auto machineOp : machineOps) {
726 MachineOpConverter converter(b, typeScope, machineOp, headerName);
727
728 if (failed(converter.dispatch())) {
729 signalPassFailure();
730 return;
731 }
732 }
733
734 // Traverse all machine instances and convert to hw instances.
735 llvm::SmallVector<HWInstanceOp> instances;
736 module.walk([&](HWInstanceOp instance) { instances.push_back(instance); });
737 for (auto instance : instances) {
738 auto fsmHWModule =
739 module.lookupSymbol<hw::HWModuleOp>(instance.getMachine());
740 assert(fsmHWModule &&
741 "FSM machine should have been converted to a hw.module");
742
743 b.setInsertionPoint(instance);
744 llvm::SmallVector<Value, 4> operands;
745 llvm::transform(instance.getOperands(), std::back_inserter(operands),
746 [&](auto operand) { return operand; });
747 auto hwInstance = hw::InstanceOp::create(
748 b, instance.getLoc(), fsmHWModule, b.getStringAttr(instance.getName()),
749 operands, nullptr);
750 instance.replaceAllUsesWith(hwInstance);
751 instance.erase();
752 }
753
754 assert(!typeScope.getBodyBlock()->empty() && "missing type decls");
755}
756
757} // end anonymous namespace
758
759std::unique_ptr<mlir::Pass> circt::createConvertFSMToSVPass() {
760 return std::make_unique<FSMToSVPass>();
761}
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
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static Block * getBodyBlock(FModuleLike mod)
create(data_type, value)
Definition hw.py:433
create(str sym_name)
Definition hw.py:581
create(str sym_name, Type type, str verilog_name=None)
Definition hw.py:571
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
create(dest, src)
Definition sv.py:98
create(value)
Definition sv.py:106
Definition sv.py:68
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createConvertFSMToSVPass()
Definition FSMToSV.cpp:759
Definition fsm.py:1
Definition sv.py:1
mlir::Type type
Definition HWTypes.h:31
mlir::StringAttr name
Definition HWTypes.h:30
This holds the name, type, direction of a module's ports.
size_t argNum
This is the argument index or the result index depending on the direction.