Structs
AVL provides a generic structs wrapper.
The main reason for including a struct wrapper is Verilator flattens all structs into a single variable. This means that the user must manually manage the offsets of each field in the struct. This is error prone, difficult to debug and maintain.
Commercial simulators usually provide PLI/VPI access to individual fields in structs, but the avl.Struct can still be useful to ensure a test-bench works across all simulators.
The variables declared in the struct must be variations of the avl.Var class, as these classes have understanding of the width and therefore can be used to pack and unpack the struct.
The declaration order of variables matches those of the Verilog struct syntax.
Example
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import copy
import avl
import cocotb
from cocotb.triggers import Timer
class packed_struct_t(avl.Struct):
single_bit : avl.Bool = avl.Bool(False)
multi_bit : avl.Uint32 = avl.Uint32(0)
state_enum : avl.Enum = avl.Enum("S0", {"S0" : 0, "S1" : 1, "S2" : 2})
class example_env(avl.Env):
def __init__(self, name, parent):
super().__init__(name, parent)
self.s0 = packed_struct_t()
self.s1 = packed_struct_t()
async def run_phase(self):
self.raise_objection()
for i in range(10):
await Timer(10, units="ns")
self.dut.value = self.s0.to_bits()
await Timer(1, "ns")
self.s1.from_bits(self.dut)
assert i%2 == self.s0.single_bit == self.s1.single_bit
assert i == self.s0.multi_bit == self.s1.multi_bit
assert self.s0.state_enum == self.s1.state_enum
self.s0.single_bit += 1
self.s0.multi_bit += 1
if bool(self.s0.single_bit):
self.s0.state_enum.value = "S2"
else:
self.s0.state_enum.value = "S0"
# Test randomization
self.s0_copy = copy.deepcopy(self.s0)
self.s0.multi_bit.add_constraint("c_multi_bit", lambda x: x < 100)
self.s0_copy.multi_bit.add_constraint("c_multi_bit", lambda x: x == 200)
self.s0.single_bit.value = 0
self.s0.multi_bit.value = 0
self.s0.state_enum.value = "S0"
for _ in range(10):
await Timer(10, units="ns")
self.randomize()
self.dut.value = self.s0.to_bits()
await Timer(1, "ns")
self.s1.from_bits(self.dut)
assert self.s0.single_bit == self.s1.single_bit
assert self.s0.multi_bit == self.s1.multi_bit
assert self.s0.multi_bit < 100
assert self.s0.state_enum == self.s1.state_enum
assert self.s0_copy.multi_bit.value == 200
await Timer(10, units="ns")
self.drop_objection()
@cocotb.test
async def test(dut):
e = example_env("env", None)
e.dut = dut.data
await e.start()