[dv/clkmgr] Add test for extclk feature

Add randomized test for the external clock clkmgr feature.
Check the ast request/response handshake.
Check that the clock divisors get ramped up unless in scan mode.

Signed-off-by: Guillermo Maturana <maturana@google.com>
diff --git a/hw/ip/clkmgr/data/clkmgr_testplan.hjson b/hw/ip/clkmgr/data/clkmgr_testplan.hjson
index c56a4a9..75be259 100644
--- a/hw/ip/clkmgr/data/clkmgr_testplan.hjson
+++ b/hw/ip/clkmgr/data/clkmgr_testplan.hjson
@@ -80,22 +80,22 @@
       desc: '''
             Tests the functionality of enabling external clocks.
 
-            - External clocks can be enabled writing `lc_ctrl_pkg::On` to CSR
-              `extclk_sel`.
-            - Writes to CSR `extclk_sel` are ignored unless the value of CSR
-              `extclk_sel_regwen` is 1.
-            - Regardless of the `extclk_sel` contents, external clocks won't
-              be enabled unless `lc_dtl_en_i == lc_ctrl_pkg::On`.
-            - External clocks can also be enabled if the input
-             `lc_clk_byp_req_i == lc_ctrl_pkg::On`.
+            - External clock is enabled if the `lc_clk_byp_req_i` input from
+              `lc_ctrl` is `lc_ctrl_pkg::On`.
+            - External clock is also be enabled when CSR `extclk_sel` is set to
+              `lc_ctrl_pkg::On` and the `lc_dtl_en_i` input from `lc_ctrl` is
+              `lc_ctrl_pkg::On`.
+            - Notice writes to the `extclk_sel` register are ignored unless the
+              CSR `extclk_sel_regwen` is 1.
             - A successful switch to external clocks will cause the clkmgr
-              to undo a divide by 2 for io_div4 and io_div2 clocks unless in
-              scan mode `(scanmode_i == lc_ctrl_pkg::On)`.
+              to undo a divide by 2 for io_div4 and io_div2 clocks except when
+              `(scanmode_i == lc_ctrl_pkg::On)`.
 
             **Stimulus**:
             - CSR writes to `extclk_sel` and `extclk_sel_regwen`.
             - Setting `lc_dft_en_i`, `lc_clk_byp_req_i`, and the handshake to
               ast via `ast_clk_byp_req_o` and `ast_clk_byp_ack_i`.
+            - Setting `scanmode_i`.
 
             **Checks**:
             - SVA assertions:
@@ -108,7 +108,7 @@
                 clocks to ramp up unless `scanmode_i == lc_ctrl_pkg::On`.
             '''
       milestone: V2
-      tests: []
+      tests: ["clkmgr_extclk"]
     }
   ]
   covergroups: [
diff --git a/hw/ip/clkmgr/dv/clkmgr_sim_cfg.hjson b/hw/ip/clkmgr/dv/clkmgr_sim_cfg.hjson
index 15ad2f1..53e6e70 100644
--- a/hw/ip/clkmgr/dv/clkmgr_sim_cfg.hjson
+++ b/hw/ip/clkmgr/dv/clkmgr_sim_cfg.hjson
@@ -57,6 +57,10 @@
       uvm_test_seq: clkmgr_smoke_vseq
     }
     {
+      name: clkmgr_extclk
+      uvm_test_seq: clkmgr_extclk_vseq
+    }
+    {
       name: clkmgr_peri
       uvm_test_seq: clkmgr_peri_vseq
     }
diff --git a/hw/ip/clkmgr/dv/env/clkmgr_env.core b/hw/ip/clkmgr/dv/env/clkmgr_env.core
index f5be154..adfc356 100644
--- a/hw/ip/clkmgr/dv/env/clkmgr_env.core
+++ b/hw/ip/clkmgr/dv/env/clkmgr_env.core
@@ -19,6 +19,7 @@
       - seq_lib/clkmgr_vseq_list.sv: {is_include_file: true}
       - seq_lib/clkmgr_base_vseq.sv: {is_include_file: true}
       - seq_lib/clkmgr_common_vseq.sv: {is_include_file: true}
+      - seq_lib/clkmgr_extclk_vseq.sv: {is_include_file: true}
       - seq_lib/clkmgr_peri_vseq.sv: {is_include_file: true}
       - seq_lib/clkmgr_smoke_vseq.sv: {is_include_file: true}
       - seq_lib/clkmgr_trans_vseq.sv: {is_include_file: true}
diff --git a/hw/ip/clkmgr/dv/env/clkmgr_env_cov.sv b/hw/ip/clkmgr/dv/env/clkmgr_env_cov.sv
index 670948b..d06381a 100644
--- a/hw/ip/clkmgr/dv/env/clkmgr_env_cov.sv
+++ b/hw/ip/clkmgr/dv/env/clkmgr_env_cov.sv
@@ -45,7 +45,7 @@
     trans_cg = new(name);
   endfunction
 
-  function sample(bit hint, bit ip_clk_en, bit scanmode, bit idle);
+  function void sample(bit hint, bit ip_clk_en, bit scanmode, bit idle);
     trans_cg.sample(hint, ip_clk_en, scanmode, idle);
   endfunction
 endclass
diff --git a/hw/ip/clkmgr/dv/env/clkmgr_if.sv b/hw/ip/clkmgr/dv/env/clkmgr_if.sv
index 1a4d88f..395d40b 100644
--- a/hw/ip/clkmgr/dv/env/clkmgr_if.sv
+++ b/hw/ip/clkmgr/dv/env/clkmgr_if.sv
@@ -58,6 +58,12 @@
   lc_ctrl_pkg::lc_tx_t extclk_sel;
   logic                jitter_enable;
 
+  // The expected and actual divided io clocks.
+  logic exp_clk_io_div2;
+  logic actual_clk_io_div2;
+  logic exp_clk_io_div4;
+  logic actual_clk_io_div4;
+
   function automatic void update_extclk_sel(lc_ctrl_pkg::lc_tx_t value);
     extclk_sel = value;
   endfunction
@@ -82,14 +88,39 @@
     scanmode_i = value;
   endfunction
 
+  function automatic void update_lc_dft_en(lc_ctrl_pkg::lc_tx_t value);
+    lc_dft_en_i = value;
+  endfunction
+
+  function automatic void update_lc_clk_byp_req(lc_ctrl_pkg::lc_tx_t value);
+    lc_clk_byp_req = value;
+  endfunction
+
+  function automatic void update_ast_clk_byp_ack(lc_ctrl_pkg::lc_tx_t value);
+    ast_clk_byp_ack = value;
+  endfunction
+
   function automatic logic get_clk_status();
     return pwr_o.clk_status;
   endfunction
 
-  task automatic init(logic [NUM_TRANS-1:0] idle, logic ip_clk_en, lc_ctrl_pkg::lc_tx_t scanmode);
-    lc_clk_byp_req = lc_ctrl_pkg::Off;
-    ast_clk_byp_ack = lc_ctrl_pkg::Off;
-    lc_dft_en_i = lc_ctrl_pkg::Off;
+  function automatic void update_exp_clk_io_divs(logic exp_div2_value,
+                                                 logic actual_div2_value,
+                                                 logic exp_div4_value,
+                                                 logic actual_div4_value);
+    exp_clk_io_div2 = exp_div2_value;
+    actual_clk_io_div2 = actual_div2_value;
+    exp_clk_io_div4 = exp_div4_value;
+    actual_clk_io_div4 = actual_div4_value;
+  endfunction
+
+  task automatic init(logic [NUM_TRANS-1:0] idle, logic ip_clk_en, lc_ctrl_pkg::lc_tx_t scanmode,
+                      lc_ctrl_pkg::lc_tx_t lc_dft_en = lc_ctrl_pkg::Off,
+                      lc_ctrl_pkg::lc_tx_t lc_clk_byp_req = lc_ctrl_pkg::Off,
+                      lc_ctrl_pkg::lc_tx_t ast_clk_byp_ack = lc_ctrl_pkg::Off);
+    update_ast_clk_byp_ack(ast_clk_byp_ack);
+    update_lc_clk_byp_req(lc_clk_byp_req);
+    update_lc_dft_en(lc_dft_en);
     update_idle(idle);
     update_ip_clk_en(ip_clk_en);
     update_scanmode(scanmode);
@@ -157,7 +188,6 @@
   endclocking
 
   // Pipelining and clocking block for transactional unit clocks.
-
   logic [PIPELINE_DEPTH-1:0][NUM_TRANS-1:0] clk_hints_ffs;
   logic [PIPELINE_DEPTH-1:0]                trans_clk_en_ffs;
   always @(posedge clocks_o.clk_main_powerup) begin
@@ -172,4 +202,29 @@
     input idle_i;
   endclocking
 
+  clocking extclk_cb @(posedge clk);
+    input extclk_sel;
+    input lc_dft_en_i;
+    input ast_clk_byp_req;
+    input lc_clk_byp_req;
+  endclocking
+
+  // Pipelining and clocking block for external clock bypass. The divisor control is
+  // triggered by an ast ack, which goes through synchronizers.
+  logic step_down_ff;
+  always @(posedge clk) begin
+    if (rst_n) begin
+      step_down_ff <= ast_clk_byp_ack == lc_ctrl_pkg::On;
+    end
+  end
+  clocking step_down_cb @(posedge clk);
+    input step_down = step_down_ff;
+  endclocking
+
+  clocking div_clks_cb @(posedge clocks_o.clk_io_powerup);
+    input exp_clk_io_div2;
+    input actual_clk_io_div2;
+    input exp_clk_io_div4;
+    input actual_clk_io_div4;
+  endclocking
 endinterface
diff --git a/hw/ip/clkmgr/dv/env/clkmgr_scoreboard.sv b/hw/ip/clkmgr/dv/env/clkmgr_scoreboard.sv
index b41ea1b..807f3a9 100644
--- a/hw/ip/clkmgr/dv/env/clkmgr_scoreboard.sv
+++ b/hw/ip/clkmgr/dv/env/clkmgr_scoreboard.sv
@@ -2,6 +2,82 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
+// Auxiliary class to deal with divided clocks. It predicts the divided clocks depending
+// on whether the clock is divided as usual (step up), or if one division is undone (step
+// down).
+//
+// The internal div2 clock is always running, and step down means we select the base
+// clk_io instead of the internal clock.
+//
+// The div4 clock is handled differently: we internally maintain both a max and cnt. In
+// normal operation the max is 1, so the div4 clock flips every two cycles; when stepped down
+// the max is set to 0, so the clock flips every clk_io cycle.
+class clock_dividers;
+
+  // Step down means undo one clock divide: runs faster, as in "step down on the gas".
+  typedef enum {DivStepDown, DivStepUp} div_step_e;
+  typedef enum {Div2SelDiv2, Div2SelIo} div2_sel_e;
+
+  // The internal div2 clock, always running.
+  local bit        clk_io_div2 = 1'b0;
+  // This selects the io clock when stepped down.
+  local div2_sel_e div2_sel = Div2SelDiv2;
+  // The predicted div4 clock.
+  local bit        clk_io_div4 = 1'b0;
+  // The maximum value of cnt: becomes 0 when stepped down.
+  local bit        clk_io_div4_max = 1;
+  local bit        cnt = 0;
+
+  function new();
+    reset();
+  endfunction
+
+  function void reset();
+    clk_io_div2 = 1'b0;
+    div2_sel = Div2SelDiv2;
+    clk_io_div4 = 1'b0;
+    clk_io_div4_max = 1;
+    cnt = 0;
+  endfunction
+
+  function void increment_div2();
+    clk_io_div2 = ~clk_io_div2;
+  endfunction
+
+  function void increment_div4(bit in_scan_mode);
+    bit real_limit = in_scan_mode ? 1 : clk_io_div4_max;
+    if (cnt < real_limit) begin
+      cnt++;
+    end else begin
+      clk_io_div4 = ~clk_io_div4;
+      cnt = 0;
+    end
+  endfunction
+
+  function void step_div4(div_step_e step);
+    if (step == DivStepUp) clk_io_div4_max = 1;
+    else clk_io_div4_max = 0;
+  endfunction
+
+  function void step_div2(div_step_e step);
+    if (step == DivStepUp) div2_sel = Div2SelDiv2;
+    else div2_sel = Div2SelIo;
+  endfunction
+
+  function string show();
+    return $sformatf("clk_div2=%b div2_sel=%0s clk_div4=%b cnt=%b max=%b",
+                     clk_io_div2, div2_sel.name, clk_io_div4, cnt, clk_io_div4_max);
+  endfunction
+
+  function bit get_div2_clk(bit actual_clk_io);
+    return div2_sel == Div2SelIo ? actual_clk_io : clk_io_div2;
+  endfunction
+
+  function bit get_div4_clk();
+    return clk_io_div4;
+  endfunction
+endclass : clock_dividers
+
 class clkmgr_scoreboard extends cip_base_scoreboard #(
     .CFG_T(clkmgr_env_cfg),
     .RAL_T(clkmgr_reg_block),
@@ -28,9 +104,11 @@
   task run_phase(uvm_phase phase);
     super.run_phase(phase);
     fork
+      monitor_ip_clk_en();
       monitor_idle();
       monitor_ip_clk_en();
       monitor_scanmode();
+      monitor_ast_clk_byp();
       begin : post_reset
         fork
           monitor_div4_peri_clock();
@@ -44,6 +122,7 @@
               monitor_trans_clock(trans_index);
             join_none
           end
+          monitor_clk_dividers();
         join_none
       end
     join_none
@@ -166,6 +245,96 @@
       end
   endtask
 
+  task monitor_ast_clk_byp();
+    lc_tx_t prev_lc_clk_byp_req = Off;
+    forever @cfg.clkmgr_vif.extclk_cb begin
+      if (cfg.clkmgr_vif.lc_clk_byp_req != prev_lc_clk_byp_req) begin
+        `uvm_info(`gfn, $sformatf("Got lc_clk_byp_req %s",
+                                  cfg.clkmgr_vif.lc_clk_byp_req == On ? "On" : "Off"),
+                  UVM_MEDIUM)
+        prev_lc_clk_byp_req = cfg.clkmgr_vif.lc_clk_byp_req;
+      end
+      if (((cfg.clkmgr_vif.extclk_cb.extclk_sel == On) &&
+           (cfg.clkmgr_vif.extclk_cb.lc_dft_en_i == On)) ||
+          (cfg.clkmgr_vif.extclk_cb.lc_clk_byp_req == On)) begin
+        `DV_CHECK_EQ(cfg.clkmgr_vif.ast_clk_byp_req, On,
+                     "Expected ast_clk_byp_req to be On")
+      end
+    end
+  endtask
+
+  task monitor_clk_dividers();
+    clock_dividers dividers = new();
+    clock_dividers::div_step_e prev_div_step = clock_dividers::DivStepUp;
+
+    #1;
+    cfg.io_clk_rst_vif.wait_for_reset();
+    fork
+      forever @(posedge cfg.io_clk_rst_vif.rst_n) begin : handle_dividers_reset
+        dividers.reset();
+        `uvm_info(`gfn, $sformatf("Reset divided clocks: %0s", dividers.show()), UVM_MEDIUM)
+      end
+      forever @cfg.clkmgr_vif.step_down_cb begin : handle_divider_step_change
+        clock_dividers::div_step_e div_step;
+        bit step_down = cfg.clkmgr_vif.step_down_cb.step_down;
+        #0;
+
+        step_down &= (cfg.clkmgr_vif.scanmode_i != On);
+        div_step = step_down ? clock_dividers::DivStepDown : clock_dividers::DivStepUp;
+        if (div_step != prev_div_step) begin
+          `uvm_info(`gfn, $sformatf("Got a %0s request", div_step.name), UVM_LOW)
+          dividers.step_div4(div_step);
+          prev_div_step = div_step;
+          @(negedge cfg.clkmgr_vif.clocks_o.clk_io_powerup) begin
+            // Reconsider scanmode_i since it is asynchronous.
+            dividers.step_div2(step_down && (cfg.clkmgr_vif.scanmode_i != On) ?
+                               clock_dividers::DivStepDown : clock_dividers::DivStepUp);
+          end
+          `uvm_info(`gfn, $sformatf("Stepped divided clocks: %0s", dividers.show()), UVM_MEDIUM)
+        end
+      end
+      // Compare divided clocks, always based on values from clocking block (thus preponed).
+      forever @cfg.clkmgr_vif.div_clks_cb begin : check_clocks
+        `DV_CHECK_EQ(cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div4,
+                     cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div4,
+                     $sformatf("Mismatch for clk_io_div4_powerup, expected %b, got %b",
+                               cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div4,
+                               cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div4))
+        `DV_CHECK_EQ(cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div2,
+                     cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div2,
+                     $sformatf("Mismatch for clk_io_div2_powerup, expected %b, got %b",
+                               cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div2,
+                               cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div2))
+      end
+      forever @(posedge cfg.clkmgr_vif.clocks_o.clk_io_powerup) begin : increment_clocks
+        if (cfg.io_clk_rst_vif.rst_n) begin
+          bit in_scan_mode = cfg.clkmgr_vif.scanmode_i == On;
+          #0;
+          // Deal with div4 update, accounting for scanmode's asynchronicity.
+          // The incremented clock will be useful for the next comparison cycle.
+          dividers.increment_div4(in_scan_mode);
+          dividers.increment_div2();
+          `uvm_info(`gfn, $sformatf("Incremented divided clocks: %0s", dividers.show()), UVM_MEDIUM)
+          `uvm_info(`gfn,
+                    $sformatf(
+                        "Update for div clk cb: div2 exp=%b, actual=%b, div4 exp=%b, actual=%b",
+                        dividers.get_div2_clk(cfg.clkmgr_vif.clocks_o.clk_io_powerup),
+                        cfg.clkmgr_vif.clocks_o.clk_io_div2_powerup,
+                        dividers.get_div4_clk(),
+                        cfg.clkmgr_vif.clocks_o.clk_io_div4_powerup),
+                    UVM_LOW)
+          // This delay seems to help with xcelium: without it the actual divided clocks are stale.
+          #1;
+          cfg.clkmgr_vif.update_exp_clk_io_divs(
+              .exp_div2_value(dividers.get_div2_clk(cfg.clkmgr_vif.clocks_o.clk_io_powerup)),
+              .actual_div2_value(cfg.clkmgr_vif.clocks_o.clk_io_div2_powerup),
+              .exp_div4_value(dividers.get_div4_clk()),
+              .actual_div4_value(cfg.clkmgr_vif.clocks_o.clk_io_div4_powerup));
+        end
+      end
+    join
+  endtask
+
   virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name);
     uvm_reg csr;
     bit     do_read_check   = 1'b1;
diff --git a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_base_vseq.sv b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_base_vseq.sv
index 179ad75..dece598 100644
--- a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_base_vseq.sv
+++ b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_base_vseq.sv
@@ -24,8 +24,8 @@
   rand bit ip_clk_en;
   rand bit [NUM_TRANS-1:0] idle;
 
-  // This selects scanmode according to sel_scanmode, which is randomized with weights.
-  rand lc_tx_t       scanmode;
+  // scanmode is set according to sel_scanmode, which is randomized with weights.
+  lc_tx_t            scanmode;
   rand lc_tx_t       scanmode_other;
   rand lc_tx_t_sel_e sel_scanmode;
   int                scanmode_on_weight = 8;
@@ -33,7 +33,16 @@
   constraint scanmode_c {
     sel_scanmode dist {LcTxTSelOn := scanmode_on_weight, LcTxTSelOff := 4, LcTxTSelOther := 4};
     !(scanmode_other inside {On, Off});
-    scanmode == get_lc_tx_t_from_sel(sel_scanmode, scanmode_other);
+  }
+
+  // extclk_sel is set according to sel_extclk_sel, which is randomized with weights.
+  lc_tx_t            extclk_sel;
+  rand lc_tx_t       extclk_sel_other;
+  rand lc_tx_t_sel_e sel_extclk_sel;
+
+  constraint extclk_sel_c {
+    sel_extclk_sel dist {LcTxTSelOn := 4, LcTxTSelOff := 2, LcTxTSelOther := 2};
+    !(extclk_sel_other inside {On, Off});
   }
 
   // various knobs to enable certain routines
@@ -41,11 +50,22 @@
 
   `uvm_object_new
 
+  function void post_randomize();
+    super.post_randomize();
+    scanmode = get_lc_tx_t_from_sel(sel_scanmode, scanmode_other);
+    extclk_sel = get_lc_tx_t_from_sel(sel_extclk_sel, extclk_sel_other);
+  endfunction  
+
+  virtual function void set_scanmode_on_low_weight();
+    scanmode_on_weight = 2;
+  endfunction
+
   task pre_start();
     // These are independent: do them in parallel since pre_start consumes time.
     fork
       begin
-        cfg.clkmgr_vif.init(.idle('1), .ip_clk_en(ip_clk_en), .scanmode(scanmode));
+        cfg.clkmgr_vif.init(.idle('1), .ip_clk_en(ip_clk_en), .scanmode(scanmode),
+                            .lc_dft_en(Off));
       end
       if (do_clkmgr_init) clkmgr_init();
       super.pre_start();
diff --git a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_extclk_vseq.sv b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_extclk_vseq.sv
new file mode 100644
index 0000000..198550b
--- /dev/null
+++ b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_extclk_vseq.sv
@@ -0,0 +1,113 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// The extclk vseq causes the external clock selection to be triggered. More details
+// in the clkmgr_testplan.hjson file.
+class clkmgr_extclk_vseq extends clkmgr_base_vseq;
+  `uvm_object_utils(clkmgr_extclk_vseq)
+
+  `uvm_object_new
+
+  // If the extclk_sel_regwen is clear, it is not possible to select external clocks.
+  // This is tested in regular csr_rw, so here this register is simply set to 1.
+
+  // lc_dft_en is set according to sel_lc_dft_en, which is randomized with weights.
+  lc_tx_t            lc_dft_en;
+  rand lc_tx_t       lc_dft_en_other;
+  rand lc_tx_t_sel_e sel_lc_dft_en;
+
+  constraint lc_dft_en_c {
+    sel_lc_dft_en dist {LcTxTSelOn := 8, LcTxTSelOff := 2, LcTxTSelOther := 2};
+    !(lc_dft_en_other inside {On, Off});
+  }
+
+  // lc_clk_byp_req is set according to sel_lc_clk_byp_req, which is randomized with weights.
+  lc_tx_t            lc_clk_byp_req;
+  rand lc_tx_t       lc_clk_byp_req_other;
+  rand lc_tx_t_sel_e sel_lc_clk_byp_req;
+
+  constraint lc_clk_byp_req_c {
+    sel_lc_clk_byp_req dist {LcTxTSelOn := 8, LcTxTSelOff := 2, LcTxTSelOther := 2};
+    !(lc_clk_byp_req_other inside {On, Off});
+  }
+
+  // This randomizes the time when the extclk_sel CSR write and the lc_clk_byp_req
+  // input is asserted for good measure. Of course, there is a good chance only a single
+  // one of these trigger a request, so they are also independently tested.
+  rand int cycles_before_extclk_sel;
+  rand int cycles_before_lc_clk_byp_req;
+  rand int cycles_before_lc_clk_byp_ack;
+  rand int cycles_before_ast_clk_byp_ack;
+  rand int cycles_before_next_trans;
+
+  constraint trans_large_c { num_trans == 16; }
+  constraint cycles_to_stim_c {
+    cycles_before_extclk_sel      inside {[4:20]};
+    cycles_before_lc_clk_byp_req  inside {[4:20]};
+    cycles_before_lc_clk_byp_ack  inside {[4:20]};
+    cycles_before_ast_clk_byp_ack inside {[3:11]};
+    cycles_before_next_trans      inside {[15:25]};
+  }
+
+  function void post_randomize();
+    super.post_randomize();
+    lc_dft_en = get_lc_tx_t_from_sel(sel_lc_dft_en, lc_dft_en_other);
+    lc_clk_byp_req = get_lc_tx_t_from_sel(sel_lc_clk_byp_req, lc_clk_byp_req_other);
+  endfunction
+
+  task body();
+    update_csrs_with_reset_values();
+    set_scanmode_on_low_weight();
+    csr_wr(.ptr(ral.extclk_sel_regwen), .value(1));
+    fork
+      forever @cfg.clkmgr_vif.ast_clk_byp_req begin : ast_clk_byp_ack
+        if (cfg.clkmgr_vif.ast_clk_byp_req == lc_ctrl_pkg::On) begin
+          `uvm_info(`gfn, "Got ast_clk_byp_req on", UVM_MEDIUM)
+          cfg.clk_rst_vif.wait_clks(cycles_before_ast_clk_byp_ack);
+          cfg.clkmgr_vif.update_ast_clk_byp_ack(lc_ctrl_pkg::On);
+        end else begin
+          `uvm_info(`gfn, "Got ast_clk_byp_req off", UVM_MEDIUM)
+          cfg.clk_rst_vif.wait_clks(cycles_before_ast_clk_byp_ack);
+          cfg.clkmgr_vif.update_ast_clk_byp_ack(lc_ctrl_pkg::Off);
+        end
+      end
+      forever @cfg.clkmgr_vif.lc_clk_byp_ack begin : lc_clk_byp_ack
+        if (cfg.clkmgr_vif.lc_clk_byp_ack == lc_ctrl_pkg::On) begin
+          `uvm_info(`gfn, "Got lc_clk_byp_ack on", UVM_MEDIUM)
+        end else begin
+          `uvm_info(`gfn, "Got lc_clk_byp_req off", UVM_MEDIUM)
+          cfg.clk_rst_vif.wait_clks(cycles_before_lc_clk_byp_ack);
+          cfg.clkmgr_vif.update_lc_clk_byp_req(lc_ctrl_pkg::Off);
+        end
+      end
+    join_none
+    for (int i = 0; i < num_trans; ++i) begin
+      logic [TL_DW-1:0] value;
+      `DV_CHECK_RANDOMIZE_FATAL(this)
+      // Init needs to be synchronous.
+      @cfg.clk_rst_vif.cb begin
+        cfg.clkmgr_vif.init(.idle(idle), .ip_clk_en(ip_clk_en), .scanmode(scanmode),
+                            .lc_dft_en(lc_dft_en));
+      end
+      fork
+        begin
+          cfg.clk_rst_vif.wait_clks(cycles_before_extclk_sel);
+          csr_wr(.ptr(ral.extclk_sel), .value(extclk_sel));
+        end
+        begin
+          cfg.clk_rst_vif.wait_clks(cycles_before_lc_clk_byp_req);
+          cfg.clkmgr_vif.update_lc_clk_byp_req(lc_clk_byp_req);
+        end
+      join
+      `uvm_info(`gfn,
+                $sformatf("extclk_sel=0x%0x, lc_clk_byp_req=0x%0x, lc_dft_en=0x%0x, scanmode=0x%0x",
+                          extclk_sel, lc_clk_byp_req, lc_dft_en, scanmode),
+                UVM_MEDIUM)
+      csr_rd_check(.ptr(ral.extclk_sel), .compare_value(extclk_sel));
+      cfg.io_clk_rst_vif.wait_clks(cycles_before_next_trans);
+    end
+    disable fork;
+  endtask
+
+endclass
diff --git a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_trans_vseq.sv b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_trans_vseq.sv
index e8e3bcb..45cace6 100644
--- a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_trans_vseq.sv
+++ b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_trans_vseq.sv
@@ -23,6 +23,7 @@
 
       `DV_CHECK_RANDOMIZE_FATAL(this)
       cfg.clkmgr_vif.init(.idle(idle), .ip_clk_en(ip_clk_en), .scanmode(scanmode));
+
       cfg.clk_rst_vif.wait_clks(10);
       `uvm_info(`gfn, $sformatf("Updating hints to 0x%0x", initial_hints), UVM_MEDIUM)
       csr_wr(.ptr(ral.clk_hints), .value(initial_hints));
diff --git a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_vseq_list.sv b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_vseq_list.sv
index 4cfc91c..a9cc4e4 100644
--- a/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_vseq_list.sv
+++ b/hw/ip/clkmgr/dv/env/seq_lib/clkmgr_vseq_list.sv
@@ -4,6 +4,7 @@
 
 `include "clkmgr_base_vseq.sv"
 `include "clkmgr_common_vseq.sv"
+`include "clkmgr_extclk_vseq.sv"
 `include "clkmgr_peri_vseq.sv"
 `include "clkmgr_smoke_vseq.sv"
 `include "clkmgr_trans_vseq.sv"