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 // The domain should not be empty
270 if (stateFunDomain.empty())
271 return solver.emitError("At least one variable or output is required.");
272
273 // For each state, declare one SMT function: `F_state(outs, vars, [time]) ->
274 // Bool`, returning `true` when `state` is activated.
275 for (auto stateOp : machineOp.front().getOps<fsm::StateOp>()) {
276 int idx = insertStates(states, stateOp.getName());
277 mlir::StringAttr funName =
278 b.getStringAttr(("F_" + stateOp.getName().str()));
279 auto rangeTy = b.getType<smt::BoolType>();
280 auto funTy = b.getType<smt::SMTFuncType>(stateFunDomain, rangeTy);
281 auto acFun = smt::DeclareFunOp::create(b, loc, funTy, funName);
282 stateFunctions.push_back({idx, acFun});
283 outputOfStateId.push_back({&stateOp.getOutput(), idx});
284 }
285
286 // For each transition `state0 && guard01 -> state1`, construct an
287 // implication `F_state_0(outs, vars, [time]) && guard01(outs, vars) ->
288 // F_state_1(outs, vars, [time])`, simulating the activation of a transition
289 // and of the state it reaches.
290 for (auto stateOp : machineOp.front().getOps<fsm::StateOp>()) {
291 if (stateOp.getTransitions().empty())
292 continue;
293 StringRef stateName = stateOp.getName();
294 auto fromState = insertStates(states, stateName);
295 for (auto tr :
296 stateOp.getTransitions().front().getOps<fsm::TransitionOp>()) {
297 auto t = getTransitionRegions(tr, fromState, states, loc);
298 if (numOut > 0)
299 t.output = getOutputRegion(outputOfStateId, t.to);
300 else
301 t.output = std::nullopt;
302 transitions.push_back(t);
303 }
304 }
305
306 SmallVector<PendingAssertion> assertions;
307
308 // The initial assertion sets variables to their initial values and sets `time
309 // == 0` (if present), computing the output values of the initial state
310 // accordingly.
311 auto initialAssertion = smt::ForallOp::create(
312 b, loc, quantifiedTypes,
313 [&](OpBuilder &b, Location loc,
314 const SmallVector<Value> &forallQuantified) -> Value {
315 // map each SMT-quantified variable to a corresponding FSM variable or
316 // argument
317 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast =
318 mapSmtToFsm(loc, b, forallQuantified, numArgs, numOut, fsmArgs,
319 fsmVars);
320 auto *initOutputReg = getOutputRegion(outputOfStateId, 0);
321 SmallVector<Value> castOutValues;
322
323 // replace variables with initial values to create the IR mapping
324 for (auto [id, couple] : llvm::enumerate(fsmToCast)) {
325 if (numArgs + numOut <= id && id < numArgs + numOut + numVars) {
326 fsmToCast[id] = {
327 couple.first,
328 hw::ConstantOp::create(b, loc, varInitValues[id - numArgs])};
329 }
330 }
331
332 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
333
334 SmallVector<mlir::Value> combOutputValues;
335
336 if (!initOutputReg->empty()) {
337 // Clone all the operations in the output region except `OutputOp` and
338 // `AssertOp`, replacing FSM variables and arguments with the results
339 // of unrealized conversion casts and replacing constants with their
340 // new clones
341 for (auto &op : initOutputReg->front()) {
342 if (isa<verif::AssertOp>(op)) {
343 // Store the assertion operations, with a pointer to the region
344 assertions.push_back({0, initOutputReg});
345 } else {
346 auto *newOp = b.clone(op, mapping);
347
348 // Retrieve all the operands of the output operation
349 if (isa<fsm::OutputOp>(newOp)) {
350 for (auto out : newOp->getOperands())
351 combOutputValues.push_back(out);
352 newOp->erase();
353 }
354 }
355
356 // Cast the (builtin integer) results obtained from the output
357 // region to SMT types, to pass them as arguments of the state
358 // function
359
360 for (auto [idx, out] : llvm::enumerate(combOutputValues)) {
361 auto convCast = UnrealizedConversionCastOp::create(
362 b, loc, forallQuantified[numArgs + idx].getType(), out);
363 castOutValues.push_back(convCast->getResult(0));
364 }
365 }
366 }
367
368 // Assign variables and output values their initial value
369 SmallVector<mlir::Value> initialCondition;
370 for (auto [idx, q] : llvm::enumerate(forallQuantified)) {
371 if (cfg.withTime) {
372 if (numArgs + numOut <= idx &&
373 idx < forallQuantified.size() -
374 1) { // FSM variables are assigned their initial value
375 // as an SMT constant
376 initialCondition.push_back(smt::BVConstantOp::create(
377 b, loc, varInitValues[idx - numArgs - numOut]));
378 } else if (numArgs <= idx &&
379 idx < forallQuantified.size() -
380 1) { // FSM outputs are assigned the value
381 // computed in the output region
382 initialCondition.push_back(castOutValues[idx - numArgs]);
383 } else if (idx == forallQuantified.size() -
384 1) // If present, time is set to 0
385 initialCondition.push_back(
386 smt::BVConstantOp::create(b, loc, 0, cfg.timeWidth));
387 } else {
388 if (numArgs + numOut <= idx) { // FSM variables are assigned their
389 // initial value as an SMT constant
390 initialCondition.push_back(smt::BVConstantOp::create(
391 b, loc, varInitValues[idx - numArgs - numOut]));
392 } else if (numArgs <= idx) { // FSM outputs are assigned the value
393 // computed in the output region
394 initialCondition.push_back(castOutValues[idx - numArgs]);
395 }
396 }
397 }
398
399 return smt::ApplyFuncOp::create(b, loc, getStateFunction(0),
400 initialCondition);
401 });
402
403 // Assert initial condition
404 smt::AssertOp::create(b, loc, initialAssertion);
405
406 // Double quantified arguments' types to consider their value at both the
407 // initial and the arriving state
408 SmallVector<Type> transitionQuantified;
409 for (auto [id, ty] : llvm::enumerate(quantifiedTypes)) {
410 transitionQuantified.push_back(ty);
411 if (id < numArgs)
412 transitionQuantified.push_back(ty);
413 }
414
415 // For each transition
416 // `F_state0(vars, outs, [time]) && guard (args, vars) ->
417 // F_state1(updatedVars, updatedOuts, [time + 1])`, build a corresponding
418 // implication.
419
420 for (auto [transId, transition] : llvm::enumerate(transitions)) {
421 // Extract values from structured binding to avoid C++20 extension warnings
422 // when capturing in lambdas
423 const auto &currentTransition = transition;
424 const auto &currentTransId = transId;
425
426 // return a SmallVector of updated SMT values as arguments of the arriving
427 // state function
428 auto action =
429 [&](SmallVector<Value> &actionArgsOutsVarsVals) -> SmallVector<Value> {
430 // Map each SMT-quantified variable to a corresponding FSM variable or
431 // argument
432 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast = mapSmtToFsm(
433 loc, b, actionArgsOutsVarsVals, numArgs, numOut, fsmArgs, fsmVars);
434
435 // Retrieve action region
436 SmallVector<Value> castUpdatedVars;
437
438 // Initialize to the previous value
439 for (size_t i = 0; i < numVars; i++)
440 castUpdatedVars.push_back(actionArgsOutsVarsVals[numArgs + numOut + i]);
441
442 if (currentTransition.action.has_value()) {
443
444 auto *actionReg = currentTransition.action.value();
445
446 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
447
448 SmallVector<std::pair<mlir::Value, mlir::Value>> combActionValues;
449
450 // Clone all the operations in the action region except for `UpdateOp`
451 // and `AssertOp`, replacing FSM variables and arguments with the
452 // results of unrealized conversion casts and replacing constants with
453 // their new clone
454 for (auto &op : actionReg->front()) {
455 if (isa<verif::AssertOp>(op)) {
456 // Ignore assertions in action regions
457 op.emitWarning("Assertions in action regions are ignored.");
458 } else {
459 auto *newOp = b.clone(op, mapping);
460 // Retrieve the updated values and their operands
461 if (isa<fsm::UpdateOp>(newOp)) {
462 auto varToUpdate = newOp->getOperand(0);
463 auto updatedValue = newOp->getOperand(1);
464 for (auto [id, var] : llvm::enumerate(fsmToCast)) {
465 if (var.second == varToUpdate) {
466 // Cast the updated value to the appropriate SMT type
467 auto convCast = UnrealizedConversionCastOp::create(
468 b, loc, actionArgsOutsVarsVals[numOut + id].getType(),
469 updatedValue);
470
471 castUpdatedVars[id - numArgs] = convCast->getResult(0);
472 }
473 }
474 newOp->erase();
475 }
476 }
477 }
478 }
479 return castUpdatedVars;
480 };
481
482 // Return an SMT value for the transition's guard
483 auto guard = [&](SmallVector<Value> &actionArgsOutsVarsVals) -> Value {
484 // Map each SMT-quantified variable to a corresponding FSM variable or
485 // argument
486 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast = mapSmtToFsm(
487 loc, b, actionArgsOutsVarsVals, numArgs, numOut, fsmArgs, fsmVars);
488
489 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
490
491 Value guardVal;
492
493 // Clone all the operations in the guard region except for `ReturnOp` and
494 // `AssertOp,
495 if (currentTransition.guard.has_value()) {
496 for (auto &op : currentTransition.guard.value()->front()) {
497 if (isa<verif::AssertOp>(op)) {
498 // Ignore assertions in guard regions
499 op.emitWarning("Assertions in guard regions are ignored.");
500 } else {
501 auto *newOp = b.clone(op, mapping);
502 // Retrieve the guard value
503 if (isa<fsm::ReturnOp>(newOp)) {
504 // Cast the guard value to an SMT boolean type
505 auto castVal = mlir::UnrealizedConversionCastOp::create(
506 b, loc, b.getType<smt::BitVectorType>(1),
507 newOp->getOperand(0));
508
509 guardVal = bv1toSmtBool(b, loc, castVal.getResult(0));
510 newOp->erase();
511 }
512 }
513 }
514 } else {
515 guardVal = smt::BoolConstantOp::create(b, loc, true);
516 }
517
518 // We prioritize transitions depending on their order in the FSM, and
519 // ensure that a transition is taken if none of the previous transitions
520 // from the same state can be taken.
521 for (auto [transId1, transition1] : llvm::enumerate(transitions)) {
522 if (transition1.from == currentTransition.from &&
523 transId1 < currentTransId) {
524 Value prevGuardVal;
525 if (transition1.guard.has_value()) {
526 for (auto &op : transition1.guard.value()->front()) {
527 if (isa<fsm::ReturnOp>(op)) {
528 // Cast the guard value to an SMT boolean type
529 auto castVal = mlir::UnrealizedConversionCastOp::create(
530 b, loc, b.getType<smt::BitVectorType>(1),
531 mapping.lookup(op.getOperand(0)));
532
533 prevGuardVal = bv1toSmtBool(b, loc, castVal.getResult(0));
534
535 // Assert that the previous guard is false
536 Value negVal = smt::NotOp::create(b, loc, prevGuardVal);
537 guardVal = smt::AndOp::create(b, loc, guardVal, negVal);
538 } else if (!isa<verif::AssertOp>(op)) {
539 b.clone(op, mapping);
540 }
541 }
542 } else {
543 // If a transition has an empty guard region we assume the guard
544 // to be true
545 Value negVal = smt::NotOp::create(
546 b, loc, smt::BoolConstantOp::create(b, loc, true));
547 guardVal = smt::AndOp::create(b, loc, guardVal, negVal);
548 }
549 }
550 }
551 return guardVal;
552 };
553
554 // Return a SmallVector of SMT values output at the arriving state
555 auto output =
556 [&](SmallVector<Value> &outputArgsOutsVarsVals) -> SmallVector<Value> {
557 // map each SMT-quantified variable to a corresponding FSM variable or
558 // argument
559 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast = mapSmtToFsm(
560 loc, b, outputArgsOutsVarsVals, numArgs, numOut, fsmArgs, fsmVars);
561 SmallVector<Value> castOutputVars;
562 auto *outputReg = currentTransition.output.value();
563 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
564
565 // Clone each operation in the output region except for `OutputOp`,
566 // replacing FSM variables and arguments with the results of unrealized
567 // conversion casts and replacing constants with their new clone
568 for (auto &op : outputReg->front()) {
569 if (isa<verif::AssertOp>(op)) {
570 // Store the assertion operations, with a pointer to the region
571 assertions.push_back({currentTransition.to, outputReg});
572 } else {
573 auto *newOp = b.clone(op, mapping);
574 // Retrieve all the operands of the output operation
575
576 if (isa<fsm::OutputOp>(newOp)) {
577 for (auto [id, operand] : llvm::enumerate(newOp->getOperands())) {
578
579 // Cast the output value to the appropriate SMT type
580 auto convCast = UnrealizedConversionCastOp::create(
581 b, loc, outputArgsOutsVarsVals[numArgs + id].getType(),
582 operand);
583 castOutputVars.push_back(convCast->getResult(0));
584 }
585 newOp->erase();
586 }
587 }
588 }
589
590 return castOutputVars;
591 };
592
593 auto forall = smt::ForallOp::create(
594 b, loc, transitionQuantified,
595 [&](OpBuilder &b, Location loc,
596 ValueRange doubledQuantifiedVars) -> Value {
597 SmallVector<Value> startingArgsOutsVars;
598 SmallVector<Value> startingFunArgs;
599 SmallVector<Value> arrivingArgsOutsVars;
600 SmallVector<Value> arrivingFunArgs;
601
602 for (size_t i = 0; i < 2 * numArgs; i++) {
603 if (i % 2 == 1)
604 arrivingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
605 else
606 startingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
607 }
608 for (auto i = 2 * numArgs; i < doubledQuantifiedVars.size(); ++i) {
609 startingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
610 arrivingArgsOutsVars.push_back(doubledQuantifiedVars[i]);
611 startingFunArgs.push_back(doubledQuantifiedVars[i]);
612 }
613
614 // Apply the starting-state function to the starting variables and
615 // outputs
616 auto lhs = smt::ApplyFuncOp::create(
617 b, loc, getStateFunction(currentTransition.from),
618 startingFunArgs);
619
620 // Update the variables according to the action region
621 auto updatedCastVals = action(startingArgsOutsVars);
622 for (size_t i = 0; i < numVars; i++)
623 arrivingArgsOutsVars[numArgs + numOut + i] = updatedCastVals[i];
624
625 // Depending on the updated variables, compute the output values at
626 // the arriving state
627 if (currentTransition.output.has_value()) {
628 auto outputCastVals = output(arrivingArgsOutsVars);
629
630 // Add the output values to the arriving-state function inputs
631 for (auto o : outputCastVals)
632 arrivingFunArgs.push_back(o);
633 }
634
635 // Add the updated variable values to the arriving-state function
636 // inputs
637 for (auto u : updatedCastVals)
638 arrivingFunArgs.push_back(u);
639
640 // Increment time variable if necessary
641 if (cfg.withTime) {
642 auto timeVal = doubledQuantifiedVars.back();
643 auto oneConst = smt::BVConstantOp::create(b, loc, 1, cfg.timeWidth);
644 auto incrementedTime = smt::BVAddOp::create(
645 b, loc, timeVal.getType(), timeVal, oneConst);
646 arrivingFunArgs.push_back(incrementedTime);
647 }
648
649 auto rhs = smt::ApplyFuncOp::create(
650 b, loc, getStateFunction(currentTransition.to), arrivingFunArgs);
651
652 // If there is a guard, compute its value with the variable and
653 // argument values at the starting state
654 auto guardVal = guard(startingArgsOutsVars);
655 auto guardedlhs = smt::AndOp::create(b, loc, lhs, guardVal);
656 return smt::ImpliesOp::create(b, loc, guardedlhs, rhs);
657 });
658
659 smt::AssertOp::create(b, loc, forall);
660 }
661
662 // Produce an implication for each `verif.assert` operation found in output
663 // regions
664 for (auto pa : assertions) {
665 auto forall = smt::ForallOp::create(
666 b, loc, quantifiedTypes,
667 [&](OpBuilder &b, Location loc, ValueRange forallQuantified) -> Value {
668 // map each SMT-quantified variable to a corresponding FSM variable or
669 // argument
670 SmallVector<std::pair<mlir::Value, mlir::Value>> fsmToCast =
671 mapSmtToFsm(loc, b, forallQuantified, numArgs, numOut, fsmArgs,
672 fsmVars);
673
674 IRMapping mapping = createIRMapping(fsmToCast, constMapper);
675
676 Value returnVal = smt::BoolConstantOp::create(b, loc, true);
677
678 // Clone each operation in the output region except for FSM ones,
679 // replacing FSM variables and arguments with the results of
680 // unrealized conversion casts and replacing constants with their new
681 // clone
682 for (auto &op : pa.outputRegion->front()) {
683 // only clone comb, hardware and verif::assert operations
684 if (isa<comb::CombDialect, hw::HWDialect>(op.getDialect()) ||
685 isa<verif::AssertOp>(op)) {
686 auto *newOp = b.clone(op, mapping);
687
688 // Retrieve the assertion values
689 if (isa<verif::AssertOp>(newOp)) {
690 auto assertedVal = newOp->getOperand(0);
691 auto castVal = mlir::UnrealizedConversionCastOp::create(
692 b, loc, b.getType<smt::BitVectorType>(1), assertedVal);
693
694 // Convert to SMT boolean type
695 auto toBool = bv1toSmtBool(b, loc, castVal.getResult(0));
696 auto inState = smt::ApplyFuncOp::create(
697 b, loc, getStateFunction(pa.stateId),
698 forallQuantified.drop_front(numArgs));
699
700 // Produce an implication `F_state(outs, vars, [time]) ->
701 // assertedVal`
702 returnVal = smt::ImpliesOp::create(b, loc, inState, toBool);
703 newOp->erase();
704 }
705 }
706 }
707 return returnVal;
708 });
709
710 smt::AssertOp::create(b, loc, forall);
711 }
712
713 smt::YieldOp::create(b, loc, typeRange, valueRange);
714
715 machineOp.erase();
716 return success();
717}
718
719struct FSMToSMTPass : public circt::impl::ConvertFSMToSMTBase<FSMToSMTPass> {
720 void runOnOperation() override;
721};
722
723void FSMToSMTPass::runOnOperation() {
724 auto module = getOperation();
725 OpBuilder b(module);
726
727 auto machineOps = to_vector(module.getOps<fsm::MachineOp>());
728 if (machineOps.empty()) {
729 return;
730 }
731
732 // Read options from the generated base
733 LoweringConfig cfg;
734 cfg.withTime = withTime;
735 cfg.timeWidth = timeWidth;
736
737 // Throw an error if the module contains an operation used inside the FSM
738 for (auto &op : module.getOps())
739 if (!isa<fsm::MachineOp>(op))
740 // check if the operation is used inside any MachineOp
741 for (auto &use : op.getUses())
742 if (use.getOwner()->getParentOfType<MachineOp>()) {
743 op.emitError("Only operations defined within fsm.machine operations "
744 "are currently supported for use within them");
745 signalPassFailure();
746 return;
747 }
748
749 for (auto machine : llvm::make_early_inc_range(module.getOps<MachineOp>())) {
750 MachineOpConverter converter(b, machine, cfg);
751 if (failed(converter.dispatch())) {
752 signalPassFailure();
753 return;
754 }
755 }
756}
757
758} // namespace
759
760std::unique_ptr<mlir::Pass> circt::createConvertFSMToSMTPass() {
761 return std::make_unique<FSMToSMTPass>();
762}
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:760
Definition comb.py:1
Definition fsm.py:1
Definition hw.py:1
Definition verif.py:1