// // 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 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