Repository: cocotb/cocotb

Issue:
Remove `Bit`?
We decided that all we really need from the types right now are a minimal set of types that correspond to the what's current available to the GPI. `Bit` and `BitArray` don't map, and `BitArray` was removed from the `LogicArray` PR. Should we remove `Bit`? I can't see it being of much use, but it isn't much to maintain. But removal will open the doors for adding simpler semantics to `Logic`:

```python
handle.value[0] == "0"  # not currently possible due to how Logic.__eq__ must behave
```


Discussion:
We don't have to remove `Bit` to accomplish binary operations with literals.
Lets reconsider on whether to remove `Bit`, as I don't think it has any real value and I doubt it's used anywhere.

Resolving patch:
diff --git a/src/cocotb/types/__init__.py b/src/cocotb/types/__init__.py
--- a/src/cocotb/types/__init__.py
+++ b/src/cocotb/types/__init__.py
@@ -4,7 +4,7 @@
 import typing
 
 from .array import Array  # noqa: F401
-from .logic import Bit, Logic  # noqa: F401
+from .logic import Logic  # noqa: F401
 from .logic_array import LogicArray  # noqa: F401
 from .range import Range  # noqa: F401
 
diff --git a/src/cocotb/types/logic.py b/src/cocotb/types/logic.py
--- a/src/cocotb/types/logic.py
+++ b/src/cocotb/types/logic.py
@@ -4,58 +4,64 @@
 import typing
 from functools import lru_cache
 
-LogicT = typing.TypeVar("LogicT", bound="Logic")
 LogicLiteralT = typing.Union[str, int, bool]
 LogicConstructibleT = typing.Union[LogicLiteralT, "Logic"]
 
 
-_0 = 0
-_1 = 1
-_X = 2
-_Z = 3
+_U = 0
+_X = 1
+_0 = 2
+_1 = 3
+_Z = 4
+_W = 5
+_L = 6
+_H = 7
+_D = 8
 
 _literal_repr: typing.Dict[LogicLiteralT, int] = {
-    # 0 and weak 0
+    # unassigned
+    "U": _U,
+    "u": _U,
+    # unknown
+    "X": _X,
+    "x": _X,
+    # 0
     False: _0,
     0: _0,
     "0": _0,
-    "L": _0,
-    "l": _0,
-    # 1 and weak 1
+    # 1
     True: _1,
     1: _1,
     "1": _1,
-    "H": _1,
-    "h": _1,
-    # unknown, unassigned, and weak unknown
-    "X": _X,
-    "x": _X,
-    "U": _X,
-    "u": _X,
-    "W": _X,
-    "w": _X,
-    "-": _X,
     # high impedance
     "Z": _Z,
     "z": _Z,
+    # weak unknown
+    "W": _W,
+    "w": _W,
+    # weak 0
+    "L": _L,
+    "l": _L,
+    # weak 1
+    "H": _H,
+    "h": _H,
+    # don't care
+    "-": _D,
 }
 
 
 class Logic:
     r"""
-    Model of a 4-value (``0``, ``1``, ``X``, ``Z``) datatype commonly seen in HDLs.
+    Model of a 9-value (``U``, ``X``, ``0``, ``1``, ``Z``, ``W``, ``L``, ``H``, ``-``) datatype commonly seen in VHDL.
 
     .. currentmodule:: cocotb.types
 
-    This is modeled after (System)Verilog's 4-value ``logic`` type.
-    VHDL's 9-value ``std_ulogic`` type maps to this type by treating weak values as full strength values
-    and treating "uninitialized" (``U``) and "don't care" (``-``) as "unknown" (``X``).
+    This is modeled after VHDL's ``std_ulogic`` type.
+    (System)Verilog's 4-value ``logic`` type only utilizes ``X``, ``0``, ``1``, and ``Z`` values.
 
-    :class:`Logic` can be converted to and from :class:`int`, :class:`str`, :class:`bool`, and :class:`Bit`
-    by using the appropriate constructor syntax.
+    :class:`Logic` can be converted to and from :class:`int`, :class:`str`, and :class:`bool`.
     The list of values convertable to :class:`Logic` includes
-    ``0``, ``1``, ``True``, ``False``, ``"0"``, ``"1"``, ``"X"``, ``"Z"``, ``Bit('0')``, and ``Bit('1')``.
-    For a comprehensive list of values that can be converted into :class:`Logic` see :file:`tests/pytest/test_logic.py`.
+    ``"U"``, ``"X"``, ``"0"``, ``"1"``, ``"Z"``, ``"W"``, ``"L"``, ``"H"``, ``"-"``, ``0``, ``1``, ``True``, and ``False``.
 
     .. code-block:: python3
 
@@ -65,8 +71,6 @@ class Logic:
         Logic('1')
         >>> Logic(1)
         Logic('1')
-        >>> Logic(Bit(0))
-        Logic('0')
 
         >>> Logic()  # default value
         Logic('X')
@@ -77,8 +81,6 @@ class Logic:
         False
         >>> int(Logic(1))
         1
-        >>> Bit(Logic("1"))
-        Bit('1')
 
     .. note::
 
@@ -106,28 +108,25 @@ class Logic:
     """
 
     __slots__ = ("_repr",)
-    _repr: int
-
-    _default = _X
-    _valid = {_X, _0, _1, _Z}
 
     @classmethod
     @lru_cache(maxsize=None)
-    def _make(cls: typing.Type[LogicT], _repr: int) -> LogicT:
+    def _make(cls: typing.Type["Logic"], _repr: int) -> "Logic":
         """enforce singleton"""
         self = object.__new__(cls)
         self._repr = _repr
-        return typing.cast(LogicT, self)
+        return typing.cast("Logic", self)
 
+    @lru_cache(maxsize=None)
     def __new__(
-        cls: typing.Type[LogicT],
+        cls: typing.Type["Logic"],
         value: typing.Optional[LogicConstructibleT] = None,
-    ) -> LogicT:
+    ) -> "Logic":
         if isinstance(value, Logic):
             # convert Logic
             _repr = value._repr
         elif value is None:
-            _repr = cls._default
+            _repr = _X
         else:
             # convert literal
             try:
@@ -136,62 +135,80 @@ def __new__(
                 raise ValueError(
                     f"{value!r} is not convertible to a {cls.__qualname__}"
                 ) from None
-        if _repr not in cls._valid:
-            raise ValueError(f"{value!r} is not a valid {cls.__qualname__}")
         obj = cls._make(_repr)
         return obj
 
-    if not typing.TYPE_CHECKING:  # pragma: no cover
-        # mypy currently does not support lru_cache on __new__
-        __new__ = lru_cache(maxsize=None)(__new__)
-
-    def __and__(self: LogicT, other: LogicT) -> LogicT:
+    def __and__(self, other: "Logic") -> "Logic":
         if not isinstance(other, type(self)):
             return NotImplemented
         return type(self)(
             (
-                ("0", "0", "0", "0"),
-                ("0", "1", "X", "X"),
-                ("0", "X", "X", "X"),
-                ("0", "X", "X", "X"),
+                # -----------------------------------------------------
+                # U    X    0    1    Z    W    L    H    -       |   |
+                # -----------------------------------------------------
+                ("U", "U", "0", "U", "U", "U", "0", "U", "U"),  # | U |
+                ("U", "X", "0", "X", "X", "X", "0", "X", "X"),  # | X |
+                ("0", "0", "0", "0", "0", "0", "0", "0", "0"),  # | 0 |
+                ("U", "X", "0", "1", "X", "X", "0", "1", "X"),  # | 1 |
+                ("U", "X", "0", "X", "X", "X", "0", "X", "X"),  # | Z |
+                ("U", "X", "0", "X", "X", "X", "0", "X", "X"),  # | W |
+                ("0", "0", "0", "0", "0", "0", "0", "0", "0"),  # | L |
+                ("U", "X", "0", "1", "X", "X", "0", "1", "X"),  # | H |
+                ("U", "X", "0", "X", "X", "X", "0", "X", "X"),  # | - |
             )[self._repr][other._repr]
         )
 
-    def __rand__(self: LogicT, other: LogicT) -> LogicT:
+    def __rand__(self: "Logic", other: "Logic") -> "Logic":
         return self & other
 
-    def __or__(self: LogicT, other: LogicT) -> LogicT:
+    def __or__(self: "Logic", other: "Logic") -> "Logic":
         if not isinstance(other, type(self)):
             return NotImplemented
         return type(self)(
             (
-                ("0", "1", "X", "X"),
-                ("1", "1", "1", "1"),
-                ("X", "1", "X", "X"),
-                ("X", "1", "X", "X"),
+                # -----------------------------------------------------
+                # U    X    0    1    Z    W    L    H    -       |   |
+                # -----------------------------------------------------
+                ("U", "U", "U", "1", "U", "U", "U", "1", "U"),  # | U |
+                ("U", "X", "X", "1", "X", "X", "X", "1", "X"),  # | X |
+                ("U", "X", "0", "1", "X", "X", "0", "1", "X"),  # | 0 |
+                ("1", "1", "1", "1", "1", "1", "1", "1", "1"),  # | 1 |
+                ("U", "X", "X", "1", "X", "X", "X", "1", "X"),  # | Z |
+                ("U", "X", "X", "1", "X", "X", "X", "1", "X"),  # | W |
+                ("U", "X", "0", "1", "X", "X", "0", "1", "X"),  # | L |
+                ("1", "1", "1", "1", "1", "1", "1", "1", "1"),  # | H |
+                ("U", "X", "X", "1", "X", "X", "X", "1", "X"),  # | - |
             )[self._repr][other._repr]
         )
 
-    def __ror__(self: LogicT, other: LogicT) -> LogicT:
+    def __ror__(self: "Logic", other: "Logic") -> "Logic":
         return self | other
 
-    def __xor__(self: LogicT, other: LogicT) -> LogicT:
+    def __xor__(self: "Logic", other: "Logic") -> "Logic":
         if not isinstance(other, type(self)):
             return NotImplemented
         return type(self)(
             (
-                ("0", "1", "X", "X"),
-                ("1", "0", "X", "X"),
-                ("X", "X", "X", "X"),
-                ("X", "X", "X", "X"),
+                # -----------------------------------------------------
+                # U    X    0    1    Z    W    L    H    -       |   |
+                # -----------------------------------------------------
+                ("U", "U", "U", "U", "U", "U", "U", "U", "U"),  # | U |
+                ("U", "X", "X", "X", "X", "X", "X", "X", "X"),  # | X |
+                ("U", "X", "0", "1", "X", "X", "0", "1", "X"),  # | 0 |
+                ("U", "X", "1", "0", "X", "X", "1", "0", "X"),  # | 1 |
+                ("U", "X", "X", "X", "X", "X", "X", "X", "X"),  # | Z |
+                ("U", "X", "X", "X", "X", "X", "X", "X", "X"),  # | W |
+                ("U", "X", "0", "1", "X", "X", "0", "1", "X"),  # | L |
+                ("U", "X", "1", "0", "X", "X", "1", "0", "X"),  # | H |
+                ("U", "X", "X", "X", "X", "X", "X", "X", "X"),  # | - |
             )[self._repr][other._repr]
         )
 
-    def __rxor__(self: LogicT, other: LogicT) -> LogicT:
+    def __rxor__(self: "Logic", other: "Logic") -> "Logic":
         return self ^ other
 
-    def __invert__(self: LogicT) -> LogicT:
-        return type(self)(("1", "0", "X", "X")[self._repr])
+    def __invert__(self: "Logic") -> "Logic":
+        return type(self)(("U", "X", "1", "0", "X", "X", "1", "0", "X")[self._repr])
 
     def __eq__(self, other: object) -> bool:
         if not isinstance(other, type(self)):
@@ -205,90 +222,18 @@ def __repr__(self) -> str:
         return f"{type(self).__qualname__}({str(self)!r})"
 
     def __str__(self) -> str:
-        return ("0", "1", "X", "Z")[self._repr]
+        return ("U", "X", "0", "1", "Z", "W", "L", "H", "-")[self._repr]
 
     def __bool__(self) -> bool:
-        if self._repr in {_0, _1}:
-            return bool(self._repr)
+        if self._repr == _0:
+            return False
+        elif self._repr == _1:
+            return True
         raise ValueError(f"Cannot convert {self!r} to bool")
 
     def __int__(self) -> int:
-        if self._repr in {_0, _1}:
-            return int(self._repr)
+        if self._repr == _0:
+            return 0
+        elif self._repr == _1:
+            return 1
         raise ValueError(f"Cannot convert {self!r} to int")
-
-
-class Bit(Logic):
-    r"""
-    Model of a 2-value (``0``, ``1``) datatype commonly seen in HDLs.
-
-    .. currentmodule:: cocotb.types
-
-    This is modeled after (System)Verilog's 2-value ``bit`` type.
-    VHDL's ``bit`` type maps to this type perfectly.
-
-    :class:`Bit` is a proper subtype of :class:`Logic`, meaning a use of :class:`Logic` can be substituted with a :class:`Bit`.
-    Some behavior may surprise you if you do not expect it.
-
-    .. code-block:: python3
-
-        >>> Bit(0) == Logic(0)
-        True
-        >>> Bit(0) in {Logic(0)}
-        True
-
-    :class:`Bit` can be converted to and from :class:`int`, :class:`str`, :class:`bool`, and :class:`Logic`
-    by using the appropriate constructor syntax.
-    The list of values convertable to :class:`Bit` includes
-    ``0``, ``1``, ``True``, ``False``, ``"0"``, ``"1"``, ``Logic('0')``, and ``Logic('1')``.
-    For a comprehensive list of values that can be converted into :class:`Bit` see :file:`tests/pytest/test_logic.py`.
-
-    .. code-block:: python3
-
-        >>> Bit("0")
-        Bit('0')
-        >>> Bit(True)
-        Bit('1')
-        >>> Bit(1)
-        Bit('1')
-        >>> Bit(Logic(0))
-        Bit('0')
-
-        >>> Bit()  # default value
-        Bit('0')
-
-        >>> str(Bit("0"))
-        '0'
-        >>> bool(Bit(False))
-        False
-        >>> int(Bit(1))
-        1
-        >>> Logic(Bit("1"))
-        Logic('1')
-
-    :class:`Bit` values are hashable and can be placed in :class:`set`\ s and used as keys in :class:`dict`\ s.
-
-    :class:`Bit` supports the common logic operations ``&``, ``|``, ``^``, and ``~``.
-
-    .. code-block:: py3
-
-        >>> def mux(a: Bit, b: Bit, s: Bit) -> Bit:
-        ...     return (a & ~s) | (b & s)
-
-        >>> a = Bit(0)
-        >>> b = Bit(1)
-        >>> sel = Bit(1)  # choose second argument
-        >>> mux(a, b, sel)
-        Bit('1')
-
-    Args:
-        value: value to construct into a :class:`Bit`.
-
-    Raises:
-        ValueError: if the value cannot be constructed into a :class:`Bit`.
-    """
-
-    __slots__ = ()
-
-    _default = _0
-    _valid = {_0, _1}


Test patch:
diff --git a/examples/simple_dff/test_dff.py b/examples/simple_dff/test_dff.py
--- a/examples/simple_dff/test_dff.py
+++ b/examples/simple_dff/test_dff.py
@@ -17,7 +17,12 @@ async def dff_simple_test(dut):
     """Test that d propagates to q"""
 
     # Assert initial output is unknown
-    assert LogicArray(dut.q.value) == LogicArray("X")
+    initial = (
+        LogicArray("U")
+        if cocotb.LANGUAGE.lower().startswith("vhdl")
+        else LogicArray("X")
+    )
+    assert LogicArray(dut.q.value) == initial
     # Set initial input value to prevent it from floating
     dut.d.value = 0
 
diff --git a/tests/pytest/test_logic.py b/tests/pytest/test_logic.py
--- a/tests/pytest/test_logic.py
+++ b/tests/pytest/test_logic.py
@@ -2,13 +2,19 @@
 # Licensed under the Revised BSD License, see LICENSE for details.
 # SPDX-License-Identifier: BSD-3-Clause
 import pytest
-from cocotb.types import Bit, Logic
+from cocotb.types import Logic
 
 
 def test_logic_conversions():
+    l = Logic("U")
+    assert Logic("u") == l
+    assert Logic(Logic("U")) == l
+
+    l = Logic("X")
+    assert Logic("x") == l
+    assert Logic(Logic("x")) == l
+
     l = Logic("0")
-    assert Logic("l") == l
-    assert Logic("L") == l
     assert Logic(0) == l
     assert Logic(False) == l
     assert Logic(Logic("0")) == l
@@ -16,53 +22,30 @@ def test_logic_conversions():
     l = Logic("1")
     assert Logic(1) == l
     assert Logic(True) == l
-    assert Logic("h") == l
-    assert Logic("H") == l
     assert Logic(Logic("1")) == l
 
-    l = Logic("X")
-    assert Logic("x") == l
-    assert Logic("w") == l
-    assert Logic("W") == l
-    assert Logic("u") == l
-    assert Logic("U") == l
-    assert Logic("-") == l
-    assert Logic(Logic("X")) == l
-
     l = Logic("Z")
     assert Logic("z") == l
     assert Logic(Logic("Z")) == l
 
-    for value in ("j", 2, object()):
-        with pytest.raises(ValueError):
-            Logic(value)
+    l = Logic("W")
+    assert Logic("w") == l
+    assert Logic(Logic("W")) == l
 
+    l = Logic("L")
+    assert Logic("l") == l
+    assert Logic(Logic("L")) == l
 
-def test_bit_conversions():
-    b = Bit(0)
-    assert Bit(False) == b
-    assert Bit("0") == b
-    assert Bit(Bit(0)) == b
+    l = Logic("H")
+    assert Logic("h") == l
+    assert Logic(Logic("H")) == l
 
-    b = Bit(1)
-    assert Bit(True) == b
-    assert Bit("1") == b
-    assert Bit(Bit(1)) == b
+    l = Logic("-")
+    assert Logic(Logic("-")) == l
 
-    for value in ("X", 2, object()):
+    for value in ("j", 2, object()):
         with pytest.raises(ValueError):
-            Bit(value)
-
-
-def test_bit_logic_conversions():
-    Logic(Bit(0))
-    Logic(Bit(1))
-    Bit(Logic(0))
-    Bit(Logic(1))
-    with pytest.raises(ValueError):
-        Bit(Logic("X"))
-    with pytest.raises(ValueError):
-        Bit(Logic("Z"))
+            Logic(value)
 
 
 def test_logic_equality():
@@ -71,40 +54,15 @@ def test_logic_equality():
     assert Logic(0) != object()
 
 
-def test_bit_equality():
-    assert Bit(0) == Bit(False)
-    assert Bit(1) != Bit("0")
-    assert Bit(1) != object()
-
-
-def test_logic_bit_equality():
-    assert Logic(0) == Bit(0)
-    assert Logic(1) == Bit(1)
-
-
 def test_logic_hashability():
     s = {Logic("0"), Logic("1"), Logic("X"), Logic("Z")}
     assert len(s) == 4
 
 
-def test_bit_hashability():
-    s = {Bit(0), Bit(1)}
-    assert len(s) == 2
-
-
-def test_logic_bit_hashability():
-    s = {Logic("0"), Logic("1"), Logic("X"), Logic("Z"), Bit("0"), Bit("1")}
-    assert len(s) == 4
-
-
 def test_logic_default_value():
     assert Logic() == Logic("X")
 
 
-def test_bit_default_value():
-    assert Bit() == Bit("0")
-
-
 def test_logic_bool_conversions():
     assert bool(Logic("1")) is True
     assert bool(Logic("0")) is False
@@ -114,11 +72,6 @@ def test_logic_bool_conversions():
         bool(Logic("Z"))
 
 
-def test_bit_bool_conversions():
-    assert bool(Bit(1)) is True
-    assert bool(Bit(0)) is False
-
-
 def test_logic_str_conversions():
     assert str(Logic("0")) == "0"
     assert str(Logic("1")) == "1"
@@ -126,11 +79,6 @@ def test_logic_str_conversions():
     assert str(Logic("Z")) == "Z"
 
 
-def test_bit_str_conversions():
-    assert str(Bit(0)) == "0"
-    assert str(Bit(1)) == "1"
-
-
 def test_logic_int_conversions():
     assert int(Logic("0")) == 0
     assert int(Logic("1")) == 1
@@ -140,11 +88,6 @@ def test_logic_int_conversions():
         int(Logic("Z"))
 
 
-def test_bit_int_conversions():
-    assert int(Bit("0")) == 0
-    assert int(Bit("1")) == 1
-
-
 def test_logic_repr():
     assert eval(repr(Logic("0"))) == Logic("0")
     assert eval(repr(Logic("1"))) == Logic("1")
@@ -152,11 +95,6 @@ def test_logic_repr():
     assert eval(repr(Logic("Z"))) == Logic("Z")
 
 
-def test_bit_repr():
-    assert eval(repr(Bit("0"))) == Bit("0")
-    assert eval(repr(Bit("1"))) == Bit("1")
-
-
 def test_logic_and():
     # will not be exhaustive
     assert Logic("0") & Logic("Z") == Logic(0)
@@ -168,24 +106,6 @@ def test_logic_and():
         8 & Logic("1")
 
 
-def test_bit_and():
-    assert Bit("0") & Bit("1") == Bit(0)
-    assert Bit(1) & Bit("1") == Bit(1)
-    with pytest.raises(TypeError):
-        Bit("1") & 8
-    with pytest.raises(TypeError):
-        8 & Bit("1")
-
-
-def test_logic_bit_and():
-    r = Logic(0) & Bit(1)
-    assert type(r) == Logic
-    assert r == Logic(0)
-    r = Bit(1) & Logic(0)
-    assert type(r) == Logic
-    assert r == Logic(0)
-
-
 def test_logic_or():
     # will not be exhaustive
     assert Logic("1") | Logic("Z") == Logic("1")
@@ -197,24 +117,6 @@ def test_logic_or():
         Logic(0) | 8
 
 
-def test_bit_or():
-    assert Bit("0") | Bit("1") == Bit(1)
-    assert Bit(0) | Bit(False) == Bit(0)
-    with pytest.raises(TypeError):
-        8 | Bit(0)
-    with pytest.raises(TypeError):
-        Bit(0) | 8
-
-
-def test_logic_bit_or():
-    r = Logic(0) | Bit(1)
-    assert type(r) == Logic
-    assert r == Logic(1)
-    r = Bit(1) | Logic(0)
-    assert type(r) == Logic
-    assert r == Logic(1)
-
-
 def test_logic_xor():
     # will not be exhaustive
     assert (Logic("1") ^ Logic(True)) == Logic(0)
@@ -226,24 +128,6 @@ def test_logic_xor():
         () ^ Logic(1)
 
 
-def test_bit_xor():
-    assert Bit(0) ^ Bit("1") == Bit(1)
-    assert Bit(False) ^ Bit(0) == Bit("0")
-    with pytest.raises(TypeError):
-        Bit(1) ^ ()
-    with pytest.raises(TypeError):
-        () ^ Bit(1)
-
-
-def test_logic_bit_xor():
-    r = Logic(0) ^ Bit(1)
-    assert type(r) == Logic
-    assert r == Logic(1)
-    r = Bit(0) ^ Logic(0)
-    assert type(r) == Logic
-    assert r == Logic(0)
-
-
 def test_logic_invert():
     assert ~Logic(0) == Logic(1)
     assert ~Logic(1) == Logic(0)
@@ -251,18 +135,8 @@ def test_logic_invert():
     assert ~Logic("Z") == Logic("X")
 
 
-def test_bit_invert():
-    assert ~Bit(0) == Bit(1)
-    assert ~Bit(1) == Bit(0)
-
-
 def test_logic_identity():
     assert Logic(0) is Logic(False)
     assert Logic("1") is Logic(1)
     assert Logic("X") is Logic("x")
     assert Logic("z") is Logic("Z")
-
-
-def test_bit_identity():
-    assert Bit(0) is Bit(False)
-    assert Bit(Logic(1)) is Bit("1")