[cov methodology] Functional coverage prototype

- Implements functional coverage for UART in an interface
- Proof of concept to drive a designer-led functional coverage
implementation methodology
- See link below for more details:
https://docs.google.com/presentation/d/1rUFpWzktORuBZNyG6eSG68kWxEWmxMkMaWdSCadhbQI

Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/hw/dv/sv/dv_utils/dv_macros.svh b/hw/dv/sv/dv_utils/dv_macros.svh
index f169faf..e9413f8 100644
--- a/hw/dv/sv/dv_utils/dv_macros.svh
+++ b/hw/dv/sv/dv_utils/dv_macros.svh
@@ -492,4 +492,29 @@
   end
 `endif
 
+// Instantiates a covergroup in an interface or module.
+//
+// This macro assumes that a covergroup of the same name as the __CG_NAME arg is defined in the
+// interface or module. It just adds some extra signals and logic to control the creation of the
+// covergroup instance with ~bit en_<cg_name>~. This defaults to 0. It is ORed with the external
+// __COND signal. The testbench can modify it at t = 0 based on the test being run.
+// NOTE: This is not meant to be invoked inside a class.
+//
+// __CG_NAME : Name of the covergroup.
+// __COND    : External condition / expr that controls the creation of the covergroup.
+// __CG_ARGS : Arguments to covergroup instance, if any. Args MUST BE wrapped in (..).
+`ifndef DV_INSTANTIATE_CG
+`define DV_INSTANTIATE_CG(__CG_NAME, __COND = 1'b1, __CG_ARGS = ()) \
+  bit en_``__CG_NAME = 1'b0; \
+  __CG_NAME __CG_NAME``_inst; \
+  initial begin \
+    /* The #1 delay below allows any part of the tb to control the conditions first at t = 0. */ \
+    #1; \
+    if ((en_``__CG_NAME) || (__COND)) begin \
+      `DV_INFO({"Creating covergroup ", `"__CG_NAME`"}, UVM_MEDIUM) \
+      __CG_NAME``_inst = new``__CG_ARGS; \
+    end \
+  end
+`endif
+
 `endif // __DV_MACROS_SVH__
diff --git a/hw/dv/sv/dv_utils/dv_utils.core b/hw/dv/sv/dv_utils/dv_utils.core
index 4751513..5efce93 100644
--- a/hw/dv/sv/dv_utils/dv_utils.core
+++ b/hw/dv/sv/dv_utils/dv_utils.core
@@ -15,6 +15,7 @@
     files:
       - dv_utils_pkg.sv
       - dv_report_server.sv: {is_include_file: true}
+      - dv_vif_wrap.sv: {is_include_file: true}
     file_type: systemVerilogSource
 
 targets:
diff --git a/hw/dv/sv/dv_utils/dv_utils_pkg.sv b/hw/dv/sv/dv_utils/dv_utils_pkg.sv
index 46e2cc9..2c99462 100644
--- a/hw/dv/sv/dv_utils/dv_utils_pkg.sv
+++ b/hw/dv/sv/dv_utils/dv_utils_pkg.sv
@@ -152,9 +152,27 @@
   endfunction
 `endif
 
+  // Returns the hierarchical path to the interface / module N levels up.
+  //
+  // Meant to be invoked inside a module or interface.
+  // hier:        String input of the interface / module, typically $sformatf("%m").
+  // n_levels_up: Integer number of levels up the hierarchy to omit.
+  //              Example: if (hier = tb.dut.foo.bar, n_levels_up = 2), then return tb.dut
+  function automatic string get_parent_hier(string hier, int n_levels_up = 1);
+    int idx;
+    int level;
+    if (n_levels_up <= 0) return hier;
+    for (idx = hier.len() - 1; idx >= 0; idx--) begin
+      if (hier[idx] == ".") level++;
+      if (level == n_levels_up) break;
+    end
+    return (hier.substr(0, idx - 1));
+  endfunction
+
   // sources
 `ifdef UVM
   `include "dv_report_server.sv"
+  `include "dv_vif_wrap.sv"
 `endif
 
 endpackage
diff --git a/hw/dv/sv/dv_utils/dv_vif_wrap.sv b/hw/dv/sv/dv_utils/dv_vif_wrap.sv
new file mode 100644
index 0000000..61d9947
--- /dev/null
+++ b/hw/dv/sv/dv_utils/dv_vif_wrap.sv
@@ -0,0 +1,96 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Abstract class meant to hold arbitrary virtual interface handles.
+//
+// Written primarily for an interface which implements functional coverage, this could be used
+// for other purposes as well. This abstract class provides utilities & macros to retrieve
+// virtual interface handles that are bound to a DUT's sub-modules. These sub-module interfaces must
+// self-register using the `DV_VIF_WRAP_SET_VIF()` macro (see details below). The extended class
+// then implements the `get_vifs()` method using the `DV_VIF_WRAP_GET_VIF*` macros below to retrieve
+// the sub-module interface handles it maintains.
+virtual class dv_vif_wrap;
+  string hier;  // Represents the hierarchy of the parent module or interface.
+  string name;  // Name of the class instance.
+
+  function new(string hier, string name = "");
+    this.hier = hier;
+    this.name = name;
+  endfunction
+
+  // Abstract method implemented by the extended class. It is recommended to invoke the helper
+  // macros below rather than manually define it.
+  pure virtual task get_vifs();
+
+endclass
+
+// Helper macros.
+//
+// These are defined in the file itself since they are tightly coupled to the class definition
+// above. These are scoped globally so that extended classes can invoke them.
+
+// Enable an interface to register itself (set its handle into the config db).
+//
+// Meant to be invoked from an interface. The macros invocation causes the interface to register
+// itself into the uvm_resource_db pool. The derived class of dv_vif_wrap retrieves the handle to
+// that interface handle from the uvm_resource db pool.
+//
+// SV LRM does not yet allow referencing the instance of an interface within itself as one
+// would in case of a class using the ~this~ keyword. However, most major simulators actually
+// support this in their own custom way. On VCS, a reference to self within the interface can be set
+// using `interface::self()` construct. On Xcelium, this is achieved by simply referencing the
+// interface name.
+//
+// _IF:    The SV interface
+// _VIF:   The corresponding virtual interface handle name
+// _LEVEL: # of hierarchical levels the module to which the SV interface is bound, starting at the
+//         top level DUT instance.
+`define DV_VIF_WRAP_SET_VIF(_IF, _VIF, _LEVEL = 0) \
+  import uvm_pkg::*; \
+  function automatic void self_register(); \
+    string path; \
+    virtual _IF vif; \
+    /* Initial block adds another level in the path hierarchy which needs to be split out. */ \
+    /* Add one more to go one level up the interface instance. */ \
+    /* Example: tb.dut.core.u_foo_if.self_register -> tb.dut.core. */ \
+    path = dv_utils_pkg::get_parent_hier(.hier($sformatf("%m")), .n_levels_up(2 + _LEVEL)); \
+`ifdef VCS \
+  vif = interface::self(); \
+`elsif XCELIUM \
+  vif = _IF; \
+`else \
+  vif = _IF; \
+`endif \
+    uvm_pkg::uvm_resource_db#(virtual _IF)::set(path, `"_VIF`", vif); \
+  endfunction \
+  initial self_register();
+
+// Enables the retrieval of individual vifs.
+//
+// The three macros below go together to define the _get_vifs() task in the extended class.
+`define DV_VIF_WRAP_GET_VIFS_BEGIN \
+  task get_vifs(); \
+    fork \
+
+// To avoid race condition between the instant when an interface handle is set into the config db
+// and the instant when it is retrieved (in the same time step, at t = 0), the macro below invokes
+// uvm_config_db#(..)::wait_modified() to ensure that the retrieval is done only after the set.
+`define DV_VIF_WRAP_GET_VIF(_IF, _VIF) \
+    begin \
+      bit vif_found; \
+      /* At most 2 retries. */ \
+      repeat (2) begin \
+        /* Force the evaluation at the end of the time step. */ \
+        #0; \
+        if (uvm_pkg::uvm_resource_db#(virtual _IF)::read_by_name(hier, `"_VIF`", _VIF)) begin \
+          vif_found = 1'b1; \
+          break; \
+        end \
+      end \
+      `DV_CHECK_FATAL(vif_found, {`"_VIF`", " not found in the resource db"}, hier) \
+    end
+
+`define DV_VIF_WRAP_GET_VIFS_END \
+    join \
+  endtask
diff --git a/hw/dv/tools/dvsim/vcs.hjson b/hw/dv/tools/dvsim/vcs.hjson
index 6193f0c..812eae8 100644
--- a/hw/dv/tools/dvsim/vcs.hjson
+++ b/hw/dv/tools/dvsim/vcs.hjson
@@ -47,7 +47,9 @@
                //  - Write (deposit) capability on registers and variables
                //  - Force capability on registers, variables, and nets
                "-debug_access+f",
-
+               // Use this to conditionally compile for VCS (example: LRM interpretations differ
+               // across tools).
+               "+define+VCS",
                // Upgrade below warnings to errors to make VCS more strict on syntax to avoid
                // having issue with other simulators but passing with VCS
                //
diff --git a/hw/dv/tools/dvsim/xcelium.hjson b/hw/dv/tools/dvsim/xcelium.hjson
index 29de0a6..c61101b 100644
--- a/hw/dv/tools/dvsim/xcelium.hjson
+++ b/hw/dv/tools/dvsim/xcelium.hjson
@@ -20,8 +20,9 @@
                "-xmlibdirname {build_dir}/xcelium.d",
                // for uvm_hdl_* used by csr backdoor
                "-access +rw",
-               "+define+XCELIUM",
-               ]
+               // Use this to conditionally compile for Xcelium (example: LRM interpretations differ
+               // across tools).
+               "+define+XCELIUM"]
 
   run_opts:   ["-input {run_script}",
                "-licqueue",
diff --git a/hw/ip/uart/dv/cov/uart_core_cov_if.sv b/hw/ip/uart/dv/cov/uart_core_cov_if.sv
new file mode 100644
index 0000000..8ae2068
--- /dev/null
+++ b/hw/ip/uart/dv/cov/uart_core_cov_if.sv
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Implements functional coverage for uart_core.sv sub-module.
+interface uart_core_cov_if (
+  input logic tx_enable,
+  input logic rx_enable
+);
+
+  // This interface is bound to `uart_core` which is 1 level underneath `uart` which is the DUT top
+  // level module.
+  `DV_VIF_WRAP_SET_VIF(uart_core_cov_if, uart_core_cov_vif, 1)
+
+endinterface
diff --git a/hw/ip/uart/dv/cov/uart_cov.core b/hw/ip/uart/dv/cov/uart_cov.core
new file mode 100644
index 0000000..06d9f3c
--- /dev/null
+++ b/hw/ip/uart/dv/cov/uart_cov.core
@@ -0,0 +1,21 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:uart_cov"
+description: "UART functional coverage interface & bind."
+
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:dv_utils
+    files:
+      - uart_core_cov_if.sv
+      - uart_cov_if.sv
+      - uart_cov_bind.sv
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/ip/uart/dv/cov/uart_cov_bind.sv b/hw/ip/uart/dv/cov/uart_cov_bind.sv
new file mode 100644
index 0000000..5179124
--- /dev/null
+++ b/hw/ip/uart/dv/cov/uart_cov_bind.sv
@@ -0,0 +1,12 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Binds UART functional coverage interaface to the top level UART module.
+module uart_cov_bind;
+
+  bind uart uart_cov_if u_uart_cov_if (.*);
+
+  bind uart_core uart_core_cov_if u_uart_core_cov_if (.*);
+
+endmodule
diff --git a/hw/ip/uart/dv/cov/uart_cov_if.sv b/hw/ip/uart/dv/cov/uart_cov_if.sv
new file mode 100644
index 0000000..823e897
--- /dev/null
+++ b/hw/ip/uart/dv/cov/uart_cov_if.sv
@@ -0,0 +1,55 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Implements functional coverage for UART.
+interface uart_cov_if (
+  input logic clk_i
+);
+
+  import uvm_pkg::*;
+  import dv_utils_pkg::*;
+
+  bit en_full_cov = 1'b1;
+  bit en_intg_cov = 1'b1;
+
+  // If en_full_cov is set, then en_intg_cov must also be set since it is a subset.
+  bit en_intg_cov_loc;
+  assign en_intg_cov_loc = en_full_cov | en_intg_cov;
+
+  // References to cov interfaces bound to sub-modules need to be held inside a class.
+  class uart_cov_vifs_wrap extends dv_vif_wrap;
+    virtual uart_core_cov_if uart_core_cov_vif;
+
+    function new(string hier, string name);
+      super.new(hier, name);
+    endfunction
+
+    // The section below constructs the `get_vifs()` task using the helper macros. It retrieves the
+    // VIF handles bound to sub-modules from the uvm_resource_db.
+    `DV_VIF_WRAP_GET_VIFS_BEGIN
+      `DV_VIF_WRAP_GET_VIF(uart_core_cov_if, uart_core_cov_vif)
+    `DV_VIF_WRAP_GET_VIFS_END
+
+  endclass
+
+  uart_cov_vifs_wrap m_uart_cov_vifs_wrap;
+
+  initial begin
+    m_uart_cov_vifs_wrap = new(dv_utils_pkg::get_parent_hier(.hier($sformatf("%m"))),
+                               "m_uart_cov_vifs_wrap");
+    m_uart_cov_vifs_wrap.get_vifs();
+  end
+
+  covergroup uart_op_cg @(posedge clk_i);
+    option.name         = "uart_op_cg";
+    option.comment      = "UART TX and RX operations";
+    option.per_instance = 1;
+
+    cp_tx_enable:     coverpoint uart_core.tx_enable;
+    cp_rx_enable:     coverpoint m_uart_cov_vifs_wrap.uart_core_cov_vif.rx_enable;
+    cr_tx_rx_enable:  cross cp_tx_enable, cp_rx_enable;
+  endgroup
+  `DV_INSTANTIATE_CG(uart_op_cg, en_full_cov)
+
+endinterface
diff --git a/hw/ip/uart/dv/uart_sim.core b/hw/ip/uart/dv/uart_sim.core
index a279900..3ae0370 100644
--- a/hw/ip/uart/dv/uart_sim.core
+++ b/hw/ip/uart/dv/uart_sim.core
@@ -13,6 +13,7 @@
     depend:
       - lowrisc:dv:uart_test
       - lowrisc:dv:uart_sva
+      - lowrisc:dv:uart_cov
     files:
       - tb/tb.sv
     file_type: systemVerilogSource
diff --git a/hw/ip/uart/dv/uart_sim_cfg.hjson b/hw/ip/uart/dv/uart_sim_cfg.hjson
index 573b3db..9e1fdd4 100644
--- a/hw/ip/uart/dv/uart_sim_cfg.hjson
+++ b/hw/ip/uart/dv/uart_sim_cfg.hjson
@@ -33,7 +33,7 @@
                 "{proj_root}/hw/dv/tools/dvsim/tests/stress_tests.hjson"]
 
   // Add additional tops for simulation.
-  sim_tops: ["-top uart_bind"]
+  sim_tops: ["-top uart_bind", "-top uart_cov_bind"]
 
   // Default iterations for all tests - each test entry can override this.
   reseed: 50