aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/n3xx/dboards/common/sync/CrossTrigger.vhd
blob: 70a8735014aa51767242ff76a9efa7180a16b5cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
-------------------------------------------------------------------------------
--
-- 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