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