diff options
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 |