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