// Copyright 2018 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

// Randomizing Stream (Ready/Valid) Master
module rand_stream_mst #(
  parameter type  data_t = logic,
  // Minimum number of clock cycles to wait between applying two consecutive values.
  parameter int   MinWaitCycles = -1,
  // Maximum number of clock cycles to wait between applying two consecutive values.
  parameter int   MaxWaitCycles = -1,
  // Application delay: time delay before output changes after an active clock edge.
  parameter time  ApplDelay = 0ps,
  // Acquisition delay: time delay before ready input is read after an active clock edge.
  parameter time  AcqDelay = 0ps
) (
  input  logic    clk_i,
  input  logic    rst_ni,

  output data_t   data_o,
  output logic    valid_o,
  input  logic    ready_i
);

  int unsigned rand_wait_cycles;

  function static void randomize_wait_cycles();
    int unsigned rand_success;
    rand_success = std::randomize(rand_wait_cycles) with {
      rand_wait_cycles >= MinWaitCycles;
      rand_wait_cycles <= MaxWaitCycles;
    };
    assert (rand_success) else $error("Failed to randomize wait cycles!");
  endfunction

  initial begin
    data_o  = '0;
    valid_o = 1'b0;
    wait (rst_ni);
    // Initially pick a random number of cycles to wait until we offer the first valid data.
    randomize_wait_cycles();
    @(posedge clk_i);
    forever begin
      // Wait for the picked number of clock cycles.
      repeat(rand_wait_cycles) begin
        @(posedge clk_i);
      end
      // Delay application of data and valid output.
      #(ApplDelay);
      // Randomize data output and set valid output.
      void'(std::randomize(data_o));
      valid_o = 1'b1;
      // Delay acquisition of ready signal. AcqDelay is relative to the clock edge, and we have
      // already waited for ApplDelay in this edge, so we need to subtract ApplDelay.
      #(AcqDelay-ApplDelay);
      // Sample the ready input. While the slave is not ready, wait a clock cycle plus the
      // acquisition delay and resample the ready input.
      while (!ready_i) begin
        @(posedge clk_i);
        #(AcqDelay);
      end
      // The slave is ready to acquire data on the next rising edge, so we pick a new number of
      // cycles to wait until we offer the next valid data.
      randomize_wait_cycles();
      if (rand_wait_cycles == 0) begin
        // If we have to wait 0 cycles, we apply new data directly after next clock edge plus the
        // application delay.
        @(posedge clk_i);
      end else begin
        // If we have to wait more than 0 cycles, we unset the valid output and randomize the data
        // output after the next clock edge plus the application delay.
        @(posedge clk_i);
        #(ApplDelay);
        valid_o = 1'b0;
        void'(std::randomize(data_o));
      end
    end
  end

  // Validate parameters.
`ifndef VERILATOR
  initial begin: validate_params
    assert (MinWaitCycles >= 0)
      else $fatal("The minimum number of wait cycles must be at least 0!");
    assert (MaxWaitCycles >= 0)
      else $fatal("The maximum number of wait cycles must be at least 0!");
    assert (MaxWaitCycles >= MinWaitCycles)
      else $fatal("The maximum number of wait cycles must be at least the minimum number of wait cycles!");
    assert (ApplDelay > 0ps)
      else $fatal("The application delay must be greater than 0!");
    assert (AcqDelay > 0ps)
      else $fatal("The acquisition delay must be greater than 0!");
    assert (AcqDelay > ApplDelay)
      else $fatal("The acquisition delay must be greater than the application delay!");
  end
`endif

endmodule