// Copyright 2020 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51

/// Hardware implementation of SystemVerilog's `$onehot()` function.
/// It uses a tree of half adders and a separate
/// or reduction tree for the carry.

// Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch>
// Author: Fabian Schuiki <fschuiki@iis.ee.ethz.ch>
// Author: Stefan Mach <smach@iis.ee.ethz.ch>
module onehot #(
  parameter int unsigned Width = 4
) (
  input  logic [Width-1:0] d_i,
  output logic is_onehot_o
);
  // trivial base case
  if (Width == 1) begin : gen_degenerated_onehot
    assign is_onehot_o = d_i;
  end else begin : gen_onehot
    localparam int LVLS = $clog2(Width) + 1;

    logic [LVLS-1:0][2**(LVLS-1)-1:0] sum, carry;
    logic [LVLS-2:0] carry_array;

    // Extend to a power of two.
    assign sum[0] = $unsigned(d_i);

    // generate half adders for each lvl
    // lvl 0 is the input level
    for (genvar i = 1; i < LVLS; i++) begin
      localparam LVL_WIDTH = 2**LVLS / 2**i;
      for (genvar j = 0; j < LVL_WIDTH; j+=2) begin
        assign sum[i][j/2] = sum[i-1][j] ^ sum[i-1][j+1];
        assign carry[i][j/2] = sum[i-1][j] & sum[i-1][j+1];
      end
      // generate carry tree
      assign carry_array[i-1] = |carry[i][LVL_WIDTH/2-1:0];
    end
    assign is_onehot_o = sum[LVLS-1][0] & ~|carry_array;
  end

endmodule