aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/x4xx_pps_sync.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/top/x400/x4xx_pps_sync.v')
-rw-r--r--fpga/usrp3/top/x400/x4xx_pps_sync.v426
1 files changed, 426 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/x4xx_pps_sync.v b/fpga/usrp3/top/x400/x4xx_pps_sync.v
new file mode 100644
index 000000000..fc44ce47c
--- /dev/null
+++ b/fpga/usrp3/top/x400/x4xx_pps_sync.v
@@ -0,0 +1,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