------------------------------------------------------------------------------- -- -- Copyright 2018 Ettus Research, a National Instruments Company -- -- SPDX-License-Identifier: LGPL-3.0-or-later -- -- -- Purpose: -- -- Uses the RP and SP edges to cross a trigger from the RefClk domain to -- the SampleClk domain. The RP FE captures the input trigger and sends it to -- the SampleClk domain. There, it is double-synchronized but only allowed to pass -- when the SP RE occurs. The trigger (now in the SampleClk domain) is then passed -- through an elastic buffer before being sent on it's merry way. -- -- Below is the latency through this module. If you assert rTriggerIn before or after -- the rRP RE, then you need to add/subtract the distance to the rRP RE. -- -- Deterministic latency through this module is (starting at the rRP RE): -- Measured difference between rRP and sSP rising edges (using a TDC, positive value -- if rRP rises before sSP). -- + One period of sSP -- + Two periods of SampleClk (Double Sync) -- + (sElasticBufferPtr value + 1) * SampleClk Period -- + One period of SampleClk -- -- How much skew between RP and SP can we allow and still safely pass triggers? -- Our "launch" edge is essentially the RP FE, and our "latch" edge is the SP RE. -- Consider the no skew (RP and SP edges align) case first. Our setup and hold budget -- is balanced at T/2. Based on this, it seems we can tolerate almost T/2 skew in either -- direction (ignoring a few Reference and Sample Clock cycles here and there). -- My recommendation is to keep the skew to a minimum, like less than T/4. -- In the context of the FTDC project for N310, this should be a no-brainer since -- the SP pulses are started only a few RefClk cycles after RP. The skew is -- easily verified by taking a FTDC measurement. If the skew is less than T/4, you can -- sleep easy. If not, then I recommend doing a comprehensive analysis of how much -- settling time you have between the trigger being launched from the RefClk domain -- and latched in the SampleClk domain. -- -- vreview_group Tdc ------------------------------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library unisim; use unisim.vcomponents.all; entity CrossTrigger is port ( aReset : in boolean; RefClk : in std_logic; -- For convenience while writing this, I have only considered the N3x0 case where -- rRP is slightly ahead of sSP in phase. rRP : in boolean; -- De-asserts the clock cycle after rTriggerIn asserts. Re-asserts after the -- second falling edge of rRP, indicating new triggers can be accepted. rReadyForInput : out boolean; -- Only one pulse will be output for each rising edge of rTriggerIn. rTriggerIn is -- ignored when rReadyForInput is de-asserted. All levels are ignored when Enable -- is de-asserted. rEnableTrigger : in boolean; rTriggerIn : in boolean; SampleClk : in std_logic; sSP : in boolean; -- An elastic buffer just before the output is used to compensate for skew -- in sSP pulses across boards. Default should be in the middle of the 4 bit -- range at 7. sElasticBufferPtr : in unsigned(3 downto 0); -- Single-cycle pulse output. sTriggerOut : out boolean ); end CrossTrigger; architecture rtl of CrossTrigger is --vhook_sigstart --vhook_sigend signal rRpFE, rRpDly, rTriggerToSClk, rTriggerCaptured, sSpRE, sSpDly : boolean; signal sTriggerBuffer : unsigned(2**sElasticBufferPtr'length-1 downto 0); signal sTriggerInSClk, sTriggerInSClk_ms : boolean; function to_StdLogic(b : boolean) return std_ulogic is begin if b then return '1'; else return '0'; end if; end to_StdLogic; function to_Boolean (s : std_ulogic) return boolean is begin return (To_X01(s)='1'); end to_Boolean; attribute async_reg : string; attribute async_reg of sTriggerInSClk : signal is "TRUE"; attribute async_reg of sTriggerInSClk_ms : signal is "TRUE"; begin -- Reference Clock Domain Trigger Capture : ------------------------------------------- -- The trigger input is captured whenever it is high. The captured value is reset -- by the falling edge of rRP. -- ------------------------------------------------------------------------------------ rRpFE <= rRpDly and not rRP; CaptureTrigger : process(aReset, RefClk) begin if aReset then rTriggerCaptured <= false; rRpDly <= false; elsif rising_edge(RefClk) then rRpDly <= rRP; if not rEnableTrigger then rTriggerCaptured <= false; elsif rTriggerIn then -- Capture trigger whenever the input is asserted (so this will work with single -- cycle and multi-cycle pulses). rTriggerCaptured <= true; elsif rRpFE then -- Reset the captured trigger one cycle after the rRP FE. rTriggerCaptured <= false; end if; end if; end process; -- Send Trigger To Sample Clock Domain : ---------------------------------------------- -- Send the captured trigger on the falling edge of rRP. -- ------------------------------------------------------------------------------------ SendTrigger : process(aReset, RefClk) begin if aReset then rTriggerToSClk <= false; elsif rising_edge(RefClk) then if not rEnableTrigger then rTriggerToSClk <= false; elsif rRpFE then rTriggerToSClk <= rTriggerCaptured; end if; end if; end process; rReadyForInput <= not (rTriggerToSClk or rTriggerCaptured); -- Capture Trigger in Sample Clock Domain : ------------------------------------------- -- On the rising edge of sSP, capture the trigger. To keep things free of -- metastability, we double-sync the trigger into the SampleClk domain first. -- ------------------------------------------------------------------------------------ ReceiveAndProcessTrigger : process(aReset, SampleClk) begin if aReset then sSpDly <= false; sTriggerBuffer <= (others => '0'); sTriggerOut <= false; sTriggerInSClk_ms <= false; sTriggerInSClk <= false; elsif rising_edge(SampleClk) then -- Edge detector delays. sSpDly <= sSP; -- Double-synchronizer for trigger. sTriggerInSClk_ms <= rTriggerToSClk; sTriggerInSClk <= sTriggerInSClk_ms; -- Delay chain for the elastic buffer. Move to the left people! Note that this -- operation incurs at least one cycle of delay. Also note the trigger input is -- gated with the SP RE. sTriggerBuffer <= sTriggerBuffer(sTriggerBuffer'high-1 downto 0) & to_stdlogic(sTriggerInSClk and sSpRE); -- Based on the buffer pointer value select and flop the output one more time. sTriggerOut <= to_boolean(sTriggerBuffer(to_integer(sElasticBufferPtr))); end if; end process; -- Rising edge detectors. sSpRE <= sSP and not sSpDly; end rtl; -------------------------------------------------------------------------------- -- Testbench for CrossTrigger -- -- Meh coverage on the triggers so far... but this tests general operation -- and latency. -------------------------------------------------------------------------------- --synopsys translate_off library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity tb_CrossTrigger is end tb_CrossTrigger; architecture test of tb_CrossTrigger is -- Sets up a 1.25 MHz period. constant kClksPerPulseMaxBits: integer := 10; constant kRpPeriodInRClks : integer := 8; constant kRpHighTimeInRClks : integer := 4; constant kSpPeriodInRClks : integer := 100; constant kSpHighTimeInRClks : integer := 50; --vhook_sigstart signal aReset: boolean; signal RefClk: std_logic := '0'; signal rEnablePulser: boolean; signal rEnableTrigger: boolean; signal rReadyForInput: boolean; signal rRP: boolean; signal rTriggerIn: boolean; signal SampleClk: std_logic := '0'; signal sElasticBufferPtr: unsigned(3 downto 0); signal sEnablePulser: boolean; signal sSP: boolean; signal sTriggerOut: boolean; --vhook_sigend signal StopSim : boolean; -- shared variable Rand : Random_t; constant kSPer : time := 8.000 ns; -- 125.00 MHz constant kRPer : time := 100.000 ns; -- 10.00 MHz signal rRfiExpected: boolean:= true; signal sTriggerOutExpected: boolean:= false; procedure ClkWait( signal Clk : in std_logic; X : positive := 1) is begin for i in 1 to X loop wait until rising_edge(Clk); end loop; end procedure ClkWait; begin SampleClk <= not SampleClk after kSPer/2 when not StopSim else '0'; RefClk <= not RefClk after kRPer/2 when not StopSim else '0'; --vhook_e Pulser RpPulser --vhook_a Clk RefClk --vhook_a cLoadLimits true --vhook_a cPeriod to_unsigned(kRpPeriodInRClks,kClksPerPulseMaxBits) --vhook_a cHighTime to_unsigned(kRpHighTimeInRClks,kClksPerPulseMaxBits) --vhook_a cEnablePulse rEnablePulser --vhook_a cPulse rRP RpPulser: entity work.Pulser (rtl) generic map (kClksPerPulseMaxBits => kClksPerPulseMaxBits) --integer range 3:32 :=16 port map ( aReset => aReset, --in boolean Clk => RefClk, --in std_logic cLoadLimits => true, --in boolean cPeriod => to_unsigned(kRpPeriodInRClks,kClksPerPulseMaxBits), --in unsigned(kClksPerPulseMaxBits-1:0) cHighTime => to_unsigned(kRpHighTimeInRClks,kClksPerPulseMaxBits), --in unsigned(kClksPerPulseMaxBits-1:0) cEnablePulse => rEnablePulser, --in boolean cPulse => rRP); --out boolean --vhook_e Pulser SpPulser --vhook_a Clk SampleClk --vhook_a cLoadLimits true --vhook_a cPeriod to_unsigned(kSpPeriodInRClks,kClksPerPulseMaxBits) --vhook_a cHighTime to_unsigned(kSpHighTimeInRClks,kClksPerPulseMaxBits) --vhook_a cEnablePulse sEnablePulser --vhook_a cPulse sSP SpPulser: entity work.Pulser (rtl) generic map (kClksPerPulseMaxBits => kClksPerPulseMaxBits) --integer range 3:32 :=16 port map ( aReset => aReset, --in boolean Clk => SampleClk, --in std_logic cLoadLimits => true, --in boolean cPeriod => to_unsigned(kSpPeriodInRClks,kClksPerPulseMaxBits), --in unsigned(kClksPerPulseMaxBits-1:0) cHighTime => to_unsigned(kSpHighTimeInRClks,kClksPerPulseMaxBits), --in unsigned(kClksPerPulseMaxBits-1:0) cEnablePulse => sEnablePulser, --in boolean cPulse => sSP); --out boolean main: process procedure SendTrigger is begin assert rReadyForInput report "RFI isn't high, so we can't issue a trigger" severity error; -- Give it some action. We need to ideally test this for every phase offset of -- rTriggerIn with respect to the rising edge of rRP, but let's get to that later. -- For now, wait until a rising edge on rRP and then wait for most of the period -- to issue the trigger. wait until rRP and not rRP'delayed; wait for (kRpPeriodInRClks-3)*kRPer; rTriggerIn <= true; ClkWait(RefClk); rTriggerIn <= false; rRfiExpected <= false; -- At this point, we wait until a sSP RE, plus two SampleClks, plus sElasticBufferPtr -- plus 1 worth of SampleClks, plus one more SampleClk, and then the trigger -- should appear. wait until not rRP and rRP'delayed; wait until sSP and not sSP'delayed; ClkWait(SampleClk,1); ClkWait(SampleClk, to_integer(sElasticBufferPtr)+1); sTriggerOutExpected <= true; ClkWait(SampleClk,1); sTriggerOutExpected <= false; wait until not rRP and rRP'delayed; ClkWait(RefClk,1); rRfiExpected <= true; end procedure SendTrigger; begin rEnablePulser <= false; sEnablePulser <= false; rEnableTrigger <= true; sElasticBufferPtr <= to_unsigned(7, sElasticBufferPtr'length); aReset <= true, false after 10 ns; ClkWait(RefClk,5); -- Start up the pulsers and ensure nothing comes out of the trigger for a while. rEnablePulser <= true; ClkWait(RefClk, 3); ClkWait(SampleClk, 2); sEnablePulser <= true; ClkWait(RefClk, kRpPeriodInRClks*5); assert (not sTriggerOut) and sTriggerOut'stable(kRpPeriodInRClks*5*kRPer) report "Rogue activity on sTriggerOut before rTriggerIn asserted!" severity error; assert (rReadyForInput) and rReadyForInput'stable(kRpPeriodInRClks*5*kRPer) report "Ready for Input was not high before trigger!" severity error; SendTrigger; ClkWait(RefClk, kRpPeriodInRClks*5); SendTrigger; ClkWait(RefClk, kRpPeriodInRClks*5); -- Turn off the trigger enable and send a trigger. rEnableTrigger <= false; ClkWait(RefClk); rTriggerIn <= true; ClkWait(RefClk); rTriggerIn <= false; -- And nothing should happen. ClkWait(RefClk, kRpPeriodInRClks*5); assert (not sTriggerOut) and sTriggerOut'stable(kRpPeriodInRClks*5*kRPer) report "Rogue activity on sTriggerOut before rTriggerIn asserted!" severity error; ClkWait(RefClk, kRpPeriodInRClks*5); StopSim <= true; wait; end process; CheckRfi : process(RefClk) begin if falling_edge(RefClk) then assert rReadyForInput = rRfiExpected report "RFI didn't match expected" severity error; end if; end process; CheckTrigOut : process(SampleClk) begin if falling_edge(SampleClk) then assert sTriggerOut = sTriggerOutExpected report "Trigger Out didn't match expected" severity error; end if; end process; --vhook_e CrossTrigger dutx dutx: entity work.CrossTrigger (rtl) port map ( aReset => aReset, --in boolean RefClk => RefClk, --in std_logic rRP => rRP, --in boolean rReadyForInput => rReadyForInput, --out boolean rEnableTrigger => rEnableTrigger, --in boolean rTriggerIn => rTriggerIn, --in boolean SampleClk => SampleClk, --in std_logic sSP => sSP, --in boolean sElasticBufferPtr => sElasticBufferPtr, --in unsigned(3:0) sTriggerOut => sTriggerOut); --out boolean end test; --synopsys translate_on