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

RTL of Scale.V2

Post Mapping of Scale.V2

Synthesis Comparison for FPGAArchitecture 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.
positionit could also be an array of constants in a look up table.type sixto16 is array (0 to 63) of unsigned(15 downto 0); constant cvt6_16: sixto16 := (x"CCC, ...x"1999);and value := cvt6_16(to_integer(position)); The aggregate value expression for the constant can be produced by a function call without writing case statement alternatives. - user1155120