Search code examples
vhdlquartusintel-fpga

How do you appropriately multiply std_logic:vector in VHDL?


I'm trying to make a module to manipulate a servomotor sg90. But I'm having problems with a part of the architecture; the module has an entry of 6 bits which controls where I want the servomotor to be at, but controls the motor with a 16bit vector. My way of doing this was multiplying a variable of 6 bits (that has the same value as the entry) and putting that on the 16bit out vector, something like this:

case position is
    when "000000" =>
        value:= X"0ccc";
    when "111111" =>
        value := X"1999";
    when others =>
        value:=std_logic_vector((control*52)+3276);
end case; 

What this should do is, for instance, if I put "000000" the out would be "0ccc", putting the servomotor on its start position. "111111" would be "1999" or the end position end everything else in between should be considered by that expression. But, I'm getting the following error:

Error (10327): VHDL error at ServomotorF.vhd(46): can't determine definition of operator ""*"" -- found 0 possible definitions

If it helps, the libraries I'm using are

use ieee.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.std_logic_unsigned.all;

I also tried using numeric_std but that just gives me more errors. The only other solution I can think of is doing one by one using a giant case structure.

if I use "unsigned" I get the error of multiple definitions of unsigned.


Solution

  • The mathematics of it is simple:

    value_out <= value_in * STEP_SIZE + MIN_VALUE_OUT;
    

    But VHDL requires a bit more effort, in essence:

    constant MIN_VALUE_IN: natural := 0;
    constant MAX_VALUE_IN: natural := 16#3F#;
    constant MIN_VALUE_OUT: natural := 16#0CCC#;
    constant MAX_VALUE_OUT: natural := 16#1999#;
    constant STEP_SIZE: natural := natural(floor(real(MAX_VALUE_OUT - MIN_VALUE_OUT) / real(MAX_VALUE_IN - MIN_VALUE_IN)));  -- Beware of rounding errors.
    signal std_in, std_out: std_logic_vector(5 downto 0);
    signal value_in, value_out: natural;
    
    value_in <= to_integer(unsigned(std_in));
    value_out <= value_in * STEP_SIZE + MIN_VALUE_OUT;
    std_out <= std_logic_vector(to_unsigned(value_out, std_out'length));
    

    Below is the full implementation of a scaler in VHDL. V1 calculates the scaled value in the VHDL, and V2 selects scaled values from a look up table pre-calculated by the compiler.

    Scale

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use ieee.math_real.all;
    
    entity Scale is
        generic
        (
            MIN_VALUE_IN: natural := 0;
            MAX_VALUE_IN: natural := 16#3F#;
            MIN_VALUE_OUT: natural := 16#0CCC#;
            MAX_VALUE_OUT: natural := 16#1999#
        );
        port
        (
            value_in: in natural range MIN_VALUE_IN to MAX_VALUE_IN;
            value_out: out natural range MIN_VALUE_OUT to MAX_VALUE_OUT
        );
    end entity;
    
    architecture V1 of Scale is
    
        constant RANGE_IN: natural := MAX_VALUE_IN - MIN_VALUE_IN;
        constant RANGE_OUT: natural := MAX_VALUE_OUT - MIN_VALUE_OUT;
    
        -- V1a
        --constant STEP_SIZE: natural := natural(floor(real(RANGE_OUT) / real(RANGE_IN)));  -- Beware of rounding errors.
    
        -- V1b
        -- Use the spare bits in the natural range for fixed point arithmetic.
        constant NATURAL_BITS: natural := natural(log2(real(natural'high)));  -- 31
        constant USED_BITS: natural := natural(ceil(log2((real(RANGE_OUT) / real(RANGE_IN) * real(MAX_VALUE_IN)))));
        constant SPARE_BITS: natural := NATURAL_BITS - USED_BITS;  -- 19
        constant MULT: real := 2.0**SPARE_BITS;
        constant DIV: natural := natural(MULT);
        constant HALF: natural := DIV / 2;  -- For rounding off the fixed point number.
        constant STEP_SIZE: natural := natural(floor(real(RANGE_OUT) * MULT / real(RANGE_IN)));  -- Convert to a fixed point number. Accuracy depends on the number of spare bits. Beware of rounding errors.
    
    begin
    
        -- V1a
        --value_out <= (value_in - MIN_VALUE_IN) * STEP_SIZE + MIN_VALUE_OUT;
    
        -- V1b
        value_out <= ((value_in - MIN_VALUE_IN) * STEP_SIZE + HALF) / DIV + MIN_VALUE_OUT;  -- Convert fixed point to natural.
    
    end architecture;
    
    architecture V2 of Scale is
    
        subtype TScaledValue is natural range MIN_VALUE_OUT to MAX_VALUE_OUT;
    
        type TScaledValues is array(MIN_VALUE_IN to MAX_VALUE_IN) of TScaledValue;
    
        function GetScaledValues return TScaledValues is
            variable result: TScaledValues;
            constant STEP_SIZE: real := real(MAX_VALUE_OUT - MIN_VALUE_OUT) / real(MAX_VALUE_IN - MIN_VALUE_IN);
        begin
            for i in TScaledValues'range loop
                result(i) := natural(real(i - MIN_VALUE_IN) * STEP_SIZE) + MIN_VALUE_OUT;
            end loop;
            return result;
        end function;
    
        constant SCALED_VALUES: TScaledValues := GetScaledValues;
    
    begin
    
        value_out <= SCALED_VALUES(value_in);
    
    end architecture;
    

    Test Bench

    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    
    entity Scale_TB is
    end entity;
    
    architecture V1 of Scale_TB is
    
        constant SYS_CLOCK_FREQ: real := 100000000.0;  -- Hz
        constant SYS_CLOCK_PERIOD: time := 1.0 sec / SYS_CLOCK_FREQ;
    
        signal halt_sys_clock: boolean := false;
        signal sys_clock: std_logic := '0';
    
        constant MIN_VALUE_IN: natural := 0;
        constant MAX_VALUE_IN: natural := 16#3F#;
        constant MIN_VALUE_OUT: natural := 16#0CCC#;
        constant MAX_VALUE_OUT: natural := 16#1999#;
        --constant MAX_VALUE_OUT: natural := 7700;  -- To see effect of rounding errors for Scale architecture V1.
        signal position: natural range MIN_VALUE_IN to MAX_VALUE_IN;
        signal servo_pos: natural range MIN_VALUE_OUT to MAX_VALUE_OUT;
        signal servo_pos_slv: std_logic_vector(15 downto 0);
    
        component Scale is
            generic
            (
                MIN_VALUE_IN: natural := 0;
                MAX_VALUE_IN: natural := 16#3F#;
                MIN_VALUE_OUT: natural := 16#0CCC#;
                MAX_VALUE_OUT: natural := 16#1999#
            );
            port
            (
                value_in: in natural range 0 to 63;
                value_out: out natural range MIN_VALUE_OUT to MAX_VALUE_OUT
            );
        end component;
    
    begin
    
        SysClockGenerator: process
        begin
            while not halt_sys_clock loop
                sys_clock <= '1';
                wait for SYS_CLOCK_PERIOD / 2.0;
                sys_clock <= '0';
                wait for SYS_CLOCK_PERIOD / 2.0;
            end loop;
            wait;
        end process SysClockGenerator;
    
        StimulusProcess: process
        begin
            for i in MIN_VALUE_IN to MAX_VALUE_IN loop
                position <= i;
                wait for SYS_CLOCK_PERIOD;
            end loop;
    
            wait for SYS_CLOCK_PERIOD;
            halt_sys_clock <= true;
    
            wait;
        end process;
    
        DUT: Scale
            generic map
            (
                MIN_VALUE_IN => MIN_VALUE_IN,
                MAX_VALUE_IN => MAX_VALUE_IN,
                MIN_VALUE_OUT => MIN_VALUE_OUT,
                MAX_VALUE_OUT => MAX_VALUE_OUT
            )
            port map
            (
                value_in => position,
                value_out => servo_pos
            );
    
        servo_pos_slv <= std_logic_vector(to_unsigned(servo_pos, servo_pos_slv'length));
    
    end architecture;
    

    Simulation of Scale.V2

    Simulation of Scale.V2

    RTL of Scale.V2

    RTL of Scale.V2

    Post Mapping of Scale.V2

    Post mapping of Scale

    Synthesis Comparison for FPGA

    Architecture V1

    • 25 logic elements with fixed point arithmetic.
    • No look-up table.
    • STEP_SIZE is of type natural.
      • V1a: Whole number.
      • V1b: Fixed point number.
    • Variable rounding errors depending on number of spare bits for fixed point arithmetic, e.g. 19 spare bits with OP's values.

    Architecture V2

    • 16 logic elements. Quartus optimised the design a bit more after compiling it a few times. Originally used 54 logic elements.
    • Uses a look-up table.
    • STEP_SIZE is of type real.
    • Smaller rounding errors.