[dv/kmac] enable EDN entropy

this PR enables test sequences to randomly select EDN entropy as a
possible entropy source, and makes the necessary updates to the
scoreboard to support this.

note that this set of changes only affects `kmac_masked` configuration,
since unmasked config does not use entropy.

Signed-off-by: Udi Jonnalagadda <udij@google.com>
diff --git a/hw/ip/kmac/data/kmac_base_testplan.hjson b/hw/ip/kmac/data/kmac_base_testplan.hjson
index c745ffd..0f74dd2 100644
--- a/hw/ip/kmac/data/kmac_base_testplan.hjson
+++ b/hw/ip/kmac/data/kmac_base_testplan.hjson
@@ -21,6 +21,7 @@
               - Randomly set endianness of input msg and internal keccak state.
               - Randomly provide a sideloaded key, do not set cfg.sideload.
               - Set output length between 1-`keccak_rate` bytes if applicable.
+              - Randomly select either SW or EDN as the source of entropy
               - Trigger KMAC to start absorbing input message.
                 - During absorption stage randomly read from STATE window, expect 0.
               - Write message to MSG_FIFO window, maximum length of 32 bytes.
diff --git a/hw/ip/kmac/dv/env/kmac_env_pkg.sv b/hw/ip/kmac/dv/env/kmac_env_pkg.sv
index fdbdd9a..922a345 100644
--- a/hw/ip/kmac/dv/env/kmac_env_pkg.sv
+++ b/hw/ip/kmac/dv/env/kmac_env_pkg.sv
@@ -91,7 +91,10 @@
   parameter int CYCLES_TO_FILL_ENTROPY = ENTROPY_STORAGE_WIDTH / ENTROPY_LFSR_WIDTH;
 
   // 7 cycles total:                                     5 cycles        + 2 cycles (latch/consume entropy)
-  parameter int SW_ENTROPY_ROUND_CYCLES_NO_FAST = CYCLES_TO_FILL_ENTROPY + 2;
+  parameter int ENTROPY_FULL_EXPANSION_CYCLES = CYCLES_TO_FILL_ENTROPY + 2;
+
+  // 3 cycles total:                         1 cycle, entropy is reused + 2 cycles (latch/consume)
+  parameter int ENTROPY_FAST_PROCESSING_CYCLES = 3;
 
   // interrupt types
   typedef enum int {
diff --git a/hw/ip/kmac/dv/env/kmac_scoreboard.sv b/hw/ip/kmac/dv/env/kmac_scoreboard.sv
index fc82286..878663f 100644
--- a/hw/ip/kmac/dv/env/kmac_scoreboard.sv
+++ b/hw/ip/kmac/dv/env/kmac_scoreboard.sv
@@ -60,6 +60,14 @@
   // is transmitted via kmac_app interface and received
   bit got_data_from_kmac_app = 0;
 
+  // The CFG.entropy_ready field is only used to transition the entropy FSM into fetching entropy
+  // from the reset state, so we can only rely on writes to CFG.entropy_ready to update internal
+  // scoreboard state after a reset is seen.
+  //
+  // To that effect, we set this bit to 1 any time the scoreboard is reset, and will unset it
+  // the first time that CFG.entropy_ready is updated.
+  bit first_op_after_rst = 0;
+
   // CFG fields
   bit kmac_en;
   sha3_pkg::sha3_mode_e hash_mode;
@@ -68,6 +76,13 @@
   bit entropy_fast_process;
   bit entropy_ready;
 
+  // Set this bit when entropy_ready is 1 and entropy_mode is EntropyModeEdn,
+  // to indicate that we are now waiting on the EDN to return valid entropy
+  bit in_edn_fetch = 0;
+
+  // This bit indicates that the KMAC is performing an entropy refresh
+  bit refresh_entropy = 0;
+
   // CMD fields
   kmac_cmd_e kmac_cmd = CmdNone;
 
@@ -161,6 +176,7 @@
     super.run_phase(phase);
     fork
       detect_kmac_app_start();
+      process_edn();
       process_prefix_and_keys();
       process_msgfifo_write();
       process_msgfifo_status();
@@ -176,6 +192,36 @@
     join_none
   endtask
 
+  // This task waits until an entropy request is sent,
+  // then waits for valid entropy to be returned from EDN
+  virtual task process_edn();
+    push_pull_agent_pkg::push_pull_item #(.DeviceDataWidth(cip_base_pkg::EDN_DATA_WIDTH)) edn_item;
+    @(negedge cfg.under_reset);
+    forever begin
+      wait(!cfg.under_reset);
+      @(posedge in_edn_fetch);
+      // Entropy interface is native 32 bits - prim_edn_req component internally
+      // does as many EDN fetches as necessary to fill up the required data bus size
+      // of the "host", in this case KMAC needs 64 bits of entropy so prim_edn_req
+      // performs 2 fetches from the EDN network.
+      repeat (kmac_pkg::MsgWidth / cip_base_pkg::EDN_BUS_WIDTH) begin
+        edn_fifo.get(edn_item);
+      end
+      `uvm_info(`gfn, "got all edn transactions", UVM_HIGH)
+      // Receiving the last EDN sequence item is synchronized on the EDN clock,
+      // so we need to synchronize into the KMAC clock domain.
+      // This takes 4 clock cycles total, on the last cycle the entropy is marked as valid
+      // to the keccak logic and any pending keccak rounds can begin.
+      cfg.clk_rst_vif.wait_clks(4);
+      in_edn_fetch = 0;
+      `uvm_info(`gfn, "dropped in_edn_fetch", UVM_HIGH)
+      if (refresh_entropy) begin
+        refresh_entropy = 0;
+        `uvm_info(`gfn, "dropped refresh_entropy", UVM_HIGH)
+      end
+    end
+  endtask
+
   // This task will check for any sideload keys that have been provided
   virtual task process_sideload_key();
     forever begin
@@ -284,33 +330,50 @@
   // the KMAC_APP digest and clearing internal state for the next hash operation.
   virtual task process_kmac_app_rsp_fifo();
     kmac_app_item kmac_app_rsp;
-    forever begin
-      wait(!cfg.under_reset);
-      @(posedge in_kmac_app);
-      `uvm_info(`gfn, $sformatf("rsp app_mode: %0s", app_mode.name()), UVM_HIGH)
-      `DV_SPINWAIT_EXIT(
-          kmac_app_rsp_fifo[app_mode].get(kmac_app_rsp);
-          `uvm_info(`gfn, $sformatf("Detected a KMAC_APP response:\n%0s", kmac_app_rsp.sprint()), UVM_HIGH)
+    fork
+      begin
+        forever begin
+          wait(!cfg.under_reset);
+          @(posedge in_kmac_app);
+          `uvm_info(`gfn, $sformatf("rsp app_mode: %0s", app_mode.name()), UVM_HIGH)
+          `DV_SPINWAIT_EXIT(
+              kmac_app_rsp_fifo[app_mode].get(kmac_app_rsp);
+              `uvm_info(`gfn, $sformatf("Detected a KMAC_APP response:\n%0s", kmac_app_rsp.sprint()), UVM_HIGH)
 
-          // safety check that things are working properly and no random KMAC_APP operations are seen
-          `DV_CHECK_FATAL(in_kmac_app == 1, "in_kmac_app is not set, scoreboard has not picked up KMAC_APP request")
+              // safety check that things are working properly and no random KMAC_APP operations are seen
+              `DV_CHECK_FATAL(in_kmac_app == 1, "in_kmac_app is not set, scoreboard has not picked up KMAC_APP request")
 
-          // TODO error checks
+              // TODO error checks
 
-          // assign digest values
-          kmac_app_digest_share0 = kmac_app_rsp.rsp_digest_share0;
-          kmac_app_digest_share1 = kmac_app_rsp.rsp_digest_share1;
+              // assign digest values
+              kmac_app_digest_share0 = kmac_app_rsp.rsp_digest_share0;
+              kmac_app_digest_share1 = kmac_app_rsp.rsp_digest_share1;
 
-          check_digest();
+              check_digest();
 
-          in_kmac_app = 0;
-          `uvm_info(`gfn, "dropped in_kmac_app", UVM_HIGH)
+              in_kmac_app = 0;
+              `uvm_info(`gfn, "dropped in_kmac_app", UVM_HIGH)
 
-          clear_state();
-          ,
-          wait(cfg.under_reset || !in_kmac_app);
-      )
-    end
+              clear_state();
+              ,
+              wait(cfg.under_reset || !in_kmac_app);
+          )
+        end
+      end
+      begin
+        forever begin
+          wait(!cfg.under_reset);
+          @(posedge in_kmac_app);
+          @(negedge in_kmac_app);
+          if (entropy_mode == EntropyModeEdn) begin
+            cfg.clk_rst_vif.wait_clks(3);
+            in_edn_fetch = cfg.enable_masking;
+            refresh_entropy = cfg.enable_masking;
+            `uvm_info(`gfn, "raised refresh_entropy", UVM_HIGH)
+          end
+        end
+      end
+    join
   endtask
 
   // This task updates the internal sha3_idle status field
@@ -501,51 +564,117 @@
   // This task waits for the keccak logic to complete a full KECCAK_NUM_ROUNDS rounds
   //
   // This task must only be called after sha3pad logic has transmitted all KeccakRate
-  // blocks to keccak logic
+  // blocks to keccak logic.
+  //
+  // If unmasked configuration, each round of the keccak will take only a single cycle.
+  //
+  // If masked configuration, each round of the keccak can take a variable number of cycles.
+  //
+  // Disabling fast entropy means that the internal 320-bit entropy state needs to be "refilled" for
+  // each round, adding a 5 cycle latency as 64-bits are "filled" at a time from the internal LFSR.
+  // So, each round will take ENTROPY_FULL_EXPANSION_CYCLES (7) cycles.
+  //
+  // Enabling fast entropy means that entropy will only be fully expanded during processing
+  // of the secret key block (only applicable for KMAC hashing), each of these rounds will be the
+  // same length as before.
+  // During non-key-processing keccak rounds, entropy will be reused rather than fully expanded to
+  // improve performance, so each round will take ENTROPY_FAST_PROCESSING_CYCLES cycles.
+  //
+  // If SW entropy is used, the length of each cycle depends mostly on whether fast entropy
+  // processing is enabled/disabled.
+  // If new SW entropy is written in the middle of a keccak round, keccak will block until the
+  // updates are complete and the fresh entropy is expanded.
+  //
+  // If entropy from the EDN is used, KMAC will automatically send a request to EDN after reset
+  // for some fresh entropy.
+  //
+  // The very first keccak round (round 0) will block until EDN responds with fresh entropy and KMAC
+  // internally expands it - the length of the remaining keccak rounds will depend on whether fast
+  // entropy is enabled/disabled.
+  //
+  // After finishing a full hash operation, KMAC sends another request to EDN to refresh its
+  // entropy.
+  // Next time keccak rounds start round 0 will take the usual amount of cycles, but round 1
+  // will block until the EDN request is fulfilled and fresh entropy is provided to the KMAC.
+  //
+  // The logic in this task is relatively straightforward and implements the described behavior
+  // in the timing model.
   virtual task wait_keccak_rounds(bit is_key_process = 1'b0);
-    int unsigned total_cycles = 0;
+    int unsigned cycles_first_round = 0;
+    int unsigned cycles_per_round = 0;
+    bit full_entropy_expansion = 0;
+
+    `uvm_info(`gfn, "entered wait_keccak_rounds", UVM_HIGH)
+
     if (cfg.enable_masking) begin
       // If masking is enabled then entropy is used,
       // timing is more complex because of the various entropy features
-      //
-      if (entropy_mode == EntropyModeSw) begin
-        // All rounds will take SW_ENTROPY_ROUND_CYCLES_NO_FAST cycles each, except the first one.
+
+      if (entropy_mode inside {EntropyModeSw, EntropyModeEdn}) begin
+
+        // If using entropy from EDN, need to check whether the request is due to a normal
+        // entropy refresh after completed hash or whether the request is being sent immediately out
+        // of reset once KMAC starts operation.
         //
-        // Without fast entropy, the internal 320-bit entropy state needs to be "refilled" for
-        // each round, adding a 5-cycle latency per round (64 bits "filled" at a time).
-        //
-        // However, by the time `run_i` is asserted to keccak logic, the entropy state will
-        // already be "filled up" in the time it takes for sha3pad to transmit data.
-        //
-        // So, latency of first round will be 3 cycles, one for the entropy FSM to transition and
-        // start entropy expansion and 2 for keccak logic to latch this entropy.
-        //
-        // However, if fast entropy is used, the keccak rounds processing the key will utilize the
-        // 5 cycle latency as entropy is refilled for each key round, but all other keccak rounds
-        // (processing prefix/msg_data) will take 3 cycles each as entropy will not be refilled.
-        if (entropy_fast_process && !is_key_process) begin
-          total_cycles = 3 * KECCAK_NUM_ROUNDS;
-        end else begin
-          total_cycles = 3 + (SW_ENTROPY_ROUND_CYCLES_NO_FAST * (KECCAK_NUM_ROUNDS - 1));
+        // In this case, full expansion is necessary
+        if (entropy_mode == EntropyModeEdn) begin
+          // zero delay to ensure all updates have settled
+          #0;
+          if (in_edn_fetch && first_op_after_rst) begin
+            full_entropy_expansion = 1;
+          end
         end
-      end else if (entropy_mode == EntropyModeEdn) begin
-        // TODO: EDN entropy isn't supported in sequences yet
+
+        if (entropy_fast_process && !is_key_process) begin
+          // fast entropy enabled and we are not processing the secret keys
+          cycles_per_round = ENTROPY_FAST_PROCESSING_CYCLES;
+        end else if (full_entropy_expansion) begin
+          cycles_per_round = ENTROPY_FULL_EXPANSION_CYCLES;
+        end else begin
+          // in the normal case, first round will take 3 cycles as expansion is handled during
+          // sha3pad operation, and each following round takes 7 cycles for full entropy expansion
+          cycles_first_round = ENTROPY_FAST_PROCESSING_CYCLES;
+          cycles_per_round = ENTROPY_FULL_EXPANSION_CYCLES;
+        end
       end else begin
-        // TODO: to uvm_fatal or not to uvm_fatal?
+        // TODO : this is an error case
       end
 
     end else begin
       // If masking is disabled then no entropy is used,
       // so just wait KECCAK_NUM_ROUNDS cycles
-      total_cycles = KECCAK_NUM_ROUNDS;
+      cycles_per_round = 1;
     end
 
     `uvm_info(`gfn, "starting to wait for keccak", UVM_HIGH)
-    cfg.clk_rst_vif.wait_clks(total_cycles);
+
+    for (int i = 0; i < KECCAK_NUM_ROUNDS; i++) begin
+      if (i == 0) begin
+        if (full_entropy_expansion) begin
+          wait(in_edn_fetch == 0);
+          cfg.clk_rst_vif.wait_clks(1 + ENTROPY_FULL_EXPANSION_CYCLES);
+        end else if (cycles_first_round != 0) begin
+          cfg.clk_rst_vif.wait_clks(cycles_first_round);
+        end else begin
+          cfg.clk_rst_vif.wait_clks(cycles_per_round);
+        end
+      end else if (i == 1 && refresh_entropy) begin
+        // If entropy is simply refreshed after the end of previous hashing operation,
+        // keccak round 0 will run as normal, but round 1 will block until entropy is refreshed
+        wait(refresh_entropy == 0);
+        cfg.clk_rst_vif.wait_clks(1 + ENTROPY_FULL_EXPANSION_CYCLES);
+      end else begin
+        cfg.clk_rst_vif.wait_clks(cycles_per_round);
+      end
+    end
+
     // need to wait for one final cycle for sha3 wrapper logic to latch Keccak `complete` signal
+    //
+    // pulse `keccak_complete_cycle` to allow other parts of the scb to handle some edge cases
     keccak_complete_cycle = 1;
     cfg.clk_rst_vif.wait_clks(1);
     keccak_complete_cycle = 0;
+
     `uvm_info(`gfn, "finished waiting for keccak", UVM_HIGH)
   endtask
 
@@ -1459,6 +1588,19 @@
 
           entropy_mode = entropy_mode_e'(item.a_data[KmacEntropyModeMSB:KmacEntropyModeLSB]);
 
+          if (entropy_mode == EntropyModeEdn &&
+              item.a_data[KmacEntropyReady] &&
+              first_op_after_rst) begin
+            in_edn_fetch = cfg.enable_masking;
+          end
+
+          if (cfg.enable_masking &&
+              entropy_mode == EntropyModeEdn &&
+              item.a_data[KmacEntropyReady]) begin
+            in_edn_fetch = 1;
+            `uvm_info(`gfn, "raised in_edn_fetch after reset", UVM_HIGH)
+          end
+
           // TODO - sample coverage
         end
       end
@@ -1500,6 +1642,14 @@
               // IDLE should go high one cycle after issuing Done cmd
               cfg.clk_rst_vif.wait_clks(1);
               sha3_idle = 1;
+
+              // if using EDN, KMAC will refresh entropy after finishing a hash operation
+              if (entropy_mode == EntropyModeEdn) begin
+                cfg.clk_rst_vif.wait_clks(1);
+                in_edn_fetch = cfg.enable_masking;
+                refresh_entropy = cfg.enable_masking;
+                `uvm_info(`gfn, "refreshing entropy from EDN", UVM_HIGH)
+              end
             end
             CmdNone: begin
               // RTL internal value, doesn't actually do anything
@@ -1703,8 +1853,11 @@
 
   virtual function void reset(string kind = "HARD");
     super.reset(kind);
+
     clear_state();
 
+    first_op_after_rst = 1;
+
     // status tracking bits
     sha3_idle     = ral.status.sha3_idle.get_reset();
     sha3_absorb   = ral.status.sha3_absorb.get_reset();
@@ -1718,6 +1871,8 @@
   virtual function void clear_state();
     `uvm_info(`gfn, "clearing scoreboard state", UVM_HIGH)
 
+    if (first_op_after_rst) first_op_after_rst = 0;
+
     msg.delete();
     kmac_app_msg.delete();
 
@@ -1735,12 +1890,16 @@
     fifo_rd_ptr             = 0;
     fifo_wr_ptr             = 0;
 
-    keys              = '0;
-    keymgr_keys       = '0;
-    sideload_key      = '0;
-    prefix            = '{default:0};
-    digest_share0     = {};
-    digest_share1     = {};
+    in_edn_fetch    = 0;
+    refresh_entropy = 0;
+
+    keys          = '0;
+    keymgr_keys   = '0;
+    sideload_key  = '0;
+    prefix        = '{default:0};
+    digest_share0 = {};
+    digest_share1 = {};
+
     kmac_app_digest_share0 = '0;
     kmac_app_digest_share1 = '0;
   endfunction
diff --git a/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv b/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv
index 3faaec1..20519f9 100644
--- a/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv
+++ b/hw/ip/kmac/dv/env/seq_lib/kmac_smoke_vseq.sv
@@ -47,7 +47,7 @@
   }
 
   constraint entropy_mode_c {
-    entropy_mode == EntropyModeSw;
+    entropy_mode inside {EntropyModeSw, EntropyModeEdn};
   }
 
   constraint entropy_ready_c {