//
// Copyright 2019 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_block_fft
//
// Description:  An FFT block for RFNoC.
//
// Parameters:
//
//   THIS_PORTID   : Control crossbar port to which this block is connected
//   CHDR_W        : AXIS CHDR interface data width
//   MTU           : Maximum transmission unit (i.e., maximum packet size) in
//                   CHDR words is 2**MTU.
//   EN_MAGNITUDE_OUT : CORDIC based magnitude calculation
//   EN_MAGNITUDE_APPROX_OUT : Multipler-less, lower resource usage
//   EN_MAGNITUDE_SQ_OUT : Magnitude squared
//   EN_FFT_SHIFT : Center zero frequency bin
//

module rfnoc_block_fft #(
  parameter THIS_PORTID   = 0,
  parameter CHDR_W        = 64,
  parameter MTU           = 10,

  parameter EN_MAGNITUDE_OUT = 0,        
  parameter EN_MAGNITUDE_APPROX_OUT = 1,
  parameter EN_MAGNITUDE_SQ_OUT = 1,    
  parameter EN_FFT_SHIFT = 1
  )
(
  //---------------------------------------------------------------------------
  // AXIS CHDR Port
  //---------------------------------------------------------------------------

  input wire rfnoc_chdr_clk,
  input wire ce_clk,

  // CHDR inputs from framework
  input  wire [CHDR_W-1:0] s_rfnoc_chdr_tdata,
  input  wire s_rfnoc_chdr_tlast,
  input  wire s_rfnoc_chdr_tvalid,
  output wire s_rfnoc_chdr_tready,

  // CHDR outputs to framework
  output wire [CHDR_W-1:0] m_rfnoc_chdr_tdata,
  output wire m_rfnoc_chdr_tlast,
  output wire m_rfnoc_chdr_tvalid,
  input  wire m_rfnoc_chdr_tready,

  // Backend interface
  input  wire [511:0] rfnoc_core_config,
  output wire [511:0] rfnoc_core_status,

  //---------------------------------------------------------------------------
  // AXIS CTRL Port
  //---------------------------------------------------------------------------

  input wire rfnoc_ctrl_clk,

  // CTRL port requests from framework
  input  wire [31:0] s_rfnoc_ctrl_tdata,
  input  wire        s_rfnoc_ctrl_tlast,
  input  wire        s_rfnoc_ctrl_tvalid,
  output wire        s_rfnoc_ctrl_tready,

  // CTRL port requests to framework
  output wire [31:0] m_rfnoc_ctrl_tdata,
  output wire        m_rfnoc_ctrl_tlast,
  output wire        m_rfnoc_ctrl_tvalid,
  input  wire        m_rfnoc_ctrl_tready
);

  // These are the only supported values for now
  localparam ITEM_W = 32;
  localparam NIPC   = 1;

  `include "../../core/rfnoc_axis_ctrl_utils.vh"

  //---------------------------------------------------------------------------
  // Signal Declarations
  //---------------------------------------------------------------------------

  wire        ctrlport_req_wr;
  wire        ctrlport_req_rd;
  wire [19:0] ctrlport_req_addr;
  wire [31:0] ctrlport_req_data;
  wire        ctrlport_resp_ack;
  wire [31:0] ctrlport_resp_data;

  wire [ITEM_W-1:0] axis_to_fft_tdata;
  wire  axis_to_fft_tlast;
  wire  axis_to_fft_tvalid;
  wire  axis_to_fft_tready;

  wire [ITEM_W-1:0] axis_from_fft_tdata;
  wire  axis_from_fft_tlast;
  wire  axis_from_fft_tvalid;
  wire  axis_from_fft_tready;

  wire [CHDR_W-1:0] m_axis_context_tdata;
  wire [       3:0] m_axis_context_tuser;
  wire [       0:0] m_axis_context_tlast;
  wire [       0:0] m_axis_context_tvalid;
  wire [       0:0] m_axis_context_tready;

  wire [CHDR_W-1:0] s_axis_context_tdata;
  wire [       3:0] s_axis_context_tuser;
  wire [       0:0] s_axis_context_tlast;
  wire [       0:0] s_axis_context_tvalid;
  wire [       0:0] s_axis_context_tready;

  wire ce_rst;


  //---------------------------------------------------------------------------
  // NoC Shell
  //---------------------------------------------------------------------------

  noc_shell_fft #(
    .THIS_PORTID (THIS_PORTID),
    .CHDR_W      (CHDR_W     ),
    .MTU         (MTU        )
  ) noc_shell_fft_i (
    .rfnoc_chdr_clk         (rfnoc_chdr_clk       ),
    .rfnoc_ctrl_clk         (rfnoc_ctrl_clk       ),
    .ce_clk                 (ce_clk               ),
    .rfnoc_chdr_rst         (                     ),
    .rfnoc_ctrl_rst         (                     ),
    .ce_rst                 (ce_rst               ),
    .rfnoc_core_config      (rfnoc_core_config    ),
    .rfnoc_core_status      (rfnoc_core_status    ),
    .s_rfnoc_chdr_tdata     (s_rfnoc_chdr_tdata   ),
    .s_rfnoc_chdr_tlast     (s_rfnoc_chdr_tlast   ),
    .s_rfnoc_chdr_tvalid    (s_rfnoc_chdr_tvalid  ),
    .s_rfnoc_chdr_tready    (s_rfnoc_chdr_tready  ),
    .m_rfnoc_chdr_tdata     (m_rfnoc_chdr_tdata   ),
    .m_rfnoc_chdr_tlast     (m_rfnoc_chdr_tlast   ),
    .m_rfnoc_chdr_tvalid    (m_rfnoc_chdr_tvalid  ),
    .m_rfnoc_chdr_tready    (m_rfnoc_chdr_tready  ),
    .s_rfnoc_ctrl_tdata     (s_rfnoc_ctrl_tdata   ),
    .s_rfnoc_ctrl_tlast     (s_rfnoc_ctrl_tlast   ),
    .s_rfnoc_ctrl_tvalid    (s_rfnoc_ctrl_tvalid  ),
    .s_rfnoc_ctrl_tready    (s_rfnoc_ctrl_tready  ),
    .m_rfnoc_ctrl_tdata     (m_rfnoc_ctrl_tdata   ),
    .m_rfnoc_ctrl_tlast     (m_rfnoc_ctrl_tlast   ),
    .m_rfnoc_ctrl_tvalid    (m_rfnoc_ctrl_tvalid  ),
    .m_rfnoc_ctrl_tready    (m_rfnoc_ctrl_tready  ),
    .ctrlport_clk           (                     ),
    .ctrlport_rst           (                     ),
    .m_ctrlport_req_wr      (ctrlport_req_wr      ),
    .m_ctrlport_req_rd      (ctrlport_req_rd      ),
    .m_ctrlport_req_addr    (ctrlport_req_addr    ),
    .m_ctrlport_req_data    (ctrlport_req_data    ),
    .m_ctrlport_resp_ack    (ctrlport_resp_ack    ),
    .m_ctrlport_resp_data   (ctrlport_resp_data   ),
    .axis_data_clk          (                     ),
    .axis_data_rst          (                     ),
    .m_in_0_payload_tdata   (axis_to_fft_tdata    ),
    .m_in_0_payload_tkeep   (                     ),
    .m_in_0_payload_tlast   (axis_to_fft_tlast    ),
    .m_in_0_payload_tvalid  (axis_to_fft_tvalid   ),
    .m_in_0_payload_tready  (axis_to_fft_tready   ),
    .m_in_0_context_tdata   (m_axis_context_tdata ),
    .m_in_0_context_tuser   (m_axis_context_tuser ),
    .m_in_0_context_tlast   (m_axis_context_tlast ),
    .m_in_0_context_tvalid  (m_axis_context_tvalid),
    .m_in_0_context_tready  (m_axis_context_tready),
    .s_out_0_payload_tdata  (axis_from_fft_tdata  ),
    .s_out_0_payload_tkeep  ({1*NIPC{1'b1}}       ),
    .s_out_0_payload_tlast  (axis_from_fft_tlast  ),
    .s_out_0_payload_tvalid (axis_from_fft_tvalid ),
    .s_out_0_payload_tready (axis_from_fft_tready ),
    .s_out_0_context_tdata  (s_axis_context_tdata ),
    .s_out_0_context_tuser  (s_axis_context_tuser ),
    .s_out_0_context_tlast  (s_axis_context_tlast ),
    .s_out_0_context_tvalid (s_axis_context_tvalid),
    .s_out_0_context_tready (s_axis_context_tready)
  );

  // The input packets are the same configuration as the output packets, so
  // just use the header information for each incoming to create the header for
  // each outgoing packet. This is done by connecting m_axis_context to
  // directly to s_axis_context.
  assign s_axis_context_tdata  = m_axis_context_tdata;
  assign s_axis_context_tuser  = m_axis_context_tuser;
  assign s_axis_context_tlast  = m_axis_context_tlast;
  assign s_axis_context_tvalid = m_axis_context_tvalid;
  assign m_axis_context_tready = s_axis_context_tready;

  wire [ 8-1:0] set_addr;
  wire [32-1:0] set_data;
  wire  set_stb;
  wire [ 8-1:0] rb_addr;
  reg  [64-1:0] rb_data;

  ctrlport_to_settings_bus # (
    .NUM_PORTS (1)
  ) ctrlport_to_settings_bus_i (
    .ctrlport_clk             (ce_clk),
    .ctrlport_rst             (ce_rst),
    .s_ctrlport_req_wr        (ctrlport_req_wr),
    .s_ctrlport_req_rd        (ctrlport_req_rd),
    .s_ctrlport_req_addr      (ctrlport_req_addr),
    .s_ctrlport_req_data      (ctrlport_req_data),
    .s_ctrlport_req_has_time  (1'b0),
    .s_ctrlport_req_time      (64'b0),
    .s_ctrlport_resp_ack      (ctrlport_resp_ack),
    .s_ctrlport_resp_data     (ctrlport_resp_data),
    .set_data                 (set_data),
    .set_addr                 (set_addr),
    .set_stb                  (set_stb),
    .set_time                 (),
    .set_has_time             (),
    .rb_stb                   (1'b1),
    .rb_addr                  (rb_addr),
    .rb_data                  (rb_data));

  localparam MAX_FFT_SIZE_LOG2          = 11;

  localparam [31:0] SR_FFT_RESET        = 131;
  localparam [31:0] SR_FFT_SIZE_LOG2    = 132;
  localparam [31:0] SR_MAGNITUDE_OUT    = 133;
  localparam [31:0] SR_FFT_DIRECTION    = 134;
  localparam [31:0] SR_FFT_SCALING      = 135;
  localparam [31:0] SR_FFT_SHIFT_CONFIG = 136;

  localparam RB_FFT_RESET        = 0;
  localparam RB_MAGNITUDE_OUT    = 1;
  localparam RB_FFT_SIZE_LOG2    = 2;
  localparam RB_FFT_DIRECTION    = 3;
  localparam RB_FFT_SCALING      = 4;
  localparam RB_FFT_SHIFT_CONFIG = 5;

  // FFT Output
  localparam [1:0] COMPLEX_OUT = 0;
  localparam [1:0] MAG_OUT     = 1;
  localparam [1:0] MAG_SQ_OUT  = 2;

  // FFT Direction
  localparam [0:0] FFT_REVERSE = 0;
  localparam [0:0] FFT_FORWARD = 1;

  wire [1:0]  magnitude_out;
  wire [31:0] fft_data_o_tdata;
  wire        fft_data_o_tlast;
  wire        fft_data_o_tvalid;
  wire        fft_data_o_tready;
  wire [15:0] fft_data_o_tuser;
  wire [31:0] fft_shift_o_tdata;
  wire        fft_shift_o_tlast;
  wire        fft_shift_o_tvalid;
  wire        fft_shift_o_tready;
  wire [31:0] fft_mag_i_tdata, fft_mag_o_tdata, fft_mag_o_tdata_int;
  wire        fft_mag_i_tlast, fft_mag_o_tlast;
  wire        fft_mag_i_tvalid, fft_mag_o_tvalid;
  wire        fft_mag_i_tready, fft_mag_o_tready;
  wire [31:0] fft_mag_sq_i_tdata, fft_mag_sq_o_tdata;
  wire        fft_mag_sq_i_tlast, fft_mag_sq_o_tlast;
  wire        fft_mag_sq_i_tvalid, fft_mag_sq_o_tvalid;
  wire        fft_mag_sq_i_tready, fft_mag_sq_o_tready;
  wire [31:0] fft_mag_round_i_tdata, fft_mag_round_o_tdata;
  wire        fft_mag_round_i_tlast, fft_mag_round_o_tlast;
  wire        fft_mag_round_i_tvalid, fft_mag_round_o_tvalid;
  wire        fft_mag_round_i_tready, fft_mag_round_o_tready;

  // Settings Registers
  wire fft_reset;
  setting_reg #(
    .my_addr(SR_FFT_RESET), .awidth(8), .width(1))
  sr_fft_reset (
    .clk(ce_clk), .rst(ce_rst),
    .strobe(set_stb), .addr(set_addr), .in(set_data), .out(fft_reset), .changed());

  // Two instances of FFT size register, one for FFT core and one for FFT shift
  localparam DEFAULT_FFT_SIZE = 8; // 256
  wire [7:0] fft_size_log2_tdata ,fft_core_size_log2_tdata;
  wire fft_size_log2_tvalid, fft_core_size_log2_tvalid, fft_size_log2_tready, fft_core_size_log2_tready;
  axi_setting_reg #(
    .ADDR(SR_FFT_SIZE_LOG2), .AWIDTH(8), .WIDTH(8), .DATA_AT_RESET(DEFAULT_FFT_SIZE), .VALID_AT_RESET(1))
  sr_fft_size_log2 (
    .clk(ce_clk), .reset(ce_rst),
    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
    .o_tdata(fft_size_log2_tdata), .o_tlast(), .o_tvalid(fft_size_log2_tvalid), .o_tready(fft_size_log2_tready));

  axi_setting_reg #(
    .ADDR(SR_FFT_SIZE_LOG2), .AWIDTH(8), .WIDTH(8), .DATA_AT_RESET(DEFAULT_FFT_SIZE), .VALID_AT_RESET(1))
  sr_fft_size_log2_2 (
    .clk(ce_clk), .reset(ce_rst),
    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
    .o_tdata(fft_core_size_log2_tdata), .o_tlast(), .o_tvalid(fft_core_size_log2_tvalid), .o_tready(fft_core_size_log2_tready));

  localparam DEFAULT_FFT_DIRECTION = FFT_FORWARD;
  wire fft_direction_tdata;
  wire fft_direction_tvalid, fft_direction_tready;
  axi_setting_reg #(
    .ADDR(SR_FFT_DIRECTION), .AWIDTH(8), .WIDTH(1), .DATA_AT_RESET(DEFAULT_FFT_DIRECTION), .VALID_AT_RESET(1))
  sr_fft_direction (
    .clk(ce_clk), .reset(ce_rst),
    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
    .o_tdata(fft_direction_tdata), .o_tlast(), .o_tvalid(fft_direction_tvalid), .o_tready(fft_direction_tready));

  localparam [11:0] DEFAULT_FFT_SCALING = 12'b011010101010; // Conservative 1/N scaling
  wire [11:0] fft_scaling_tdata;
  wire fft_scaling_tvalid, fft_scaling_tready;
  axi_setting_reg #(
    .ADDR(SR_FFT_SCALING), .AWIDTH(8), .WIDTH(12), .DATA_AT_RESET(DEFAULT_FFT_SCALING), .VALID_AT_RESET(1))
  sr_fft_scaling (
    .clk(ce_clk), .reset(ce_rst),
    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
    .o_tdata(fft_scaling_tdata), .o_tlast(), .o_tvalid(fft_scaling_tvalid), .o_tready(fft_scaling_tready));

  wire [1:0] fft_shift_config_tdata;
  wire fft_shift_config_tvalid, fft_shift_config_tready;
  axi_setting_reg #(
    .ADDR(SR_FFT_SHIFT_CONFIG), .AWIDTH(8), .WIDTH(2))
  sr_fft_shift_config (
    .clk(ce_clk), .reset(ce_rst),
    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
    .o_tdata(fft_shift_config_tdata), .o_tlast(), .o_tvalid(fft_shift_config_tvalid), .o_tready(fft_shift_config_tready));

  // Synchronize writing configuration to the FFT core
  reg fft_config_ready;
  wire fft_config_write = fft_config_ready & axis_to_fft_tvalid & axis_to_fft_tready;
  always @(posedge ce_clk) begin
    if (ce_rst | fft_reset) begin
      fft_config_ready   <= 1'b1;
    end else begin
      if (fft_config_write) begin
        fft_config_ready <= 1'b0;
      end else if (axis_to_fft_tlast) begin
        fft_config_ready <= 1'b1;
      end
    end
  end

  wire [23:0] fft_config_tdata     = {3'd0, fft_scaling_tdata, fft_direction_tdata, fft_core_size_log2_tdata};
  wire fft_config_tvalid           = fft_config_write & (fft_scaling_tvalid | fft_direction_tvalid | fft_core_size_log2_tvalid);
  wire fft_config_tready;
  assign fft_core_size_log2_tready = fft_config_tready & fft_config_write;
  assign fft_direction_tready      = fft_config_tready & fft_config_write;
  assign fft_scaling_tready        = fft_config_tready & fft_config_write;
  axi_fft inst_axi_fft (
    .aclk(ce_clk), .aresetn(~(fft_reset)),
    .s_axis_data_tvalid(axis_to_fft_tvalid),
    .s_axis_data_tready(axis_to_fft_tready),
    .s_axis_data_tlast(axis_to_fft_tlast),
    .s_axis_data_tdata({axis_to_fft_tdata[15:0],axis_to_fft_tdata[31:16]}),
    .m_axis_data_tvalid(fft_data_o_tvalid),
    .m_axis_data_tready(fft_data_o_tready),
    .m_axis_data_tlast(fft_data_o_tlast),
    .m_axis_data_tdata({fft_data_o_tdata[15:0],fft_data_o_tdata[31:16]}),
    .m_axis_data_tuser(fft_data_o_tuser), // FFT index
    .s_axis_config_tdata(fft_config_tdata),
    .s_axis_config_tvalid(fft_config_tvalid),
    .s_axis_config_tready(fft_config_tready),
    .event_frame_started(),
    .event_tlast_unexpected(),
    .event_tlast_missing(),
    .event_status_channel_halt(),
    .event_data_in_channel_halt(),
    .event_data_out_channel_halt());

  // Mux control signals
  assign fft_shift_o_tready     = (magnitude_out == MAG_OUT)    ? fft_mag_i_tready         :
                                  (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_i_tready      : axis_from_fft_tready;
  assign fft_mag_i_tvalid       = (magnitude_out == MAG_OUT)    ? fft_shift_o_tvalid       : 1'b0;
  assign fft_mag_i_tlast        = (magnitude_out == MAG_OUT)    ? fft_shift_o_tlast        : 1'b0;
  assign fft_mag_i_tdata        = fft_shift_o_tdata;
  assign fft_mag_o_tready       = (magnitude_out == MAG_OUT)    ? fft_mag_round_i_tready   : 1'b0;
  assign fft_mag_sq_i_tvalid    = (magnitude_out == MAG_SQ_OUT) ? fft_shift_o_tvalid       : 1'b0;
  assign fft_mag_sq_i_tlast     = (magnitude_out == MAG_SQ_OUT) ? fft_shift_o_tlast        : 1'b0;
  assign fft_mag_sq_i_tdata     = fft_shift_o_tdata;
  assign fft_mag_sq_o_tready    = (magnitude_out == MAG_SQ_OUT) ? fft_mag_round_i_tready   : 1'b0;
  assign fft_mag_round_i_tvalid = (magnitude_out == MAG_OUT)    ? fft_mag_o_tvalid         :
                                  (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_o_tvalid      : 1'b0;
  assign fft_mag_round_i_tlast  = (magnitude_out == MAG_OUT)    ? fft_mag_o_tlast          :
                                  (magnitude_out == MAG_SQ_OUT) ? fft_mag_sq_o_tlast       : 1'b0;
  assign fft_mag_round_i_tdata  = (magnitude_out == MAG_OUT)    ? fft_mag_o_tdata          : fft_mag_sq_o_tdata;
  assign fft_mag_round_o_tready = axis_from_fft_tready;
  assign axis_from_fft_tvalid     = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tvalid : fft_shift_o_tvalid;
  assign axis_from_fft_tlast      = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tlast  : fft_shift_o_tlast;
  assign axis_from_fft_tdata      = (magnitude_out == MAG_OUT | magnitude_out == MAG_SQ_OUT) ? fft_mag_round_o_tdata  : fft_shift_o_tdata;

  // Conditionally synth magnitude / magnitude^2 logic
  generate
    if (EN_MAGNITUDE_OUT | EN_MAGNITUDE_APPROX_OUT | EN_MAGNITUDE_SQ_OUT) begin : generate_magnitude_out
      setting_reg #(
        .my_addr(SR_MAGNITUDE_OUT), .awidth(8), .width(2))
      sr_magnitude_out (
        .clk(ce_clk), .rst(ce_rst),
        .strobe(set_stb), .addr(set_addr), .in(set_data), .out(magnitude_out), .changed());
    end else begin : generate_magnitude_out_else
      // Magnitude calculation logic not included, so always bypass
      assign magnitude_out = 2'd0;
    end

    if (EN_FFT_SHIFT) begin : generate_fft_shift
      fft_shift #(
        .MAX_FFT_SIZE_LOG2(MAX_FFT_SIZE_LOG2),
        .WIDTH(32))
      inst_fft_shift (
        .clk(ce_clk), .reset(ce_rst | fft_reset),
        .config_tdata(fft_shift_config_tdata),
        .config_tvalid(fft_shift_config_tvalid),
        .config_tready(fft_shift_config_tready),
        .fft_size_log2_tdata(fft_size_log2_tdata[$clog2(MAX_FFT_SIZE_LOG2)-1:0]),
        .fft_size_log2_tvalid(fft_size_log2_tvalid),
        .fft_size_log2_tready(fft_size_log2_tready),
        .i_tdata(fft_data_o_tdata),
        .i_tlast(fft_data_o_tlast),
        .i_tvalid(fft_data_o_tvalid),
        .i_tready(fft_data_o_tready),
        .i_tuser(fft_data_o_tuser[MAX_FFT_SIZE_LOG2-1:0]),
        .o_tdata(fft_shift_o_tdata),
        .o_tlast(fft_shift_o_tlast),
        .o_tvalid(fft_shift_o_tvalid),
        .o_tready(fft_shift_o_tready));
    end 
    else begin : generate_fft_shift_else 
      assign fft_shift_o_tdata = fft_data_o_tdata;
      assign fft_shift_o_tlast = fft_data_o_tlast;
      assign fft_shift_o_tvalid = fft_data_o_tvalid;
      assign fft_data_o_tready = fft_shift_o_tready;
    end

    // More accurate magnitude calculation takes precedence if enabled
    if (EN_MAGNITUDE_OUT) begin : generate_complex_to_magphase 
      complex_to_magphase
      inst_complex_to_magphase (
        .aclk(ce_clk), .aresetn(~(ce_rst | fft_reset)),
        .s_axis_cartesian_tvalid(fft_mag_i_tvalid),
        .s_axis_cartesian_tlast(fft_mag_i_tlast),
        .s_axis_cartesian_tready(fft_mag_i_tready),
        .s_axis_cartesian_tdata(fft_mag_i_tdata),
        .m_axis_dout_tvalid(fft_mag_o_tvalid),
        .m_axis_dout_tlast(fft_mag_o_tlast),
        .m_axis_dout_tdata(fft_mag_o_tdata_int),
        .m_axis_dout_tready(fft_mag_o_tready));
      assign fft_mag_o_tdata = {1'b0, fft_mag_o_tdata_int[15:0], 15'd0};
    end 
    else if (EN_MAGNITUDE_APPROX_OUT) begin : generate_complex_to_mag_approx
      complex_to_mag_approx
      inst_complex_to_mag_approx (
        .clk(ce_clk), .reset(ce_rst | fft_reset), .clear(1'b0),
        .i_tvalid(fft_mag_i_tvalid),
        .i_tlast(fft_mag_i_tlast),
        .i_tready(fft_mag_i_tready),
        .i_tdata(fft_mag_i_tdata),
        .o_tvalid(fft_mag_o_tvalid),
        .o_tlast(fft_mag_o_tlast),
        .o_tready(fft_mag_o_tready),
        .o_tdata(fft_mag_o_tdata_int[15:0]));
      assign fft_mag_o_tdata = {1'b0, fft_mag_o_tdata_int[15:0], 15'd0};
    end  
    else begin : generate_complex_to_mag_approx_else
      assign fft_mag_o_tdata = fft_mag_i_tdata;
      assign fft_mag_o_tlast = fft_mag_i_tlast;
      assign fft_mag_o_tvalid = fft_mag_i_tvalid;
      assign fft_mag_i_tready = fft_mag_o_tready;
    end

    if (EN_MAGNITUDE_SQ_OUT) begin : generate_complex_to_magsq
      complex_to_magsq
      inst_complex_to_magsq (
        .clk(ce_clk), .reset(ce_rst | fft_reset), .clear(1'b0),
        .i_tvalid(fft_mag_sq_i_tvalid),
        .i_tlast(fft_mag_sq_i_tlast),
        .i_tready(fft_mag_sq_i_tready),
        .i_tdata(fft_mag_sq_i_tdata),
        .o_tvalid(fft_mag_sq_o_tvalid),
        .o_tlast(fft_mag_sq_o_tlast),
        .o_tready(fft_mag_sq_o_tready),
        .o_tdata(fft_mag_sq_o_tdata));
    end
    else begin : generate_complex_to_magsq_else
      assign fft_mag_sq_o_tdata = fft_mag_sq_i_tdata;
      assign fft_mag_sq_o_tlast = fft_mag_sq_i_tlast;
      assign fft_mag_sq_o_tvalid = fft_mag_sq_i_tvalid;
      assign fft_mag_sq_i_tready = fft_mag_sq_o_tready;
    end

    // Convert to SC16
    if (EN_MAGNITUDE_OUT | EN_MAGNITUDE_APPROX_OUT | EN_MAGNITUDE_SQ_OUT) begin : generate_axi_round_and_clip
      axi_round_and_clip #(
        .WIDTH_IN(32),
        .WIDTH_OUT(16),
        .CLIP_BITS(1))
      inst_axi_round_and_clip (
        .clk(ce_clk), .reset(ce_rst | fft_reset),
        .i_tdata(fft_mag_round_i_tdata),
        .i_tlast(fft_mag_round_i_tlast),
        .i_tvalid(fft_mag_round_i_tvalid),
        .i_tready(fft_mag_round_i_tready),
        .o_tdata(fft_mag_round_o_tdata[31:16]),
        .o_tlast(fft_mag_round_o_tlast),
        .o_tvalid(fft_mag_round_o_tvalid),
        .o_tready(fft_mag_round_o_tready));
      assign fft_mag_round_o_tdata[15:0] = {16{16'd0}};
    end 
    else begin : generate_axi_round_and_clip_else
      assign fft_mag_round_o_tdata = fft_mag_round_i_tdata;
      assign fft_mag_round_o_tlast = fft_mag_round_i_tlast;
      assign fft_mag_round_o_tvalid = fft_mag_round_i_tvalid;
      assign fft_mag_round_i_tready = fft_mag_round_o_tready;
    end 
  endgenerate

  // Readback registers
  always @*
    case(rb_addr)
      RB_FFT_RESET        : rb_data <= {63'd0, fft_reset};
      RB_MAGNITUDE_OUT    : rb_data <= {62'd0, magnitude_out};
      RB_FFT_SIZE_LOG2    : rb_data <= {fft_size_log2_tdata};
      RB_FFT_DIRECTION    : rb_data <= {63'd0, fft_direction_tdata};
      RB_FFT_SCALING      : rb_data <= {52'd0, fft_scaling_tdata};
      RB_FFT_SHIFT_CONFIG : rb_data <= {62'd0, fft_shift_config_tdata};
      default : rb_data <= 64'h0BADC0DE0BADC0DE;
  endcase

endmodule
