Numeric formats¶
Numeric helpers make Python and numpy values match finite hardware words. They are independent of cocotb and useful in functional models, codecs and unit tests that need exact raw representations.
Unsigned words¶
UIntFormat masks values to a selected width and chooses a suitable unsigned
numpy storage type. Values outside the range wrap through the raw word mask.
from fpga_verification.formats import UIntFormat, unsigned_storage_dtype
mono10 = UIntFormat(width=10)
raw_pixels = mono10.array([0, 1023, 1024, -1])
assert raw_pixels.tolist() == [0, 1023, 0, 1023]
print(raw_pixels.dtype)
print(hex(mono10.mask))
for width in (1, 9, 17, 33):
print(width, unsigned_storage_dtype(width))
zeros(), full(), array() and wrap() preserve the selected storage
representation. Use them instead of a bare numpy constructor when the bit
width is part of the test contract.
Fixed-point words¶
QFormat(qi, qf, signed) describes a stored fixed-point word: qi integer
bits, qf fractional bits, and a signed or unsigned two's-complement
interpretation. It exposes raw, scaled-integer and floating-point conversions.
from fpga_verification.formats import QFormat
unsigned = QFormat(qi=2, qf=0, signed=False)
signed = QFormat(qi=2, qf=0, signed=True)
print(unsigned.qraw_to_int(0b11)) # 3
print(signed.qraw_to_int(0b10)) # -2
q = QFormat(qi=3, qf=4, signed=True)
print(q.min_int, q.max_int)
print(q.min_float, q.max_float)
The distinction matters: a raw word is a bit pattern, an integer is its signed or unsigned interpretation, and a float includes the fractional scale.
q = QFormat(qi=3, qf=4, signed=False)
raw = q.float_to_qraw(3.75)
assert raw == 60
assert q.qraw_to_float(raw) == 3.75
Arrays, filling and random data¶
All conversions accept scalars and numpy-compatible arrays.
q = QFormat(qi=3, qf=2, signed=True)
raw = q.float_to_qraw([1.25, -1.0, 7.75])
restored = q.qraw_to_float(raw)
print(raw.tolist())
print(restored.tolist())
print(q.zeros(4))
print(q.ones(4))
print(q.full(4, 1.25))
print(q.full(4, 1, raw=True))
randomize(raw=True) samples raw bit patterns uniformly and returns the
unsigned storage dtype. randomize(raw=False) samples numeric values in the
representable float range; values are quantised only when converted through
float_to_qraw().
Saturation, wrapping and quantisation¶
Float conversion saturates by default. Set saturate=False only when hardware
semantics explicitly wrap.
q = QFormat(qi=3, qf=2, signed=True)
saturated = q.float_to_qraw([10.0, -10.0])
wrapped = q.float_to_qraw([10.0, -10.0], saturate=False)
quantized = q.float_to_qraw([0.49, -0.49])
print(q.qraw_to_float(saturated).tolist())
print(q.qraw_to_float(wrapped).tolist())
print(q.qraw_to_float(quantized).tolist())
Document the chosen overflow rule in the functional model. A silent choice between saturation and wrapping is a common source of model-versus-RTL mismatches.
Multiplication¶
multiply() operates on scaled integer representations. It can shift the
product to a requested fractional width but does not select an output word
width or apply the final overflow policy.
left = QFormat(qi=3, qf=2, signed=False)
right = QFormat(qi=2, qf=4, signed=False)
raw_product = left.multiply(10, right, 10)
result_format = QFormat(qi=5, qf=6, signed=False)
print(result_format.qraw_to_float(raw_product))
Create the intended output format explicitly, then choose saturation or masking at the hardware boundary. Keeping that final decision visible makes a functional model reviewable.