aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/b2xxmini/b205_ref_pll.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/top/b2xxmini/b205_ref_pll.v')
-rw-r--r--fpga/usrp3/top/b2xxmini/b205_ref_pll.v278
1 files changed, 278 insertions, 0 deletions
diff --git a/fpga/usrp3/top/b2xxmini/b205_ref_pll.v b/fpga/usrp3/top/b2xxmini/b205_ref_pll.v
new file mode 100644
index 000000000..0483bc93b
--- /dev/null
+++ b/fpga/usrp3/top/b2xxmini/b205_ref_pll.v
@@ -0,0 +1,278 @@
+//
+// Copyright 2015 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+module b205_ref_pll(
+ input reset,
+ input clk, // 200 MHz sample clock
+ input refclk, // 40 MHz reference clock
+ input ref, // PPS or 10 MHz external reference
+ output reg locked,
+
+ // SPI lines to AD5662
+ output sclk,
+ output mosi,
+ output sync_n
+ );
+
+ // Base parameters
+ localparam SAMPLE_CLOCK_FREQ=200_000_000;
+ localparam REF_FREQ_PPS=1;
+ localparam REF_FREQ_10MHZ=10_000_000;
+ localparam REF_CLK_FREQ=40_000_000;
+ localparam PFD_FREQ_PPS=1;
+ localparam PFD_FREQ_10MHZ=10;
+
+ // Lock detection parameters
+ localparam LOCK_TOLERANCE_PPM=1;
+ localparam LOCK_MARGIN_PPS=(SAMPLE_CLOCK_FREQ/PFD_FREQ_PPS)*LOCK_TOLERANCE_PPM/1_000_000;
+ localparam LOCK_MARGIN_10MHZ=(SAMPLE_CLOCK_FREQ/PFD_FREQ_10MHZ)*LOCK_TOLERANCE_PPM/1_000_000;
+
+ // Reference frequency detection parameters
+ // References are only valid if they are +/-5ppm because that is the range of the VCTXCO
+ localparam REF_PERIOD_PPS=SAMPLE_CLOCK_FREQ/REF_FREQ_PPS;
+ localparam REF_PERIOD_10MHZ=SAMPLE_CLOCK_FREQ/REF_FREQ_10MHZ;
+ localparam REF_PERIOD_PPS_MIN=REF_PERIOD_PPS-(REF_PERIOD_PPS*5/1_000_000)-1;
+ localparam REF_PERIOD_PPS_MAX=REF_PERIOD_PPS+(REF_PERIOD_PPS*5/1_000_000)+1;
+ localparam REF_PERIOD_10MHZ_MIN=REF_PERIOD_10MHZ-(REF_PERIOD_10MHZ*5/1_000_000)-1;
+ localparam REF_PERIOD_10MHZ_MAX=REF_PERIOD_10MHZ+(REF_PERIOD_10MHZ*5/1_000_000)+1;
+
+ // R divider parameters
+ localparam RDIV_PPS=REF_FREQ_PPS/PFD_FREQ_PPS;
+ localparam RDIV_10MHZ=REF_FREQ_10MHZ/PFD_FREQ_10MHZ;
+
+ // N divider parameters (refclk is divided by 2)
+ localparam NDIV_PPS=REF_CLK_FREQ/2/PFD_FREQ_PPS;
+ localparam NDIV_10MHZ=REF_CLK_FREQ/2/PFD_FREQ_10MHZ;
+
+ // PFD parameters
+ localparam PFD_PERIOD_PPS=SAMPLE_CLOCK_FREQ/PFD_FREQ_PPS;
+ localparam PFD_PERIOD_10MHZ=SAMPLE_CLOCK_FREQ/PFD_FREQ_10MHZ;
+
+
+ // Initial divide by 2 for 40 MHz clock
+ // (since refclk cannot be sampled directly)
+ reg refclk_div;
+ always @(posedge refclk) begin
+ refclk_div <= ~refclk_div;
+ end
+
+ // flop signals into sample clock domain together
+ reg [3:0] refsmp;
+ reg [3:0] refclksmp;
+ always @(posedge clk) begin
+ refsmp <= {refsmp[2:0],ref};
+ refclksmp <= {refclksmp[2:0],refclk_div};
+ end
+
+ // rising edge detection
+ wire ref_rising = (refsmp[3:2] == 2'b01);
+ wire refclk_rising = (refclksmp[3:2] == 2'b01);
+
+ // reference frequency detection
+ reg [27:0] refcnt;
+ reg ref_detected;
+ reg ref_is_10M;
+ reg ref_is_pps;
+ wire valid_ref = ref_is_10M | ref_is_pps;
+ always @(posedge clk) begin
+ if (reset) begin
+ refcnt <= 28'd0;
+ ref_detected <= 1'b0;
+ ref_is_10M <= 1'b0;
+ ref_is_pps <= 1'b0;
+ end
+ else if (ref_rising) begin
+ refcnt <= 28'd1;
+ ref_detected <= 1'b1;
+ ref_is_10M <= ((refcnt >= REF_PERIOD_10MHZ_MIN) && (refcnt <= REF_PERIOD_10MHZ_MAX));
+ ref_is_pps <= ((refcnt >= REF_PERIOD_PPS_MIN) && (refcnt <= REF_PERIOD_PPS_MAX));
+ end
+ else if ((ref_is_10M && (refcnt > REF_PERIOD_10MHZ_MAX)) || (refcnt > REF_PERIOD_PPS_MAX)) begin
+ // consider the reference lost
+ refcnt <= 28'd0;
+ ref_detected <= 1'b0;
+ ref_is_10M <= 1'b0;
+ ref_is_pps <= 1'b0;
+ end
+ else if (ref_detected)
+ refcnt <= refcnt + 28'd1;
+ end
+
+ // R divider
+ wire [23:0] rdiv = ref_is_10M ? RDIV_10MHZ : RDIV_PPS;
+ reg [23:0] rcnt;
+ wire [23:0] next_rcnt = ~valid_ref ? 24'd0 : (rcnt == rdiv) ? 24'd1 : rcnt + 1'b1;
+ reg r_rising;
+ always @(posedge clk) begin
+ if (ref_rising)
+ rcnt <= next_rcnt;
+ r_rising <= (ref_rising && ((ref_is_10M && (rcnt == rdiv)) || ref_is_pps));
+ end
+
+ // N divider
+ // Enable on rising edge of R after valid_ref
+ // is asserted so R and N signals start aligned.
+ // Disable if reference lost.
+ wire [25:0] ndiv = ref_is_10M ? NDIV_10MHZ : NDIV_PPS;
+ reg [25:0] ncnt;
+ wire [25:0] next_ncnt = ~valid_ref ? 26'd0 : ncnt == ndiv ? 26'd1 : ncnt + 1'b1;
+ reg n_rising;
+ always @(posedge clk) begin
+ if (refclk_rising)
+ ncnt <= next_ncnt;
+ n_rising <= (refclk_rising && (ncnt == ndiv));
+ end
+
+ // Frequency Counter
+ wire signed [28:0] period = ref_is_10M ? PFD_PERIOD_10MHZ : PFD_PERIOD_PPS;
+ reg signed [28:0] r_period_cnt;
+ reg signed [28:0] freq_err;
+ always @(posedge clk) begin
+ if (reset | ~valid_ref) begin
+ r_period_cnt <= 28'd0;
+ freq_err <= 29'sd0;
+ end
+ else if (r_rising) begin
+ r_period_cnt <= 28'd1;
+ freq_err <= period - r_period_cnt;
+ end
+ else
+ r_period_cnt <= r_period_cnt + 28'd1;
+ end
+
+ // Phase Counter
+ reg signed [28:0] lead_cnt;
+ reg lead_cnt_ena;
+ reg signed [28:0] lead;
+ always @(posedge clk) begin
+ // Count how much N leads R
+ // The count is negative because it measures
+ // how much the VCTCXO must be slowed down.
+ if (~valid_ref | n_rising) begin
+ lead_cnt <= 29'sd0;
+ lead_cnt_ena <= 1'b1;
+ if (r_rising)
+ lead <= 29'sd0;
+ end
+ else if (r_rising) begin
+ if (lead_cnt_ena)
+ lead <= lead_cnt - 29'sd1;
+ else begin
+ // R rising with no preceding N rising.
+ // N has changed from leading to lagging R,
+ // but we don't yet know by how much so
+ // assume 1.
+ lead <= 29'sd1;
+ end
+ lead_cnt_ena <= 1'b0;
+ end
+ else if (lead_cnt_ena)
+ lead_cnt <= lead_cnt - 29'sd1;
+ end
+
+ // PFD State Machine
+ localparam MEASURE=4'd0;
+ localparam CAPTURE=4'd1;
+ localparam CAPTURE_LAG=4'd2;
+ localparam CAPTURE_LEAD=4'd3;
+ localparam CALCULATE_ERROR=4'd4;
+ localparam CALCULATE_10M_GAIN=4'd5;
+ localparam CALCULATE_ADJUSTMENT=4'd6;
+ localparam CALCULATE_OUTPUT_VALUE=4'd7;
+ localparam APPLY_OUTPUT_VALUE=4'd8;
+ reg [3:0] state;
+ reg [15:0] daco = 16'd32767;
+ wire signed [28:0] lock_margin = ref_is_10M ? LOCK_MARGIN_10MHZ : LOCK_MARGIN_PPS;
+ wire signed [28:0] lag = lead + period;
+ reg signed [28:0] phase_err;
+ reg signed [28:0] err;
+ reg signed [28:0] shift;
+ reg signed [28:0] adj;
+ wire signed [28:0] dacv = {13'd0, daco};
+ reg signed [28:0] sum;
+ reg [2:0] ld;
+ always @(posedge clk) begin
+ if (reset || ~valid_ref) begin
+ state <= MEASURE;
+ daco <= 16'd32767;
+ err <= 29'sd0;
+ shift <= 29'sd0;
+ adj <= 29'sd0;
+ ld <= 3'd0;
+ end
+ else begin
+ case(state)
+ MEASURE: begin
+ if (r_rising)
+ state <= CAPTURE;
+ end
+ CAPTURE: begin
+ if (lag < -lead)
+ state <= CAPTURE_LAG;
+ else
+ state <= CAPTURE_LEAD;
+ end
+ CAPTURE_LAG: begin
+ phase_err <= lag;
+ ld <= {ld[1:0], (lag <= lock_margin)};
+ state <= CALCULATE_ERROR;
+ end
+ CAPTURE_LEAD: begin
+ phase_err <= lead;
+ ld <= {ld[1:0], (-lead <= lock_margin)};
+ state <= CALCULATE_ERROR;
+ end
+ CALCULATE_ERROR: begin
+ err <= phase_err + freq_err;
+ state <= ref_is_10M ? CALCULATE_10M_GAIN : CALCULATE_ADJUSTMENT;
+ end
+ CALCULATE_10M_GAIN: begin
+ shift <= (err < -7 || err > 7) ? 7 : (err < 0 ? -err : err);
+ state <= CALCULATE_ADJUSTMENT;
+ end
+ CALCULATE_ADJUSTMENT: begin
+ // The VCTCXO is +/-5 ppm from 0.3V to 1.5V and the DAC is 16 bits,
+ // which works out to 0.000228885 ppm per DAC unit.
+ // The 200 MHz sampling clock means each unit of error is 0.005 ppm,
+ // which works out to 21.845 DAC units to correct each unit of error.
+ // Theory is nice, but the proportional and integral gains used here
+ // were determined through manual tuning.
+ if (ref_is_10M)
+ adj <= (err <<< shift);
+ else
+ adj <= (err <<< 4) - err;
+ state <= CALCULATE_OUTPUT_VALUE;
+ end
+ CALCULATE_OUTPUT_VALUE: begin
+ sum <= dacv + adj;
+ state <= APPLY_OUTPUT_VALUE;
+ end
+ APPLY_OUTPUT_VALUE: begin
+ // Clip and apply
+ if (sum < 29'sd0)
+ daco <= 16'd0;
+ else if (sum > 29'sd65535)
+ daco <= 16'd65535;
+ else
+ daco <= sum[15:0];
+ state <= MEASURE;
+ end
+ endcase
+ end
+ end
+
+ always @(posedge clk)
+ locked <= (ld == 3'b111);
+
+ ad5662_auto_spi dac
+ (
+ .clk(clk),
+ .dat(daco),
+ .sclk(sclk),
+ .mosi(mosi),
+ .sync_n(sync_n)
+ );
+endmodule