VHDL constant value overflow

How do MyHDL people usually work around the VHDL size limit for integer literals? Consider the following MyHDL code:

Y_LEN = 71
Y_MIN, Y_MAX = (-2**(Y_LEN - 1), 2**(Y_LEN - 1))
y = Signal(intbv(0, Y_MIN, Y_MAX))

# An attempt to implement saturation somewhere in the code
if result < 0:
    accumulator.next = Y_MIN
else:
    accumulator.next = Y_MAX - 1

This converts to VHDL as follows:

if (result < 0) then
    accumulator <= signed'("11111111111111111111111111111111111100000000000000000000000000000000000");
else
    accumulator <= to_signed(34359738368 - 1, 71);
end if;

The first case translates to a bit string literal, which is fine (although hexadecimal would be much easier to read than binary at these lengths). The second case with the arithmetic operation translates to an integer literal that is too large – at least as far as the compiler is concerned. Tweaking the constants would take care of this, but all the added plus ones and minus ones are not exactly good for reducing off-by-one errors. Are there any other options?

Would it be difficult or undesirable for some reason to have MyHDL compute the result of constant expressions and replace them with bit string literals, just like in the case of a single constant?

IMHO it should be fairly straightforward to do, and surely an improvement. It would even be nicer if his code would generate a constant declaration:

    constant prefix_Y_MIN : signed(70 downto 0) := b"100...000...000...000";
    constant prefix_Y_MAX : signed(70 downto 0) := x"3fffffffffffffffff";
    constant prefix_Y_MAX_ALT : signed(70 downto 0) := (x"3fffffffffffffffff")(70 downto 0);

I’m not sure whether the hexadecimal notation will apply if the bits are a: not a multiple of 4 and b: more than 32. Perhaps the the alternative notation works?

BTW: 34359738368 != 2**70, neither is the binary string equal to -2**70

Ah, yes! Constant declarations would be sweet. I could try implementing them myself, but I find the MyHDL source code a bit hard to navigate, so I don’t know where to look for it.

Hexadecimal string literals need to contain a multiple of four bits, but there is no limit to the length. Only integers are limited to 32 bits. (Or is it 34 bits in the spec? Vendor tools may have the 32-bit limit anyway.)

True. The saturation is at 36 bits. I copied a piece of code without thinking, so the Y_MIN and Y_MAX are not what they are supposed to be.

I wonder whether the community is ready for a Constant type, but I think we can do it under the radar by tweaking the _toVHDL.py code.
It would be nice if you’d provide small but complete example code; it saves me from having to type (and think) to get a test running.

Would this Q0.x fixed-point gain be small enough?

"""Scale fixed-point signal"""

from myhdl import block, always_comb, always_seq
from myhdl import Signal, ResetSignal, intbv

@block
def rescale(clk, reset, x, gain, shift, y):
    """Rescale with fractional gain and bit shift

    The rescale block operates with signed (two's complement) fixed-point
    numbers [-1.0, 1.0) with the following roles:
        Input:   x
        Output:  y = gain * x << shift
    """
    X_LEN = len(x)
    Y_LEN = len(y)
    GAIN_LEN = len(gain)
    Y_MIN, Y_MAX_M1 = (-2**(Y_LEN - 1), 2**(Y_LEN - 1) - 1)
    ZERO_SHIFT = GAIN_LEN - 1 + X_LEN - Y_LEN
    RESULT_LEN = Y_LEN + shift
    RESULT_MIN, RESULT_MAX = (-2**(RESULT_LEN - 1), 2**(RESULT_LEN - 1))
    
    # Internal signals
    result = Signal(intbv(0, RESULT_MIN, RESULT_MAX))
    
    @always_comb
    def logic():
        """Rescale"""
        result.next = x * gain >> ZERO_SHIFT - shift

    @always_seq(clk.posedge, reset=reset)
    def propagate():
        """Update y, and saturate in case of overflow"""
        if result[:Y_LEN].signed() > 0:
            y.next = Y_MAX_M1
        elif result[:Y_LEN].signed() < -1:
            y.next = Y_MIN
        else:
            y.next = result

    return logic, propagate

# Auxiliary functions

def convert(hdl):
    """Convert the design to HDL"""
    clk = Signal(bool(0))
    reset = ResetSignal(0, active=1, async=True)
    gain, shift = [Signal(intbv(0)[32:]) for _ in range(2)]
    x, y = [Signal(intbv(0)[32:]) for _ in range(2)]
    rtl = rescale(clk, reset, x, gain, shift, y)
    rtl.convert(hdl)

if __name__ == '__main__':
    convert('VHDL')

It doesn’t have constant arithmetic expressions aside from the declaration, though, but one could change the following lines to make one:

Y_MIN, Y_MAX = (-2**(Y_LEN - 1), 2**(Y_LEN - 1))
y.next = Y_MAX - 1

Edit: The original example was purely combinational, but I’m not sure not sure whether it was actually synthesizable.

With VHDL 2008, you can specify the bit length of the literal : 3X"2" is equal to “010”.
The MyHDL VHDL converter already uses this notation.

We already spoke about this before in other threads. I think we need constants in MyHDL.
I already started to code such a functionality but only for VHDL, not for verilog.

1 Like

I maybe should have said senate in stead of community :slight_smile:

Do you mean this one? What do you guys think about adding generics support in the constant type? In my opinion, the constructor for the constant type could have a named parameter with default value parameter=False. If parameter were set to True, a generic would be generated instead of a constant.

@mhavu
No, not this one.
There are few threads where it is discussed. I don’t remember which one exactly except this one : VHDL conversion - missing constant

From my point of view, the problem with MyHDL dev is with PRs that are very long to be accepted/rejected ( A future for the MyHDL community?)

I got bit by this again. MyHDL doesn’t always handle the large constants correctly. Take, for example, this piece of MyHDL-generated VHDL:

if (signed(unsigned(rescale1_result(68-1 downto drop))) >= 68719476735) then
            scaled_output <= signed'("011111111111111111111111111111111111");
            rescale2_overflow <= '1';
        elsif (signed(unsigned(rescale1_result(68-1 downto drop))) < (- 68719476736)) then
            scaled_output <= signed'("100000000000000000000000000000000000");
            rescale2_overflow <= '1';
        else
            scaled_output <= resize(signed(unsigned(rescale1_result(68-1 downto (drop + 1)))) + to_signed(rescale1_result(drop), 2), 36);
            rescale2_overflow <= '0';
        end if;

68719476735 and -68719476736 are values of two MyHDL constants. They are over 32 bits long, so I would have expected to see them converted to bit string literals. However, the conversion result is a 37-bit wide integer that breaks the compilation in Quartus. Can someone point me to a place in the MyHDL source code where this happens, so I can fix it? (Or if @DrPi’s constant type is ready, I’d be glad to test it.)

Can you provide a fully functional minimal example reproducing the problem ?

In this case as the test-values are a power of 2 you could, as a work-around, use simple bit checking:

SCALING = 36
if not rescale_result[high] and rescale_result[high - 1 : drop + SCALING] != 0:
    scaled_output.next =  ...
elif rescale_result[high] and rescale_result[high - 1 : drop + SCALING] == 0:
    scaled_output.next = ....
else:
   ...

This is how I did it in VHDL

@DrPi, here’s a minimum working example:

from myhdl import block, always_comb, Signal, intbv

@block
def test_constant(x, y):
    THRESHOLD = 2**(len(x) - 2)
    
    @always_comb
    def logic():
        if x > THRESHOLD:
            y.next = 1
        else:
            y.next = 0

    return logic

if __name__ == '__main__':
    x = Signal(intbv(0, -2**35, 2**35))
    y = Signal(bool(0))
    rtl = test_constant(x, y)
    rtl.convert('VHDL')

The VHDL conversion results in:

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;

use work.pck_myhdl_010.all;

entity test_constant is
    port (
        x: in signed (35 downto 0);
        y: out std_logic
    );
end entity test_constant;


architecture MyHDL of test_constant is



begin




TEST_CONSTANT_LOGIC: process (x) is
begin
    if (x > 17179869184) then
        y <= '1';
    else
        y <= '0';
    end if;
end process TEST_CONSTANT_LOGIC;

end architecture MyHDL;

The threshold constant 17179869184 is wider than 32 bits, but it is not expressed as a bit string literal.

@josyb, I used to do that (check that the high bits are either all 0 or all 1). If I remember correctly, it results in fewer FPGA resources being used as a bonus. However, if you want rounding, a new case is introduced (high bits all 0, low bits all 1), so the code would be much more readable with simple >= and < checks.

As a quick workaround, you can declare THRESHOLD like this :

THRESHOLD = Signal(intbv(2**(len(x) - 2), -2**35, 2**35))

I’m working on a better solution.

1 Like

The fix is 4 lines of code in _toVHDL.py module.

In _AnnotateTypesVisitor() class, the new code for visit_Compare() function is the following :

    def visit_Compare(self, node):
        node.vhd = vhd_boolean()
        self.generic_visit(node)
        left, op, right = node.left, node.ops[0], node.comparators[0]
        if isinstance(left.vhd, vhd_std_logic) or isinstance(right.vhd, vhd_std_logic):
            left.vhd = right.vhd = vhd_std_logic()
        elif isinstance(left.vhd, vhd_unsigned) and maybeNegative(right.vhd):
            left.vhd = vhd_signed(left.vhd.size + 1)
        elif maybeNegative(left.vhd) and isinstance(right.vhd, vhd_unsigned):
            right.vhd = vhd_signed(right.vhd.size + 1)
        if isinstance(left.vhd, (vhd_nat, vhd_int)) :    # New
            left.vhd = right.vhd                         # New
        if isinstance(right.vhd, (vhd_nat, vhd_int)) :   # New
            right.vhd = left.vhd                         # New
        node.vhdOri = copy(node.vhd)
1 Like