diff --git a/libraries/base/common/hdllib.cfg b/libraries/base/common/hdllib.cfg
index e42e9b956044f6124596fd2c96dcdba0b073840d..81f4422437440dab0b384a21efbc319818696ad5 100644
--- a/libraries/base/common/hdllib.cfg
+++ b/libraries/base/common/hdllib.cfg
@@ -191,6 +191,7 @@ test_bench_files =
     tb/vhdl/tb_common_transpose_symbol.vhd
     tb/vhdl/tb_common_zip.vhd
     tb/vhdl/tb_common_variable_delay.vhd
+    tb/vhdl/tb_common_gcd.vhd
     tb/vhdl/tb_requantize.vhd
     tb/vhdl/tb_resize.vhd
     tb/vhdl/tb_round.vhd
@@ -224,6 +225,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_common_gcd.vhd
     tb/vhdl/tb_tb_resize.vhd
     tb/vhdl/tb_tb_round.vhd
     tb/vhdl/tb_requantize.vhd
diff --git a/libraries/base/common/src/vhdl/common_pkg.vhd b/libraries/base/common/src/vhdl/common_pkg.vhd
index ef84bffe9d09ed520781e7108c6212dddc026d18..dd8e2b4c9e2b3864e09fddd1ca1780bee457813b 100644
--- a/libraries/base/common/src/vhdl/common_pkg.vhd
+++ b/libraries/base/common/src/vhdl/common_pkg.vhd
@@ -205,6 +205,7 @@ PACKAGE common_pkg IS
   FUNCTION ceil_div(   n : UNSIGNED; d: NATURAL) RETURN UNSIGNED;
   FUNCTION ceil_value( n : UNSIGNED; d: NATURAL) RETURN UNSIGNED;
   FUNCTION floor_value(n : UNSIGNED; d: NATURAL) RETURN UNSIGNED;
+  FUNCTION gcd(a, b : NATURAL) RETURN NATURAL;   -- greatest common divider
   
   FUNCTION slv(n: IN STD_LOGIC)        RETURN STD_LOGIC_VECTOR;  -- standard logic to 1 element standard logic vector
   FUNCTION sl( n: IN STD_LOGIC_VECTOR) RETURN STD_LOGIC;         -- 1 element standard logic vector to standard logic
@@ -719,6 +720,15 @@ PACKAGE BODY common_pkg IS
     RETURN p(w-1 DOWNTO 0);  -- return same width as n
   END;
   
+  FUNCTION gcd(a, b : NATURAL) RETURN NATURAL IS   -- greatest common divider
+  BEGIN
+    IF b = 0 THEN
+      RETURN a;
+    ELSE
+      RETURN gcd(b, a MOD b);
+    END IF;
+  END;
+
   FUNCTION slv(n: IN STD_LOGIC) RETURN STD_LOGIC_VECTOR IS
     VARIABLE r : STD_LOGIC_VECTOR(0 DOWNTO 0);
   BEGIN
diff --git a/libraries/base/common/tb/vhdl/tb_common_gcd.vhd b/libraries/base/common/tb/vhdl/tb_common_gcd.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..2083b0ee588d86468c8a848d55ea37d97e7bed7f
--- /dev/null
+++ b/libraries/base/common/tb/vhdl/tb_common_gcd.vhd
@@ -0,0 +1,50 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright (C) 2009
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-------------------------------------------------------------------------------
+
+-- Author: E. Kooistra 2022
+-- Purpose: Test bench for gcd() in common_pkg.vhd
+-- Usage:
+-- > run -a
+
+LIBRARY IEEE;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE IEEE.NUMERIC_STD.ALL;
+USE work.common_pkg.ALL;
+
+ENTITY tb_common_gcd IS
+END tb_common_gcd;
+
+ARCHITECTURE tb OF tb_common_gcd IS
+
+BEGIN
+
+  ASSERT gcd( 0, 10) = 10 REPORT "Wrong gcd( 0, 10)" SEVERITY ERROR;
+  ASSERT gcd( 1,  1) =  1 REPORT "Wrong gcd( 1,  1)" SEVERITY ERROR;
+  ASSERT gcd(10,  1) =  1 REPORT "Wrong gcd(10,  1)" SEVERITY ERROR;
+  ASSERT gcd(10,  3) =  1 REPORT "Wrong gcd(10,  3)" SEVERITY ERROR;
+  ASSERT gcd(10,  4) =  2 REPORT "Wrong gcd(10,  4)" SEVERITY ERROR;
+  ASSERT gcd(10,  5) =  5 REPORT "Wrong gcd(10,  5)" SEVERITY ERROR;
+  ASSERT gcd(16,  4) =  4 REPORT "Wrong gcd(16,  4)" SEVERITY ERROR;
+  ASSERT gcd(15,  5) =  5 REPORT "Wrong gcd(15,  5)" SEVERITY ERROR;
+  ASSERT gcd(17, 17) = 17 REPORT "Wrong gcd(17, 17)" SEVERITY ERROR;
+  ASSERT gcd(17,  4) =  1 REPORT "Wrong gcd(17,  4)" SEVERITY ERROR;
+
+END tb;
diff --git a/libraries/base/dp/src/vhdl/dp_bsn_source_reg_v2.vhd b/libraries/base/dp/src/vhdl/dp_bsn_source_reg_v2.vhd
index 94535504404868246c757a796885e8c6f53738db..ead48b48a7d79a60e916b27ef1e746237bca17de 100644
--- a/libraries/base/dp/src/vhdl/dp_bsn_source_reg_v2.vhd
+++ b/libraries/base/dp/src/vhdl/dp_bsn_source_reg_v2.vhd
@@ -67,8 +67,7 @@ ENTITY dp_bsn_source_reg_v2 IS
     st_nof_clk_per_sync        : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);  -- nof block per sync
     st_bsn_init                : OUT STD_LOGIC_VECTOR;  -- wr init BSN
     st_current_bsn             : IN  STD_LOGIC_VECTOR;  -- rd current BSN
-    st_bsn_time_offset         : OUT STD_LOGIC_VECTOR;
-    st_current_bsn_time_offset : IN  STD_LOGIC_VECTOR
+    st_bsn_time_offset         : OUT STD_LOGIC_VECTOR
   );
 END dp_bsn_source_reg_v2;
 
@@ -92,16 +91,15 @@ ARCHITECTURE rtl OF dp_bsn_source_reg_v2 IS
   SIGNAL st_on_ctrl       : STD_LOGIC_VECTOR(1 DOWNTO 0);  -- = st_on_pps & st_on
   SIGNAL mm_on_status     : STD_LOGIC;
   
-  SIGNAL mm_nof_clk_per_sync : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL mm_nof_clk_per_sync   : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
   
   SIGNAL mm_bsn_init           : STD_LOGIC_VECTOR(c_longword_w-1 DOWNTO 0);
   SIGNAL mm_bsn_init_wr        : STD_LOGIC;
   SIGNAL mm_current_bsn        : STD_LOGIC_VECTOR(c_longword_w-1 DOWNTO 0) := (OTHERS=>'0');
   SIGNAL mm_current_bsn_hi     : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0) := (OTHERS=>'0');
 
-  SIGNAL mm_bsn_time_offset         : STD_LOGIC_VECTOR(c_bsn_time_offset_w-1 DOWNTO 0) := (OTHERS=>'0');
-  SIGNAL mm_bsn_time_offset_wr      : STD_LOGIC;
-  SIGNAL mm_current_bsn_time_offset : STD_LOGIC_VECTOR(c_bsn_time_offset_w-1 DOWNTO 0) := (OTHERS=>'0');
+  SIGNAL mm_bsn_time_offset    : STD_LOGIC_VECTOR(c_bsn_time_offset_w-1 DOWNTO 0) := (OTHERS=>'0');
+  SIGNAL mm_bsn_time_offset_wr : STD_LOGIC;
   
   -- Registers in st_clk domain
     
@@ -134,7 +132,7 @@ BEGIN
       sla_out.rdval <= '0';
       
       -- Access event defaults
-      mm_bsn_init_wr    <= '0';
+      mm_bsn_init_wr <= '0';
       mm_bsn_time_offset_wr <= '0';
       
       -- Write access: set register value
@@ -142,22 +140,22 @@ BEGIN
         CASE TO_UINT(sla_in.address(c_mm_reg.adr_w-1 DOWNTO 0)) IS
           -- Write Block Sync
           WHEN 0 =>
-            mm_on                 <= sla_in.wrdata(0);
-            mm_on_pps             <= sla_in.wrdata(1); 
+            mm_on     <= sla_in.wrdata(0);
+            mm_on_pps <= sla_in.wrdata(1);
           WHEN 1 =>
-            mm_nof_clk_per_sync      <= sla_in.wrdata(31 DOWNTO 0);
+            mm_nof_clk_per_sync <= sla_in.wrdata(31 DOWNTO 0);
             
           -- Write init BSN
           WHEN 2 =>
-            mm_bsn_init(31 DOWNTO  0)  <= sla_in.wrdata(31 DOWNTO 0);
+            mm_bsn_init(31 DOWNTO  0) <= sla_in.wrdata(31 DOWNTO 0);
           WHEN 3 =>
-            mm_bsn_init(63 DOWNTO 32)  <= sla_in.wrdata(31 DOWNTO 0);
-            mm_bsn_init_wr             <= '1';
+            mm_bsn_init(63 DOWNTO 32) <= sla_in.wrdata(31 DOWNTO 0);
+            mm_bsn_init_wr            <= '1';
           
           -- write bsn_time_offset
           WHEN 4 =>
-            mm_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0) <= sla_in.wrdata(c_bsn_time_offset_w-1 DOWNTO 0);
-            mm_bsn_time_offset_wr                              <= '1';
+            mm_bsn_time_offset    <= sla_in.wrdata(c_bsn_time_offset_w-1 DOWNTO 0);
+            mm_bsn_time_offset_wr <= '1';
 
           WHEN OTHERS => NULL;  -- not used MM addresses
         END CASE;
@@ -179,11 +177,11 @@ BEGIN
             sla_out.rddata(31 DOWNTO 0) <= mm_current_bsn(31 DOWNTO  0);
             mm_current_bsn_hi           <= mm_current_bsn(63 DOWNTO 32);  -- first read low part and preserve high part
           WHEN 3 =>
-            sla_out.rddata(31 DOWNTO 0) <= mm_current_bsn_hi;
+            sla_out.rddata(31 DOWNTO 0) <= mm_current_bsn_hi;  -- then read preserved high part
           
           -- Read current bsn_time_offset
           WHEN 4 =>
-            sla_out.rddata(c_bsn_time_offset_w-1 DOWNTO 0) <= mm_current_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0);  
+            sla_out.rddata <= RESIZE_UVEC(mm_bsn_time_offset, c_word_w);
           
           WHEN OTHERS => NULL;  -- not used MM addresses
         END CASE;
@@ -213,7 +211,7 @@ BEGIN
     st_on_pps           <= mm_on_pps;
     st_nof_clk_per_sync <= mm_nof_clk_per_sync;
     st_bsn_init         <= mm_bsn_init(c_bsn_w-1 DOWNTO 0);
-    st_bsn_time_offset  <= mm_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0);
+    st_bsn_time_offset  <= mm_bsn_time_offset;
     
     p_st_clk : PROCESS(st_rst, st_clk)
     BEGIN
@@ -222,16 +220,16 @@ BEGIN
         st_bsn_time_offset <= TO_UVEC(0, c_bsn_time_offset_w);
       ELSIF rising_edge(st_clk) THEN
         IF mm_bsn_init_wr='1' THEN
-          st_bsn_init <= mm_bsn_init(c_bsn_w-1 DOWNTO 0);  -- use wr of mm_bsn_init high part for in_new to ensure proper transfer of double word
+          -- use wr of mm_bsn_init high part for in_new to ensure proper transfer of double word
+          st_bsn_init <= mm_bsn_init(c_bsn_w-1 DOWNTO 0);
         END IF;
         IF mm_bsn_time_offset_wr='1' THEN
-          st_bsn_time_offset <= mm_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0);  -- use wr of mm_bsn_init high part for in_new to ensure proper transfer of double word
+          st_bsn_time_offset <= mm_bsn_time_offset;
         END IF;
       END IF;
     END PROCESS;
     
-    mm_current_bsn(c_bsn_w-1 DOWNTO 0) <= st_current_bsn;  -- MM user may read current_bsn twice to avoid small chance that the high part of the double word changed (i.e. incremented)
-    mm_current_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0) <= st_current_bsn_time_offset;
+    mm_current_bsn(c_bsn_w-1 DOWNTO 0) <= st_current_bsn;
   END GENERATE;  -- no_cross
 
   gen_cross : IF g_cross_clock_domain = TRUE GENERATE
@@ -311,8 +309,8 @@ BEGIN
     PORT MAP (
       in_rst      => mm_rst,
       in_clk      => mm_clk,
-      in_new      => mm_bsn_time_offset_wr,  -- use wr of mm_bsn_time_offset high part for in_new to ensure proper transfer of double word
-      in_dat      => mm_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0),
+      in_new      => mm_bsn_time_offset_wr,
+      in_dat      => mm_bsn_time_offset,
       in_done     => OPEN,                   -- pulses when no more pending in_new
       out_rst     => st_rst,
       out_clk     => st_clk,
@@ -320,20 +318,6 @@ BEGIN
       out_new     => OPEN
     );
     
-    -- write occurs with sufficient margin before it is used, still use common_reg_cross_domain nonetheless
-    u_current_bsn_offset : ENTITY common_lib.common_reg_cross_domain
-    PORT MAP (
-      in_rst      => st_rst,
-      in_clk      => st_clk,
-      in_new      => '1',
-      in_dat      => st_current_bsn_time_offset,
-      in_done     => OPEN,                   -- pulses when no more pending in_new
-      out_rst     => mm_rst,
-      out_clk     => mm_clk,
-      out_dat     => mm_current_bsn_time_offset(c_bsn_time_offset_w-1 DOWNTO 0),
-      out_new     => OPEN
-    );
-
   END GENERATE;  -- gen_cross
   
 END rtl;
diff --git a/libraries/base/dp/src/vhdl/dp_bsn_source_v2.vhd b/libraries/base/dp/src/vhdl/dp_bsn_source_v2.vhd
index 107073c4340d4e0f7ea3aa02911c7454dcac0e03..a2f1756cc82d92e2ca8ee5ba16e20e88321a42d0 100644
--- a/libraries/base/dp/src/vhdl/dp_bsn_source_v2.vhd
+++ b/libraries/base/dp/src/vhdl/dp_bsn_source_v2.vhd
@@ -19,29 +19,40 @@
 --
 -------------------------------------------------------------------------------
 
--- Purpose : 
+-- Author : P.Donker  okt. 2020, added bsn_time_offset
+--          E. Kooistra aug 2022, corected s_bsn_time_offset, clarified
+--            sync_size_cnt, removed redundant current_bsn_time_offset
+--            and s_init, simplified implementation of dp_on_status.
+--
+-- Purpose :
 --   Start a periodic block sync interval and maintain a block sequence
---   number
+--   number according to the PPS and BSN grid defined in [1].
 -- Description:
 --   When dp_on is low then all outputs are low. When dp_on is high, the 
 --   output sync starts pulsing after bsn_time_offset (in clk cycles) with 
 --   a period of g_block_size number of clk cycles and the output valid, 
 --   sop and eop will be active.
---   Alternatively, one can assert dp_on while dp_on_pps is high to 
---   start the data path on the next PPS.
---   The dp_on is asynchronous. The dp_bsn_source_v2 takes care that
---   src_out.valid starts with a src_out.sop and that src_out.valid can
---   only go low after a src_out.eop, to ensure that src_out only produces
---   complete sop-eop blocks that enter the subsequent processing.
---   The bs_start is active at the first src_out.sop after dp_on went high.
+--   The dp_on is asynchronous. Alternatively, one can assert dp_on while
+--   dp_on_pps is high to start the data path on the next PPS.
+--   The src_out.sync always happens at the src_out.sop.
+--   If nof_clk_per_sync / g_block_size is an integer than all src_out.sync
+--   intervals will have nof_clk_per_sync clk cycles, else nof_clk_per_sync
+--   is the average number of clock cycles between src_out.sync and then the
+--   number of blocks per sync intervals will vary between c_nof_block_hi
+--   and c_nof_block_lo.
+--   The dp_bsn_source_v2 takes care that src_out.valid starts with a
+--   src_out.sync and src_out.sop and that src_out.valid can only go low
+--   after a src_out.eop, to ensure that src_out only produces complete
+--   sop-eop blocks that enter the subsequent processing.
+--   The bs_restart is active at the first src_out.sop after dp_on went high.
 -- Remarks:
 -- . Starting the data path is only possible from the dp_off state, so one
 --   has to disable (dp_on='0') the data path before restarting it.
 -- . Effectively dp_on_status = src_out.valid, because when the BSN source
 --   is on, then src_out.valid = '1' at every clk cycle.
 --
--- author : P.Donker  okt. 2020, added bsn_time_offset
---
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L2+STAT+Decision%3A+Timing+in+Station
 
 LIBRARY IEEE, common_lib;
 USE IEEE.STD_LOGIC_1164.ALL;
@@ -51,7 +62,7 @@ USE work.dp_stream_pkg.ALL;
 
 ENTITY dp_bsn_source_v2 IS
   GENERIC (
-    g_block_size         : NATURAL := 256;  
+    g_block_size         : NATURAL := 256;  -- >= 3, see state machine
     g_nof_clk_per_sync   : NATURAL := 200 * 10**6; 
     g_bsn_w              : NATURAL := 48;
     g_bsn_time_offset_w  : NATURAL := 10 
@@ -70,7 +81,6 @@ ENTITY dp_bsn_source_v2 IS
     nof_clk_per_sync        : IN  STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0) := TO_UVEC(g_nof_clk_per_sync, c_word_w);
     bsn_init                : IN  STD_LOGIC_VECTOR(g_bsn_w-1 DOWNTO 0) := (OTHERS=>'0');
     bsn_time_offset         : IN  STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0) := (OTHERS=>'0');
-    current_bsn_time_offset : OUT STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0);  -- output for monitoring purpose in test bench.
 
     src_out                 : OUT t_dp_sosi  -- only uses sync, bsn[], valid, sop and eop
   );
@@ -82,21 +92,10 @@ ARCHITECTURE rtl OF dp_bsn_source_v2 IS
   CONSTANT c_block_size_cnt_w : NATURAL := ceil_log2(g_block_size);
   CONSTANT c_block_cnt_zero   : STD_LOGIC_VECTOR(g_bsn_w-1 DOWNTO 0) := (OTHERS => '0');
 
-  -- The state machine starts synchronously via s_bsn_time_offset, s_dp_on_sop to s_dp_on, or it starts
-  -- directly to s_dp_on. When in dp_on it loops between s_dp_on and s_dp_on_eop for every block.
-  -- If the BSN source is switched off, then after completing the block s_dp_on_eop goes back to
-  -- s_dp_off.
-  --
-  -- Sketch of the state machine:
-  --
-  -- s_init --> s_dp_off --> s_bsn_time_offset --                      ------------ (loop) <---
-  --            \       \                        \                    /                        \ 
-  --             \       ---------------------------> s_dp_on_sop --> s_dp_on --> s_dp_on_eop --
-  --              \                                                                            /
-  --               ----------------------------------------------------------------- (off) <--- 
-
+  CONSTANT c_nof_block_lo     : NATURAL := g_nof_clk_per_sync / g_block_size;
+  CONSTANT c_nof_block_hi     : NATURAL := ceil_div(g_nof_clk_per_sync, g_block_size);
 
-  TYPE t_state_enum IS (s_init, s_dp_off, s_bsn_time_offset, s_dp_on_sop, s_dp_on, s_dp_on_eop);
+  TYPE t_state_enum IS (s_dp_off, s_bsn_time_offset, s_dp_on_sop, s_dp_on, s_dp_on_eop);
 
   SIGNAL state       : t_state_enum;
   SIGNAL nxt_state   : t_state_enum; 
@@ -108,144 +107,152 @@ ARCHITECTURE rtl OF dp_bsn_source_v2 IS
   SIGNAL i_src_out          : t_dp_sosi := c_dp_sosi_init;
   SIGNAL nxt_src_out        : t_dp_sosi;
  
-  SIGNAL i_dp_on_status     : STD_LOGIC;
-  SIGNAL nxt_dp_on_status   : STD_LOGIC;
   SIGNAL nxt_bs_restart     : STD_LOGIC;
 
   SIGNAL nxt_bsn_time_offset_cnt : STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0);
   SIGNAL bsn_time_offset_cnt     : STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0);
   
-  SIGNAL i_current_bsn_time_offset   : STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0);
-  SIGNAL nxt_current_bsn_time_offset : STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0);
-
-  SIGNAL nxt_clk_cnt        : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-  SIGNAL clk_cnt            : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-  SIGNAL nxt_sync           : STD_LOGIC;
-  SIGNAL sync               : STD_LOGIC;
+  SIGNAL nxt_sync_size_cnt       : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL sync_size_cnt           : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+  SIGNAL nxt_sync                : STD_LOGIC;
+  SIGNAL sync                    : STD_LOGIC;
  
 BEGIN
 
   src_out <= i_src_out;
-  dp_on_status <= i_dp_on_status;
-  current_bsn_time_offset <= i_current_bsn_time_offset;
+  dp_on_status <= i_src_out.valid;
 
-  p_state : PROCESS(state, i_src_out, block_size_cnt, clk_cnt, sync, i_dp_on_status, bsn_time_offset_cnt, bsn_time_offset, nof_clk_per_sync, bsn_init, dp_on, dp_on_pps, pps, prev_state)
+  p_state : PROCESS(sync, sync_size_cnt, nof_clk_per_sync,
+                    state, i_src_out, block_size_cnt, bsn_time_offset_cnt,
+                    bsn_init, dp_on, dp_on_pps, pps, bsn_time_offset, prev_state)
   BEGIN  
+    -- Maintain sync_size_cnt for nof_clk_per_sync
+    -- . nof_clk_per_sync is the number of clk per pps interval and the
+    --   average number of clk per src_out.sync interval, due to that the
+    --   src_out.sync has to occur at the src_out.sop of a block.
+    -- . The sync_size_cnt is started in s_dp_off at the pps
+    -- . The pps interval is nof_clk_per_sync, so when the sync_size_cnt
+    --   wraps, then the sync is used to ensure that src_out.sync will
+    --   pulse at the src_out.sop of the next block.
+    nxt_sync <= sync;
+    nxt_sync_size_cnt <= INCR_UVEC(sync_size_cnt, 1);
+    IF UNSIGNED(sync_size_cnt) = UNSIGNED(nof_clk_per_sync) - 1 THEN
+      nxt_sync <= '1';  -- will set src_out.sync on next src_out.sop
+      nxt_sync_size_cnt <= (OTHERS=>'0');
+    END IF;
+
+    -- State machine for src_out
     nxt_state                   <= state;
-    nxt_src_out                 <= i_src_out;
+    nxt_src_out                 <= i_src_out;  -- hold src_out.bsn
     nxt_src_out.sync            <= '0';
     nxt_src_out.valid           <= '0';
     nxt_src_out.sop             <= '0';
     nxt_src_out.eop             <= '0';
     nxt_block_size_cnt          <= block_size_cnt;
-    nxt_clk_cnt                 <= INCR_UVEC(clk_cnt, 1);
-    nxt_sync                    <= sync;
-    nxt_dp_on_status            <= i_dp_on_status;
-    nxt_bs_restart              <= '0';
     nxt_bsn_time_offset_cnt     <= bsn_time_offset_cnt;
-    nxt_current_bsn_time_offset <= bsn_time_offset;
-
-
-    IF UNSIGNED(clk_cnt) = UNSIGNED(nof_clk_per_sync) - 1 THEN
-      nxt_clk_cnt <=  (OTHERS=>'0');
-      nxt_sync    <= '1';  -- will set src_out.sync on next src_out.sop
-    END IF;
 
     CASE state IS
-
-      WHEN s_init =>
-        nxt_state <= s_dp_off;
-      
       WHEN s_dp_off =>
-        nxt_dp_on_status <= '0';
         nxt_src_out.bsn <= RESIZE_DP_BSN(bsn_init);
+        nxt_bsn_time_offset_cnt <= (OTHERS=>'0');
         nxt_sync <= '0';
-        nxt_clk_cnt <=  (OTHERS=>'0');
-        IF dp_on = '1' THEN 
+        nxt_sync_size_cnt <= (OTHERS=>'0');
+        nxt_block_size_cnt <= (OTHERS=>'0');
+        IF dp_on = '1' THEN
+          nxt_sync <= '1';  -- ensure issue sync at first sync interval
           IF dp_on_pps = '1' THEN
-            nxt_sync <= '1';   -- ensure issue sync at first sync interval for start at PPS.
+            -- start at pps
             IF pps = '1' THEN
-              nxt_bsn_time_offset_cnt <= (OTHERS=>'0');
               nxt_state <= s_bsn_time_offset;
-            END IF; 
-          ELSE 
-            nxt_state   <= s_dp_on_sop;
+            END IF;
+          ELSE
+            -- start immediately
+            nxt_state <= s_dp_on_sop;
           END IF;
         END IF;
 
       WHEN s_bsn_time_offset =>
         IF UNSIGNED(bsn_time_offset_cnt) = UNSIGNED(bsn_time_offset) THEN
+          -- The bsn_time_offset can be 0, so IF UNSIGNED(bsn_time_offset)-1
+          -- can not be used to account for on clk latency of state
+          -- s_bsn_time_offset. Therefore do not count sync_size_cnt during
+          -- latency of state s_bsn_time_offset.
+          nxt_sync_size_cnt <= sync_size_cnt;
           nxt_state <= s_dp_on_sop;
         ELSE
           nxt_bsn_time_offset_cnt <= INCR_UVEC(bsn_time_offset_cnt, 1);
         END IF;
 
+      -- using separate states s_dp_on_sop and s_dp_on_eop instead of only
+      -- s_dp_on state and block_size_cnt, cause that g_block_size must be
+      -- >= 3, but that is fine.
       WHEN s_dp_on_sop =>
-        nxt_dp_on_status   <= '1';
-        nxt_src_out.sop    <= '1';
+        -- Start of block
+        nxt_src_out.sop <= '1';
         nxt_src_out.valid  <= '1';
-        nxt_state          <= s_dp_on;
+        nxt_state <= s_dp_on;
+        -- block_size_cnt = 0 at src_out.sop
         nxt_block_size_cnt <= (OTHERS=>'0');
+        -- after first block, increment bsn per block
         IF prev_state = s_dp_on_eop THEN
           nxt_src_out.bsn <= INCR_DP_BSN(i_src_out.bsn, 1, g_bsn_w);
         END IF;
-        IF sync = '1' THEN 
+        -- check for pending sync
+        IF sync = '1' THEN
           nxt_src_out.sync <= '1';
           nxt_sync <= '0';
         END IF;
-        IF i_dp_on_status = '0' THEN -- transition from 0 to 1 is a (re)start
-          nxt_bs_restart <= '1'; -- bs_restart indicates a restart as a pulse on the sop (and sync if dp_on_pps is used).
-        END IF;
 
       WHEN s_dp_on =>
-        nxt_src_out.valid  <= '1';
+        -- During block
+        nxt_src_out.valid <= '1';
+        -- block_size_cnt increments to determine end of block
         nxt_block_size_cnt <= INCR_UVEC(block_size_cnt, 1);
-        IF UNSIGNED(block_size_cnt) = g_block_size -3 THEN
-          nxt_state        <= s_dp_on_eop;
+        IF UNSIGNED(block_size_cnt) >= g_block_size - 3 THEN
+          nxt_state <= s_dp_on_eop;
         END IF;
 
       WHEN s_dp_on_eop =>
-        nxt_src_out.eop    <= '1';
-        nxt_src_out.valid  <= '1'; 
-        nxt_state          <= s_dp_on_sop;
-        nxt_block_size_cnt <= INCR_UVEC(block_size_cnt, 1);
+        -- End of block
+        nxt_src_out.eop <= '1';
+        nxt_src_out.valid <= '1';
+        nxt_state <= s_dp_on_sop;
+        -- block_size_cnt is dont care at at src_out.eop
+        -- accept dp_off after eop, to avoid fractional blocks
         IF dp_on = '0' THEN
           nxt_state <= s_dp_off;
         END IF;
 
-      WHEN OTHERS => -- s_init
+      WHEN OTHERS =>  -- reover from undefined state
         nxt_state <= s_dp_off;
-      
     END CASE;
   END PROCESS;
 
+  -- src_out.valid transition from 0 to 1 is a bs_restart, use nxt_bs_restart
+  -- to have bs_restart at first src_out.sync and src_out.sop.
+  nxt_bs_restart <= nxt_src_out.valid AND NOT i_src_out.valid;
 
   p_clk : PROCESS(rst, clk)
   BEGIN
     IF rst='1' THEN
-      prev_state     <= s_init;
-      state          <= s_init;
-      i_src_out      <= c_dp_sosi_rst;
-      clk_cnt        <= (OTHERS=>'0');
-      sync           <= '0'; 
-      block_size_cnt <= (OTHERS=>'0');
-      i_dp_on_status <= '0';
-      bs_restart     <= '0';
+      prev_state          <= s_dp_off;
+      state               <= s_dp_off;
+      i_src_out           <= c_dp_sosi_rst;
+      sync_size_cnt       <= (OTHERS=>'0');
+      sync                <= '0';
+      block_size_cnt      <= (OTHERS=>'0');
+      bs_restart          <= '0';
       bsn_time_offset_cnt <= (OTHERS=>'0');
     ELSIF rising_edge(clk) THEN
-      prev_state     <= state;
-      state          <= nxt_state;
-      i_src_out      <= nxt_src_out;
-      clk_cnt        <= nxt_clk_cnt;
-      sync           <= nxt_sync;
-      block_size_cnt <= nxt_block_size_cnt;
-      i_dp_on_status <= nxt_dp_on_status;
-      bs_restart     <= nxt_bs_restart;
+      prev_state          <= state;
+      state               <= nxt_state;
+      i_src_out           <= nxt_src_out;
+      sync_size_cnt       <= nxt_sync_size_cnt;
+      sync                <= nxt_sync;
+      block_size_cnt      <= nxt_block_size_cnt;
+      bs_restart          <= nxt_bs_restart;
       bsn_time_offset_cnt <= nxt_bsn_time_offset_cnt;
-      i_current_bsn_time_offset <= nxt_current_bsn_time_offset;
     END IF;
   END PROCESS;
 
 END rtl;
-
-
diff --git a/libraries/base/dp/src/vhdl/dp_bsn_sync_scheduler.vhd b/libraries/base/dp/src/vhdl/dp_bsn_sync_scheduler.vhd
index 62e5956eca69267ecf4434ab5fc0570419f82d3a..4ec4b826ef01ff0027fd13a3896710ecaf69dc2e 100644
--- a/libraries/base/dp/src/vhdl/dp_bsn_sync_scheduler.vhd
+++ b/libraries/base/dp/src/vhdl/dp_bsn_sync_scheduler.vhd
@@ -18,7 +18,8 @@
 --
 -- Author: Eric Kooistra, 30 July 2021
 -- Purpose : 
---   Create programmable sync interval for input stream.
+--   Create programmable sync interval for input stream, according to the
+--   PPS and BSN grid defined in [1].
 -- Description:
 -- * ctrl_start_bsn:
 --   The output sync interval starts at an in_sosi.bsn that is programmable via
@@ -105,6 +106,9 @@
 --   The differences are that:
 --   . dp_bsn_sync_scheduler requires in_sosi.sync and copies the other in_sosi
 --     ctrl, info and data, whereas generates bs_sosi.ctrl.
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L2+STAT+Decision%3A+Timing+in+Station
 
 LIBRARY IEEE, common_lib;
 USE IEEE.STD_LOGIC_1164.ALL;
diff --git a/libraries/base/dp/src/vhdl/mms_dp_bsn_source_v2.vhd b/libraries/base/dp/src/vhdl/mms_dp_bsn_source_v2.vhd
index bc82cda1f4970574aa8da25eee1ecfd57bec1d35..c20fc561b114fd9b1a0e952636baa56d7c5cd62c 100644
--- a/libraries/base/dp/src/vhdl/mms_dp_bsn_source_v2.vhd
+++ b/libraries/base/dp/src/vhdl/mms_dp_bsn_source_v2.vhd
@@ -70,7 +70,6 @@ ARCHITECTURE str OF mms_dp_bsn_source_v2 IS
   SIGNAL dp_on_status            : STD_LOGIC;
   
   SIGNAL capture_bsn             : STD_LOGIC_VECTOR(g_bsn_w-1 DOWNTO 0) := (OTHERS=>'0');
-  SIGNAL current_bsn_time_offset : STD_LOGIC_VECTOR(g_bsn_time_offset_w-1 DOWNTO 0);
 
 BEGIN
 
@@ -99,8 +98,7 @@ BEGIN
     st_nof_clk_per_sync        => nof_clk_per_sync,
     st_bsn_init                => bsn_init,
     st_current_bsn             => capture_bsn,
-    st_bsn_time_offset         => bsn_time_offset,
-    st_current_bsn_time_offset => current_bsn_time_offset
+    st_bsn_time_offset         => bsn_time_offset
   );
   
   u_bsn_source : ENTITY work.dp_bsn_source_v2
@@ -121,13 +119,10 @@ BEGIN
     bsn_init                => bsn_init,
     nof_clk_per_sync        => nof_clk_per_sync,
     bsn_time_offset         => bsn_time_offset,
-    current_bsn_time_offset => current_bsn_time_offset,
     -- Streaming
     src_out                 => i_bs_sosi
   );
   
-  --capture_bsn <= i_bs_sosi.bsn;                                                  -- capture current BSN
-  --capture_bsn <= i_bs_sosi.bsn WHEN rising_edge(dp_clk) AND dp_pps='1';          -- capture BSN at external PPS
   capture_bsn <= i_bs_sosi.bsn WHEN rising_edge(dp_clk) AND i_bs_sosi.sync='1';  -- capture BSN at internal sync
   
 END str;
diff --git a/libraries/base/dp/tb/vhdl/tb_dp_bsn_source_v2.vhd b/libraries/base/dp/tb/vhdl/tb_dp_bsn_source_v2.vhd
index 7f15f845fc365470ac223203310aa98097df13eb..c6ace124e0d2abd4500634a85ca50b75da766e2a 100644
--- a/libraries/base/dp/tb/vhdl/tb_dp_bsn_source_v2.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_dp_bsn_source_v2.vhd
@@ -1,6 +1,6 @@
 -------------------------------------------------------------------------------
 --
--- Copyright (C) 2011
+-- Copyright (C) 2020
 -- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
 -- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
 --
@@ -19,22 +19,28 @@
 --
 -------------------------------------------------------------------------------
 
--- Author: P. Donker
--- Verify if eop and sop come in pairs and if sync is at sop and at expected_sync puls.
--- The tb is using a SSN (second sample number) and BSN (block sample number) generator as reference
--- for the test, it uses g_pps_interval and g_block_size for generator timing settings.
--- Start/Stop BSN source tests:
--- 1) test 1x asynchronously (dp_on_pps='0') without automatic check, check visualy in wave window.
--- 2) test 3x synchronously (dp_on_pps='1') with automatic check.
+-- Author: P. Donker, E. Kooistra
+-- Purpose: Tb to verify that the BS source can be started in any PPS interval
+--          to create the BSN grid and sync interval as defined in Fig. 3.1 in
+--          [1].
+-- Decsription:
+-- * Start/Stop BSN source tests:
+--   1) test once asynchronously (dp_on_pps='0') without automatic check, check
+--      visualy in wave window.
+--   2) test c_nof_repeat synchronously (dp_on_pps='1') with automatic check.
+-- . Verify if bs_sosi.eop and bs_sosi.sop come in pairs
+-- . Verify that bs_sosi.sync is at bs_sosi.sop
+-- . Verify that bs_sosi has fixed latency with respect to ref_grid
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/display/L2M/L2+STAT+Decision%3A+Timing+in+Station
+-- [2] https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+BSN+source+with+offset
 --
--- [doc] = https://support.astron.nl/confluence/display/L2M/L6+FWLIB+Design+Document%3A+BSN+source+with+offset
-
 -- Usage:
--- > as 10
+-- > as 8, e.g. view bs_sosi, exp_grid and unexpected_bs_sync
 -- > run -all
 -- . sop, eop are verified automatically
--- . sync and bsn are verified automatically
--- and then manually verify on/off in Wave window
+-- . sync and bsn are verified automatically using the ref_grid
 
 LIBRARY IEEE, common_lib, dp_lib;
 USE IEEE.STD_LOGIC_1164.ALL;
@@ -46,193 +52,219 @@ USE dp_lib.tb_dp_pkg.ALL;
 
 ENTITY tb_dp_bsn_source_v2 IS
   GENERIC (
-    g_nof_pps      : NATURAL := 20;
-    g_pps_interval : NATURAL := 230;
-    g_block_size   : NATURAL := 32 
+    g_pps_interval : NATURAL := 16; --101;
+    g_block_size   : NATURAL := 5 --23
   );
 END tb_dp_bsn_source_v2;
 
 ARCHITECTURE tb OF tb_dp_bsn_source_v2 IS
 
-  CONSTANT c_clk_period   : TIME    := 10 ns;
-  CONSTANT c_bsn_w        : NATURAL := 31;
-  CONSTANT c_dut_latency  : NATURAL := 2;
+  -- The nof block per sync interval will be the same after every
+  -- c_min_nof_pps_interval. The c_gcd is the greatest common divider of
+  -- g_pps_interval and g_block_size, so g_block_size / c_gcd yields an
+  -- integer. When g_pps_interval and g_block_size are relative prime,
+  -- then c_gcd = 1, and then it takes g_block_size nof g_pps_interval
+  -- for the pattern of c_nof_block_per_sync_lo and c_nof_block_per_sync_hi
+  -- to repeat. If c_gcd = g_block_size, then c_nof_block_per_sync_lo =
+  -- c_nof_block_per_sync_hi, so then the nof block per sync interval is
+  -- the same in every pps interval.
+  CONSTANT c_gcd                   : NATURAL := gcd(g_pps_interval, g_block_size);
+  CONSTANT c_min_nof_pps_interval  : NATURAL := g_block_size / c_gcd;
+
+  CONSTANT c_nof_block_per_sync_lo : NATURAL := g_pps_interval / g_block_size;
+  CONSTANT c_nof_block_per_sync_hi : NATURAL := ceil_div(g_pps_interval, g_block_size);
+
+  -- choose c_nof_pps and c_nof_repeat > c_min_nof_pps_interval, because the
+  -- fractional sync pattern will repeat every c_min_nof_pps_interval number
+  -- of g_pps_intervals.
+  CONSTANT c_factor            : NATURAL := 3;
+  CONSTANT c_nof_pps           : NATURAL := c_min_nof_pps_interval * c_factor;
+  CONSTANT c_nof_repeat        : NATURAL := c_min_nof_pps_interval * c_factor;
+
+  CONSTANT c_clk_period        : TIME    := 10 ns;
+  CONSTANT c_bsn_w             : NATURAL := 31;
+  CONSTANT c_bsn_time_offset_w : NATURAL := ceil_log2(g_block_size);
+
+  -- Minimum latency between sync and PPS, due to logic in DUT
+  CONSTANT c_dut_latency       : NATURAL := 3;
 
   -- The state name tells what kind of test is being done
   TYPE t_state_enum IS (
     s_disable,
-    s_start,
-    s_pps_start
+    s_dp_on,
+    s_dp_on_pps
   );
 
-  SIGNAL tb_state     : t_state_enum;
+  -- Define the PPS (SSN) and BSN grid that both start at 0 according to Figure 3.1 in [1]:
+  TYPE t_time_grid IS RECORD
+    pps  : STD_LOGIC;  -- pulse per second, g_pps_interval clk per pps interval
+    ssn  : NATURAL;  -- seconds sequence number
+    bsn  : NATURAL;  -- block sequence number, g_block_size clk per block
+    sync : STD_LOGIC;  -- active at sop when pps is active or was active
+    sop  : STD_LOGIC;   -- start of block
+    eop  : STD_LOGIC;  -- end of block
+  END RECORD;
+
+  CONSTANT c_time_grid_rst : t_time_grid := ('0', 0, 0, '0', '0', '0');
+
+  -- Reference grid
+  SIGNAL ref_grid        : t_time_grid := c_time_grid_rst;
+  SIGNAL ssn_eop         : STD_LOGIC := '0';
+  SIGNAL hold_pps        : STD_LOGIC := '0';
+  SIGNAL nxt_hold_pps    : STD_LOGIC := '0';
 
+  -- Tb
   SIGNAL tb_end       : STD_LOGIC := '0';
   SIGNAL rst          : STD_LOGIC := '1';
   SIGNAL clk          : STD_LOGIC := '1';
+  SIGNAL tb_state        : t_state_enum := s_disable;
 
   -- DUT
-  SIGNAL dp_on        : STD_LOGIC := '0';
-  SIGNAL dp_on_pps    : STD_LOGIC := '0';
-  SIGNAL bsn_init     : STD_LOGIC_VECTOR(c_bsn_w-1 DOWNTO 0) := (OTHERS=>'0');
-
-  SIGNAL bs_sosi      : t_dp_sosi;
+  SIGNAL dp_on           : STD_LOGIC := '0';
+  SIGNAL dp_on_pps       : STD_LOGIC := '0';
+  SIGNAL dp_on_status    : STD_LOGIC;
+  SIGNAL bs_restart      : STD_LOGIC;
+  SIGNAL bsn_init        : STD_LOGIC_VECTOR(c_bsn_w-1 DOWNTO 0) := (OTHERS=>'0');
+  SIGNAL bsn_time_offset : STD_LOGIC_VECTOR(c_bsn_time_offset_w-1 DOWNTO 0) := (OTHERS=>'0');
+  SIGNAL bs_sosi         : t_dp_sosi;
 
   -- Verify
-  SIGNAL verify_sync  : STD_LOGIC := '0';
-  SIGNAL hold_bs_sop  : STD_LOGIC;
-
-  SIGNAL tb_bsn_cnt   : INTEGER := 0;
-
-  -- Define the PPS grid and the BSN grid that both start at 0 according to Figure 3.1 in [doc]:
-  SIGNAL SSN                 : NATURAL   := 0;
-  SIGNAL BSN                 : NATURAL   := 0;
-  
-  SIGNAL pps_sop             : STD_LOGIC := '0';
-  SIGNAL nxt_pps_sop         : STD_LOGIC := '0';
-  SIGNAL pps_eop             : STD_LOGIC := '0';
-  SIGNAL bsn_sop             : STD_LOGIC := '0';
-  SIGNAL nxt_bsn_sop         : STD_LOGIC := '0';
-  SIGNAL bsn_eop             : STD_LOGIC := '0';
-  SIGNAL expected_sync       : STD_LOGIC := '0';
-  SIGNAL expected_sync_dly   : STD_LOGIC := '0';
-  SIGNAL expected_bsn        : NATURAL   := 0;
-  SIGNAL expected_offset_bsn : NATURAL   := 0;
-  SIGNAL dbg_nof_blk         : NATURAL   := 0;
-  SIGNAL dbg_accumulate      : NATURAL   := 0;
-  SIGNAL dbg_expected_bsn    : NATURAL   := 0;
+  SIGNAL exp_grid           : t_time_grid;
+  SIGNAL unexpected_bs_sync : STD_LOGIC;
+  SIGNAL sl0                : STD_LOGIC := '0';
+  SIGNAL verify_en          : STD_LOGIC := '0';
+  SIGNAL verify_sync        : STD_LOGIC := '0';
+  SIGNAL hold_bs_sop        : STD_LOGIC;
+  SIGNAL prev_bs_valid      : STD_LOGIC;
+  SIGNAL bs_starts_cnt      : NATURAL := 0;
+
+  SIGNAL dbg_c_nof_pps      : NATURAL := c_nof_pps;
+  SIGNAL dbg_c_nof_repeat   : NATURAL := c_nof_repeat;
+
+  SIGNAL dbg_nof_blk        : NATURAL;
+  SIGNAL dbg_accumulate     : NATURAL;
+  SIGNAL dbg_expected_bsn   : NATURAL;
 
 BEGIN
 
-  -----------------------------------------------------------------------------
-  -- Stimuli
-  -----------------------------------------------------------------------------
   rst <= '1', '0' AFTER c_clk_period*7;
   clk <= (NOT clk) OR tb_end AFTER c_clk_period/2;
 
-  
-  -- SSN/BSN generator
-  SSN <= SSN + 1 WHEN rising_edge(clk) AND pps_eop='1';              -- Seconds Sequence Number
-  BSN <= BSN + 1 WHEN rising_edge(clk) AND bsn_eop='1';              -- Block Sequence Number
-
-  proc_common_gen_pulse(1, g_pps_interval, '1', rst, clk, nxt_pps_sop);  -- make PPS grid with pps_sop at start of interval
-  pps_sop <= nxt_pps_sop AFTER c_clk_period;
-  pps_eop <= pps_sop'DELAYED((g_pps_interval-1)*c_clk_period);     -- make PPS grid with pps_eop at end of intervals
-  
-  proc_common_gen_pulse(1, g_block_size, '1', rst, clk, nxt_bsn_sop);    -- make BSN grid with bsn_sop at start of interval
-  bsn_sop <= nxt_bsn_sop AFTER c_clk_period;
-  bsn_eop <= bsn_sop'DELAYED((g_block_size-1)*c_clk_period);       -- make BSN grid with bsn_eop at end of interval
- 
-  -- Define the expected sync that occurs when pps_sop = bsn sop, or else at the first bsn_sop after the pps_sop, see Figure 3.1 in [doc].
-  p_expected_sync : PROCESS
+  -----------------------------------------------------------------------------
+  -- Generate reference time grid
+  -----------------------------------------------------------------------------
+  proc_common_gen_pulse(1, g_pps_interval, '1', sl0, clk, ref_grid.pps);
+  proc_common_gen_pulse(1, g_block_size, '1', sl0, clk, ref_grid.sop);
+  ref_grid.eop <= ref_grid.sop'DELAYED((g_block_size - 1) * c_clk_period);
+  ssn_eop <= ref_grid.pps'DELAYED((g_pps_interval - 1) * c_clk_period);
+  ref_grid.ssn <= ref_grid.ssn + 1 WHEN rising_edge(clk) AND ssn_eop = '1';
+  ref_grid.bsn <= ref_grid.bsn + 1 WHEN rising_edge(clk) AND ref_grid.eop = '1';
+
+  -- Issue sync at start of block
+  p_ref_grid_sync : PROCESS(ref_grid, hold_pps)
   BEGIN
-    WAIT UNTIL rising_edge(clk);
-    expected_sync <= '0';
-    proc_common_wait_until_high(clk, pps_sop);
-    IF bsn_sop = '1' THEN
-      expected_sync <= '1';
-      expected_bsn <= BSN+1;
-      expected_offset_bsn <= 0;
-    ELSE
-      proc_common_wait_until_high(clk, bsn_sop);
-      expected_sync <= '1';
-      expected_bsn <= BSN+1;
-      expected_offset_bsn <= (BSN+1) * g_block_size - SSN * g_pps_interval;
+    ref_grid.sync <= '0';
+    nxt_hold_pps <= hold_pps;
+
+    IF ref_grid.pps = '1' THEN
+      IF ref_grid.sop = '1' THEN
+        ref_grid.sync <= '1';  -- immediately issue sync
+      ELSE
+        nxt_hold_pps <= '1';  -- wait until next block
+      END IF;
+    END IF;
+
+    IF hold_pps = '1' THEN
+      IF ref_grid.sop = '1' THEN
+        ref_grid.sync <= '1';  -- issue pending sync
+        nxt_hold_pps <= '0';
+      END IF;
     END IF;
   END PROCESS;
-  
-  expected_sync_dly <= expected_sync'DELAYED(c_dut_latency*c_clk_period);
 
-  -- MM control
+  hold_pps <= nxt_hold_pps WHEN rising_edge(clk);
+
+  exp_grid <= ref_grid'DELAYED(c_dut_latency * c_clk_period);
+
+  -----------------------------------------------------------------------------
+  -- Stimuli
+  -----------------------------------------------------------------------------
   p_mm : PROCESS
-    VARIABLE v_bsn_time_offset : NATURAL;
+    VARIABLE v_ssn             : NATURAL;
     VARIABLE v_bsn_init        : NATURAL;
+    VARIABLE v_bsn_time_offset : NATURAL;
   BEGIN
-    tb_end   <= '0';
-    tb_state <= s_disable;
-    --pps      <= '0';
-
-    dp_on   <= '0';
-    dp_on_pps  <= '0';
-
     -- Get synchronous to clk
     proc_common_wait_until_low(clk, rst);
-    proc_common_wait_some_cycles(clk, 500);
+    proc_common_wait_some_cycles(clk, 10);
 
     -- Start asynchronously by making dp_on high
-    proc_common_wait_until_high(clk, expected_sync_dly);
-    tb_state  <= s_pps_start;
+    verify_en <= '0';  -- only verify visualy in wave window
+    tb_state <= s_dp_on;
     dp_on_pps <= '0';
-    dp_on     <= '1';
-    verify_sync <= '0';  -- only verify visualy in wave window
-    proc_common_wait_some_cycles(clk, g_nof_pps*g_pps_interval);
-    verify_sync <= '0';
-    -- Stop by making dp_on low
+    dp_on <= '1';
+    proc_common_wait_some_cycles(clk, c_nof_pps*g_pps_interval);
     tb_state <= s_disable;
     dp_on <= '0';
     dp_on_pps <= '0';
-    
-    -- wait until one pps_interval before next begin of SSN generator (pps_sop = bsn_sop)    
-    proc_common_wait_until_high(clk, pps_sop);
-    v_bsn_time_offset := ((SSN + 2) * g_pps_interval) MOD g_block_size;
-    WHILE v_bsn_time_offset > 0 LOOP
-      proc_common_wait_some_cycles(clk, g_pps_interval);
-      v_bsn_time_offset := ((SSN + 2) * g_pps_interval) MOD g_block_size;
-    END LOOP;
+    proc_common_wait_some_cycles(clk, 10);
 
-    -- Start synchronously by making dp_on high at pps
-    FOR i IN 0 TO 2 LOOP
-      -- Now start on PPS
-      proc_common_wait_until_high(clk, expected_sync);
-      v_bsn_time_offset := ((SSN + 1) * g_pps_interval) MOD g_block_size;
-      v_bsn_init := ((SSN + 1) * g_pps_interval) / g_block_size;
-      IF v_bsn_time_offset = 0 THEN
-        bsn_init <= TO_UVEC(v_bsn_init, c_bsn_w); 
-      ELSE
-        bsn_init <= TO_UVEC(v_bsn_init+1, c_bsn_w); 
-      END IF;
-      tb_state  <= s_pps_start;
+    -- Start synchronously by making dp_on and dp_on_pps high
+    verify_en <= '1';  -- verify automatically in test bench
+
+    FOR I IN 0 TO c_nof_repeat-1 LOOP
+      -- Wait some variable time between tests, to enforce testing different
+      -- bsn_time_offset values
+      proc_common_wait_some_cycles(clk, 20);
+      proc_common_wait_some_cycles(clk, I*g_pps_interval);
+
+      -- Wait until in the beginning of PPS interval
+      proc_common_wait_until_hi_lo(clk, ref_grid.pps);
+      proc_common_wait_some_cycles(clk, c_dut_latency);
+
+      -- Determine bsn_init and bsn_time_offset for BSN source start
+      -- . bsn_init = BSN at sync
+      -- . bsn_time_offset = number of clk that sync occurs after PPS
+      v_ssn := ref_grid.ssn + 1;  -- +1 to prepare start in next PPS interval
+      v_bsn_init := ceil_div(v_SSN * g_pps_interval, g_block_size);  -- Equation 3.6 in [1]
+      v_bsn_time_offset := v_bsn_init * g_block_size - v_SSN * g_pps_interval;  -- Equation 3.7 in [1]
+      bsn_init <= TO_UVEC(v_bsn_init, c_bsn_w);  --
+      bsn_time_offset <= TO_UVEC(v_bsn_time_offset, c_bsn_time_offset_w);
+      -- Start synchronously by making dp_on and dp_on_pps high
+      tb_state  <= s_dp_on_pps;
       dp_on_pps <= '1';
-      dp_on     <= '1';
-      verify_sync <= '1';  -- verify automatically in test bench
-      proc_common_wait_some_cycles(clk, g_nof_pps*g_pps_interval);
-      verify_sync <= '0';
-      -- Stop by making dp_on low
+      dp_on <= '1';
+      proc_common_wait_some_cycles(clk, c_nof_pps*g_pps_interval);
       tb_state <= s_disable;
       dp_on <= '0';
       dp_on_pps <= '0';
-      
-      -- wait until one pps_interval before next begin of SSN generator (pps_sop = bsn_sop)    
-      proc_common_wait_until_high(clk, pps_sop);
-      v_bsn_time_offset := ((SSN + 2) * g_pps_interval) MOD g_block_size;
-      WHILE v_bsn_time_offset > 0 LOOP
-        proc_common_wait_some_cycles(clk, g_pps_interval);
-        v_bsn_time_offset := ((SSN + 2) * g_pps_interval) MOD g_block_size;
-      END LOOP;
-
     END LOOP;
 
-    tb_end   <= '1';
+    proc_common_wait_some_cycles(clk, 10);
+    ASSERT bs_starts_cnt = 1 + c_nof_repeat REPORT "Wrong number of BSN source starts." SEVERITY ERROR;
+
+    tb_end <= '1';
     WAIT;
   END PROCESS;
 
 
   -----------------------------------------------------------------------------
   -- Verification
+  -- . Some aspects of bs_sosi are verified multiple times in different ways,
+  --   this overlap is fine, because the tb and DUT are rather complicated, so
+  --   using different approaches also helpt to verify the tb itself.
   -----------------------------------------------------------------------------
+  verify_sync <= verify_en AND bs_sosi.valid;
+
   proc_dp_verify_sop_and_eop(clk, bs_sosi.valid, bs_sosi.sop, bs_sosi.eop, hold_bs_sop);  -- Verify that sop and eop come in pairs
-  proc_dp_verify_sync(clk, verify_sync, bs_sosi.sync, bs_sosi.sop, expected_sync_dly);  -- Verify sync at sop and at expected_sync
-
-  -- Verify sync at sop and at expected_sync again:
-  -- . now using the proc_dp_verify_sync() variant for dp_bsn_source_v2 that
-  --   can verify fractional sync periods.
-  -- . the proc_dp_verify_sync() v2 variant was made later, so in fact
-  --   this tb_dp_bsn_source_v2 verifies this new v2 procedure.
-  proc_dp_verify_sync(TO_UINT(bsn_init),
+  --proc_dp_verify_sync(clk, verify_sync, bs_sosi.sync, exp_grid.sop, exp_grid.sync);  -- Verify sync at sop and at expected_sync
+
+  -- Verify sync at sop and at expected_sync
+  proc_dp_verify_sync(0,  -- start bsn of PPS grid and BSN grid is 0, see [1]
                       g_pps_interval,
                       g_block_size,
                       clk,
-                      verify_sync,
+                      verify_en,
                       bs_sosi.sync,
                       bs_sosi.sop,
                       bs_sosi.bsn,
@@ -240,6 +272,38 @@ BEGIN
                       dbg_accumulate,
                       dbg_expected_bsn);
 
+  -- Verify bs_sosi by comparing with exp_grid, this again verifies bs_sosi.sync, sop and bsn
+  p_verify_bs_sosi_grid : PROCESS(clk)
+  BEGIN
+    IF rising_edge(clk) THEN
+      unexpected_bs_sync <= '0';
+      IF verify_en = '1' AND bs_sosi.valid = '1' THEN
+        ASSERT TO_UINT(bs_sosi.bsn) = exp_grid.bsn REPORT "Wrong bs_sosi.bsn /= exp_grid.bsn" SEVERITY ERROR;
+        ASSERT bs_sosi.sync = exp_grid.sync REPORT "Wrong bs_sosi.sync /= exp_grid.sync" SEVERITY ERROR;
+        ASSERT bs_sosi.sop = exp_grid.sop REPORT "Wrong bs_sosi.sop /= exp_grid.sop" SEVERITY ERROR;
+        ASSERT bs_sosi.eop = exp_grid.eop REPORT "Wrong bs_sosi.eop /= exp_grid.eop" SEVERITY ERROR;
+        -- Mark error in Wave window
+        IF bs_sosi.sync = '1' AND bs_sosi.sync /= exp_grid.sync THEN
+          unexpected_bs_sync <= '1';
+        END IF;
+      END IF;
+    END IF;
+  END PROCESS;
+
+  -- Verify that bs_sosi.valid = '1' did happen after dp_on by verifying bs_start
+  prev_bs_valid <= bs_sosi.valid WHEN rising_edge(clk);
+  bs_starts_cnt <= bs_starts_cnt + 1 WHEN rising_edge(clk) AND bs_sosi.valid = '1' AND prev_bs_valid = '0';
+
+  p_verify_bs_restart : PROCESS(clk)
+  BEGIN
+    IF rising_edge(clk) THEN
+      IF bs_restart = '1' THEN
+        ASSERT bs_sosi.sync = '1' REPORT "Unexpected bs_start while bs_sosi.sync /= 1" SEVERITY ERROR;
+        ASSERT prev_bs_valid = '0' REPORT "Unexpected bs_start while prev_bs_valid /= 0" SEVERITY ERROR;
+      END IF;
+    END IF;
+  END PROCESS;
+
   -----------------------------------------------------------------------------
   -- DUT: dp_bsn_source_v2
   -----------------------------------------------------------------------------
@@ -248,18 +312,25 @@ BEGIN
   GENERIC MAP (
     g_block_size         => g_block_size,
     g_nof_clk_per_sync   => g_pps_interval,
-    g_bsn_w              => c_bsn_w
+    g_bsn_w              => c_bsn_w,
+    g_bsn_time_offset_w  => c_bsn_time_offset_w
   )
   PORT MAP (
-    rst       => rst,
-    clk       => clk,
-    pps       => pps_sop,
+    rst              => rst,
+    clk              => clk,
+    pps              => ref_grid.pps,
     -- MM control
-    dp_on     => dp_on,
-    dp_on_pps => dp_on_pps,
-    bsn_init  => bsn_init,
+    dp_on            => dp_on,
+    dp_on_pps        => dp_on_pps,
+
+    dp_on_status     => dp_on_status,  -- = src_out.valid
+    bs_restart       => bs_restart,    -- = src_out.sop for first sop after dp_on went high
+
+    bsn_init         => bsn_init,
+    bsn_time_offset  => bsn_time_offset,
+
     -- Streaming
-    src_out   => bs_sosi
+    src_out          => bs_sosi
   );
 
 END tb;
diff --git a/libraries/base/dp/tb/vhdl/tb_dp_bsn_sync_scheduler.vhd b/libraries/base/dp/tb/vhdl/tb_dp_bsn_sync_scheduler.vhd
index 930588f18e0c8b0114dd064c864f1449a19ea81a..8d301b21d186c7a2f6c9d51367c7afd3ff28b445 100644
--- a/libraries/base/dp/tb/vhdl/tb_dp_bsn_sync_scheduler.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_dp_bsn_sync_scheduler.vhd
@@ -63,6 +63,10 @@
 --    temporarily changing the ASSERT condition
 --  b Initialy used LOOP in p_stimuli to repeat test. Later used list of
 --    c_nof_test_intervals and tb_state to try different stimuli.
+--
+-- References:
+-- [1] https://support.astron.nl/confluence/pages/viewpage.action?spaceKey=L2M&title=L2+STAT+Decision%3A+Timing+in+Station
+--
 
 LIBRARY IEEE, common_lib, dp_lib;
 USE IEEE.STD_LOGIC_1164.ALL;
@@ -516,17 +520,18 @@ BEGIN
   -----------------------------------------------------------------------------
   verify_sync <= NOT recover_from_in_lost;
 
+  -- Verify that sync is on PPS and BSN grid as defined in [1].
   proc_dp_verify_sync(TO_UINT(ctrl_start_bsn),
-                              ctrl_interval_size,
-                              g_block_size,
-                              clk,
-                              verify_sync,
-                              out_sosi.sync,
-                              out_sosi.sop,
-                              out_sosi.bsn,
-                              dbg_nof_blk,
-                              dbg_accumulate,
-                              dbg_expected_bsn);
+                      ctrl_interval_size,
+                      g_block_size,
+                      clk,
+                      verify_sync,
+                      out_sosi.sync,
+                      out_sosi.sop,
+                      out_sosi.bsn,
+                      dbg_nof_blk,
+                      dbg_accumulate,
+                      dbg_expected_bsn);
 
   dbg_out_sosi_sync <= out_sosi.sync;
   dbg_out_sosi_sop  <= out_sosi.sop;
diff --git a/libraries/base/dp/tb/vhdl/tb_tb_dp_bsn_source_v2.vhd b/libraries/base/dp/tb/vhdl/tb_tb_dp_bsn_source_v2.vhd
index b893581245a4262daf58758174bb4fd8ba9558fd..63a5385422a3adbef497d38fbdd597664134d926 100644
--- a/libraries/base/dp/tb/vhdl/tb_tb_dp_bsn_source_v2.vhd
+++ b/libraries/base/dp/tb/vhdl/tb_tb_dp_bsn_source_v2.vhd
@@ -25,7 +25,7 @@ USE IEEE.std_logic_1164.ALL;
 USE work.tb_dp_pkg.ALL;
 
 
--- > as 2
+-- > as 4
 -- > run -all --> OK
 
 ENTITY tb_tb_dp_bsn_source_v2 IS
@@ -33,30 +33,44 @@ END tb_tb_dp_bsn_source_v2;
 
 
 ARCHITECTURE tb OF tb_tb_dp_bsn_source_v2 IS
-  SIGNAL tb_end : STD_LOGIC := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
 
-  CONSTANT c_nof_pps : NATURAL := 50;  -- choose > g_block_size, because the fractional sync pattern will repeat
-                                       -- within g_block_size number of g_pps_interval's
+  SIGNAL tb_end : STD_LOGIC := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
 
 BEGIN
   -- from tb_dp_bsn_source_v2.vhd
   --
-  -- g_nof_pps      : NATURAL := 20;
   -- g_pps_interval : NATURAL := 240
-  -- g_block_size   : NATURAL := 32  
+  -- g_block_size   : NATURAL := 32
+
+  -- test integer case
+  u_20_10  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (20, 10);  -- 20 // 10 = 2, 20 MOD 10 = 0, 20/10 = 2 block/sync
+  u_22_11  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (22, 11);  -- 22 // 11 = 2, 22 MOD 11 = 0, 22/11 = 2 block/sync
+  u_39_13  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (39, 13);  -- 39 // 13 = 3, 39 MOD 13 = 0, 39/13 = 3 block/sync
+
+  -- test smallest nof block per sync
+  u_10_10  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (10, 10);  -- 1 block/sync
+  u_5_5    : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (5, 5);    -- 1 block/sync
+
+  -- test smallest g_block_size case
+  u_3_3    : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (3, 3);  -- 3 // 3 = 1, 3 MOD 3 = 0, 3/3 = 1 block/sync
+  u_6_3    : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (6, 3);  -- 6 // 3 = 2, 6 MOD 3 = 0, 6/3 = 2 block/sync
+  u_7_3    : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (7, 3);  -- 7 // 3 = 2, 7 MOD 3 = 1, 7/3 = 2.33 block/sync
 
-  -- test different clk_per_sync
-  u_230_32_div_7_mod_6  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 230, 32);  -- 230 / 32 = 7, 230 MOD 32 = 6
-  u_240_32_div_7_mod_16 : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 240, 32);  -- 240 / 32 = 7, 240 MOD 32 = 16
-  u_248_32_div_7_mod_24 : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 248, 32);  -- 248 / 32 = 7, 248 MOD 32 = 24
+  -- test lofar case with 0.5 fraction in average nof block/sync
+  u_20_8   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (20, 8);  -- 20 // 8 = 2, 20 MOD 8 = 4, 20/8 = 2.5 block/sync
 
-  -- test different block_size's
-  u_240_27_div_8_mod_24 : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 240, 27);  -- 240 / 27 = 8, 240 MOD 27 = 24
-  u_240_30_div_8_mod_0  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 240, 30);  -- 240 / 30 = 8, 240 MOD 30 = 0
+  -- test fractional (corner) cases
+  u_18_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (18, 9);  -- 18 MOD 9 = 0
+  u_17_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (17, 9);  -- 17 MOD 9 = 8 = g_block_size - 1
+  u_19_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (19, 9);  -- 19 MOD 9 = 1
+  u_20_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (20, 9);  -- 20 MOD 9 = 2
+  u_25_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (25, 9);  -- 25 MOD 9 = 7
+  u_26_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (26, 9);  -- 26 MOD 9 = 8 = g_block_size - 1
+  u_27_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (27, 9);  -- 27 MOD 9 = 0
 
   -- test some prime values
-  u_101_17_div_5_mod_16  : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 101, 17);  -- 101 / 17 = 5, 101 MOD 17 = 16
-  u_101_23_div_4_mod_9   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (c_nof_pps, 101, 23);  -- 101 / 23 = 4, 101 MOD 23 = 9
+  u_17_3   : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (17, 3);    -- 17 // 3 = 5, 17 MOD 3 = 2, 17/3 = 5.66 block/sync
+  u_101_17 : ENTITY work.tb_dp_bsn_source_v2 GENERIC MAP (101, 17);  -- 101 // 17 = 5, 101 MOD 17 = 16, 101/17 = 5.9411 block/sync
 
 END tb;