//
// Copyright 2018 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: settings_bus_timed_2clk
// Description:
// - Stores settings bus transaction in a FIFO and 
//   releases them based on VITA time input
// - Also moves the settings bus to the timebase
//   clock domain
//

module settings_bus_timed_2clk #(
  parameter SR_AWIDTH     = 8,
  parameter SR_DWIDTH     = 32,
  parameter RB_AWIDTH     = 8,
  parameter RB_DWIDTH     = 64,
  parameter TIMED_CMDS_EN = 0
) (
  input                  sb_clk,          // Settings bus clock
  input                  sb_rst,          // Reset (sb_clk)
  input                  tb_clk,          // Timebase clock
  input                  tb_rst,          // Reset (tb_clk)

  input  [63:0]          vita_time,       // Current timebase time
                         
  input                  s_set_stb,       // Settings bus strobe
  input  [SR_AWIDTH-1:0] s_set_addr,      // Settings address
  input  [SR_DWIDTH-1:0] s_set_data,      // Settings data
  input                  s_set_has_time,  // Is this a timed command?
  input  [63:0]          s_set_time,      // Command time
  output                 s_set_pending,   // Is settings transaction pending?
  input  [RB_AWIDTH-1:0] s_rb_addr,       // Readback address
  output                 s_rb_stb,        // Readback data strobe
  output [RB_DWIDTH-1:0] s_rb_data,       // Readback data value

  output                 m_set_stb,       // Settings bus strobe
  output [SR_AWIDTH-1:0] m_set_addr,      // Settings address
  output [SR_DWIDTH-1:0] m_set_data,      // Settings data
  output                 m_set_has_time,  // Is this a timed command?
  output [63:0]          m_set_time,      // Command time
  input                  m_set_pending,   // Is settings transaction pending?
  output [RB_AWIDTH-1:0] m_rb_addr,       // Readback address
  input                  m_rb_stb,        // Readback data strobe
  input  [RB_DWIDTH-1:0] m_rb_data        // Readback data value
);

  // States for input and output state machines
  localparam [2:0] ST_IDLE        = 3'd0; // Nothing is happening on the bus
  localparam [2:0] ST_SET_ISSUED  = 3'd1; // A settings transaction has been issued
  localparam [2:0] ST_SET_PENDING = 3'd2; // A settings transaction is pending
  localparam [2:0] ST_RB_PENDING  = 3'd3; // Waiting for readback data
  localparam [2:0] ST_RB_DONE     = 3'd4; // Readback data is valid
  
  wire rb_valid;

  // Input state machine
  reg [2:0] in_state = ST_IDLE;
  always @(posedge sb_clk) begin
    if (sb_rst) begin
      in_state <= ST_IDLE;
    end else begin
      case (in_state)
        ST_IDLE: begin
          if (s_set_stb) begin
            in_state <= ST_SET_PENDING;
          end
        end
        ST_SET_PENDING: begin
          if (rb_valid) begin
            in_state <= ST_RB_DONE;
          end
        end
        ST_RB_DONE: begin
          in_state <= ST_IDLE;
        end
        default: begin
          in_state <= ST_IDLE;
        end
      endcase
    end
  end
  assign s_set_pending = (in_state == ST_SET_PENDING);
  assign s_rb_stb = (in_state == ST_RB_DONE);

  // Clock crossing FIFO (settings)
  // TODO: Look into a more efficient implementation for a single element
  //       clock crossing FIFO.
  wire set_pending, set_finished;
  axi_fifo_2clk #(
    .WIDTH(SR_AWIDTH+SR_DWIDTH+1+64+RB_AWIDTH), .SIZE(0)
  ) sb_2clk_fifo_i (
    .i_aclk(sb_clk), .reset(sb_rst),
    .i_tdata({s_set_addr, s_set_data, s_set_has_time, s_set_time, s_rb_addr}),
    .i_tvalid(s_set_stb), .i_tready(/* Ignored: FIFO may not have an exact size*/),
    .o_aclk(tb_clk),
    .o_tdata({m_set_addr, m_set_data, m_set_has_time, m_set_time, m_rb_addr}),
    .o_tvalid(set_pending), .o_tready(set_finished)
  );

  // Time compare logic
  // If ~has_time then pass the transaction through, otherwise wait for time
  // to tick up to command time
  wire now, late;
  wire go = ((TIMED_CMDS_EN == 1) && m_set_has_time) ? (now | late) : 1'b1;

  // If this is a timed command then vita_time == m_set_time one cycle before
  // strobe is asserted i.e. timed strobe assertion has a one cycle latency
  time_compare time_compare (
    .clk(tb_clk), .reset(tb_rst),
    .time_now(vita_time), .trigger_time(m_set_time),
    .now(now), .early(), .late(late), .too_early()
  );

  // Clock crossing FIFO (readback)
  reg [RB_DWIDTH-1:0] cached_rb_data;
  axi_fifo_2clk #(
    .WIDTH(RB_DWIDTH), .SIZE(0)
  ) rbdata_2clk_fifo_i (
    .reset(tb_rst),
    .i_aclk(tb_clk), .i_tdata(cached_rb_data), .i_tvalid(set_finished), .i_tready(),
    .o_aclk(sb_clk), .o_tdata(s_rb_data), .o_tvalid(rb_valid), .o_tready(s_rb_stb)
  );

  // Output state machine
  reg [2:0] out_state = ST_IDLE;
  always @(posedge tb_clk) begin
    if (tb_rst) begin
      out_state <= ST_IDLE;
    end else begin
      case (out_state)
        ST_IDLE: begin
          if (go & set_pending) begin
            out_state <= ST_SET_ISSUED;
          end
        end
        ST_SET_ISSUED: begin
          out_state <= ST_SET_PENDING;
        end
        ST_SET_PENDING: begin
          if (~m_set_pending) begin
            if (m_rb_stb) begin
              out_state <= ST_RB_DONE;
              cached_rb_data <= m_rb_data;
            end else begin
              out_state <= ST_RB_PENDING;
            end
          end
        end
        ST_RB_PENDING: begin
          if (m_rb_stb) begin
            out_state <= ST_RB_DONE;
            cached_rb_data <= m_rb_data;
          end
        end
        ST_RB_DONE: begin
          out_state <= ST_IDLE;
        end
        default: begin
          out_state <= ST_IDLE;
        end
      endcase
    end
  end

  assign m_set_stb = (out_state == ST_SET_ISSUED);
  assign set_finished = (out_state == ST_RB_DONE);

endmodule