module complex_invert
(
  input clk, input reset, input clear,
  input  [31:0] i_tdata, input  i_tlast, input  i_tvalid, output i_tready,
  output [31:0] o_tdata, output o_tlast, output o_tvalid, input  o_tready);

  wire [15:0] a_tdata;
  wire [31:0] a_tdata_int;
  wire        a_tlast;
  wire        a_tvalid;
  wire        a_tready;
  wire [15:0] b_tdata;
  wire [31:0] b_tdata_int;
  wire        b_tlast;
  wire        b_tvalid;
  wire        b_tready;
  wire [31:0] a_b_tdata;
  wire        a_b_tlast;
  wire        a_b_tvalid;
  wire        a_b_tready;

  // Replicate input data into three streams with FIFOing to account for varying latency on the paths
  split_stream_fifo #(
    .WIDTH(32),
    .ACTIVE_MASK(4'b0111),
    .FIFO_SIZE(5))
  input_split_stream_fifo0 (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata(i_tdata), .i_tlast(i_tlast), .i_tvalid(i_tvalid), .i_tready(i_tready),
    .o0_tdata(a_tdata_int), .o0_tlast(a_tlast), .o0_tvalid(a_tvalid), .o0_tready(a_tready),
    .o1_tdata(b_tdata_int), .o1_tlast(b_tlast), .o1_tvalid(b_tvalid), .o1_tready(b_tready),
    .o2_tdata(a_b_tdata), .o2_tlast(a_b_tlast), .o2_tvalid(a_b_tvalid), .o2_tready(a_b_tready),
    .o3_tdata(), .o3_tlast(), .o3_tvalid(), .o3_tready(1'b0));

  assign a_tdata = a_tdata_int[31:16];
  assign b_tdata = b_tdata_int[15:0];

  wire [31:0] a2_plus_b2_tdata;
  wire        a2_plus_b2_tlast;
  wire        a2_plus_b2_tvalid;
  wire        a2_plus_b2_tready;

  // a^2 + b^2
  complex_to_magsq
  a2_p_b2_complex_to_magsq (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata(a_b_tdata), .i_tlast(a_b_tlast), .i_tvalid(a_b_tvalid), .i_tready(a_b_tready),
    .o_tdata(a2_plus_b2_tdata), .o_tlast(a2_plus_b2_tlast), .o_tvalid(a2_plus_b2_tvalid), .o_tready(a2_plus_b2_tready));

  wire [31:0] a2_plus_b2_0_tdata;
  wire        a2_plus_b2_0_tlast;
  wire        a2_plus_b2_0_tvalid;
  wire        a2_plus_b2_0_tready;
  wire [31:0] a2_plus_b2_1_tdata;
  wire        a2_plus_b2_1_tlast;
  wire        a2_plus_b2_1_tvalid;
  wire        a2_plus_b2_1_tready;

  // Replicate two a^2 + b^2 streams for dividers
  split_stream_fifo #(
    .WIDTH(32),
    .ACTIVE_MASK(4'b0011),
    .FIFO_SIZE(5))
  input_split_stream_fifo1 (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata(a2_plus_b2_tdata), .i_tlast(a2_plus_b2_tlast), .i_tvalid(a2_plus_b2_tvalid), .i_tready(a2_plus_b2_tready),
    .o0_tdata(a2_plus_b2_0_tdata), .o0_tlast(a2_plus_b2_0_tlast), .o0_tvalid(a2_plus_b2_0_tvalid), .o0_tready(a2_plus_b2_0_tready),
    .o1_tdata(a2_plus_b2_1_tdata), .o1_tlast(a2_plus_b2_1_tlast), .o1_tvalid(a2_plus_b2_1_tvalid), .o1_tready(a2_plus_b2_1_tready),
    .o2_tdata(), .o2_tlast(), .o2_tvalid(), .o2_tready(1'b0),
    .o3_tdata(), .o3_tlast(), .o3_tvalid(), .o3_tready(1'b0));

  wire        div_by_zero_a;
  wire [47:0] a_div_a2_plus_b2_tdata_int; // signed bit, 15 integer bits, fraction sign bit, 31 fraction
  wire [47:0] a_div_a2_plus_b2_tdata = div_by_zero_a ? 48'd0 : a_div_a2_plus_b2_tdata_int;
  wire        a_div_a2_plus_b2_tlast;
  wire        a_div_a2_plus_b2_tvalid;
  wire        a_div_a2_plus_b2_tready;

  //     a
  // ---------
  // a^2 + b^2
  // Warning: Divider does not sign extend fractional part into the integer part, although we throw away the integer
  //          part so this issue does not affect our design.
  divide_int16_int32
  a_div_a2_plus_b2_divider (
    .aclk(clk), .aresetn(~reset),
    .s_axis_divisor_tdata(a2_plus_b2_0_tdata), .s_axis_divisor_tlast(a2_plus_b2_0_tlast), .s_axis_divisor_tvalid(a2_plus_b2_0_tvalid), .s_axis_divisor_tready(a2_plus_b2_0_tready),
    .s_axis_dividend_tdata(a_tdata), .s_axis_dividend_tlast(a_tlast), .s_axis_dividend_tvalid(a_tvalid), .s_axis_dividend_tready(a_tready),
    .m_axis_dout_tdata(a_div_a2_plus_b2_tdata_int), .m_axis_dout_tlast(a_div_a2_plus_b2_tlast), .m_axis_dout_tvalid(a_div_a2_plus_b2_tvalid), .m_axis_dout_tready(a_div_a2_plus_b2_tready),
    .m_axis_dout_tuser(div_by_zero_a));

  wire [15:0] neg_b_tdata;
  wire        neg_b_tlast;
  wire        neg_b_tvalid;
  wire        neg_b_tready;
  wire [15:0] neg_b = (b_tdata == -16'sd32768) ? 16'sd32767 : (~b_tdata + 1'b1);

  // Negate b
  axi_fifo_flop #(.WIDTH(17))
  neg_b_axi_fifo_flop (
    .clk(clk), .reset(reset), .clear(clear),
    .i_tdata({b_tlast,neg_b}), .i_tvalid(b_tvalid), .i_tready(b_tready),
    .o_tdata({neg_b_tlast,neg_b_tdata}), .o_tvalid(neg_b_tvalid), .o_tready(neg_b_tready),
    .space(), .occupied());

  wire        div_by_zero_b;
  wire [47:0] neg_b_div_a2_plus_b2_tdata_int;
  wire [47:0] neg_b_div_a2_plus_b2_tdata = div_by_zero_b ? 48'd0 : neg_b_div_a2_plus_b2_tdata_int;
  wire        neg_b_div_a2_plus_b2_tlast;
  wire        neg_b_div_a2_plus_b2_tvalid;
  wire        neg_b_div_a2_plus_b2_tready;

  //     bi
  // ---------
  // a^2 + b^2
  divide_int16_int32
  neg_b_div_a2_plus_b2_divider (
    .aclk(clk), .aresetn(~reset),
    .s_axis_divisor_tdata(a2_plus_b2_1_tdata), .s_axis_divisor_tlast(a2_plus_b2_1_tlast), .s_axis_divisor_tvalid(a2_plus_b2_1_tvalid), .s_axis_divisor_tready(a2_plus_b2_1_tready),
    .s_axis_dividend_tdata(neg_b_tdata), .s_axis_dividend_tlast(neg_b_tlast), .s_axis_dividend_tvalid(neg_b_tvalid), .s_axis_dividend_tready(neg_b_tready),
    .m_axis_dout_tdata(neg_b_div_a2_plus_b2_tdata_int), .m_axis_dout_tlast(neg_b_div_a2_plus_b2_tlast), .m_axis_dout_tvalid(neg_b_div_a2_plus_b2_tvalid), .m_axis_dout_tready(neg_b_div_a2_plus_b2_tready),
    .m_axis_dout_tuser(div_by_zero_b));

  // Throw away integer part as the result will always be a fraction due to a^2 + b^2 > a (or b)
  wire [63:0] one_div_a_plus_bi_tdata = {a_div_a2_plus_b2_tdata[31:0],neg_b_div_a2_plus_b2_tdata[31:0]};
  wire        one_div_a_plus_bi_tlast;
  wire        one_div_a_plus_bi_tvalid;
  wire        one_div_a_plus_bi_tready;

  // Join into one word
  axi_join #(
    .INPUTS(2))
  inst_axi_join (
   .i_tlast({a_div_a2_plus_b2_tlast,neg_b_div_a2_plus_b2_tlast}), .i_tvalid({a_div_a2_plus_b2_tvalid,neg_b_div_a2_plus_b2_tvalid}), .i_tready({a_div_a2_plus_b2_tready,neg_b_div_a2_plus_b2_tready}),
   .o_tlast(one_div_a_plus_bi_tlast), .o_tvalid(one_div_a_plus_bi_tvalid), .o_tready(one_div_a_plus_bi_tready));

  // Truncate to a complex int16
  axi_round_and_clip_complex #(
    .WIDTH_IN(32),
    .WIDTH_OUT(16),
    .CLIP_BITS(11), // Calibrated value
    .FIFOSIZE())
  inst_axi_round_and_clip_complex (
    .clk(clk), .reset(reset),
    .i_tdata(one_div_a_plus_bi_tdata), .i_tlast(one_div_a_plus_bi_tlast), .i_tvalid(one_div_a_plus_bi_tvalid), .i_tready(one_div_a_plus_bi_tready),
    .o_tdata(o_tdata), .o_tlast(o_tlast), .o_tvalid(o_tvalid), .o_tready(o_tready));

endmodule