The expected DUT output in the previous example was derived as sum +=
(a *$8 b)
(in other words, sum = sum + (a *$8 b)
). The
multiply operator here is not just *; it is explicitly given as an
unsigned, 8-bit multiply. This is an important point: Maia operators
look like hardware function units, and coding complex arithmetic looks like
hardware. You can, if necessary, use plain operators and plain
(signed) int variables, and the language will then try to work out
what you actually wanted, in much the same way as C, but this may not be the
right thing to do.
In short: the language does not, in general, attempt to deduce your intent. Variables ('objects') do not have properties apart from their size. They are not signed, or unsigned, or floating-point, or 1's complement, or 2's complement: they are just data. The relationship between variables and operators is the same as the relationship between hardware memory and hardware function units: in other words, 'memory' simply stores arbitrary data patterns.
This is generally a good fit for modelling and verifying hardware, but does have its own issues: carrying out floating-point arithmetic is long-winded, for example. On the other hand, FP arithmetic is of little use in hardware verification (you can't, for example, verify a floating-point multiplier by comparing the FPU output against the result which is produced by the * operator in whatever CPU, language, and FPU setup you're using). This is, in software enginering terms, pretty much the exact opposite of being 'Object Oriented'.
This example shows some more complex arithmetic. It calculates the 130-bit result of a single round of a Poly1305 (RFC8439) message authenticator. I use this function as part of a unit test of my own VHDL Poly1305 implementation. The RTL is difficult, because of the requirement to keep track of the bit widths and limits of the operations, and the modulo reduction operation. The RTL result is compared against this Maia implementation:
bit130 round(bit128 r, bit128 block, int nbytes) { static bit128 clamp = 128'h0fff_fffc_0fff_fffc_0fff_fffc_0fff_ffff; static bit130 primep = 130'h3_ffff_ffff_ffff_ffff_ffff_ffff_ffff_fffb; static bit130 acc130 = 0; // static variables maintain their bit129 blk129; // values between function calls bit131 sum131; bit256 mul256; r &= clamp; // down to 124 bits blk129 = byte_swap(block, nbytes); // byte swap and expand by 1 bit sum131 = acc130 +$131 blk129; // 130+129 bits = 131 bits mul256 = sum131 *$256 r; // result known to fit in 255 bits acc130 = mul256 % primep; // modulo reduction assert(acc130 < primep); return acc130; }