diff --git a/include/circt/Support/FVInt.h b/include/circt/Support/FVInt.h new file mode 100644 index 000000000000..e657f65f10d2 --- /dev/null +++ b/include/circt/Support/FVInt.h @@ -0,0 +1,592 @@ +//===- FVInt.h - Four-valued integer ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements a class to represent arbitrary precision integers where +// each bit can be one of four values. This corresponds to SystemVerilog's +// four-valued `logic` type (originally defined in IEEE 1364, later merged into +// IEEE 1800). +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_FVINT_H +#define CIRCT_SUPPORT_FVINT_H + +#include "circt/Support/LLVM.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace circt { + +/// Four-valued arbitrary precision integers. +/// +/// Each bit of the integer can be 0, 1, X, or Z. Internally the bits are stored +/// in a pair of `APInt`s, one of which specifies the value of each bit (0/X or +/// 1/Z), and the other whether the bit is unknown (X or Z). +class FVInt { +public: + /// Construct an `FVInt` from a 64-bit value. The result has no X or Z bits. + FVInt(unsigned numBits, uint64_t value, bool isSigned = false) + : FVInt(APInt(numBits, value, isSigned)) {} + + /// Construct an `FVInt` from an `APInt`. The result has no X or Z bits. + FVInt(const APInt &value) + : value(value), unknown(APInt::getZero(value.getBitWidth())) {} + + /// Construct an `FVInt` from two `APInt`s used internally to store the bit + /// data. The first argument specifies whether each bit is 0/X or 1/Z. The + /// second argument specifies whether each bit is 0/1 or X/Z. Both `APInt`s + /// must have the same bitwidth. The two arguments correspond to the results + /// of `getRawValue()` and `getRawUnknown()`. + FVInt(APInt &&rawValue, APInt &&rawUnknown) + : value(rawValue), unknown(rawUnknown) { + assert(rawValue.getBitWidth() == rawUnknown.getBitWidth()); + } + + /// Construct an `FVInt` with all bits set to 0. + static FVInt getZero(unsigned numBits) { + return FVInt(APInt::getZero(numBits)); + } + + /// Construct an `FVInt` with all bits set to 1. + static FVInt getAllOnes(unsigned numBits) { + return FVInt(APInt::getAllOnes(numBits)); + } + + /// Construct an `FVInt` with all bits set to X. + static FVInt getAllX(unsigned numBits) { + return FVInt(APInt::getZero(numBits), APInt::getAllOnes(numBits)); + } + + /// Construct an `FVInt` with all bits set to Z. + static FVInt getAllZ(unsigned numBits) { + return FVInt(APInt::getAllOnes(numBits), APInt::getAllOnes(numBits)); + } + + /// Return the number of bits this integer has. + unsigned getBitWidth() const { return value.getBitWidth(); } + + /// Return the underlying `APInt` used to store whether a bit is 0/X or 1/Z. + const APInt &getRawValue() const { return value; } + + /// Return the underlying `APInt` used to store whether a bit is unknown (X or + /// Z). + const APInt &getRawUnknown() const { return unknown; } + + /// Convert the four-valued `FVInt` to a two-valued `APInt` by mapping X and Z + /// bits to either 0 or 1. + APInt toAPInt(bool unknownBitMapping) const { + auto v = value; + if (unknownBitMapping) + v |= unknown; // set unknown bits to 1 + else + v &= ~unknown; // set unknown bits to 0 + return v; + } + + //===--------------------------------------------------------------------===// + // Resizing + //===--------------------------------------------------------------------===// + + /// Zero-extend the integer to a new bit width. The additional high-order bits + /// are filled in with zero. + FVInt zext(unsigned bitWidth) const { + return FVInt(value.zext(bitWidth), unknown.zext(bitWidth)); + } + + /// Sign-extend the integer to a new bit width. The additional high-order bits + /// are filled in with the sign bit (top-most bit) of the original number, + /// also when that sign bit is X or Z. Zero-width integers are extended with + /// zeros. + FVInt sext(unsigned bitWidth) const { + return FVInt(value.sext(bitWidth), unknown.sext(bitWidth)); + } + + //===--------------------------------------------------------------------===// + // Value Tests + //===--------------------------------------------------------------------===// + + /// Determine if any bits are X or Z. + bool hasUnknown() const { return !unknown.isZero(); } + + /// Determine if all bits are 0. This is true for zero-width values. + bool isZero() const { return value.isZero() && unknown.isZero(); } + + /// Determine if all bits are 1. This is true for zero-width values. + bool isAllOnes() const { return value.isAllOnes() && unknown.isZero(); } + + /// Determine if all bits are X. This is true for zero-width values. + bool isAllX() const { return value.isZero() && unknown.isAllOnes(); } + + /// Determine if all bits are Z. This is true for zero-width values. + bool isAllZ() const { return value.isAllOnes() && unknown.isAllOnes(); } + + //===--------------------------------------------------------------------===// + // Bit Manipulation + //===--------------------------------------------------------------------===// + + /// The value of an individual bit. Can be 0, 1, X, or Z. + enum Bit { V0 = 0b00, V1 = 0b01, X = 0b10, Z = 0b11 }; + + /// Get the value of an individual bit. + Bit getBit(unsigned index) const { + return static_cast(value[index] | unknown[index] << 1); + } + + /// Set the value of an individual bit. + void setBit(unsigned index, Bit bit) { + value.setBitVal(index, (bit >> 0) & 1); + unknown.setBitVal(index, (bit >> 1) & 1); + } + + /// Compute a mask of all the 0 bits in this integer. + APInt getZeroBits() const { return ~value & ~unknown; } + + /// Compute a mask of all the 1 bits in this integer. + APInt getOneBits() const { return value & ~unknown; } + + /// Compute a mask of all the X bits in this integer. + APInt getXBits() const { return ~value & unknown; } + + /// Compute a mask of all the Z bits in this integer. + APInt getZBits() const { return value & unknown; } + + /// Set the value of all bits in the mask to 0. + template + void setZeroBits(const T &mask) { + value &= ~mask; + unknown &= ~mask; + } + + /// Set the value of all bits in the mask to 1. + template + void setOneBits(const T &mask) { + value |= mask; + unknown &= ~mask; + } + + /// Set the value of all bits in the mask to X. + template + void setXBits(const T &mask) { + value &= ~mask; + unknown |= mask; + } + + /// Set the value of all bits in the mask to Z. + template + void setZBits(const T &mask) { + value |= mask; + unknown |= mask; + } + + /// Set all bits to 0. + void setAllZero() { + value.clearAllBits(); + unknown.clearAllBits(); + } + + /// Set all bits to 1. + void setAllOne() { + value.setAllBits(); + unknown.clearAllBits(); + } + + /// Set all bits to X. + void setAllX() { + value.clearAllBits(); + unknown.setAllBits(); + } + + /// Set all bits to Z. + void setAllZ() { + value.setAllBits(); + unknown.setAllBits(); + } + + /// Replace all Z bits with X. This is useful since most logic operations will + /// treat X and Z bits the same way and produce an X bit in the output. By + /// mapping Z bits to X, these operations can then just handle 0, 1, and X + /// bits. + void replaceZWithX() { + // Z bits have value and unknown set to 1. X bits have value set to 0 and + // unknown set to 1. To convert between the two, make sure that value is 0 + // wherever unknown is 1. + value &= ~unknown; + } + + /// If any bits are X or Z, set the entire integer to X. + void setAllXIfAnyUnknown() { + if (hasUnknown()) + setAllX(); + } + + /// If any bits in this integer or another integer are X or Z, set the entire + /// integer to X. This is useful for binary operators which want to set their + /// result to X if either of the two inputs contained an X or Z bit. + void setAllXIfAnyUnknown(const FVInt &other) { + if (hasUnknown() || other.hasUnknown()) + setAllX(); + } + + //===--------------------------------------------------------------------===// + // Shift Operators + //===--------------------------------------------------------------------===// + + /// Perform a logical left-shift. If any bits in the shift amount are unknown, + /// the entire result is X. + FVInt &operator<<=(const FVInt &amount) { + if (amount.hasUnknown()) { + setAllX(); + } else { + value <<= amount.value; + unknown <<= amount.value; + } + return *this; + } + + /// Perform a logical left-shift by a two-valued amount. + template + FVInt &operator<<=(const T &amount) { + value <<= amount; + unknown <<= amount; + return *this; + } + + //===--------------------------------------------------------------------===// + // Logic Operators + //===--------------------------------------------------------------------===// + + /// Compute the logical NOT of this integer. This implements the following + /// bit-wise truth table: + /// ``` + /// 0 | 1 + /// 1 | 0 + /// X | X + /// Z | X + /// ``` + void flipAllBits() { + value = ~value; + replaceZWithX(); + } + + /// Compute the logical NOT. + FVInt operator~() const { + auto v = *this; + v.flipAllBits(); + return v; + } + + /// Compute the logical AND of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 0 0 0 + /// 1 | 0 1 X X + /// X | 0 X X X + /// Z | 0 X X X + /// ``` + FVInt &operator&=(const FVInt &other) { + auto zeros = getZeroBits() | other.getZeroBits(); + value &= other.value; + unknown |= other.unknown; + unknown &= ~zeros; + replaceZWithX(); + return *this; + } + + /// Compute the logical AND of this integer and a two-valued integer. + template + FVInt &operator&=(T other) { + value &= other; + unknown &= other; // make 0 bits known + replaceZWithX(); + return *this; + } + + /// Compute the logical AND. + template + FVInt operator&(const T &other) const { + auto v = *this; + v &= other; + return v; + } + + /// Compute the logical OR of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 1 X X + /// 1 | 1 1 1 1 + /// X | X 1 X X + /// Z | X 1 X X + /// ``` + FVInt &operator|=(const FVInt &other) { + auto ones = getOneBits() | other.getOneBits(); + value |= other.value; + unknown |= other.unknown; + unknown &= ~ones; + replaceZWithX(); + return *this; + } + + /// Compute the logical OR of this integer and a two-valued integer. + template + FVInt &operator|=(T other) { + value |= other; + unknown &= ~other; // make 1 bits known + replaceZWithX(); + return *this; + } + + /// Compute the logical OR. + template + FVInt operator|(const T &other) const { + auto v = *this; + v |= other; + return v; + } + + /// Compute the logical XOR of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 1 X X + /// 1 | 1 0 X X + /// X | X X X X + /// Z | X X X X + /// ``` + FVInt &operator^=(const FVInt &other) { + value ^= other.value; + unknown |= other.unknown; + replaceZWithX(); + return *this; + } + + /// Compute the logical XOR of this integer and a two-valued integer. + template + FVInt &operator^=(const T &other) { + value ^= other; + replaceZWithX(); + return *this; + } + + /// Compute the logical XOR. + template + FVInt operator^(const T &other) const { + auto v = *this; + v ^= other; + return v; + } + + //===--------------------------------------------------------------------===// + // Arithmetic Operators + //===--------------------------------------------------------------------===// + + /// Compute the negation of this integer. If any bits are unknown, the entire + /// result is X. + void negate() { + value.negate(); + setAllXIfAnyUnknown(); + } + + /// Compute the negation of this integer. + FVInt operator-() const { + auto v = *this; + v.negate(); + return v; + } + + /// Compute the addition of this integer and another. If any bits in either + /// integer are unknown, the entire result is X. + FVInt &operator+=(const FVInt &other) { + value += other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the addition of this integer and a two-valued integer. If any bit + /// in the integer is unknown, the entire result is X. + template + FVInt &operator+=(const T &other) { + value += other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute an addition. + template + FVInt operator+(const T &other) const { + auto v = *this; + v += other; + return v; + } + + /// Compute the subtraction of this integer and another. If any bits in either + /// integer are unknown, the entire result is X. + FVInt &operator-=(const FVInt &other) { + value -= other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the subtraction of this integer and a two-valued integer. If any + /// bit in the integer is unknown, the entire result is X. + template + FVInt &operator-=(const T &other) { + value -= other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute an subtraction. + template + FVInt operator-(const T &other) const { + auto v = *this; + v -= other; + return v; + } + + /// Compute the multiplication of this integer and another. If any bits in + /// either integer are unknown, the entire result is X. + FVInt &operator*=(const FVInt &other) { + value *= other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the multiplication of this integer and a two-valued integer. If + /// any bit in the integer is unknown, the entire result is X. + template + FVInt &operator*=(const T &other) { + value *= other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute a multiplication. + template + FVInt operator*(const T &other) const { + auto v = *this; + v *= other; + return v; + } + + //===--------------------------------------------------------------------===// + // Comparison + //===--------------------------------------------------------------------===// + + /// Determine whether this integer is equal to another. Note that this + /// corresponds to SystemVerilog's `===` operator. + bool operator==(const FVInt &other) const { + return value == other.value && unknown == other.unknown; + } + + /// Determine whether this integer is equal to a two-valued integer. Note that + /// this corresponds to SystemVerilog's `===` operator. + template + bool operator==(const T &other) const { + return value == other && !hasUnknown(); + } + + /// Determine whether this integer is not equal to another. + bool operator!=(const FVInt &other) const { return !((*this) == other); } + + /// Determine whether this integer is not equal to a two-valued integer. + template + bool operator!=(const T &other) const { + return !((*this) == other); + } + + //===--------------------------------------------------------------------===// + // String Conversion + //===--------------------------------------------------------------------===// + + /// Convert a string into an `FVInt`. + /// + /// The radix can be 2, 8, 10, or 16. For radix 2, the input string may + /// contain the characters `x` or `X` to indicate an unknown X bit, and `z` or + /// `Z` to indicate an unknown Z bit. For radix 8, each X or Z counts as 3 + /// bits. For radix 16, each X and Z counts as 4 bits. When radix is 10 the + /// input cannot contain any X or Z. + /// + /// Returns the parsed integer if the string is non-empty and a well-formed + /// number, otherwise returns none. + static std::optional tryFromString(StringRef str, unsigned radix = 10); + + /// Convert a string into an `FVInt`. Same as `tryFromString`, but aborts if + /// the string is malformed. + static FVInt fromString(StringRef str, unsigned radix = 10) { + auto v = tryFromString(str, radix); + assert(v.has_value() && "string is not a well-formed FVInt"); + return *v; + } + + /// Convert an `FVInt` to a string. + /// + /// The radix can be 2, 8, 10, or 16. For radix 8 or 16, the integer can only + /// contain unknown bits in groups of 3 or 4, respectively, such that a `X` or + /// `Z` can be printed for the entire group of bits. For radix 10, the integer + /// cannot contain any unknown bits. In case the output contains letters, + /// `uppercase` specifies whether they are printed as uppercase letters. + /// + /// Appends the output characters to `str` and returns true if the integer + /// could be printed with the given configuration. Otherwise returns false and + /// leaves `str` in its original state. Always succeeds for radix 2. + bool tryToString(SmallVectorImpl &str, unsigned radix = 10, + bool uppercase = true) const; + + /// Convert an `FVInt` to a string. Same as `tryToString`, but directly + /// returns the string and aborts if the conversion is unsuccessful. + SmallString<16> toString(unsigned radix = 10, bool uppercase = true) const { + SmallString<16> str; + bool success = tryToString(str, radix, uppercase); + assert(success && "radix cannot represent FVInt"); + return str; + } + + /// Print an `FVInt` to an output stream. + void print(raw_ostream &os) const; + +private: + APInt value; + APInt unknown; +}; + +inline FVInt operator&(uint64_t a, const FVInt &b) { return b & a; } +inline FVInt operator|(uint64_t a, const FVInt &b) { return b | a; } +inline FVInt operator^(uint64_t a, const FVInt &b) { return b ^ a; } +inline FVInt operator+(uint64_t a, const FVInt &b) { return b + a; } +inline FVInt operator*(uint64_t a, const FVInt &b) { return b * a; } + +inline FVInt operator&(const APInt &a, const FVInt &b) { return b & a; } +inline FVInt operator|(const APInt &a, const FVInt &b) { return b | a; } +inline FVInt operator^(const APInt &a, const FVInt &b) { return b ^ a; } +inline FVInt operator+(const APInt &a, const FVInt &b) { return b + a; } +inline FVInt operator*(const APInt &a, const FVInt &b) { return b * a; } + +inline FVInt operator-(uint64_t a, const FVInt &b) { + return FVInt(b.getBitWidth(), a) - b; +} + +inline FVInt operator-(const APInt &a, const FVInt &b) { return FVInt(a) - b; } + +inline bool operator==(uint64_t a, const FVInt &b) { return b == a; } +inline bool operator!=(uint64_t a, const FVInt &b) { return b != a; } + +inline raw_ostream &operator<<(raw_ostream &os, const FVInt &value) { + value.print(os); + return os; +} + +} // namespace circt + +#endif // CIRCT_SUPPORT_FVINT_H diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 08b9d250ad35..1a38545425ab 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -11,6 +11,7 @@ add_circt_library(CIRCTSupport CustomDirectiveImpl.cpp Debug.cpp FieldRef.cpp + FVInt.cpp JSON.cpp LoweringOptions.cpp Naming.cpp @@ -33,13 +34,15 @@ add_circt_library(CIRCTSupport #------------------------------------------------------------------------------- # Generate Version.cpp #------------------------------------------------------------------------------- + find_first_existing_vc_file("${CIRCT_SOURCE_DIR}" CIRCT_GIT_LOGS_HEAD) set(GEN_VERSION_SCRIPT "${CIRCT_SOURCE_DIR}/cmake/modules/GenVersionFile.cmake") if (CIRCT_RELEASE_TAG_ENABLED) add_custom_command(OUTPUT "${VERSION_CPP}" DEPENDS "${CIRCT_GIT_LOGS_HEAD}" "${GEN_VERSION_SCRIPT}" - COMMAND ${CMAKE_COMMAND} -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" -DOUT_FILE="${VERSION_CPP}" -DRELEASE_PATTERN=${CIRCT_RELEASE_TAG}* -DDRY_RUN=OFF -DSOURCE_ROOT="${CIRCT_SOURCE_DIR}" -P "${GEN_VERSION_SCRIPT}") @@ -48,7 +51,8 @@ else () # cmake configuration. add_custom_command(OUTPUT "${VERSION_CPP}" DEPENDS "${GEN_VERSION_SCRIPT}" - COMMAND ${CMAKE_COMMAND} -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" -DOUT_FILE="${VERSION_CPP}" -DDRY_RUN=ON -DSOURCE_ROOT="${CIRCT_SOURCE_DIR}" -P "${GEN_VERSION_SCRIPT}") endif() diff --git a/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp new file mode 100644 index 000000000000..e70323cb8936 --- /dev/null +++ b/lib/Support/FVInt.cpp @@ -0,0 +1,139 @@ +//===- FVInt.cpp - Four-valued integer --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Support/FVInt.h" +#include "llvm/ADT/StringExtras.h" + +#define DEBUG_TYPE "fvint" + +using namespace circt; + +std::optional FVInt::tryFromString(StringRef str, unsigned radix) { + assert(radix == 2 || radix == 8 || radix == 10 || radix == 16); + if (str.empty()) + return {}; + + // Overestimate the number of bits that will be needed to hold all digits. + unsigned radixLog2 = 0; + for (unsigned r = radix - 1; r > 0; r >>= 1) + ++radixLog2; + bool radixIsPow2 = (radix == (1U << radixLog2)); + + // Parse the string. + auto result = FVInt::getZero(str.size() * radixLog2); + while (!str.empty()) { + unsigned digit = llvm::toLower(str[0]); + str = str.drop_front(); + + // Handle X and Z digits. + if (digit == 'x' || digit == 'z') { + if (!radixIsPow2) + return {}; + result <<= radixLog2; + result.unknown.setLowBits(radixLog2); + if (digit == 'z') + result.value.setLowBits(radixLog2); + continue; + } + + // Determine the value of the current digit. + if (digit >= '0' && digit <= '9') + digit = digit - '0'; + else if (digit >= 'a' && digit <= 'z') + digit = digit - 'a' + 10; + else + return {}; + if (digit >= radix) + return {}; + + // Add the digit to the result. + if (radixIsPow2) { + result <<= radixLog2; + result.value |= digit; + } else { + result.value *= radix; + result.value += digit; + } + } + + return result; +} + +bool FVInt::tryToString(SmallVectorImpl &str, unsigned radix, + bool uppercase) const { + size_t strBaseLen = str.size(); + assert(radix == 2 || radix == 8 || radix == 10 || radix == 16); + + // Determine if the radix is a power of two. + unsigned radixLog2 = 0; + for (unsigned r = radix - 1; r > 0; r >>= 1) + ++radixLog2; + bool radixIsPow2 = (radix == (1U << radixLog2)); + unsigned radixMask = (1U << radixLog2) - 1; + + // If the number has no X or Z bits, take the easy route and print the `APInt` + // directly. + if (!hasUnknown()) { + value.toString(str, radix, /*Signed=*/false, /*formatAsCLiteral=*/false, + uppercase); + return true; + } + + // We can only print with non-power-of-two radices if there are no X or Z + // bits. So at this point we require radix be a power of two. + if (!radixIsPow2) + return false; + + // Otherwise chop off digits at the bottom and print them to the string. This + // prints the digits in reverse order, with the least significant digit as the + // first character. + APInt value = this->value; + APInt unknown = this->unknown; + + char chrA = uppercase ? 'A' : 'a'; + char chrX = uppercase ? 'X' : 'x'; + char chrZ = uppercase ? 'Z' : 'z'; + + while (!value.isZero() || !unknown.isZero()) { + unsigned digitValue = value.getRawData()[0] & radixMask; + unsigned digitUnknown = unknown.getRawData()[0] & radixMask; + value.lshrInPlace(radixLog2); + unknown.lshrInPlace(radixLog2); + + // Handle unknown bits. Since we only get to print a single X or Z character + // to the string, either all bits in the digit have to be X, or all have to + // be Z. But we cannot represent the case where X, Z and 0/1 bits are mixed. + if (digitUnknown != 0) { + if (digitUnknown != radixMask || + (digitValue != 0 && digitValue != radixMask)) { + str.resize(strBaseLen); + return false; + } + str.push_back(digitValue == 0 ? chrX : chrZ); + continue; + } + + // Handle known bits. + if (digitValue < 10) + str.push_back(digitValue + '0'); + else + str.push_back(digitValue - 10 + chrA); + } + + // Reverse the digits. + std::reverse(str.begin() + strBaseLen, str.end()); + return true; +} + +void FVInt::print(raw_ostream &os) const { + SmallString<32> buffer; + if (!tryToString(buffer)) + if (!tryToString(buffer, 16)) + tryToString(buffer, 2); + os << buffer; +} diff --git a/unittests/Support/CMakeLists.txt b/unittests/Support/CMakeLists.txt index 48431537442c..005e2389e396 100644 --- a/unittests/Support/CMakeLists.txt +++ b/unittests/Support/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_unittest(CIRCTSupportTests + FVIntTest.cpp JSONTest.cpp PrettyPrinterTest.cpp ) diff --git a/unittests/Support/FVIntTest.cpp b/unittests/Support/FVIntTest.cpp new file mode 100644 index 000000000000..c61311797418 --- /dev/null +++ b/unittests/Support/FVIntTest.cpp @@ -0,0 +1,134 @@ +//===- FVIntTest.cpp - Four-valued integer unit tests ===------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Unit tests for the `FVInt` class. +// +//===----------------------------------------------------------------------===// + +#include "circt/Support/FVInt.h" +#include "gtest/gtest.h" + +using namespace circt; + +namespace { + +TEST(FVIntTest, Resizing) { + ASSERT_EQ(FVInt::fromString("01", 2).zext(5), FVInt::fromString("00001", 2)); + ASSERT_EQ(FVInt::fromString("01", 2).sext(5), FVInt::fromString("00001", 2)); + ASSERT_EQ(FVInt::fromString("10", 2).zext(5), FVInt::fromString("00010", 2)); + ASSERT_EQ(FVInt::fromString("10", 2).sext(5), FVInt::fromString("11110", 2)); + ASSERT_EQ(FVInt::fromString("X1", 2).zext(5), FVInt::fromString("000X1", 2)); + ASSERT_EQ(FVInt::fromString("X1", 2).sext(5), FVInt::fromString("XXXX1", 2)); + ASSERT_EQ(FVInt::fromString("Z1", 2).zext(5), FVInt::fromString("000Z1", 2)); + ASSERT_EQ(FVInt::fromString("Z1", 2).sext(5), FVInt::fromString("ZZZZ1", 2)); +} + +TEST(FVIntTest, Basics) { + ASSERT_TRUE(FVInt::getZero(42).isZero()); + ASSERT_TRUE(FVInt::getAllOnes(42).isAllOnes()); + ASSERT_TRUE(FVInt::getAllX(42).isAllX()); + ASSERT_TRUE(FVInt::getAllZ(42).isAllZ()); + + ASSERT_FALSE(FVInt::getZero(42).hasUnknown()); + ASSERT_FALSE(FVInt::getAllOnes(42).hasUnknown()); + ASSERT_TRUE(FVInt::getAllX(42).hasUnknown()); + ASSERT_TRUE(FVInt::getAllZ(42).hasUnknown()); + + auto x = FVInt::fromString("01XZ", 2); + ASSERT_EQ(x.toAPInt(false), 0b0100); + ASSERT_EQ(x.toAPInt(true), 0b0111); + ASSERT_EQ(x.getBit(0), FVInt::Z); + ASSERT_EQ(x.getBit(1), FVInt::X); + ASSERT_EQ(x.getBit(2), FVInt::V1); + ASSERT_EQ(x.getBit(3), FVInt::V0); + ASSERT_EQ(FVInt::V1, 1); + ASSERT_EQ(FVInt::V0, 0); + + ASSERT_EQ(FVInt(32, 9001), FVInt(32, 9001)); + ASSERT_EQ(FVInt(32, 9001), 9001); + ASSERT_EQ(9001, FVInt(32, 9001)); + + ASSERT_NE(FVInt(32, 9001), FVInt(32, 1337)); + ASSERT_NE(FVInt(32, 9001), 1337); + ASSERT_NE(9001, FVInt(32, 1337)); +} + +TEST(FVIntTest, StringConversion) { + auto v = FVInt::fromString("ZX1001XZ", 2); + ASSERT_EQ(v.getZeroBits(), 0b00011000); + ASSERT_EQ(v.getOneBits(), 0b00100100); + ASSERT_EQ(v.getXBits(), 0b01000010); + ASSERT_EQ(v.getZBits(), 0b10000001); + + ASSERT_EQ(FVInt::getZero(0).toString(2), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(8), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(10), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(16), StringRef("0")); + + // Parsing/printing without unknown values. + ASSERT_EQ(FVInt::fromString("10101100", 2).toString(2), + StringRef("10101100")); + ASSERT_EQ(FVInt::fromString("1234567", 8).toString(8), StringRef("1234567")); + ASSERT_EQ(FVInt::fromString("1234567890", 10).toString(10), + StringRef("1234567890")); + ASSERT_EQ(FVInt::fromString("1234567890ABCDEF", 16).toString(16), + "1234567890ABCDEF"); + ASSERT_EQ(FVInt::fromString("1234567890abcdef", 16).toString(16, false), + "1234567890abcdef"); + + // Parsing/printing with unknown values. + ASSERT_EQ(FVInt::fromString("10XZ1XZ0", 2).toString(2), + StringRef("10XZ1XZ0")); + ASSERT_EQ(FVInt::fromString("10xz1xz0", 2).toString(2, false), + StringRef("10xz1xz0")); + ASSERT_EQ(FVInt::fromString("1234XZ567", 8).toString(8), + StringRef("1234XZ567")); + ASSERT_EQ(FVInt::fromString("1234xz567", 8).toString(8, false), + StringRef("1234xz567")); + ASSERT_EQ(FVInt::fromString("12345XZ67890ABCDEF", 16).toString(16), + StringRef("12345XZ67890ABCDEF")); + ASSERT_EQ(FVInt::fromString("12345xz67890abcdef", 16).toString(16, false), + StringRef("12345xz67890abcdef")); +} + +TEST(FVIntTest, LogicOps) { + auto a = FVInt::fromString("01XZ01XZ01XZ01XZ", 2); + auto b = FVInt::fromString("00001111XXXXZZZZ", 2); + auto c = FVInt::fromString("01XZ", 2); + + ASSERT_EQ(~c, FVInt::fromString("10XX", 2)); + ASSERT_EQ(a & b, FVInt::fromString("000001XX0XXX0XXX", 2)); + ASSERT_EQ(a | b, FVInt::fromString("01XX1111X1XXX1XX", 2)); + ASSERT_EQ(a ^ b, FVInt::fromString("01XX10XXXXXXXXXX", 2)); +} + +TEST(FVIntTest, ArithmeticOps) { + auto a = FVInt::fromString("123").zext(32); + auto b = FVInt::fromString("234").zext(32); + auto c = FVInt::fromString("1XZ", 16).zext(32); + + ASSERT_EQ(-a, uint32_t(-123)); + ASSERT_TRUE((-c).isAllX()); + + ASSERT_EQ(a + 1, FVInt::fromString("124").zext(32)); + ASSERT_EQ(1 + b, FVInt::fromString("235").zext(32)); + ASSERT_EQ(a + b, FVInt::fromString("357").zext(32)); + ASSERT_TRUE((a + c).isAllX()); + + ASSERT_EQ(a - 1, FVInt::fromString("122").zext(32)); + ASSERT_EQ(234 - a, FVInt::fromString("111").zext(32)); + ASSERT_EQ(b - a, FVInt::fromString("111").zext(32)); + ASSERT_TRUE((a - c).isAllX()); + + ASSERT_EQ(a * 2, FVInt::fromString("246").zext(32)); + ASSERT_EQ(2 * b, FVInt::fromString("468").zext(32)); + ASSERT_EQ(a * b, FVInt::fromString("28782").zext(32)); + ASSERT_TRUE((a * c).isAllX()); +} + +} // namespace