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