[dv/sram] implement covergroups

This PR implements the covergroups listed in #6710,
and sample them in the scoreboard when appropriate.

Signed-off-by: Udi Jonnalagadda <udij@google.com>
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
index 3f89812..0f0f038 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cov.sv
@@ -16,11 +16,111 @@
   // sram_ctrl_env_cfg: cfg
 
   // covergroups
-  // [add covergroups here]
+
+  // cover that all access granularities have been tested for both memory reads and writes
+  covergroup subword_access_cg with function sample(bit we, bit [TL_DBW-1:0] mask);
+    subword_we: coverpoint we;
+    subword_granularity: coverpoint mask {
+      bins zero_access        = {'b0};
+      bins byte_access        = {'b0001, 'b0010, 'b0100, 'b1000};
+      bins halfword_access    = {'b0011, 'b0110, 'b1100};
+      bins triple_byte_access = {'b0111, 'b1110};
+      bins word_access        = {'b1111};
+      illegal_bins ill_access = default;
+    }
+    subword_access: cross subword_we, subword_granularity;
+  endgroup
+
+  // cover that SRAM handles mem accesses during key requests, both reads and writes
+  covergroup access_during_key_req_cg with function sample(tlul_pkg::tl_a_op_e opcode);
+    access_during_key_req: coverpoint opcode {
+      bins write            = {tlul_pkg::PutFullData, tlul_pkg::PutPartialData};
+      bins read             = {tlul_pkg::Get};
+      illegal_bins ill_type = default;
+    }
+  endgroup
+
+  // covers SRAM receiving a key in Off/On states,
+  // with both valid/invalid key seeds.
+  covergroup key_seed_valid_cg with function sample(bit in_lc_esc, bit seed_valid);
+      key_seed_valid: coverpoint seed_valid;
+      escalated: coverpoint in_lc_esc;
+
+      key_seed_valid_cross: cross key_seed_valid, escalated;
+  endgroup
+
+  // covers RAW hazards that are detected in the SRAM,
+  // specifically, whether these hazards are due to address collisions or not.
+  covergroup raw_hazard_cg with function sample(bit addr_collision);
+    raw_hazard: coverpoint addr_collision;
+  endgroup
+
+  // covers that all combinations of reads/writes can create b2b scenario
+  covergroup b2b_access_types_cg with function sample(bit first_write_en, bit second_write_en);
+    b2b_access_types: cross first_write_en, second_write_en;
+  endgroup
+
+  covergroup lc_escalation_rst_cg with function sample(bit rst_n);
+    lc_escalation_rst: coverpoint rst_n;
+  endgroup
+
+  // covers various scenarios that enable/disable SRAM executability
+  covergroup executable_cg with function sample(logic [3:0] lc_hw_debug_en,
+                                                logic [7:0] en_sram_ifetch,
+                                                logic [2:0] csr_exec);
+    // placeholder comment
+    en_sram_ifetch_cp: coverpoint en_sram_ifetch {
+      bins sram_ifetch_enable = {otp_ctrl_pkg::Enabled};
+      bins sram_ifetch_valid_disable = {otp_ctrl_pkg::Disabled};
+      bins sram_ifetch_invalid_disable = {
+          [0 : otp_ctrl_pkg::Disabled-1],
+          [otp_ctrl_pkg::Disabled+1 : otp_ctrl_pkg::Enabled-1],
+          [otp_ctrl_pkg::Enabled+1 : '1]};
+    }
+    lc_hw_debug_en_cp: coverpoint lc_hw_debug_en {
+      bins hw_debug_en_on           = {lc_ctrl_pkg::On};
+      bins hw_debug_en_valid_off    = {lc_ctrl_pkg::Off};
+      bins hw_debug_en_invalid_off  = {[0 : lc_ctrl_pkg::Off-1],
+                                       [lc_ctrl_pkg::Off+1 : lc_ctrl_pkg::On-1],
+                                       [lc_ctrl_pkg::On+1 : '1]};
+    }
+    csr_exec_cp : coverpoint csr_exec {
+      bins instr_en = {tlul_pkg::InstrEn};
+      bins instr_valid_dis = {tlul_pkg::InstrDis};
+      bins instr_invalid_dis = {[0 : tlul_pkg::InstrDis-1],
+                                [tlul_pkg::InstrDis+1 : tlul_pkg::InstrEn-1],
+                                [tlul_pkg::InstrEn+1 : '1]};
+    }
+    executable_cross: cross lc_hw_debug_en_cp, en_sram_ifetch_cp, csr_exec_cp {
+      bins csr_exec_en = binsof(en_sram_ifetch_cp.sram_ifetch_enable) &&
+                         binsof(csr_exec_cp.instr_en) &&
+                         binsof(lc_hw_debug_en_cp);
+
+      bins lc_exec_en = binsof(en_sram_ifetch_cp.sram_ifetch_enable) &&
+                        binsof(lc_hw_debug_en_cp.hw_debug_en_on) &&
+                        binsof(csr_exec_cp);
+
+      bins valid_exec_dis = (binsof(en_sram_ifetch_cp.sram_ifetch_enable) &&
+                             binsof(csr_exec_cp.instr_valid_dis)) ||
+                            (binsof(en_sram_ifetch_cp.sram_ifetch_valid_disable) &&
+                             binsof(lc_hw_debug_en_cp.hw_debug_en_valid_off));
+
+      bins invalid_exec_dis = (binsof(en_sram_ifetch_cp.sram_ifetch_enable) &&
+                               binsof(csr_exec_cp.instr_invalid_dis)) ||
+                              (binsof(en_sram_ifetch_cp.sram_ifetch_invalid_disable) &&
+                               binsof(lc_hw_debug_en_cp.hw_debug_en_invalid_off));
+    }
+  endgroup
 
   function new(string name, uvm_component parent);
     super.new(name, parent);
-    // [instantiate covergroups here]
+    subword_access_cg         = new();
+    access_during_key_req_cg  = new();
+    key_seed_valid_cg         = new();
+    raw_hazard_cg             = new();
+    b2b_access_types_cg       = new();
+    lc_escalation_rst_cg      = new();
+    executable_cg             = new();
   endfunction : new
 
   virtual function void build_phase(uvm_phase phase);
diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
index 22b843f..39a69a1 100644
--- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
+++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv
@@ -234,6 +234,7 @@
   task run_phase(uvm_phase phase);
     super.run_phase(phase);
     fork
+      sample_key_req_access_cg();
       process_sram_init();
       process_lc_escalation();
       process_sram_executable();
@@ -244,6 +245,35 @@
     join_none
   endtask
 
+  // This task spins forever and samples the appropriate covergroup whenever
+  // in_key_req is high and a new valid addr_phase transaction is seen on the memory bus.
+  virtual task sample_key_req_access_cg();
+    forever begin
+      @(negedge cfg.under_reset);
+      `DV_SPINWAIT_EXIT(
+          forever begin
+            @(posedge in_key_req);
+            `DV_SPINWAIT_EXIT(
+                forever begin
+                  // sample the covergroup every time a new TL request is seen
+                  // while a key request is outstanding.
+                  @(posedge cfg.m_sram_cfg.vif.h2d.a_valid);
+                  // zero delay to allow bus values to settle
+                  #0;
+                  if (cfg.en_cov) begin
+                    cov.access_during_key_req_cg.sample(cfg.m_sram_cfg.vif.h2d.a_opcode);
+                  end
+                end
+                ,
+                @(negedge in_key_req);
+            )
+          end
+          ,
+          wait(cfg.under_reset == 1);
+      )
+    end
+  endtask
+
   virtual task process_sram_init();
     // SRAM initialization happens once at the beginning of each simulation and requires a key to be
     // provisioned from OTP first.
@@ -322,6 +352,11 @@
 
       // lc escalation status will be dropped after reset, no further action needed
       wait(cfg.lc_vif.lc_esc_en == lc_ctrl_pkg::Off);
+
+      // sample coverage
+      if (cfg.en_cov) begin
+        cov.lc_escalation_rst_cg.sample(cfg.clk_rst_vif.rst_n);
+      end
     end
   endtask
 
@@ -335,6 +370,13 @@
       detected_hw_debug_en = cfg.exec_vif.lc_hw_debug_en;
       detected_en_sram_ifetch = cfg.exec_vif.otp_en_sram_ifetch;
 
+      // sample executability-related coverage
+      if (cfg.en_cov) begin
+        cov.executable_cg.sample(detected_hw_debug_en,
+                                 detected_en_sram_ifetch,
+                                 detected_csr_exec);
+      end
+
       `uvm_info(`gfn, $sformatf("detected_hw_debug_en: %0b", detected_hw_debug_en), UVM_HIGH)
       `uvm_info(`gfn, $sformatf("detected_en_sram_ifetch: %0b", detected_en_sram_ifetch), UVM_HIGH)
 
@@ -359,7 +401,9 @@
 
         if (!cfg.en_scb) continue;
 
-        if (in_key_req) continue;
+        if (in_key_req) begin
+          `uvm_error(`gfn, "Received SRAM_TLUL transaction while requesting a new key")
+        end
 
         // If the escalation propagation has finished,
         // do not process anymore addr_phase transactions
@@ -507,6 +551,11 @@
           //
           // as a result we need to check for an address collision then act accordingly.
 
+          // sample b2b-related coovergroup
+          if (cfg.en_cov) begin
+            cov.b2b_access_types_cg.sample(data_trans.we, addr_trans.we);
+          end
+
           // if we have an address collision (read address is the same as the pending write address)
           // return data based on the `held_data`
           if (eq_sram_addr(data_trans.addr, held_trans.addr)) begin
@@ -542,6 +591,11 @@
 
           `uvm_info(`gfn, $sformatf("addr_trans: %0p", addr_trans), UVM_HIGH)
 
+          // sample b2b-related covergroup
+          if (cfg.en_cov) begin
+            cov.b2b_access_types_cg.sample(data_trans.we, addr_trans.we);
+          end
+
           if (addr_trans.we == 0) begin
             // if we see a read directly after a write and we are not currently in a RAW hazard
             // handling state, we need to do the following:
@@ -559,6 +613,11 @@
             waddr = {data_trans.addr[TL_AW-1:2], 2'b00};
             held_data = cfg.mem_bkdr_vif.sram_encrypt_read32(waddr, data_trans.key, data_trans.nonce);
 
+            // sample covergroup
+            if (cfg.en_cov) begin
+              cov.raw_hazard_cg.sample(waddr == word_align_addr(addr_trans.addr));
+            end
+
             for (int i = 0; i < TL_DBW; i++) begin
               if (data_trans.mask[i]) begin
                 held_data[i*8 +: 8] = data_trans.data[i*8 +: 8];
@@ -596,6 +655,11 @@
       // When KDI item is seen, update key, nonce
       {key, nonce, seed_valid} = item.d_data;
 
+      // sample coverage on seed_valid
+      if (cfg.en_cov) begin
+        cov.key_seed_valid_cg.sample(status_lc_esc, seed_valid);
+      end
+
       // scr_key_valid simply denotes that a successful handshake with OTP has completed,
       // so this will be 1 whenever we get a copmleted transaction item
       exp_status[SramCtrlScrKeyValid]     = 1;
@@ -621,6 +685,11 @@
     forever begin
       completed_trans_mbox.get(trans);
 
+      // sample access granularity for each completed transaction
+      if (cfg.en_cov) begin
+        cov.subword_access_cg.sample(trans.we, trans.mask);
+      end
+
       `uvm_info({`gfn, "::process_completed_trans()"},
                 $sformatf("Checking SRAM memory transaction: %0p", trans),
                 UVM_HIGH)