Cocotb is a coroutine-based cosimulation library for writing VHDL and Verilog testbenches in Python.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Cocotb's binary and type system provides HDL-compatible data types for representing and manipulating binary values, logic states, arrays, and ranges. The system supports multiple binary representations, 4-value logic, and HDL-style indexing and slicing operations.
Core class for representing binary values with multiple formats and automatic conversion capabilities.
class BinaryValue(value=None, n_bits=None, bigEndian=True, binaryRepresentation=BinaryRepresentation.UNSIGNED):
"""
Binary value representation supporting multiple formats and bit manipulations.
Parameters:
- value: Initial value (int, str, bytes, or BinaryValue)
- n_bits: Number of bits (auto-detected if None)
- bigEndian: True for big-endian bit ordering
- binaryRepresentation: Binary format (UNSIGNED, SIGNED_MAGNITUDE, TWOS_COMPLEMENT)
Properties:
- integer: Value as unsigned integer
- signed_integer: Value as signed integer
- binstr: Binary string representation
- buff: Raw buffer data
- n_bits: Number of bits
- is_resolvable: True if value contains no X/Z states
Methods:
- assign(value): Assign new value
- hex(): Hexadecimal string representation
"""
@property
def integer(self) -> int:
"""Get value as unsigned integer."""
@property
def signed_integer(self) -> int:
"""Get value as signed integer."""
@property
def binstr(self) -> str:
"""Get binary string representation."""
@property
def buff(self) -> bytes:
"""Get raw buffer data."""
@property
def n_bits(self) -> int:
"""Get number of bits."""
@property
def is_resolvable(self) -> bool:
"""Check if value contains no X/Z states."""
def assign(self, value):
"""
Assign new value to BinaryValue.
Parameters:
- value: New value to assign
"""
def hex(self) -> str:
"""
Get hexadecimal string representation.
Returns:
Hexadecimal string (e.g., "0xABCD")
"""
class BinaryRepresentation:
"""Binary representation format constants."""
UNSIGNED = "UNSIGNED"
SIGNED_MAGNITUDE = "SIGNED_MAGNITUDE"
TWOS_COMPLEMENT = "TWOS_COMPLEMENT"Usage Examples:
@cocotb.test()
async def binary_value_test(dut):
"""Test demonstrating BinaryValue operations."""
# Create binary values with different representations
unsigned_val = BinaryValue(42, n_bits=8, binaryRepresentation=BinaryRepresentation.UNSIGNED)
signed_val = BinaryValue(-42, n_bits=8, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT)
# Access different representations
cocotb.log.info(f"Unsigned 42: binary={unsigned_val.binstr}, hex={unsigned_val.hex()}")
cocotb.log.info(f"Signed -42: binary={signed_val.binstr}, hex={signed_val.hex()}")
# Convert from different sources
from_string = BinaryValue("10101010", n_bits=8)
from_int = BinaryValue(170, n_bits=8)
from_hex = BinaryValue(0xAA, n_bits=8)
# All should be equivalent
assert from_string.integer == from_int.integer == from_hex.integer
# Assign to DUT signals
dut.data_bus.value = unsigned_val
await Timer(10, units="ns")
# Read back and verify
readback = BinaryValue(dut.data_bus.value, n_bits=8)
assert readback.integer == 42
@cocotb.test()
async def binary_formats_test(dut):
"""Test different binary representation formats."""
# Test unsigned format
unsigned = BinaryValue(200, n_bits=8, binaryRepresentation=BinaryRepresentation.UNSIGNED)
cocotb.log.info(f"Unsigned 200: {unsigned.integer} (0x{unsigned.integer:02X})")
# Test two's complement
twos_comp = BinaryValue(-56, n_bits=8, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT)
cocotb.log.info(f"2's complement -56: signed={twos_comp.signed_integer}, unsigned={twos_comp.integer}")
# Test sign-magnitude (rarely used)
sign_mag = BinaryValue(-56, n_bits=8, binaryRepresentation=BinaryRepresentation.SIGNED_MAGNITUDE)
cocotb.log.info(f"Sign-magnitude -56: {sign_mag.binstr}")
# Test big vs little endian
big_endian = BinaryValue(0x1234, n_bits=16, bigEndian=True)
little_endian = BinaryValue(0x1234, n_bits=16, bigEndian=False)
cocotb.log.info(f"Big endian 0x1234: {big_endian.binstr}")
cocotb.log.info(f"Little endian 0x1234: {little_endian.binstr}")Utility functions for binary string manipulation and X/Z value resolution.
def resolve(string):
"""
Resolve X/Z values in binary string.
Converts X and Z characters to 0 for numeric operations.
Parameters:
- string: Binary string potentially containing X/Z
Returns:
Binary string with X/Z resolved to 0
"""Usage Examples:
@cocotb.test()
async def binary_utilities_test(dut):
"""Test demonstrating binary utility functions."""
# Handle X/Z values in signals
dut.test_signal.value = "10X1Z010" # String with X/Z
await Timer(1, units="ns")
# Read back signal value
signal_value = str(dut.test_signal.value)
cocotb.log.info(f"Signal with X/Z: {signal_value}")
# Resolve X/Z for numeric operations
resolved = resolve(signal_value)
cocotb.log.info(f"Resolved value: {resolved}")
# Convert to numeric value
numeric_val = BinaryValue(resolved)
cocotb.log.info(f"Numeric value: {numeric_val.integer}")
# Check if value is fully resolvable
test_values = ["11010101", "110X0101", "110Z0101", "XXXXXXXX"]
for test_val in test_values:
bv = BinaryValue(test_val)
cocotb.log.info(f"Value {test_val}: resolvable={bv.is_resolvable}")HDL-compatible logic types supporting 0, 1, X (unknown), and Z (high-impedance) states.
class Logic:
"""
4-value logic type supporting 0, 1, X, Z states.
Constants:
- Logic.0: Logic 0 state
- Logic.1: Logic 1 state
- Logic.X: Unknown/uninitialized state
- Logic.Z: High-impedance state
Operations:
- & (and): Bitwise AND operation
- | (or): Bitwise OR operation
- ^ (xor): Bitwise XOR operation
- ~ (not): Bitwise NOT operation
"""
# Type alias for backward compatibility
Bit = Logic
class LogicArray:
"""
Array of Logic values with HDL semantics.
Properties:
- binstr: Binary string representation
- integer: Integer value (X/Z resolved to 0)
- signed_integer: Signed integer value
Methods:
- to_BinaryValue(): Convert to BinaryValue object
"""
@property
def binstr(self) -> str:
"""Get binary string representation."""
@property
def integer(self) -> int:
"""Get integer value (X/Z resolved to 0)."""
@property
def signed_integer(self) -> int:
"""Get signed integer value."""
def to_BinaryValue(self):
"""
Convert to BinaryValue object.
Returns:
BinaryValue representation of the LogicArray
"""Usage Examples:
@cocotb.test()
async def logic_types_test(dut):
"""Test demonstrating 4-value logic operations."""
# Create individual logic values
from cocotb.types import Logic
zero = Logic.0
one = Logic.1
unknown = Logic.X
high_z = Logic.Z
# Logic operations
and_result = one & zero # Should be Logic.0
or_result = one | zero # Should be Logic.1
xor_result = one ^ one # Should be Logic.0
not_result = ~zero # Should be Logic.1
cocotb.log.info(f"Logic operations:")
cocotb.log.info(f" 1 & 0 = {and_result}")
cocotb.log.info(f" 1 | 0 = {or_result}")
cocotb.log.info(f" 1 ^ 1 = {xor_result}")
cocotb.log.info(f" ~0 = {not_result}")
# Unknown value propagation
unknown_and = one & unknown # Should be Logic.X
unknown_or = zero | unknown # Should be Logic.X
cocotb.log.info(f"Unknown propagation:")
cocotb.log.info(f" 1 & X = {unknown_and}")
cocotb.log.info(f" 0 | X = {unknown_or}")
@cocotb.test()
async def logic_array_test(dut):
"""Test demonstrating LogicArray operations."""
from cocotb.types import LogicArray, Logic
# Create logic arrays
array1 = LogicArray([Logic.1, Logic.0, Logic.1, Logic.0]) # 0b1010
array2 = LogicArray([Logic.0, Logic.1, Logic.X, Logic.Z]) # with X and Z
# Access properties
cocotb.log.info(f"Array1 binary: {array1.binstr}")
cocotb.log.info(f"Array1 integer: {array1.integer}")
cocotb.log.info(f"Array2 binary: {array2.binstr}")
cocotb.log.info(f"Array2 integer: {array2.integer}") # X/Z resolved to 0
# Convert to BinaryValue
bv1 = array1.to_BinaryValue()
bv2 = array2.to_BinaryValue()
cocotb.log.info(f"BinaryValue1: 0x{bv1.integer:X}")
cocotb.log.info(f"BinaryValue2 resolvable: {bv2.is_resolvable}")
# Use with DUT signals
dut.logic_signal.value = array1
await Timer(10, units="ns")HDL-compatible container types for arrays and ranges with proper indexing semantics.
class Array[T](obj, range):
"""
Fixed-size arbitrarily-indexed array.
Parameters:
- obj: Object to wrap as array
- range: Range object defining array bounds
Properties:
- range: Range object for array bounds
Supports:
- Indexing: array[index]
- Slicing: array[start:end]
- Iteration: for item in array
"""
@property
def range(self):
"""Get range object defining array bounds."""
class Range(left, direction, right):
"""
HDL-style inclusive range specification.
Parameters:
- left: Left bound of range
- direction: "to" or "downto"
- right: Right bound of range
Properties:
- left: Left bound
- right: Right bound
- direction: Range direction
Methods:
- to_range(): Convert to Python range object
- from_range(range): Create Range from Python range
"""
@property
def left(self) -> int:
"""Get left bound of range."""
@property
def right(self) -> int:
"""Get right bound of range."""
@property
def direction(self) -> str:
"""Get range direction ('to' or 'downto')."""
def to_range(self):
"""
Convert to Python range object.
Returns:
Python range object with equivalent bounds
"""
@classmethod
def from_range(cls, range):
"""
Create Range from Python range object.
Parameters:
- range: Python range object
Returns:
Range object with equivalent bounds
"""Usage Examples:
@cocotb.test()
async def container_types_test(dut):
"""Test demonstrating container type operations."""
from cocotb.types import Array, Range
# Create HDL-style ranges
range1 = Range(31, "downto", 0) # 32-bit word, MSB first
range2 = Range(0, "to", 7) # 8-bit byte, LSB first
cocotb.log.info(f"Range1: {range1.left} {range1.direction} {range1.right}")
cocotb.log.info(f"Range2: {range2.left} {range2.direction} {range2.right}")
# Convert to Python ranges for iteration
py_range1 = range1.to_range()
py_range2 = range2.to_range()
cocotb.log.info(f"Python range1: {list(py_range1)}")
cocotb.log.info(f"Python range2: {list(py_range2)}")
# Create arrays with specific ranges
if hasattr(dut, 'register_file'):
reg_array = Array(dut.register_file, Range(31, "downto", 0))
# Access array elements using HDL indexing
first_reg = reg_array[31] # MSB
last_reg = reg_array[0] # LSB
# Set values
first_reg.value = 0xDEAD
last_reg.value = 0xBEEF
await Timer(10, units="ns")
cocotb.log.info(f"Register[31]: 0x{first_reg.value:X}")
cocotb.log.info(f"Register[0]: 0x{last_reg.value:X}")
@cocotb.test()
async def array_slicing_test(dut):
"""Test demonstrating array slicing operations."""
from cocotb.types import Array, Range
# Create array representing a memory
if hasattr(dut, 'memory_block'):
memory = Array(dut.memory_block, Range(1023, "downto", 0))
# Write test pattern to slice
for i in range(16):
memory[i].value = i * 0x10
await Timer(50, units="ns")
# Read back slice
for i in range(16):
value = memory[i].value
expected = i * 0x10
assert value == expected, f"Memory[{i}]: expected {expected}, got {value}"
cocotb.log.info("Array slicing test passed")Operations for combining and manipulating HDL-compatible types.
def concat(a, b, *args):
"""
Concatenate arrays or values.
Parameters:
- a: First array/value
- b: Second array/value
- *args: Additional arrays/values to concatenate
Returns:
Concatenated result
"""Usage Examples:
@cocotb.test()
async def type_operations_test(dut):
"""Test demonstrating type operations."""
from cocotb.types import LogicArray, Logic, concat
# Create arrays to concatenate
high_nibble = LogicArray([Logic.1, Logic.0, Logic.1, Logic.1]) # 0xB
low_nibble = LogicArray([Logic.0, Logic.1, Logic.0, Logic.1]) # 0x5
# Concatenate to form byte
full_byte = concat(high_nibble, low_nibble)
cocotb.log.info(f"High nibble: {high_nibble.binstr}")
cocotb.log.info(f"Low nibble: {low_nibble.binstr}")
cocotb.log.info(f"Concatenated: {full_byte.binstr}")
cocotb.log.info(f"Result: 0x{full_byte.integer:02X}")
# Multiple concatenation
byte1 = LogicArray([Logic.1, Logic.1, Logic.0, Logic.0]) # 0xC
byte2 = LogicArray([Logic.0, Logic.0, Logic.1, Logic.1]) # 0x3
byte3 = LogicArray([Logic.1, Logic.0, Logic.1, Logic.0]) # 0xA
word = concat(byte1, byte2, byte3, low_nibble) # 16-bit word
cocotb.log.info(f"Multi-concat result: {word.binstr}")
cocotb.log.info(f"Word value: 0x{word.integer:04X}")
# Use with DUT
dut.concat_result.value = word
await Timer(10, units="ns")class SignedByte:
"""Custom wrapper for signed 8-bit values."""
def __init__(self, value=0):
self._value = BinaryValue(value, n_bits=8,
binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT)
@property
def value(self):
return self._value.signed_integer
@value.setter
def value(self, val):
self._value.assign(val)
@property
def binary_value(self):
return self._value
def __str__(self):
return f"SignedByte({self.value})"
@cocotb.test()
async def custom_types_test(dut):
"""Test demonstrating custom type wrappers."""
# Create signed byte values
sb1 = SignedByte(100)
sb2 = SignedByte(-50)
cocotb.log.info(f"Signed byte 1: {sb1}")
cocotb.log.info(f"Signed byte 2: {sb2}")
# Use with DUT
dut.signed_input.value = sb1.binary_value
await Timer(10, units="ns")
# Arithmetic with overflow handling
sb1.value = 127 # Maximum positive
cocotb.log.info(f"Max positive: {sb1}")
sb1.value = 128 # Wraps to -128
cocotb.log.info(f"Overflow to: {sb1}")def to_logic_array(value, width=None):
"""Convert various types to LogicArray."""
from cocotb.types import LogicArray, Logic
if isinstance(value, str):
# Convert binary string
logic_bits = []
for char in value:
if char == '0':
logic_bits.append(Logic.0)
elif char == '1':
logic_bits.append(Logic.1)
elif char.upper() == 'X':
logic_bits.append(Logic.X)
elif char.upper() == 'Z':
logic_bits.append(Logic.Z)
return LogicArray(logic_bits)
elif isinstance(value, int):
# Convert integer to LogicArray
if width is None:
width = value.bit_length() if value > 0 else 1
binary_str = format(value, f'0{width}b')
return to_logic_array(binary_str)
else:
raise ValueError(f"Cannot convert {type(value)} to LogicArray")
@cocotb.test()
async def conversion_test(dut):
"""Test demonstrating type conversion utilities."""
# Convert from different sources
from_string = to_logic_array("1010X01Z")
from_int = to_logic_array(0xAB, width=8)
cocotb.log.info(f"From string: {from_string.binstr}")
cocotb.log.info(f"From int: {from_int.binstr}")
# Use converted values
dut.test_input1.value = from_string
dut.test_input2.value = from_int
await Timer(20, units="ns")Install with Tessl CLI
npx tessl i tessl/pypi-cocotb