diff --git a/doc/source/data_types.rst b/doc/source/data_types.rst index 84fb5c2..f1f7328 100644 --- a/doc/source/data_types.rst +++ b/doc/source/data_types.rst @@ -151,6 +151,37 @@ PyVSC also provides operator overloading for `randobj`-decorated classes that allows the value of class attributes to be accessed directly. +Converting Bit Patterns to Values +================================== + +When interfacing with hardware (DUT), bit patterns often need to be interpreted +as signed or unsigned values. PyVSC provides the `ValueInt.from_bits()` class method +to convert bit patterns to properly-interpreted integer values: + +.. code-block:: python3 + + import vsc + + # Convert unsigned 8-bit value + unsigned_val = vsc.ValueInt.from_bits(0xFF, width=8, signed=False) + # Result: 255 + + # Convert signed 8-bit value + signed_val = vsc.ValueInt.from_bits(0xFF, width=8, signed=True) + # Result: -1 + + # Practical example: reading from DUT + dut_register_value = 0xFFFFFFF0 + python_value = vsc.ValueInt.from_bits(dut_register_value, width=32, signed=True) + # Result: -16 + +The method properly: + +* Masks the value to the specified bit width +* Detects the sign bit (MSB) when `signed=True` +* Converts negative values using two's complement representation + + List-type Attributes ================================ diff --git a/src/vsc/model/value_scalar.py b/src/vsc/model/value_scalar.py index 5e78b6f..889aacc 100644 --- a/src/vsc/model/value_scalar.py +++ b/src/vsc/model/value_scalar.py @@ -8,6 +8,36 @@ class ValueInt(int): """Wrapper for 'int' values. Permits operators""" + + @classmethod + def from_bits(cls, value, width, signed=False): + """ + Create a ValueInt from a bit pattern with specified width and signedness. + + Args: + value: The bit pattern value (integer) + width: The bit width (e.g., 8, 16, 32) + signed: Whether to interpret the value as signed (default: False) + + Returns: + ValueInt: A properly interpreted integer value + + Example: + >>> ValueInt.from_bits(0xFF, 8, signed=True) # Returns -1 + >>> ValueInt.from_bits(0xFF, 8, signed=False) # Returns 255 + """ + # Mask to the specified width + mask = (1 << width) - 1 + masked_value = int(value) & mask + + # If signed and MSB is set, convert to negative (two's complement) + if signed and (masked_value & (1 << (width - 1))): + # Convert to negative: -(2^width - value) + result = -((1 << width) - masked_value) + else: + result = masked_value + + return cls(result) def __getitem__(self, rng): val = self.__int__() diff --git a/ve/unit/test_value_int.py b/ve/unit/test_value_int.py new file mode 100644 index 0000000..c519a06 --- /dev/null +++ b/ve/unit/test_value_int.py @@ -0,0 +1,142 @@ +''' +Created on Nov 8, 2024 + +Test for ValueInt.from_bits functionality +''' +import sys +import os +# Add src to path to allow importing vsc modules +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) + +from unittest import TestCase + +# Import ValueInt directly to avoid full vsc package dependencies +try: + from vsc.types import ValueInt +except ImportError: + # Fallback to direct import if vsc package can't be fully loaded + from vsc.model.value_scalar import ValueInt + + +class TestValueInt(TestCase): + + def test_from_bits_unsigned_8bit(self): + """Test unsigned 8-bit value conversion""" + # Test maximum unsigned value + result = ValueInt.from_bits(0xFF, 8, signed=False) + self.assertEqual(result, 255) + + # Test zero + result = ValueInt.from_bits(0x00, 8, signed=False) + self.assertEqual(result, 0) + + # Test mid-range value + result = ValueInt.from_bits(0x7F, 8, signed=False) + self.assertEqual(result, 127) + + def test_from_bits_signed_8bit(self): + """Test signed 8-bit value conversion""" + # Test -1 (0xFF in 8-bit two's complement) + result = ValueInt.from_bits(0xFF, 8, signed=True) + self.assertEqual(result, -1) + + # Test -128 (0x80 in 8-bit two's complement) + result = ValueInt.from_bits(0x80, 8, signed=True) + self.assertEqual(result, -128) + + # Test positive value (MSB = 0) + result = ValueInt.from_bits(0x7F, 8, signed=True) + self.assertEqual(result, 127) + + # Test zero + result = ValueInt.from_bits(0x00, 8, signed=True) + self.assertEqual(result, 0) + + def test_from_bits_unsigned_32bit(self): + """Test unsigned 32-bit value conversion""" + # Test maximum unsigned 32-bit value + result = ValueInt.from_bits(0xFFFFFFFF, 32, signed=False) + self.assertEqual(result, 4294967295) + + # Test mid-range value + result = ValueInt.from_bits(0x80000000, 32, signed=False) + self.assertEqual(result, 2147483648) + + def test_from_bits_signed_32bit(self): + """Test signed 32-bit value conversion""" + # Test -1 (0xFFFFFFFF in 32-bit two's complement) + result = ValueInt.from_bits(0xFFFFFFFF, 32, signed=True) + self.assertEqual(result, -1) + + # Test minimum signed 32-bit value + result = ValueInt.from_bits(0x80000000, 32, signed=True) + self.assertEqual(result, -2147483648) + + # Test positive value + result = ValueInt.from_bits(0x7FFFFFFF, 32, signed=True) + self.assertEqual(result, 2147483647) + + def test_from_bits_masking(self): + """Test that values are properly masked to specified width""" + # Value larger than 8 bits should be masked + result = ValueInt.from_bits(0x1FF, 8, signed=False) + self.assertEqual(result, 0xFF) + + # Value larger than 4 bits should be masked + result = ValueInt.from_bits(0xFF, 4, signed=False) + self.assertEqual(result, 0x0F) + + # Signed masking + result = ValueInt.from_bits(0xFF, 4, signed=True) + self.assertEqual(result, -1) + + def test_from_bits_sign_detection(self): + """Test sign bit detection at various widths""" + # 4-bit: 0x8 should be -8 when signed + result = ValueInt.from_bits(0x8, 4, signed=True) + self.assertEqual(result, -8) + + # 4-bit: 0x7 should be 7 when signed (positive) + result = ValueInt.from_bits(0x7, 4, signed=True) + self.assertEqual(result, 7) + + # 16-bit: 0x8000 should be -32768 when signed + result = ValueInt.from_bits(0x8000, 16, signed=True) + self.assertEqual(result, -32768) + + # 16-bit: 0x7FFF should be 32767 when signed (positive) + result = ValueInt.from_bits(0x7FFF, 16, signed=True) + self.assertEqual(result, 32767) + + def test_from_bits_edge_cases(self): + """Test edge cases""" + # 1-bit unsigned + result = ValueInt.from_bits(1, 1, signed=False) + self.assertEqual(result, 1) + + result = ValueInt.from_bits(0, 1, signed=False) + self.assertEqual(result, 0) + + # 1-bit signed: can only represent -1 and 0 + result = ValueInt.from_bits(1, 1, signed=True) + self.assertEqual(result, -1) + + result = ValueInt.from_bits(0, 1, signed=True) + self.assertEqual(result, 0) + + def test_from_bits_practical_dut_example(self): + """Test practical example from DUT interface""" + # Simulating a DUT returning 0xFFFFFFF0 from a 32-bit signed register + dut_val = 0xFFFFFFF0 + vsc_val = ValueInt.from_bits(dut_val, width=32, signed=True) + self.assertEqual(vsc_val, -16) + + # Simulating a DUT returning 0xFF from an 8-bit signed register + dut_val = 0xFF + vsc_val = ValueInt.from_bits(dut_val, width=8, signed=True) + self.assertEqual(vsc_val, -1) + + # Simulating a DUT returning 0xFF from an 8-bit unsigned register + dut_val = 0xFF + vsc_val = ValueInt.from_bits(dut_val, width=8, signed=False) + self.assertEqual(vsc_val, 255)