CIRCT 23.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 Value foundClock;
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 // Calls the right function to fetch `next` operand
574 Value extractRegNext(seq::CompRegOp reg) const { return reg.getInput(); }
575 Value extractRegNext(seq::FirRegOp reg) const { return reg.getNext(); }
576
577 // Extracts the arguments from a given register op
578 template <typename RegT>
579 void extractRegArgs(RegT reg, int64_t &width, Value &next, Value &reset,
580 Value &resetVal, Value &clk) const {
581 width = hw::getBitWidth(reg.getType());
582 reset = reg.getReset();
583 resetVal = reg.getResetValue();
584 clk = reg.getClk();
585
586 // Next is weird: same input, different function
587 next = extractRegNext(reg);
588 }
589
590 // Generates the transitions required to finalize the register to state
591 // transition system conversion
592 void finalizeRegVisit(Operation *op) {
593 int64_t width;
594 Value next, reset, resetVal, clk;
595
596 // Extract the operands depending on the register type
597 auto extract = TypeSwitch<Operation *, LogicalResult>(op)
598 .Case<seq::CompRegOp, seq::FirRegOp>([&](auto reg) {
599 extractRegArgs(reg, width, next, reset, resetVal, clk);
600 return success();
601 })
602 .Default([&](auto) {
603 op->emitError("Invalid register operation !");
604 return failure();
605 });
606
607 // Exit if an invalid register op was detected
608 if (failed(extract))
609 return;
610
611 // Check for multiple clocks
612 if (foundClock) {
613 if (clk != foundClock) {
614 op->emitError("Multi-clock designs are not currently supported.");
615 return;
616 }
617 } else {
618 foundClock = clk;
619 }
620
621 genSort("bitvec", width);
622
623 // Next should already be associated to an LID at this point
624 // As we are going to override it, we need to keep track of the original
625 // instruction
626 size_t nextLID = noLID;
627
628 // We need to check if the next value is a port to avoid nullptrs
629 // To do so, we start by checking if our operation is a block argument
630 if (BlockArgument barg = dyn_cast<BlockArgument>(next)) {
631 // Extract the block argument index and use that to get the line number
632 size_t argIdx = barg.getArgNumber();
633
634 // Check that the extracted argument is in range before using it
635 nextLID = inputLIDs[argIdx];
636
637 } else {
638 nextLID = getOpLID(next);
639 }
640
641 // Check if the register has a reset
642 if (reset) {
643 size_t resetValLID = noLID;
644
645 // Check if the reset signal is a port to avoid nullptrs (as done above
646 // with next)
647 size_t resetLID = noLID;
648 if (BlockArgument barg = dyn_cast<BlockArgument>(reset)) {
649 // Extract the block argument index and use that to get the line
650 // number
651 size_t argIdx = barg.getArgNumber();
652
653 // Check that the extracted argument is in range before using it
654 resetLID = inputLIDs[argIdx];
655
656 } else {
657 resetLID = getOpLID(reset);
658 }
659
660 // Check for a reset value, if none exists assume it's zero
661 if (resetVal)
662 resetValLID = getOpLID(resetVal.getDefiningOp());
663 else
664 resetValLID = genZero(width);
665
666 // Assign a new LID to next
667 setOpLID(next.getDefiningOp());
668
669 // Sanity check: at this point the next operation should have had it's
670 // btor2 counterpart emitted if not then something terrible must have
671 // happened.
672 assert(nextLID != noLID);
673
674 // Generate the ite for the register update reset condition
675 // i.e. reg <= reset ? 0 : next
676 genIte(next.getDefiningOp(), resetLID, resetValLID, nextLID, width);
677 } else {
678 // Sanity check: next should have been assigned
679 if (nextLID == noLID) {
680 next.getDefiningOp()->emitError(
681 "Register input does not point to a valid op!");
682 return;
683 }
684 }
685
686 // Finally generate the next statement
687 genNext(next, op, width);
688 }
689
690public:
691 // Ignore all other explicitly mentioned operations
692 // ** Purposefully left empty **
693 void ignore(Operation *op) {}
694
695 /// Visitor Methods used later on for pattern matching
696
697 // Visitor for the inputs of the module.
698 // This will generate additional sorts and input declaration explicitly for
699 // btor2 Note that outputs are ignored in btor2 as they do not contribute to
700 // the final assertions
701 void visit(hw::PortInfo &port) {
702 // Separate the inputs from outputs and generate the first btor2 lines for
703 // input declaration We only consider ports with an explicit bit-width (so
704 // ignore clocks and immutables)
705 if (port.isInput() && !isa<seq::ClockType, seq::ImmutableType>(port.type)) {
706 // Generate the associated btor declaration for the inputs
707 StringRef iName = port.getName();
708
709 // Guarantees that a sort will exist for the generation of this port's
710 // translation into btor2
711 int64_t w = requireSort(port.type);
712
713 // Save lid for later
714 size_t inlid = lid;
715
716 // Record the defining operation's line ID (the module itself in the
717 // case of ports)
718 inputLIDs[port.argNum] = lid;
719
720 // Increment the lid to keep it unique
721 lid++;
722
723 genInput(inlid, w, iName);
724 }
725 }
726
727 // Emits the associated btor2 operation for a constant. Note that for
728 // simplicity, we will only emit `constd` in order to avoid bit-string
729 // conversions
730 void visitTypeOp(hw::ConstantOp op) {
731 // Make sure the constant hasn't already been created
732 if (handledOps.contains(op))
733 return;
734
735 // Make sure that a sort has been created for our operation
736 int64_t w = requireSort(op.getType());
737
738 // Prepare for for const generation by extracting the const value and
739 // generting the btor2 string
740 genConst(op.getValue(), w, op);
741 }
742
743 // Wires should have been removed in PrepareForFormal
744 void visit(hw::WireOp op) {
745 op->emitError("Wires are not supported in btor!");
746 return signalPassFailure();
747 }
748
749 void visitTypeOp(Operation *op) { visitInvalidTypeOp(op); }
750
751 // Handles non-hw operations
752 void visitInvalidTypeOp(Operation *op) {
753 // Try comb ops
754 dispatchCombinationalVisitor(op);
755 }
756
757 // Binary operations are all emitted the same way, so we can group them into
758 // a single method.
759 template <typename Op>
760 void visitBinOp(Op op, StringRef inst) {
761 // Generate the sort
762 int64_t w = requireSort(op.getType());
763
764 // Start by extracting the operands
765 Value op1 = op.getOperand(0);
766 Value op2 = op.getOperand(1);
767
768 // Generate the line
769 genBinOp(inst, op, op1, op2, w);
770 }
771
772 template <typename Op>
773 void visitVariadicOp(Op op, StringRef inst) {
774 // Generate the sort
775 int64_t w = requireSort(op.getType());
776
777 // Generate the line
778 genVariadicOp(inst, op, w);
779 }
780
781 // Visitors for the binary ops
782 void visitComb(comb::AddOp op) { visitVariadicOp(op, "add"); }
783 void visitComb(comb::SubOp op) { visitBinOp(op, "sub"); }
784 void visitComb(comb::MulOp op) { visitVariadicOp(op, "mul"); }
785 void visitComb(comb::DivSOp op) { visitBinOp(op, "sdiv"); }
786 void visitComb(comb::DivUOp op) { visitBinOp(op, "udiv"); }
787 void visitComb(comb::ModSOp op) { visitBinOp(op, "smod"); }
788 void visitComb(comb::ShlOp op) { visitBinOp(op, "sll"); }
789 void visitComb(comb::ShrUOp op) { visitBinOp(op, "srl"); }
790 void visitComb(comb::ShrSOp op) { visitBinOp(op, "sra"); }
791 void visitComb(comb::AndOp op) { visitVariadicOp(op, "and"); }
792 void visitComb(comb::OrOp op) { visitVariadicOp(op, "or"); }
793 void visitComb(comb::XorOp op) { visitVariadicOp(op, "xor"); }
794 void visitComb(comb::ConcatOp op) { visitVariadicOp(op, "concat"); }
795
796 // Extract ops translate to a slice operation in btor2 in a one-to-one
797 // manner
798 void visitComb(comb::ExtractOp op) {
799 int64_t w = requireSort(op.getType());
800
801 // Start by extracting the necessary information for the emission (i.e.
802 // operand, low bit, ...)
803 Value op0 = op.getOperand();
804 size_t lb = op.getLowBit();
805
806 // Generate the slice instruction
807 genSlice(op, op0, lb, w);
808 }
809
810 // Btor2 uses similar syntax as hw for its comparisons
811 // So we simply need to emit the cmpop name and check for corner cases
812 // where the namings differ.
813 void visitComb(comb::ICmpOp op) {
814 Value lhs = op.getOperand(0);
815 Value rhs = op.getOperand(1);
816
817 // Extract the predicate name (assuming that its a valid btor2
818 // predicate)
819 StringRef pred = stringifyICmpPredicate(op.getPredicate());
820
821 // Check for special cases where hw doesn't align with btor syntax
822 if (pred == "ne")
823 pred = "neq";
824 else if (pred == "ule")
825 pred = "ulte";
826 else if (pred == "sle")
827 pred = "slte";
828 else if (pred == "uge")
829 pred = "ugte";
830 else if (pred == "sge")
831 pred = "sgte";
832
833 // Width of result is always 1 for comparison
834 genSort("bitvec", 1);
835
836 // With the special cases out of the way, the emission is the same as that
837 // of a binary op
838 genBinOp(pred, op, lhs, rhs, 1);
839 }
840
841 // Muxes generally convert to an ite statement
842 void visitComb(comb::MuxOp op) {
843 // Extract predicate, true and false values
844 Value pred = op.getCond();
845 Value tval = op.getTrueValue();
846 Value fval = op.getFalseValue();
847
848 // We assume that both tval and fval have the same width
849 // This width should be the same as the output width
850 int64_t w = requireSort(op.getType());
851
852 // Generate the ite instruction
853 genIte(op, pred, tval, fval, w);
854 }
855
856 // Replicate ops are expanded as a series of concats
857 void visitComb(comb::ReplicateOp op) {
858 Value op0 = op.getOperand();
859 auto count = op.getMultiple();
860 auto inputWidth = op0.getType().getIntOrFloatBitWidth();
861
862 // Generate the concat chain
863 genReplicateAsConcats(op, op0, count, inputWidth);
864 }
865
866 void visitComb(Operation *op) { visitInvalidComb(op); }
867
868 // Try sv ops when comb is done
869 void visitInvalidComb(Operation *op) { dispatchSVVisitor(op); }
870
871 // Assertions are negated then converted to a btor2 bad instruction
872 void visitSV(sv::AssertOp op) {
873 // Expression is what we will try to invert for our assertion
874 Value expr = op.getExpression();
875
876 // This sort is for assertion inversion and potential implies
877 genSort("bitvec", 1);
878
879 // Check for an overaching enable
880 // In our case the sv.if operation will probably only be used when
881 // conditioning an sv.assert on an enable signal. This means that
882 // its condition is probably used to imply our assertion
883 if (auto ifop = dyn_cast<sv::IfOp>(((Operation *)op)->getParentOp())) {
884 Value en = ifop.getOperand();
885
886 // Generate the implication
887 genImplies(ifop, en, expr);
888
889 // Generate the implies inversion
890 genUnaryOp(op, ifop, "not", 1);
891 } else {
892 // Generate the expression inversion
893 genUnaryOp(op, expr, "not", 1);
894 }
895
896 // Generate the bad btor2 instruction
897 genBad(op);
898 }
899 // Assumptions are converted to a btor2 constraint instruction
900 void visitSV(sv::AssumeOp op) {
901 // Extract the expression that we want our constraint to be about
902 Value expr = op.getExpression();
903 genConstraint(expr);
904 }
905
906 // Our only concern with an AlwaysOp is that it follows clocking constraints
907 void visitSV(sv::AlwaysOp op) {
908 if (op.getEvents().size() > 1) {
909 op->emitError("Multiple events in sv.always are not supported.");
910 return signalPassFailure();
911 }
912
913 auto cond = op.getCondition(0);
914
915 if (cond.event != sv::EventControl::AtPosEdge) {
916 op->emitError("Only posedge clocking is supported in sv.always.");
917 return signalPassFailure();
918 }
919
920 if (isa<BlockArgument>(cond.value) ||
921 !isa<seq::FromClockOp>(cond.value.getDefiningOp())) {
922 op->emitError("This pass only currently supports sv.always ops that use "
923 "a top-level seq.clock input (converted using "
924 "seq.from_clock) as their clock.");
925 return signalPassFailure();
926 }
927
928 // By now we know that the condition is a clock signal coming from a
929 // seq.from_clock op
930 auto clk = cond.value.getDefiningOp()->getOperand(0);
931 if (foundClock) {
932 if (clk != foundClock) {
933 op->emitError("Multi-clock designs are not currently supported.");
934 return signalPassFailure();
935 }
936 } else {
937 foundClock = clk;
938 }
939 }
940
941 void visitSV(Operation *op) { visitInvalidSV(op); }
942
943 // Once SV Ops are visited, we need to check for seq ops
944 void visitInvalidSV(Operation *op) { dispatchVerifVisitor(op); }
945
946 template <typename Op>
947 void visitAssertLike(Op op) {
948 // Expression is what we will try to invert for our assertion
949 Value prop = op.getProperty();
950 Value en = op.getEnable();
951
952 // This sort is for assertion inversion and potential implies
953 genSort("bitvec", 1);
954
955 size_t assertLID = noLID;
956 // Check for a related enable signal
957 if (en) {
958 // Generate the implication
959 genImplies(op, en, prop);
960
961 // Generate the implies inversion
962 assertLID = genUnaryOp(op, "not", 1);
963 } else {
964 // Generate the expression inversion
965 assertLID = genUnaryOp(prop.getDefiningOp(), "not", 1);
966 }
967
968 // Generate the bad btor2 instruction
969 genBad(assertLID);
970 }
971
972 template <typename Op>
973 void visitAssumeLike(Op op) {
974 // Expression is what we will try to invert for our assertion
975 Value prop = op.getProperty();
976 Value en = op.getEnable();
977
978 size_t assumeLID = getOpLID(prop);
979 // Check for a related enable signal
980 if (en) {
981 // This sort is for assertion inversion and potential implies
982 genSort("bitvec", 1);
983 // Generate the implication
984 assumeLID = genImplies(op, en, prop);
985 }
986
987 // Generate the bad btor2 instruction
988 genConstraint(assumeLID);
989 }
990
991 // Folds the enable signal into the property and converts the result into a
992 // bad instruction.
993 void visitVerif(verif::AssertOp op) { visitAssertLike(op); }
994 void visitVerif(verif::ClockedAssertOp op) { visitAssertLike(op); }
995
996 // Fold the enable into the property and convert the assumption into a
997 // constraint instruction.
998 void visitVerif(verif::AssumeOp op) { visitAssumeLike(op); }
999 void visitVerif(verif::ClockedAssumeOp op) { visitAssumeLike(op); }
1000
1001 // Error out on most unhandled verif ops
1002 void visitUnhandledVerif(Operation *op) {
1003 op->emitError("not supported in btor2!");
1004 return signalPassFailure();
1005 }
1006
1007 // Dispatch next visitors
1008 void visitInvalidVerif(Operation *op) { visit(op); }
1009
1010 // Seq operation visitor, that dispatches to other seq ops
1011 // Also handles all remaining operations that should be explicitly ignored
1012 void visit(Operation *op) {
1013 // Typeswitch is used here because other seq types will be supported
1014 // like all operations relating to memories and CompRegs
1015 TypeSwitch<Operation *, void>(op)
1016 .Case<seq::FirRegOp, seq::CompRegOp, seq::FromClockOp, seq::ToClockOp>(
1017 [&](auto expr) { visit(expr); })
1018 .Default([&](auto expr) { visitUnsupportedOp(op); });
1019 }
1020
1021 // Firrtl registers generate a state instruction
1022 // The final update is also used to generate a set of next btor
1023 // instructions
1024 void visit(seq::FirRegOp reg) {
1025 // Start by retrieving the register's name and width
1026 StringRef regName = reg.getName();
1027 auto type = reg.getType();
1028 if (!isa<mlir::IntegerType>(type)) {
1029 reg.emitError("Only integer typed seq.firregs are supported in BTOR2.");
1030 return signalPassFailure();
1031 }
1032 int64_t w = requireSort(type);
1033
1034 // Generate state instruction (represents the register declaration)
1035 genState(reg, w, regName);
1036
1037 // Record the operation for future `next` instruction generation
1038 // This is required to model transitions between states (i.e. how a
1039 // register's value evolves over time)
1040 regOps.push_back(reg);
1041 }
1042
1043 // Compregs behave in a similar way as firregs for btor2 emission
1044 void visit(seq::CompRegOp reg) {
1045 // Start by retrieving the register's name and width
1046 StringRef regName = reg.getName().value();
1047 auto type = reg.getType();
1048 if (!isa<mlir::IntegerType>(type)) {
1049 reg.emitError("Only integer typed seq.compregs are supported in BTOR2.");
1050 return signalPassFailure();
1051 }
1052 int64_t w = requireSort(type);
1053
1054 // Check for initial values which must be emitted before the state in
1055 // btor2
1056 auto init = reg.getInitialValue();
1057 auto resetVal = reg.getResetValue();
1058
1059 // If there's an initial value, we need to generate a constant for the
1060 // initial value, then declare the state, then generate the init statement
1061 // (BTOR2 parsers are picky about it being in this order)
1062 auto shouldInitReset = assumeInitReset && resetVal;
1063 // We should create an init statement either if we have an initial value or
1064 // if we assume an initial reset
1065 if (init || shouldInitReset) {
1066 hw::ConstantOp initialConstant;
1067 // Assuming an initial reset takes priority over an initial value if
1068 // both are present
1069 if (shouldInitReset) {
1070 initialConstant = resetVal.getDefiningOp<hw::ConstantOp>();
1071 if (!initialConstant) {
1072 reg->emitError(
1073 "Reset value must be emitted directly by a hw.constant "
1074 "op when --assume-init-reset is in use.");
1075 return;
1076 }
1077 } else {
1078 if (!init.getDefiningOp<seq::InitialOp>()) {
1079 reg->emitError(
1080 "Initial value must be emitted directly by a seq.initial op");
1081 return;
1082 }
1083 // Check that the initial value is a non-null constant
1084 initialConstant = circt::seq::unwrapImmutableValue(init)
1085 .getDefiningOp<hw::ConstantOp>();
1086 if (!initialConstant)
1087 reg->emitError("initial value must be constant");
1088 }
1089
1090 // Visit the initial Value to generate the constant
1091 dispatchTypeOpVisitor(initialConstant);
1092
1093 // Add it to the list of visited operations
1094 handledOps.insert(initialConstant);
1095
1096 // Now we can declare the state
1097 genState(reg, w, regName);
1098
1099 // Finally generate the init statement
1100 genInit(reg, initialConstant, w);
1101 } else {
1102 // Just generate state instruction (represents the register declaration)
1103 genState(reg, w, regName);
1104 }
1105
1106 // Record the operation for future `next` instruction generation
1107 // This is required to model transitions between states (i.e. how a
1108 // register's value evolves over time)
1109 regOps.push_back(reg);
1110 }
1111
1112 void visit(seq::FromClockOp op) {
1113 for (auto *user : op->getResult(0).getUsers()) {
1114 if (!isa<sv::AlwaysOp, verif::ClockedAssertOp>(user)) {
1115 op->emitError("This pass only supports seq.from_clock results being "
1116 "used by sv.always and verif.clocked_assert operations.");
1117 signalPassFailure();
1118 }
1119 }
1120 }
1121
1122 void visit(seq::ToClockOp op) {
1123 // Make sure this value is top-level
1124 if (!isa<BlockArgument>(op.getInput())) {
1125 op->emitError("This pass only supports seq.to_clock operations that take "
1126 "a top-level input as their argument.");
1127 }
1128 // Make sure this clock is never used by anything other than a register so
1129 // we can safely make it implicit
1130 for (auto *user : op->getResult(0).getUsers())
1131 if (!isa<seq::FirRegOp, seq::CompRegOp>(user)) {
1132 op->emitError("This pass only supports seq.to_clock results being "
1133 "used by seq.firreg and seq.compreg operations.");
1134 signalPassFailure();
1135 }
1136 }
1137
1138 // Tail method that handles all operations that weren't handled by previous
1139 // visitors. Here we simply make the pass fail or ignore the op
1140 void visitUnsupportedOp(Operation *op) {
1141 // Check for explicitly ignored ops vs unsupported ops (which cause a
1142 // failure)
1143 TypeSwitch<Operation *, void>(op)
1144 // All explicitly ignored operations are defined here
1145 .Case<sv::MacroDefOp, sv::MacroDeclOp, sv::VerbatimOp,
1146 sv::VerbatimExprOp, sv::VerbatimExprSEOp, sv::IfOp, sv::IfDefOp,
1147 sv::IfDefProceduralOp, sv::AlwaysCombOp, seq::InitialOp,
1148 sv::AlwaysFFOp, seq::InitialOp, seq::YieldOp, hw::OutputOp,
1150 // Specifically ignore printfs, as we can't do anything with them
1151 // in btor2
1152 verif::FormatVerilogStringOp, verif::PrintOp>(
1153 [&](auto expr) { ignore(op); })
1154
1155 // Anything else is considered unsupported and might cause a wrong
1156 // behavior if ignored, so an error is thrown
1157 .Default([&](auto expr) {
1158 op->emitOpError("is an unsupported operation");
1159 return signalPassFailure();
1160 });
1161 }
1162};
1163} // end anonymous namespace
1164
1165void ConvertHWToBTOR2Pass::runOnOperation() {
1166 // Btor2 does not have the concept of modules or module
1167 // hierarchies, so we assume that no nested modules exist at this point.
1168 // This greatly simplifies translation.
1169 getOperation().walk([&](hw::HWModuleOp module) {
1170 // Start by extracting the inputs and generating appropriate instructions
1171 for (auto &port : module.getPortList()) {
1172 // Check whether the port is used as a clock
1173 if (port.isInput()) {
1174 auto portVal = module.getArgumentForInput(port.argNum);
1175 auto usedAsClock =
1176 llvm::any_of(portVal.getUsers(), [](Operation *user) {
1177 return isa<seq::ToClockOp>(user);
1178 });
1179 if (usedAsClock) {
1180 // If it's used as a clock, it can't be used anywhere else (as clocks
1181 // are implicit in BTOR2)
1182 if (portVal.getNumUses() > 1) {
1183 module.emitError(
1184 "Inputs converted to clocks may only have one user.");
1185 return signalPassFailure();
1186 }
1187 // Ports used as clocks should be implicit so don't visit them
1188 continue;
1189 }
1190 }
1191 visit(port);
1192 }
1193
1194 // Previsit all registers in the module in order to avoid dependency cycles
1195 module.walk([&](Operation *op) {
1196 TypeSwitch<Operation *, void>(op)
1197 .Case<seq::FirRegOp, seq::CompRegOp>([&](auto reg) {
1198 visit(reg);
1199 handledOps.insert(op);
1200 })
1201 .Default([&](auto expr) {});
1202 });
1203
1204 // Visit all of the operations in our module
1205 module.walk([&](Operation *op) {
1206 // Check: instances are not (yet) supported
1207 if (isa<hw::InstanceOp>(op)) {
1208 op->emitOpError("not supported in BTOR2 conversion");
1209 return;
1210 }
1211
1212 // Don't process ops that have already been emitted
1213 if (handledOps.contains(op))
1214 return;
1215
1216 // Fill in our worklist
1217 worklist.insert({op, op->operand_begin()});
1218
1219 // Process the elements in our worklist
1220 while (!worklist.empty()) {
1221 auto &[op, operandIt] = worklist.back();
1222 if (operandIt == op->operand_end()) {
1223 // All of the operands have been emitted, it is safe to emit our op
1224 dispatchTypeOpVisitor(op);
1225
1226 // Record that our op has been emitted
1227 handledOps.insert(op);
1228 worklist.pop_back();
1229 continue;
1230 }
1231
1232 // Send the operands of our op to the worklist in case they are still
1233 // un-emitted
1234 Value operand = *(operandIt++);
1235 auto *defOp = operand.getDefiningOp();
1236
1237 // Make sure that we don't emit the same operand twice
1238 if (!defOp || handledOps.contains(defOp))
1239 continue;
1240
1241 // This is triggered if our operand is already in the worklist and
1242 // wasn't handled
1243 if (!worklist.insert({defOp, defOp->operand_begin()}).second) {
1244 defOp->emitError("dependency cycle");
1245 return;
1246 }
1247 }
1248 });
1249
1250 // Iterate through the registers and generate the `next` instructions
1251 for (size_t i = 0; i < regOps.size(); ++i) {
1252 finalizeRegVisit(regOps[i]);
1253 }
1254 });
1255 // Clear data structures to allow for pass reuse
1256 sortToLIDMap.clear();
1257 constToLIDMap.clear();
1258 opLIDMap.clear();
1259 inputLIDs.clear();
1260 regOps.clear();
1261 handledOps.clear();
1262 worklist.clear();
1263}
1264
1265// Constructor with a custom ostream
1266std::unique_ptr<mlir::Pass>
1268 return std::make_unique<ConvertHWToBTOR2Pass>(os);
1269}
1270
1271// Basic default constructor
1272std::unique_ptr<mlir::Pass> circt::createConvertHWToBTOR2Pass() {
1273 return std::make_unique<ConvertHWToBTOR2Pass>(llvm::outs());
1274}
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.