aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/e31x/ppsloop.v
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-01-23 16:10:22 -0800
committerMartin Braun <martin.braun@ettus.com>2020-01-28 09:35:36 -0800
commitbafa9d95453387814ef25e6b6256ba8db2df612f (patch)
tree39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/top/e31x/ppsloop.v
parent3075b981503002df3115d5f1d0b97d2619ba30f2 (diff)
downloaduhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.gz
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.bz2
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.zip
Merge FPGA repository back into UHD repository
The FPGA codebase was removed from the UHD repository in 2014 to reduce the size of the repository. However, over the last half-decade, the split between the repositories has proven more burdensome than it has been helpful. By merging the FPGA code back, it will be possible to create atomic commits that touch both FPGA and UHD codebases. Continuous integration testing is also simplified by merging the repositories, because it was previously difficult to automatically derive the correct UHD branch when testing a feature branch on the FPGA repository. This commit also updates the license files and paths therein. We are therefore merging the repositories again. Future development for FPGA code will happen in the same repository as the UHD host code and MPM code. == Original Codebase and Rebasing == The original FPGA repository will be hosted for the foreseeable future at its original local location: https://github.com/EttusResearch/fpga/ It can be used for bisecting, reference, and a more detailed history. The final commit from said repository to be merged here is 05003794e2da61cabf64dd278c45685a7abad7ec. This commit is tagged as v4.0.0.0-pre-uhd-merge. If you have changes in the FPGA repository that you want to rebase onto the UHD repository, simply run the following commands: - Create a directory to store patches (this should be an empty directory): mkdir ~/patches - Now make sure that your FPGA codebase is based on the same state as the code that was merged: cd src/fpga # Or wherever your FPGA code is stored git rebase v4.0.0.0-pre-uhd-merge Note: The rebase command may look slightly different depending on what exactly you're trying to rebase. - Create a patch set for your changes versus v4.0.0.0-pre-uhd-merge: git format-patch v4.0.0.0-pre-uhd-merge -o ~/patches Note: Make sure that only patches are stored in your output directory. It should otherwise be empty. Make sure that you picked the correct range of commits, and only commits you wanted to rebase were exported as patch files. - Go to the UHD repository and apply the patches: cd src/uhd # Or wherever your UHD repository is stored git am --directory fpga ~/patches/* rm -rf ~/patches # This is for cleanup == Contributors == The following people have contributed mainly to these files (this list is not complete): Co-authored-by: Alex Williams <alex.williams@ni.com> Co-authored-by: Andrej Rode <andrej.rode@ettus.com> Co-authored-by: Ashish Chaudhari <ashish@ettus.com> Co-authored-by: Ben Hilburn <ben.hilburn@ettus.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Daniel Jepson <daniel.jepson@ni.com> Co-authored-by: Derek Kozel <derek.kozel@ettus.com> Co-authored-by: EJ Kreinar <ej@he360.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Ian Buckley <ian.buckley@gmail.com> Co-authored-by: Jörg Hofrichter <joerg.hofrichter@ni.com> Co-authored-by: Jon Kiser <jon.kiser@ni.com> Co-authored-by: Josh Blum <josh@joshknows.com> Co-authored-by: Jonathon Pendlum <jonathan.pendlum@ettus.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Matt Ettus <matt@ettus.com> Co-authored-by: Michael West <michael.west@ettus.com> Co-authored-by: Moritz Fischer <moritz.fischer@ettus.com> Co-authored-by: Nick Foster <nick@ettus.com> Co-authored-by: Nicolas Cuervo <nicolas.cuervo@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Paul David <paul.david@ettus.com> Co-authored-by: Ryan Marlow <ryan.marlow@ettus.com> Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com> Co-authored-by: Sylvain Munaut <tnt@246tNt.com> Co-authored-by: Trung Tran <trung.tran@ettus.com> Co-authored-by: Vidush Vishwanath <vidush.vishwanath@ettus.com> Co-authored-by: Wade Fife <wade.fife@ettus.com>
Diffstat (limited to 'fpga/usrp3/top/e31x/ppsloop.v')
-rw-r--r--fpga/usrp3/top/e31x/ppsloop.v415
1 files changed, 415 insertions, 0 deletions
diff --git a/fpga/usrp3/top/e31x/ppsloop.v b/fpga/usrp3/top/e31x/ppsloop.v
new file mode 100644
index 000000000..ea720a373
--- /dev/null
+++ b/fpga/usrp3/top/e31x/ppsloop.v
@@ -0,0 +1,415 @@
+//
+// Copyright 2015 Ettus Research
+//
+
+`timescale 1ns / 1ps
+
+
+module ppsloop(
+ input reset,
+ input xoclk, // 40 MHz from VCTCXO
+ input ppsgps,
+ input ppsext,
+ input [1:0] refsel,
+ output reg lpps,
+ output reg is10meg,
+ output reg ispps,
+ output reg reflck,
+ output plllck,// status of things
+ output sclk,
+ output mosi,
+ output sync_n,
+ input [15:0] dac_dflt
+ );
+ wire ppsref = (refsel==2'b00)?ppsgps:
+ (refsel==2'b11)?ppsext:
+ 1'b0;
+ // reference pps to discilpline the VCTX|CXO to, from GPS or EXT in
+
+ wire clk_200M_o, clk;
+ BUFG x_clk_gen ( .I(clk_200M_o), .O(clk));
+ wire clk_40M;
+
+ wire n_pps = (refsel==2'b01) | (refsel==2'b10);
+ reg _npps, no_pps;
+ always @(posedge clk) { no_pps, _npps } <= { _npps, n_pps };
+
+ PLLE2_ADV #(.BANDWIDTH("OPTIMIZED"), .COMPENSATION("INTERNAL"),
+ .DIVCLK_DIVIDE(1),
+ .CLKFBOUT_MULT(30),
+ .CLKOUT0_DIVIDE(6),
+ .CLKOUT1_DIVIDE(30),
+ .CLKIN1_PERIOD(25.0)
+ )
+ clkgen (
+ .PWRDWN(1'b0), .RST(1'b0),
+ .CLKIN1(xoclk),
+ .CLKOUT0(clk_200M_o),
+ .CLKOUT1(clk_40M),
+ .LOCKED(plllck)
+ );
+
+ // state machine to manage reference detection and xo adjustment steps
+ reg [2:0] sstate, nxt_sstate;
+ localparam REFDET=3'b000;
+ localparam CFADJ=3'b001;
+ localparam SLEDGEA=3'b010;
+ localparam SLEDGEB=3'b011;
+ localparam FINEADJ=3'b100;
+
+ // state machine to manage lead-lag count
+ reg [1:0] llstate, nxt_llstate;
+ localparam READY=2'b00;
+ localparam COUNT=2'b01;
+ localparam DONE=2'b11;
+ localparam WAIT=2'b10;
+
+ /* Counter generating a local pps for the xo derived clock domains.
+ nxt_lcnt is manipulated by a state machine (sstate) to allow
+ quick re-alignment of the local pps rising edge with that of
+ the reference.
+ */
+ reg [27:0] lcnt, nxt_lcnt;
+ wire recycle = (28'd199_999_999==lcnt); // sets the period, 1 sec
+
+ always @(posedge clk) begin
+ sstate <= nxt_sstate;
+ lcnt <= nxt_lcnt;
+ lpps <= lcnt > 28'd150_000_000; // ~25% duty cycle
+ end
+
+ /* Reference signal detection:
+ * Count the time interval between rising edges on the reference
+ * signal. The interval counter "rcnt" is restarted at rising edges
+ * of ppsref. "ppsref" could be either a pps signal, or a 10 MHz clock.
+ * Register "rlst" captures the value of rcnt at each rising edge.
+ * From this count value, we know the reference frequency.
+ */
+ reg [27:0] rcnt, rlst;
+ reg signed [28:0] rdiff;
+ wire signed [28:0] srlst = { 1'b0, rlst }; // sign extended version of rlst
+ wire [27:0] nxt_rcnt;
+ reg rcnt_ovfl;
+ reg [3:0] ple; // pipeline from reference rising edge det.
+ wire valid_ref = is10meg | ispps;
+
+
+ /* If the reference is at 10 MHz, derive a reference pps using a counter
+ * to feed the frequency control logic. To detect a 0.5 ppm deviation
+ * on a 10 MHz signal using counters requires the better part of a second
+ * anyway, so samples at a 1 Hz rate are appropriate. This allows much of
+ * the same logic to be used for pps or 10 Mhz references.
+ */
+ reg [23:0] tcnt;
+ reg tpps;
+ wire [23:0] nxt_tcnt = (~is10meg | tcnt==24'd9999999) ? 24'b0 : tcnt+1'b1;
+ always @(posedge ppsref) begin
+ /* note this is clocked by the reference signal and is not useful when
+ * the reference is a pps.
+ */
+ tcnt <= nxt_tcnt;
+ tpps <= (tcnt>24'd7499999);
+ end
+
+ /* The reference needs to be synchronized into the local clock domain,
+ * and while the local 'pps' is generated synchronously within this
+ * domain, it gets passed through identical stages to maintain
+ * the time relationship between detected rising edges.
+ */
+ reg [2:0] refsmp;
+ reg [2:0] tsmp;
+ reg [2:0] xosmp;
+ always @(posedge clk) begin
+ // apply same sync delay to all pps flavors
+ refsmp <= { refsmp[1:0], ppsref};
+ tsmp <= { tsmp[1:0], tpps};
+ xosmp <= { xosmp[1:0], lpps };
+ end
+
+
+ wire rising_r = (refsmp[2:1]==2'b01);
+ wire rising_t = (tsmp[2:1]==2'b01);
+ wire rising_ref = is10meg ? rising_t : rising_r;
+ wire rising_xo = (xosmp[2:1]==2'b01);
+ wire lead = rising_xo & ~rising_ref;
+ wire lag = ~rising_xo & rising_ref;
+ wire trig = rising_xo ^ rising_ref;
+ wire dtrig = rising_xo & rising_ref;
+ wire untrig = rising_xo | rising_ref;
+ wire llrdy = (is10meg ? ~tsmp[2] : ~refsmp[2]) & ~xosmp[2];
+ wire rhigh = is10meg ? tsmp[1] : refsmp[1];
+
+
+ reg [5:0] pcnt;
+ reg pcnt_ovfl;
+ wire [5:0] nxt_pcnt = (rising_r | pcnt_ovfl) ? 6'b0 : pcnt+1'b1;
+ always @(posedge clk) begin
+ pcnt <= nxt_pcnt;
+ if (rcnt_ovfl)
+ is10meg <= 1'b0;
+ else if (pcnt == 6'b111111) begin
+ pcnt_ovfl <= 1'b1;
+ is10meg <= 1'b0;
+ end
+ else if (rising_r) begin
+ is10meg <= (pcnt > 6'd16) & (pcnt < 6'd24);
+ pcnt_ovfl <= 1'b0;
+ end
+ end
+
+ reg rr;
+ assign nxt_rcnt = rr ? 28'b0 : rcnt+1'b1;
+ always @(posedge clk) begin
+ rr <= rising_ref;
+ ple[3:0] <= {ple[2:0],rising_ref & valid_ref};
+
+ rcnt <= nxt_rcnt;
+
+ // set the overflow flag if no reference edge is detected and
+ // hold it asserted until an edge does arrive. This allows clearing of
+ // the other flags, even if there is no reference.
+ if (rcnt==28'b1111111111111111111111111111)
+ rcnt_ovfl <= 1'b1;
+ else if (rr)
+ rcnt_ovfl <= 1'b0;
+
+ if (rr) begin
+ // a rising edge arrived, grab the count and compare to bounds
+ rlst <= rcnt;
+ end
+ if (rr | rcnt_ovfl) begin
+ ispps <= ~is10meg & ~rcnt_ovfl & (rcnt > 28'd199997000) & (rcnt < 200003000);
+ /* reference frequency detect limits:
+ * 10M sampled with 200M should be 20 cycles, 16-24 provides xtra margin
+ * to allow for tolerances and possibly sampling at jittery edges
+ * allow +- 15 ppm on a pps signal
+ */
+
+ end
+ end
+
+
+ reg signed [27:0] coarse;
+ reg [15:0] dacv = 16'd32767; // power-on default mid-scale
+ wire signed [16:0] sdacv = { 1'b0, dacv};
+ /* to exit coarse adjustment, the frequency error shall be small for
+ * several cycles
+ */
+ reg esmall;
+ reg [2:0] es;
+
+ reg pr;
+
+
+ /* The xo can be on-frequency while the rising edges are still
+ * out-of-phase, so a phase detector is also required. The
+ * counter "llcnt" accumulates how many ticks local pps leads
+ * or lags the reference pps . The range of this counter
+ * need not be as large as "rcnt". The count increments
+ * or decrements based upon which signal has a rising edge first,
+ * and the count is halted when the other rising edge occurs.
+ * Both signals are required to transition back to the low state
+ * to re-arm the detection state machine.
+ */
+ reg llcntena;
+ reg lead_lagn;
+ reg signed [11:0] llcnt, nxt_llcnt;
+ wire signed [11:0] incr = lead_lagn ? -12'sd1 : 12'sd1; // -1 lead, +1 lag
+ reg [3:0] llsmall;
+ reg llovfl;
+
+ reg [2:0] refs1, refs0;
+ reg refchanged;
+ reg refinternal;
+ always @(posedge clk) begin
+ refs1 <= { refs1[1:0], refsel[1] };
+ refs0 <= { refs0[1:0], refsel[0] };
+ refchanged <= { refs1[2], refs0[2] } != { refs1[1], refs0[1] };
+ refinternal <= refs1[2] ^ refs0[2]; // not gps or external
+
+ // compute how far off the expected period we are
+ if (ple[1]) begin
+ rdiff <= srlst-29'd199999999;
+ end
+
+ // compute an adjustment for the dac
+ if (ple[2]) begin
+ // if rdiff is (+), the xo is fast
+ // include a bit of gain for quick adjustment
+ // an approximate gain was initially determined by 'theory' using
+ // the xo tuning sensitivity, and was find-tuned 'by hand'
+ // by observing the loop behaviour (with rdiff instrumented and
+ // pps signals connected out to an oscilloscope).
+ coarse <= sdacv - (rdiff <<< 3);
+ end
+
+ // determine when the period error is small
+ if (ple[2] | rcnt_ovfl) begin
+ es <= { es[1:0], (rdiff<29'sd8 && rdiff>-29'sd8) };
+ esmall <= valid_ref & ~rcnt_ovfl & (es[2:0] == 3'b111);
+ end
+ else if (sstate==REFDET) begin
+ es <= 3'b0;
+ esmall <= 1'b0;
+ end
+
+ // assign the dac value when doing coarse-adjustment
+ // in the fine-adjust phaase, the PI control filtering takes over
+ if (ple[3] & (sstate==CFADJ)) begin
+ dacv <= coarse[15:0];
+ end
+ else if (sstate==REFDET) begin
+ dacv <= 16'd32767; // center the DAC
+ end
+ end
+
+
+ always @(*) begin
+ nxt_sstate=sstate;
+ pr = 1'b0;
+ nxt_lcnt = recycle ? 26'd0 : lcnt + 1'b1;
+ case (sstate)
+ REFDET: begin // determine reference type
+ pr = 1'b0;
+ if (valid_ref) nxt_sstate = CFADJ;
+ end
+ CFADJ: begin // coarse freqency adjustment
+ pr = 1'b1;
+ if (esmall) nxt_sstate = SLEDGEA;
+ end
+ SLEDGEA: begin // ensure local pps is low and wait for a ref edge
+ pr = 1'b1; // preload the integrator
+ if (rhigh) nxt_sstate = SLEDGEB;
+ end
+ SLEDGEB: begin // force local pps rising edge to match reference
+ nxt_lcnt = 26'd0;
+ pr = 1'b1; // preload the integrator
+ if(rhigh) begin
+ nxt_lcnt = 28'd149_999_998; // force rising edge in a couple cycles
+ nxt_sstate = FINEADJ;
+ end
+ end
+ FINEADJ: begin // wide-ish bandwidth PI control
+ if (~valid_ref | llovfl) nxt_sstate = REFDET;
+ end
+ default: begin
+ nxt_sstate = REFDET;
+ end
+ endcase
+ // overriding conditions:
+ if (refinternal | refchanged | rcnt_ovfl ) nxt_sstate = REFDET;
+ end
+
+ reg llsena;
+ always @(posedge clk) begin
+ llstate <= nxt_llstate;
+ if (llcntena) llcnt <= nxt_llcnt;
+ if (llstate==READY) lead_lagn <= lead;
+ if (llsena) llsmall <= { (llsmall[2:0] == 3'b111), llsmall[1:0],
+ (llcnt < 12'sd3)&(llcnt > -12'sd3)};
+ if (llcntena) llovfl <= (llcnt>12'sd1800) | (llcnt< -12'sd1800);
+ end
+
+ reg ppsfltena;
+ always @(*) begin
+ // values to hold by default:
+ nxt_llstate = llstate;
+ llcntena=1'b0;
+ nxt_llcnt=llcnt;
+ ppsfltena = 1'b0;
+ llsena = 1'b0;
+
+ case (llstate)
+ READY: begin
+ nxt_llcnt=12'b0;
+ if (trig | dtrig) begin
+ nxt_llstate = trig ? COUNT : DONE;
+ llcntena=1'b1;
+ // even if dtrig, set llcnt to 0 to feed the filter pipe
+ end
+ end
+ COUNT: begin
+ if (untrig) begin // the second edge arrived
+ nxt_llstate = DONE;
+ end
+ else begin
+ llcntena=1'b1;
+ nxt_llcnt=llcnt+incr;
+ end
+ end
+ DONE: begin
+ nxt_llstate = WAIT;
+ ppsfltena = 1'b1;
+ end
+ WAIT: begin
+ if (llrdy) begin
+ nxt_llstate = READY;
+ llsena = 1'b1;
+ end
+ end
+ endcase
+ if (sstate==REFDET) begin
+ nxt_llstate = READY;
+ llcntena=1'b0;
+ ppsfltena = 1'b0;
+ llsena = 1'b0;
+ end
+ end
+
+
+ reg[15:0] daco;
+
+ reg [1:0] enchain=2'b00;
+ always @(posedge clk) enchain <= { enchain[1:0], ppsfltena & (enchain==2'b00) };
+
+ reg signed [23:0] integ;
+ reg signed [23:0] prop;
+ wire signed [23:0] nxt_integ = integ + (llcnt <<< 6);
+ wire signed [23:0] nxt_prop = (llcnt <<< 7);
+ wire signed [23:0] eff = integ + prop;
+ wire urng = eff[23], orng = eff[23:22]==2'b01;
+ reg erng;
+ /* The values for proportional and integral gain terms were originally
+ * estimated using a model that accounted for the xo tuning sensitivity.
+ * When implemented, the loop dynamics observed differed significantly
+ * from model results, probably as a result of the Xilinx PLL
+ * (which was not modelled) being present in the loop. The gain values
+ * were find-tuned 'by hand' by observing the loop behaviour (with llcnt
+ * instrumented) and pps signals connected out to an oscilloscope).
+ */
+
+ always @(posedge clk) begin
+ if (no_pps) begin
+ daco <= dac_dflt;
+ end
+ else if (pr) begin
+ integ <= { 2'b00, dacv, 6'b0 }; // precharge the accumulator
+ daco <= dacv;
+ end
+ else begin
+ if (enchain[0]) begin
+ integ <= nxt_integ;
+ prop <= nxt_prop;
+ end
+ if (enchain[1]) begin
+ daco <= eff[21:6];
+ erng <= urng | orng;
+ end
+ end
+ end
+
+ wire fadj= (sstate==FINEADJ);
+ always @(posedge clk) begin
+ reflck <= refinternal | fadj;
+ end
+
+ ad5662_auto_spi dac
+ (
+ .clk(clk),
+ .dat(daco),
+ .sclk(sclk),
+ .mosi(mosi),
+ .sync_n(sync_n)
+ );
+
+endmodule