//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: db_gpio_interface
//
// Description:
//   Interface for GPIO interface towards daughterboards.
//
//   A ControlPort interface is serialized into bytes along with a valid signal.
//   The ControlPort supports write requests only. Byte enables are not supported.
//   There is support for timed commands.
//   Furthermore there are 4 state wires towards the DB. Ensure an appropriate
//   hold time on the states as the transmission happens in pll_ref_clk, which is
//   slower than radio_clk. Pulses of e.g. just a single clock cycle may not get
//   transferred to the DB.
//
//   The 20 available GPIO lines are assigned with
//   - 5x empty
//   - bytestream direction
//   - bytestream valid
//   - bytestream data (8 bits)
//   - 1x empty
//   - db_state (4 bits)
//

`default_nettype none

module db_gpio_interface (
  // Clocks and reset
  input  wire radio_clk,
  input  wire pll_ref_clk,

  // DB state lines (domain: radio_clk)
  input  wire [ 3:0] db_state,

  // time interfaces (domain: radio_clk)
  input  wire [63:0] radio_time,
  input  wire        radio_time_stb,
  input  wire [ 3:0] time_ignore_bits,

  // Request (domain: radio_clk)
  input  wire        ctrlport_rst,
  input  wire        s_ctrlport_req_wr,
  input  wire        s_ctrlport_req_rd,
  input  wire [19:0] s_ctrlport_req_addr,
  input  wire [31:0] s_ctrlport_req_data,
  input  wire [ 3:0] s_ctrlport_req_byte_en,
  input  wire        s_ctrlport_req_has_time,
  input  wire [63:0] s_ctrlport_req_time,

  // Response (domain: radio_clk)
  output wire        s_ctrlport_resp_ack,
  output wire [ 1:0] s_ctrlport_resp_status,
  output wire [31:0] s_ctrlport_resp_data,

  // GPIO interface (domain: pll_ref_clk)
  input  wire [19:0] gpio_in,
  output wire [19:0] gpio_out,
  output wire [19:0] gpio_out_en,

  // Version (Constant)
  output wire [95:0] version_info
);

  `include "../regmap/versioning_regs_regmap_utils.vh"
  `include "../regmap/versioning_utils.vh"

  //----------------------------------------------------------------------------
  // Timed command processing
  //----------------------------------------------------------------------------
  wire [19:0] ctrlport_timed_req_addr;
  wire [31:0] ctrlport_timed_req_data;
  wire        ctrlport_timed_req_rd;
  wire        ctrlport_timed_req_wr;
  wire        ctrlport_timed_resp_ack;
  reg  [31:0] ctrlport_timed_resp_data = 0;
  reg  [ 1:0] ctrlport_timed_resp_status = 0;

  ctrlport_timer #(
    .EXEC_LATE_CMDS(1)
  ) ctrlport_timer_i (
    .clk                      (radio_clk),
    .rst                      (ctrlport_rst),
    .time_now                 (radio_time),
    .time_now_stb             (radio_time_stb),
    .time_ignore_bits         (time_ignore_bits),
    .s_ctrlport_req_wr        (s_ctrlport_req_wr),
    .s_ctrlport_req_rd        (s_ctrlport_req_rd),
    .s_ctrlport_req_addr      (s_ctrlport_req_addr),
    .s_ctrlport_req_data      (s_ctrlport_req_data),
    .s_ctrlport_req_byte_en   (s_ctrlport_req_byte_en),
    .s_ctrlport_req_has_time  (s_ctrlport_req_has_time),
    .s_ctrlport_req_time      (s_ctrlport_req_time),
    .s_ctrlport_resp_ack      (s_ctrlport_resp_ack),
    .s_ctrlport_resp_status   (s_ctrlport_resp_status),
    .s_ctrlport_resp_data     (s_ctrlport_resp_data),
    .m_ctrlport_req_wr        (ctrlport_timed_req_wr),
    .m_ctrlport_req_rd        (ctrlport_timed_req_rd),
    .m_ctrlport_req_addr      (ctrlport_timed_req_addr),
    .m_ctrlport_req_data      (ctrlport_timed_req_data),
    .m_ctrlport_req_byte_en   (),
    .m_ctrlport_resp_ack      (ctrlport_timed_resp_ack),
    .m_ctrlport_resp_status   (ctrlport_timed_resp_status),
    .m_ctrlport_resp_data     (ctrlport_timed_resp_data)
  );

  //----------------------------------------------------------------------------
  // Clock domain crossing (radio_clk -> pll_ref_clk)
  //----------------------------------------------------------------------------
  // Radio_clk is derived from pll_ref_clk by an integer multiplier and
  // originate from the same PLL.
  // Therefore the clock crossing can be achieved by using simple registers.
  // Static timing analysis will be able to meet setup and hold requirements on
  // them.

  // holding read and write flags for multiple radio_clk cycles
  reg         ctrlport_timed_req_wr_hold = 1'b0;
  reg         ctrlport_timed_req_rd_hold = 1'b0;

  reg  [19:0] ctrlport_req_addr_prc = 20'b0;
  reg  [31:0] ctrlport_req_data_prc = 32'b0;
  reg         ctrlport_req_rd_prc   = 1'b0;
  reg         ctrlport_req_wr_prc   = 1'b0;

  wire        ctrlport_resp_ack_prc;
  wire [31:0] ctrlport_resp_data_prc;
  wire [ 1:0] ctrlport_resp_status_prc;

  reg         ctrlport_req_rd_fall      = 1'b0;
  reg         ctrlport_req_wr_fall      = 1'b0;
  reg  [31:0] ctrlport_resp_data_fall   = 32'b0;
  reg  [ 1:0] ctrlport_resp_status_fall = 2'b0;
  reg         ctrlport_resp_ack_fall    = 1'b0;

  // Retime signals to falling edge of radio_clk.
  // Because radio_clk is more heavily loaded than pll_ref_clk, it arrives at
  // the FF's later, which leads to hold time violations when moving signals
  // from pll_ref_clk to radio_clk. By sampling on the falling edge of
  // radio_clk, we provide (nominally) half a radio_clk period of hold, while
  // reducing setup time by half. The late arrival of radio_clk adds back some
  // of the lost setup margin.
  always @(negedge radio_clk) begin
    ctrlport_req_rd_fall      <= ctrlport_req_rd_prc;
    ctrlport_req_wr_fall      <= ctrlport_req_wr_prc;
    ctrlport_resp_ack_fall    <= ctrlport_resp_ack_prc;
    ctrlport_resp_status_fall <= ctrlport_resp_status_prc;
    ctrlport_resp_data_fall   <= ctrlport_resp_data_prc;
  end

  always @(posedge radio_clk) begin
    if (ctrlport_req_wr_fall) begin
      ctrlport_timed_req_wr_hold <= 1'b0;
    end else if (ctrlport_timed_req_wr) begin
      ctrlport_timed_req_wr_hold <= 1'b1;
    end
    if (ctrlport_req_rd_fall) begin
      ctrlport_timed_req_rd_hold <= 1'b0;
    end else if (ctrlport_timed_req_rd) begin
      ctrlport_timed_req_rd_hold <= 1'b1;
    end

    // capture request address and data
    if (ctrlport_timed_req_wr || ctrlport_timed_req_rd) begin
      ctrlport_req_addr_prc <= ctrlport_timed_req_addr;
      ctrlport_req_data_prc <= ctrlport_timed_req_data;
    end
  end

  // capture extended flags in pll_ref_clk domain
  always @(posedge pll_ref_clk) begin
    ctrlport_req_wr_prc <= ctrlport_timed_req_wr_hold;
    ctrlport_req_rd_prc <= ctrlport_timed_req_rd_hold;
  end

  // search for rising edge in response
  reg [1:0] ctrlport_timed_ack_reg = 2'b0;
  always @(posedge radio_clk) begin
    ctrlport_timed_ack_reg = {ctrlport_timed_ack_reg[0], ctrlport_resp_ack_fall};
  end
  assign ctrlport_timed_resp_ack = ctrlport_timed_ack_reg[0] & ~ctrlport_timed_ack_reg[1];

  // capture response data
  always @(posedge radio_clk) begin
    if (ctrlport_resp_ack_fall) begin
      ctrlport_timed_resp_status <= ctrlport_resp_status_fall;
      ctrlport_timed_resp_data   <= ctrlport_resp_data_fall;
    end
  end

  // transfer state lines
  reg [3:0] db_state_prc    = 4'b0;
  reg [3:0] db_state_prc_fe = 4'b0;
  always @(posedge pll_ref_clk) begin
    db_state_prc    <= db_state;
  end
  always @(negedge pll_ref_clk) begin
    db_state_prc_fe <= db_state_prc;
  end

  // transfer reset
  reg ctrlport_rst_hold = 1'b0;
  reg ctrlport_rst_prc  = 1'b0;
  reg ctrlport_rst_fall = 1'b0;
  always @(posedge radio_clk) begin
    if (ctrlport_rst) begin
      ctrlport_rst_hold <= 1'b1;
    end else if (ctrlport_rst_fall) begin
      ctrlport_rst_hold <= 1'b0;
    end
  end
  always @(posedge pll_ref_clk) begin
    ctrlport_rst_prc <= ctrlport_rst_hold;
  end
  always @(negedge radio_clk) begin
    ctrlport_rst_fall <= ctrlport_rst_prc;
  end


  //----------------------------------------------------------------------------
  // Ctrlport serializer
  //----------------------------------------------------------------------------
  wire [7:0] bytestream_data_in;
  wire [7:0] bytestream_data_out;
  wire       bytestream_direction;
  wire       bytestream_output_enable;
  wire       bytestream_valid_in;
  wire       bytestream_valid_out;

  ctrlport_byte_serializer serializer_i (
    .ctrlport_clk              (pll_ref_clk),
    .ctrlport_rst              (ctrlport_rst_prc),
    .s_ctrlport_req_wr         (ctrlport_req_wr_prc),
    .s_ctrlport_req_rd         (ctrlport_req_rd_prc),
    .s_ctrlport_req_addr       (ctrlport_req_addr_prc),
    .s_ctrlport_req_data       (ctrlport_req_data_prc),
    .s_ctrlport_resp_ack       (ctrlport_resp_ack_prc),
    .s_ctrlport_resp_status    (ctrlport_resp_status_prc),
    .s_ctrlport_resp_data      (ctrlport_resp_data_prc),
    .bytestream_data_in        (bytestream_data_in),
    .bytestream_valid_in       (bytestream_valid_in),
    .bytestream_data_out       (bytestream_data_out),
    .bytestream_valid_out      (bytestream_valid_out),
    .bytestream_direction      (bytestream_direction),
    .bytestream_output_enable  (bytestream_output_enable)
  );

  // IOB registers to drive data on the falling edge
  reg [7:0] bytestream_data_out_fe;
  reg       bytestream_direction_fe;
  reg       bytestream_output_enable_fe;
  reg       bytestream_valid_out_fe;

  // Signals are shifted into a falling edge domain to help meet
  // hold requirements at CPLD
  always @(negedge pll_ref_clk) begin
    if (ctrlport_rst_prc) begin
      bytestream_data_out_fe      <= 8'b0;
      bytestream_valid_out_fe     <= 1'b0;
      bytestream_direction_fe     <= 1'b0;
      bytestream_output_enable_fe <= 1'b1;
    end else begin
      bytestream_data_out_fe      <= bytestream_data_out;
      bytestream_valid_out_fe     <= bytestream_valid_out;
      bytestream_direction_fe     <= bytestream_direction;
      bytestream_output_enable_fe <= bytestream_output_enable;
    end
  end

  //----------------------------------------------------------------------------
  // wire assignment
  //----------------------------------------------------------------------------
  // 5 unused, 10 used, 1 unused and 4 used signals
  assign gpio_out    = {5'b0, bytestream_direction_fe, bytestream_valid_out_fe, bytestream_data_out_fe, 1'b0, db_state_prc_fe};
  assign gpio_out_en = {5'b0, 1'b1,                    {9 {bytestream_output_enable_fe}},               1'b0, {4 {1'b1}}  };

  assign bytestream_valid_in = gpio_in[13];
  assign bytestream_data_in  = gpio_in[12:5];

  //----------------------------------------------------------------------------
  // version_info
  //----------------------------------------------------------------------------

  // Version metadata, constants come from auto-generated versioning_regs_regmap_utils.vh
  assign version_info = build_component_versions(
    DB_GPIO_IFC_VERSION_LAST_MODIFIED_TIME,
    build_version(
      DB_GPIO_IFC_OLDEST_COMPATIBLE_VERSION_MAJOR,
      DB_GPIO_IFC_OLDEST_COMPATIBLE_VERSION_MINOR,
      DB_GPIO_IFC_OLDEST_COMPATIBLE_VERSION_BUILD),
    build_version(
      DB_GPIO_IFC_CURRENT_VERSION_MAJOR,
      DB_GPIO_IFC_CURRENT_VERSION_MINOR,
      DB_GPIO_IFC_CURRENT_VERSION_BUILD));

endmodule

`default_nettype wire

//XmlParse xml_on
//<regmap name="VERSIONING_REGS_REGMAP">
//  <group name="VERSIONING_CONSTANTS">
//    <enumeratedtype name="DB_GPIO_IFC_VERSION" showhex="true">
//      <info>
//        Daughterboard GPIO interface.{BR/}
//        For guidance on when to update these revision numbers,
//        please refer to the register map documentation accordingly:
//        <li> Current version: @.VERSIONING_REGS_REGMAP..CURRENT_VERSION
//        <li> Oldest compatible version: @.VERSIONING_REGS_REGMAP..OLDEST_COMPATIBLE_VERSION
//        <li> Version last modified: @.VERSIONING_REGS_REGMAP..VERSION_LAST_MODIFIED
//      </info>
//      <value name="DB_GPIO_IFC_CURRENT_VERSION_MAJOR"           integer="1"/>
//      <value name="DB_GPIO_IFC_CURRENT_VERSION_MINOR"           integer="0"/>
//      <value name="DB_GPIO_IFC_CURRENT_VERSION_BUILD"           integer="0"/>
//      <value name="DB_GPIO_IFC_OLDEST_COMPATIBLE_VERSION_MAJOR" integer="1"/>
//      <value name="DB_GPIO_IFC_OLDEST_COMPATIBLE_VERSION_MINOR" integer="0"/>
//      <value name="DB_GPIO_IFC_OLDEST_COMPATIBLE_VERSION_BUILD" integer="0"/>
//      <value name="DB_GPIO_IFC_VERSION_LAST_MODIFIED_TIME"      integer="0x20110616"/>
//    </enumeratedtype>
//  </group>
//</regmap>
//XmlParse xml_off