//
// Copyright 2019 Ettus Research, a National Instruments Company
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rfnoc_block_duc
//
// Description:  An digital up-converter block for RFNoC.
//
// Parameters:
//
//   THIS_PORTID    : Control crossbar port to which this block is connected
//   CHDR_W         : AXIS CHDR interface data width
//   NUM_PORTS      : Number of DUC signal processing chains
//   MTU            : Maximum transmission unit (i.e., maximum packet size) in
//                    CHDR words is 2**MTU.
//   NUM_HB         : Number of half-band filter blocks to include (0-3)
//   CIC_MAX_INTERP : Maximum interpolation to support in the CIC filter
//

module rfnoc_block_duc #(
  parameter THIS_PORTID    = 0,
  parameter CHDR_W         = 64,
  parameter NUM_PORTS      = 2,
  parameter MTU            = 10,
  parameter NUM_HB         = 2,
  parameter CIC_MAX_INTERP = 128
) (
  //---------------------------------------------------------------------------
  // AXIS CHDR Port
  //---------------------------------------------------------------------------

  input wire rfnoc_chdr_clk,
  input wire ce_clk,

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

  // CHDR outputs to framework
  output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata,
  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tlast,
  output wire [       NUM_PORTS-1:0] m_rfnoc_chdr_tvalid,
  input  wire [       NUM_PORTS-1:0] 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;

  localparam COMPAT_MAJOR  = 16'h0;
  localparam COMPAT_MINOR  = 16'h1;

  `include "rfnoc_block_duc_regs.vh"
  `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_req_has_time;
  wire [63:0] ctrlport_req_time;
  wire        ctrlport_resp_ack;
  wire [31:0] ctrlport_resp_data;

  wire [NUM_PORTS*ITEM_W-1:0] m_axis_data_tdata;
  wire [       NUM_PORTS-1:0] m_axis_data_tlast;
  wire [       NUM_PORTS-1:0] m_axis_data_tvalid;
  wire [       NUM_PORTS-1:0] m_axis_data_tready;
  wire [    NUM_PORTS*64-1:0] m_axis_data_ttimestamp;
  wire [       NUM_PORTS-1:0] m_axis_data_thas_time;
  wire [    NUM_PORTS*16-1:0] m_axis_data_tlength;
  wire [       NUM_PORTS-1:0] m_axis_data_teob;
  wire [   NUM_PORTS*128-1:0] m_axis_data_tuser;

  wire [NUM_PORTS*ITEM_W-1:0] s_axis_data_tdata;
  wire [       NUM_PORTS-1:0] s_axis_data_tlast;
  wire [       NUM_PORTS-1:0] s_axis_data_tvalid;
  wire [       NUM_PORTS-1:0] s_axis_data_tready;
  wire [   NUM_PORTS*128-1:0] s_axis_data_tuser;
  wire [       NUM_PORTS-1:0] s_axis_data_teob;
  wire [    NUM_PORTS*64-1:0] s_axis_data_ttimestamp;
  wire [       NUM_PORTS-1:0] s_axis_data_thas_time;


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

  wire ce_rst;

  noc_shell_duc #(
    .THIS_PORTID (THIS_PORTID),
    .CHDR_W      (CHDR_W),
    .MTU         (MTU),
    .NUM_PORTS   (NUM_PORTS)
  ) noc_shell_duc_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_req_has_time (ctrlport_req_has_time),
    .m_ctrlport_req_time     (ctrlport_req_time),
    .m_ctrlport_resp_ack     (ctrlport_resp_ack),
    .m_ctrlport_resp_data    (ctrlport_resp_data),
    .axis_data_clk           (),
    .axis_data_rst           (),
    .m_in_axis_tdata         (m_axis_data_tdata),
    .m_in_axis_tkeep         (),
    .m_in_axis_tlast         (m_axis_data_tlast),
    .m_in_axis_tvalid        (m_axis_data_tvalid),
    .m_in_axis_tready        (m_axis_data_tready),
    .m_in_axis_ttimestamp    (m_axis_data_ttimestamp),
    .m_in_axis_thas_time     (m_axis_data_thas_time),
    .m_in_axis_tlength       (m_axis_data_tlength),
    .m_in_axis_teov          (),
    .m_in_axis_teob          (m_axis_data_teob),
    .s_out_axis_tdata        (s_axis_data_tdata),
    .s_out_axis_tkeep        ({NUM_PORTS*NIPC{1'b1}}),
    .s_out_axis_tlast        (s_axis_data_tlast),
    .s_out_axis_tvalid       (s_axis_data_tvalid),
    .s_out_axis_tready       (s_axis_data_tready),
    .s_out_axis_ttimestamp   (s_axis_data_ttimestamp),
    .s_out_axis_thas_time    (s_axis_data_thas_time),
    .s_out_axis_teov         ({NUM_PORTS{1'b0}}),
    .s_out_axis_teob         (s_axis_data_teob)
  );


  //---------------------------------------------------------------------------
  // Register Translation
  //---------------------------------------------------------------------------
  //
  // Each DUC block is allocated an address spaces. This block translates CTRL
  // port transactions in that space to settings bus.
  //
  //---------------------------------------------------------------------------

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

  ctrlport_to_settings_bus # (
    .NUM_PORTS (NUM_PORTS)
  ) 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  (ctrlport_req_has_time),
    .s_ctrlport_req_time      (ctrlport_req_time),
    .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_time),
    .set_has_time             (set_has_time),
    .rb_stb                   ({NUM_PORTS{1'b1}}),
    .rb_addr                  (rb_addr),
    .rb_data                  (rb_data),
    .timestamp                (64'b0)
  );


  //---------------------------------------------------------------------------
  // DUC Implementation
  //---------------------------------------------------------------------------

  // Unused signals
  wire [   NUM_PORTS-1:0] clear_tx_seqnum = 0;
  wire [16*NUM_PORTS-1:0] src_sid         = 0;
  wire [16*NUM_PORTS-1:0] next_dst_sid    = 0;

  localparam MAX_M = CIC_MAX_INTERP * 2<<(NUM_HB-1);

  genvar i;
  generate
    for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_duc_chains
      wire clear_user;
      wire clear_duc = clear_tx_seqnum[i] | clear_user;

      wire        set_stb_int      = set_stb[i];
      wire [7:0]  set_addr_int     = set_addr[8*i+7:8*i];
      wire [31:0] set_data_int     = set_data[32*i+31:32*i];
      wire [63:0] set_time_int     = set_time[64*i+63:64*i];
      wire        set_has_time_int = set_has_time[i];

      // Build the expected tuser CHDR header
      cvita_hdr_encoder cvita_hdr_encoder_i (
        .pkt_type       (2'b0),
        .eob            (m_axis_data_teob[i]),
        .has_time       (m_axis_data_thas_time[i]),
        .seqnum         (12'b0),
        .payload_length (m_axis_data_tlength[16*i +: 16]),
        .src_sid        (16'b0),
        .dst_sid        (16'b0),
        .vita_time      (m_axis_data_ttimestamp[64*i +: 64]),
        .header         (m_axis_data_tuser[128*i+:128])
      );

      // Extract bit fields from outgoing tuser CHDR header
      assign s_axis_data_teob[i]              = s_axis_data_tuser[128*i+124 +:  1];
      assign s_axis_data_thas_time[i]         = s_axis_data_tuser[128*i+125 +:  1];
      assign s_axis_data_ttimestamp[64*i+:64] = s_axis_data_tuser[128*i+  0 +: 64];

      // TODO Readback register for number of FIR filter taps
      always @(*) begin
        case(rb_addr[i*8+7:i*8])
          RB_COMPAT_NUM     : rb_data[i*64+63:i*64] <= {COMPAT_MAJOR, COMPAT_MINOR};
          RB_NUM_HB         : rb_data[i*64+63:i*64] <= NUM_HB;
          RB_CIC_MAX_INTERP : rb_data[i*64+63:i*64] <= CIC_MAX_INTERP;
          default           : rb_data[i*64+63:i*64] <= 64'h0BADC0DE0BADC0DE;
        endcase
      end

      ////////////////////////////////////////////////////////////
      //
      // Timed CORDIC
      // - Implements timed cordic tunes. Placed between AXI Wrapper
      //   and AXI Rate Change due to it needing access to the
      //   vita time of the samples.
      //
      ////////////////////////////////////////////////////////////
      wire [31:0]  m_axis_rc_tdata;
      wire         m_axis_rc_tlast;
      wire         m_axis_rc_tvalid;
      wire         m_axis_rc_tready;
      wire [127:0] m_axis_rc_tuser;

      dds_timed #(
        .SR_FREQ_ADDR(SR_FREQ_ADDR),
        .SR_SCALE_IQ_ADDR(SR_SCALE_IQ_ADDR))
      dds_timed (
        .clk(ce_clk), .reset(ce_rst), .clear(clear_tx_seqnum[i]),
        .timed_cmd_fifo_full(),
        .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int),
        .set_time(set_time_int), .set_has_time(set_has_time_int),
        .i_tdata(m_axis_rc_tdata), .i_tlast(m_axis_rc_tlast), .i_tvalid(m_axis_rc_tvalid),
        .i_tready(m_axis_rc_tready), .i_tuser(m_axis_rc_tuser),
        .o_tdata(s_axis_data_tdata[ITEM_W*i+:ITEM_W]), .o_tlast(s_axis_data_tlast[i]), .o_tvalid(s_axis_data_tvalid[i]),
        .o_tready(s_axis_data_tready[i]), .o_tuser(s_axis_data_tuser[128*i+:128]));

      ////////////////////////////////////////////////////////////
      //
      // Increase Rate
      //
      ////////////////////////////////////////////////////////////
      wire [31:0] sample_tdata, sample_duc_tdata;
      wire sample_tvalid, sample_tready;
      wire sample_duc_tvalid, sample_duc_tready;
      axi_rate_change #(
        .WIDTH(32),
        .MAX_N(1),
        .MAX_M(MAX_M),
        .SR_N_ADDR(SR_N_ADDR),
        .SR_M_ADDR(SR_M_ADDR),
        .SR_CONFIG_ADDR(SR_CONFIG_ADDR),
        .SR_TIME_INCR_ADDR(SR_TIME_INCR_ADDR))
      axi_rate_change (
        .clk(ce_clk), .reset(ce_rst), .clear(clear_tx_seqnum[i]), .clear_user(clear_user),
        .src_sid(src_sid[16*i+15:16*i]), .dst_sid(next_dst_sid[16*i+15:16*i]),
        .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int),
        .i_tdata(m_axis_data_tdata[ITEM_W*i+:ITEM_W]), .i_tlast(m_axis_data_tlast[i]), .i_tvalid(m_axis_data_tvalid[i]),
        .i_tready(m_axis_data_tready[i]), .i_tuser(m_axis_data_tuser[128*i+:128]),
        .o_tdata(m_axis_rc_tdata), .o_tlast(m_axis_rc_tlast), .o_tvalid(m_axis_rc_tvalid),
        .o_tready(m_axis_rc_tready), .o_tuser(m_axis_rc_tuser),
        .m_axis_data_tdata({sample_tdata}), .m_axis_data_tlast(), .m_axis_data_tvalid(sample_tvalid),
        .m_axis_data_tready(sample_tready),
        .s_axis_data_tdata(sample_duc_tdata), .s_axis_data_tlast(1'b0), .s_axis_data_tvalid(sample_duc_tvalid),
        .s_axis_data_tready(sample_duc_tready),
        .warning_long_throttle(), .error_extra_outputs(), .error_drop_pkt_lockup());

      ////////////////////////////////////////////////////////////
      //
      // Digital Up Converter
      //
      ////////////////////////////////////////////////////////////
      duc #(
        .SR_INTERP_ADDR(SR_INTERP_ADDR),
        .NUM_HB(NUM_HB),
        .CIC_MAX_INTERP(CIC_MAX_INTERP))
      duc (
        .clk(ce_clk), .reset(ce_rst), .clear(clear_duc),
        .set_stb(set_stb_int), .set_addr(set_addr_int), .set_data(set_data_int),
        .i_tdata(sample_tdata), .i_tuser(128'b0), .i_tvalid(sample_tvalid), .i_tready(sample_tready),
        .o_tdata(sample_duc_tdata), .o_tuser(), .o_tvalid(sample_duc_tvalid), .o_tready(sample_duc_tready));

    end
  endgenerate

endmodule
