// (C) 2001-2021 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.


//////////////////////////////////////////////////////////////////////////////
// The random number generator uses the LFSR module to generate random numbers
// within a parametrizable range.
//////////////////////////////////////////////////////////////////////////////
module altera_emif_avl_tg_rand_num_gen # (
   parameter RAND_NUM_WIDTH                    = "",
   parameter RAND_NUM_MIN                      = "",
   parameter RAND_NUM_MAX                      = "",
   parameter RAND_NUM_IS_LESS_THAN_THRESHOLD   = 0
) (
   // Clock and reset
   input  logic                        clk,
   input  logic                        reset_n,

   // Control and status
   input  logic                        enable,
   output logic                        ready,

   // Random number generator output
   output logic [RAND_NUM_WIDTH-1:0]   rand_num,
   output logic                        is_less_than
);
   timeunit 1ns;
   timeprecision 1ps;
   
   import avl_tg_defs::*;

   // Derive LFSR parameters
   localparam LFSR_DATA_RANGE   = RAND_NUM_MAX - RAND_NUM_MIN + 1;
   localparam LFSR_DATA_WIDTH   = ceil_log2(LFSR_DATA_RANGE);
   localparam LFSR_WIDTH        = max(4, ceil_log2(LFSR_DATA_RANGE + 1));

   generate
   if (RAND_NUM_MIN == RAND_NUM_MAX)
   begin : constant_gen
      // The max and min of the range equal
      // Simply output a constant number

      assign ready = 1'b1;
      assign rand_num = RAND_NUM_MIN;
      assign is_less_than = (RAND_NUM_MIN < RAND_NUM_IS_LESS_THAN_THRESHOLD) ? 1'b1 : 1'b0;

   end else if (RAND_NUM_MIN < RAND_NUM_MAX)
   begin : random_gen
      // Instantiate the LFSR which is automatically run
      // until the output is within the specified range

      // Registered random number output
      logic                        rand_num_valid_reg;
      logic [RAND_NUM_WIDTH-1:0]   rand_num_reg;
      logic                        is_less_than_reg;

      // LFSR output
      logic                        lfsr_valid;
      logic [LFSR_WIDTH-1:0]       lfsr_data;

      assign ready        = rand_num_valid_reg;
      assign rand_num     = rand_num_reg;
      assign is_less_than = is_less_than_reg;

      // The LFSR output is valid if it is in the range of 0 and LFSR_DATA_RANGE
      assign lfsr_valid = lfsr_data[LFSR_DATA_WIDTH-1:0] < LFSR_DATA_RANGE;

      // Output the number within range by adding RAND_NUM_MIN
      always_ff @(posedge clk)
      begin
         if (!reset_n) begin
            rand_num_valid_reg <= 1'b0;
            rand_num_reg <= '0;
            is_less_than_reg <= 1'b0;
            
         end else if ((!rand_num_valid_reg && lfsr_valid) || enable) begin
            rand_num_valid_reg <= lfsr_valid;
            rand_num_reg <= lfsr_data[LFSR_DATA_WIDTH-1:0] + RAND_NUM_MIN[RAND_NUM_WIDTH-1:0];
            is_less_than_reg <= ((lfsr_data[LFSR_DATA_WIDTH-1:0] + RAND_NUM_MIN[RAND_NUM_WIDTH-1:0]) < RAND_NUM_IS_LESS_THAN_THRESHOLD) ? 1'b1 : 1'b0;
         end
      end

      // The LFSR module
      altera_emif_avl_tg_lfsr # (
         .WIDTH    (LFSR_WIDTH)
      ) lfsr_inst (
         .clk      (clk),
         .reset_n  (reset_n),
         .enable   (~lfsr_valid | ~rand_num_valid_reg | enable),
         .data     (lfsr_data)
      );
   end
   endgenerate

   // Simulation assertions
   // synthesis translate_off
   initial
   begin
      assert (RAND_NUM_MAX >= RAND_NUM_MIN) else $error ("Invalid random number range");
      assert (RAND_NUM_MAX < 2**RAND_NUM_WIDTH) else $error ("Invalid random number width");
   end
   // synthesis translate_on
endmodule

