diff options
author | Martin Braun <martin.braun@ettus.com> | 2020-01-23 16:10:22 -0800 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2020-01-28 09:35:36 -0800 |
commit | bafa9d95453387814ef25e6b6256ba8db2df612f (patch) | |
tree | 39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/lib/io_cap_gen | |
parent | 3075b981503002df3115d5f1d0b97d2619ba30f2 (diff) | |
download | uhd-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/lib/io_cap_gen')
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/Makefile.srcs | 23 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/cap_pattern_verifier.v | 125 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/cat_input_lvds.v | 609 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/cat_io_lvds.v | 200 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v | 397 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/cat_output_lvds.v | 396 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/catcap_ddr_cmos.v | 95 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/catcodec_ddr_cmos.v | 123 | ||||
-rw-r--r-- | fpga/usrp3/lib/io_cap_gen/catgen_ddr_cmos.v | 90 |
9 files changed, 2058 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/io_cap_gen/Makefile.srcs b/fpga/usrp3/lib/io_cap_gen/Makefile.srcs new file mode 100644 index 000000000..a068103cd --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/Makefile.srcs @@ -0,0 +1,23 @@ +# +# Copyright 2014 Ettus Research LLC +# Copyright 2016 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# IO Capture and Generation Sources +################################################## +CAT_CAP_GEN_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/io_cap_gen/, \ +./catcap_ddr_cmos.v \ +./catgen_ddr_cmos.v \ +./catcodec_ddr_cmos.v \ +./cat_input_lvds.v \ +./cat_output_lvds.v \ +./cat_io_lvds.v \ +./cat_io_lvds_dual_mode.v \ +)) + +CAP_GEN_GENERIC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/io_cap_gen/, \ +./cap_pattern_verifier.v \ +)) diff --git a/fpga/usrp3/lib/io_cap_gen/cap_pattern_verifier.v b/fpga/usrp3/lib/io_cap_gen/cap_pattern_verifier.v new file mode 100644 index 000000000..0d4877187 --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/cap_pattern_verifier.v @@ -0,0 +1,125 @@ +// +// Copyright 2015 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// +// Synthesizable test pattern checker +// + +module cap_pattern_verifier #( + parameter WIDTH = 16, //Width of data bus + parameter PATTERN = "RAMP", //Pattern to detect. Choose from {RAMP, ONES, ZEROS, TOGGLE, LEFT_BARREL, RIGHT_BARREL} + parameter RAMP_START = 'h0000, //Start value for ramp (PATTERN=RAMP only) + parameter RAMP_STOP = 'hFFFF, //Stop value for ramp (PATTERN=RAMP only) + parameter RAMP_INCR = 'h0001, //Increment for ramp (PATTERN=RAMP only) + parameter BARREL_INIT = 'h0001, //Initial value for the barrel shifter (PATTERN=*_BARREL only) + parameter HOLD_CYCLES = 1 //Number of cycles to hold each value in the pattern +) ( + input clk, + input rst, + + //Data input + input valid, + input [WIDTH-1:0] data, + + //Status output (2 cycle latency) + output reg [31:0] count, + output reg [31:0] errors, + output locked, + output failed +); + + //Create a synchronous version of rst + wire sync_rst; + reset_sync reset_sync_i ( + .clk(clk), .reset_in(rst), .reset_out(sync_rst)); + + // Register the data to minimize fanout at source + reg [WIDTH-1:0] data_reg; + reg valid_reg; + always @(posedge clk) + {data_reg, valid_reg} <= {data, valid}; + + // Define pattern start and next states + wire [WIDTH-1:0] patt_start, patt_next; + reg [WIDTH-1:0] patt_next_reg; + generate if (PATTERN == "RAMP") begin + assign patt_start = RAMP_START; + assign patt_next = (data_reg==RAMP_STOP) ? RAMP_START : data_reg+RAMP_INCR; + end else if (PATTERN == "ZEROS") begin + assign patt_start = {WIDTH{1'b0}}; + assign patt_next = {WIDTH{1'b0}}; + end else if (PATTERN == "ONES") begin + assign patt_start = {WIDTH{1'b1}}; + assign patt_next = {WIDTH{1'b1}}; + end else if (PATTERN == "TOGGLE") begin + assign patt_start = {(WIDTH/2){2'b10}}; + assign patt_next = ~data_reg; + end else if (PATTERN == "LEFT_BARREL") begin + assign patt_start = BARREL_INIT; + assign patt_next = {data_reg[WIDTH-2:0],data_reg[WIDTH-1]}; + end else if (PATTERN == "RIGHT_BARREL") begin + assign patt_start = BARREL_INIT; + assign patt_next = {data_reg[0],data_reg[WIDTH-1:1]}; + end endgenerate + + reg [1:0] state; + localparam ST_IDLE = 2'd0; + localparam ST_LOCKED = 2'd1; + + reg [7:0] cyc_count; + + //All registers in this state machine need to have an + //asynchronous reset because the "data" and "valid" can + //be metastable coming into this module, and can possibly + //corrupt "state". + always @(posedge clk or posedge rst) begin + if (rst) begin //Asynchronous reset + count <= 32'd0; + errors <= 32'd0; + state <= ST_IDLE; + cyc_count <= 8'd0; + patt_next_reg <= {WIDTH{1'b0}}; + end else begin + //Only do something if data is valid + if (valid_reg & ~sync_rst) begin + case (state) + ST_IDLE: begin + //Trigger on start of pattern + //We use a case equality here to ensure that this module + //does the right thing in simulation. In HW this should + //infer a "==" + if (data_reg === patt_start) begin + state <= ST_LOCKED; + count <= 32'd1; + cyc_count <= HOLD_CYCLES - 1; + end + end + ST_LOCKED: begin + if (cyc_count == 0) begin //Hold counter has expired. Check next word + count <= count + 32'd1; + //We use a case equality here to ensure that this module + //does the right thing in simulation. In HW this should + //infer a "!=" + if (data_reg !== patt_next_reg) begin + errors <= errors + 32'd1; + end + cyc_count <= HOLD_CYCLES - 1; + end else begin //Hold until the next update + cyc_count <= cyc_count - 1; + end + end + endcase + patt_next_reg <= patt_next; //Update next pattern + end + end + end + + assign locked = (state == ST_LOCKED); + assign failed = (errors != 32'd0) && locked; + +endmodule + + + diff --git a/fpga/usrp3/lib/io_cap_gen/cat_input_lvds.v b/fpga/usrp3/lib/io_cap_gen/cat_input_lvds.v new file mode 100644 index 000000000..9b24ca6ca --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/cat_input_lvds.v @@ -0,0 +1,609 @@ +// +// Copyright 2016 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: cat_input_lvds +// +// Description: +// +// Receive interface to AD9361 (Catalina) in LVDS mode. +// +// Use Xilinx SERDES to deserialize interleaved sample data off +// half-word-width LVDS differential data bus from the Catalina. +// +// Use FRAME signal to initially synchronize to incoming data after reset +// de-asserts. +// +// In all modes (SISO or MIMO) we output a clock of 1/4 the frequency +// of the Catalina source-synchronous bus clock to be used as the radio_clk. +// +// In SISO mode, every cycle of the radio_clk supplies a new RX sample which +// is routed to both radios, even if only one is actively receiving. +// +// In MIMO mode, every cycle of the radio clock supplies a pair of +// time aligned MIMO samples which are routed to different radios. +// +// The frame_sample signal controls the expected frame signal timing. When +// frame_sample is 0, the period of the ddr_frame signal is expected to equal +// two samples (e.g., one from each channel). When frame_sample is 1, the frame +// period is expected to equal the length of one sample. This allows the module +// to be used for 2R2T (frame_sample = 1) or 1R1T mode (frame_sample = 0). +// + + +module cat_input_lvds #( + parameter INVERT_FRAME_RX = 0, + parameter INVERT_DATA_RX = 6'b00_0000, + parameter USE_CLOCK_DELAY = 1, + parameter USE_DATA_DELAY = 1, + parameter CLOCK_DELAY_MODE = "VAR_LOAD", + parameter DATA_DELAY_MODE = "VAR_LOAD", + parameter CLOCK_DELAY = 0, + parameter DATA_DELAY = 0, + parameter WIDTH = 6, + parameter GROUP = "DEFAULT", + parameter USE_BUFG = 1 +) ( + input clk200, + input rst, + + // Data and frame timing (synchronous to radio_clk) + input mimo, // Output one channel (MIMO=0) or two (MIMO=1) + input frame_sample, // Two samples per frame period (frame_sample=0) or one sample per frame (frame_sample=1) + + // Region local Clocks for I/O cells. + output ddr_clk, + output sdr_clk, + + // Source Synchronous external input clock + input ddr_clk_p, + input ddr_clk_n, + + // Source Synchronous data lines + input [WIDTH-1:0] ddr_data_p, + input [WIDTH-1:0] ddr_data_n, + input ddr_frame_p, + input ddr_frame_n, + + // Delay control interface + input ctrl_clk, + input [4:0] ctrl_data_delay, + input [4:0] ctrl_clk_delay, + input ctrl_ld_data_delay, + input ctrl_ld_clk_delay, + + // Global output clocks, ddr_clk/4 & ddr_clk/2 + output radio_clk, + output radio_clk_2x, + + // SDR Data buses + output reg [(WIDTH*2)-1:0] i0, + output reg [(WIDTH*2)-1:0] q0, + output reg [(WIDTH*2)-1:0] i1, + output reg [(WIDTH*2)-1:0] q1, + output reg rx_aligned + +); + + //------------------------------------------------------------------ + // UG471 says take reset high asynchronously, and de-assert + // synchronized to CLKDIV (sdr_clk) for SERDES. + //------------------------------------------------------------------ + (* ASYNC_REG = "TRUE" *) reg rst_sdr_sync, rst_sdr_sync_ms; + + always @(posedge sdr_clk or posedge rst) + if (rst) begin + rst_sdr_sync_ms <= 1'b1; + rst_sdr_sync <= 1'b1; + end else begin + rst_sdr_sync_ms <= 1'b0; + rst_sdr_sync <= rst_sdr_sync_ms; + end + + + //------------------------------------------------------------------ + // IDELAY is calibrated using (mandatory) IDELAYCTRL cell. + // Must be feed stable free running clock specified by: FIDELAYCTRL_REF.(200MHz) + // Mandatory async reset required, min pulse of: TIDELAYCTRL_RPW (~60nS) + //------------------------------------------------------------------ + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + IDELAYCTRL IDELAYCTRL_i0 ( + .REFCLK (clk200), + .RST (rst_sdr_sync), + .RDY () + ); + + + //------------------------------------------------------------------ + // Clock input + //------------------------------------------------------------------ + wire ddr_clk_dly, ddr_clk_unbuf; + + IBUFDS #( + .DIFF_TERM("TRUE") + ) clk_ibufds ( + .O(ddr_clk_unbuf), + .I(ddr_clk_p), + .IB(ddr_clk_n) + ); + + generate + if (USE_CLOCK_DELAY) begin : gen_clock_delay + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + IDELAYE2 #( + .CINVCTRL_SEL ("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) + .DELAY_SRC ("IDATAIN"), // Delay input (IDATAIN, DATAIN) + .HIGH_PERFORMANCE_MODE ("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") + .IDELAY_TYPE (CLOCK_DELAY_MODE), + .IDELAY_VALUE (CLOCK_DELAY), + .PIPE_SEL ("FALSE"), + .REFCLK_FREQUENCY (200.0), + .SIGNAL_PATTERN ("CLOCK") + ) ddr_clk_idelaye2 ( + .CNTVALUEOUT (), // 5-bit output: Counter value output + .DATAOUT (ddr_clk_dly), // 1-bit output: Delayed data output + .C (ctrl_clk), // 1-bit input: Clock input + .CE (1'b0), // 1-bit input: Active high enable increment/decrement input + .CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input + .CNTVALUEIN (ctrl_clk_delay), // 5-bit input: Counter value input + .DATAIN (1'b0), // 1-bit input: Internal delay data input + .IDATAIN (ddr_clk_unbuf), // 1-bit input: Data input from the I/O + .INC (1'b0), // 1-bit input: Increment / Decrement tap delay input + .LD (ctrl_ld_clk_delay), // 1-bit input: Load IDELAY_VALUE input + .LDPIPEEN (1'b0), // 1-bit input: Enable PIPELINE register to load data input + .REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input + ); + end + else begin + assign ddr_clk_dly = ddr_clk_unbuf; + end + + endgenerate + + // IO CLock is DDR freq. This drives SERDES and other I/O elements with minimal clock skew. + BUFIO ddr_clk_bufio (.O(ddr_clk),.I(ddr_clk_dly)); + + // SDR clock is one quarter DDR freq and local to regio using BUFR + // BUFR is a constraint of the SERDES since we need frequency agnostic clock division. + // UG471 states can use BUFIO and BUFR divided to directly drive a SERDES legally. + // (Other option is pair of BUFG's plus an MMCM - But MMCM has fixed frequency) + wire sdr_clk_2x; + + BUFR #( + .BUFR_DIVIDE ("2"), + .SIM_DEVICE ("7SERIES") + ) sdr_clk_2x_bufr ( + .O (sdr_clk_2x), + .CE (1'b1), + .CLR (1'b0), + .I (ddr_clk_dly) + ); + + BUFR #( + .BUFR_DIVIDE("4"), + .SIM_DEVICE("7SERIES") + ) sdr_clk_bufr ( + .O(sdr_clk), + .CE(1'b1), + .CLR(1'b0), + .I(ddr_clk_dly) + ); + + generate + if (USE_BUFG) begin : gen_BUFG + // radio_clock is sdr_clk re-buffered with BUFG, and radio_clk_2x is + // sdr_clk_2x re-buffered, so both can be used globally. This introduces skew + // between sdr_clk -> radio_clock so we must hand data between them carefully + // even though they have a fixed phase relationship. + BUFG radio_clk_1x_bufg (.O(radio_clk), .I(sdr_clk)); + BUFG radio_clk_2x_bufg (.O(radio_clk_2x), .I(sdr_clk_2x)); + end else begin + assign radio_clk = sdr_clk; + assign radio_clk_2x = sdr_clk_2x; + end + endgenerate + + + + //------------------------------------------------------------------ + // Frame Signal + //------------------------------------------------------------------ + wire ddr_frame, ddr_frame_dly; + wire [7:0] des_frame; // deserialized frame signal + reg bitslip; + reg aligned; + + + // + // Use FRAME signal to get bitstream word aligned. + // + // In MIMO mode, FRAME is asserted during the entirety of channel 0, and + // deasserts during the entirety of channel 1. + // + localparam IDLE = 0; + localparam SEARCH = 1; + localparam SLIP1 = 3; + localparam SLIP2 = 2; + localparam SLIP3 = 4; + localparam SLIP4 = 5; + localparam SYNC = 6; + + + reg [2:0] frame_state; + + // + // Delay start of framesync operation for 64 clocks after reset de-asserts to + // SERDES to be sure they are in a steady state. + // + // Each time we assert bitslip we then have to wait 2 cycles before we can + // examine the results. + // + // Checking for 0xF0 and 0xCC allows us to support 1R1T and 2R2T timing, + // which have different frame periods. + wire frame_is_aligned = + (!frame_sample && (des_frame[7:0] == (INVERT_FRAME_RX ? 8'h0F : 8'hF0))) || + ( frame_sample && (des_frame[7:0] == (INVERT_FRAME_RX ? 8'h33 : 8'hCC))); + + reg [5:0] sync_delay; + reg run_sync; + + + always @(posedge sdr_clk) + if (rst_sdr_sync) begin + sync_delay <= 6'h0; + run_sync <= 1'b0; + end else if (sync_delay == 6'h3F) + run_sync <= 1'b1; + else + sync_delay <= sync_delay + 1'b1; + + always @(posedge sdr_clk) + begin + if (!run_sync) begin + frame_state <= IDLE; + bitslip <= 1'b0; + aligned <= 1'b0; + end else begin + case (frame_state) + IDLE: begin + bitslip <= 1'b0; + aligned <= 1'b0; + frame_state <= SEARCH; + end + + SEARCH: begin + if (frame_is_aligned) begin + frame_state <= SYNC; + bitslip <= 1'b0; + aligned <= 1'b1; + end else begin + // Bitslip until captured frame is aligned + bitslip <= 1'b1; + frame_state <= SLIP1; + aligned <= 1'b0; + end + end + + SLIP1: begin + frame_state <= SLIP2; + bitslip <= 1'b0; + aligned <= 1'b0; + end + + SLIP2: begin + frame_state <= SLIP3; + bitslip <= 1'b0; + aligned <= 1'b0; + end + + SLIP3: begin + frame_state <= SLIP4; + bitslip <= 1'b0; + aligned <= 1'b0; + end + + SLIP4: begin + frame_state <= SEARCH; + bitslip <= 1'b0; + aligned <= 1'b0; + end + + SYNC: begin + if (frame_is_aligned) begin + frame_state <= SYNC; + aligned <= 1'b1; + end else begin + frame_state <= SEARCH; + aligned <= 1'b0; + end + end + + endcase // case(frame_state) + + end + end + + + IBUFDS #( + .DIFF_TERM ("TRUE") + ) ddr_frame_ibufds ( + .O (ddr_frame), + .I (ddr_frame_p), + .IB (ddr_frame_n) + ); + + generate + if (USE_DATA_DELAY) begin : gen_frame_delay + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + IDELAYE2 #( + .CINVCTRL_SEL ("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) + .DELAY_SRC ("IDATAIN"), // Delay input (IDATAIN, DATAIN) + .HIGH_PERFORMANCE_MODE ("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") + .IDELAY_TYPE (DATA_DELAY_MODE), + .IDELAY_VALUE (DATA_DELAY), + .PIPE_SEL ("FALSE"), + .REFCLK_FREQUENCY (200.0), + .SIGNAL_PATTERN ("DATA") + ) ddr_frame_idelaye2 ( + .CNTVALUEOUT (), // 5-bit output: Counter value output + .DATAOUT (ddr_frame_dly), // 1-bit output: Delayed data output + .C (ctrl_clk), // 1-bit input: Clock input + .CE (1'b0), // 1-bit input: Active high enable increment/decrement input + .CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input + .CNTVALUEIN (ctrl_data_delay), // 5-bit input: Counter value input + .DATAIN (1'b0), // 1-bit input: Internal delay data input + .IDATAIN (ddr_frame), // 1-bit input: Data input from the I/O + .INC (1'b0), // 1-bit input: Increment / Decrement tap delay input + .LD (ctrl_ld_data_delay), // 1-bit input: Load IDELAY_VALUE input + .LDPIPEEN (1'b0), // 1-bit input: Enable PIPELINE register to load data input + .REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input + ); + end + else begin + assign ddr_frame_dly = ddr_frame; + end + endgenerate + + ISERDESE2 #( + .DATA_RATE ("DDR"), // DDR, SDR + .DATA_WIDTH (8), // Parallel data width (2-8,10,14) + .DYN_CLKDIV_INV_EN ("FALSE"), // Enable DYNCLKDIVINVSEL inversion (FALSE, TRUE) + .DYN_CLK_INV_EN ("FALSE"), // Enable DYNCLKINVSEL inversion (FALSE, TRUE) + // INIT_Q1 - INIT_Q4: Initial value on the Q outputs (0/1) + .INIT_Q1 (1'b0), + .INIT_Q2 (1'b0), + .INIT_Q3 (1'b0), + .INIT_Q4 (1'b0), + .INTERFACE_TYPE ("NETWORKING"), + .IOBDELAY ("BOTH"), + .NUM_CE (1), + .OFB_USED ("FALSE"), + .SERDES_MODE ("MASTER"), + // SRVAL_Q1 - SRVAL_Q4: Q output values when SR is used (0/1) + .SRVAL_Q1 (1'b0), + .SRVAL_Q2 (1'b0), + .SRVAL_Q3 (1'b0), + .SRVAL_Q4 (1'b0) + ) ddr_frame_serdese2 ( + .O (), // 1-bit output: Combinatorial output + // Q1 - Q8: 1-bit (each) output: Registered data outputs + .Q1 (des_frame[0]), + .Q2 (des_frame[1]), + .Q3 (des_frame[2]), + .Q4 (des_frame[3]), + .Q5 (des_frame[4]), + .Q6 (des_frame[5]), + .Q7 (des_frame[6]), + .Q8 (des_frame[7]), + // SHIFTOUT1, SHIFTOUT2: 1-bit (each) output: Data width expansion output ports + .SHIFTOUT1 (), + .SHIFTOUT2 (), + // 1-bit input: The BITSLIP pin performs a Bitslip operation synchronous to + // CLKDIV when asserted (active High). Subsequently, the data seen on the Q1 + // to Q8 output ports will shift, as in a barrel-shifter operation, one + // position every time Bitslip is invoked (DDR operation is different from SDR) + .BITSLIP (bitslip), + // CE1, CE2: 1-bit (each) input: Data register clock enable inputs + .CE1 (1'b1), + .CE2 (1'b1), + .CLKDIVP (1'b0), // 1-bit input: TBD + // Clocks: 1-bit (each) input: ISERDESE2 clock input ports + .CLK (ddr_clk), // 1-bit input: High-speed clock + .CLKB (~ddr_clk), // 1-bit input: High-speed secondary clock + .CLKDIV (sdr_clk), // 1-bit input: Divided clock + .OCLK (1'b0), // 1-bit input: High-speed output clock used when INTERFACE_TYPE="MEMORY" + // Dynamic Clock Inversions: 1-bit (each) input: Dynamic clock inversion pins to switch clock polarity + .DYNCLKDIVSEL (1'b0), // 1-bit input: Dynamic CLKDIV inversion + .DYNCLKSEL (1'b0), // 1-bit input: Dynamic CLK/CLKB inversion + // Input Data: 1-bit (each) input: ISERDESE2 data input ports + .D (1'b0), // 1-bit input: Data input + .DDLY (ddr_frame_dly), // 1-bit input: Serial data from IDELAYE2 + .OFB (1'b0), // 1-bit input: Data feedback from OSERDESE2 + .OCLKB (1'b0), // 1-bit input: High-speed negative edge output clock + .RST (rst_sdr_sync), // 1-bit input: Active high asynchronous reset + // SHIFTIN1, SHIFTIN2: 1-bit (each) input: Data width expansion input ports + .SHIFTIN1 (1'b0), + .SHIFTIN2 (1'b0) + ); + + + //------------------------------------------------------------------ + // Data Bus + //------------------------------------------------------------------ + wire [WIDTH-1:0] ddr_data; + wire [WIDTH-1:0] ddr_data_dly; + wire [(WIDTH*2)-1:0] data_i0, data_i1; + wire [(WIDTH*2)-1:0] data_q0, data_q1; + + + genvar i; + generate + for (i=0 ; i<WIDTH ; i=i+1) begin : generate_data_bus + + IBUFDS #( + .DIFF_TERM ("TRUE") + ) ddr_data_ibufds ( + .O (ddr_data[i]), + .I (ddr_data_p[i]), + .IB (ddr_data_n[i]) + ); + + if (USE_DATA_DELAY) begin : gen_data_delay + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + IDELAYE2 #( + .CINVCTRL_SEL ("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) + .DELAY_SRC ("IDATAIN"), // Delay input (IDATAIN, DATAIN) + .HIGH_PERFORMANCE_MODE ("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") + .IDELAY_TYPE (DATA_DELAY_MODE), + .IDELAY_VALUE (DATA_DELAY), + .PIPE_SEL ("FALSE"), + .REFCLK_FREQUENCY (200.0), + .SIGNAL_PATTERN ("DATA") + ) ddr_data_idelaye2 ( + .CNTVALUEOUT (), // 5-bit output: Counter value output + .DATAOUT (ddr_data_dly[i]), // 1-bit output: Delayed data output + .C (ctrl_clk), // 1-bit input: Clock input + .CE (1'b0), // 1-bit input: Active high enable increment/decrement input + .CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input + .CNTVALUEIN (ctrl_data_delay), // 5-bit input: Counter value input + .DATAIN (1'b0), // 1-bit input: Internal delay data input + .IDATAIN (ddr_data[i]), // 1-bit input: Data input from the I/O + .INC (1'b0), // 1-bit input: Increment / Decrement tap delay input + .LD (ctrl_ld_data_delay), // 1-bit input: Load IDELAY_VALUE input + .LDPIPEEN (1'b0), // 1-bit input: Enable PIPELINE register to load data input + .REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input + ); + end + else begin + assign ddr_data_dly[i] = ddr_data[i]; + end + + ISERDESE2 #( + .DATA_RATE ("DDR"), // DDR, SDR + .DATA_WIDTH (8), // Parallel data width (2-8,10,14) + .DYN_CLKDIV_INV_EN ("FALSE"), // Enable DYNCLKDIVINVSEL inversion (FALSE, TRUE) + .DYN_CLK_INV_EN ("FALSE"), // Enable DYNCLKINVSEL inversion (FALSE, TRUE) + // INIT_Q1 - INIT_Q4: Initial value on the Q outputs (0/1) + .INIT_Q1 (1'b0), + .INIT_Q2 (1'b0), + .INIT_Q3 (1'b0), + .INIT_Q4 (1'b0), + .INTERFACE_TYPE ("NETWORKING"), + .IOBDELAY ("BOTH"), + .NUM_CE (1), + .OFB_USED ("FALSE"), + .SERDES_MODE ("MASTER"), + // SRVAL_Q1 - SRVAL_Q4: Q output values when SR is used (0/1) + .SRVAL_Q1 (1'b0), + .SRVAL_Q2 (1'b0), + .SRVAL_Q3 (1'b0), + .SRVAL_Q4 (1'b0) + ) ddr_data_serdese2 ( + .O (), // 1-bit output: Combinatorial output + // Q1 - Q8: 1-bit (each) output: Registered data outputs + .Q1 (data_q1[i]), + .Q2 (data_i1[i]), + .Q3 (data_q1[WIDTH+i]), + .Q4 (data_i1[WIDTH+i]), + .Q5 (data_q0[i]), + .Q6 (data_i0[i]), + .Q7 (data_q0[WIDTH+i]), + .Q8 (data_i0[WIDTH+i]), + // SHIFTOUT1, SHIFTOUT2: 1-bit (each) output: Data width expansion output ports + .SHIFTOUT1 (), + .SHIFTOUT2 (), + // 1-bit input: The BITSLIP pin performs a Bitslip operation synchronous to + // CLKDIV when asserted (active High). Subsequently, the data seen on the Q1 + // to Q8 output ports will shift, as in a barrel-shifter operation, one + // position every time Bitslip is invoked (DDR operation is different from SDR) + .BITSLIP (bitslip), + // CE1, CE2: 1-bit (each) input: Data register clock enable inputs + .CE1 (1'b1), + .CE2 (1'b1), + .CLKDIVP (1'b0), // 1-bit input: TBD + // Clocks: 1-bit (each) input: ISERDESE2 clock input ports + .CLK (ddr_clk), // 1-bit input: High-speed clock + .CLKB (~ddr_clk), // 1-bit input: High-speed secondary clock + .CLKDIV (sdr_clk), // 1-bit input: Divided clock + .OCLK (1'b0), // 1-bit input: High-speed output clock used when INTERFACE_TYPE="MEMORY" + // Dynamic Clock Inversions: 1-bit (each) input: Dynamic clock inversion pins to switch clock polarity + .DYNCLKDIVSEL (1'b0), // 1-bit input: Dynamic CLKDIV inversion + .DYNCLKSEL (1'b0), // 1-bit input: Dynamic CLK/CLKB inversion + // Input Data: 1-bit (each) input: ISERDESE2 data input ports + .D (1'b0), // 1-bit input: Data input + .DDLY (ddr_data_dly[i]), // 1-bit input: Serial data from IDELAYE2 + .OFB (1'b0), // 1-bit input: Data feedback from OSERDESE2 + .OCLKB (1'b0), // 1-bit input: High-speed negative edge output clock + .RST (rst_sdr_sync), // 1-bit input: Active high asynchronous reset + // SHIFTIN1, SHIFTIN2: 1-bit (each) input: Data width expansion input ports + .SHIFTIN1 (1'b0), + .SHIFTIN2 (1'b0) + ); + + end // block: generate_data_bus + endgenerate + + // + // Cross these cycles to radio_clk using negative edge. This give 1/2 a radio + // clock period + BUFG insertion delay for signals to propagate. Thats > 6nS. + // + reg [(WIDTH*2)-1:0] radio_data_i0, radio_data_i1, radio_data_q0, radio_data_q1; + reg radio_aligned; + + always @(negedge radio_clk) + begin + radio_data_i0[(WIDTH*2)-1:0] <= data_i0[(WIDTH*2)-1:0] ^ {INVERT_DATA_RX,INVERT_DATA_RX}; + radio_data_q0[(WIDTH*2)-1:0] <= data_q0[(WIDTH*2)-1:0] ^ {INVERT_DATA_RX,INVERT_DATA_RX}; + radio_data_i1[(WIDTH*2)-1:0] <= data_i1[(WIDTH*2)-1:0] ^ {INVERT_DATA_RX,INVERT_DATA_RX}; + radio_data_q1[(WIDTH*2)-1:0] <= data_q1[(WIDTH*2)-1:0] ^ {INVERT_DATA_RX,INVERT_DATA_RX}; + radio_aligned <= aligned; + end + + always @(posedge radio_clk) + begin + i0 <= radio_data_i0; + q0 <= radio_data_q0; + if (mimo) { i1, q1 } <= { radio_data_i1, radio_data_q1 }; + else { i1, q1 } <= { radio_data_i0, radio_data_q0 }; // dup single valid channel to both radios + rx_aligned <= radio_aligned; + end + + /******************************************************************* + * Debug only logic below here. + ******************************************************************/ +/*-----\/----- EXCLUDED -----\/----- + (* keep = "true", max_fanout = 10 *) reg [7:0] des_frame_reg; + (* keep = "true", max_fanout = 10 *) reg rst_sdr_sync_reg; + (* keep = "true", max_fanout = 10 *) reg run_sync_reg; + (* keep = "true", max_fanout = 10 *) reg bitslip_reg; + (* keep = "true", max_fanout = 10 *) reg aligned_reg; + (* keep = "true", max_fanout = 10 *) reg [2:0] frame_state_reg; + + always @(posedge sdr_clk) + begin + des_frame_reg <= des_frame; + rst_sdr_sync_reg <= rst_sdr_sync; + run_sync_reg <= run_sync; + bitslip_reg <= bitslip; + aligned_reg <= aligned; + frame_state_reg <= frame_state; + end + + ila64 ila64_i ( + .clk(sdr_clk), // input clk + .probe0( + { + des_frame_reg, + rst_sdr_sync_reg, + run_sync_reg, + bitslip_reg, + aligned_reg, + frame_state_reg + } + ) + ); + -----/\----- EXCLUDED -----/\----- */ + +endmodule + diff --git a/fpga/usrp3/lib/io_cap_gen/cat_io_lvds.v b/fpga/usrp3/lib/io_cap_gen/cat_io_lvds.v new file mode 100644 index 000000000..e43e26e00 --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/cat_io_lvds.v @@ -0,0 +1,200 @@ +// +// Copyright 2016 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: cat_io_lvds +// +// Description: +// +// This is an LVDS interface for the AD9361 (Catalina). It consists of of an +// input module for Rx and an output module for Tx. See the AD9361 Interface +// Specification and the AD9361 data sheet for details. +// +// This module assumes a dual-port, full-duplex topology, in 2R2T timing mode. +// The mimo signal allows you to support one (mimo = 0) or two channels (mimo = +// 1). When mimo = 0, the data from one channel is simply ignored and radio_clk +// frequency remains equal to rx_clk_p / 4. +// + +module cat_io_lvds #( + parameter INVERT_FRAME_RX = 0, + parameter INVERT_DATA_RX = 6'b00_0000, + parameter INVERT_FRAME_TX = 0, + parameter INVERT_DATA_TX = 6'b00_0000, + parameter USE_CLOCK_IDELAY = 1, + parameter USE_DATA_IDELAY = 1, + parameter DATA_IDELAY_MODE = "VAR_LOAD", + parameter CLOCK_IDELAY_MODE = "VAR_LOAD", + parameter INPUT_CLOCK_DELAY = 16, + parameter INPUT_DATA_DELAY = 0, + parameter USE_CLOCK_ODELAY = 0, + parameter USE_DATA_ODELAY = 0, + parameter DATA_ODELAY_MODE = "VAR_LOAD", + parameter CLOCK_ODELAY_MODE = "VAR_LOAD", + parameter OUTPUT_CLOCK_DELAY = 16, + parameter OUTPUT_DATA_DELAY = 0, + parameter USE_BUFG = 1 +) ( + input rst, + input clk200, + + // Data and frame timing (synchronous to radio_clk) + input mimo, + input frame_sample, + + // Delay Control Interface + input ctrl_clk, + input [4:0] ctrl_in_data_delay, + input [4:0] ctrl_in_clk_delay, + input ctrl_ld_in_data_delay, + input ctrl_ld_in_clk_delay, + input [4:0] ctrl_out_data_delay, + input [4:0] ctrl_out_clk_delay, + input ctrl_ld_out_data_delay, + input ctrl_ld_out_clk_delay, + + // Baseband sample interface + output radio_clk, + output radio_clk_2x, + output rx_aligned, + output [11:0] rx_i0, + output [11:0] rx_q0, + output [11:0] rx_i1, + output [11:0] rx_q1, + input [11:0] tx_i0, + input [11:0] tx_q0, + input [11:0] tx_i1, + input [11:0] tx_q1, + + // Catalina LVDS interface + input rx_clk_p, + input rx_clk_n, + input rx_frame_p, + input rx_frame_n, + input [5:0] rx_d_p, + input [5:0] rx_d_n, + output tx_clk_p, + output tx_clk_n, + output tx_frame_p, + output tx_frame_n, + output [5:0] tx_d_p, + output [5:0] tx_d_n +); + + wire sdr_clk, ddr_clk; + + //--------------------------------------------------------------------------- + // Input (Rx) Interface + //--------------------------------------------------------------------------- + + cat_input_lvds #( + .INVERT_FRAME_RX (INVERT_FRAME_RX), + .INVERT_DATA_RX (INVERT_DATA_RX), + .USE_CLOCK_DELAY (USE_CLOCK_IDELAY), + .USE_DATA_DELAY (USE_DATA_IDELAY), + .CLOCK_DELAY_MODE (CLOCK_IDELAY_MODE), + .DATA_DELAY_MODE (DATA_IDELAY_MODE), + .CLOCK_DELAY (INPUT_CLOCK_DELAY), + .DATA_DELAY (INPUT_DATA_DELAY), + .WIDTH (6), + .GROUP ("CATALINA"), + .USE_BUFG (USE_BUFG) + ) cat_input_lvds_i0 ( + .clk200 (clk200), + .rst (rst), + + // Data and frame timing + .mimo (mimo), + .frame_sample (frame_sample), + + // Region local Clocks for I/O cells. + .ddr_clk (ddr_clk), + .sdr_clk (sdr_clk), + + // Source Synchronous external input clock + .ddr_clk_p (rx_clk_p), + .ddr_clk_n (rx_clk_n), + + // Source Synchronous data lines + .ddr_data_p (rx_d_p), + .ddr_data_n (rx_d_n), + .ddr_frame_p (rx_frame_p), + .ddr_frame_n (rx_frame_n), + + // Delay control interface + .ctrl_clk (ctrl_clk), + .ctrl_data_delay (ctrl_in_data_delay), + .ctrl_clk_delay (ctrl_in_clk_delay), + .ctrl_ld_data_delay (ctrl_ld_in_data_delay), + .ctrl_ld_clk_delay (ctrl_ld_in_clk_delay), + + // SDR output clock(s) + .radio_clk (radio_clk), + .radio_clk_2x (radio_clk_2x), + + // SDR Data buses + .i0 (rx_i0), + .q0 (rx_q0), + .i1 (rx_i1), + .q1 (rx_q1), + .rx_aligned (rx_aligned) + ); + + + //--------------------------------------------------------------------------- + // Output (Tx) Interface + //--------------------------------------------------------------------------- + + cat_output_lvds #( + .INVERT_FRAME_TX (INVERT_FRAME_TX), + .INVERT_DATA_TX (INVERT_DATA_TX), + .USE_CLOCK_DELAY (USE_CLOCK_ODELAY), + .USE_DATA_DELAY (USE_DATA_ODELAY), + .CLOCK_DELAY_MODE (CLOCK_ODELAY_MODE), + .DATA_DELAY_MODE (DATA_ODELAY_MODE), + .CLOCK_DELAY (OUTPUT_CLOCK_DELAY), + .DATA_DELAY (OUTPUT_DATA_DELAY), + .WIDTH (6), + .GROUP ("CATALINA") + ) cat_output_lvds_i0 ( + .clk200 (clk200), + .rst (rst), + + // Two samples per frame period (frame_sample=0; e.g., for two-channel + // mode) or one sample per frame (frame_sample=1) + .frame_sample(frame_sample), + + // Region local Clocks for I/O cells. + .ddr_clk (ddr_clk), + .sdr_clk (sdr_clk), + + // Source Synchronous external input clock + .ddr_clk_p (tx_clk_p), + .ddr_clk_n (tx_clk_n), + + // Source Synchronous data lines + .ddr_data_p (tx_d_p), + .ddr_data_n (tx_d_n), + .ddr_frame_p (tx_frame_p), + .ddr_frame_n (tx_frame_n), + + // Delay control interface + .ctrl_clk (ctrl_clk), + .ctrl_data_delay (ctrl_out_data_delay), + .ctrl_clk_delay (ctrl_out_clk_delay), + .ctrl_ld_data_delay (ctrl_ld_out_data_delay), + .ctrl_ld_clk_delay (ctrl_ld_out_clk_delay), + + // SDR global input clock + .radio_clk (radio_clk), + + // SDR Data buses + .i0 (tx_i0), + .q0 (tx_q0), + .i1 (tx_i1), + .q1 (tx_q1) + ); + + +endmodule // cat_io_lvds diff --git a/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v b/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v new file mode 100644 index 000000000..13b6ae1f9 --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/cat_io_lvds_dual_mode.v @@ -0,0 +1,397 @@ +// +// Copyright 2016 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: cat_io_lvds_dual_mode +// +// Description: +// +// This is an LVDS interface for the AD9361 (Catalina). It uses the cat_io_lvds +// module to implement the interface, but supports both 1R1T and 2R2T timing +// modes while using full LVDS bandwidth. That is, it can support 1R1T at twice +// the sample rate of 2R2T. +// +// This is controlled by the a_mimo control signal. When MIMO = 0 (1R1T mode), +// the radio_clk frequency equals that of rx_clk/2 and the data is output to +// both radio channels. If MIMO = 1 (2R2T), the frequency of radio_clk equals +// rx_clk/4 and the data stream is split between channel 0 and channel 1. This is used +// for 2R2T mode. +// + +module cat_io_lvds_dual_mode #( + parameter INVERT_FRAME_RX = 0, + parameter INVERT_DATA_RX = 6'b00_0000, + parameter INVERT_FRAME_TX = 0, + parameter INVERT_DATA_TX = 6'b00_0000, + parameter USE_CLOCK_IDELAY = 1, + parameter USE_DATA_IDELAY = 1, + parameter DATA_IDELAY_MODE = "FIXED", + parameter CLOCK_IDELAY_MODE = "FIXED", + parameter INPUT_CLOCK_DELAY = 16, + parameter INPUT_DATA_DELAY = 0, + parameter USE_CLOCK_ODELAY = 0, + parameter USE_DATA_ODELAY = 0, + parameter DATA_ODELAY_MODE = "FIXED", + parameter CLOCK_ODELAY_MODE = "FIXED", + parameter OUTPUT_CLOCK_DELAY = 16, + parameter OUTPUT_DATA_DELAY = 0 +) ( + input rst, + input clk200, + + // Data and frame timing (asynchronous, glitch free) + input a_mimo, // MIMO vs. SISO mode + input a_tx_ch, // Which channel to transmit when MIMO=0 + + // Delay Control Interface + input ctrl_clk, + input [4:0] ctrl_in_data_delay, + input [4:0] ctrl_in_clk_delay, + input ctrl_ld_in_data_delay, + input ctrl_ld_in_clk_delay, + input [4:0] ctrl_out_data_delay, + input [4:0] ctrl_out_clk_delay, + input ctrl_ld_out_data_delay, + input ctrl_ld_out_clk_delay, + + // Baseband sample interface + output radio_clk, + // + output reg rx_aligned, + output reg [11:0] rx_i0, + output reg [11:0] rx_q0, + output reg [11:0] rx_i1, + output reg [11:0] rx_q1, + // + input [11:0] tx_i0, + input [11:0] tx_q0, + input [11:0] tx_i1, + input [11:0] tx_q1, + + // Catalina LVDS interface + input rx_clk_p, + input rx_clk_n, + input rx_frame_p, + input rx_frame_n, + input [5:0] rx_d_p, + input [5:0] rx_d_n, + // + output tx_clk_p, + output tx_clk_n, + output tx_frame_p, + output tx_frame_n, + output [5:0] tx_d_p, + output [5:0] tx_d_n +); + + wire radio_clk_1x; // rx_clk_p divided by 4 + wire radio_clk_2x; // rx_clk_p divided by 2 + + + //--------------------------------------------------------------------------- + // Mode Selection + //--------------------------------------------------------------------------- + + wire r_mimo; + wire r_tx_ch; + + // Double synchronize the MIMO signal + synchronizer mimo_sync ( + .clk(radio_clk_1x), + .rst(1'b0), + .in(a_mimo), + .out(r_mimo)); + + // Double synchronize the Tx channel signal + synchronizer tx_ch_sync ( + .clk(radio_clk_1x), + .rst(1'b0), + .in(a_tx_ch), + .out(r_tx_ch)); + + + //--------------------------------------------------------------------------- + // Clock Mux + //--------------------------------------------------------------------------- + + // Use radio_clk_1x when MIMO = 1, radio_clk_2x when MIMO = 0 + BUFGCTRL BUFGCTRL_radio_clk ( + .I0 (radio_clk_1x), + .I1 (radio_clk_2x), + .S0 (r_mimo), + .S1 (~r_mimo), + .CE0 (1), + .CE1 (1), + .O (radio_clk), + .IGNORE0 (0), + .IGNORE1 (0) + ); + + + //--------------------------------------------------------------------------- + // Generate Alignment Strobes + //--------------------------------------------------------------------------- + // + // The LVDS input logic generates the following two clocks: + // + // radio_clk_1x |‾‾‾‾‾|_____|‾‾‾‾‾|_____|‾‾‾‾‾|_____|‾‾‾‾‾|_____|‾‾‾‾‾| + // + // radio_clk_2x |‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__|‾‾|__| + // + // + // Using simple logic, we create the following two signals from these clocks: + // + // align_1x |‾‾‾‾‾‾‾‾‾‾‾|___________|‾‾‾‾‾‾‾‾‾‾‾|___________|‾‾‾‾‾‾ + // + // align_2x ______|‾‾‾‾‾‾‾‾‾‾‾|___________|‾‾‾‾‾‾‾‾‾‾‾|___________| + // + // These two alignment signals allow us to tell where in the frame period we + // are so that we can deserialize in the correct order. + // + //--------------------------------------------------------------------------- + + reg align_1x = 0; + reg align_2x = 0; + + always @(posedge radio_clk_1x) + begin + align_1x <= ~align_1x; + end + + always @(posedge radio_clk_2x) + begin + // Align data capture to 1x clock so that we stay in sync with data. + // Otherwise, the data might be serialized in the wrong order. + align_2x <= align_1x; + end + + + //--------------------------------------------------------------------------- + // Rx MIMO/SISO Serialization + //--------------------------------------------------------------------------- + // + // This block of code takes the dual outputs when in SISO mode and serializes + // them. Because we use the 2x clock when in SISO mode, this allows us to + // double the data rate when using a single channel. + // + //--------------------------------------------------------------------------- + + reg [11:0] rx_i0_ser; + reg [11:0] rx_q0_ser; + reg [11:0] rx_i1_ser; + reg [11:0] rx_q1_ser; + + reg [11:0] rx_i0_out; + reg [11:0] rx_q0_out; + reg [11:0] rx_i1_out; + reg [11:0] rx_q1_out; + + always @(posedge radio_clk_2x) + begin + rx_aligned <= rx_aligned_t; + + if (align_1x ^ align_2x) begin + // This clock cycle corresponds to the first 1x cycle in which two + // samples are output, so grab data from port 0. + rx_i0_ser <= rx_i0_t; + rx_q0_ser <= rx_q0_t; + rx_i1_ser <= rx_i0_t; + rx_q1_ser <= rx_q0_t; + end else begin + // This radio_clk_2x cycle corresponds to the second 1x cycle in which + // two samples are output, so grab data from port 1. + rx_i0_ser <= rx_i1_t; + rx_q0_ser <= rx_q1_t; + rx_i1_ser <= rx_i1_t; + rx_q1_ser <= rx_q1_t; + end + + // Select the correct Rx output based on MIMO setting + if (r_mimo) begin + rx_i0_out <= rx_i0_t; + rx_q0_out <= rx_q0_t; + rx_i1_out <= rx_i1_t; + rx_q1_out <= rx_q1_t; + end else begin + rx_i0_out <= rx_i0_ser; + rx_q0_out <= rx_q0_ser; + rx_i1_out <= rx_i1_ser; + rx_q1_out <= rx_q1_ser; + end + end + + + //--------------------------------------------------------------------------- + // Synchronize Rx to radio_clk Domain + //--------------------------------------------------------------------------- + // + // This crosses the radio data from the radio_clk_1x domain to the radio_clk + // domain. We use the falling edge of radio_clk to allow for the BUFG + // insertion delay. + // + //--------------------------------------------------------------------------- + + reg [11:0] rx_i0_fall; + reg [11:0] rx_q0_fall; + reg [11:0] rx_i1_fall; + reg [11:0] rx_q1_fall; + + always @(negedge radio_clk) + begin + rx_i0_fall <= rx_i0_out; + rx_q0_fall <= rx_q0_out; + rx_i1_fall <= rx_i1_out; + rx_q1_fall <= rx_q1_out; + end + + // Re-clock data on the rising edge to present the whole period to external IP + always @(posedge radio_clk) + begin + rx_i0 <= rx_i0_fall; + rx_q0 <= rx_q0_fall; + rx_i1 <= rx_i1_fall; + rx_q1 <= rx_q1_fall; + end + + + //--------------------------------------------------------------------------- + // Tx MIMO/SISO Deserialization + //--------------------------------------------------------------------------- + // + // This block of code takes the serialized output from the radios and + // parallelizes it onto the two radio ports of the Catalina interface. It + // also takes the radio data, output on the radio_clk domain, and crosses it + // to the radio_clk_1x domain. + // + //--------------------------------------------------------------------------- + + reg [11:0] tx_i0_del; + reg [11:0] tx_q0_del; + reg [11:0] tx_i1_del; + reg [11:0] tx_q1_del; + + always @(posedge radio_clk_2x) + begin + // Capture copy of the data delayed by one radio_clk_2c cycle. + tx_i0_del <= tx_i0; + tx_q0_del <= tx_q0; + tx_i1_del <= tx_i1; + tx_q1_del <= tx_q1; + end + + always @(posedge radio_clk_1x) + begin + if (r_mimo) begin + // In MIMO mode, radio_clk is radio_clk_1x, so we just capture the same + // data for each radio_clk_1x cycle. + tx_i0_t <= tx_i0; + tx_q0_t <= tx_q0; + tx_i1_t <= tx_i1; + tx_q1_t <= tx_q1; + end else begin + // In SISO mode, data is updated every radio_clk_2x cycle, so we output + // the data from the previous radio_clk_2x cycle onto channel 0 and the + // data from the current radio_clk_2x cycle onto channel 1. This puts the + // data in the correct order when in 1R1T mode. + if (r_tx_ch == 0) begin + tx_i0_t <= tx_i0_del; + tx_q0_t <= tx_q0_del; + tx_i1_t <= tx_i0; + tx_q1_t <= tx_q0; + end else begin + tx_i0_t <= tx_i1_del; + tx_q0_t <= tx_q1_del; + tx_i1_t <= tx_i1; + tx_q1_t <= tx_q1; + end + end + end + + + //--------------------------------------------------------------------------- + // Catalina TX/RX Interface + //--------------------------------------------------------------------------- + + wire rx_aligned_t; + wire [11:0] rx_i0_t; + wire [11:0] rx_q0_t; + wire [11:0] rx_i1_t; + wire [11:0] rx_q1_t; + + reg [11:0] tx_i0_t; + reg [11:0] tx_q0_t; + reg [11:0] tx_i1_t; + reg [11:0] tx_q1_t; + + cat_io_lvds #( + .INVERT_FRAME_RX (0), + .INVERT_DATA_RX (6'b00_0000), + .INVERT_FRAME_TX (0), + .INVERT_DATA_TX (6'b00_0000), + .USE_CLOCK_IDELAY (USE_CLOCK_IDELAY), + .USE_DATA_IDELAY (USE_DATA_IDELAY), + .DATA_IDELAY_MODE (DATA_IDELAY_MODE), + .CLOCK_IDELAY_MODE (CLOCK_IDELAY_MODE), + .INPUT_CLOCK_DELAY (INPUT_CLOCK_DELAY), + .INPUT_DATA_DELAY (INPUT_DATA_DELAY), + .USE_CLOCK_ODELAY (USE_CLOCK_ODELAY), + .USE_DATA_ODELAY (USE_DATA_ODELAY), + .DATA_ODELAY_MODE (DATA_ODELAY_MODE), + .CLOCK_ODELAY_MODE (CLOCK_ODELAY_MODE), + .OUTPUT_CLOCK_DELAY (OUTPUT_CLOCK_DELAY), + .OUTPUT_DATA_DELAY (OUTPUT_DATA_DELAY), + .USE_BUFG (0) + ) cat_io_lvds_i0 ( + .rst (rst), + .clk200 (clk200), + + // Data and frame timing + .mimo (1), // Set to 1 to always return all samples + .frame_sample (~r_mimo), // Frame timing corresponds to SISO/MIMO setting + + // Delay control interface + .ctrl_clk (ctrl_clk), + // + .ctrl_in_data_delay (ctrl_in_data_delay), + .ctrl_in_clk_delay (ctrl_in_clk_delay), + .ctrl_ld_in_data_delay (ctrl_ld_in_data_delay), + .ctrl_ld_in_clk_delay (ctrl_ld_in_clk_delay), + // + .ctrl_out_data_delay (ctrl_out_data_delay), + .ctrl_out_clk_delay (ctrl_out_clk_delay), + .ctrl_ld_out_data_delay (ctrl_ld_out_data_delay), + .ctrl_ld_out_clk_delay (ctrl_ld_out_clk_delay), + + // Baseband sample interface + .radio_clk (radio_clk_1x), + .radio_clk_2x (radio_clk_2x), + .rx_aligned (rx_aligned_t), + // + .rx_i0 (rx_i0_t), + .rx_q0 (rx_q0_t), + .rx_i1 (rx_i1_t), + .rx_q1 (rx_q1_t), + // + .tx_i0 (tx_i0_t), + .tx_q0 (tx_q0_t), + .tx_i1 (tx_i1_t), + .tx_q1 (tx_q1_t), + + // Catalina interface + .rx_clk_p (rx_clk_p), + .rx_clk_n (rx_clk_n), + .rx_frame_p (rx_frame_p), + .rx_frame_n (rx_frame_n), + .rx_d_p (rx_d_p), + .rx_d_n (rx_d_n), + // + .tx_clk_p (tx_clk_p), + .tx_clk_n (tx_clk_n), + .tx_frame_p (tx_frame_p), + .tx_frame_n (tx_frame_n), + .tx_d_p (tx_d_p), + .tx_d_n (tx_d_n) + ); + +endmodule // cat_io_lvds_dual_mode diff --git a/fpga/usrp3/lib/io_cap_gen/cat_output_lvds.v b/fpga/usrp3/lib/io_cap_gen/cat_output_lvds.v new file mode 100644 index 000000000..2c412d7f3 --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/cat_output_lvds.v @@ -0,0 +1,396 @@ +// +// Copyright 2016 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: cat_output_lvds +// Description: Transmit interface to AD9361 in LVDS mode. +// +// The frame_sample signal controls the expected frame signal timing. When +// frame_sample is 0, the period of the ddr_frame signal will be equal to two +// samples (e.g., one from each channel). When frame_sample is 1, the frame +// period will be equal to the length of one sample. This allows the module to +// be used for 2R2T (frame_sample = 1) or 1R1T mode (frame_sample = 0). + + +module cat_output_lvds #( + parameter INVERT_FRAME_TX = 0, + parameter INVERT_DATA_TX = 6'b00_0000, + parameter USE_CLOCK_DELAY = 1, + parameter USE_DATA_DELAY = 1, + parameter CLOCK_DELAY_MODE = "VAR_LOAD", + parameter DATA_DELAY_MODE = "VAR_LOAD", + parameter CLOCK_DELAY = 0, + parameter DATA_DELAY = 0, + parameter WIDTH = 6, + parameter GROUP = "DEFAULT" +) ( + input clk200, + input rst, + + // Data and frame timing (synchronous to radio_clk) + input frame_sample, // Two samples per frame period (frame_sample=0) or one sample per frame (frame_sample=1) + + // Region local Clocks for I/O cells. + input ddr_clk, + input sdr_clk, + + // Source synchronous external input clock + output ddr_clk_p, + output ddr_clk_n, + + // Source synchronous data lines + output [WIDTH-1:0] ddr_data_p, + output [WIDTH-1:0] ddr_data_n, + output ddr_frame_p, + output ddr_frame_n, + + // Delay control interface + input ctrl_clk, + input [4:0] ctrl_data_delay, + input [4:0] ctrl_clk_delay, + input ctrl_ld_data_delay, + input ctrl_ld_clk_delay, + + // Global input clock + input radio_clk, + + // SDR data buses + input [(WIDTH*2)-1:0] i0, + input [(WIDTH*2)-1:0] q0, + input [(WIDTH*2)-1:0] i1, + input [(WIDTH*2)-1:0] q1 + +); + + + //------------------------------------------------------------------ + // UG471 says take reset high asynchronously, and de-assert + // synchronized to CLKDIV (sdr_clk) for SERDES. + //------------------------------------------------------------------ + reg rst_sdr_sync; + + always @(posedge sdr_clk or posedge rst) + if (rst) + rst_sdr_sync <= 1'b1; + else + rst_sdr_sync <= 1'b0; + + //------------------------------------------------------------------ + // Route radio data to SERDES for SISO and MIMO modes. + //------------------------------------------------------------------ + reg [(WIDTH*2)-1:0] radio_data_i0, radio_data_q0; + reg [(WIDTH*2)-1:0] radio_data_i1, radio_data_q1; + reg [(WIDTH*2)-1:0] data_i0, data_q0, data_i1, data_q1; + // + // + always @(posedge radio_clk) + begin + // When in 2R2T mode, data mapping is the same for SISO and MIMO. The data + // for channel 1 will be ignored. + radio_data_i0 <= i0; + radio_data_q0 <= q0; + radio_data_i1 <= i1; + radio_data_q1 <= q1; + end + + // + // Cross data into sdr_clock domain. + // sdr_clock leads radio_clk in phase by insertion delay of BUFG. + // We can transfer data in this direction with no special logic. + // Path length must be radio_clk period - BUFG delay to make timing. + // + always @(posedge sdr_clk) + begin + data_i0 <= radio_data_i0 ^ {INVERT_DATA_TX,INVERT_DATA_TX}; + data_q0 <= radio_data_q0 ^ {INVERT_DATA_TX,INVERT_DATA_TX}; + data_i1 <= radio_data_i1 ^ {INVERT_DATA_TX,INVERT_DATA_TX}; + data_q1 <= radio_data_q1 ^ {INVERT_DATA_TX,INVERT_DATA_TX}; + end + + //------------------------------------------------------------------ + // Clock output + //------------------------------------------------------------------ + wire ddr_clk_out, ddr_clk_dly; + + OSERDESE2 #( + .DATA_RATE_OQ ("DDR"), // DDR, SDR + .DATA_RATE_TQ ("DDR"), // DDR, BUF, SDR + .DATA_WIDTH (8), // Parallel data width (2-8,10,14) + .INIT_OQ (1'b0), // Initial value of OQ output (1'b0,1'b1) + .INIT_TQ (1'b0), // Initial value of TQ output (1'b0,1'b1) + .SERDES_MODE ("MASTER"), // MASTER, SLAVE + .SRVAL_OQ (1'b0), // OQ output value when SR is used (1'b0,1'b1) + .SRVAL_TQ (1'b0), // TQ output value when SR is used (1'b0,1'b1) + .TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE) + .TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE) + .TRISTATE_WIDTH (1) // 3-state converter width (1,4) + ) ddr_clk_oserdese2 ( + .OFB (), // High-speed data output to ODELAYE2 + .OQ (ddr_clk_out), // High-speed data output direct to OBUF + // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each) + .SHIFTOUT1 (), + .SHIFTOUT2 (), + .TBYTEOUT (), + .TFB (), + .TQ (), + .CLK (ddr_clk), + .CLKDIV (sdr_clk), + // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each) + .D1 (1'b1), // Canned Clock waveform synthesized as data. + .D2 (1'b0), + .D3 (1'b1), + .D4 (1'b0), + .D5 (1'b1), + .D6 (1'b0), + .D7 (1'b1), + .D8 (1'b0), + .OCE (1'b1), // Active high clock enable + .RST (rst_sdr_sync), + // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each) + .SHIFTIN1 (1'b0), + .SHIFTIN2 (1'b0), + // T1 - T4: 1-bit (each) input: Parallel 3-state inputs + .T1 (1'b0), + .T2 (1'b0), + .T3 (1'b0), + .T4 (1'b0), + .TBYTEIN (1'b0), + .TCE (1'b0) + ); + + generate + if (USE_CLOCK_DELAY) begin : gen_clock_odelay + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + ODELAYE2 #( + .CINVCTRL_SEL ("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) + .DELAY_SRC ("ODATAIN"), // Delay input (ODATAIN, CLKIN) + .HIGH_PERFORMANCE_MODE ("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") + .ODELAY_TYPE (CLOCK_DELAY_MODE), // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE + .ODELAY_VALUE (CLOCK_DELAY), // Output delay tap setting (0-31) + .PIPE_SEL ("FALSE"), // Select pipelined mode, FALSE, TRUE + .REFCLK_FREQUENCY (200.0), // IDELAYCTRL clock input frequency in MHz (190.0-210.0, 290.0-310.0). + .SIGNAL_PATTERN ("CLOCK") // DATA, CLOCK input signal + ) ddr_clk_odelaye2 ( + .CNTVALUEOUT (), // 5-bit output: Counter value output + .DATAOUT (ddr_clk_dly), // 1-bit output: Delayed data/clock output + .C (ctrl_clk), // 1-bit input: Clock input + .CE (1'b0), // 1-bit input: Active high enable increment/decrement input + .CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input + .CLKIN (1'b0), // 1-bit input: Clock delay input + .CNTVALUEIN (ctrl_clk_delay), // 5-bit input: Counter value input + .INC (1'b0), // 1-bit input: Increment / Decrement tap delay input + .LD (ctrl_ld_clk_delay), // 1-bit input: Loads ODELAY_VALUE tap delay in VARIABLE mode, in VAR_LOAD or + // VAR_LOAD_PIPE mode, loads the value of CNTVALUEIN + .LDPIPEEN (1'b0), // 1-bit input: Enables the pipeline register to load data + .ODATAIN (ddr_clk_out), // 1-bit input: Output delay data input + .REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input + ); + end else begin + assign ddr_clk_dly = ddr_clk_out; + end + endgenerate + + + + OBUFDS ddr_clk_obuf ( + .O (ddr_clk_p), // Diff_p output (connect directly to top-level port) + .OB (ddr_clk_n), // Diff_n output (connect directly to top-level port) + .I (ddr_clk_dly) // Buffer input + ); + + + + //------------------------------------------------------------------ + // Frame Signal + //------------------------------------------------------------------ + wire ddr_frame, ddr_frame_dly; + + OSERDESE2 #( + .DATA_RATE_OQ ("DDR"), // DDR, SDR + .DATA_RATE_TQ ("DDR"), // DDR, BUF, SDR + .DATA_WIDTH (8), // Parallel data width (2-8,10,14) + .INIT_OQ (1'b0), // Initial value of OQ output (1'b0,1'b1) + .INIT_TQ (1'b0), // Initial value of TQ output (1'b0,1'b1) + .SERDES_MODE ("MASTER"), // MASTER, SLAVE + .SRVAL_OQ (1'b0), // OQ output value when SR is used (1'b0,1'b1) + .SRVAL_TQ (1'b0), // TQ output value when SR is used (1'b0,1'b1) + .TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE) + .TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE) + .TRISTATE_WIDTH (1) // 3-state converter width (1,4) + ) ddr_frame_oserdese2 ( + .OFB (), // High-speed data output to ODELAYE2 + .OQ (ddr_frame), // High-speed data output direct to OBUF + // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each) + .SHIFTOUT1 (), + .SHIFTOUT2 (), + .TBYTEOUT (), + .TFB (), + .TQ (), + .CLK (ddr_clk), + .CLKDIV (sdr_clk), + // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each). Frame is + // either 11110000 or 11001100 depending on if frame_sample is true or + // false, respectively, and it can be inverted by INVERT_FRAME_TX, becoming + // 00001111 or 00110011. + .D1 (~INVERT_FRAME_TX[0]), + .D2 (~INVERT_FRAME_TX[0]), + .D3 (INVERT_FRAME_TX[0] ~^ frame_sample), + .D4 (INVERT_FRAME_TX[0] ~^ frame_sample), + .D5 (INVERT_FRAME_TX[0] ^ frame_sample), + .D6 (INVERT_FRAME_TX[0] ^ frame_sample), + .D7 (INVERT_FRAME_TX[0]), + .D8 (INVERT_FRAME_TX[0]), + .OCE (1'b1), // Active high clock enable + .RST (rst_sdr_sync), + // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each) + .SHIFTIN1 (1'b0), + .SHIFTIN2 (1'b0), + // T1 - T4: 1-bit (each) input: Parallel 3-state inputs + .T1 (1'b0), + .T2 (1'b0), + .T3 (1'b0), + .T4 (1'b0), + .TBYTEIN (1'b0), + .TCE (1'b0) + ); + + generate + if (USE_DATA_DELAY) begin : gen_frame_odelay + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + ODELAYE2 #( + .CINVCTRL_SEL ("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) + .DELAY_SRC ("ODATAIN"), // Delay input (ODATAIN, CLKIN) + .HIGH_PERFORMANCE_MODE ("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") + .ODELAY_TYPE (DATA_DELAY_MODE), // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE + .ODELAY_VALUE (DATA_DELAY), // Output delay tap setting (0-31) + .PIPE_SEL ("FALSE"), // Select pipelined mode, FALSE, TRUE + .REFCLK_FREQUENCY (200.0), // IDELAYCTRL clock input frequency in MHz (190.0-210.0, 290.0-310.0). + .SIGNAL_PATTERN ("DATA") // DATA, CLOCK input signal + ) ddr_frame_odelaye2 ( + .CNTVALUEOUT (), // 5-bit output: Counter value output + .DATAOUT (ddr_frame_dly), // 1-bit output: Delayed data/clock output + .C (ctrl_clk), // 1-bit input: Clock input + .CE (1'b0), // 1-bit input: Active high enable increment/decrement input + .CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input + .CLKIN (1'b0), // 1-bit input: Clock delay input + .CNTVALUEIN (ctrl_data_delay), // 5-bit input: Counter value input + .INC (1'b0), // 1-bit input: Increment / Decrement tap delay input + .LD (ctrl_ld_data_delay), // 1-bit input: Loads ODELAY_VALUE tap delay in VARIABLE mode, in VAR_LOAD or + // VAR_LOAD_PIPE mode, loads the value of CNTVALUEIN + .LDPIPEEN (1'b0), // 1-bit input: Enables the pipeline register to load data + .ODATAIN (ddr_frame), // 1-bit input: Output delay data input + .REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input + ); + end else begin + assign ddr_frame_dly = ddr_frame; + end + endgenerate + + OBUFDS ddr_frame_obuf ( + .O (ddr_frame_p), // Diff_p output (connect directly to top-level port) + .OB (ddr_frame_n), // Diff_n output (connect directly to top-level port) + .I (ddr_frame_dly) // Buffer input + ); + + + //------------------------------------------------------------------ + // Data Bus + //------------------------------------------------------------------ + wire [WIDTH-1:0] ddr_data; + wire [WIDTH-1:0] ddr_data_dly ; + + + // wire [(WIDTH*2)-1:0] sdr_data_i; + // wire [(WIDTH*2)-1:0] sdr_data_q; + + genvar i; + generate + for (i=0 ; i<WIDTH ; i=i+1) begin : generate_data_bus + + OSERDESE2 #( + .DATA_RATE_OQ ("DDR"), // DDR, SDR + .DATA_RATE_TQ ("DDR"), // DDR, BUF, SDR + .DATA_WIDTH (8), // Parallel data width (2-8,10,14) + .INIT_OQ (1'b0), // Initial value of OQ output (1'b0,1'b1) + .INIT_TQ (1'b0), // Initial value of TQ output (1'b0,1'b1) + .SERDES_MODE ("MASTER"), // MASTER, SLAVE + .SRVAL_OQ (1'b0), // OQ output value when SR is used (1'b0,1'b1) + .SRVAL_TQ (1'b0), // TQ output value when SR is used (1'b0,1'b1) + .TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE) + .TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE) + .TRISTATE_WIDTH (1) // 3-state converter width (1,4) + ) ddr_data_oserdese2 ( + .OFB (), // High-speed data output to ODELAYE2 + .OQ (ddr_data[i]), // High-speed data output direct to OBUF + // SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each) + .SHIFTOUT1 (), + .SHIFTOUT2 (), + .TBYTEOUT (), + .TFB (), + .TQ (), + .CLK (ddr_clk), + .CLKDIV (sdr_clk), + // D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each) + .D1 (data_i0[WIDTH+i]), + .D2 (data_q0[WIDTH+i]), + .D3 (data_i0[i]), + .D4 (data_q0[i]), + .D5 (data_i1[WIDTH+i]), + .D6 (data_q1[WIDTH+i]), + .D7 (data_i1[i]), + .D8 (data_q1[i]), + .OCE (1'b1), // Active high clock enable + .RST (rst_sdr_sync), + // SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each) + .SHIFTIN1 (1'b0), + .SHIFTIN2 (1'b0), + // T1 - T4: 1-bit (each) input: Parallel 3-state inputs + .T1 (1'b0), + .T2 (1'b0), + .T3 (1'b0), + .T4 (1'b0), + .TBYTEIN (1'b0), + .TCE (1'b0) + ); + + if (USE_DATA_DELAY) begin : gen_data_odelay + (* IODELAY_GROUP = GROUP *) // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL + ODELAYE2 #( + .CINVCTRL_SEL ("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE) + .DELAY_SRC ("ODATAIN"), // Delay input (ODATAIN, CLKIN) + .HIGH_PERFORMANCE_MODE ("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE") + .ODELAY_TYPE (DATA_DELAY_MODE), // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE + .ODELAY_VALUE (DATA_DELAY), // Output delay tap setting (0-31) + .PIPE_SEL ("FALSE"), // Select pipelined mode, FALSE, TRUE + .REFCLK_FREQUENCY (200.0), // IDELAYCTRL clock input frequency in MHz (190.0-210.0, 290.0-310.0). + .SIGNAL_PATTERN ("DATA") // DATA, CLOCK input signal + ) ddr_data_odelaye2 ( + .CNTVALUEOUT (), // 5-bit output: Counter value output + .DATAOUT (ddr_data_dly[i]), // 1-bit output: Delayed data/clock output + .C (ctrl_clk), // 1-bit input: Clock input + .CE (1'b0), // 1-bit input: Active high enable increment/decrement input + .CINVCTRL (1'b0), // 1-bit input: Dynamic clock inversion input + .CLKIN (1'b0), // 1-bit input: Clock delay input + .CNTVALUEIN (ctrl_data_delay), // 5-bit input: Counter value input + .INC (1'b0), // 1-bit input: Increment / Decrement tap delay input + .LD (ctrl_ld_data_delay), // 1-bit input: Loads ODELAY_VALUE tap delay in VARIABLE mode, in VAR_LOAD or + // VAR_LOAD_PIPE mode, loads the value of CNTVALUEIN + .LDPIPEEN (1'b0), // 1-bit input: Enables the pipeline register to load data + .ODATAIN (ddr_data[i]), // 1-bit input: Output delay data input + .REGRST (1'b0) // 1-bit input: Active-high reset tap-delay input + ); + end else begin + assign ddr_data_dly[i] = ddr_data[i]; + end + + OBUFDS ddr_data_obuf ( + .O (ddr_data_p[i]), // Diff_p output (connect directly to top-level port) + .OB (ddr_data_n[i]), // Diff_n output (connect directly to top-level port) + .I (ddr_data_dly[i]) // Buffer input + ); + end // block: generate_data_bus + + endgenerate + +endmodule // cat_output_lvds diff --git a/fpga/usrp3/lib/io_cap_gen/catcap_ddr_cmos.v b/fpga/usrp3/lib/io_cap_gen/catcap_ddr_cmos.v new file mode 100644 index 000000000..bb4c77f0d --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/catcap_ddr_cmos.v @@ -0,0 +1,95 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module catcap_ddr_cmos +#( + parameter DEVICE = "7SERIES" // "7SERIES" or "SPARTAN6" +) +( + input data_clk, + input mimo, + input rx_frame, + input [11:0] rx_d, + output rx_clk, output rx_strobe, + output reg [11:0] i0, output reg [11:0] q0, + output reg [11:0] i1, output reg [11:0] q1 +); + + wire [11:0] i,q; + wire frame_0, frame_1; + + genvar z; + + generate + for(z = 0; z < 12; z = z + 1) + begin : gen_pins + if (DEVICE == "SPARTAN6") begin + // i[] & q[] swapped + IDDR2 #( + .DDR_ALIGNMENT("C0")) + iddr2 ( + .Q0(q[z]), .Q1(i[z]), .C0(data_clk), .C1(~data_clk), + .CE(1'b1), .D(rx_d[z]), .R(1'b0), .S(1'b0)); + end + else if (DEVICE == "7SERIES") begin + IDDR #( + .DDR_CLK_EDGE("SAME_EDGE")) + iddr ( + .Q1(q[z]), .Q2(i[z]), .C(data_clk), + .CE(1'b1), .D(rx_d[z]), .R(1'b0), .S(1'b0)); + end + end + endgenerate + + generate + if (DEVICE == "SPARTAN6") begin + IDDR2 #( + .DDR_ALIGNMENT("C0")) + iddr2_frame ( + .Q0(frame_0), .Q1(frame_1), .C0(data_clk), .C1(~data_clk), + .CE(1'b1), .D(rx_frame), .R(1'b0), .S(1'b0)); + end + else if (DEVICE == "7SERIES") begin + IDDR #( + .DDR_CLK_EDGE("SAME_EDGE")) + iddr_frame ( + .Q1(frame_0), .Q2(frame_1), .C(data_clk), + .CE(1'b1), .D(rx_frame), .R(1'b0), .S(1'b0)); + end + endgenerate + + reg frame_d1, frame_d2; + always @(posedge data_clk) + if(~mimo) + { frame_d2, frame_d1 } <= { frame_1, 1'b0 }; + else + { frame_d2, frame_d1 } <= { frame_d1, frame_1 }; + + assign rx_strobe = frame_d2; + + reg [11:0] i_del, q_del; + always @(posedge data_clk) + if(mimo) + if(frame_0) begin + i_del <= i; + q_del <= q; + end + else begin + i1 <= i; + q1 <= q; + i0 <= i_del; + q0 <= q_del; + end + else begin + i0 <= i; + q0 <= q; + i1 <= i; + q1 <= q; + end + assign rx_clk = data_clk; + +endmodule // catcap_ddr_cmos diff --git a/fpga/usrp3/lib/io_cap_gen/catcodec_ddr_cmos.v b/fpga/usrp3/lib/io_cap_gen/catcodec_ddr_cmos.v new file mode 100644 index 000000000..44aa4d8f9 --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/catcodec_ddr_cmos.v @@ -0,0 +1,123 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module catcodec_ddr_cmos +#( + parameter DEVICE = "SPARTAN6" // "7SERIES" or "SPARTAN6", determines device specific implementation of clock divider +) +( + //output source sync clock for baseband data + output radio_clk, + + //async reset for clocking + input arst, + + //control mimo mode + input mimo, + + //baseband sample interface + output reg [31:0] rx1, + output reg [31:0] rx2, + input [31:0] tx1, + input [31:0] tx2, + + //capture interface + input rx_clk, + input rx_frame, + input [11:0] rx_d, + + //generate interface + output tx_clk, + output tx_frame, + output [11:0] tx_d +); + + wire clk0, clkdv; + wire codec_clk, half_clk; + wire radio_clk_locked; + + // Synchronize MIMO signal into codec_clk domain + wire mimo_r; + synchronizer mimo_sync ( + .clk(codec_clk), + .rst(1'b0), + .in(mimo), + .out(mimo_r)); + + generate + if (DEVICE == "SPARTAN6") begin + DCM_SP #( + .CLKDV_DIVIDE(2), + .CLK_FEEDBACK("1X")) + DCM_SP_codec_clk ( + .RST(arst), + .CLKIN(rx_clk), .CLKFB(clk0), + .CLK0(clk0), .CLKDV(clkdv), + .LOCKED(radio_clk_locked)); + BUFG BUFG_codec_clk(.I(clk0), .O(codec_clk)); + BUFG BUFG_half_clk(.I(clkdv), .O(half_clk)); + BUFGMUX BUFGMUX_radio_clk (.I0(codec_clk), .I1(half_clk), .S(mimo_r), .O(radio_clk)); + end + else if (DEVICE == "7SERIES") begin + wire rx_clk_ibufg, clkfb_out, clkfb_in; + // Create clocks for source synchronous interface + // Goal is to create a capture clock (codec_clk) and a sample clock (radio_clk). + // - Capture clock's and source clock's (rx_clk) phase are aligned due to + // the MMCM's deskew ability (see the BUFG in the feedback clock path). + // - BUFGCTRL muxes between the 1x and 1/2x clocks depending on MIMO mode. In MIMO mode, the 1/2x + // clock is used, because the sample clock rate is half the source clock rate. + // - Locked signal is used to ensure the BUFG's output is disabled if the MMCM is not locked. + // - Avoided cascading BUFGs to ensure minimal skew between codec_clk and radio_clk. + catcodec_mmcm inst_catcodec_mmcm ( + .CLK_IN1(rx_clk_ibufg), + .CLK_OUT(clk0), + .CLK_OUT_DIV2(clkdv), + .CLKFB_IN(clkfb_in), + .CLKFB_OUT(clkfb_out), + .RESET(arst), + .LOCKED(radio_clk_locked)); + IBUFG (.I(rx_clk), .O(rx_clk_ibufg)); + BUFG (.I(clkfb_out), .O(clkfb_in)); + BUFGCE (.I(clk0), .O(codec_clk), .CE(radio_clk_locked)); + BUFGCTRL BUFGCTRL_radio_clk (.I0(clk0), .I1(clkdv), .S0(~mimo_r), .S1(mimo_r), .CE0(radio_clk_locked), .CE1(radio_clk_locked), .O(radio_clk)); + end + endgenerate + + //assign baseband sample interfaces + //all samples are registered on strobe + wire rx_strobe, tx_strobe; + wire [11:0] rx_i0, rx_q0, rx_i1, rx_q1; + reg [11:0] tx_i0, tx_q0, tx_i1, tx_q1; + //tx mux to feed single channel mode from either input + wire [31:0] txm = (mimo_r || (tx1 != 32'b0))? tx1: tx2; + always @(posedge codec_clk) begin + if (rx_strobe) rx2 <= {rx_i1, 4'b0, rx_q1, 4'b0}; + if (rx_strobe) rx1 <= {rx_i0, 4'b0, rx_q0, 4'b0}; + if (tx_strobe) {tx_i0, tx_q0} <= {txm[31:20], txm[15:4]}; + if (tx_strobe) {tx_i1, tx_q1} <= {tx2[31:20], tx2[15:4]}; + end + + // CMOS Data interface to AD9361 + catcap_ddr_cmos #( + .DEVICE(DEVICE)) + catcap ( + .data_clk(codec_clk), .mimo(mimo_r), + .rx_frame(rx_frame), .rx_d(rx_d), + .rx_clk(/*out*/), .rx_strobe(rx_strobe), + .i0(rx_i0), .q0(rx_q0), + .i1(rx_i1), .q1(rx_q1)); + + catgen_ddr_cmos #( + .DEVICE(DEVICE)) + catgen ( + .data_clk(tx_clk), .mimo(mimo_r), + .tx_frame(tx_frame), .tx_d(tx_d), + .tx_clk(codec_clk), .tx_strobe(tx_strobe), + .i0(tx_i0), .q0(tx_q0), + .i1(tx_i1), .q1(tx_q1)); + +endmodule // catcodec_ddr_cmos diff --git a/fpga/usrp3/lib/io_cap_gen/catgen_ddr_cmos.v b/fpga/usrp3/lib/io_cap_gen/catgen_ddr_cmos.v new file mode 100644 index 000000000..017a2f432 --- /dev/null +++ b/fpga/usrp3/lib/io_cap_gen/catgen_ddr_cmos.v @@ -0,0 +1,90 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module catgen_ddr_cmos +#( + parameter DEVICE = "7SERIES" // "7SERIES" or "SPARTAN6" +) +( + output data_clk, + input mimo, + output tx_frame, + output [11:0] tx_d, + input tx_clk, + output reg tx_strobe, + input [11:0] i0, + input [11:0] q0, + input [11:0] i1, + input [11:0] q1 +); + + reg [11:0] i,q; + genvar z; + reg tx_strobe_d; + + generate + for(z = 0; z < 12; z = z + 1) + begin : gen_pins + if (DEVICE == "SPARTAN6") begin + ODDR2 #( + .DDR_ALIGNMENT("C0"), .SRTYPE("ASYNC")) + oddr2 ( + .Q(tx_d[z]), .C0(tx_clk), .C1(~tx_clk), + .CE(1'b1), .D0(i[z]), .D1(q[z]), .R(1'b0), .S(1'b0)); + end + else if (DEVICE == "7SERIES") begin + ODDR #( + .DDR_CLK_EDGE("SAME_EDGE"), .SRTYPE("ASYNC")) + oddr ( + .Q(tx_d[z]), .C(tx_clk), + .CE(1'b1), .D1(i[z]), .D2(q[z]), .R(1'b0), .S(1'b0)); + end + end + endgenerate + + generate + if (DEVICE == "SPARTAN6") begin + ODDR2 #( + .DDR_ALIGNMENT("C0"), .SRTYPE("ASYNC")) + oddr2_frame ( + .Q(tx_frame), .C0(tx_clk), .C1(~tx_clk), + .CE(1'b1), .D0(tx_strobe_d), .D1(mimo&tx_strobe_d), .R(1'b0), .S(1'b0)); + + ODDR2 #( + .DDR_ALIGNMENT("C0"), .SRTYPE("ASYNC")) + oddr2_clk ( + .Q(data_clk), .C0(tx_clk), .C1(~tx_clk), + .CE(1'b1), .D0(1'b1), .D1(1'b0), .R(1'b0), .S(1'b0)); + end + else if (DEVICE == "7SERIES") begin + ODDR #( + .DDR_CLK_EDGE("SAME_EDGE"), .SRTYPE("ASYNC")) + oddr_frame ( + .Q(tx_frame), .C(tx_clk), + .CE(1'b1), .D1(tx_strobe_d), .D2(mimo&tx_strobe_d), .R(1'b0), .S(1'b0)); + + ODDR #( + .DDR_CLK_EDGE("SAME_EDGE"), .SRTYPE("ASYNC")) + oddr_clk ( + .Q(data_clk), .C(tx_clk), + .CE(1'b1), .D1(1'b1), .D2(1'b0), .R(1'b0), .S(1'b0)); + end + endgenerate + + always @(posedge tx_clk) + tx_strobe <= (mimo)? ~tx_strobe : 1'b1; + + always @(posedge tx_clk) + tx_strobe_d <= tx_strobe; + + always @(posedge tx_clk) + if(tx_strobe) + {i,q} <= {i0,q0}; + else + {i,q} <= {i1,q1}; + +endmodule // catgen_ddr_cmos |