// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Supports packed and unpacked modes
// Uses FIFO timing on the control signals
// No masking or flush functions supported

// Timings - case where InW < OutW
// clk_i      __|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__
// wvalid_i   _____|~~~~|_____|~~~~|_____|~~~~|_____|~~~~|___________________
// wdata_i    Val N     |Val N+1   |Val N+2   |Val N+3   |-------------------
// wready_o   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|__________|~~~~~~~~
// rvalid_o   ___________________________________________|~~~~~~~~~~|________
// rdata_o    -------------------------------------------|Val       |--------
// rready_i   _________________________________________________|~~~~|________
// depth_o    0000000000|1111111111|2222222222|3333333333|4444444444|00000000


// Timings - case where InW > OutW
// clk_i      __|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__
// wvalid_i   _____|~~~~|____________________________________________________
// wdata_i    -----|Val |----------------------------------------------------
// wready_o   ~~~~~~~~~~|___________________________________________|~~~~~~~~
// rvalid_o   __________|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|________
// rdata_o    ----------|Val N     |Val N+1   |Val N+2   |Val N+3   |--------
// rready_i   ________________|~~~~|_____|~~~~|_____|~~~~|_____|~~~~|________
// depth_o    0000000000|4444444444|3333333333|2222222222|1111111111|00000000


// Timings - case where InW = OutW
// clk_i      __|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__~~|__|~~|__
// wvalid_i   _____|~~~~|____________________________________________________
// wdata_i    -----|Val |----------------------------------------------------
// wready_o   ~~~~~~~~~~|__________|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// rvalid_o   __________|~~~~~~~~~~|_________________________________________
// rdata_o    ----------|Val       |-----------------------------------------
// rready_i   ________________|~~~~|_________________________________________
// depth_o    0000000000|1111111111|00000000000000000000000000000000000000000


`include "prim_assert.sv"

module prim_packer_fifo #(
  parameter int InW  = 32,
  parameter int OutW = 8,
  parameter bit ClearOnRead = 1'b1, // if == 1 always output 0 after read
  // derived parameters
  localparam int MaxW = (InW > OutW) ? InW : OutW,
  localparam int MinW = (InW < OutW) ? InW : OutW,
  localparam int DepthW = $clog2(MaxW/MinW)
) (
  input logic clk_i ,
  input logic rst_ni,

  input logic               clr_i,
  input logic               wvalid_i,
  input logic  [InW-1:0]    wdata_i,
  output logic              wready_o,

  output logic              rvalid_o,
  output logic [OutW-1:0]   rdata_o,
  input logic               rready_i,
  output logic [DepthW:0]   depth_o
);

  localparam int unsigned   WidthRatio = MaxW / MinW;
  localparam bit [DepthW:0] FullDepth = WidthRatio[DepthW:0];
  localparam bit [DepthW:0] DepthOne = 1;

  // signals
  logic  load_data;
  logic  clear_data;
  logic  clear_status;

  // flops
  logic [DepthW:0] depth_q, depth_d;
  logic [MaxW-1:0] data_q, data_d;
  logic            clr_q, clr_d;

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (!rst_ni) begin
      depth_q <= '0;
      data_q  <= '0;
      clr_q   <= 1'b1;
    end else begin
      depth_q <= depth_d;
      data_q  <= data_d;
      clr_q   <= clr_d;
    end
  end

  // flop for handling reset case for clr
  assign clr_d = clr_i;

  assign depth_o = depth_q;

  if (InW < OutW) begin : gen_pack_mode
    logic [MaxW-1:0] wdata_shifted;

    assign wdata_shifted = {{OutW - InW{1'b0}}, wdata_i} << (depth_q*InW);
    assign clear_status = (rready_i && rvalid_o) || clr_q;
    assign clear_data = (ClearOnRead && clear_status) || clr_q;
    assign load_data = wvalid_i && wready_o;

    assign depth_d =  clear_status ? '0 :
           load_data ? (depth_q + DepthOne):
           depth_q;

    assign data_d = clear_data ? '0 :
           load_data ? (wdata_shifted | (depth_q == 0 ? '0 : data_q)) :
           data_q;

    // set outputs
    assign wready_o = !(depth_q == FullDepth) && !clr_q;
    assign rdata_o =  data_q;
    assign rvalid_o = (depth_q == FullDepth) && !clr_q;

  end else begin : gen_unpack_mode
    logic [MaxW-1:0] rdata_shifted;
    logic            pull_data;
    logic [DepthW:0] ptr_q, ptr_d;
    logic [DepthW:0] lsb_is_one;
    logic [DepthW:0] max_value;

    always_ff @(posedge clk_i or negedge rst_ni) begin
      if (!rst_ni) begin
        ptr_q   <= '0;
      end else begin
        ptr_q   <= ptr_d;
      end
    end

    assign lsb_is_one = {{DepthW{1'b0}},1'b1};
    assign max_value = FullDepth;
    assign rdata_shifted = data_q >> ptr_q*OutW;
    assign clear_status = (rready_i && (depth_q == lsb_is_one)) || clr_q;
    assign clear_data = (ClearOnRead && clear_status) || clr_q;
    assign load_data = wvalid_i && wready_o;
    assign pull_data = rvalid_o && rready_i;

    assign depth_d =  clear_status ? '0 :
           load_data ? max_value :
           pull_data ? (depth_q - DepthOne) :
           depth_q;

    assign ptr_d =  clear_status ? '0 :
           pull_data ? (ptr_q + DepthOne) :
           ptr_q;

    assign data_d = clear_data ? '0 :
           load_data ? wdata_i :
           data_q;

    // set outputs
    assign wready_o = (depth_q == '0) && !clr_q;
    assign rdata_o =  rdata_shifted[OutW-1:0];
    assign rvalid_o = !(depth_q == '0) && !clr_q;

    // Avoid possible lint errors in case InW > OutW.
    if (InW > OutW) begin : gen_unused
      logic [MaxW-MinW-1:0] unused_rdata_shifted;
      assign unused_rdata_shifted = rdata_shifted[MaxW-1:MinW];
    end
  end


  //////////////////////////////////////////////
  // Assertions, Assumptions, and Coverpoints //
  //////////////////////////////////////////////

  // If not acked, valid_o should keep asserting
  `ASSERT(ValidOPairedWithReadyI_A,
          rvalid_o && !rready_i && !clr_i |=> rvalid_o)

  // If output port doesn't accept the data, the data should be stable
  `ASSERT(DataOStableWhenPending_A,
          ##1 rvalid_o && $past(rvalid_o)
          && !$past(rready_i) && !$past(clr_i) |-> $stable(rdata_o))

endmodule
