CIRCT 23.0.0git
Loading...
Searching...
No Matches
FSMToSMT.cpp
Go to the documentation of this file.
1//===- FSMToSMT.cpp -----------------------------------------------===//
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//===----------------------------------------------------------------------===//
13#include "mlir/Dialect/SMT/IR/SMTOps.h"
14#include "mlir/IR/BuiltinTypes.h"
15#include "mlir/IR/IRMapping.h"
16#include "mlir/Pass/Pass.h"
17
18namespace circt {
19#define GEN_PASS_DEF_CONVERTFSMTOSMT
20#include "circt/Conversion/Passes.h.inc"
21} // namespace circt
22
23using namespace mlir;
24using namespace circt;
25using namespace fsm;
26
27//===----------------------------------------------------------------------===//
28// Convert FSM to SMT pass
29//===----------------------------------------------------------------------===//
30
31namespace {
32
33struct LoweringConfig {
34 // If true, include a `time` parameter in the relation, to be incremented at
35 // every transition.
36 bool withTime = false;
37 // Width of `time` parameter (if present)
38 unsigned timeWidth = 8;
39};
40
41class MachineOpConverter {
42public:
43 MachineOpConverter(OpBuilder &builder, MachineOp machineOp,
44 const LoweringConfig &cfg)
45 : machineOp(machineOp), b(builder), cfg(cfg) {}
46 LogicalResult dispatch();
47
48private:
49 struct PendingAssertion {
50 int stateId;
51 Region *outputRegion;
52 };
53
54 struct Transition {
55 int from;
56 int to;
57 std::optional<Region *> guard;
58 std::optional<Region *> action;
59 std::optional<Region *> output;
60 };
61
62 // Store all the functions `F_state(outs, vars, [time]) -> Bool` describing
63 // the activation of each state
64 SmallVector<std::pair<int, Value>> stateFunctions;
65
66 Transition getTransitionRegions(fsm::TransitionOp t, int from,
67 SmallVector<StringRef> &states,
68 Location &loc) {
69 StringRef nextState = t.getNextState();
70 Transition tr = {from, insertStates(states, nextState), std::nullopt,
71 std::nullopt, std::nullopt};
72 if (t.hasGuard())
73 tr.guard = &t.getGuard();
74 if (t.hasAction())
75 tr.action = &t.getAction();
76 return tr;
77 }
78
79 static int insertStates(SmallVector<StringRef> &states, llvm::StringRef st) {
80 for (auto [id, s] : llvm::enumerate(states))
81 if (s == st)
82 return id;
83 states.push_back(st); // materialize once, stored in vector
84 return states.size() - 1;
85 }
86
87 Region *
88 getOutputRegion(const SmallVector<std::pair<Region *, int>> &outputOfStateId,
89 int stateId) {
90 for (auto oid : outputOfStateId)
91 if (stateId == oid.second)
92 return oid.first;
93 llvm_unreachable("State could not be found.");
94 }
95
96 SmallVector<std::pair<mlir::Value, mlir::Value>>
97 mapSmtToFsm(Location loc, OpBuilder b, SmallVector<Value> smtValues,
98 int numArgs, int numOut, SmallVector<Value> fsmArgs,
99 SmallVector<Value> fsmVars) {
100 // map each SMT-quantified variable to a corresponding FSM variable or
101 // argument
102 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast;
103 for (auto [idx, fq] : llvm::enumerate(smtValues)) {
104 if (int(idx) < numArgs) { // arguments
105 auto convCast = UnrealizedConversionCastOp::create(
106 b, loc, fsmArgs[idx].getType(), fq);
107 fsmToCast.push_back({fsmArgs[idx], convCast->getResult(0)});
108 } else if (numArgs + numOut <= int(idx)) { // variables
109 if (cfg.withTime && idx == smtValues.size() - 1)
110 break;
111 auto convCast = UnrealizedConversionCastOp::create(
112 b, loc, fsmVars[idx - numArgs - numOut].getType(), fq);
113 fsmToCast.push_back(
114 {fsmVars[idx - numArgs - numOut], convCast->getResult(0)});
115 }
116 }
117 return fsmToCast;
118 }
119
120 IRMapping
121 createIRMapping(const SmallVector<std::pair<Value, Value>> &fsmToCast,
122 const IRMapping &constMapper) {
123 IRMapping mapping;
124 for (auto couple : fsmToCast)
125 mapping.map(couple.first, couple.second);
126 for (auto &pair : constMapper.getValueMap())
127 mapping.map(pair.first, pair.second);
128
129 return mapping;
130 }
131
132 Value bv1toSmtBool(OpBuilder &b, Location loc, Value i1Value) {
133 auto castVal = UnrealizedConversionCastOp::create(
134 b, loc, b.getType<smt::BitVectorType>(1), i1Value);
135 return smt::EqOp::create(b, loc, castVal->getResult(0),
136 smt::BVConstantOp::create(b, loc, 1, 1));
137 }
138
139 Value getStateFunction(int stateId) {
140 for (auto sf : stateFunctions)
141 if (sf.first == stateId)
142 return sf.second;
143 llvm_unreachable("State function could not be found.");
144 }
145
146 MachineOp machineOp;
147 OpBuilder &b;
148 LoweringConfig cfg;
149};
150
151LogicalResult MachineOpConverter::dispatch() {
152 b.setInsertionPoint(machineOp);
153 auto loc = machineOp.getLoc();
154 auto machineArgs = machineOp.getArguments();
155
156 mlir::SmallVector<mlir::Value> fsmVars;
157 mlir::SmallVector<mlir::Value> fsmArgs;
158 mlir::SmallVector<mlir::Type> quantifiedTypes;
159
160 TypeRange typeRange;
161 ValueRange valueRange;
162
163 // Do not allow any operations other than constants outside of FSM regions
164 for (auto &op : machineOp.front().getOperations())
165 if (!isa<fsm::FSMDialect>(op.getDialect()) && !isa<hw::ConstantOp>(op))
166 return op.emitError(
167 "Only fsm operations and hw.constants are allowed in the "
168 "top level of the fsm.machine op.");
169
170 // Do not allow any operations other than constants, comb, and FSM operations
171 // inside FSM output, guard and action regions
172 for (auto stateOp : machineOp.front().getOps<fsm::StateOp>()) {
173 if (!stateOp.getOutput().empty())
174 for (auto &op : stateOp.getOutput().front().getOperations())
175 if (!isa<fsm::FSMDialect, comb::CombDialect, hw::HWDialect>(
176 op.getDialect()) &&
177 !isa<verif::AssertOp>(op))
178 return op.emitError(
179 "Only fsm, comb, hw, and verif.assert operations are handled in "
180 "the output region of a state.");
181 if (!stateOp.getTransitions().empty())
182 for (auto t :
183 stateOp.getTransitions().front().getOps<fsm::TransitionOp>()) {
184 if (t.hasGuard())
185 for (auto &op : t.getGuard().front().getOperations())
186 if (!isa<fsm::FSMDialect, comb::CombDialect, hw::HWDialect>(
187 op.getDialect()) &&
188 !isa<verif::AssertOp>(op))
189 return op.emitError("Only fsm, comb, hw, and verif.assert "
190 "operations are handled in the guard "
191 "region of a transition.");
192 if (t.hasAction())
193 for (auto &op : t.getAction().front().getOperations())
194 if (!isa<fsm::FSMDialect, comb::CombDialect, hw::HWDialect>(
195 op.getDialect()) &&
196 !isa<verif::AssertOp>(op))
197 return op.emitError("Only fsm, comb, hw, and verif.assert "
198 "operations are handled in the action "
199 "region of a transition.");
200 }
201 }
202
203 auto solver = smt::SolverOp::create(b, loc, typeRange, valueRange);
204 solver.getBodyRegion().emplaceBlock();
205 b.setInsertionPointToStart(solver.getBody());
206
207 // Collect arguments and their types
208 for (auto a : machineArgs) {
209 fsmArgs.push_back(a);
210 if (!isa<IntegerType>(a.getType()))
211 return solver.emitError("Only integer arguments are supported in FSMs.");
212 quantifiedTypes.push_back(
213 b.getType<smt::BitVectorType>(a.getType().getIntOrFloatBitWidth()));
214 }
215 size_t numArgs = fsmArgs.size();
216
217 // Collect output types
218 if (!machineOp.getResultTypes().empty()) {
219 for (auto t : machineOp.getResultTypes()) {
220 if (!isa<IntegerType>(t))
221 return solver.emitError("Only integer outputs are supported in FSMs.");
222 quantifiedTypes.push_back(
223 b.getType<smt::BitVectorType>(t.getIntOrFloatBitWidth()));
224 }
225 }
226
227 size_t numOut = quantifiedTypes.size() - numArgs;
228
229 // Collect FSM variables, their types, and initial values
230 SmallVector<llvm::APInt> varInitValues;
231 for (auto v : machineOp.front().getOps<fsm::VariableOp>()) {
232 if (!isa<IntegerType>(v.getType()))
233 return v.emitError("Only integer variables are supported in FSMs.");
234 auto intAttr = dyn_cast<IntegerAttr>(v.getInitValueAttr());
235 varInitValues.push_back(intAttr.getValue());
236 quantifiedTypes.push_back(
237 b.getType<smt::BitVectorType>(v.getType().getIntOrFloatBitWidth()));
238 fsmVars.push_back(v.getResult());
239 }
240
241 // Map constant operations to their clones in the new solver region
242 IRMapping constMapper;
243 for (auto constOp : machineOp.front().getOps<hw::ConstantOp>())
244 b.clone(*constOp, constMapper);
245
246 // Add a time variable if flag is enabled
247 if (cfg.withTime)
248 quantifiedTypes.push_back(b.getType<smt::BitVectorType>(cfg.timeWidth));
249
250 size_t numVars = varInitValues.size();
251
252 // Store all the transitions state0 -> state1 in the FSM
253 SmallVector<MachineOpConverter::Transition> transitions;
254 // Store the name of each state
255 SmallVector<StringRef> states;
256 // Store the output region of each state
257 SmallVector<std::pair<Region *, int>> outputOfStateId;
258
259 // Get FSM initial state and store it in the states vector
260 StringRef initialState = machineOp.getInitialState();
261 insertStates(states, initialState);
262
263 // Only outputs and variables belong in the state function's domain, since the
264 // arguments are universally quantified.
265 SmallVector<Type> stateFunDomain;
266 for (auto i = numArgs; i < quantifiedTypes.size(); i++)
267 stateFunDomain.push_back(quantifiedTypes[i]);
268
269 // For each state, declare one SMT function: `F_state(outs, vars, [time]) ->
270 // Bool`, returning `true` when `state` is activated.
271 for (auto stateOp : machineOp.front().getOps<fsm::StateOp>()) {
272 int idx = insertStates(states, stateOp.getName());
273 mlir::StringAttr funName =
274 b.getStringAttr(("F_" + stateOp.getName().str()));
275 auto rangeTy = b.getType<smt::BoolType>();
276 auto funTy = b.getType<smt::SMTFuncType>(stateFunDomain, rangeTy);
277 auto acFun = smt::DeclareFunOp::create(b, loc, funTy, funName);
278 stateFunctions.push_back({idx, acFun});
279 outputOfStateId.push_back({&stateOp.getOutput(), idx});
280 }
281
282 // For each transition `state0 && guard01 -> state1`, construct an
283 // implication `F_state_0(outs, vars, [time]) && guard01(outs, vars) ->
284 // F_state_1(outs, vars, [time])`, simulating the activation of a transition
285 // and of the state it reaches.
286 for (auto stateOp : machineOp.front().getOps<fsm::StateOp>()) {
287 if (stateOp.getTransitions().empty())
288 continue;
289 StringRef stateName = stateOp.getName();
290 auto fromState = insertStates(states, stateName);
291 for (auto tr :
292 stateOp.getTransitions().front().getOps<fsm::TransitionOp>()) {
293 auto t = getTransitionRegions(tr, fromState, states, loc);
294 if (numOut > 0)
295 t.output = getOutputRegion(outputOfStateId, t.to);
296 else
297 t.output = std::nullopt;
298 transitions.push_back(t);
299 }
300 }
301
302 SmallVector<PendingAssertion> assertions;
303
304 // The initial assertion sets variables to their initial values and sets `time
305 // == 0` (if present), computing the output values of the initial state
306 // accordingly.
307 auto initialAssertion = smt::ForallOp::create(
308 b, loc, quantifiedTypes,
309 [&](OpBuilder &b, Location loc,
310 const SmallVector<Value> &forallQuantified) -> Value {
311 // map each SMT-quantified variable to a corresponding FSM variable or
312 // argument
313 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast =
314 mapSmtToFsm(loc, b, forallQuantified, numArgs, numOut, fsmArgs,
315 fsmVars);
316 auto *initOutputReg = getOutputRegion(outputOfStateId, 0);
317 SmallVector<Value> castOutValues;
318
319 // replace variables with initial values to create the IR mapping
320 for (auto [id, couple] : llvm::enumerate(fsmToCast)) {
321 if (numArgs + numOut <= id && id < numArgs + numOut + numVars) {
322 fsmToCast[id] = {
323 couple.first,
324 hw::ConstantOp::create(b, loc, varInitValues[id - numArgs])};
325 }
326 }
327
328 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
329
330 SmallVector<mlir::Value> combOutputValues;
331
332 if (!initOutputReg->empty()) {
333 // Clone all the operations in the output region except `OutputOp` and
334 // `AssertOp`, replacing FSM variables and arguments with the results
335 // of unrealized conversion casts and replacing constants with their
336 // new clones
337 for (auto &op : initOutputReg->front()) {
338 if (isa<verif::AssertOp>(op)) {
339 // Store the assertion operations, with a pointer to the region
340 assertions.push_back({0, initOutputReg});
341 } else {
342 auto *newOp = b.clone(op, mapping);
343
344 // Retrieve all the operands of the output operation
345 if (isa<fsm::OutputOp>(newOp)) {
346 for (auto out : newOp->getOperands())
347 combOutputValues.push_back(out);
348 newOp->erase();
349 }
350 }
351
352 // Cast the (builtin integer) results obtained from the output
353 // region to SMT types, to pass them as arguments of the state
354 // function
355
356 for (auto [idx, out] : llvm::enumerate(combOutputValues)) {
357 auto convCast = UnrealizedConversionCastOp::create(
358 b, loc, forallQuantified[numArgs + idx].getType(), out);
359 castOutValues.push_back(convCast->getResult(0));
360 }
361 }
362 }
363
364 // Assign variables and output values their initial value
365 SmallVector<mlir::Value> initialCondition;
366 for (auto [idx, q] : llvm::enumerate(forallQuantified)) {
367 if (cfg.withTime) {
368 if (numArgs + numOut <= idx &&
369 idx < forallQuantified.size() -
370 1) { // FSM variables are assigned their initial value
371 // as an SMT constant
372 initialCondition.push_back(smt::BVConstantOp::create(
373 b, loc, varInitValues[idx - numArgs - numOut]));
374 } else if (numArgs <= idx &&
375 idx < forallQuantified.size() -
376 1) { // FSM outputs are assigned the value
377 // computed in the output region
378 initialCondition.push_back(castOutValues[idx - numArgs]);
379 } else if (idx == forallQuantified.size() -
380 1) // If present, time is set to 0
381 initialCondition.push_back(
382 smt::BVConstantOp::create(b, loc, 0, cfg.timeWidth));
383 } else {
384 if (numArgs + numOut <= idx) { // FSM variables are assigned their
385 // initial value as an SMT constant
386 initialCondition.push_back(smt::BVConstantOp::create(
387 b, loc, varInitValues[idx - numArgs - numOut]));
388 } else if (numArgs <= idx) { // FSM outputs are assigned the value
389 // computed in the output region
390 initialCondition.push_back(castOutValues[idx - numArgs]);
391 }
392 }
393 }
394
395 return smt::ApplyFuncOp::create(b, loc, getStateFunction(0),
396 initialCondition);
397 });
398
399 // Assert initial condition
400 smt::AssertOp::create(b, loc, initialAssertion);
401
402 // Double quantified arguments' types to consider their value at both the
403 // initial and the arriving state
404 SmallVector<Type> transitionQuantified;
405 for (auto [id, ty] : llvm::enumerate(quantifiedTypes)) {
406 transitionQuantified.push_back(ty);
407 if (id < numArgs)
408 transitionQuantified.push_back(ty);
409 }
410
411 // For each transition
412 // `F_state0(vars, outs, [time]) && guard (args, vars) ->
413 // F_state1(updatedVars, updatedOuts, [time + 1])`, build a corresponding
414 // implication.
415
416 for (auto [transId, transition] : llvm::enumerate(transitions)) {
417 // Extract values from structured binding to avoid C++20 extension warnings
418 // when capturing in lambdas
419 const auto &currentTransition = transition;
420 const auto &currentTransId = transId;
421
422 // return a SmallVector of updated SMT values as arguments of the arriving
423 // state function
424 auto action =
425 [&](SmallVector<Value> &actionArgsOutsVarsVals) -> SmallVector<Value> {
426 // Map each SMT-quantified variable to a corresponding FSM variable or
427 // argument
428 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast = mapSmtToFsm(
429 loc, b, actionArgsOutsVarsVals, numArgs, numOut, fsmArgs, fsmVars);
430
431 // Retrieve action region
432 SmallVector<Value> castUpdatedVars;
433
434 // Initialize to the previous value
435 for (size_t i = 0; i < numVars; i++)
436 castUpdatedVars.push_back(actionArgsOutsVarsVals[numArgs + numOut + i]);
437
438 if (currentTransition.action.has_value()) {
439
440 auto *actionReg = currentTransition.action.value();
441
442 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
443
444 SmallVector<std::pair<mlir::Value, mlir::Value>> combActionValues;
445
446 // Clone all the operations in the action region except for `UpdateOp`
447 // and `AssertOp`, replacing FSM variables and arguments with the
448 // results of unrealized conversion casts and replacing constants with
449 // their new clone
450 for (auto &op : actionReg->front()) {
451 if (isa<verif::AssertOp>(op)) {
452 // Ignore assertions in action regions
453 op.emitWarning("Assertions in action regions are ignored.");
454 } else {
455 auto *newOp = b.clone(op, mapping);
456 // Retrieve the updated values and their operands
457 if (isa<fsm::UpdateOp>(newOp)) {
458 auto varToUpdate = newOp->getOperand(0);
459 auto updatedValue = newOp->getOperand(1);
460 for (auto [id, var] : llvm::enumerate(fsmToCast)) {
461 if (var.second == varToUpdate) {
462 // Cast the updated value to the appropriate SMT type
463 auto convCast = UnrealizedConversionCastOp::create(
464 b, loc, actionArgsOutsVarsVals[numOut + id].getType(),
465 updatedValue);
466
467 castUpdatedVars[id - numArgs] = convCast->getResult(0);
468 }
469 }
470 newOp->erase();
471 }
472 }
473 }
474 }
475 return castUpdatedVars;
476 };
477
478 // Return an SMT value for the transition's guard
479 auto guard = [&](SmallVector<Value> &actionArgsOutsVarsVals) -> Value {
480 // Map each SMT-quantified variable to a corresponding FSM variable or
481 // argument
482 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast = mapSmtToFsm(
483 loc, b, actionArgsOutsVarsVals, numArgs, numOut, fsmArgs, fsmVars);
484
485 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
486
487 Value guardVal;
488
489 // Clone all the operations in the guard region except for `ReturnOp` and
490 // `AssertOp,
491 if (currentTransition.guard.has_value()) {
492 for (auto &op : currentTransition.guard.value()->front()) {
493 if (isa<verif::AssertOp>(op)) {
494 // Ignore assertions in guard regions
495 op.emitWarning("Assertions in guard regions are ignored.");
496 } else {
497 auto *newOp = b.clone(op, mapping);
498 // Retrieve the guard value
499 if (isa<fsm::ReturnOp>(newOp)) {
500 // Cast the guard value to an SMT boolean type
501 auto castVal = mlir::UnrealizedConversionCastOp::create(
502 b, loc, b.getType<smt::BitVectorType>(1),
503 newOp->getOperand(0));
504
505 guardVal = bv1toSmtBool(b, loc, castVal.getResult(0));
506 newOp->erase();
507 }
508 }
509 }
510 } else {
511 guardVal = smt::BoolConstantOp::create(b, loc, true);
512 }
513
514 // We prioritize transitions depending on their order in the FSM, and
515 // ensure that a transition is taken if none of the previous transitions
516 // from the same state can be taken.
517 for (auto [transId1, transition1] : llvm::enumerate(transitions)) {
518 if (transition1.from == currentTransition.from &&
519 transId1 < currentTransId) {
520 Value prevGuardVal;
521 if (transition1.guard.has_value()) {
522 for (auto &op : transition1.guard.value()->front()) {
523 if (isa<fsm::ReturnOp>(op)) {
524 // Cast the guard value to an SMT boolean type
525 auto castVal = mlir::UnrealizedConversionCastOp::create(
526 b, loc, b.getType<smt::BitVectorType>(1),
527 mapping.lookup(op.getOperand(0)));
528
529 prevGuardVal = bv1toSmtBool(b, loc, castVal.getResult(0));
530
531 // Assert that the previous guard is false
532 Value negVal = smt::NotOp::create(b, loc, prevGuardVal);
533 guardVal = smt::AndOp::create(b, loc, guardVal, negVal);
534 } else if (!isa<verif::AssertOp>(op)) {
535 b.clone(op, mapping);
536 }
537 }
538 } else {
539 // If a transition has an empty guard region we assume the guard
540 // to be true
541 Value negVal = smt::NotOp::create(
542 b, loc, smt::BoolConstantOp::create(b, loc, true));
543 guardVal = smt::AndOp::create(b, loc, guardVal, negVal);
544 }
545 }
546 }
547 return guardVal;
548 };
549
550 // Return a SmallVector of SMT values output at the arriving state
551 auto output =
552 [&](SmallVector<Value> &outputArgsOutsVarsVals) -> SmallVector<Value> {
553 // map each SMT-quantified variable to a corresponding FSM variable or
554 // argument
555 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast = mapSmtToFsm(
556 loc, b, outputArgsOutsVarsVals, numArgs, numOut, fsmArgs, fsmVars);
557 SmallVector<Value> castOutputVars;
558 auto *outputReg = currentTransition.output.value();
559 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
560
561 // Clone each operation in the output region except for `OutputOp`,
562 // replacing FSM variables and arguments with the results of unrealized
563 // conversion casts and replacing constants with their new clone
564 for (auto &op : outputReg->front()) {
565 if (isa<verif::AssertOp>(op)) {
566 // Store the assertion operations, with a pointer to the region
567 assertions.push_back({currentTransition.to, outputReg});
568 } else {
569 auto *newOp = b.clone(op, mapping);
570 // Retrieve all the operands of the output operation
571
572 if (isa<fsm::OutputOp>(newOp)) {
573 for (auto [id, operand] : llvm::enumerate(newOp->getOperands())) {
574
575 // Cast the output value to the appropriate SMT type
576 auto convCast = UnrealizedConversionCastOp::create(
577 b, loc, outputArgsOutsVarsVals[numArgs + id].getType(),
578 operand);
579 castOutputVars.push_back(convCast->getResult(0));
580 }
581 newOp->erase();
582 }
583 }
584 }
585
586 return castOutputVars;
587 };
588
589 auto forall = smt::ForallOp::create(
590 b, loc, transitionQuantified,
591 [&](OpBuilder &b, Location loc,
592 ValueRange doubledQuantifiedVars) -> Value {
593 SmallVector<Value> startingArgsOutsVars;
594 SmallVector<Value> startingFunArgs;
595 SmallVector<Value> arrivingArgsOutsVars;
596 SmallVector<Value> arrivingFunArgs;
597
598 for (size_t i = 0; i < 2 * numArgs; i++) {
599 if (i % 2 == 1)
600 arrivingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
601 else
602 startingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
603 }
604 for (auto i = 2 * numArgs; i < doubledQuantifiedVars.size(); ++i) {
605 startingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
606 arrivingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
607 startingFunArgs.push_back(doubledQuantifiedVars[i]);
608 }
609
610 // Apply the starting-state function to the starting variables and
611 // outputs
612 auto lhs = smt::ApplyFuncOp::create(
613 b, loc, getStateFunction(currentTransition.from),
614 startingFunArgs);
615
616 // Update the variables according to the action region
617 auto updatedCastVals = action(startingArgsOutsVars);
618 for (size_t i = 0; i < numVars; i++)
619 arrivingArgsOutsVars[numArgs + numOut + i] = updatedCastVals[i];
620
621 // Depending on the updated variables, compute the output values at
622 // the arriving state
623 if (currentTransition.output.has_value()) {
624 auto outputCastVals = output(arrivingArgsOutsVars);
625
626 // Add the output values to the arriving-state function inputs
627 for (auto o : outputCastVals)
628 arrivingFunArgs.push_back(o);
629 }
630
631 // Add the updated variable values to the arriving-state function
632 // inputs
633 for (auto u : updatedCastVals)
634 arrivingFunArgs.push_back(u);
635
636 // Increment time variable if necessary
637 if (cfg.withTime) {
638 auto timeVal = doubledQuantifiedVars.back();
639 auto oneConst = smt::BVConstantOp::create(b, loc, 1, cfg.timeWidth);
640 auto incrementedTime = smt::BVAddOp::create(
641 b, loc, timeVal.getType(), timeVal, oneConst);
642 arrivingFunArgs.push_back(incrementedTime);
643 }
644
645 auto rhs = smt::ApplyFuncOp::create(
646 b, loc, getStateFunction(currentTransition.to), arrivingFunArgs);
647
648 // If there is a guard, compute its value with the variable and
649 // argument values at the starting state
650 auto guardVal = guard(startingArgsOutsVars);
651 auto guardedlhs = smt::AndOp::create(b, loc, lhs, guardVal);
652 return smt::ImpliesOp::create(b, loc, guardedlhs, rhs);
653 });
654
655 smt::AssertOp::create(b, loc, forall);
656 }
657
658 // Produce an implication for each `verif.assert` operation found in output
659 // regions
660 for (auto pa : assertions) {
661 auto forall = smt::ForallOp::create(
662 b, loc, quantifiedTypes,
663 [&](OpBuilder &b, Location loc, ValueRange forallQuantified) -> Value {
664 // map each SMT-quantified variable to a corresponding FSM variable or
665 // argument
666 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast =
667 mapSmtToFsm(loc, b, forallQuantified, numArgs, numOut, fsmArgs,
668 fsmVars);
669
670 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
671
672 Value returnVal = smt::BoolConstantOp::create(b, loc, true);
673
674 // Clone each operation in the output region except for FSM ones,
675 // replacing FSM variables and arguments with the results of
676 // unrealized conversion casts and replacing constants with their new
677 // clone
678 for (auto &op : pa.outputRegion->front()) {
679 // only clone comb, hardware and verif::assert operations
680 if (isa<comb::CombDialect, hw::HWDialect>(op.getDialect()) ||
681 isa<verif::AssertOp>(op)) {
682 auto *newOp = b.clone(op, mapping);
683
684 // Retrieve the assertion values
685 if (isa<verif::AssertOp>(newOp)) {
686 auto assertedVal = newOp->getOperand(0);
687 auto castVal = mlir::UnrealizedConversionCastOp::create(
688 b, loc, b.getType<smt::BitVectorType>(1), assertedVal);
689
690 // Convert to SMT boolean type
691 auto toBool = bv1toSmtBool(b, loc, castVal.getResult(0));
692 auto inState = smt::ApplyFuncOp::create(
693 b, loc, getStateFunction(pa.stateId),
694 forallQuantified.drop_front(numArgs));
695
696 // Produce an implication `F_state(outs, vars, [time]) ->
697 // assertedVal`
698 returnVal = smt::ImpliesOp::create(b, loc, inState, toBool);
699 newOp->erase();
700 }
701 }
702 }
703 return returnVal;
704 });
705
706 smt::AssertOp::create(b, loc, forall);
707 }
708
709 smt::YieldOp::create(b, loc, typeRange, valueRange);
710
711 machineOp.erase();
712 return success();
713}
714
715struct FSMToSMTPass : public circt::impl::ConvertFSMToSMTBase<FSMToSMTPass> {
716 void runOnOperation() override;
717};
718
719void FSMToSMTPass::runOnOperation() {
720 auto module = getOperation();
721 OpBuilder b(module);
722
723 auto machineOps = to_vector(module.getOps<fsm::MachineOp>());
724 if (machineOps.empty()) {
725 return;
726 }
727
728 // Read options from the generated base
729 LoweringConfig cfg;
730 cfg.withTime = withTime;
731 cfg.timeWidth = timeWidth;
732
733 // Throw an error if the module contains an operation used inside the FSM
734 for (auto &op : module.getOps())
735 if (!isa<fsm::MachineOp>(op))
736 // check if the operation is used inside any MachineOp
737 for (auto &use : op.getUses())
738 if (use.getOwner()->getParentOfType<MachineOp>()) {
739 op.emitError("Only operations defined within fsm.machine operations "
740 "are currently supported for use within them");
741 signalPassFailure();
742 return;
743 }
744
745 for (auto machine : llvm::make_early_inc_range(module.getOps<MachineOp>())) {
746 MachineOpConverter converter(b, machine, cfg);
747 if (failed(converter.dispatch())) {
748 signalPassFailure();
749 return;
750 }
751 }
752}
753
754} // namespace
755
756std::unique_ptr<mlir::Pass> circt::createConvertFSMToSMTPass() {
757 return std::make_unique<FSMToSMTPass>();
758}
create(data_type, value)
Definition hw.py:433
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createConvertFSMToSMTPass()
Definition FSMToSMT.cpp:756
Definition comb.py:1
Definition fsm.py:1
Definition hw.py:1
Definition verif.py:1