Repository: cocotb/cocotb

Issue:
Discussion: bool cast of weak drivers
![Image](https://github.com/user-attachments/assets/054a103a-9f4e-43cc-9b00-b82456b3b06f)

currently the 2.0 style Logic implementation only allows casts to boolean for 1/0 values.

W/L/H leads to a ValueError. For W this makes sense as it is essentially a 'weak X', but L and H are perfectly valid high and low values, usually encountered in pad logic or other scenarios involving Pulls or Open Drain scenarios (think I2C)

Hence the question: has it been discussed whether these should cast to a valid boolean?

Discussion:
markusdd: is this really what we want?

I feel raising the error in case of X/Z/W/U actually was good to be honest, because these are not valid logical values in a boolean sense. (caught a bug with this just the other day because a error response line was X, not zero)

At least my initial idea was more targeted towards the actual valid values. I think including the invalid ones and just assuming False is bad from a verification perspective.
ktbarrett: I suppose you are right. The VHDL behavior seems a bit odd to me. I feel like it was done mostly due to screeching from Verilog users. It also doesn't parallel `__int__`. `bool` is a subtype of `int`, so the behavior of `bool(logic)` should be a subset of `int(logic)`.

I'll remove it.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cocotb/cocotb/pull/4551?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocotb) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 77.41%. Comparing base [(`e3e7f80`)](https://app.codecov.io/gh/cocotb/cocotb/commit/e3e7f8083e28182cba40fdd16df1e3768c30d1fb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocotb) to head [(`8b306cc`)](https://app.codecov.io/gh/cocotb/cocotb/commit/8b306cc8fc2e278213167c1a756d93f7ae75549e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocotb).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4551      +/-   ##
==========================================
- Coverage   77.42%   77.41%   -0.02%     
==========================================
  Files          53       53              
  Lines        8399     8395       -4     
  Branches     2132     2144      +12     
==========================================
- Hits         6503     6499       -4     
- Misses       1402     1418      +16     
+ Partials      494      478      -16     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cocotb/cocotb/pull/4551?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocotb).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cocotb).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
markusdd: glad I'm not the only one who considers VHDL behavior a bit 'odd' at times haha

makes sense.
ktbarrett: Apparently we *do* need both `__index__` and `__int__`, `int` calling `__index__` is a Python 3.8+ behavior...

Patch:
diff --git a/docs/source/newsfragments/4551.change.1.rst b/docs/source/newsfragments/4551.change.1.rst
new file mode 100644
index 00000000..a01bf420
--- /dev/null
+++ b/docs/source/newsfragments/4551.change.1.rst
@@ -0,0 +1,1 @@
+:class:`bool(Logic) <cocotb.types.Logic>` casts now treat ``H`` as ``True`` and ``L`` as ``False`` instead of raising :exc:`ValueError`.
diff --git a/docs/source/newsfragments/4551.change.2.rst b/docs/source/newsfragments/4551.change.2.rst
new file mode 100644
index 00000000..56fdd3f2
--- /dev/null
+++ b/docs/source/newsfragments/4551.change.2.rst
@@ -0,0 +1,1 @@
+:meth:`.LogicArray.is_resolvable` now returns ``True`` for arrays containing ``H`` and ``L`` values.
diff --git a/docs/source/newsfragments/4551.change.rst b/docs/source/newsfragments/4551.change.rst
new file mode 100644
index 00000000..b703c021
--- /dev/null
+++ b/docs/source/newsfragments/4551.change.rst
@@ -0,0 +1,1 @@
+:class:`int(Logic) <cocotb.types.Logic>` casts now treat ``H`` as ``1`` and ``L`` as ``0`` instead of raising :exc:`ValueError`.
diff --git a/src/cocotb/types/logic.py b/src/cocotb/types/logic.py
index b654975e..7d2fbe71 100644
--- a/src/cocotb/types/logic.py
+++ b/src/cocotb/types/logic.py
@@ -87,7 +87,7 @@ class Logic:
 
     .. note::
 
-        The :class:`int` and :class:`bool` conversions will raise :exc:`ValueError` if the value is not ``0`` or ``1``.
+        The :class:`int` and :class:`bool` conversions will raise :exc:`ValueError` if the value is not ``0``, ``1``, ``L``, or ``H``.
 
     :class:`Logic` values are immutable and therefore hashable and can be placed in :class:`set`\ s and used as keys in :class:`dict`\ s.
 
@@ -229,16 +229,16 @@ class Logic:
         return ("U", "X", "0", "1", "Z", "W", "L", "H", "-")[self._repr]
 
     def __bool__(self) -> bool:
-        if self._repr == _0:
+        if self._repr in (_0, _L):
             return False
-        elif self._repr == _1:
+        elif self._repr in (_1, _H):
             return True
         raise ValueError(f"Cannot convert {self!r} to bool")
 
     def __int__(self) -> int:
-        if self._repr == _0:
+        if self._repr in (_0, _L):
             return 0
-        elif self._repr == _1:
+        elif self._repr in (_1, _H):
             return 1
         raise ValueError(f"Cannot convert {self!r} to int")
 
diff --git a/src/cocotb/types/logic_array.py b/src/cocotb/types/logic_array.py
index ee18b04d..2f42d116 100644
--- a/src/cocotb/types/logic_array.py
+++ b/src/cocotb/types/logic_array.py
@@ -325,6 +325,10 @@ class LogicArray(ArrayLike[Logic]):
         if self._value_as_int is None:
             # May convert list to str before converting to int.
             value_as_str = self._get_str()
+
+            # always resolve L and H to 0 and 1
+            value_as_str = value_as_str.translate(_resolve_lh_table)
+
             try:
                 self._value_as_int = int(value_as_str, 2)
             except ValueError:
@@ -332,9 +336,6 @@ class LogicArray(ArrayLike[Logic]):
                 if resolve is None:
                     resolve = RESOLVE_X
 
-                # resolve L and H to 0 and 1
-                value_as_str = value_as_str.translate(_resolve_lh_table)
-
                 # resolve remaining
                 resolve_table = _resolve_tables[resolve]
                 value_as_str = value_as_str.translate(resolve_table)
@@ -535,8 +536,10 @@ class LogicArray(ArrayLike[Logic]):
 
     @property
     def is_resolvable(self) -> bool:
-        """``True`` if all elements are ``0`` or ``1``."""
-        return all(bit in (Logic(0), Logic(1)) for bit in self)
+        """``True`` if all elements are ``0``, ``1``, ``L``, ``H``."""
+        return all(
+            bit in (Logic("0"), Logic("1"), Logic("L"), Logic("H")) for bit in self
+        )
 
     @property
     @deprecated("`.integer` property is deprecated. Use `value.to_unsigned()` instead.")
@@ -778,11 +781,9 @@ class LogicArray(ArrayLike[Logic]):
     def __invert__(self) -> "LogicArray":
         return LogicArray(~v for v in self)
 
+    @deprecated(
+        "`bool()` casts and using LogicArray in conditional expressions is deprecated. "
+        """Use explicit comparisons (e.g. `LogicArray() == "11"`) or `all`/`any` expressions instead."""
+    )
     def __bool__(self) -> bool:
-        warnings.warn(
-            "The behavior of bool casts and using LogicArray in conditionals may change in the future. "
-            """Use explicit comparisons (e.g. `LogicArray() == "11"`) instead""",
-            FutureWarning,
-            stacklevel=2,
-        )
         return any(v in (Logic("H"), Logic("1")) for v in self)


Test patch:
diff --git a/tests/pytest/test_logic.py b/tests/pytest/test_logic.py
index 159d6378..dcd0edd1 100644
--- a/tests/pytest/test_logic.py
+++ b/tests/pytest/test_logic.py
@@ -64,11 +64,19 @@ def test_logic_equality():
 
 def test_logic_bool_conversions():
     assert bool(Logic("1")) is True
+    assert bool(Logic("H")) is True
     assert bool(Logic("0")) is False
+    assert bool(Logic("L")) is False
     with pytest.raises(ValueError):
         bool(Logic("X"))
     with pytest.raises(ValueError):
         bool(Logic("Z"))
+    with pytest.raises(ValueError):
+        bool(Logic("U"))
+    with pytest.raises(ValueError):
+        bool(Logic("W"))
+    with pytest.raises(ValueError):
+        bool(Logic("-"))
 
 
 def test_logic_str_conversions():
@@ -88,10 +96,18 @@ def test_logic_index_cast():
 def test_logic_int_conversions():
     assert int(Logic("0")) == 0
     assert int(Logic("1")) == 1
+    assert int(Logic("L")) == 0
+    assert int(Logic("H")) == 1
     with pytest.raises(ValueError):
         int(Logic("X"))
     with pytest.raises(ValueError):
         int(Logic("Z"))
+    with pytest.raises(ValueError):
+        int(Logic("U"))
+    with pytest.raises(ValueError):
+        int(Logic("-"))
+    with pytest.raises(ValueError):
+        int(Logic("W"))
 
 
 def test_logic_repr():
diff --git a/tests/pytest/test_logic_array.py b/tests/pytest/test_logic_array.py
index a4379fe2..5c57be60 100644
--- a/tests/pytest/test_logic_array.py
+++ b/tests/pytest/test_logic_array.py
@@ -396,10 +396,10 @@ def test_null_vector():
 
 
 def test_bool_cast():
-    with pytest.warns(FutureWarning):
+    with pytest.warns(DeprecationWarning):
         assert LogicArray("0110")
     with warnings.catch_warnings():
-        warnings.filterwarnings(action="ignore", category=FutureWarning)
+        warnings.filterwarnings(action="ignore", category=DeprecationWarning)
         assert not LogicArray("0000")
         assert LogicArray("01XZ")
         assert LogicArray("XZ01")