aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/x4xx_pps_sync.v
blob: fc44ce47ca966c6a18f42eee8b4b553b03428239 (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
415
416
417
418
419
420
421
422
423
424
425
426
//
// Copyright 2021 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: x4xx_pps_sync
//
// Description:
//
//   This module encapsulates the PPS handling and the related LMK SYNC signal.
//
// Parameters:
//
//   SIMULATION : When true, lowers 10 MHz PPS base reference clock to 10 kHz
//                to shorten simulation run time.
//

`default_nettype none


module x4xx_pps_sync #(
  parameter SIMULATION = 0 
) (
  // clock and reset
  input  wire base_ref_clk, // BRC
  input  wire pll_ref_clk,  // PRC
  input  wire ctrl_clk,     // CC
  input  wire radio_clk,    // RC

  input  wire brc_rst,

  // PPS
  input  wire pps_in, // BRC domain
  output wire pps_out_brc,
  output reg  pps_out_rc = 1'b0,

  // LMK control signal
  output reg  sync = 1'b0,

  // Control signals (CC domain)
  input  wire [1:0]  pps_select,
  input  wire        pll_sync_trigger,
  input  wire [7:0]  pll_sync_delay,
  output wire        pll_sync_done,
  input  wire [7:0]  pps_brc_delay,
  input  wire [25:0] pps_prc_delay,
  input  wire [1:0]  prc_rc_divider,
  input  wire        pps_rc_enabled,

  //signal for debugging
  output wire [1:0] debug
);

  `include "regmap/global_regs_regmap_utils.vh"

  //---------------------------------------------------------------------------
  // PPS Generation and Capturing (BRC domain)
  //---------------------------------------------------------------------------

  // Divide 10 MHz to 10 kHz in case test mode is activated
  localparam FREQUENCY_10M = SIMULATION ? 32'd10_000 : 32'd10_000_000;
  localparam FREQUENCY_25M = 32'd25_000_000;

  // Generate internal PPS signals, each with a 25% duty cycle, based on
  // the different Reference Clock rates. Only one will be used at a time.
  // Available base reference clock rates are: 10 MHz, 25 MHz
  wire pps_int_10mhz_brc;
  pps_generator #(
    .CLK_FREQ   (FREQUENCY_10M),
    .DUTY_CYCLE (25),
    .PIPELINE   ("OUT")
  ) pps_generator_10mhz (
    .clk   (base_ref_clk),
    .reset (1'b0),
    .pps   (pps_int_10mhz_brc)
  );
  wire pps_int_25mhz_brc;
  pps_generator #(
    .CLK_FREQ   (FREQUENCY_25M),
    .DUTY_CYCLE (25),
    .PIPELINE   ("OUT")
  ) pps_generator_25mhz (
    .clk   (base_ref_clk),
    .reset (1'b0),
    .pps   (pps_int_25mhz_brc)
  );

  // Capture the external PPSs with a FF before sending them to the mux. To be
  // safe, we double-synchronize the external signals. If we meet timing (which
  // we should) then this is a two-cycle delay. If we don't meet timing, then
  // it's 1-2 cycles and our system timing is thrown off--but at least our
  // downstream logic doesn't go metastable!
  wire pps_ext_brc;
  synchronizer #(
    .FALSE_PATH_TO_IN (0)
  ) synchronizer_pps_ext (
    .clk (base_ref_clk),
    .rst (1'b0),
    .in  (pps_in),
    .out (pps_ext_brc)
  );

  // Synchronize the select bits over to the reference clock as well. Note that this is
  // a vector, so we could have some invalid values creep through when changing.
  // See the note below as to why this is safe.
  wire [1:0] pps_select_brc;
  synchronizer #(
    .FALSE_PATH_TO_IN (1),
    .WIDTH            (2)
  ) synchronizer_pps_select (
    .clk (base_ref_clk),
    .rst (1'b0),
    .in  (pps_select),
    .out (pps_select_brc)
  );

  // PPS MUX - selects internal or external PPS.
  reg pps_brc = 1'b0;
  always @(posedge base_ref_clk) begin
    // It is possible when the vector is being double-synchronized to the
    // reference clock domain that there could be multiple bits asserted
    // simultaneously. This is not problematic because the order of operations
    // in the following selection mux should take over and only one PPS should
    // win. This could result in glitches, but that is expected during ANY PPS
    // switchover since the switch is performed asynchronously to the PPS
    // signal.
    case (pps_select_brc)
      PPS_INT_10MHZ: begin
        pps_brc <= pps_int_10mhz_brc;
      end
      PPS_INT_25MHZ: begin
        pps_brc <= pps_int_25mhz_brc;
      end
      default: begin
        pps_brc <= pps_ext_brc;
      end
    endcase
  end

  // forward BRC based PPS to output
  assign pps_out_brc = pps_brc;


  //---------------------------------------------------------------------------
  // LMK sync generation (BRC domain)
  //---------------------------------------------------------------------------

  // Detect rising edge of PPS
  reg  pps_brc_delayed;
  wire pps_rising_edge_brc;
  always @(posedge base_ref_clk) begin
    pps_brc_delayed <= pps_brc;
  end
  assign pps_rising_edge_brc = pps_brc & ~pps_brc_delayed;

  // Transfer control signals to internal clock domain
  wire pll_sync_trigger_brc;
  synchronizer #(
    .FALSE_PATH_TO_IN (1)
  ) synchronizer_sync_trigger (
    .clk (base_ref_clk),
    .rst (1'b0),
    .in  (pll_sync_trigger),
    .out (pll_sync_trigger_brc)
  );

  // There is no data coherency guaranteed by this synchronizer, but this is
  // not required. The information is derived in the same clock domain as the
  // sync trigger. Both information in the worst case arrive in the same clock
  // cycle. In the state machine the trigger is changing the state to ARMED.
  // The delay value is required in the ARMED state. This way there is one more
  // clock cycle for this synchronizer to propagate the correct value of all
  // bits.
  wire [7:0] pll_sync_delay_brc;
  synchronizer #(
    .FALSE_PATH_TO_IN (1),
    .WIDTH            (8)
  ) synchronizer_sync_delay (
    .clk (base_ref_clk),
    .rst (1'b0),
    .in  (pll_sync_delay),
    .out (pll_sync_delay_brc)
  );

  // Synchronization state machine
  localparam IDLE  = 2'd0;
  localparam ARMED = 2'd1;
  localparam COUNT = 2'd2;
  localparam DONE  = 2'd3;

  reg [7:0] delay_counter_brc = 8'd0;
  reg [1:0] state = IDLE;
  reg       pll_sync_done_brc = 1'b0;
  reg       sync_int = 1'b0;

  always @(posedge base_ref_clk) begin
    if (brc_rst) begin
      sync_int <= 1'b0;
      pll_sync_done_brc <= 1'b0;
      state <= IDLE;
    end
    else begin
      case (state)
        IDLE: begin
          // Wait for trigger from control interface
          if (pll_sync_trigger_brc) begin
            state <= ARMED;
          end
        end

        ARMED: begin
          // Wait for the rising edge of PPS and reset counter
          delay_counter_brc <= pll_sync_delay_brc;
          if (pps_rising_edge_brc) begin
            state <= COUNT;
          end
        end

        // Delay assertion of sync signal by the given number of cycles
        COUNT: begin
          delay_counter_brc <= delay_counter_brc - 1;
          if (delay_counter_brc == 0) begin
            state <= DONE;
            sync_int <= 1'b1;
          end
        end

        // Issue done signal until the trigger is released
        DONE: begin
          sync_int <= 1'b0;
          pll_sync_done_brc <= 1'b1;
          if (pll_sync_trigger_brc == 0) begin
            state <= IDLE;
            pll_sync_done_brc <= 1'b0;
          end
        end

        // In case we run into an undefined state
        default: begin
          state <= IDLE;
        end
      endcase
    end
  end

  // Transfer done signal back to ctrl_clk domain
  synchronizer #(
    .FALSE_PATH_TO_IN (1)
  ) synchronizer_pll_sync_done (
    .clk (ctrl_clk),
    .rst (1'b0),
    .in  (pll_sync_done_brc),
    .out (pll_sync_done)
  );

  // Sync signal is captured at falling edge of clock to ensure hold time
  always @(negedge base_ref_clk) begin
    sync <= sync_int;
  end

  //---------------------------------------------------------------------------
  // PPS clock domain crossings
  //---------------------------------------------------------------------------
  // In the section below the PPS crosses multiple clock domains.
  // From the generation in BRC clock domain we transfer the signal over to
  // PRC using the aligned edge of the external LMK IC.
  // Afterwards we use the integer clock multiplier between PRC and RC to
  // get the PPS trigger to the radio clock domain.

  // BRC       --\____/----\____/----\____/----\____/----\____/----\____/
  // PRC       ___/---\___/---\___/---\___/---\___/---\___/---\___/---\__
  // RC        -\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\_/-\
  //                                      | aligned edge
  // PPS (BRC) __/--------------------------------------------------------
  // PPS (BRC delayed) ___________________/-------------------------------
  //   Has to shift PPS to start on aligned edge.
  //
  // PPS (PRC) __________________________________________/----------------
  //                                      |------------->| 2 PRC cycles
  //   2 stage synchronizer = 2 PRC cycle delay on aligned edge
  //
  // PPS (PRC delayed) __________/----------------------------------------
  //                                                     |------------------
  //   ------------------------->| up to PRC frequency cycles
  //   Shifts PPS pulse by up to 1 second (PPS period) to be present in the
  //   clock cycle before the aligned edge.
  //
  // PPS (RC)  ___________________________/-\_____________________________
  //                             |------->| RC clock multiplier based cycles
  //   Number of sync registers depends on clock multiplier between PRC and
  //   RC to align PPS signal with aligned edge. Additional logic to restore
  //   a one cycle long pulse from PPS signal with 25% duty cycle.

  //---------------------------------------------------------------------------
  // PPS delay (BRC domain)
  //---------------------------------------------------------------------------
  // This shift register delays the PPS trigger until the appearance of
  // the aligned edge of BRC and PRC.
  // This delay has to incorporate the delay of the state machine above from
  // pps to sync output, the delay of the LMK chip from sync edge to aligned
  // edge and delay setting applied to the sync signal. Be sure to reduce the
  // number by 1 at the end to account for the final register.

  wire [7:0] pps_brc_delay_brc;
  synchronizer #(
    .FALSE_PATH_TO_IN (1),
    .WIDTH            (8)
  ) synchronizer_pps_brc_delay (
    .clk (base_ref_clk),
    .rst (1'b0),
    .in  (pps_brc_delay),
    .out (pps_brc_delay_brc)
  );

  reg [255:0] pps_shift_reg_brc = 256'b0;
  reg         pps_delayed_brc   = 1'b0;
  always @(posedge base_ref_clk) begin
    pps_shift_reg_brc <= {pps_shift_reg_brc[254:0], pps_brc};
    pps_delayed_brc <= pps_shift_reg_brc[pps_brc_delay_brc];
  end

  //---------------------------------------------------------------------------
  // PPS clock domain crossing
  //---------------------------------------------------------------------------
  // On the aligned edge of BRC and PRC this synchronizer is just a two stage
  // delay into the PRC domain as the edges occur at the same time the tools
  // should make sure we close timing on this edge
  
  wire pps_prc;
  synchronizer #(
    .FALSE_PATH_TO_IN (0)
  ) synchronizer_pps_prc (
    .clk (pll_ref_clk),
    .rst (1'b0),
    .in  (pps_delayed_brc),
    .out (pps_prc)
  );

  //---------------------------------------------------------------------------
  // PPS delay (PRC)
  //---------------------------------------------------------------------------
  // Delay the PPS signal in PRC domain by a specified amount to align with
  // other devices (max delay = 1 sec = next occurrence of pps rising edge).
  // Make sure that the initial count value accounts for the two stage
  // synchronizer from BRC to PRC, the final register upon counter reaches
  // its final value and it has to be one cycle earlier than the aligned edge
  // to get transferred to radio clock afterwards.

  wire [25:0] pps_prc_delay_prc;
  synchronizer #(
    .FALSE_PATH_TO_IN (1),
    .WIDTH            (26)
  ) synchronizer_pps_prc_delay (
    .clk (pll_ref_clk),
    .rst (1'b0),
    .in  (pps_prc_delay),
    .out (pps_prc_delay_prc)
  );

  reg [25:0] delay_counter_prc = 26'b0;
  reg        pps_delayed_prc = 1'b0;
  reg        pps_prc_delayed = 1'b0;
  always @(posedge pll_ref_clk) begin
    // Disable delayed rising edge by default
    pps_delayed_prc <= 1'b0;
    pps_prc_delayed <= pps_prc;

    // Reset counter on rising edge
    if (pps_prc & ~pps_prc_delayed) begin
      delay_counter_prc <= pps_prc_delay_prc;
    end
    else begin
      if (delay_counter_prc != 0) begin
        delay_counter_prc <= delay_counter_prc - 1;
      end
      if (delay_counter_prc == 1) begin
        pps_delayed_prc <= 1'b1;
      end
    end
  end

  //---------------------------------------------------------------------------
  // PPS PRC to radio clock
  //---------------------------------------------------------------------------
  // Tiny shift register to account for the clock multiplier between prc and
  // rc. The divider has to account for the output register and the shift
  // register.

  wire [1:0] prc_rc_divider_rc;
  wire       pps_rc_enabled_rc;
  synchronizer #(
    .FALSE_PATH_TO_IN (1),
    .WIDTH            (2)
  ) synchronizer_prc_rc_divider (
    .clk (radio_clk),
    .rst (1'b0),
    .in  (prc_rc_divider),
    .out (prc_rc_divider_rc)
  );
  synchronizer #(
    .FALSE_PATH_TO_IN (1)
  ) synchronizer_pps_rc_enabled (
    .clk (radio_clk),
    .rst (1'b0),
    .in  (pps_rc_enabled),
    .out (pps_rc_enabled_rc)
  );

  reg [3:0] pps_shift_reg_rc = 4'b0;
  always @(posedge radio_clk) begin
    pps_shift_reg_rc <= {pps_shift_reg_rc[2:0],  pps_delayed_prc};
    // Restoring a one clock cycle pulse by feeding back to output value.
    pps_out_rc <= pps_shift_reg_rc[prc_rc_divider_rc] & ~pps_out_rc & pps_rc_enabled_rc;
  end

  //---------------------------------------------------------------------------
  // Debug assignment
  //---------------------------------------------------------------------------

  assign debug[0] = pps_delayed_brc;
  assign debug[1] = pps_delayed_prc;

endmodule


`default_nettype wire