CIRCT 20.0.0git
Loading...
Searching...
No Matches
HWToBTOR2.cpp
Go to the documentation of this file.
1//===- HWToBTOR2.cpp - HW to BTOR2 translation ------------------*- C++ -*-===//
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// Converts a hw module to a btor2 format and prints it out
9//
10//===----------------------------------------------------------------------===//
11
32#include "mlir/Pass/Pass.h"
33#include "llvm/ADT/MapVector.h"
34#include "llvm/ADT/TypeSwitch.h"
35#include "llvm/Support/raw_ostream.h"
36
37namespace circt {
38#define GEN_PASS_DEF_CONVERTHWTOBTOR2
39#include "circt/Conversion/Passes.h.inc"
40} // namespace circt
41
42using namespace circt;
43using namespace hw;
44
45namespace {
46// The goal here is to traverse the operations in order and convert them one by
47// one into btor2
48struct ConvertHWToBTOR2Pass
49 : public circt::impl::ConvertHWToBTOR2Base<ConvertHWToBTOR2Pass>,
50 public comb::CombinationalVisitor<ConvertHWToBTOR2Pass>,
51 public sv::Visitor<ConvertHWToBTOR2Pass>,
52 public hw::TypeOpVisitor<ConvertHWToBTOR2Pass>,
53 public verif::Visitor<ConvertHWToBTOR2Pass> {
54public:
55 ConvertHWToBTOR2Pass(raw_ostream &os) : os(os) {}
56 // Executes the pass
57 void runOnOperation() override;
58
59private:
60 // Output stream in which the btor2 will be emitted
61 raw_ostream &os;
62
63 // Create a counter that attributes a unique id to each generated btor2 line
64 size_t lid = 1; // btor2 line identifiers usually start at 1
65 size_t nclocks = 0;
66
67 // Create maps to keep track of lid associations
68 // We need these in order to reference results as operands in btor2
69
70 // Keeps track of the ids associated to each declared sort
71 // This is used in order to guarantee that sorts are unique and to allow for
72 // instructions to reference the given sorts (key: width, value: LID)
73 DenseMap<size_t, size_t> sortToLIDMap;
74 // Keeps track of {constant, width} -> LID mappings
75 // This is used in order to avoid duplicating constant declarations
76 // in the output btor2. It is also useful when tracking
77 // constants declarations that aren't tied to MLIR ops.
78 DenseMap<APInt, size_t> constToLIDMap;
79 // Keeps track of the most recent update line for each operation
80 // This allows for operations to be used throughout the btor file
81 // with their most recent expression. Btor uses unique identifiers for each
82 // instruction, so we need to have an association between those and MLIR Ops.
83 DenseMap<Operation *, size_t> opLIDMap;
84 // Stores the LID of the associated input.
85 // This holds a similar function as the opLIDMap but keeps
86 // track of block argument index -> LID mappings
87 DenseMap<size_t, size_t> inputLIDs;
88 // Stores all of the register declaration ops.
89 // This allows for the emission of transition arcs for the regs
90 // to be deferred to the end of the pass.
91 // This is necessary, as we need to wait for the `next` operation to
92 // have been converted to btor2 before we can emit the transition.
93 SmallVector<Operation *> regOps;
94
95 // Used to perform a DFS search through the module to declare all operands
96 // before they are used
97 llvm::SmallMapVector<Operation *, OperandRange::iterator, 16> worklist;
98
99 // Keeps track of operations that have been declared
100 DenseSet<Operation *> handledOps;
101
102 // Constants used during the conversion
103 static constexpr size_t noLID = -1UL;
104 [[maybe_unused]] static constexpr int64_t noWidth = -1L;
105
106 /// Field helper functions
107public:
108 // Checks if an operation was declared
109 // If so, its lid will be returned
110 // Otherwise a new lid will be assigned to the op
111 size_t getOpLID(Operation *op) {
112 // Look for the original operation declaration
113 // Make sure that wires are considered when looking for an lid
114 Operation *defOp = op;
115 auto &f = opLIDMap[defOp];
116
117 // If the op isn't associated to an lid, assign it a new one
118 if (!f)
119 f = lid++;
120 return f;
121 }
122
123 // Associates the current lid to an operation
124 // The LID is then incremented to maintain uniqueness
125 size_t setOpLID(Operation *op) {
126 size_t oplid = lid++;
127 opLIDMap[op] = oplid;
128 return oplid;
129 }
130
131 // Checks if an operation was declared
132 // If so, its lid will be returned
133 // Otherwise -1 will be returned
134 size_t getOpLID(Value value) {
135 Operation *defOp = value.getDefiningOp();
136
137 if (auto it = opLIDMap.find(defOp); it != opLIDMap.end())
138 return it->second;
139
140 // Check for special case where op is actually a port
141 // To do so, we start by checking if our operation is a block argument
142 if (BlockArgument barg = dyn_cast<BlockArgument>(value)) {
143 // Extract the block argument index and use that to get the line number
144 size_t argIdx = barg.getArgNumber();
145
146 // Check that the extracted argument is in range before using it
147 if (auto it = inputLIDs.find(argIdx); it != inputLIDs.end())
148 return it->second;
149 }
150
151 // Return -1 if no LID was found
152 return noLID;
153 }
154
155private:
156 // Checks if a sort was declared with the given width
157 // If so, its lid will be returned
158 // Otherwise -1 will be returned
159 size_t getSortLID(size_t w) {
160 if (auto it = sortToLIDMap.find(w); it != sortToLIDMap.end())
161 return it->second;
162
163 // If no lid was found return -1
164 return noLID;
165 }
166
167 // Associate the sort with a new lid
168 size_t setSortLID(size_t w) {
169 size_t sortlid = lid;
170 // Add the width to the declared sorts along with the associated line id
171 sortToLIDMap[w] = lid++;
172 return sortlid;
173 }
174
175 // Checks if a constant of a given size has been declared.
176 // If so, its lid will be returned.
177 // Otherwise -1 will be returned.
178 size_t getConstLID(int64_t val, size_t w) {
179 if (auto it = constToLIDMap.find(APInt(w, val)); it != constToLIDMap.end())
180 return it->second;
181
182 // if no lid was found return -1
183 return noLID;
184 }
185
186 // Associates a constant declaration to a new lid
187 size_t setConstLID(int64_t val, size_t w) {
188 size_t constlid = lid;
189 // Keep track of this value in a constant declaration tracker
190 constToLIDMap[APInt(w, val)] = lid++;
191 return constlid;
192 }
193
194 /// String generation helper functions
195
196 // Generates a sort declaration instruction given a type ("bitvec" or array)
197 // and a width.
198 void genSort(StringRef type, size_t width) {
199 // Check that the sort wasn't already declared
200 if (getSortLID(width) != noLID) {
201 return; // If it has already been declared then return an empty string
202 }
203
204 size_t sortlid = setSortLID(width);
205
206 // Build and return a sort declaration
207 os << sortlid << " "
208 << "sort"
209 << " " << type << " " << width << "\n";
210 }
211
212 // Generates an input declaration given a sort lid and a name.
213 void genInput(size_t inlid, size_t width, StringRef name) {
214 // Retrieve the lid associated with the sort (sid)
215 size_t sid = sortToLIDMap.at(width);
216
217 // Generate input declaration
218 os << inlid << " "
219 << "input"
220 << " " << sid << " " << name << "\n";
221 }
222
223 // Generates a constant declaration given a value, a width and a name.
224 void genConst(APInt value, size_t width, Operation *op) {
225 // For now we're going to assume that the name isn't taken, given that hw
226 // is already in SSA form
227 size_t opLID = getOpLID(op);
228
229 // Retrieve the lid associated with the sort (sid)
230 size_t sid = sortToLIDMap.at(width);
231
232 os << opLID << " "
233 << "constd"
234 << " " << sid << " " << value << "\n";
235 }
236
237 // Generates a zero constant expression
238 size_t genZero(size_t width) {
239 // Check if the constant has been created yet
240 size_t zlid = getConstLID(0, width);
241 if (zlid != noLID)
242 return zlid;
243
244 // Retrieve the lid associated with the sort (sid)
245 size_t sid = sortToLIDMap.at(width);
246
247 // Associate an lid to the new constant
248 size_t constlid = setConstLID(0, width);
249
250 // Build and return the zero btor instruction
251 os << constlid << " "
252 << "zero"
253 << " " << sid << "\n";
254 return constlid;
255 }
256
257 // Generates an init statement, which allows for the use of initial values
258 // operands in compreg registers
259 void genInit(Operation *reg, Value initVal, int64_t width) {
260 // Retrieve the various identifiers we require for this
261 size_t regLID = getOpLID(reg);
262 size_t sid = sortToLIDMap.at(width);
263 size_t initValLID = getOpLID(initVal);
264
265 // Build and emit the string (the lid here doesn't need to be associated
266 // to an op as it won't be used)
267 os << lid++ << " "
268 << "init"
269 << " " << sid << " " << regLID << " " << initValLID << "\n";
270 }
271
272 // Generates a binary operation instruction given an op name, two operands
273 // and a result width.
274 void genBinOp(StringRef inst, Operation *binop, Value op1, Value op2,
275 size_t width) {
276 // TODO: adding support for most variadic ops shouldn't be too hard
277 if (binop->getNumOperands() != 2) {
278 binop->emitError("variadic operations not are not currently supported");
279 return;
280 }
281
282 // Set the LID for this operation
283 size_t opLID = getOpLID(binop);
284
285 // Find the sort's lid
286 size_t sid = sortToLIDMap.at(width);
287
288 // Assuming that the operands were already emitted
289 // Find the LIDs associated to the operands
290 size_t op1LID = getOpLID(op1);
291 size_t op2LID = getOpLID(op2);
292
293 // Build and return the string
294 os << opLID << " " << inst << " " << sid << " " << op1LID << " " << op2LID
295 << "\n";
296 }
297
298 // Generates a slice instruction given an operand, the lowbit, and the width
299 void genSlice(Operation *srcop, Value op0, size_t lowbit, int64_t width) {
300 // Assign a LID to this operation
301 size_t opLID = getOpLID(srcop);
302
303 // Find the sort's associated lid in order to use it in the instruction
304 size_t sid = sortToLIDMap.at(width);
305
306 // Assuming that the operand has already been emitted
307 // Find the LID associated to the operand
308 size_t op0LID = getOpLID(op0);
309
310 // Build and return the slice instruction
311 os << opLID << " "
312 << "slice"
313 << " " << sid << " " << op0LID << " " << (lowbit + width - 1) << " "
314 << lowbit << "\n";
315 }
316
317 // Generates a constant declaration given a value, a width and a name
318 void genUnaryOp(Operation *srcop, Operation *op0, StringRef inst,
319 size_t width) {
320 // Register the source operation with the current line id
321 size_t opLID = getOpLID(srcop);
322
323 // Retrieve the lid associated with the sort (sid)
324 size_t sid = sortToLIDMap.at(width);
325
326 // Assuming that the operand has already been emitted
327 // Find the LID associated to the operand
328 size_t op0LID = getOpLID(op0);
329
330 os << opLID << " " << inst << " " << sid << " " << op0LID << "\n";
331 }
332
333 // Generates a constant declaration given a value, a width and a name and
334 // returns the LID associated to it
335 void genUnaryOp(Operation *srcop, Value op0, StringRef inst, size_t width) {
336 genUnaryOp(srcop, op0.getDefiningOp(), inst, width);
337 }
338
339 // Generates a constant declaration given a operand lid, a width and a name
340 size_t genUnaryOp(size_t op0LID, StringRef inst, size_t width) {
341 // Register the source operation with the current line id
342 size_t curLid = lid++;
343
344 // Retrieve the lid associated with the sort (sid)
345 size_t sid = sortToLIDMap.at(width);
346
347 os << curLid << " " << inst << " " << sid << " " << op0LID << "\n";
348 return curLid;
349 }
350
351 // Generates a constant declaration given a value, a width and a name
352 size_t genUnaryOp(Operation *op0, StringRef inst, size_t width) {
353 return genUnaryOp(getOpLID(op0), inst, width);
354 }
355
356 // Generates a constant declaration given a value, a width and a name and
357 // returns the LID associated to it
358 size_t genUnaryOp(Value op0, StringRef inst, size_t width) {
359 return genUnaryOp(getOpLID(op0), inst, width);
360 }
361
362 // Generate a btor2 assertion given an assertion operation
363 // Note that a predicate inversion must have already been generated at this
364 // point
365 void genBad(Operation *assertop) {
366 // Start by finding the expression lid
367 size_t assertLID = getOpLID(assertop);
368 genBad(assertLID);
369 }
370
371 // Generate a btor2 assertion given an assertion operation's LID
372 // Note that a predicate inversion must have already been generated at this
373 // point
374 void genBad(size_t assertLID) {
375 // Build and return the btor2 string
376 // Also update the lid as this instruction is not associated to an mlir op
377 os << lid++ << " "
378 << "bad"
379 << " " << assertLID << "\n";
380 }
381
382 // Generate a btor2 constraint given an expression from an assumption
383 // operation
384 void genConstraint(Value expr) {
385 // Start by finding the expression lid
386 size_t exprLID = getOpLID(expr);
387
388 genConstraint(exprLID);
389 }
390
391 // Generate a btor2 constraint given an expression from an assumption
392 // operation
393 void genConstraint(size_t exprLID) {
394 // Build and return the btor2 string
395 // Also update the lid as this instruction is not associated to an mlir op
396 os << lid++ << " "
397 << "constraint"
398 << " " << exprLID << "\n";
399 }
400
401 // Generate an ite instruction (if then else) given a predicate, two values
402 // and a res width
403 void genIte(Operation *srcop, Value cond, Value t, Value f, int64_t width) {
404 // Retrieve the operand lids, assuming they were emitted
405 size_t condLID = getOpLID(cond);
406 size_t tLID = getOpLID(t);
407 size_t fLID = getOpLID(f);
408
409 genIte(srcop, condLID, tLID, fLID, width);
410 }
411
412 // Generate an ite instruction (if then else) given a predicate, two values
413 // and a res width
414 void genIte(Operation *srcop, size_t condLID, size_t tLID, size_t fLID,
415 int64_t width) {
416 // Register the source operation with the current line id
417 size_t opLID = getOpLID(srcop);
418
419 // Retrieve the lid associated with the sort (sid)
420 size_t sid = sortToLIDMap.at(width);
421
422 // Build and return the ite instruction
423 os << opLID << " "
424 << "ite"
425 << " " << sid << " " << condLID << " " << tLID << " " << fLID << "\n";
426 }
427
428 // Generate a logical implication given a lhs and a rhs
429 size_t genImplies(Operation *srcop, Value lhs, Value rhs) {
430 // Retrieve LIDs for the lhs and rhs
431 size_t lhsLID = getOpLID(lhs);
432 size_t rhsLID = getOpLID(rhs);
433
434 return genImplies(srcop, lhsLID, rhsLID);
435 }
436
437 // Generate a logical implication given a lhs and a rhs
438 size_t genImplies(Operation *srcop, size_t lhsLID, size_t rhsLID) {
439 // Register the source operation with the current line id
440 size_t opLID = getOpLID(srcop);
441 return genImplies(opLID, lhsLID, rhsLID);
442 }
443
444 size_t genImplies(size_t opLID, size_t lhsLID, size_t rhsLID) {
445 // Retrieve the lid associated with the sort (sid)
446 size_t sid = sortToLIDMap.at(1);
447 // Build and emit the implies operation
448 os << opLID << " "
449 << "implies"
450 << " " << sid << " " << lhsLID << " " << rhsLID << "\n";
451 return opLID;
452 }
453
454 // Generates a state instruction given a width and a name
455 void genState(Operation *srcop, int64_t width, StringRef name) {
456 // Register the source operation with the current line id
457 size_t opLID = getOpLID(srcop);
458
459 // Retrieve the lid associated with the sort (sid)
460 size_t sid = sortToLIDMap.at(width);
461
462 // Build and return the state instruction
463 os << opLID << " "
464 << "state"
465 << " " << sid << " " << name << "\n";
466 }
467
468 // Generates a next instruction, given a width, a state LID, and a next
469 // value LID
470 void genNext(Value next, Operation *reg, int64_t width) {
471 // Retrieve the lid associated with the sort (sid)
472 size_t sid = sortToLIDMap.at(width);
473
474 // Retrieve the LIDs associated to reg and next
475 size_t regLID = getOpLID(reg);
476 size_t nextLID = getOpLID(next);
477
478 // Build and return the next instruction
479 // Also update the lid as this instruction is not associated to an mlir op
480 os << lid++ << " "
481 << "next"
482 << " " << sid << " " << regLID << " " << nextLID << "\n";
483 }
484
485 // Verifies that the sort required for the given operation's btor2 emission
486 // has been generated
487 int64_t requireSort(mlir::Type type) {
488 // Start by figuring out what sort needs to be generated
489 int64_t width = hw::getBitWidth(type);
490
491 // Sanity check: getBitWidth can technically return -1 it is a type with
492 // no width (like a clock). This shouldn't be allowed as width is required
493 // to generate a sort
494 assert(width != noWidth);
495
496 // Generate the sort regardles of resulting width (nothing will be added
497 // if the sort already exists)
498 genSort("bitvec", width);
499 return width;
500 }
501
502 // Generates the transitions required to finalize the register to state
503 // transition system conversion
504 void finalizeRegVisit(Operation *op) {
505 int64_t width;
506 Value next, reset, resetVal;
507
508 // Extract the operands depending on the register type
509 if (auto reg = dyn_cast<seq::CompRegOp>(op)) {
510 width = hw::getBitWidth(reg.getType());
511 next = reg.getInput();
512 reset = reg.getReset();
513 resetVal = reg.getResetValue();
514 } else if (auto reg = dyn_cast<seq::FirRegOp>(op)) {
515 width = hw::getBitWidth(reg.getType());
516 next = reg.getNext();
517 reset = reg.getReset();
518 resetVal = reg.getResetValue();
519 } else {
520 op->emitError("Invalid register operation !");
521 return;
522 }
523
524 genSort("bitvec", width);
525
526 // Next should already be associated to an LID at this point
527 // As we are going to override it, we need to keep track of the original
528 // instruction
529 size_t nextLID = noLID;
530
531 // We need to check if the next value is a port to avoid nullptrs
532 // To do so, we start by checking if our operation is a block argument
533 if (BlockArgument barg = dyn_cast<BlockArgument>(next)) {
534 // Extract the block argument index and use that to get the line number
535 size_t argIdx = barg.getArgNumber();
536
537 // Check that the extracted argument is in range before using it
538 nextLID = inputLIDs[argIdx];
539
540 } else {
541 nextLID = getOpLID(next);
542 }
543
544 // Check if the register has a reset
545 if (reset) {
546 size_t resetValLID = noLID;
547
548 // Check if the reset signal is a port to avoid nullptrs (as done above
549 // with next)
550 size_t resetLID = noLID;
551 if (BlockArgument barg = dyn_cast<BlockArgument>(reset)) {
552 // Extract the block argument index and use that to get the line
553 // number
554 size_t argIdx = barg.getArgNumber();
555
556 // Check that the extracted argument is in range before using it
557 resetLID = inputLIDs[argIdx];
558
559 } else {
560 resetLID = getOpLID(reset);
561 }
562
563 // Check for a reset value, if none exists assume it's zero
564 if (resetVal)
565 resetValLID = getOpLID(resetVal.getDefiningOp());
566 else
567 resetValLID = genZero(width);
568
569 // Assign a new LID to next
570 setOpLID(next.getDefiningOp());
571
572 // Sanity check: at this point the next operation should have had it's
573 // btor2 counterpart emitted if not then something terrible must have
574 // happened.
575 assert(nextLID != noLID);
576
577 // Generate the ite for the register update reset condition
578 // i.e. reg <= reset ? 0 : next
579 genIte(next.getDefiningOp(), resetLID, resetValLID, nextLID, width);
580 } else {
581 // Sanity check: next should have been assigned
582 if (nextLID == noLID) {
583 next.getDefiningOp()->emitError(
584 "Register input does not point to a valid op!");
585 return;
586 }
587 }
588
589 // Finally generate the next statement
590 genNext(next, op, width);
591 }
592
593public:
594 /// Visitor Methods used later on for pattern matching
595
596 // Visitor for the inputs of the module.
597 // This will generate additional sorts and input declaration explicitly for
598 // btor2 Note that outputs are ignored in btor2 as they do not contribute to
599 // the final assertions
600 void visit(hw::PortInfo &port) {
601 // Separate the inputs from outputs and generate the first btor2 lines for
602 // input declaration We only consider ports with an explicit bit-width (so
603 // ignore clocks and immutables)
604 if (port.isInput() && !isa<seq::ClockType, seq::ImmutableType>(port.type)) {
605 // Generate the associated btor declaration for the inputs
606 StringRef iName = port.getName();
607
608 // Guarantees that a sort will exist for the generation of this port's
609 // translation into btor2
610 int64_t w = requireSort(port.type);
611
612 // Save lid for later
613 size_t inlid = lid;
614
615 // Record the defining operation's line ID (the module itself in the
616 // case of ports)
617 inputLIDs[port.argNum] = lid;
618
619 // Increment the lid to keep it unique
620 lid++;
621
622 genInput(inlid, w, iName);
623 }
624 }
625
626 // Emits the associated btor2 operation for a constant. Note that for
627 // simplicity, we will only emit `constd` in order to avoid bit-string
628 // conversions
629 void visitTypeOp(hw::ConstantOp op) {
630 // Make sure the constant hasn't already been created
631 if (handledOps.contains(op))
632 return;
633
634 // Make sure that a sort has been created for our operation
635 int64_t w = requireSort(op.getType());
636
637 // Prepare for for const generation by extracting the const value and
638 // generting the btor2 string
639 genConst(op.getValue(), w, op);
640 }
641
642 // Wires should have been removed in PrepareForFormal
643 void visit(hw::WireOp op) {
644 op->emitError("Wires are not supported in btor!");
645 return signalPassFailure();
646 }
647
648 void visitTypeOp(Operation *op) { visitInvalidTypeOp(op); }
649
650 // Handles non-hw operations
651 void visitInvalidTypeOp(Operation *op) {
652 // Try comb ops
653 dispatchCombinationalVisitor(op);
654 }
655
656 // Binary operations are all emitted the same way, so we can group them into
657 // a single method.
658 template <typename Op>
659 void visitBinOp(Op op, StringRef inst) {
660 // Generete the sort
661 int64_t w = requireSort(op.getType());
662
663 // Start by extracting the operands
664 Value op1 = op.getOperand(0);
665 Value op2 = op.getOperand(1);
666
667 // Generate the line
668 genBinOp(inst, op, op1, op2, w);
669 }
670
671 // Visitors for the binary ops
672 void visitComb(comb::AddOp op) { visitBinOp(op, "add"); }
673 void visitComb(comb::SubOp op) { visitBinOp(op, "sub"); }
674 void visitComb(comb::MulOp op) { visitBinOp(op, "mul"); }
675 void visitComb(comb::DivSOp op) { visitBinOp(op, "sdiv"); }
676 void visitComb(comb::DivUOp op) { visitBinOp(op, "udiv"); }
677 void visitComb(comb::ModSOp op) { visitBinOp(op, "smod"); }
678 void visitComb(comb::ShlOp op) { visitBinOp(op, "sll"); }
679 void visitComb(comb::ShrUOp op) { visitBinOp(op, "srl"); }
680 void visitComb(comb::ShrSOp op) { visitBinOp(op, "sra"); }
681 void visitComb(comb::AndOp op) { visitBinOp(op, "and"); }
682 void visitComb(comb::OrOp op) { visitBinOp(op, "or"); }
683 void visitComb(comb::XorOp op) { visitBinOp(op, "xor"); }
684 void visitComb(comb::ConcatOp op) { visitBinOp(op, "concat"); }
685
686 // Extract ops translate to a slice operation in btor2 in a one-to-one
687 // manner
688 void visitComb(comb::ExtractOp op) {
689 int64_t w = requireSort(op.getType());
690
691 // Start by extracting the necessary information for the emission (i.e.
692 // operand, low bit, ...)
693 Value op0 = op.getOperand();
694 size_t lb = op.getLowBit();
695
696 // Generate the slice instruction
697 genSlice(op, op0, lb, w);
698 }
699
700 // Btor2 uses similar syntax as hw for its comparisons
701 // So we simply need to emit the cmpop name and check for corner cases
702 // where the namings differ.
703 void visitComb(comb::ICmpOp op) {
704 Value lhs = op.getOperand(0);
705 Value rhs = op.getOperand(1);
706
707 // Extract the predicate name (assuming that its a valid btor2
708 // predicate)
709 StringRef pred = stringifyICmpPredicate(op.getPredicate());
710
711 // Check for special cases where hw doesn't align with btor syntax
712 if (pred == "ne")
713 pred = "neq";
714 else if (pred == "ule")
715 pred = "ulte";
716 else if (pred == "sle")
717 pred = "slte";
718 else if (pred == "uge")
719 pred = "ugte";
720 else if (pred == "sge")
721 pred = "sgte";
722
723 // Width of result is always 1 for comparison
724 genSort("bitvec", 1);
725
726 // With the special cases out of the way, the emission is the same as that
727 // of a binary op
728 genBinOp(pred, op, lhs, rhs, 1);
729 }
730
731 // Muxes generally convert to an ite statement
732 void visitComb(comb::MuxOp op) {
733 // Extract predicate, true and false values
734 Value pred = op.getCond();
735 Value tval = op.getTrueValue();
736 Value fval = op.getFalseValue();
737
738 // We assume that both tval and fval have the same width
739 // This width should be the same as the output width
740 int64_t w = requireSort(op.getType());
741
742 // Generate the ite instruction
743 genIte(op, pred, tval, fval, w);
744 }
745
746 void visitComb(Operation *op) { visitInvalidComb(op); }
747
748 // Try sv ops when comb is done
749 void visitInvalidComb(Operation *op) { dispatchSVVisitor(op); }
750
751 // Assertions are negated then converted to a btor2 bad instruction
752 void visitSV(sv::AssertOp op) {
753 // Expression is what we will try to invert for our assertion
754 Value expr = op.getExpression();
755
756 // This sort is for assertion inversion and potential implies
757 genSort("bitvec", 1);
758
759 // Check for an overaching enable
760 // In our case the sv.if operation will probably only be used when
761 // conditioning an sv.assert on an enable signal. This means that
762 // its condition is probably used to imply our assertion
763 if (auto ifop = dyn_cast<sv::IfOp>(((Operation *)op)->getParentOp())) {
764 Value en = ifop.getOperand();
765
766 // Generate the implication
767 genImplies(ifop, en, expr);
768
769 // Generate the implies inversion
770 genUnaryOp(op, ifop, "not", 1);
771 } else {
772 // Generate the expression inversion
773 genUnaryOp(op, expr, "not", 1);
774 }
775
776 // Generate the bad btor2 instruction
777 genBad(op);
778 }
779 // Assumptions are converted to a btor2 constraint instruction
780 void visitSV(sv::AssumeOp op) {
781 // Extract the expression that we want our constraint to be about
782 Value expr = op.getExpression();
783 genConstraint(expr);
784 }
785
786 void visitSV(Operation *op) { visitInvalidSV(op); }
787
788 // Once SV Ops are visited, we need to check for seq ops
789 void visitInvalidSV(Operation *op) { dispatchVerifVisitor(op); }
790
791 template <typename Op>
792 void visitAssertLike(Op op) {
793 // Expression is what we will try to invert for our assertion
794 Value prop = op.getProperty();
795 Value en = op.getEnable();
796
797 // This sort is for assertion inversion and potential implies
798 genSort("bitvec", 1);
799
800 size_t assertLID = noLID;
801 // Check for a related enable signal
802 if (en) {
803 // Generate the implication
804 genImplies(op, en, prop);
805
806 // Generate the implies inversion
807 assertLID = genUnaryOp(op, "not", 1);
808 } else {
809 // Generate the expression inversion
810 assertLID = genUnaryOp(prop.getDefiningOp(), "not", 1);
811 }
812
813 // Generate the bad btor2 instruction
814 genBad(assertLID);
815 }
816
817 template <typename Op>
818 void visitAssumeLike(Op op) {
819 // Expression is what we will try to invert for our assertion
820 Value prop = op.getProperty();
821 Value en = op.getEnable();
822
823 size_t assumeLID = getOpLID(prop);
824 // Check for a related enable signal
825 if (en) {
826 // This sort is for assertion inversion and potential implies
827 genSort("bitvec", 1);
828 // Generate the implication
829 assumeLID = genImplies(op, en, prop);
830 }
831
832 // Generate the bad btor2 instruction
833 genConstraint(assumeLID);
834 }
835
836 // Folds the enable signal into the property and converts the result into a
837 // bad instruction.
838 void visitVerif(verif::AssertOp op) { visitAssertLike(op); }
839 void visitVerif(verif::ClockedAssertOp op) { visitAssertLike(op); }
840
841 // Fold the enable into the property and convert the assumption into a
842 // constraint instruction.
843 void visitVerif(verif::AssumeOp op) { visitAssumeLike(op); }
844 void visitVerif(verif::ClockedAssumeOp op) { visitAssumeLike(op); }
845
846 // Cover is not supported in btor2
847 void visitVerif(verif::CoverOp op) {
848 op->emitError("Cover is not supported in btor2!");
849 return signalPassFailure();
850 }
851
852 void visitVerif(verif::ClockedCoverOp op) {
853 op->emitError("Cover is not supported in btor2!");
854 return signalPassFailure();
855 }
856
857 void visitInvalidVerif(Operation *op) { visit(op); }
858
859 // Seq operation visitor, that dispatches to other seq ops
860 // Also handles all remaining operations that should be explicitly ignored
861 void visit(Operation *op) {
862 // Typeswitch is used here because other seq types will be supported
863 // like all operations relating to memories and CompRegs
864 TypeSwitch<Operation *, void>(op)
865 .Case<seq::FirRegOp, seq::CompRegOp>([&](auto expr) { visit(expr); })
866 .Default([&](auto expr) { visitUnsupportedOp(op); });
867 }
868
869 // Firrtl registers generate a state instruction
870 // The final update is also used to generate a set of next btor
871 // instructions
872 void visit(seq::FirRegOp reg) {
873 // Start by retrieving the register's name and width
874 StringRef regName = reg.getName();
875 int64_t w = requireSort(reg.getType());
876
877 // Generate state instruction (represents the register declaration)
878 genState(reg, w, regName);
879
880 // Record the operation for future `next` instruction generation
881 // This is required to model transitions between states (i.e. how a
882 // register's value evolves over time)
883 regOps.push_back(reg);
884 }
885
886 // Compregs behave in a similar way as firregs for btor2 emission
887 void visit(seq::CompRegOp reg) {
888 // Start by retrieving the register's name and width
889 StringRef regName = reg.getName().value();
890 int64_t w = requireSort(reg.getType());
891
892 // Check for initial values which must be emitted before the state in
893 // btor2
894 auto init = reg.getInitialValue();
895
896 // If there's an initial value, we need to generate a constant for the
897 // initial value, then declare the state, then generate the init statement
898 // (BTOR2 parsers are picky about it being in this order)
899 if (init) {
900 if (!init.getDefiningOp<seq::InitialOp>()) {
901 reg->emitError(
902 "Initial value must be emitted directly by a seq.initial op");
903 return;
904 }
905 // Check that the initial value is a non-null constant
906 auto initialConstant = circt::seq::unwrapImmutableValue(init)
907 .getDefiningOp<hw::ConstantOp>();
908 if (!initialConstant)
909 reg->emitError("initial value must be constant");
910
911 // Visit the initial Value to generate the constant
912 dispatchTypeOpVisitor(initialConstant);
913
914 // Add it to the list of visited operations
915 handledOps.insert(initialConstant);
916
917 // Now we can declare the state
918 genState(reg, w, regName);
919
920 // Finally generate the init statement
921 genInit(reg, initialConstant, w);
922 } else {
923 // Just generate state instruction (represents the register declaration)
924 genState(reg, w, regName);
925 }
926
927 // Record the operation for future `next` instruction generation
928 // This is required to model transitions between states (i.e. how a
929 // register's value evolves over time)
930 regOps.push_back(reg);
931 }
932
933 // Ignore all other explicitly mentionned operations
934 // ** Purposefully left empty **
935 void ignore(Operation *op) {}
936
937 // Tail method that handles all operations that weren't handled by previous
938 // visitors. Here we simply make the pass fail or ignore the op
939 void visitUnsupportedOp(Operation *op) {
940 // Check for explicitly ignored ops vs unsupported ops (which cause a
941 // failure)
942 TypeSwitch<Operation *, void>(op)
943 // All explicitly ignored operations are defined here
944 .Case<sv::MacroDefOp, sv::MacroDeclOp, sv::VerbatimOp,
945 sv::VerbatimExprOp, sv::VerbatimExprSEOp, sv::IfOp, sv::IfDefOp,
946 sv::IfDefProceduralOp, sv::AlwaysOp, sv::AlwaysCombOp,
947 seq::InitialOp, sv::AlwaysFFOp, seq::FromClockOp, seq::InitialOp,
948 seq::YieldOp, hw::OutputOp, hw::HWModuleOp>(
949 [&](auto expr) { ignore(op); })
950
951 // Make sure that the design only contains one clock
952 .Case<seq::FromClockOp>([&](auto expr) {
953 if (++nclocks > 1UL) {
954 op->emitOpError("Mutli-clock designs are not supported!");
955 return signalPassFailure();
956 }
957 })
958
959 // Anything else is considered unsupported and might cause a wrong
960 // behavior if ignored, so an error is thrown
961 .Default([&](auto expr) {
962 op->emitOpError("is an unsupported operation");
963 return signalPassFailure();
964 });
965 }
966};
967} // end anonymous namespace
968
969void ConvertHWToBTOR2Pass::runOnOperation() {
970 // Btor2 does not have the concept of modules or module
971 // hierarchies, so we assume that no nested modules exist at this point.
972 // This greatly simplifies translation.
973 getOperation().walk([&](hw::HWModuleOp module) {
974 // Start by extracting the inputs and generating appropriate instructions
975 for (auto &port : module.getPortList()) {
976 visit(port);
977 }
978
979 // Previsit all registers in the module in order to avoid dependency cycles
980 module.walk([&](Operation *op) {
981 TypeSwitch<Operation *, void>(op)
982 .Case<seq::FirRegOp, seq::CompRegOp>([&](auto reg) {
983 visit(reg);
984 handledOps.insert(op);
985 })
986 .Default([&](auto expr) {});
987 });
988
989 // Visit all of the operations in our module
990 module.walk([&](Operation *op) {
991 // Check: instances are not (yet) supported
992 if (isa<hw::InstanceOp>(op)) {
993 op->emitOpError("not supported in BTOR2 conversion");
994 return;
995 }
996
997 // Don't process ops that have already been emitted
998 if (handledOps.contains(op))
999 return;
1000
1001 // Fill in our worklist
1002 worklist.insert({op, op->operand_begin()});
1003
1004 // Process the elements in our worklist
1005 while (!worklist.empty()) {
1006 auto &[op, operandIt] = worklist.back();
1007 if (operandIt == op->operand_end()) {
1008 // All of the operands have been emitted, it is safe to emit our op
1009 dispatchTypeOpVisitor(op);
1010
1011 // Record that our op has been emitted
1012 handledOps.insert(op);
1013 worklist.pop_back();
1014 continue;
1015 }
1016
1017 // Send the operands of our op to the worklist in case they are still
1018 // un-emitted
1019 Value operand = *(operandIt++);
1020 auto *defOp = operand.getDefiningOp();
1021
1022 // Make sure that we don't emit the same operand twice
1023 if (!defOp || handledOps.contains(defOp))
1024 continue;
1025
1026 // This is triggered if our operand is already in the worklist and
1027 // wasn't handled
1028 if (!worklist.insert({defOp, defOp->operand_begin()}).second) {
1029 defOp->emitError("dependency cycle");
1030 return;
1031 }
1032 }
1033 });
1034
1035 // Iterate through the registers and generate the `next` instructions
1036 for (size_t i = 0; i < regOps.size(); ++i) {
1037 finalizeRegVisit(regOps[i]);
1038 }
1039 });
1040 // Clear data structures to allow for pass reuse
1041 sortToLIDMap.clear();
1042 constToLIDMap.clear();
1043 opLIDMap.clear();
1044 inputLIDs.clear();
1045 regOps.clear();
1046 handledOps.clear();
1047 worklist.clear();
1048}
1049
1050// Constructor with a custom ostream
1051std::unique_ptr<mlir::Pass>
1053 return std::make_unique<ConvertHWToBTOR2Pass>(os);
1054}
1055
1056// Basic default constructor
1057std::unique_ptr<mlir::Pass> circt::createConvertHWToBTOR2Pass() {
1058 return std::make_unique<ConvertHWToBTOR2Pass>(llvm::outs());
1059}
assert(baseType &&"element must be base type")
static SmallVector< PortInfo > getPortList(ModuleTy &mod)
Definition HWOps.cpp:1420
This helps visit Combinational nodes.
This helps visit TypeOp nodes.
Definition HWVisitors.h:25
Value unwrapImmutableValue(mlir::TypedValue< seq::ImmutableType > immutableVal)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createConvertHWToBTOR2Pass()
Definition hw.py:1
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
mlir::Type type
Definition HWTypes.h:31
This holds the name, type, direction of a module's ports.
StringRef getName() const
size_t argNum
This is the argument index or the result index depending on the direction.