Search code examples
vhdl

Convert from signed to unsigned in VHDL properly


I have a signed accumulator that is used as the index to a BROM LUT. Anyways, I have error checking in my code to detect overflow/underflow events. These are absolutely critical as the application is the AGC of an analog RF front end, so underflowing might cause a booming signal to get maximum gain, blowing up our front end parts. Thus, I need to find a way to properly convert from signed to unsigned. For example, here is what I have so far:

library ieee;
...
use ieee.numeric_std.all;
...

process (clk)
    variable preSumV   : signed(accumToLut'left downto 0) := (others => '0');
    variable satCheckV : std_logic_vector(2 downto 0) := (others => '0');
begin
    if rising_edge(clk) then
        if reset = '1' then
            preSumV         := (others => '0');
            satCheckV       := (others => '0');
            overflow        <= '0';
            underflow       <= '0';
            accumToLut      <= (others => '0');
            accumToLutValid <= '0';
        else
            accumToLutValid <= mult.resultValid;

            -- accumulate
            if mult.resultValid = '1' then
                -- perform accum
                preSumV   := preSumV + mult.result(mult.result'left downto mult.result'left-accumToLut'length+1);
                satCheckV := accumToLut(accumToLut'left) & mult.result(mult.result'left) & preSumV(preSumV'left);

                -- check for signed OVF/saturation
                -- A,B is pos, sum neg = overflow so set max pos
                if satCheckV = "001" then
                    overflow <= '1';
                    accumToLut(accumToLut'left) <= '0';
                    accumToLut(accumToLut'left-1 downto 0) <= (others => '1');

                -- A,B is neg, sum pos = underflow so set max neg
                elsif satCheckV = "110" then
                    underflow <= '1';
                    accumToLut(accumToLut'left) <= '1';
                    accumToLut(accumToLut'left-1 downto 0) <= (others => '0');

                -- -- no overflow
                else
                    overflow   <= '0';
                    underflow  <= '0';
                    accumToLut <= preSumV;
                    --accumToLut <= preSumV(preSumV'left-1 downto 0);
                end if;
            end if;
        end if;
    end if;
end process;

accumToLutScaled <= accumToLut(accumToLut'left downto accumToLut'left-GainWordLookup'length+1);
index            <= unsigned(accumToLutScaled);
GainWordLookup   <= c_LinToDbLut(to_integer(accumToLutScaled));

The issue I am experiencing is the signed to unsigned conversion with the signal index. Because this is signed 2's complement, there is no change in the bits. Thus, when I set the accumToLut value to either the max/min signed value, this is not translating to the corresponding max/min unsigned value when I perform index <= unsigned(accumToLutScaled).

To provide an example, assume that preSumV, mult.result, and accumToLut are all 12 bits. When there is an overflow event, accumToLut gets set to 0x7FF or 0b0111111111111 which is valid. However, when I convert to unsigned, I would like this to be FFF, corresponding to the maximum entry in the LUT. Is it best to just add an offset to the index assignment, or is there a cleaner way of doing this?


Solution

  • From @Tricky, adding a fixed offset based on the size of my table, I have fixed this issue:

    index <= unsigned(accumToLutScaled + 2**(accumToLutScaled'length - 1))