CIRCT 24.0.0git
Loading...
Searching...
No Matches
FSMOps.cpp
Go to the documentation of this file.
1//===- FSMOps.cpp - Implementation of FSM dialect operations --------------===//
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 "mlir/Dialect/Arith/IR/Arith.h"
11#include "mlir/Dialect/Func/IR/FuncOps.h"
12#include "mlir/IR/BuiltinOps.h"
13#include "mlir/IR/DialectImplementation.h"
14#include "mlir/IR/PatternMatch.h"
15#include "mlir/Interfaces/FunctionImplementation.h"
16#include "llvm/Support/FormatVariadic.h"
17
18using namespace mlir;
19using namespace circt;
20using namespace fsm;
21
22//===----------------------------------------------------------------------===//
23// MachineOp
24//===----------------------------------------------------------------------===//
25
26void MachineOp::build(OpBuilder &builder, OperationState &state, StringRef name,
27 StringRef initialStateName, FunctionType type,
28 ArrayRef<NamedAttribute> attrs,
29 ArrayRef<DictionaryAttr> argAttrs) {
30 state.addAttribute(MachineOp::getSymNameAttrName(state.name),
31 builder.getStringAttr(name));
32 state.addAttribute(MachineOp::getFunctionTypeAttrName(state.name),
33 TypeAttr::get(type));
34 state.addAttribute("initialState",
35 StringAttr::get(state.getContext(), initialStateName));
36 state.attributes.append(attrs.begin(), attrs.end());
37 Region *region = state.addRegion();
38 Block *body = new Block();
39 region->push_back(body);
40 body->addArguments(
41 type.getInputs(),
42 SmallVector<Location, 4>(type.getNumInputs(), builder.getUnknownLoc()));
43
44 if (argAttrs.empty())
45 return;
46 assert(type.getNumInputs() == argAttrs.size());
47 call_interface_impl::addArgAndResultAttrs(
48 builder, state, argAttrs,
49 /*resultAttrs=*/{}, MachineOp::getArgAttrsAttrName(state.name),
50 MachineOp::getResAttrsAttrName(state.name));
51}
52
53/// Get the initial state of the machine.
54StateOp MachineOp::getInitialStateOp() {
55 return dyn_cast_or_null<StateOp>(lookupSymbol(getInitialState()));
56}
57
58size_t MachineOp::getNumStates() {
59 auto stateOps = getBody().getOps<StateOp>();
60 return std::distance(stateOps.begin(), stateOps.end());
61}
62
63StringAttr MachineOp::getArgName(size_t i) {
64 if (auto args = getArgNames())
65 return cast<StringAttr>((*args)[i]);
66
67 return StringAttr::get(getContext(), "in" + std::to_string(i));
68}
69
70StringAttr MachineOp::getResName(size_t i) {
71 if (auto resNameAttrs = getResNames())
72 return cast<StringAttr>((*resNameAttrs)[i]);
73
74 return StringAttr::get(getContext(), "out" + std::to_string(i));
75}
76
77/// Get the port information of the machine.
78void MachineOp::getHWPortInfo(SmallVectorImpl<hw::PortInfo> &ports) {
79 ports.clear();
80 auto machineType = getFunctionType();
81 for (unsigned i = 0, e = machineType.getNumInputs(); i < e; ++i) {
82 hw::PortInfo port;
83 port.name = getArgName(i);
84 if (!port.name)
85 port.name = StringAttr::get(getContext(), "in" + std::to_string(i));
87 port.type = machineType.getInput(i);
88 port.argNum = i;
89 ports.push_back(port);
90 }
91
92 for (unsigned i = 0, e = machineType.getNumResults(); i < e; ++i) {
93 hw::PortInfo port;
94 port.name = getResName(i);
95 if (!port.name)
96 port.name = StringAttr::get(getContext(), "out" + std::to_string(i));
98 port.type = machineType.getResult(i);
99 port.argNum = i;
100 ports.push_back(port);
101 }
102}
103
104ParseResult MachineOp::parse(OpAsmParser &parser, OperationState &result) {
105 auto buildFuncType =
106 [&](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
107 function_interface_impl::VariadicFlag,
108 std::string &) { return builder.getFunctionType(argTypes, results); };
109
110 return function_interface_impl::parseFunctionOp(
111 parser, result, /*allowVariadic=*/false,
112 MachineOp::getFunctionTypeAttrName(result.name), buildFuncType,
113 MachineOp::getArgAttrsAttrName(result.name),
114 MachineOp::getResAttrsAttrName(result.name));
115}
116
117void MachineOp::print(OpAsmPrinter &p) {
118 function_interface_impl::printFunctionOp(
119 p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(),
120 getArgAttrsAttrName(), getResAttrsAttrName());
121}
122
123static LogicalResult compareTypes(Location loc, TypeRange rangeA,
124 TypeRange rangeB) {
125 if (rangeA.size() != rangeB.size())
126 return emitError(loc) << "mismatch in number of types compared ("
127 << rangeA.size() << " != " << rangeB.size() << ")";
128
129 size_t index = 0;
130 for (auto zip : llvm::zip(rangeA, rangeB)) {
131 auto typeA = std::get<0>(zip);
132 auto typeB = std::get<1>(zip);
133 if (typeA != typeB)
134 return emitError(loc) << "type mismatch at index " << index << " ("
135 << typeA << " != " << typeB << ")";
136 ++index;
137 }
138
139 return success();
140}
141
142LogicalResult MachineOp::verify() {
143 // If this function is external there is nothing to do.
144 if (isExternal())
145 return success();
146
147 // Verify that the argument list of the function and the arg list of the entry
148 // block line up. The trait already verified that the number of arguments is
149 // the same between the signature and the block.
150 if (failed(compareTypes(getLoc(), getArgumentTypes(),
151 front().getArgumentTypes())))
152 return emitOpError(
153 "entry block argument types must match the machine input types");
154
155 // Verify that the machine only has one block terminated with OutputOp.
156 if (!llvm::hasSingleElement(*this))
157 return emitOpError("must only have a single block");
158
159 // Verify that the initial state exists
160 if (!getInitialStateOp())
161 return emitOpError("initial state '" + getInitialState() +
162 "' was not defined in the machine");
163
164 if (getArgNames() && getArgNames()->size() != getArgumentTypes().size())
165 return emitOpError() << "number of machine arguments ("
166 << getArgumentTypes().size()
167 << ") does "
168 "not match the provided number "
169 "of argument names ("
170 << getArgNames()->size() << ")";
171
172 if (getResNames() && getResNames()->size() != getResultTypes().size())
173 return emitOpError() << "number of machine results ("
174 << getResultTypes().size()
175 << ") does "
176 "not match the provided number "
177 "of result names ("
178 << getResNames()->size() << ")";
179
180 return success();
181}
182
183SmallVector<hw::PortInfo> MachineOp::getPortList() {
184 SmallVector<hw::PortInfo> ports;
185 auto fnType = getFunctionType();
186 auto addPorts = [&](TypeRange types, std::optional<ArrayAttr> names,
187 hw::ModulePort::Direction dir, StringRef prefix) {
188 for (auto [i, type] : llvm::enumerate(types)) {
189 StringAttr name = (names && i < names->size())
190 ? cast<StringAttr>((*names)[i])
191 : StringAttr::get(getContext(), prefix + Twine(i));
192 ports.push_back({{name, type, dir}, static_cast<size_t>(i), {}, {}});
193 }
194 };
195
196 addPorts(fnType.getInputs(), getArgNames(), hw::ModulePort::Direction::Input,
197 "input");
198 addPorts(fnType.getResults(), getResNames(),
199 hw::ModulePort::Direction::Output, "output");
200
201 return ports;
202}
203
204//===----------------------------------------------------------------------===//
205// InstanceOp
206//===----------------------------------------------------------------------===//
207
208/// Lookup the machine for the symbol. This returns null on invalid IR.
209MachineOp InstanceOp::getMachineOp() {
210 auto module = (*this)->getParentOfType<ModuleOp>();
211 return module.lookupSymbol<MachineOp>(getMachine());
212}
213
214LogicalResult InstanceOp::verify() {
215 auto m = getMachineOp();
216 if (!m)
217 return emitError("cannot find machine definition '") << getMachine() << "'";
218
219 return success();
220}
221
222void InstanceOp::getAsmResultNames(
223 function_ref<void(Value, StringRef)> setNameFn) {
224 setNameFn(getInstance(), getName());
225}
226
227//===----------------------------------------------------------------------===//
228// TriggerOp
229//===----------------------------------------------------------------------===//
230
231template <typename OpType>
232static LogicalResult verifyCallerTypes(OpType op) {
233 auto machine = op.getMachineOp();
234 if (!machine)
235 return op.emitError("cannot find machine definition");
236
237 // Check operand types first.
238 if (failed(compareTypes(op.getLoc(), machine.getArgumentTypes(),
239 op.getInputs().getTypes()))) {
240 auto diag =
241 op.emitOpError("operand types must match the machine input types");
242 diag.attachNote(machine->getLoc()) << "original machine declared here";
243 return failure();
244 }
245
246 // Check result types.
247 if (failed(compareTypes(op.getLoc(), machine.getResultTypes(),
248 op.getOutputs().getTypes()))) {
249 auto diag =
250 op.emitOpError("result types must match the machine output types");
251 diag.attachNote(machine->getLoc()) << "original machine declared here";
252 return failure();
253 }
254
255 return success();
256}
257
258/// Lookup the machine for the symbol. This returns null on invalid IR.
259MachineOp TriggerOp::getMachineOp() {
260 auto instanceOp = getInstance().getDefiningOp<InstanceOp>();
261 if (!instanceOp)
262 return nullptr;
263
264 return instanceOp.getMachineOp();
265}
266
267LogicalResult TriggerOp::verify() { return verifyCallerTypes(*this); }
268
269//===----------------------------------------------------------------------===//
270// HWInstanceOp
271//===----------------------------------------------------------------------===//
272
273// InstanceOpInterface interface
274
275/// Lookup the machine for the symbol. This returns null on invalid IR.
276MachineOp HWInstanceOp::getMachineOp() {
277 auto module = (*this)->getParentOfType<ModuleOp>();
278 return module.lookupSymbol<MachineOp>(getMachine());
279}
280
281LogicalResult HWInstanceOp::verify() { return verifyCallerTypes(*this); }
282
283SmallVector<hw::PortInfo> HWInstanceOp::getPortList() {
284 return getMachineOp().getPortList();
285}
286
287/// Module name is the same as the machine name.
288StringRef HWInstanceOp::getModuleName() { return getMachine(); }
289FlatSymbolRefAttr HWInstanceOp::getModuleNameAttr() { return getMachineAttr(); }
290
291mlir::StringAttr HWInstanceOp::getInstanceNameAttr() { return getNameAttr(); }
292
293llvm::StringRef HWInstanceOp::getInstanceName() { return getName(); }
294
295//===----------------------------------------------------------------------===//
296// StateOp
297//===----------------------------------------------------------------------===//
298
299void StateOp::build(OpBuilder &builder, OperationState &state,
300 StringRef stateName) {
301 OpBuilder::InsertionGuard guard(builder);
302 Region *output = state.addRegion();
303 output->push_back(new Block());
304 builder.setInsertionPointToEnd(&output->back());
305 fsm::OutputOp::create(builder, state.location);
306 Region *transitions = state.addRegion();
307 transitions->push_back(new Block());
308 state.addAttribute(StateOp::getSymNameAttrName(state.name),
309 builder.getStringAttr(stateName));
310}
311
312void StateOp::build(OpBuilder &builder, OperationState &state,
313 StringRef stateName, ValueRange outputs) {
314 OpBuilder::InsertionGuard guard(builder);
315 Region *output = state.addRegion();
316 output->push_back(new Block());
317 builder.setInsertionPointToEnd(&output->back());
318 fsm::OutputOp::create(builder, state.location, outputs);
319 Region *transitions = state.addRegion();
320 transitions->push_back(new Block());
321 state.addAttribute(StateOp::getSymNameAttrName(state.name),
322 builder.getStringAttr(stateName));
323}
324
325SetVector<StateOp> StateOp::getNextStates() {
326 SmallVector<StateOp> nextStates;
327 llvm::transform(
328 getTransitions().getOps<TransitionOp>(),
329 std::inserter(nextStates, nextStates.begin()),
330 [](TransitionOp transition) { return transition.getNextStateOp(); });
331 return SetVector<StateOp>(nextStates.begin(), nextStates.end());
332}
333
334LogicalResult StateOp::canonicalize(StateOp op, PatternRewriter &rewriter) {
335 bool hasAlwaysTakenTransition = false;
336 SmallVector<TransitionOp, 4> transitionsToErase;
337 // Remove all transitions after an "always-taken" transition.
338 for (auto transition : op.getTransitions().getOps<TransitionOp>()) {
339 if (!hasAlwaysTakenTransition)
340 hasAlwaysTakenTransition = transition.isAlwaysTaken();
341 else
342 transitionsToErase.push_back(transition);
343 }
344
345 for (auto transition : transitionsToErase)
346 rewriter.eraseOp(transition);
347
348 return failure(transitionsToErase.empty());
349}
350
351LogicalResult StateOp::verify() {
352 MachineOp parent = getOperation()->getParentOfType<MachineOp>();
353
354 if (parent.getNumResults() != 0 && (getOutput().empty()))
355 return emitOpError("state must have a non-empty output region when the "
356 "machine has results.");
357
358 if (!getOutput().empty()) {
359 // Ensure that the output block has a single OutputOp terminator.
360 Block *outputBlock = &getOutput().front();
361 if (outputBlock->empty() || !isa<fsm::OutputOp>(outputBlock->back()))
362 return emitOpError("output block must have a single OutputOp terminator");
363 }
364
365 return success();
366}
367
368Block *StateOp::ensureOutput(OpBuilder &builder) {
369 if (getOutput().empty()) {
370 OpBuilder::InsertionGuard g(builder);
371 auto *block = new Block();
372 getOutput().push_back(block);
373 builder.setInsertionPointToStart(block);
374 fsm::OutputOp::create(builder, getLoc());
375 }
376 return &getOutput().front();
377}
378
379//===----------------------------------------------------------------------===//
380// OutputOp
381//===----------------------------------------------------------------------===//
382
383LogicalResult OutputOp::verify() {
384 if ((*this)->getParentRegion() ==
385 &(*this)->getParentOfType<StateOp>().getTransitions()) {
386 if (getNumOperands() != 0)
387 emitOpError("transitions region must not output any value");
388 return success();
389 }
390
391 // Verify that the result list of the machine and the operand list of the
392 // OutputOp line up.
393 auto machine = (*this)->getParentOfType<MachineOp>();
394 if (failed(
395 compareTypes(getLoc(), machine.getResultTypes(), getOperandTypes())))
396 return emitOpError("operand types must match the machine output types");
397
398 return success();
399}
400
401//===----------------------------------------------------------------------===//
402// TransitionOp
403//===----------------------------------------------------------------------===//
404
405void TransitionOp::build(OpBuilder &builder, OperationState &state,
406 StateOp nextState) {
407 build(builder, state, nextState.getName());
408}
409
410void TransitionOp::build(OpBuilder &builder, OperationState &state,
411 StringRef nextState,
412 llvm::function_ref<void()> guardCtor,
413 llvm::function_ref<void()> actionCtor) {
414 state.addAttribute("nextState",
415 FlatSymbolRefAttr::get(builder.getStringAttr(nextState)));
416 OpBuilder::InsertionGuard guard(builder);
417
418 Region *guardRegion = state.addRegion(); // guard
419 if (guardCtor) {
420 builder.createBlock(guardRegion);
421 guardCtor();
422 }
423
424 Region *actionRegion = state.addRegion(); // action
425 if (actionCtor) {
426 builder.createBlock(actionRegion);
427 actionCtor();
428 }
429}
430
431Block *TransitionOp::ensureGuard(OpBuilder &builder) {
432 if (getGuard().empty()) {
433 OpBuilder::InsertionGuard g(builder);
434 auto *block = new Block();
435 getGuard().push_back(block);
436 builder.setInsertionPointToStart(block);
437 fsm::ReturnOp::create(builder, getLoc());
438 }
439 return &getGuard().front();
440}
441
442Block *TransitionOp::ensureAction(OpBuilder &builder) {
443 if (getAction().empty())
444 getAction().push_back(new Block());
445 return &getAction().front();
446}
447
448/// Lookup the next state for the symbol. This returns null on invalid IR.
449StateOp TransitionOp::getNextStateOp() {
450 auto machineOp = (*this)->getParentOfType<MachineOp>();
451 if (!machineOp)
452 return nullptr;
453
454 return machineOp.lookupSymbol<StateOp>(getNextState());
455}
456
457bool TransitionOp::isAlwaysTaken() {
458 if (!hasGuard())
459 return true;
460
461 auto guardReturn = getGuardReturn();
462 if (guardReturn.getNumOperands() == 0)
463 return true;
464
465 if (auto constantOp =
466 guardReturn.getOperand().getDefiningOp<mlir::arith::ConstantOp>())
467 return cast<BoolAttr>(constantOp.getValue()).getValue();
468
469 return false;
470}
471
472LogicalResult TransitionOp::canonicalize(TransitionOp op,
473 PatternRewriter &rewriter) {
474 if (op.hasGuard()) {
475 auto guardReturn = op.getGuardReturn();
476 if (guardReturn.getNumOperands() == 1)
477 if (auto constantOp = guardReturn.getOperand()
478 .getDefiningOp<mlir::arith::ConstantOp>()) {
479 // Simplify when the guard region returns a constant value.
480 if (cast<BoolAttr>(constantOp.getValue()).getValue()) {
481 // Replace the original return op with a new one without any operands
482 // if the constant is TRUE.
483 rewriter.setInsertionPoint(guardReturn);
484 fsm::ReturnOp::create(rewriter, guardReturn.getLoc());
485 rewriter.eraseOp(guardReturn);
486 } else {
487 // Erase the whole transition op if the constant is FALSE, because the
488 // transition will never be taken.
489 rewriter.eraseOp(op);
490 }
491 return success();
492 }
493 }
494
495 return failure();
496}
497
498LogicalResult TransitionOp::verify() {
499 if (!getNextStateOp())
500 return emitOpError("cannot find the definition of the next state `")
501 << getNextState() << "`";
502
503 // Verify the action region, if present.
504 if (hasGuard()) {
505 if (getGuard().front().empty() ||
506 !isa_and_nonnull<fsm::ReturnOp>(&getGuard().front().back()))
507 return emitOpError("guard region must terminate with a ReturnOp");
508 }
509
510 // Verify the transition is located in the correct region.
511 if ((*this)->getParentRegion() != &getCurrentState().getTransitions())
512 return emitOpError("must only be located in the transitions region");
513
514 return success();
515}
516
517//===----------------------------------------------------------------------===//
518// VariableOp
519//===----------------------------------------------------------------------===//
520
521void VariableOp::getAsmResultNames(
522 function_ref<void(Value, StringRef)> setNameFn) {
523 setNameFn(getResult(), getName());
524}
525
526//===----------------------------------------------------------------------===//
527// ReturnOp
528//===----------------------------------------------------------------------===//
529
530void ReturnOp::setOperand(Value value) {
531 if (getOperand())
532 getOperation()->setOperand(0, value);
533 else
534 getOperation()->insertOperands(0, {value});
535}
536
537//===----------------------------------------------------------------------===//
538// UpdateOp
539//===----------------------------------------------------------------------===//
540
541/// Get the targeted variable operation. This returns null on invalid IR.
542VariableOp UpdateOp::getVariableOp() {
543 return getVariable().getDefiningOp<VariableOp>();
544}
545
546LogicalResult UpdateOp::canonicalize(UpdateOp op, PatternRewriter &rewriter) {
547 // Remove no-op updates that update a variable to itself
548 if (op.getVariable() == op.getValue()) {
549 rewriter.eraseOp(op);
550 return success();
551 }
552 return failure();
553}
554
555LogicalResult UpdateOp::verify() {
556 if (!getVariable())
557 return emitOpError("destination is not a variable operation");
558
559 if (!(*this)->getParentOfType<TransitionOp>().getAction().isAncestor(
560 (*this)->getParentRegion()))
561 return emitOpError("must only be located in the action region");
562
563 auto transition = (*this)->getParentOfType<TransitionOp>();
564 for (auto otherUpdateOp : transition.getAction().getOps<UpdateOp>()) {
565 if (otherUpdateOp == *this)
566 continue;
567 if (otherUpdateOp.getVariable() == getVariable())
568 return otherUpdateOp.emitOpError(
569 "multiple updates to the same variable within a single action region "
570 "is disallowed");
571 }
572
573 return success();
574}
575
576//===----------------------------------------------------------------------===//
577// TableGen generated logic
578//===----------------------------------------------------------------------===//
579
580// Provide the autogenerated implementation guts for the Op classes.
581#define GET_OP_CLASSES
582#include "circt/Dialect/FSM/FSM.cpp.inc"
583#undef GET_OP_CLASSES
584
585#include "circt/Dialect/FSM/FSMDialect.cpp.inc"
assert(baseType &&"element must be base type")
static LogicalResult verifyCallerTypes(OpType op)
Definition FSMOps.cpp:232
static LogicalResult compareTypes(Location loc, TypeRange rangeA, TypeRange rangeB)
Definition FSMOps.cpp:123
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:193
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
NamedAttrList attrs
static StringAttr getResName(Operation *op, size_t idx)
static StringAttr getArgName(Operation *op, size_t idx)
static InstancePath empty
type(self)
Definition fsm.py:58
create(*operands)
Definition fsm.py:160
output(self)
Definition fsm.py:148
transitions(self)
Definition fsm.py:152
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
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.
Definition fsm.py:1
mlir::Type type
Definition HWTypes.h:34
mlir::StringAttr name
Definition HWTypes.h:33
size_t argNum
This is the argument index or the result index depending on the direction.