Variable Types
AVL provides a number of variable types more appropriate for HDL design than the standard Python types.
Python lacks 4-state supports. As such all cocotb and AVL variables are 2-state.
Python lacks width support for integers. AVL adds native width and sign support including half, single and double precision floating point numbers.
System Verilog Type |
Python Type |
AVL Variable |
|---|---|---|
shortint |
int |
avl.Int16 |
int |
int |
avl.Int32 |
longint |
int |
avl.Int64 |
byte |
int |
avl.Byte / avl.Int8 |
bit |
bool |
avl.Logic / avl.Bool |
logic |
int |
avl.Logic / avl.Unit8 / avl.Uint16 / avl.Uint32 / avl.Uint64 |
reg |
int |
|
integer |
int |
avl.Int / avl.Int32 |
time |
int |
avl.Int64 |
real |
float |
avl.Double / avl.Fp64 |
shortreal |
float |
avl.Half / avl.Fp32 |
float |
avl.Float / avl.Fp16 |
|
string |
str |
str |
enum |
Enum |
avl.Enum |
Variable Usage
AVL attempts to make variable usage as close to Python and SystemVerilog as possible.
All AVL variable types inherit from the avl.Var base class which provides the magic methods to make the variables behave like their Python and SystemVerilog counterparts.
The only exception to this is when attempting to directly update the value of a variable, when the user must update the underlying value explicitly.
Unlike python ints avl_vars are mutable, so the user can update the value of the variable directly without creating a new handle. AVL variables can be used in the same way as Python variables, and can be used in expressions, assignments, and function calls. This is to allow variables saved for lambda functions to be preserved.
avl.Logic objects are unsigned and support constraints usings bitwise operations. However, due to this they can be slower to randomize than avl.Int and avl.Uint objects which only support arithmetic constraints.
avl.Logic and its children (i.e. avl.Uint, avl.Int and avl.Bool) all support bit slice access with the following constraints:
Single-index slices are supported, e.g.
myvar[31]will return bit 31 ofmyvar.Slice indexes follow the same rule as for strings and lists:
myvar[0:8]will access bits 0 to 7 ofmyvar.Slice indexes must always be positive.
SystemVerilog-like accesses where the greater index is first (e.g.
myvar[8:0]), are not supported.Steps (e.g.
myvar[0:8:1]) are not supported.
Integer / Logic Example
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import avl
import cocotb
from z3 import And
class example_env(avl.Env):
def test_unsigned(self):
self.info("Testing unsigned logic and arithmetic operations...")
a = avl.Logic(5, width=4) # 0b0101
b = avl.Logic(13, width=4) # 0b1101
c = 1 + a
assert c == 6 # (1 + 5) % 16 = 6
assert type(c) is avl.Logic # Ensure c is still a Logic object
# Constants
mod = 1 << 4 # 16
assert mod == 16
# Arithmetic
assert a + b == 2 # (5 + 13) % 16 = 18 % 16 = 2
assert a - b == 8 # (5 - 13) % 16 = -8 % 16 = 8
assert b - a == 8 # (13 - 5) % 16 = 8
assert -a == 11 # (-5) % 16 = 11
assert abs(b) == 13
# Overflow / wraparound
max_val = avl.Logic(15, width=4)
assert max_val + 1 == 0 # 15 + 1 = 16 % 16 = 0
min_val = avl.Logic(0, width=4)
assert min_val - 1 == 15 # 0 - 1 = -1 % 16 = 15
# Multiplication / division
assert a * 3 == 15 # (5 * 3) % 16 = 15
assert b // 2 == 6 # 13 // 2 = 6
assert b % 4 == 1 # 13 % 4 = 1
# Comparisons
assert (a < b) is True # 5 < 13
assert (a > b) is False
assert (a == 5) is True
assert (b != 5) is True
assert (a <= 5) is True
assert (b >= 13) is True
# Bitwise operations
assert a & b == 5 # 0b0101 & 0b1101 = 0b0101 = 5
assert a | b == 13 # 0b0101 | 0b1101 = 0b1101 = 13
assert a ^ b == 8 # 0b0101 ^ 0b1101 = 0b1000 = 8
assert ~a == 10 # ~5 = -6 % 16 = 10
# Shifts
assert a << 1 == 10 # (5 << 1) % 16 = 10
assert b >> 1 == 6 # 13 >> 1 = 6
# Oversized int operands
assert a + 1000 == 13 # (5 + 1000) % 16 = 1005 % 16 = 13
assert a - 300 == 9 # (5 - 300) % 16 = -295 % 16 = 9
assert a * 123 == 7 # (5 * 123) % 16 = 615 % 16 = 7
assert b // 999 == 0 # 13 // 999 = 0
assert b % 99 == 13 # 13 % 99 = 13
# Comparisons with large ints
assert (a < 1000) is True
assert (b < 1000) is True
assert (a > -1000) is True
assert (b != 999) is True
# Bitwise operations with oversized ints
assert a & 0b10011010010 == 0 # 5 & (1234 % 16 = 2) = 0b0101 & 0b0010 = 0b0000 = 0
assert a | 0b10011010010 == 7 # 5 | 2 = 0b0101 | 0b0010 = 0b0111 = 7
assert a ^ 0b10011010010 == 7 # 5 ^ 2 = 0b0101 ^ 0b0010 = 0b0111 = 7
# Immutability check
a_id = id(a)
c = a + 1
assert c == 6
assert id(a) == a_id
assert id(c) != a_id
a += 1
assert a == 6
assert id(a) == a_id # a should still point to the same object
assert id(c) != a_id # c should still point to a different object
# slices
c = avl.Logic(0xdeadbeef, width=32)
assert c[0:8] == 0xef
assert c[24:32] == 0xde
assert c[8:24] == 0xadbe
assert c[19] == 1
assert c[14] == 0
c[8:16] = 0xab
assert c == 0xdeadabef
c[3] = 0
assert c == 0xdeadabe7
self.info("Unsigned logic and arithmetic operations passed successfully.")
def test_randomization(self):
self.info("Testing randomization...")
# Create a random Logic variable
rand_var = avl.Logic(0, auto_random=True, width=4)
# Check if the value is within the expected range
assert 0 <= rand_var < (1 << 4)
# Simple randomization
for _ in range(10):
rand_var.randomize(hard=[lambda x: And(x >=1, x <= 2)])
assert rand_var >= 1 and rand_var <= 2
# Bitwise
for _ in range(10):
rand_var.randomize(hard=[lambda x: And(x & 0b0001 == 0, x & 0b0010 == 0)])
assert rand_var.value % 4 == 0
rand_var.randomize(hard=[lambda x: (x ^ 0b0011) & 0b0011 == 0])
assert rand_var.value % 4 == 3
self.info("Randomization test passed successfully.")
def __init__(self, name, parent):
super().__init__(name, parent)
# Test unsigned arithmetic operations
self.test_unsigned()
# Test randomization
self.test_randomization()
@cocotb.test
async def test(dut):
_ = example_env("env", None)
Float / Real Example
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import avl
import cocotb
import numpy as np
from cocotb.triggers import Timer
class example_env(avl.Env):
def test_avl_fp16(self):
a = avl.Fp16(2.0)
b = avl.Fp16(3.0)
# Arithmetic
assert a + b == 5.0 # 2.0 + 3.0 = 5.0
c = avl.Fp16(a)
c += b
assert c == 5.0 # 2.0 + 3.0 = 5.0
assert a - b == -1.0 # 2.0 - 3.0 = -1.0
c = avl.Fp16(a)
c -= b
assert c == -1.0 # 2.0 - 3.0 = -1.0
assert a * b == 6.0 # 2.0 * 3.0 = 6.0
c = avl.Fp16(a)
c *= b
assert c == 6.0 # 2.0 * 3.0 = 6.0
assert b / a == 1.5 # 3.0 / 2.0 = 1.5
c = avl.Fp16(b)
c /= a
assert c == 1.5 # 3.0 / 2.0 = 1.5
# Power
assert a ** 2 == 4.0 # 2.0 ** 2 = 4.0
c = avl.Fp16(a)
c **= 2
assert c == 4.0 # 2.0 ** 2 = 4.0
# Comparisons
assert a < b # 2.0 < 3.0
assert not (a > b)
assert a <= b
assert not (a >= b)
assert a != b
assert a == avl.Fp16(2.0)
# String
assert str(a) == "2.0" # str of float16
assert isinstance(repr(a), str)
# === Special values ===
# Positive infinity
pos_inf = avl.Fp16(np.inf)
assert pos_inf > b # inf > 3.0
assert str(pos_inf) == "inf"
# Negative infinity
neg_inf = avl.Fp16(-np.inf)
assert neg_inf < b # -inf < 3.0
assert str(neg_inf) == "-inf"
# NaN
nan = avl.Fp16(np.nan)
assert nan != nan # NaN is not equal to anything
assert not (nan < b) # Comparisons with NaN are always False
assert not (nan > b)
assert not (nan == b)
assert str(nan) == "nan"
# Overflow to inf
large = avl.Fp16(1e5)
assert large.value == np.inf # np.float16 overflows at ~65504
# Underflow to 0
tiny = avl.Fp16(1e-10)
assert tiny.value == 0.0 # underflow to zero in float16
def test_avl_fp32(self):
a = avl.Fp32(2.0)
b = avl.Fp32(3.0)
# Arithmetic
assert a + b == 5.0 # 2.0 + 3.0 = 5.0
c = avl.Fp32(a)
c += b
assert c == 5.0 # 2.0 + 3.0 = 5.0
assert a - b == -1.0 # 2.0 - 3.0 = -1.0
c = avl.Fp32(a)
c -= b
assert c == -1.0 # 2.0 - 3.0 = -1.0
assert a * b == 6.0 # 2.0 * 3.0 = 6.0
c = avl.Fp32(a)
c *= b
assert c == 6.0 # 2.0 * 3.0 = 6.0
assert b / a == 1.5 # 3.0 / 2.0 = 1.5
c = avl.Fp32(b)
c /= a
assert c == 1.5 # 3.0 / 2.0 = 1.5
# Power
assert a ** 2 == 4.0 # 2.0 ** 2 = 4.0
c = avl.Fp32(a)
c **= 2
assert c == 4.0 # 2.0 ** 2 = 4.0
# Comparisons
assert a < b
assert not (a > b)
assert a <= b
assert not (a >= b)
assert a != b
assert a == avl.Fp32(2.0)
# String
assert str(a) == "2.0"
assert isinstance(repr(a), str)
# Special values
pos_inf = avl.Fp32(np.inf)
assert pos_inf > b
assert str(pos_inf) == "inf"
neg_inf = avl.Fp32(-np.inf)
assert neg_inf < b
assert str(neg_inf) == "-inf"
nan = avl.Fp32(np.nan)
assert nan != nan
assert not (nan < b)
assert not (nan > b)
assert not (nan == b)
assert str(nan) == "nan"
large = avl.Fp32(1e40)
assert large.value == np.inf
tiny = avl.Fp32(1e-50)
assert tiny.value == 0.0
def test_avl_fp64(self):
a = avl.Fp64(2.0)
b = avl.Fp64(3.0)
# Arithmetic
assert a + b == 5.0
c = avl.Fp64(a)
c += b
assert c == 5.0
assert a - b == -1.0
c = avl.Fp64(a)
c -= b
assert c == -1.0
assert a * b == 6.0
c = avl.Fp64(a)
c *= b
assert c == 6.0
assert b / a == 1.5
c = avl.Fp64(b)
c /= a
assert c == 1.5
# Power
assert a ** 2 == 4.0
c = avl.Fp64(a)
c **= 2
assert c == 4.0
# Comparisons
assert a < b
assert not (a > b)
assert a <= b
assert not (a >= b)
assert a != b
assert a == avl.Fp64(2.0)
# String
assert str(a) == "2.0"
assert isinstance(repr(a), str)
# Special values
pos_inf = avl.Fp64(np.inf)
assert pos_inf > b
assert str(pos_inf) == "inf"
neg_inf = avl.Fp64(-np.inf)
assert neg_inf < b
assert str(neg_inf) == "-inf"
nan = avl.Fp64(np.nan)
assert nan != nan
assert not (nan < b)
assert not (nan > b)
assert not (nan == b)
assert str(nan) == "nan"
large = avl.Fp64(1e310)
assert large.value == np.inf
tiny = avl.Fp64(1e-400)
assert tiny.value == 0.0
def __init__(self, name, parent):
super().__init__(name, parent)
self.test_avl_fp16()
self.test_avl_fp32()
self.test_avl_fp64()
# Variables for testing HDL interaction
self.fp16 = avl.Fp16(0.0)
self.fp32 = avl.Fp32(0.0)
self.fp64 = avl.Fp64(0.0)
@cocotb.test
async def test(dut):
e = example_env("env", None)
await Timer(100, unit="ns")
e.fp16 += -0.3
assert e.fp16 == -0.3
dut.fp16.value = e.fp16.to_bits()
e.fp32 += 11.4
assert e.fp32 == 11.4
dut.fp32.value = e.fp32.to_bits()
e.fp64 += 110202.0821
assert e.fp64 == 110202.0821
dut.fp64.value = e.fp64.to_bits()
await Timer(100, unit="ns")
# Read back and check
fp16 = avl.Fp16(0.0)
fp16.from_bits(dut.fp16.value)
assert fp16 == e.fp16
fp32 = avl.Fp32(0.0)
fp32.from_bits(dut.fp32.value)
assert fp32 == e.fp32
fp64 = avl.Fp64(0.0)
fp64.from_bits(dut.fp64.value)
assert fp64 == e.fp64
Enum Example
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import avl
import cocotb
class example_env(avl.Env):
def __init__(self, name, parent):
super().__init__(name, parent)
self.e = avl.Enum("A", {"A": 0, "B": 1, "C": 2})
# Add a constraint
self.e.add_constraint("c", lambda x: x != self.e.A)
@cocotb.test
async def test(dut):
e = example_env("env", None)
# Direct allocation - must use .value to avoid trampling the class
e.e.value = e.e.B
assert e.e == e.e.B
# Increment
e.e += 1
assert e.e == e.e.C
# Random
for _ in range(10):
e.e.randomize()
assert e.e in e.e.values.values()