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