CIRCT 20.0.0git
Loading...
Searching...
No Matches
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 = 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.
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 = 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
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 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
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 = 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
594FailureOr<Value>
595MachineOpConverter::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
653FailureOr<MachineOpConverter::StateConversionResult>
654MachineOpConverter::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
679struct FSMToSVPass : public circt::impl::ConvertFSMToSVBase<FSMToSVPass> {
680 void runOnOperation() override;
681};
682
683void 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
748std::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
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(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:748
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.