diff options
Diffstat (limited to 'fpga/usrp3/top/b2xxmini/b205_ref_pll.v')
-rw-r--r-- | fpga/usrp3/top/b2xxmini/b205_ref_pll.v | 278 |
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 |