-- EMACS settings: -*-	tab-width: 2; indent-tabs-mode: t -*-
-- vim: tabstop=2:shiftwidth=2:noexpandtab
-- kate: tab-width 2; replace-tabs off; indent-width 2;
-- =============================================================================
-- Authors:					Thomas B. Preusser
--
-- Entity:					A flexible scaler for fixed-point values.
--
-- Description:
-- 
-- A flexible scaler for fixed-point values. The scaler is implemented for a set
-- of multiplier and divider values. Each individual scaling operation can
-- arbitrarily select one value from each these sets.
--
-- The computation calculates: ``unsigned(arg) * MULS(msel) / DIVS(dsel)``
-- rounded to the nearest (tie upwards) fixed-point result of the same precision
-- as ``arg``.
--
-- The computation is started by asserting ``start`` to high for one cycle. If a
-- computation is running, it will be restarted. The completion of a calculation
-- is signaled via ``done``. ``done`` is high when no computation is in progress.
-- The result of the last scaling operation is stable and can be read from
-- ``res``. The weight of the LSB of ``res`` is the same as the LSB of ``arg``.
-- Make sure to tap a sufficient number of result bits in accordance to the
-- highest scaling ratio to be used in order to avoid a truncation overflow.
--
-- License:
-- =============================================================================
-- Copyright 2007-2016 Technische Universitaet Dresden - Germany
--										 Chair of VLSI-Design, Diagnostics and Architecture
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--		http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- =============================================================================

library	IEEE;
use			IEEE.std_logic_1164.all;

library	poc;
use			poc.utils.all;


entity [docs]arith_scaler is
	generic (
		MULS : T_POSVEC := (0 => 1);	-- The set of multipliers to choose from in scaling operations.
		DIVS : T_POSVEC := (0 => 1)		-- The set of divisors to choose from in scaling operations.
	);
	port (
		clk	 : in	std_logic;
		rst	 : in	std_logic;

		start : in	std_logic;					-- Start of Computation
		arg	 : in	std_logic_vector;			-- Fixed-point value to be scaled
		msel	: in	std_logic_vector(log2ceil(MULS'length)-1 downto 0) := (others => '0');
		dsel	: in	std_logic_vector(log2ceil(DIVS'length)-1 downto 0) := (others => '0');

		done	: out std_logic;					-- Completion
		res		: out std_logic_vector		-- Result
	);
end entity arith_scaler;


library	IEEE;
use			IEEE.numeric_std.all;

architecture [docs]rtl of arith_scaler is

	-- Derived Constants
	constant N : positive := arg'length;
	constant X : positive := log2ceil(imax(imax(MULS), imax(DIVS)/2+1));
	constant R : positive := log2ceil(imax(DIVS)+1);

	-- Division Properties
	type tDivProps is
		record	-- Properties of the operation for a divisor
			steps : T_POSVEC(DIVS'range);	-- Steps to perform
			align : T_POSVEC(DIVS'range);	-- Left-aligned divisor
		end record;

	function [docs]computeProps return tDivProps is
		variable	res			 : tDivProps;
		variable	min_steps : positive;
	begin
		for i in DIVS'range loop
			res.steps(i) := N+X - log2ceil(DIVS(i)+1) + 1;
		end loop;
		min_steps := imin(res.steps);
		for i in DIVS'range loop
			res.align(i) := DIVS(i) * 2**(res.steps(i) - min_steps);
		end loop;
		return	res;
	end computeProps;

	constant DIV_PROPS : tDivProps := computeProps;

	constant MAX_MUL_STEPS : positive := N;
	constant MAX_DIV_STEPS : positive := imax(DIV_PROPS.steps);
	constant MAX_ANY_STEPS : positive := imax(MAX_MUL_STEPS, MAX_DIV_STEPS);

	subtype tResMask	is std_logic_vector(MAX_DIV_STEPS-1 downto 0);
	type		tResMasks is array(natural range<>) of tResMask;
	function [docs]computeMasks return tResMasks is
		variable res : tResMasks(DIVS'range);
	begin
		for i in DIVS'range loop
			res(i)																:= (others => '0');
			res(i)(DIV_PROPS.steps(i)-1 downto 0) := (others => '1');
		end loop;
		return res;
	end computeMasks;
	constant RES_MASKS : tResMasks(DIVS'range) := computeMasks;

	-- Values computed for the selected multiplier/divisor pair.
	signal muloffset	: unsigned(X-1 downto 0);	-- Offset for correct rounding.
	signal multiplier : unsigned(X	 downto 0);	-- The actual multiplier value.
	signal divisor		: unsigned(R-1 downto 0);	-- The actual divisor value.
	signal divcini		: unsigned(log2ceil(MAX_ANY_STEPS)-1 downto 0);	-- Count for division steps.
	signal divmask		: tResMask;								-- Result Mask

begin

	-----------------------------------------------------------------------------
	-- Compute Parameters according to selected Multiplier/Divisor Pair.

	-- Selection of Multiplier
	genMultiMul: if MULS'length > 1 generate
		signal MS : unsigned(msel'range) := (others => '-');
	begin
		process(clk)
		begin
			if rising_edge(clk) then
				if rst = '1' then
					MS <= (others => '-');
				elsif start = '1' then
					MS <= unsigned(msel);
				end if;
			end if;
		end process;
		multiplier <= (others => 'X') when Is_X(std_logic_vector(MS)) else
									to_unsigned(MULS(to_integer(MS)), multiplier'length);
	end generate genMultiMul;
	genSingleMul: if MULS'length = 1 generate
		multiplier <= to_unsigned(MULS(0), multiplier'length);
	end generate genSingleMul;

	-- Selection of Divisor
	genMultiDiv: if DIVS'length > 1 generate
		signal DS : unsigned(dsel'range) := (others => '-');
	begin
		process(clk)
		begin
			if rising_edge(clk) then
				if rst = '1' then
					DS <= (others => '-');
				elsif start = '1' then
					DS <= unsigned(dsel);
				end if;
			end if;
		end process;
		muloffset <= (others => 'X') when Is_X(dsel) else
								 to_unsigned(DIVS(to_integer(unsigned(dsel)))/2, muloffset'length);
		divisor	 <= (others => 'X') when Is_X(std_logic_vector(DS)) else
								 to_unsigned(DIV_PROPS.align(to_integer(DS)), divisor'length);
		divcini	 <= (others => 'X') when Is_X(std_logic_vector(DS)) else
								 to_unsigned(DIV_PROPS.steps(to_integer(DS))-1, divcini'length);
		divmask	 <= (others => 'X') when Is_X(std_logic_vector(DS)) else
								 RES_MASKS(to_integer(DS));
	end generate genMultiDiv;
	genSingleDiv: if DIVS'length = 1 generate
		muloffset <= to_unsigned(DIVS(0)/2, muloffset'length);
		divisor	 <= to_unsigned(DIV_PROPS.align(0), divisor'length);
		divcini	 <= to_unsigned(DIV_PROPS.steps(0)-1, divcini'length);
		divmask	 <= RES_MASKS(0);
	end generate genSingleDiv;

	-----------------------------------------------------------------------------
	-- Implementation of Scaling Operation
	blkMain : block
		signal C : unsigned(1+log2ceil(MAX_ANY_STEPS) downto 0) := ('0', others => '-');
		signal Q : unsigned(X+N											 downto 0) := (others			=> '-');
	begin
		[docs]process(clk)
			variable cnxt : unsigned(C'range);
			variable d		: unsigned(R downto 0);
		begin
			if rising_edge(clk) then
				if rst = '1' then
					C <= ('0', others => '-');
					Q <= (others			=> '-');
				else
					if start = '1' then
						C <= "11" & to_unsigned(MAX_MUL_STEPS-1, C'length-2);
						Q <= '0' & muloffset & unsigned(arg);
					elsif C(C'left) = '1' then

						cnxt := C - 1;
						if C(C'left-1) = '1' then
							-- MUL Phase
							Q <= "00" & Q(X+N-1 downto 1);
							if Q(0) = '1' then
								Q(X+N-1 downto N-1) <= ('0' & Q(X+N-1 downto N)) + multiplier;
							end if;

							-- Transition to DIV
							if cnxt(cnxt'left-1) = '0' then
								cnxt(cnxt'left-2 downto 0) := divcini;
							end if;
						else
							-- DIV Phase
							d := Q(Q'left downto Q'left-R) - divisor;
							Q <= Q(Q'left-1 downto 0) & not d(d'left);
							if d(d'left) = '0' then
								Q(Q'left downto Q'left-R+1) <= d(d'left-1 downto 0);
							end if;
						end if;
						C <= cnxt;

					end if;
				end if;
			end if;
		end process;
		done <= not C(C'left);
		process(Q, divmask)
			variable r : std_logic_vector(res'length-1 downto 0);
		begin
			r	 := (others => '0');
			r(imin(r'left, tResMask'left) downto 0) :=
				std_logic_vector(Q(imin(r'left, tResMask'left) downto 0)) and
				divmask(imin(r'left, tResMask'left) downto 0);
			res <= r;
		end process;
	end block blkMain;

end rtl;