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