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 if (binop->getNumOperands() != 2) {
278 binop->emitError(
279 "only the binary form of this operation is currently supported");
280 return;
281 }
282
283 // Set the LID for this operation
284 size_t opLID = getOpLID(binop);
285
286 // Find the sort's lid
287 size_t sid = sortToLIDMap.at(width);
288
289 // Assuming that the operands were already emitted
290 // Find the LIDs associated to the operands
291 size_t op1LID = getOpLID(op1);
292 size_t op2LID = getOpLID(op2);
293
294 // Build and return the string
295 os << opLID << " " << inst << " " << sid << " " << op1LID << " " << op2LID
296 << "\n";
297 }
298
299 // Expands a variadic operation into multiple binary operation instructions
300 void genVariadicOp(StringRef inst, Operation *op, size_t width) {
301 auto operands = op->getOperands();
302 size_t sid = sortToLIDMap.at(width);
303
304 if (operands.size() < 2) {
305 op->emitError("variadic operations with less than 2 operands are not "
306 "currently supported");
307 return;
308 }
309
310 // TODO: this excludes concat, which requires special handling for result
311 // sorts
312 if (!op->hasTrait<mlir::OpTrait::SameOperandsAndResultType>()) {
313 op->emitError(
314 "variadic operations with differing operand and result types are "
315 "not currently supported");
316 return;
317 }
318
319 // Unroll variadic op into series of binary ops
320 // This will represent the previous operand in the chain:
321 auto prevOperandLID = getOpLID(operands[0]);
322
323 for (auto operand : operands.drop_front()) {
324 // Manually increment lid since we need multiple per op
325 auto thisLid = lid++;
326 auto thisOperandLID = getOpLID(operand);
327 os << thisLid << " " << inst << " " << sid << " " << prevOperandLID << " "
328 << thisOperandLID << "\n";
329 prevOperandLID = thisLid;
330 }
331
332 // Send lookups of the op's LID to the final binary op in the chain
333 opLIDMap[op] = prevOperandLID;
334 }
335
336 // Generates a slice instruction given an operand, the lowbit, and the width
337 void genSlice(Operation *srcop, Value op0, size_t lowbit, int64_t width) {
338 // Assign a LID to this operation
339 size_t opLID = getOpLID(srcop);
340
341 // Find the sort's associated lid in order to use it in the instruction
342 size_t sid = sortToLIDMap.at(width);
343
344 // Assuming that the operand has already been emitted
345 // Find the LID associated to the operand
346 size_t op0LID = getOpLID(op0);
347
348 // Build and return the slice instruction
349 os << opLID << " "
350 << "slice"
351 << " " << sid << " " << op0LID << " " << (lowbit + width - 1) << " "
352 << lowbit << "\n";
353 }
354
355 // Generates a constant declaration given a value, a width and a name
356 void genUnaryOp(Operation *srcop, Operation *op0, StringRef inst,
357 size_t width) {
358 // Register the source operation with the current line id
359 size_t opLID = getOpLID(srcop);
360
361 // Retrieve the lid associated with the sort (sid)
362 size_t sid = sortToLIDMap.at(width);
363
364 // Assuming that the operand has already been emitted
365 // Find the LID associated to the operand
366 size_t op0LID = getOpLID(op0);
367
368 os << opLID << " " << inst << " " << sid << " " << op0LID << "\n";
369 }
370
371 // Generates a constant declaration given a value, a width and a name and
372 // returns the LID associated to it
373 void genUnaryOp(Operation *srcop, Value op0, StringRef inst, size_t width) {
374 genUnaryOp(srcop, op0.getDefiningOp(), inst, width);
375 }
376
377 // Generates a constant declaration given a operand lid, a width and a name
378 size_t genUnaryOp(size_t op0LID, StringRef inst, size_t width) {
379 // Register the source operation with the current line id
380 size_t curLid = lid++;
381
382 // Retrieve the lid associated with the sort (sid)
383 size_t sid = sortToLIDMap.at(width);
384
385 os << curLid << " " << inst << " " << sid << " " << op0LID << "\n";
386 return curLid;
387 }
388
389 // Generates a constant declaration given a value, a width and a name
390 size_t genUnaryOp(Operation *op0, StringRef inst, size_t width) {
391 return genUnaryOp(getOpLID(op0), inst, width);
392 }
393
394 // Generates a constant declaration given a value, a width and a name and
395 // returns the LID associated to it
396 size_t genUnaryOp(Value op0, StringRef inst, size_t width) {
397 return genUnaryOp(getOpLID(op0), inst, width);
398 }
399
400 // Generate a btor2 assertion given an assertion operation
401 // Note that a predicate inversion must have already been generated at this
402 // point
403 void genBad(Operation *assertop) {
404 // Start by finding the expression lid
405 size_t assertLID = getOpLID(assertop);
406 genBad(assertLID);
407 }
408
409 // Generate a btor2 assertion given an assertion operation's LID
410 // Note that a predicate inversion must have already been generated at this
411 // point
412 void genBad(size_t assertLID) {
413 // Build and return the btor2 string
414 // Also update the lid as this instruction is not associated to an mlir op
415 os << lid++ << " "
416 << "bad"
417 << " " << assertLID << "\n";
418 }
419
420 // Generate a btor2 constraint given an expression from an assumption
421 // operation
422 void genConstraint(Value expr) {
423 // Start by finding the expression lid
424 size_t exprLID = getOpLID(expr);
425
426 genConstraint(exprLID);
427 }
428
429 // Generate a btor2 constraint given an expression from an assumption
430 // operation
431 void genConstraint(size_t exprLID) {
432 // Build and return the btor2 string
433 // Also update the lid as this instruction is not associated to an mlir op
434 os << lid++ << " "
435 << "constraint"
436 << " " << exprLID << "\n";
437 }
438
439 // Generate an ite instruction (if then else) given a predicate, two values
440 // and a res width
441 void genIte(Operation *srcop, Value cond, Value t, Value f, int64_t width) {
442 // Retrieve the operand lids, assuming they were emitted
443 size_t condLID = getOpLID(cond);
444 size_t tLID = getOpLID(t);
445 size_t fLID = getOpLID(f);
446
447 genIte(srcop, condLID, tLID, fLID, width);
448 }
449
450 // Generate an ite instruction (if then else) given a predicate, two values
451 // and a res width
452 void genIte(Operation *srcop, size_t condLID, size_t tLID, size_t fLID,
453 int64_t width) {
454 // Register the source operation with the current line id
455 size_t opLID = getOpLID(srcop);
456
457 // Retrieve the lid associated with the sort (sid)
458 size_t sid = sortToLIDMap.at(width);
459
460 // Build and return the ite instruction
461 os << opLID << " "
462 << "ite"
463 << " " << sid << " " << condLID << " " << tLID << " " << fLID << "\n";
464 }
465
466 // Generate a logical implication given a lhs and a rhs
467 size_t genImplies(Operation *srcop, Value lhs, Value rhs) {
468 // Retrieve LIDs for the lhs and rhs
469 size_t lhsLID = getOpLID(lhs);
470 size_t rhsLID = getOpLID(rhs);
471
472 return genImplies(srcop, lhsLID, rhsLID);
473 }
474
475 // Generate a logical implication given a lhs and a rhs
476 size_t genImplies(Operation *srcop, size_t lhsLID, size_t rhsLID) {
477 // Register the source operation with the current line id
478 size_t opLID = getOpLID(srcop);
479 return genImplies(opLID, lhsLID, rhsLID);
480 }
481
482 size_t genImplies(size_t opLID, size_t lhsLID, size_t rhsLID) {
483 // Retrieve the lid associated with the sort (sid)
484 size_t sid = sortToLIDMap.at(1);
485 // Build and emit the implies operation
486 os << opLID << " "
487 << "implies"
488 << " " << sid << " " << lhsLID << " " << rhsLID << "\n";
489 return opLID;
490 }
491
492 // Generates a state instruction given a width and a name
493 void genState(Operation *srcop, int64_t width, StringRef name) {
494 // Register the source operation with the current line id
495 size_t opLID = getOpLID(srcop);
496
497 // Retrieve the lid associated with the sort (sid)
498 size_t sid = sortToLIDMap.at(width);
499
500 // Build and return the state instruction
501 os << opLID << " "
502 << "state"
503 << " " << sid << " " << name << "\n";
504 }
505
506 // Generates a next instruction, given a width, a state LID, and a next
507 // value LID
508 void genNext(Value next, Operation *reg, int64_t width) {
509 // Retrieve the lid associated with the sort (sid)
510 size_t sid = sortToLIDMap.at(width);
511
512 // Retrieve the LIDs associated to reg and next
513 size_t regLID = getOpLID(reg);
514 size_t nextLID = getOpLID(next);
515
516 // Build and return the next instruction
517 // Also update the lid as this instruction is not associated to an mlir op
518 os << lid++ << " "
519 << "next"
520 << " " << sid << " " << regLID << " " << nextLID << "\n";
521 }
522
523 // Verifies that the sort required for the given operation's btor2 emission
524 // has been generated
525 int64_t requireSort(mlir::Type type) {
526 // Start by figuring out what sort needs to be generated
527 int64_t width = hw::getBitWidth(type);
528
529 // Sanity check: getBitWidth can technically return -1 it is a type with
530 // no width (like a clock). This shouldn't be allowed as width is required
531 // to generate a sort
532 assert(width != noWidth);
533
534 // Generate the sort regardles of resulting width (nothing will be added
535 // if the sort already exists)
536 genSort("bitvec", width);
537 return width;
538 }
539
540 // Generates the transitions required to finalize the register to state
541 // transition system conversion
542 void finalizeRegVisit(Operation *op) {
543 int64_t width;
544 Value next, reset, resetVal;
545
546 // Extract the operands depending on the register type
547 if (auto reg = dyn_cast<seq::CompRegOp>(op)) {
548 width = hw::getBitWidth(reg.getType());
549 next = reg.getInput();
550 reset = reg.getReset();
551 resetVal = reg.getResetValue();
552 } else if (auto reg = dyn_cast<seq::FirRegOp>(op)) {
553 width = hw::getBitWidth(reg.getType());
554 next = reg.getNext();
555 reset = reg.getReset();
556 resetVal = reg.getResetValue();
557 } else {
558 op->emitError("Invalid register operation !");
559 return;
560 }
561
562 genSort("bitvec", width);
563
564 // Next should already be associated to an LID at this point
565 // As we are going to override it, we need to keep track of the original
566 // instruction
567 size_t nextLID = noLID;
568
569 // We need to check if the next value is a port to avoid nullptrs
570 // To do so, we start by checking if our operation is a block argument
571 if (BlockArgument barg = dyn_cast<BlockArgument>(next)) {
572 // Extract the block argument index and use that to get the line number
573 size_t argIdx = barg.getArgNumber();
574
575 // Check that the extracted argument is in range before using it
576 nextLID = inputLIDs[argIdx];
577
578 } else {
579 nextLID = getOpLID(next);
580 }
581
582 // Check if the register has a reset
583 if (reset) {
584 size_t resetValLID = noLID;
585
586 // Check if the reset signal is a port to avoid nullptrs (as done above
587 // with next)
588 size_t resetLID = noLID;
589 if (BlockArgument barg = dyn_cast<BlockArgument>(reset)) {
590 // Extract the block argument index and use that to get the line
591 // number
592 size_t argIdx = barg.getArgNumber();
593
594 // Check that the extracted argument is in range before using it
595 resetLID = inputLIDs[argIdx];
596
597 } else {
598 resetLID = getOpLID(reset);
599 }
600
601 // Check for a reset value, if none exists assume it's zero
602 if (resetVal)
603 resetValLID = getOpLID(resetVal.getDefiningOp());
604 else
605 resetValLID = genZero(width);
606
607 // Assign a new LID to next
608 setOpLID(next.getDefiningOp());
609
610 // Sanity check: at this point the next operation should have had it's
611 // btor2 counterpart emitted if not then something terrible must have
612 // happened.
613 assert(nextLID != noLID);
614
615 // Generate the ite for the register update reset condition
616 // i.e. reg <= reset ? 0 : next
617 genIte(next.getDefiningOp(), resetLID, resetValLID, nextLID, width);
618 } else {
619 // Sanity check: next should have been assigned
620 if (nextLID == noLID) {
621 next.getDefiningOp()->emitError(
622 "Register input does not point to a valid op!");
623 return;
624 }
625 }
626
627 // Finally generate the next statement
628 genNext(next, op, width);
629 }
630
631public:
632 /// Visitor Methods used later on for pattern matching
633
634 // Visitor for the inputs of the module.
635 // This will generate additional sorts and input declaration explicitly for
636 // btor2 Note that outputs are ignored in btor2 as they do not contribute to
637 // the final assertions
638 void visit(hw::PortInfo &port) {
639 // Separate the inputs from outputs and generate the first btor2 lines for
640 // input declaration We only consider ports with an explicit bit-width (so
641 // ignore clocks and immutables)
642 if (port.isInput() && !isa<seq::ClockType, seq::ImmutableType>(port.type)) {
643 // Generate the associated btor declaration for the inputs
644 StringRef iName = port.getName();
645
646 // Guarantees that a sort will exist for the generation of this port's
647 // translation into btor2
648 int64_t w = requireSort(port.type);
649
650 // Save lid for later
651 size_t inlid = lid;
652
653 // Record the defining operation's line ID (the module itself in the
654 // case of ports)
655 inputLIDs[port.argNum] = lid;
656
657 // Increment the lid to keep it unique
658 lid++;
659
660 genInput(inlid, w, iName);
661 }
662 }
663
664 // Emits the associated btor2 operation for a constant. Note that for
665 // simplicity, we will only emit `constd` in order to avoid bit-string
666 // conversions
667 void visitTypeOp(hw::ConstantOp op) {
668 // Make sure the constant hasn't already been created
669 if (handledOps.contains(op))
670 return;
671
672 // Make sure that a sort has been created for our operation
673 int64_t w = requireSort(op.getType());
674
675 // Prepare for for const generation by extracting the const value and
676 // generting the btor2 string
677 genConst(op.getValue(), w, op);
678 }
679
680 // Wires should have been removed in PrepareForFormal
681 void visit(hw::WireOp op) {
682 op->emitError("Wires are not supported in btor!");
683 return signalPassFailure();
684 }
685
686 void visitTypeOp(Operation *op) { visitInvalidTypeOp(op); }
687
688 // Handles non-hw operations
689 void visitInvalidTypeOp(Operation *op) {
690 // Try comb ops
691 dispatchCombinationalVisitor(op);
692 }
693
694 // Binary operations are all emitted the same way, so we can group them into
695 // a single method.
696 template <typename Op>
697 void visitBinOp(Op op, StringRef inst) {
698 // Generate the sort
699 int64_t w = requireSort(op.getType());
700
701 // Start by extracting the operands
702 Value op1 = op.getOperand(0);
703 Value op2 = op.getOperand(1);
704
705 // Generate the line
706 genBinOp(inst, op, op1, op2, w);
707 }
708
709 template <typename Op>
710 void visitVariadicOp(Op op, StringRef inst) {
711 // Generate the sort
712 int64_t w = requireSort(op.getType());
713
714 // Generate the line
715 genVariadicOp(inst, op, w);
716 }
717
718 // Visitors for the binary ops
719 void visitComb(comb::AddOp op) { visitVariadicOp(op, "add"); }
720 void visitComb(comb::SubOp op) { visitBinOp(op, "sub"); }
721 void visitComb(comb::MulOp op) { visitVariadicOp(op, "mul"); }
722 void visitComb(comb::DivSOp op) { visitBinOp(op, "sdiv"); }
723 void visitComb(comb::DivUOp op) { visitBinOp(op, "udiv"); }
724 void visitComb(comb::ModSOp op) { visitBinOp(op, "smod"); }
725 void visitComb(comb::ShlOp op) { visitBinOp(op, "sll"); }
726 void visitComb(comb::ShrUOp op) { visitBinOp(op, "srl"); }
727 void visitComb(comb::ShrSOp op) { visitBinOp(op, "sra"); }
728 void visitComb(comb::AndOp op) { visitVariadicOp(op, "and"); }
729 void visitComb(comb::OrOp op) { visitVariadicOp(op, "or"); }
730 void visitComb(comb::XorOp op) { visitVariadicOp(op, "xor"); }
731 void visitComb(comb::ConcatOp op) { visitBinOp(op, "concat"); }
732
733 // Extract ops translate to a slice operation in btor2 in a one-to-one
734 // manner
735 void visitComb(comb::ExtractOp op) {
736 int64_t w = requireSort(op.getType());
737
738 // Start by extracting the necessary information for the emission (i.e.
739 // operand, low bit, ...)
740 Value op0 = op.getOperand();
741 size_t lb = op.getLowBit();
742
743 // Generate the slice instruction
744 genSlice(op, op0, lb, w);
745 }
746
747 // Btor2 uses similar syntax as hw for its comparisons
748 // So we simply need to emit the cmpop name and check for corner cases
749 // where the namings differ.
750 void visitComb(comb::ICmpOp op) {
751 Value lhs = op.getOperand(0);
752 Value rhs = op.getOperand(1);
753
754 // Extract the predicate name (assuming that its a valid btor2
755 // predicate)
756 StringRef pred = stringifyICmpPredicate(op.getPredicate());
757
758 // Check for special cases where hw doesn't align with btor syntax
759 if (pred == "ne")
760 pred = "neq";
761 else if (pred == "ule")
762 pred = "ulte";
763 else if (pred == "sle")
764 pred = "slte";
765 else if (pred == "uge")
766 pred = "ugte";
767 else if (pred == "sge")
768 pred = "sgte";
769
770 // Width of result is always 1 for comparison
771 genSort("bitvec", 1);
772
773 // With the special cases out of the way, the emission is the same as that
774 // of a binary op
775 genBinOp(pred, op, lhs, rhs, 1);
776 }
777
778 // Muxes generally convert to an ite statement
779 void visitComb(comb::MuxOp op) {
780 // Extract predicate, true and false values
781 Value pred = op.getCond();
782 Value tval = op.getTrueValue();
783 Value fval = op.getFalseValue();
784
785 // We assume that both tval and fval have the same width
786 // This width should be the same as the output width
787 int64_t w = requireSort(op.getType());
788
789 // Generate the ite instruction
790 genIte(op, pred, tval, fval, w);
791 }
792
793 void visitComb(Operation *op) { visitInvalidComb(op); }
794
795 // Try sv ops when comb is done
796 void visitInvalidComb(Operation *op) { dispatchSVVisitor(op); }
797
798 // Assertions are negated then converted to a btor2 bad instruction
799 void visitSV(sv::AssertOp op) {
800 // Expression is what we will try to invert for our assertion
801 Value expr = op.getExpression();
802
803 // This sort is for assertion inversion and potential implies
804 genSort("bitvec", 1);
805
806 // Check for an overaching enable
807 // In our case the sv.if operation will probably only be used when
808 // conditioning an sv.assert on an enable signal. This means that
809 // its condition is probably used to imply our assertion
810 if (auto ifop = dyn_cast<sv::IfOp>(((Operation *)op)->getParentOp())) {
811 Value en = ifop.getOperand();
812
813 // Generate the implication
814 genImplies(ifop, en, expr);
815
816 // Generate the implies inversion
817 genUnaryOp(op, ifop, "not", 1);
818 } else {
819 // Generate the expression inversion
820 genUnaryOp(op, expr, "not", 1);
821 }
822
823 // Generate the bad btor2 instruction
824 genBad(op);
825 }
826 // Assumptions are converted to a btor2 constraint instruction
827 void visitSV(sv::AssumeOp op) {
828 // Extract the expression that we want our constraint to be about
829 Value expr = op.getExpression();
830 genConstraint(expr);
831 }
832
833 void visitSV(Operation *op) { visitInvalidSV(op); }
834
835 // Once SV Ops are visited, we need to check for seq ops
836 void visitInvalidSV(Operation *op) { dispatchVerifVisitor(op); }
837
838 template <typename Op>
839 void visitAssertLike(Op op) {
840 // Expression is what we will try to invert for our assertion
841 Value prop = op.getProperty();
842 Value en = op.getEnable();
843
844 // This sort is for assertion inversion and potential implies
845 genSort("bitvec", 1);
846
847 size_t assertLID = noLID;
848 // Check for a related enable signal
849 if (en) {
850 // Generate the implication
851 genImplies(op, en, prop);
852
853 // Generate the implies inversion
854 assertLID = genUnaryOp(op, "not", 1);
855 } else {
856 // Generate the expression inversion
857 assertLID = genUnaryOp(prop.getDefiningOp(), "not", 1);
858 }
859
860 // Generate the bad btor2 instruction
861 genBad(assertLID);
862 }
863
864 template <typename Op>
865 void visitAssumeLike(Op op) {
866 // Expression is what we will try to invert for our assertion
867 Value prop = op.getProperty();
868 Value en = op.getEnable();
869
870 size_t assumeLID = getOpLID(prop);
871 // Check for a related enable signal
872 if (en) {
873 // This sort is for assertion inversion and potential implies
874 genSort("bitvec", 1);
875 // Generate the implication
876 assumeLID = genImplies(op, en, prop);
877 }
878
879 // Generate the bad btor2 instruction
880 genConstraint(assumeLID);
881 }
882
883 // Folds the enable signal into the property and converts the result into a
884 // bad instruction.
885 void visitVerif(verif::AssertOp op) { visitAssertLike(op); }
886 void visitVerif(verif::ClockedAssertOp op) { visitAssertLike(op); }
887
888 // Fold the enable into the property and convert the assumption into a
889 // constraint instruction.
890 void visitVerif(verif::AssumeOp op) { visitAssumeLike(op); }
891 void visitVerif(verif::ClockedAssumeOp op) { visitAssumeLike(op); }
892
893 void visitUnhandledVerif(Operation *op) {
894 op->emitError("not supported in btor2!");
895 return signalPassFailure();
896 }
897
898 void visitInvalidVerif(Operation *op) { visit(op); }
899
900 // Seq operation visitor, that dispatches to other seq ops
901 // Also handles all remaining operations that should be explicitly ignored
902 void visit(Operation *op) {
903 // Typeswitch is used here because other seq types will be supported
904 // like all operations relating to memories and CompRegs
905 TypeSwitch<Operation *, void>(op)
906 .Case<seq::FirRegOp, seq::CompRegOp>([&](auto expr) { visit(expr); })
907 .Default([&](auto expr) { visitUnsupportedOp(op); });
908 }
909
910 // Firrtl registers generate a state instruction
911 // The final update is also used to generate a set of next btor
912 // instructions
913 void visit(seq::FirRegOp reg) {
914 // Start by retrieving the register's name and width
915 StringRef regName = reg.getName();
916 int64_t w = requireSort(reg.getType());
917
918 // Generate state instruction (represents the register declaration)
919 genState(reg, w, regName);
920
921 // Record the operation for future `next` instruction generation
922 // This is required to model transitions between states (i.e. how a
923 // register's value evolves over time)
924 regOps.push_back(reg);
925 }
926
927 // Compregs behave in a similar way as firregs for btor2 emission
928 void visit(seq::CompRegOp reg) {
929 // Start by retrieving the register's name and width
930 StringRef regName = reg.getName().value();
931 int64_t w = requireSort(reg.getType());
932
933 // Check for initial values which must be emitted before the state in
934 // btor2
935 auto init = reg.getInitialValue();
936
937 // If there's an initial value, we need to generate a constant for the
938 // initial value, then declare the state, then generate the init statement
939 // (BTOR2 parsers are picky about it being in this order)
940 if (init) {
941 if (!init.getDefiningOp<seq::InitialOp>()) {
942 reg->emitError(
943 "Initial value must be emitted directly by a seq.initial op");
944 return;
945 }
946 // Check that the initial value is a non-null constant
947 auto initialConstant = circt::seq::unwrapImmutableValue(init)
948 .getDefiningOp<hw::ConstantOp>();
949 if (!initialConstant)
950 reg->emitError("initial value must be constant");
951
952 // Visit the initial Value to generate the constant
953 dispatchTypeOpVisitor(initialConstant);
954
955 // Add it to the list of visited operations
956 handledOps.insert(initialConstant);
957
958 // Now we can declare the state
959 genState(reg, w, regName);
960
961 // Finally generate the init statement
962 genInit(reg, initialConstant, w);
963 } else {
964 // Just generate state instruction (represents the register declaration)
965 genState(reg, w, regName);
966 }
967
968 // Record the operation for future `next` instruction generation
969 // This is required to model transitions between states (i.e. how a
970 // register's value evolves over time)
971 regOps.push_back(reg);
972 }
973
974 // Ignore all other explicitly mentionned operations
975 // ** Purposefully left empty **
976 void ignore(Operation *op) {}
977
978 // Tail method that handles all operations that weren't handled by previous
979 // visitors. Here we simply make the pass fail or ignore the op
980 void visitUnsupportedOp(Operation *op) {
981 // Check for explicitly ignored ops vs unsupported ops (which cause a
982 // failure)
983 TypeSwitch<Operation *, void>(op)
984 // All explicitly ignored operations are defined here
985 .Case<sv::MacroDefOp, sv::MacroDeclOp, sv::VerbatimOp,
986 sv::VerbatimExprOp, sv::VerbatimExprSEOp, sv::IfOp, sv::IfDefOp,
987 sv::IfDefProceduralOp, sv::AlwaysOp, sv::AlwaysCombOp,
988 seq::InitialOp, sv::AlwaysFFOp, seq::FromClockOp, seq::InitialOp,
989 seq::YieldOp, hw::OutputOp, hw::HWModuleOp>(
990 [&](auto expr) { ignore(op); })
991
992 // Make sure that the design only contains one clock
993 .Case<seq::FromClockOp>([&](auto expr) {
994 if (++nclocks > 1UL) {
995 op->emitOpError("Mutli-clock designs are not supported!");
996 return signalPassFailure();
997 }
998 })
999
1000 // Anything else is considered unsupported and might cause a wrong
1001 // behavior if ignored, so an error is thrown
1002 .Default([&](auto expr) {
1003 op->emitOpError("is an unsupported operation");
1004 return signalPassFailure();
1005 });
1006 }
1007};
1008} // end anonymous namespace
1009
1010void ConvertHWToBTOR2Pass::runOnOperation() {
1011 // Btor2 does not have the concept of modules or module
1012 // hierarchies, so we assume that no nested modules exist at this point.
1013 // This greatly simplifies translation.
1014 getOperation().walk([&](hw::HWModuleOp module) {
1015 // Start by extracting the inputs and generating appropriate instructions
1016 for (auto &port : module.getPortList()) {
1017 visit(port);
1018 }
1019
1020 // Previsit all registers in the module in order to avoid dependency cycles
1021 module.walk([&](Operation *op) {
1022 TypeSwitch<Operation *, void>(op)
1023 .Case<seq::FirRegOp, seq::CompRegOp>([&](auto reg) {
1024 visit(reg);
1025 handledOps.insert(op);
1026 })
1027 .Default([&](auto expr) {});
1028 });
1029
1030 // Visit all of the operations in our module
1031 module.walk([&](Operation *op) {
1032 // Check: instances are not (yet) supported
1033 if (isa<hw::InstanceOp>(op)) {
1034 op->emitOpError("not supported in BTOR2 conversion");
1035 return;
1036 }
1037
1038 // Don't process ops that have already been emitted
1039 if (handledOps.contains(op))
1040 return;
1041
1042 // Fill in our worklist
1043 worklist.insert({op, op->operand_begin()});
1044
1045 // Process the elements in our worklist
1046 while (!worklist.empty()) {
1047 auto &[op, operandIt] = worklist.back();
1048 if (operandIt == op->operand_end()) {
1049 // All of the operands have been emitted, it is safe to emit our op
1050 dispatchTypeOpVisitor(op);
1051
1052 // Record that our op has been emitted
1053 handledOps.insert(op);
1054 worklist.pop_back();
1055 continue;
1056 }
1057
1058 // Send the operands of our op to the worklist in case they are still
1059 // un-emitted
1060 Value operand = *(operandIt++);
1061 auto *defOp = operand.getDefiningOp();
1062
1063 // Make sure that we don't emit the same operand twice
1064 if (!defOp || handledOps.contains(defOp))
1065 continue;
1066
1067 // This is triggered if our operand is already in the worklist and
1068 // wasn't handled
1069 if (!worklist.insert({defOp, defOp->operand_begin()}).second) {
1070 defOp->emitError("dependency cycle");
1071 return;
1072 }
1073 }
1074 });
1075
1076 // Iterate through the registers and generate the `next` instructions
1077 for (size_t i = 0; i < regOps.size(); ++i) {
1078 finalizeRegVisit(regOps[i]);
1079 }
1080 });
1081 // Clear data structures to allow for pass reuse
1082 sortToLIDMap.clear();
1083 constToLIDMap.clear();
1084 opLIDMap.clear();
1085 inputLIDs.clear();
1086 regOps.clear();
1087 handledOps.clear();
1088 worklist.clear();
1089}
1090
1091// Constructor with a custom ostream
1092std::unique_ptr<mlir::Pass>
1094 return std::make_unique<ConvertHWToBTOR2Pass>(os);
1095}
1096
1097// Basic default constructor
1098std::unique_ptr<mlir::Pass> circt::createConvertHWToBTOR2Pass() {
1099 return std::make_unique<ConvertHWToBTOR2Pass>(llvm::outs());
1100}
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.