dataclass is more of a Python feature than a cocotb feature, but I find it most useful to write concise and cleaner code with cocotb. I mainly use dataclass with configuration objects (or sometimes transactions), as it gives useful dunder methods for free with dataclass similar to NamedTuple. The main difference is NamedTuple is immutable.
What is dataclass Link to heading
PEP557 describes what dataclass provides:
So, why is this PEP needed?
With the addition of PEP 526, Python has a concise way to specify the type of class members. This PEP leverages that syntax to provide a simple, unobtrusive way to describe Data Classes. With two exceptions, the specified attribute type annotation is completely ignored by Data Classes.
Basically, dataclass gives these methods:
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0) -> None:
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
def __repr__(self):
return f'InventoryItem(name={self.name!r}, unit_price={self.unit_price!r}, quantity_on_hand={self.quantity_on_hand!r})'
def __eq__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) == (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
def __ne__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) != (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
def __lt__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) < (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
def __le__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) <= (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
def __gt__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) > (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
def __ge__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) >= (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
Example Link to heading
This is a small example using dataclass and Enum to define a configuration (used as a singleton or passed around between cocotb components). field is also a way to provide default values for fields to be overridden by __init__.
from dataclasses import dataclass, field
from enum import Enum
import cocotb
from cocotb.triggers import Timer
class DeviceType(Enum):
T1 = 0
T2 = 1
@dataclass
class PortCfg:
device_type: DeviceType = field(default=DeviceType.T1)
@dataclass
class DeviceCfg:
num_ports: int = field(default=0)
port_cfg: list[PortCfg] = field(default_factory=list)
class TB():
def __init__(self, dut):
self.dut = dut
self.cfg = DeviceCfg(num_ports=2, port_cfg=[PortCfg(), PortCfg(device_type=DeviceType.T2)])
async def run(self):
# Configuration
cocotb.log.info("="*20)
cocotb.log.info(f"Device has configuration {self.cfg}")
cocotb.log.info("="*20)
# Running
# Teardown
@cocotb.test()
async def test_device_cfg(dut):
tb = TB(dut)
cocotb.start_soon(tb.run())
await Timer(100, unit="ns")
0.00ns INFO test ====================
0.00ns INFO test Device has configuration DeviceCfg(num_ports=2, port_cfg=[PortCfg(device_type=<DeviceType.T1: 0>), PortCfg(device_type=<DeviceType.T2: 1>)])
0.00ns INFO test ====================
PS. There are more advanced packages, such as Pydantic, that provide more features such as validation. But dataclass does the job for me, considering I only use external packages if I have to.