]> jspc29.x-matter.uni-frankfurt.de Git - trb3.git/commitdiff
add LUPO trigger receiver to CTS master
authorJan Michel <michel@physik.uni-frankfurt.de>
Wed, 26 Nov 2025 14:00:40 +0000 (15:00 +0100)
committerJan Michel <michel@physik.uni-frankfurt.de>
Wed, 26 Nov 2025 14:00:40 +0000 (15:00 +0100)
cts/source/clock_sense.vhd [new file with mode: 0644]
cts/source/timestamp_lupo.vhd [new file with mode: 0644]

diff --git a/cts/source/clock_sense.vhd b/cts/source/clock_sense.vhd
new file mode 100644 (file)
index 0000000..b9a24d6
--- /dev/null
@@ -0,0 +1,229 @@
+library ieee;\r
+  use ieee.std_logic_1164.all;\r
+  use ieee.numeric_std.all;\r
+\r
+library work;\r
+\r
+entity clock_sense is\r
+  port(\r
+    CLEAR           : in  std_logic;\r
+    CLK             : in  std_logic; -- 100MHz system clock\r
+    LUPO_CLK        : in  std_logic; -- 25MHz LUPO clock\r
+    LUPO_RESET      : in  std_logic; -- LUPO reset signal\r
+    CLK_OK_OUT      : out std_logic;\r
+    RST_PLL_OUT     : out std_logic;\r
+    LUPO_VALID_OUT  : out std_logic;\r
+    LOCAL_CTR_OUT   : out std_logic_vector(47 downto 0);\r
+    CENTRAL_CTR_OUT : out std_logic_vector(47 downto 0);\r
+    RESULT_OUT      : out std_logic_vector(15 downto 0)\r
+  );\r
+end entity clock_sense;\r
+\r
+architecture clock_sense_arch of clock_sense is\r
+\r
+  -- normal signals\r
+  signal main_counter     : unsigned(15 downto 0);\r
+  signal clk_counter      : unsigned(15 downto 0);\r
+  signal main_done_x      : std_logic;\r
+  signal main_done        : std_logic_vector(3 downto 0);\r
+  signal clk_register     : std_logic_vector(15 downto 0);\r
+  signal clk_in_range_x   : std_logic;\r
+  signal clk_in_range     : std_logic;\r
+\r
+  signal clk_divided      : std_logic;\r
+  signal clk_sample_q     : std_logic_vector(2 downto 0);\r
+  signal clk_re_x         : std_logic;\r
+  signal clk_re           : std_logic;\r
+\r
+  signal lock_counter     : unsigned(4 downto 0);\r
+  signal ce_lock_ctr_x    : std_logic;\r
+  signal rst_lock_ctr_x   : std_logic;\r
+  signal clk_locked       : std_logic;\r
+\r
+  signal real_sample_q    : std_logic_vector(2 downto 0);\r
+  signal real_re_x        : std_logic;\r
+  signal real_re          : std_logic;\r
+\r
+  signal local_ctr        : unsigned(47 downto 0);\r
+  signal central_ctr      : unsigned(47 downto 0);\r
+\r
+  signal sync_lupo_rst_q  : std_logic_vector(2 downto 0);\r
+  signal lupo_rst_re_x    : std_logic;\r
+  signal lupo_rst_re      : std_logic;\r
+  signal lupo_rst_fe_x    : std_logic;\r
+  signal lupo_rst_fe      : std_logic;\r
+\r
+  signal lupo_valid     : std_logic;\r
+\r
+begin\r
+\r
+  -- main counter, driven by 100MHz\r
+  PROC_MAIN_COUNTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      main_counter <= (others => '0');\r
+      main_done    <= (others => '0');\r
+    elsif( rising_edge(CLK) ) then\r
+      main_counter <= main_counter + 1;\r
+      main_done    <= main_done(2 downto 0) & main_done_x;\r
+    end if;\r
+  end process PROC_MAIN_COUNTER;\r
+\r
+  -- overflow mark, will latch results and reset counters\r
+  main_done_x <= '1' when (main_counter = x"ffff") else '0';\r
+\r
+  -- divide external clock by two to get on normal routing\r
+  PROC_CLK_DIVIDE: process( LUPO_CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      clk_divided <= '0';\r
+    elsif( rising_edge(LUPO_CLK) ) then\r
+      clk_divided <= not clk_divided;\r
+    end if;\r
+  end process PROC_CLK_DIVIDE;\r
+\r
+  -- detect rising edge of divided signal\r
+  PROC_SHIFTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      clk_sample_q <= (others => '0');\r
+      clk_re       <= '0';\r
+      clk_in_range <= '0';\r
+    elsif( rising_edge(CLK) ) then\r
+      clk_sample_q <= clk_sample_q(1 downto 0) & clk_divided;\r
+      clk_re       <= clk_re_x;\r
+      clk_in_range <= clk_in_range_x;\r
+    end if;\r
+  end process PROC_SHIFTER;\r
+\r
+  -- detect rising edge of divided clock\r
+  clk_re_x <= '1' when (clk_sample_q(2 downto 1) = b"01") else '0';\r
+\r
+  -- "alien" clock, let's assume 25MHz\r
+  PROC_LUPO_CLK_COUNTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      clk_counter <= (others => '0');\r
+      clk_register <= (others => '0');\r
+    elsif( rising_edge(CLK) ) then\r
+      if   ( main_done(0) = '1' ) then\r
+        clk_counter <= (others => '0');\r
+        clk_register <= std_logic_vector(clk_counter);\r
+      elsif( clk_re = '1' ) then\r
+        if( clk_counter(15) = '0' ) then\r
+          clk_counter <= clk_counter + 1;\r
+        end if;\r
+      end if;\r
+    end if;\r
+  end process PROC_LUPO_CLK_COUNTER;\r
+\r
+  -- let's check if the external alien clock is in range\r
+  clk_in_range_x <= '1' when (clk_register(15 downto 4) = x"1ff") or (clk_register(15 downto 4) = x"200") else '0';\r
+  -- We assume 25MHz on the pin, so we get 12.5MHz divided, and have 80ns cycle time.\r
+  -- With 16bit reference counter on 100MHz system clock, we get a period of 655.36 us, which should give us\r
+  -- 8192 clock cycles of 12.5MHz clock. We allow +/- 16 clock cycles here to compensate for slight PPM offsets.\r
+\r
+  -- We need 16 successful measurements to state "CLOCK IS OK", but one only bad measurement will cease the lock.\r
+  PROC_LOCK_COUNTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      lock_counter <= (others => '0');\r
+    elsif( rising_edge(CLK) ) then\r
+      if   ( rst_lock_ctr_x = '1' ) then\r
+        lock_counter <= (others => '0');\r
+      elsif( lock_counter(4) = '0' ) then\r
+        if( ce_lock_ctr_x = '1' ) then\r
+          lock_counter <= lock_counter + 1;\r
+        end if;\r
+      end if;\r
+    end if;\r
+  end process PROC_LOCK_COUNTER;\r
+\r
+  rst_lock_ctr_x <= '1' when ((clk_in_range = '0') and (main_done(2) = '1')) else '0';\r
+  ce_lock_ctr_x  <= '1' when ((clk_in_range = '1') and (main_done(2) = '1')) else '0';\r
+\r
+  clk_locked <= std_logic(lock_counter(4));\r
+\r
+  -- detect rising edge of clock signal\r
+  PROC_REAL_SHIFTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      real_sample_q <= (others => '0');\r
+      real_re       <= '0';\r
+    elsif( rising_edge(CLK) ) then\r
+      real_sample_q <= real_sample_q(1 downto 0) & LUPO_CLK;\r
+      real_re       <= real_re_x;\r
+    end if;\r
+  end process PROC_REAL_SHIFTER;\r
+\r
+  -- detect rising edge of clock signal\r
+--  real_re_x <= '1' when ((real_sample_q(2 downto 1) = b"01") and (clk_locked = '1')) else '0';\r
+  real_re_x <= '1' when (real_sample_q(2 downto 1) = b"01") else '0';\r
+\r
+  -- local 100MHz counter\r
+  PROC_LOCAL_COUNTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      local_ctr <= (others => '0');\r
+    elsif( rising_edge(CLK) ) then\r
+      local_ctr <= local_ctr + 1;\r
+    end if;\r
+  end process PROC_LOCAL_COUNTER;\r
+\r
+  -- central 25MHz counter\r
+  PROC_CENTRAL_COUNTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      central_ctr <= (others => '0');\r
+    elsif( rising_edge(CLK) ) then\r
+      if   ( (clk_locked = '0') or (lupo_rst_fe = '1') ) then\r
+        central_ctr <= (others => '0');\r
+      elsif( real_re = '1' ) then\r
+        central_ctr <= central_ctr + 1;\r
+      end if;\r
+    end if;\r
+  end process PROC_CENTRAL_COUNTER;\r
+\r
+  -- detect edges of LUPO reset signal\r
+  PROC_LUPO_RST_SHIFTER: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      sync_lupo_rst_q <= (others => '0');\r
+      lupo_rst_re     <= '0';\r
+      lupo_rst_fe     <= '0';\r
+    elsif( rising_edge(CLK) ) then\r
+      sync_lupo_rst_q <= sync_lupo_rst_q(1 downto 0) & LUPO_RESET;\r
+      lupo_rst_re     <= lupo_rst_re_x;\r
+      lupo_rst_fe     <= lupo_rst_fe_x;\r
+    end if;\r
+  end process PROC_LUPO_RST_SHIFTER;\r
+\r
+  -- detect edges on LUPO RESET signal\r
+  lupo_rst_re_x <= '1' when (sync_lupo_rst_q(2 downto 1) = b"01") else '0';\r
+  lupo_rst_fe_x <= '1' when (sync_lupo_rst_q(2 downto 1) = b"10") else '0';\r
+\r
+  -- generate a "LUPO valid" signal:\r
+  -- set only if the LUPO clock is valid and a LUPO reset is sent.\r
+  -- reset with clock being invalid, or by sending a LUPO reset while clock is invalid\r
+  PROC_LUPO_VALID: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      lupo_valid <= '0';\r
+    elsif( rising_edge(CLK) ) then\r
+      if   ( clk_locked = '0' ) then\r
+        lupo_valid <= '0';\r
+      elsif( lupo_rst_fe = '1' ) then\r
+        lupo_valid <= '1';\r
+      end if;\r
+    end if;\r
+  end process PROC_LUPO_VALID;\r
+\r
+  -- outputs\r
+  CLK_OK_OUT      <=     clk_locked;\r
+  RST_PLL_OUT     <= not clk_locked;\r
+  LUPO_VALID_OUT  <= lupo_valid;\r
+  RESULT_OUT      <= clk_register;\r
+  CENTRAL_CTR_OUT <= std_logic_vector(central_ctr);\r
+  LOCAL_CTR_OUT   <= std_logic_vector(local_ctr);\r
+\r
+end architecture clock_sense_arch;\r
diff --git a/cts/source/timestamp_lupo.vhd b/cts/source/timestamp_lupo.vhd
new file mode 100644 (file)
index 0000000..bb356d2
--- /dev/null
@@ -0,0 +1,157 @@
+library IEEE;\r
+use IEEE.STD_LOGIC_1164.ALL;\r
+use IEEE.numeric_std.All;\r
+\r
+library work;\r
+use work.trb_net_std.all;\r
+\r
+\r
+entity timestamp_lupo is\r
+  port(\r
+    CLK                 : in std_logic; -- system clk 100 MHz\r
+    CLEAR               : in std_logic; -- system reset\r
+    -- LUPO interface\r
+    TIMER_CLOCK_IN      : in std_logic;\r
+    TIMER_RESET_IN      : in std_logic;\r
+    -- trigger signal (already in 100MHZ domain)\r
+    TRIGGER_IN          : in  std_logic;\r
+    -- readout interface\r
+    BUSRDO_RX           : in  READOUT_RX;\r
+    BUSRDO_TX           : out READOUT_TX\r
+  );\r
+end entity timestamp_lupo;\r
+\r
+\r
+\r
+architecture arch1 of timestamp_lupo is\r
+\r
+  type   state_readout is (RDO_IDLE, RDO_WRITE0, RDO_WRITE1, RDO_WRITE2, RDO_WRITE3, RDO_WRITE4, RDO_WRITE5, RDO_FINISH);\r
+  signal rdostate : state_readout := RDO_IDLE;\r
+\r
+  signal trigger_q                : std_logic;\r
+  signal trigger_re_x           : std_logic;\r
+  signal trigger_re             : std_logic;\r
+\r
+  signal local_ctr                : std_logic_vector(47 downto 0);\r
+  signal central_ctr              : std_logic_vector(47 downto 0);\r
+  signal local_reg                : std_logic_vector(47 downto 0);\r
+  signal central_reg              : std_logic_vector(47 downto 0);\r
+\r
+  signal readout_done           : std_logic;\r
+\r
+  signal lupo_valid             : std_logic;\r
+  signal lupo_signature         : std_logic_vector(31 downto 0);\r
+\r
+begin\r
+\r
+  -- delay trigger signal by one cycle for edge detection\r
+  PROC_TRG_RE: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      trigger_q <= '0';\r
+      trigger_re <= '0';\r
+    elsif( rising_edge(CLK) ) then\r
+      trigger_q <= TRIGGER_IN;\r
+      trigger_re <= trigger_re_x;\r
+    end if;\r
+  end process PROC_TRG_RE;\r
+\r
+  trigger_re_x <= '1' when ((TRIGGER_IN = '1') and (trigger_q = '0')) else '0';\r
+\r
+  -- Clock surveillance and scaler counters\r
+  THE_CLOCK_SENSE: entity work.clock_sense\r
+  port map(\r
+    CLEAR           => CLEAR,\r
+    CLK             => CLK,\r
+    LUPO_CLK        => TIMER_CLOCK_IN,\r
+    LUPO_RESET      => TIMER_RESET_IN,\r
+    CLK_OK_OUT      => open,\r
+    RST_PLL_OUT     => open,\r
+    LUPO_VALID_OUT  => lupo_valid,\r
+    LOCAL_CTR_OUT   => local_ctr,\r
+    CENTRAL_CTR_OUT => central_ctr,\r
+    RESULT_OUT      => open\r
+  );\r
+\r
+  -- multiplexer for signature, will show if LUPO data is trustworth\r
+  PROC_SEL_SIGNATURE: process( lupo_valid )\r
+  begin\r
+    case lupo_valid is\r
+      when '1'    => lupo_signature <= x"4c55504f";\r
+      when others => lupo_signature <= x"6c75706f";\r
+    end case;\r
+  end process PROC_SEL_SIGNATURE;\r
+\r
+  -- Latch process for counters\r
+  PROC_LATCH_COUNTERS: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      local_reg   <= (others => '0');\r
+      central_reg <= (others => '0');\r
+    elsif( rising_edge(CLK) ) then\r
+      if   ( readout_done = '1' ) then\r
+        local_reg   <= x"aaaaaaaaaaaa";\r
+        central_reg <= x"aaaaaaaaaaaa";\r
+      elsif( trigger_re = '1' ) then\r
+        local_reg <= local_ctr;\r
+        central_reg <= centraL_ctr;\r
+      end if;\r
+    end if;\r
+  end process PROC_LATCH_COUNTERS;\r
+\r
+\r
+  -- Readout process\r
+  PROC_RDO: process( CLK, CLEAR )\r
+  begin\r
+    if   ( CLEAR = '1' ) then\r
+      rdostate     <= RDO_IDLE;\r
+      BUSRDO_TX.data_write        <= '0';\r
+      BUSRDO_TX.data_finished   <= '0';\r
+      BUSRDO_TX.statusbits        <= (others => '0');\r
+      BUSRDO_TX.data              <= x"00000000";\r
+      BUSRDO_TX.busy_release      <= '0';\r
+      readout_done               <= '0';\r
+    elsif( rising_edge(CLK) ) then\r
+      case rdostate is\r
+        when RDO_IDLE =>\r
+          if( (trigger_re = '1') or (BUSRDO_RX.valid_notiming_trg = '1') or (BUSRDO_RX.invalid_trg = '1') ) then\r
+            rdostate <= RDO_WRITE0;\r
+          end if;\r
+        when RDO_WRITE0 =>\r
+          rdostate  <= RDO_WRITE1;\r
+          BUSRDO_TX.data  <= lupo_signature; -- "LUPO" or "lupo"\r
+          BUSRDO_TX.data_write <= '1';\r
+        when RDO_WRITE1 =>\r
+          rdostate  <= RDO_WRITE2;\r
+          BUSRDO_TX.data <= x"a0" & central_reg(47 downto 24);\r
+          BUSRDO_TX.data_write <= '1';\r
+        when RDO_WRITE2 =>\r
+          rdostate <= RDO_WRITE3;\r
+          BUSRDO_TX.data <= x"a1" & central_reg(23 downto 0);\r
+          BUSRDO_TX.data_write <= '1';\r
+        when RDO_WRITE3 =>\r
+          rdostate <= RDO_WRITE4;\r
+          BUSRDO_TX.data <= x"a2" & local_reg(47 downto 24);\r
+          BUSRDO_TX.data_write <= '1';\r
+        when RDO_WRITE4 =>\r
+          rdostate <= RDO_WRITE5;\r
+          BUSRDO_TX.data <= x"a3" & local_reg(23 downto 0);\r
+          BUSRDO_TX.data_write <= '1';\r
+        when RDO_WRITE5 =>\r
+          rdostate <= RDO_FINISH;\r
+          BUSRDO_TX.data_write <= '0';\r
+          BUSRDO_TX.data_finished <= '1';\r
+          BUSRDO_TX.busy_release <= '1';\r
+          readout_done <= '1';\r
+        when RDO_FINISH =>\r
+          rdostate     <= RDO_IDLE;\r
+          BUSRDO_TX.data_finished <= '0';\r
+          BUSRDO_TX.busy_release <= '0';\r
+          readout_done <= '0';\r
+        when others =>\r
+          rdostate     <= RDO_IDLE;\r
+      end case;\r
+    end if;\r
+  end process PROC_RDO;\r
+\r
+end architecture;\r