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