// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// Interface: pins_if
// Description: Pin interface for driving and sampling individual pins such as interrupts, alerts
// and gpios.
`ifndef SYNTHESIS

interface [docs]pins_if #(
  parameter int Width = 1,
  parameter bit [8*4-1:0] PullStrength = "Pull"
) (
  inout [Width-1:0] pins
);

  `include "prim_assert.sv"

  `ASSERT_INIT(PullStrengthParamValid, PullStrength inside {"Weak", "Pull"})

  logic [Width-1:0] pins_o;       // value to be driven out
  bit   [Width-1:0] pins_oe = '0; // output enable
  bit   [Width-1:0] pins_pd = '0; // pull down enable
  bit   [Width-1:0] pins_pu = '0; // pull up enable

  // function to set pin output enable for specific pin (useful for single pin interface)
  function automatic void [docs]drive_en_pin(int idx = 0, bit val);
    pins_oe[idx] = val;
  endfunction

  // function to set pin output enable for all pins
  function automatic void [docs]drive_en(bit [Width-1:0] val);
    pins_oe = val;
  endfunction

  // function to drive a specific pin with a value (useful for single pin interface)
  function automatic void [docs]drive_pin(int idx = 0, logic val);
    pins_oe[idx] = 1'b1;
    pins_o[idx] = val;
  endfunction // drive_pin

  // function to drive all pins
  function automatic void [docs]drive(logic [Width-1:0] val);
    pins_oe = {Width{1'b1}};
    pins_o = val;
  endfunction // drive

  // function to drive all pull down values
  function automatic void [docs]set_pulldown_en(bit [Width-1:0] val);
    pins_pd = val;
  endfunction // set_pulldown_en

  // function to drive all pull up values
  function automatic void [docs]set_pullup_en(bit [Width-1:0] val);
    pins_pu = val;
  endfunction // set_pullup_en

  // function to drive the pull down value on a specific pin
  function automatic void [docs]set_pulldown_en_pin(int idx = 0, bit val);
    pins_pd[idx] = val;
  endfunction // set_pulldown_en_pin

  // function to drive the pull up value on a specific pin
  function automatic void [docs]set_pullup_en_pin(int idx = 0, bit val);
    pins_pu[idx] = val;
  endfunction // set_pullup_en_pin

  // function to sample a specific pin (useful for single pin interface)
  function automatic logic [docs]sample_pin(int idx = 0);
    return pins[idx];
  endfunction

  // function to sample all pins
  function automatic logic [Width-1:0] [docs]sample();
    return pins;
  endfunction

  // Fully disconnect this interface, including the pulls.
  function automatic void [docs]disconnect();
    pins_oe = {Width{1'b0}};
    pins_pu = {Width{1'b0}};
    pins_pd = {Width{1'b0}};
  endfunction

  // make connections
  for (genvar i = 0; i < Width; i++) begin : gen_each_pin
`ifdef VERILATOR
    assign pins[i] = pins_oe[i] ? pins_o[i] :
                     pins_pu[i] ? 1'b1 :
                     pins_pd[i] ? 1'b0 : 1'bz;
`else
    // Drive the pin based on whether pullup / pulldown is enabled.
    //
    // If output is not enabled, then the pin is pulled up or down with the `PullStrength` strength
    // Pullup has priority over pulldown.
    if (PullStrength == "Pull") begin : gen_pull_strength_pull
      assign (pull0, pull1) pins[i] = ~pins_oe[i] ? (pins_pu[i] ? 1'b1 :
                                                     pins_pd[i] ? 1'b0 : 1'bz) : 1'bz;
    end : gen_pull_strength_pull
    else if (PullStrength == "Weak") begin : gen_pull_strength_weak
      assign (weak0, weak1) pins[i] = ~pins_oe[i] ? (pins_pu[i] ? 1'b1 :
                                                     pins_pd[i] ? 1'b0 : 1'bz) : 1'bz;
    end : gen_pull_strength_weak

    // If output enable is 1, strong driver assigns pin to 'value to be driven out';
    // the external strong driver can still affect pin, if exists.
    // Else if output enable is 0, weak pullup or pulldown is applied to pin.
    // By doing this, we make sure that weak pullup or pulldown does not override
    // any 'x' value on pin, that may result due to conflicting values
    // between 'value to be driven out' and the external driver's value.
    assign pins[i] = pins_oe[i] ? pins_o[i] : 1'bz;
`endif
  end : gen_each_pin

endinterface
`endif