diff --git a/libraries/base/common/src/vhdl/common_pkg.vhd b/libraries/base/common/src/vhdl/common_pkg.vhd
index 750cd5e6bebe818c7667deffa69778c34d2f8426..e81085087bca4e5a67920bea7e00f08decda7f2d 100644
--- a/libraries/base/common/src/vhdl/common_pkg.vhd
+++ b/libraries/base/common/src/vhdl/common_pkg.vhd
@@ -372,6 +372,9 @@ PACKAGE common_pkg IS
   
   FUNCTION RESIZE_UVEC_32(vec : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;  -- = RESIZE_UVEC() with w=32 for t_slv_32_arr slv elements
   FUNCTION RESIZE_SVEC_32(vec : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;  -- = RESIZE_SVEC() with w=32 for t_slv_32_arr slv elements
+
+  -- Negate vec, but avoid overflow by forcing -min to +max. Use w <= vec'LENGTH.
+  FUNCTION NEGATE_SVEC(vec : STD_LOGIC_VECTOR; w : INTEGER) RETURN STD_LOGIC_VECTOR;
   
   FUNCTION INCR_UVEC(vec : STD_LOGIC_VECTOR; dec : INTEGER)  RETURN STD_LOGIC_VECTOR;
   FUNCTION INCR_UVEC(vec : STD_LOGIC_VECTOR; dec : UNSIGNED) RETURN STD_LOGIC_VECTOR; 
@@ -1754,6 +1757,22 @@ PACKAGE BODY common_pkg IS
     RETURN RESIZE_SVEC(vec, 32);
   END;
   
+  -- Negate vec, but avoid overflow by forcing -min to +max. Use w <= vec'LENGTH.
+  FUNCTION NEGATE_SVEC(vec : STD_LOGIC_VECTOR; w : INTEGER) RETURN STD_LOGIC_VECTOR IS
+    CONSTANT c_max   : INTEGER :=  2**(w-1)-1;
+    CONSTANT c_min   : INTEGER := -2**(w-1);
+    CONSTANT c_vec_w : NATURAL := vec'LENGTH;
+    VARIABLE v_vec   : STD_LOGIC_VECTOR(c_vec_w-1 DOWNTO 0) := vec;  -- independent of vec'RANGE
+    VARIABLE v_val   : STD_LOGIC_VECTOR(w-1 DOWNTO 0);
+  BEGIN
+    v_val := v_vec(w-1 DOWNTO 0);  -- operate on width w and resize to c_vec_w for return
+    IF SIGNED(v_val) = c_min THEN
+      RETURN STD_LOGIC_VECTOR(TO_SIGNED(c_max, c_vec_w));  -- most negative becomes most positive
+    ELSE
+      RETURN STD_LOGIC_VECTOR(RESIZE_NUM(-SIGNED(v_val), c_vec_w));  -- negate by multiplying by -1
+    END IF;
+  END;
+
   FUNCTION INCR_UVEC(vec : STD_LOGIC_VECTOR; dec : INTEGER) RETURN STD_LOGIC_VECTOR IS
     VARIABLE v_dec : INTEGER;
   BEGIN
diff --git a/libraries/dsp/si/hdllib.cfg b/libraries/dsp/si/hdllib.cfg
new file mode 100755
index 0000000000000000000000000000000000000000..b9c5b293a1aec62f6d333dd08119ea202fc3ca15
--- /dev/null
+++ b/libraries/dsp/si/hdllib.cfg
@@ -0,0 +1,21 @@
+hdl_lib_name = si
+hdl_library_clause_name = si_lib
+hdl_lib_uses_synth = common dp
+hdl_lib_uses_sim = 
+hdl_lib_technology = 
+
+synth_files = 
+    src/vhdl/si.vhd 
+ 
+test_bench_files = 
+    tb/vhdl/tb_si.vhd 
+
+regression_test_vhdl = 
+    tb/vhdl/tb_si.vhd
+
+
+[modelsim_project_file]
+
+
+[quartus_project_file]
+
diff --git a/libraries/dsp/si/src/vhdl/si.vhd b/libraries/dsp/si/src/vhdl/si.vhd
new file mode 100755
index 0000000000000000000000000000000000000000..14d61189ce30513a56070e74e5c0680a2387f789
--- /dev/null
+++ b/libraries/dsp/si/src/vhdl/si.vhd
@@ -0,0 +1,108 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- 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
+-- Purpose: Spectral inversion.
+-- Description:
+--   In the even Nyquist zones the sampled spectrum gets flipped in frequency.
+--   This flip can be compensated for by a spectral inversion (SI). When
+--   enabled the SI multiplies the input samples by (-1)**n = +1, -1, ...,
+--   where n = 0 is the first sample in the FFT block. For more information
+--   see section 4.19 in LOFAR_ASTRON_SDD_018_RSP_Firmware_DD.pdf.
+-- Remark:
+-- . Ported from LOFAR1 rsp. Then rewrote code to use t_dp_sosi.
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib, dp_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+
+ENTITY si IS
+  GENERIC (
+    g_pipeline  : NATURAL := 1;   -- 0 for wires, 1 for output pipeline
+    g_dat_w     : NATURAL := 18
+  );
+  PORT (
+    in_sosi     : IN  t_dp_sosi;
+    out_sosi    : OUT t_dp_sosi;
+    si_en       : IN  STD_LOGIC;
+    clk         : IN  STD_LOGIC;
+    rst         : IN  STD_LOGIC
+  );
+END si;
+
+ARCHITECTURE rtl OF si IS
+
+  SIGNAL plus          : STD_LOGIC;
+  SIGNAL plus_reg      : STD_LOGIC;
+  SIGNAL si_plus       : STD_LOGIC;
+  SIGNAL si_sosi       : t_dp_sosi;
+
+BEGIN
+
+  p_reg : PROCESS(rst, clk)
+  BEGIN
+    IF rst='1' THEN
+      plus_reg  <= '1';
+    ELSIF rising_edge(clk) THEN
+      plus_reg  <= plus;
+    END IF;
+  END PROCESS;
+  
+  -- Control -1**n to start with +1 at sop and then toggle at every valid
+  p_si_control : PROCESS (plus_reg, in_sosi)
+  BEGIN
+    plus <= plus_reg;
+    IF in_sosi.sop = '1' THEN
+      plus <= '1';
+    ELSIF in_sosi.valid = '1' THEN
+      plus <= NOT plus_reg;
+    END IF;
+  END PROCESS;
+  
+  -- Use SI when enabled, else pass on input
+  si_plus <= plus WHEN si_en = '1' ELSE '1';
+
+  si_data : PROCESS (si_plus, in_sosi)
+  BEGIN
+    si_sosi <= in_sosi;
+    IF si_plus = '0' THEN
+      si_sosi.data <= NEGATE_SVEC(in_sosi.data, g_dat_w);
+      si_sosi.re   <= NEGATE_SVEC(in_sosi.re, g_dat_w);
+      si_sosi.im   <= NEGATE_SVEC(in_sosi.im, g_dat_w);
+    END IF;
+  END PROCESS;
+
+  -- Output
+  u_pipeline : ENTITY dp_lib.dp_pipeline
+  GENERIC MAP (
+    g_pipeline  => g_pipeline
+  )
+  PORT MAP (
+    rst     => rst,
+    clk     => clk,
+    snk_in  => si_sosi,
+    src_out => out_sosi
+  );
+  
+END rtl;
diff --git a/libraries/dsp/si/tb/vhdl/tb_si.vhd b/libraries/dsp/si/tb/vhdl/tb_si.vhd
new file mode 100755
index 0000000000000000000000000000000000000000..462b6b6a8a1b9a671e1b3ed8852ea70431f384d8
--- /dev/null
+++ b/libraries/dsp/si/tb/vhdl/tb_si.vhd
@@ -0,0 +1,183 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- 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
+-- Purpose: Spectral inversion.
+-- Description:
+--   Test bench for si.vhd.
+-- Remark:
+-- . Ported from LOFAR1 rsp. Made the tb self-stopping and self-checking.
+
+LIBRARY IEEE, common_lib, dp_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.tb_common_pkg.ALL;
+USE dp_lib.dp_stream_pkg.ALL;
+
+ENTITY tb_si IS
+END tb_si;
+
+ARCHITECTURE tb OF tb_si IS
+
+  CONSTANT c_clk_period   : TIME := 10 ns;
+
+  CONSTANT c_dat_w        : NATURAL := 5;
+  CONSTANT c_max          : INTEGER :=  2**(c_dat_w-1)-1;
+  CONSTANT c_min          : INTEGER := -2**(c_dat_w-1);
+  CONSTANT c_block_size   : NATURAL := 9;
+
+  SIGNAL in_sosi        : t_dp_sosi;
+  SIGNAL out_sosi       : t_dp_sosi;
+
+  SIGNAL in_dat         : STD_LOGIC_VECTOR(c_dat_w-1 DOWNTO 0) := (OTHERS => '0');
+  SIGNAL in_val         : STD_LOGIC;
+  SIGNAL in_sop         : STD_LOGIC;
+  SIGNAL in_sync        : STD_LOGIC;
+  SIGNAL out_dat        : STD_LOGIC_VECTOR(c_dat_w-1 DOWNTO 0);
+  SIGNAL out_val        : STD_LOGIC;
+  SIGNAL out_sop        : STD_LOGIC;
+  SIGNAL out_sync       : STD_LOGIC;
+  SIGNAL si_en          : STD_LOGIC;
+  SIGNAL clk            : STD_LOGIC := '1';
+  SIGNAL rst            : STD_LOGIC;
+  SIGNAL tb_end         : STD_LOGIC := '0';
+
+  SIGNAL toggle         : STD_LOGIC;
+  SIGNAL clip_even      : STD_LOGIC;
+  SIGNAL clip_odd       : STD_LOGIC;
+
+BEGIN
+
+  rst <= '1', '0' AFTER c_clk_period;
+  clk <= NOT(clk) OR tb_end AFTER c_clk_period/2;
+  
+  u_si : ENTITY work.si
+  GENERIC MAP (
+    g_pipeline  => 0,
+    g_dat_w     => c_dat_w
+  )
+  PORT MAP(
+    in_sosi     => in_sosi,
+    out_sosi    => out_sosi,
+    si_en       => si_en,
+    clk         => clk,
+    rst         => rst
+  );
+  
+  -- wires
+  in_sosi.sync  <= in_sync;
+  in_sosi.sop   <= in_sop;
+  in_sosi.valid <= in_val;
+  in_sosi.data  <= RESIZE_DP_SDATA(in_dat);
+  in_sosi.re    <= TO_DP_DSP_DATA(0);
+  in_sosi.im    <= TO_DP_DSP_DATA(0);
+
+  out_sync <= out_sosi.sync;
+  out_sop  <= out_sosi.sop;
+  out_val  <= out_sosi.valid;
+  out_dat  <= out_sosi.data(c_dat_w-1 DOWNTO 0);
+
+  -- Create in_dat with equal value per pair
+  p_clk : PROCESS(rst, clk)
+  BEGIN
+    IF rst='1' THEN
+      in_dat   <= (OTHERS => '0');
+      toggle   <= '0';
+    ELSIF rising_edge(clk) THEN
+      IF in_val='1' THEN
+        IF toggle='1' THEN
+          in_dat <= INCR_UVEC(in_dat, 1);
+        END IF;
+        toggle <= NOT toggle;
+      END IF;
+    END IF;
+  END PROCESS;
+
+  p_stimuli : PROCESS
+  BEGIN
+    si_en   <= '1';
+    in_sop  <= '0';
+    in_sync <= '0';
+    in_val  <= '0';
+    WAIT FOR 10*c_clk_period;
+
+    -- pulse in_sync, to check that it is passed on
+    -- pulse sop and continue with valid data
+    in_sync <= '1';
+    in_sop  <= '1';
+    in_val  <= '1';
+    WAIT FOR c_clk_period;
+    in_sync <= '0';
+    in_sop  <= '0';
+    WAIT FOR c_block_size*c_clk_period;
+    
+    -- insert some valid low cycles
+    in_val  <= '0';
+    WAIT FOR 3*c_clk_period;
+    in_val  <= '1';
+
+    -- some more blocks
+    FOR I IN 0 TO 15 LOOP
+      in_sop <= '1';
+      WAIT FOR c_clk_period;
+      in_sop <= '0';
+      WAIT FOR c_block_size*c_clk_period;
+    END LOOP;
+    
+    tb_end <= '1';
+    WAIT;
+  END PROCESS;
+
+  p_verify : PROCESS
+    VARIABLE v_even      : INTEGER;
+    VARIABLE v_odd       : INTEGER;
+    VARIABLE v_clip_even : STD_LOGIC;
+    VARIABLE v_clip_odd  : STD_LOGIC;
+  BEGIN
+    -- verify per pair
+    WAIT FOR c_clk_period;
+    proc_common_wait_until_high(clk, out_val);
+    v_even := TO_SINT(out_dat);
+    WAIT FOR c_clk_period;
+    proc_common_wait_until_high(clk, out_val);
+    v_odd := TO_SINT(out_dat);
+    -- identify clip wrap of -c_min to +c_max
+    IF v_even = c_max AND v_odd = c_min THEN v_clip_even := '1'; ELSE v_clip_even := '0'; END IF;
+    IF v_even = c_min AND v_odd = c_max THEN v_clip_odd  := '1'; ELSE v_clip_odd  := '0'; END IF;
+    clip_even <= v_clip_even;  -- show in wave window (only cycle late)
+    clip_odd  <= v_clip_odd;   -- show in wave window (only cycle late)
+    -- compare pair
+    IF tb_end = '0' THEN
+      IF v_even /= -v_odd THEN
+        IF NOT (v_clip_even = '1') THEN
+          IF NOT (v_clip_odd = '1') THEN
+            REPORT "Wrong negate value" SEVERITY ERROR;
+	  END IF;
+	END IF;
+      END IF;
+    ELSE
+      WAIT;
+    END IF;
+  END PROCESS; 
+  
+END tb;
+