-
Notifications
You must be signed in to change notification settings - Fork 513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace BinaryValue with LogicArray #3244
Conversation
@ktbarrett I would greatly appreciate your feedback on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not against this change. I appreciate the work! I think this PR should come in, but maybe we should fix a few critical bugs and do a 1.8 release before this comes in.
There is some additional development on these types that needs to occur, namely for performance (#2769). Also we need to consider removing Bit
(#2666), as I don't think it's really useful.
cocotb/types/logic_array.py
Outdated
elif isinstance(value, ctypes.Structure): | ||
# ctypes.Structure is also typing.Iterable, but it is not supported | ||
raise ValueError(f"{value} is an instance of ctypes.Structure which cannot be converted to LogicArray") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to add this since it's been deprecated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea was to help user switch to LogicArray with a (hopefully) straightforward error.
While migrating big projects it could save even more time.
You should install pre-commit and run pre-commit on your commits. This should make sure that your commits will pass all the lint checks. pip install pre-commit
pre-commit install You can run the checks at any time using pre-commit run -a |
See comment from from @ktbarrett in cocotb#3244 for context: > This isn't a good idea. It doesn't make it clear to the user which representation > to interpret the bits as. Instead of making a choice for the user, users should > make an explicit choice using the .integer and .signed_integer methods. > > From PEP 20: > > Explicit is better than implicit. > > In the face of ambiguity, refuse the temptation to guess.
Enables straighforward BigEndian / LittleEndian convertion. (And one small description of exception raised by LogicArray.integer)
… integer on integer comparison (issue_768)
Co-authored-by: Kaleb Barrett <dev.ktbarrett@gmail.com>
Co-authored-by: Kaleb Barrett <dev.ktbarrett@gmail.com>
Co-authored-by: Kaleb Barrett <dev.ktbarrett@gmail.com>
See comment from from @ktbarrett in cocotb#3244 for context: > This isn't a good idea. It doesn't make it clear to the user which representation > to interpret the bits as. Instead of making a choice for the user, users should > make an explicit choice using the .integer and .signed_integer methods. > > From PEP 20: > > Explicit is better than implicit. > > In the face of ambiguity, refuse the temptation to guess.
Failed example: File "logic_array.py" (doctest) la.reverse() File "logic_array.py", line 194, in reverse return type(self)(reversed(self), range=reversed(self.range)) File "logic_array.py", line 184, in __init__ if len(self._value) != len(self._range): Error: TypeError: object of type 'range_iterator' has no len() Explanation: Range.__reversed__() returns an range_iterator and LogicArray's constructor (LogicArray.__init__()) expects a Range. Fix: Implement a Range.reverse() function to return a reversed Range Alternative: Modify Range.__reversed__() to return a reversed Range instead of a range_iterator
Fix format by running pre-commit as recommanded by ktbarrett
61bf2e5
to
9380621
Compare
Example of fail: Cause: Previous implementation relies on the convertion to integer to compare two LogicArrays. It fails when one of the logic array contains X/Z bits. This commit also contains does not assume the internal binstr representation uses uppercase, as suggested by ktbarret.
Remove reference to binaryValue in test_logic_array.py
Have been suggested by ktbarret: > Also add a check for the reversal of the range.
BinaryValue have been removed from this branch and replaced with LogicArray. LogicArray has it's dedicated pytest tests.
Adding __mul__ __imul__ and __rmul__ operand to LogicArray to make it behave similarly to BinaryValue (required by performance test test_matrix_multiplier.multiply_test)
Hi, Regarding the status, performance analysis is something to consider as well. Based on current
The source of the performance drop comes from the way the value is handled in LogicArray: each time the integer representation is needed, the array is looped over to build it. |
Yes performance is an issue too and it is in the to do list at the top of the issue, thanks for mentioning it. Optimizing performance of something not functional could be very error prone. Without the full picture we could be missing optimization opportunities or optimize out things we need. I suggest we get this branch to pass CI first, then refactor LogicArray to improve performance. EDIT: I assume we want to use LogicArray instead of BinaryValue anyway and that we don't want to introduce a new type. |
Test `test_my_design.my_first_test` and `test_my_design.my_second_test` are doing comparison with a bit and an integer. In order to make LogicArray behave similarly to BinaryValue we also need to implement comparison between Bit and int. I also add a doctest in logic_array.py to make sure we don't break it when refactoring LogicArray to improve performance. (It is not in logic.py because we may remove it when refactoring)
See comment from from @ktbarrett in cocotb#3244 for context: > This isn't a good idea. It doesn't make it clear to the user which representation > to interpret the bits as. Instead of making a choice for the user, users should > make an explicit choice using the .integer and .signed_integer methods. > > From PEP 20: > > Explicit is better than implicit. > > In the face of ambiguity, refuse the temptation to guess.
I tried to "cache LogicArray.integer" in a separate branch (look for "Experiment 1: 'Cache' self.integer" in first top of the PR). I get significant performance improvements with a simple change, but it's nowhere near BinaryValue. I will keep experimenting with different implementations and share results and code in the first comment, at the top of the PR. If anyone else want to give it a try, you can comment below with your results/branch and I will add it to the list too. |
Python 3.7 needs __int__ to perform integer conversion where Python 3.8+ uses index() This is a fix for the CI / python3.7 tests
The latest commit passes CI. FYI: To run python3.7 tests locally, I used --- a/noxfile.py
+++ b/noxfile.py
@@ -123,7 +123,7 @@ def dev_build(session: nox.Session) -> None:
session.warn("No building is necessary for development sessions.")
-@nox.session
+@nox.session(python=["3.10", "3.7", "3.8"])
def dev_test(session: nox.Session) -> None:
"""Run all development tests as configured through environment variables.""" Just in case someone else needs to debug python3.7 compatibility. EDIT: I'm currently working on improving the performance and writing new performance benchmarks. |
Thanks for your work, @aubindetrez! I just had a chat with @ktbarrett (and he'll probably jump in with more details), but just as a first notice:
While looking at this issue I thought about ways to make the API more explicit, please have a look at #3418 and join the discussion. |
I think we can also remove |
Generally what we need is:
What we cannot assume:
|
Hi all, To expand on the performance trials, I implemented an int and string representation for LogicArray that is used as much as possible when valid, i.e not modified by set_item and other arithmetic operations that are implemented for now (see logicarray_perf_repr). The results are far better than the original implementation but we are nowhere near the results with BinaryValue per the construction of LogicArray inheriting from Array[Logic]. A lot of time is spent rebuilding the array even with the string and int representations (total time 7 seconds for binstr, integer has better results around 4 seconds total time). |
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## master #3244 +/- ##
==========================================
+ Coverage 43.78% 43.91% +0.12%
==========================================
Files 49 48 -1
Lines 8863 8433 -430
Branches 2458 2356 -102
==========================================
- Hits 3881 3703 -178
+ Misses 4401 4190 -211
+ Partials 581 540 -41
☔ View full report in Codecov by Sentry. |
References to the Bit type are removed from documentation and tests.
6c277df
to
eec5994
Compare
@teobiton The next stumbling block is probably the construction of the We can then investigate maintaining multiple Range objects as well. I wonder if we should start from the ground up with performance in mind (at least for the |
@teobiton Actually, I see you are still creating the An easy way to do this is with getter which encapsulate the creation. The only time you need to set the string and int representations is on construction and I don't know if we need to cache them after that fact, so once they are invalidated on a A small mock upclass LogicArray:
@overload
def __init__(self, value: str):
...
@overload
def __init__(self, value: int, *, byteorder: str) -> None:
...
@overload
def __init__(self, value: SizedIterable[LogicConstructible]) -> None:
...
def __init__(self, value, *, byteorder: str):
self._str_impl_valid = False
self._list_impl_valid = False
self._int_impl_valid = False
if isinstance(value, str):
self._str_impl = value
# validate
self._str_impl_valid = True
elif isinstance(value, int):
self._int_impl = value
self._int_impl_byteorder = byteorder
# validate
self._int_impl_valid = True
else:
self._list_impl = list(value)
# validate
self._list_impl_valid = True
def __str__(self) -> str:
if self._str_impl_valid:
return self._str_impl
# otherwise construct from int or logic representation
def as_int(self, *, byteorder: str, signed: bool) -> int:
if self._int_impl_valid and self._int_impl_byteorder == byteorder:
# might need to work out if two's complement is needed if 'signed' is False and '_int_impl' is negative
return self._int_impl
# otherwise construct from str, int, or logic representation
@property
def _list_impl(self) -> List[Logic]:
if self._list_impl_valid:
return self._list_impl_
# construct from str or int representation
def __iter__(self) -> Iterable[Logic]:
return iter(self._list_impl)
def __reversed__(self) -> Iterable[Logic]:
return reversed(self._list_impl)
def __setitem__(self, key: int, value: LogicConstructable) -> None:
self._int_impl_valid = False
self._str_impl_valid = False
# validate
return self._list_impl[index] |
Where are we with this? |
We (I) still need to change the API as discussed by Philipp and you. This will break many tests and examples. We also need to improve performance further. I personally don't have a lot of free time right now but I hope it should get better in a few weeks. If anyone else wants to work on it he/she is more than welcome. |
So I picked up things where I left them and tried a new implementation based on your comments @ktbarrett, still on the same branch logicarray_perf_repr). It's not quite done yet. Do we still want a |
@ktbarrett Is this PR superseded by your other handle PRs? |
Superceded by #3634. |
Replace BinaryValue with LogicArray like mentioned in issue #608 and PR #706.
All internal references to BinaryValue are removed.
Please this is a Breaking Changes - do not merge as-is, further discussions are required.
I think BinaryValue should be reintroduced for compatibility reason but marked deprecated. (And next major release it can be definitely removed). Let me know what you think.
Status / To dos:
value = signal[3:0]
)signal[3:0] = value
)LogicArray
: higher performance implementation #2769cocotb/cocotb/handle.py
usesdownto
by default, do we want to add an option for usingto
?LogicArray
's operators compared toBinaryValue
The list of operators have been moved to: https://gist.github.com/aubindetrez/16ffe3b172ff0edd138001d61437d4ef (to improve clarity.
Failing tests
pytest cocotb/tests/pytests/test_logic_array.py
depends onBinaryValue
pytest cocotb/tests/pytest/test_binary_value.py
test_matrix_multiplier_icarus
fromcocotb/cocotb/examples/matrix_multiplier/tests/test_matrix_multiplier.py
with errorModuleNotFoundError: No module named 'cocotb.binary'
test_matrix_multiplier_ghdl
issue_142_overflow_error
fromcocotb/tests/test_cases/issue_142/issue_142.py
with errorTest failed with RANDOM_SEED=1694419089
test_assigning_structure
fromcocotb/tests/test_cases/test_cocotb/test_deprecated.py
with errorTest failed with RANDOM_SEED=1694419168
test_force_release
fromcocotb/tests/test_cases/test_force_release/test_force_release.py
with errorTest failed with RANDOM_SEED=1694419237
test_my_design.my_first_test
fromcocotb/cocotb/examples/doc_examples/quickstart
with errorAssertionError: my_signal_2[0] is not 0!
test_my_design.my_second_test
fromcocotb/cocotb/examples/doc_examples/quickstart
with errorAssertionError: my_signal_2[0] is not 0!
issue_142_overflow_error
test_force_release
*If checked, the test passed at least locally (nox + pytest) but for the CI you have to scroll down this page.
Performance
LogicArray is 5x slower than BinaryValue for MatrixMultiplication
https://gist.github.com/aubindetrez/bb6e311bbfe8044a839ee22f98ae7b18LogicArray is 2.7x slower than BinaryValue for MatrixMultiplication