diff --git a/libraries/base/common/hdllib.cfg b/libraries/base/common/hdllib.cfg index 6e815027bcfc900a809dc93fecf5bf33026710ee..507266e0ecca3abf8a42762d4ac1ba85ce4a7258 100644 --- a/libraries/base/common/hdllib.cfg +++ b/libraries/base/common/hdllib.cfg @@ -196,6 +196,7 @@ test_bench_files = tb/vhdl/tb_delta_cycle_demo.vhd tb/vhdl/tb_mms_common_variable_delay.vhd + tb/vhdl/tb_tb_resize.vhd tb/vhdl/tb_tb_common_add_sub.vhd tb/vhdl/tb_tb_common_adder_tree.vhd tb/vhdl/tb_tb_common_fanout_tree.vhd @@ -218,7 +219,7 @@ regression_test_vhdl = tb/vhdl/tb_common_shiftreg.vhd tb/vhdl/tb_common_transpose_symbol.vhd tb/vhdl/tb_common_variable_delay.vhd - tb/vhdl/tb_resize.vhd + tb/vhdl/tb_tb_resize.vhd #tb/vhdl/tb_round.vhd -- has no self verification yet tb/vhdl/tb_requantize.vhd tb/vhdl/tb_common_to_sreal.vhd diff --git a/libraries/base/common/src/vhdl/common_pkg.vhd b/libraries/base/common/src/vhdl/common_pkg.vhd index 55a7a6a8446deaa3f36105b592d966a147a37d67..cf03d8a7b2723ad19b51505269d471b2eb01b8c4 100644 --- a/libraries/base/common/src/vhdl/common_pkg.vhd +++ b/libraries/base/common/src/vhdl/common_pkg.vhd @@ -383,12 +383,38 @@ PACKAGE common_pkg IS FUNCTION TO_UREAL(uvec : STD_LOGIC_VECTOR; resolution_w : INTEGER) RETURN REAL; -- convert unsigned fixed point slv of any length, and with resolution of 2**resolution_w, to REAL FUNCTION TO_SREAL(svec : STD_LOGIC_VECTOR; resolution_w : INTEGER) RETURN REAL; -- convert signed fixed point slv of any length, and with resolution of 2**resolution_w, to REAL --- The RESIZE for SIGNED in IEEE.NUMERIC_STD extends the sign bit or it keeps the sign bit and LS part. This + -- RESIZE_NUM() original description: + -- The RESIZE for SIGNED in IEEE.NUMERIC_STD extends the sign bit or it keeps the sign bit and LS part. This -- behaviour of preserving the sign bit is less suitable for DSP and not necessary in general. A more -- appropriate approach is to ignore the MSbit sign and just keep the LS part. For too large values this -- means that the result gets wrapped, but that is fine for default behaviour, because that is also what -- happens for RESIZE of UNSIGNED. Therefor this is what the RESIZE_NUM for SIGNED and the RESIZE_SVEC do -- and better not use RESIZE for SIGNED anymore. + -- + -- RESIZE_NUM() updated description (27 oct 2021): + -- The RESIZE() from IEEE.NUMERIC_STD keeps the sign bit and the w-1 LSbits, this results in a signal that + -- keeps the sign, but wraps between - and 0 for negative input, and that wraps between + and 0 for + -- positive input. + -- The RESIZE_NUM() from in this common_pkg.vhd simply keeps the w LSbits, so it wraps between - and +. + -- Hence RESIZE_NUM() works the same for SIGNED as for UNSIGNED. For an adder that sums multiple inputs, + -- it can be better to wrap over the entire -, + range, like RESIZE_NUM() does, because if the final adder + -- sum again fits in w bits, then any wrapping effects for intermediate sums will cancel in the end sum. + -- If the number of bit w is sufficient to have no resize overflow, then RESIZE() = RESIZE_NUM(). In an + -- application overflow should be avoided anyway, so then using either RESIZE() or RESIZE_NUM() is fine. + -- When w keeps or increases the data width then the values do not change (of course). When w reduces the + -- data width then overflow can occur in an application, so then use: + -- * RESIZE() to preserve the sign, + -- * RESIZE_NUM() to wrap similar for SIGNED as for UNSIGNED, + -- * common_resize.vhd to clip the overflow (and use symmetrical -, + clipping to avoid introducing DC bias). + -- The resize functions and component are verified by tb_tb_resize.vhd. + -- Conclusion: + -- 1) Keep original RESIZE_NUM(), so resize by selecting the w LSbits for both SIGNED and UNSIGNED. + -- 2) For applications without overflow RESIZE() = RESIZE_NUM() = common_resize. + -- 3) For applications with overflow choose to use RESIZE_NUM(), because it wraps similar for SIGNED as + -- for UNSIGNED (because both keep the w LSbits), instead of behaving differently for SIGNED like + -- RESIZE() does (keeping the MSbit and the w-1 LSbits). The wrapping of RESIZE_NUM() preserves the + -- capability of recovering from intermediate overflow in a summator, which can be beneficial for e.g. + -- a beamformer. FUNCTION RESIZE_NUM( u : UNSIGNED; w : NATURAL) RETURN UNSIGNED; -- left extend with '0' or keep LS part (same as RESIZE for UNSIGNED) FUNCTION RESIZE_NUM( s : SIGNED; w : NATURAL) RETURN SIGNED; -- extend sign bit or keep LS part FUNCTION RESIZE_UVEC(sl : STD_LOGIC; w : NATURAL) RETURN STD_LOGIC_VECTOR; -- left extend with '0' into slv @@ -1975,8 +2001,8 @@ PACKAGE BODY common_pkg IS FUNCTION RESIZE_NUM(u : UNSIGNED; w : NATURAL) RETURN UNSIGNED IS BEGIN - -- left extend with '0' or keep LS part (same as RESIZE for UNSIGNED) - RETURN RESIZE(u, w); + -- left extend with '0' or remove MSbits and keep LS part (= u[w-1:0]) + RETURN RESIZE(u, w); -- same as RESIZE for UNSIGNED END; FUNCTION RESIZE_NUM(s : SIGNED; w : NATURAL) RETURN SIGNED IS @@ -1985,7 +2011,12 @@ PACKAGE BODY common_pkg IS IF w>s'LENGTH THEN RETURN RESIZE(s, w); -- extend sign bit ELSE - RETURN SIGNED(RESIZE(UNSIGNED(s), w)); -- keep LSbits (= vec[w-1:0]) + -- RESIZE() wraps between -, 0 for negative and 0, + for positive, so it keeps the sign and w-1 LSbits + -- RESIZE_NUM() removes MSbits, so it wraps from + to - and from - to +, and it keeps the w LSbits + + -- remove MSbits and keep LS part (= s[w-1:0]) + -- use RESIZE(UNSIGNED()) rather than s[w-1:0] to be independent of RANGE of s + RETURN SIGNED(RESIZE(UNSIGNED(s), w)); END IF; END; diff --git a/libraries/base/common/tb/vhdl/tb_resize.vhd b/libraries/base/common/tb/vhdl/tb_resize.vhd index 900976fc66568f0e6da4fb7b3fe2bf012fd88c57..cc7a8044e92be04fddd7b4c433025a100b1026d9 100644 --- a/libraries/base/common/tb/vhdl/tb_resize.vhd +++ b/libraries/base/common/tb/vhdl/tb_resize.vhd @@ -24,16 +24,28 @@ USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; USE work.common_pkg.ALL; --- Purpose: Test bench for common_resize.vhd +-- Purpose: Test bench for common_resize.vhd and for RESIZE_NUM() from +-- common_pkg.vhd -- Usage: -- > do wave_resize.do --- > run 500 ns --- . Verify clipping using c_clip=TRUE and c_clip_symmetric=TRUE or FALSE --- . Verify wrapping using c_clip=FALSE +-- or use: +-- > as 5 +-- and manually set the signed data output signals to radix decimal +-- > run -a +-- . Verify clipping using g_clip=TRUE and g_clip_symmetric=TRUE or FALSE +-- . Verify wrapping using g_clip=FALSE -- . Observe reg_dat with respect to out_sdat, out_sovr for signed -- . Observe reg_dat with respect to out_udat, out_uovr for unsigned ENTITY tb_resize IS + GENERIC ( + -- Supported for RESIZE_NUM() and common_resize.vhd + g_in_dat_w : NATURAL := 5; + g_out_dat_w : NATURAL := 3; + -- Only supported for common_resize.vhd + g_clip : BOOLEAN := TRUE; + g_clip_symmetric : BOOLEAN := TRUE + ); END tb_resize; ARCHITECTURE tb OF tb_resize IS @@ -43,28 +55,38 @@ ARCHITECTURE tb OF tb_resize IS CONSTANT c_pipeline_input : NATURAL := 0; CONSTANT c_pipeline_output : NATURAL := 1; CONSTANT c_pipeline : NATURAL := c_pipeline_input + c_pipeline_output; - CONSTANT c_clip : BOOLEAN := TRUE; - CONSTANT c_clip_symmetric : BOOLEAN := TRUE; - CONSTANT c_in_dat_w : NATURAL := 5; - CONSTANT c_out_dat_w : NATURAL := 3; - - SIGNAL in_val : STD_LOGIC; - SIGNAL in_dat : STD_LOGIC_VECTOR(c_in_dat_w-1 DOWNTO 0); - SIGNAL in_vec : STD_LOGIC_VECTOR(c_in_dat_w DOWNTO 0); - SIGNAL reg_vec : STD_LOGIC_VECTOR(c_in_dat_w DOWNTO 0); - SIGNAL reg_val : STD_LOGIC; - SIGNAL reg_dat : STD_LOGIC_VECTOR(c_in_dat_w-1 DOWNTO 0); - SIGNAL out_sdat : STD_LOGIC_VECTOR(c_out_dat_w-1 DOWNTO 0); - SIGNAL out_udat : STD_LOGIC_VECTOR(c_out_dat_w-1 DOWNTO 0); - SIGNAL out_sovr : STD_LOGIC; - SIGNAL out_uovr : STD_LOGIC; - SIGNAL tb_end : STD_LOGIC := '0'; - SIGNAL clk : STD_LOGIC := '1'; - SIGNAL rst : STD_LOGIC; - - CONSTANT c_init : STD_LOGIC_VECTOR(in_dat'RANGE) := (OTHERS=>'0'); + -- Test data + SIGNAL in_val : STD_LOGIC; + SIGNAL in_dat : STD_LOGIC_VECTOR(g_in_dat_w-1 DOWNTO 0); + SIGNAL reg_val : STD_LOGIC; + SIGNAL reg_dat : STD_LOGIC_VECTOR(g_in_dat_w-1 DOWNTO 0); + + -- Signed output data, view as radix decimal in Wave window + SIGNAL reg_sdat : STD_LOGIC_VECTOR(g_in_dat_w-1 DOWNTO 0); + SIGNAL lowrange_sdat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- keep LSbits or sign extend + SIGNAL resize_num_sdat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- using RESIZE_NUM() from common_pkg.vhd + SIGNAL resize_sdat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- using RESIZE() from IEEE.NUMERIC_STD + SIGNAL out_sdat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- using common_resize.vhd + SIGNAL out_sovr : STD_LOGIC; -- overflow for out_sdat + + -- Unsigned output data, view as radix unsigned in Wave window + SIGNAL reg_udat : STD_LOGIC_VECTOR(g_in_dat_w-1 DOWNTO 0); + SIGNAL lowrange_udat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- keep LSbits + SIGNAL resize_num_udat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- using RESIZE_NUM() from common_pkg.vhd + SIGNAL resize_udat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- using RESIZE() from IEEE.NUMERIC_STD + SIGNAL out_udat : STD_LOGIC_VECTOR(g_out_dat_w-1 DOWNTO 0); -- using common_resize.vhd + SIGNAL out_uovr : STD_LOGIC; -- overflow for out_udat + + SIGNAL tb_end : STD_LOGIC := '0'; + SIGNAL clk : STD_LOGIC := '1'; + SIGNAL rst : STD_LOGIC; + CONSTANT c_init : STD_LOGIC_VECTOR(in_dat'RANGE) := (OTHERS=>'0'); + CONSTANT g_clip_umax : NATURAL := 2**g_out_dat_w - 1; + CONSTANT g_clip_smax : NATURAL := 2**(g_out_dat_w - 1) - 1; + CONSTANT g_clip_smin : INTEGER := -2**(g_out_dat_w - 1); + BEGIN -- Stimuli @@ -99,35 +121,54 @@ BEGIN END IF; END PROCESS; - -- Delay input as much as DUT output - in_vec <= in_val & in_dat; - - u_pipe : ENTITY work.common_pipeline - GENERIC MAP ( - g_representation => "SIGNED", - g_pipeline => c_pipeline, - g_in_dat_w => c_in_dat_w+1, - g_out_dat_w => c_in_dat_w+1 - ) - PORT MAP ( - clk => clk, - in_dat => in_vec, - out_dat => reg_vec - ); - - reg_val <= reg_vec(c_in_dat_w); - reg_dat <= reg_vec(c_in_dat_w-1 DOWNTO 0); - + -- Delay input as much as DUT output, assume c_pipeline = 1 + reg_val <= in_val WHEN rising_edge(clk); + reg_dat <= in_dat WHEN rising_edge(clk); + reg_sdat <= reg_dat; + reg_udat <= reg_dat; + + gen_extend_lowrange : IF g_out_dat_w >= g_in_dat_w GENERATE + p_extend_lowrange : PROCESS(clk) + BEGIN + IF rising_edge(clk) THEN + -- Extend MSbits for "SIGNED" and "UNSIGNED" + lowrange_sdat <= (OTHERS => in_dat(g_in_dat_w-1)); -- Extend MSbit for "SIGNED" + lowrange_udat <= (OTHERS => '0'); -- Extend '0' for "UNSIGNED" + lowrange_sdat(g_in_dat_w-1 DOWNTO 0) <= in_dat; + lowrange_udat(g_in_dat_w-1 DOWNTO 0) <= in_dat; + END IF; + END PROCESS; + END GENERATE; + + gen_reduce_lowrange : IF g_out_dat_w < g_in_dat_w GENERATE + p_reduce_lowrange : PROCESS(clk) + BEGIN + IF rising_edge(clk) THEN + -- Remove MSbits for "SIGNED" and "UNSIGNED" + lowrange_sdat <= in_dat(g_out_dat_w-1 DOWNTO 0); + lowrange_udat <= in_dat(g_out_dat_w-1 DOWNTO 0); + END IF; + END PROCESS; + END GENERATE; + + -- IEEE RESIZE() for "SIGNED" and "UNSIGNED" + resize_sdat <= STD_LOGIC_VECTOR(RESIZE( SIGNED(in_dat), g_out_dat_w)) WHEN rising_edge(clk); + resize_udat <= STD_LOGIC_VECTOR(RESIZE(UNSIGNED(in_dat), g_out_dat_w)) WHEN rising_edge(clk); + + -- RESIZE_NUM() from common_pkg.vhd for "SIGNED" and "UNSIGNED" + resize_num_sdat <= STD_LOGIC_VECTOR(RESIZE_NUM( SIGNED(in_dat), g_out_dat_w)) WHEN rising_edge(clk); + resize_num_udat <= STD_LOGIC_VECTOR(RESIZE_NUM(UNSIGNED(in_dat), g_out_dat_w)) WHEN rising_edge(clk); + -- DUT for "SIGNED" u_s_resize : ENTITY work.common_resize GENERIC MAP ( g_representation => "SIGNED", - g_clip => c_clip, - g_clip_symmetric => c_clip_symmetric, + g_clip => g_clip, + g_clip_symmetric => g_clip_symmetric, g_pipeline_input => c_pipeline_input, g_pipeline_output => c_pipeline_output, - g_in_dat_w => c_in_dat_w, - g_out_dat_w => c_out_dat_w + g_in_dat_w => g_in_dat_w, + g_out_dat_w => g_out_dat_w ) PORT MAP ( clk => clk, @@ -140,12 +181,12 @@ BEGIN u_u_resize : ENTITY work.common_resize GENERIC MAP ( g_representation => "UNSIGNED", - g_clip => c_clip, - g_clip_symmetric => c_clip_symmetric, + g_clip => g_clip, + g_clip_symmetric => g_clip_symmetric, g_pipeline_input => c_pipeline_input, g_pipeline_output => c_pipeline_output, - g_in_dat_w => c_in_dat_w, - g_out_dat_w => c_out_dat_w + g_in_dat_w => g_in_dat_w, + g_out_dat_w => g_out_dat_w ) PORT MAP ( clk => clk, @@ -155,16 +196,49 @@ BEGIN ); -- Verification - p_verify_increase : PROCESS + p_verify : PROCESS BEGIN WAIT UNTIL rising_edge(clk); - IF reg_val = '1' AND c_in_dat_w<=c_out_dat_w THEN - IF SIGNED(out_sdat) /= SIGNED(reg_dat) THEN - REPORT "Mismatch between signed resize to larger vector" SEVERITY ERROR; - END IF; - IF UNSIGNED(out_udat) /= UNSIGNED(reg_dat) THEN - REPORT "Mismatch between unsigned resize to larger vector" SEVERITY ERROR; + IF reg_val = '1' THEN + IF g_in_dat_w<=g_out_dat_w THEN + -- For extended width expected value is same as input value + ASSERT SIGNED( lowrange_sdat) = SIGNED(reg_dat) REPORT "Wrong extended lowrange_sdat" SEVERITY ERROR; + ASSERT UNSIGNED( lowrange_udat) = UNSIGNED(reg_dat) REPORT "Wrong extended lowrange_udat" SEVERITY ERROR; + ASSERT SIGNED( resize_sdat) = SIGNED(reg_dat) REPORT "Wrong extended resize_sdat" SEVERITY ERROR; + ASSERT UNSIGNED( resize_udat) = UNSIGNED(reg_dat) REPORT "Wrong extended resize_udat" SEVERITY ERROR; + ASSERT SIGNED(resize_num_sdat) = SIGNED(reg_dat) REPORT "Wrong extended resize_num_sdat" SEVERITY ERROR; + ASSERT UNSIGNED(resize_num_udat) = UNSIGNED(reg_dat) REPORT "Wrong extended resize_num_udat" SEVERITY ERROR; + ASSERT SIGNED( out_sdat) = SIGNED(reg_dat) REPORT "Wrong extended out_sdat" SEVERITY ERROR; + ASSERT UNSIGNED( out_udat) = UNSIGNED(reg_dat) REPORT "Wrong extended out_udat" SEVERITY ERROR; + ELSE + -- For reduced width compare unsigned with lowrange + ASSERT UNSIGNED( resize_udat) = UNSIGNED(lowrange_udat) REPORT "Wrong wrapped resize_udat" SEVERITY ERROR; + ASSERT UNSIGNED(resize_num_udat) = UNSIGNED(lowrange_udat) REPORT "Wrong wrapped resize_num_udat" SEVERITY ERROR; + ASSERT UNSIGNED( out_udat) = UNSIGNED(lowrange_udat) OR + UNSIGNED( out_udat) = g_clip_umax REPORT "Wrong clipped out_udat" SEVERITY ERROR; + + -- For reduced width compare signed with lowrange + -- . no need to verify RESIZE(), because it is part of IEEE.NUMERIC_STD + -- . verify RESIZE_NUM() below for all g_out_dat_w + -- . verify common_resize here + IF g_clip THEN + IF g_clip_symmetric THEN + ASSERT (SIGNED(out_sdat) = SIGNED(lowrange_sdat) OR SIGNED(out_sdat) = -g_clip_smax OR SIGNED(out_sdat) = g_clip_smax) AND + SIGNED(out_sdat) /= g_clip_smin AND + SIGNED(out_sdat) /= -g_clip_smin REPORT "Wrong clipped symmetrical out_sdat" SEVERITY ERROR; + ELSE + ASSERT (SIGNED(out_sdat) = SIGNED(lowrange_sdat) OR SIGNED(out_sdat) = g_clip_smin OR SIGNED(out_sdat) = g_clip_smax) + REPORT "Wrong clipped out_sdat" SEVERITY ERROR; + END IF; + ELSE + ASSERT SIGNED(out_sdat) = SIGNED(lowrange_sdat) REPORT "Wrong wrapped out_sdat" SEVERITY ERROR; + END IF; END IF; + + -- RESIZE_NUM() in common_pkg.vhd is always equivalent to lowrange + ASSERT SIGNED(resize_num_sdat) = SIGNED(lowrange_sdat) REPORT "Wrong resize_num_sdat /= lowrange_sdat" SEVERITY ERROR; + ASSERT UNSIGNED(resize_num_udat) = UNSIGNED(lowrange_udat) REPORT "Wrong resize_num_udat /= lowrange_udat" SEVERITY ERROR; + END IF; END PROCESS; diff --git a/libraries/base/common/tb/vhdl/tb_tb_resize.vhd b/libraries/base/common/tb/vhdl/tb_tb_resize.vhd new file mode 100644 index 0000000000000000000000000000000000000000..a76d06bcd115a561fd1429e159d0c5d48c910022 --- /dev/null +++ b/libraries/base/common/tb/vhdl/tb_tb_resize.vhd @@ -0,0 +1,49 @@ +-- -------------------------------------------------------------------------- +-- Copyright 2021 +-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/> +-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +-- +-- 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. +-- -------------------------------------------------------------------------- +-- +-- Author: E. Kooistra, 26 okt 2021 +-- Purpose: Multi tb for common_resize.vhd and RESIZE_NUM() in common_pkg.vhd + +LIBRARY IEEE; +USE IEEE.std_logic_1164.ALL; + +ENTITY tb_tb_resize IS +END tb_tb_resize; + +ARCHITECTURE tb OF tb_tb_resize IS + SIGNAL tb_end : STD_LOGIC := '0'; -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end' +BEGIN + -- -- Supported for RESIZE_NUM() and common_resize.vhd + -- g_in_dat_w : NATURAL := 5; + -- g_out_dat_w : NATURAL := 3; + -- -- Only supported for common_resize.vhd + -- g_clip : BOOLEAN := TRUE; + -- g_clip_symmetric : BOOLEAN := TRUE; + + u_wrap_extend_w : ENTITY work.tb_resize GENERIC MAP (3, 5, FALSE, FALSE); + u_wrap_keep_w : ENTITY work.tb_resize GENERIC MAP (5, 5, FALSE, FALSE); + u_wrap_reduce_w : ENTITY work.tb_resize GENERIC MAP (5, 3, FALSE, FALSE); + + u_clip_extend_w : ENTITY work.tb_resize GENERIC MAP (3, 5, TRUE, FALSE); + u_clip_keep_w : ENTITY work.tb_resize GENERIC MAP (5, 5, TRUE, FALSE); + u_clip_reduce_w : ENTITY work.tb_resize GENERIC MAP (5, 3, TRUE, FALSE); + + u_clip_sym_extend_w : ENTITY work.tb_resize GENERIC MAP (3, 5, TRUE, TRUE); + u_clip_sym_keep_w : ENTITY work.tb_resize GENERIC MAP (5, 5, TRUE, TRUE); + u_clip_sym_reduce_w : ENTITY work.tb_resize GENERIC MAP (5, 3, TRUE, TRUE); +END tb;