[top/dv] add test chip_sw_uart_tx_rx_alt_clk_freq

Extend from chip_sw_uart_rand_baudrate with following added settings.
- Configure LC to RMA state, so that it allows clkmgr to use external clock.
- Configure clkmgr to select external clock.
- Randomize `LOW_SPEED_SEL`, so that uart core clock frequency can be either
  ext_clk_freq / 4 or ext_clk_freq / 2.

Signed-off-by: Weicai Yang <weicai@google.com>
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index 1d2275d..f5e6faa 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -61,15 +61,16 @@
     }
     {
       name: chip_sw_uart_tx_rx_alt_clk_freq
-      desc: '''Verify the transmission of UART while randomizing the core clock frequency.
+      desc: '''Verify the transmission of UART via using external clock as uart core clock.
 
-            Run the chip_uart_tx_rx test with the core clock frequency randomized between known
-            bounds.
-
-            TODO: Find out what the range is for the core clock frequency randomization.
+            Extend from chip_sw_uart_rand_baudrate with following added settings.
+            - Configure LC to RMA state, so that it allows clkmgr to use external clock.
+            - Configure clkmgr to select external clock.
+            - Randomize `LOW_SPEED_SEL`, so that uart core clock frequency can be either
+              ext_clk_freq / 4 or ext_clk_freq / 2.
             '''
       milestone: V1
-      tests: []
+      tests: ["chip_sw_uart_tx_rx_alt_clk_freq", "chip_sw_uart_tx_rx_alt_clk_freq_low_speed"]
     }
 
     // GPIO (pre-verified IP) integration tests:
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index d531da4..a36e98c 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -230,6 +230,22 @@
       reseed: 20
     }
     {
+      name: chip_sw_uart_tx_rx_alt_clk_freq
+      uvm_test_seq: chip_sw_uart_rand_baudrate_vseq
+      sw_images: ["sw/device/tests/uart_tx_rx_test:1"]
+      en_run_modes: ["sw_test_mode"]
+      run_opts: ["+use_otp_image=LcStRma", "+use_extclk=1"]
+      reseed: 10
+    }
+    {
+      name: chip_sw_uart_tx_rx_alt_clk_freq_low_speed
+      uvm_test_seq: chip_sw_uart_rand_baudrate_vseq
+      sw_images: ["sw/device/tests/uart_tx_rx_test:1"]
+      en_run_modes: ["sw_test_mode"]
+      run_opts: ["+use_otp_image=LcStRma", "+use_extclk=1", "extclk_low_speed_sel=1"]
+      reseed: 10
+    }
+    {
       name: chip_sw_spi_device_tx_rx
       uvm_test_seq: chip_sw_spi_tx_rx_vseq
       sw_images: ["sw/device/tests/spi_tx_rx_test:1"]
diff --git a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_uart_rand_baudrate_vseq.sv b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_uart_rand_baudrate_vseq.sv
index 06751aa..5218031 100644
--- a/hw/top_earlgrey/dv/env/seq_lib/chip_sw_uart_rand_baudrate_vseq.sv
+++ b/hw/top_earlgrey/dv/env/seq_lib/chip_sw_uart_rand_baudrate_vseq.sv
@@ -2,9 +2,9 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-`define CALC_NCO(baud_rate, nco_width, clk_freq_mhz) \
-  (baud_rate == BaudRate1p5Mbps && clk_freq_mhz == 24) ? 16'hffff : \
-      (longint'(baud_rate) * (2**(nco_width+4))) / (clk_freq_mhz * 1000_000)
+`define CALC_NCO(baud_rate, nco_width, clk_freq_khz) \
+  (baud_rate == BaudRate1p5Mbps && clk_freq_khz == 24_000) ? 16'hffff : \
+      (longint'(baud_rate) * (2**(nco_width+4))) / (clk_freq_khz * 1000)
 
 class chip_sw_uart_rand_baudrate_vseq extends chip_sw_uart_tx_rx_vseq;
   `uvm_object_utils(chip_sw_uart_rand_baudrate_vseq)
@@ -13,19 +13,38 @@
 
   localparam NCO_WIDTH = 16;
 
-  rand baud_rate_e baud_rate;
-  rand int uart_clk_freq_mhz = 24;
+  // Clkmgr external clock settings
+  bit use_extclk = 0;
+  bit extclk_low_speed_sel = 0;
 
-  // Use the fixed 24Mhz, override it in extended vseq
-  constraint uart_clk_freq_mhz_c {
-    uart_clk_freq_mhz == 24;
-  }
+  int uart_clk_freq_khz; // use khz to avoid fractional value
+
+  rand baud_rate_e baud_rate;
 
   constraint baud_rate_c {
     // constrain nco not over nco width
-    `CALC_NCO(baud_rate, NCO_WIDTH, uart_clk_freq_mhz) < (1 << NCO_WIDTH);
+    `CALC_NCO(baud_rate, NCO_WIDTH, uart_clk_freq_khz) < (1 << NCO_WIDTH);
   }
 
+  function void pre_randomize();
+    super.pre_randomize();
+    void'($value$plusargs("use_extclk=%0d", use_extclk));
+    void'($value$plusargs("extclk_low_speed_sel=%0d", extclk_low_speed_sel));
+    if (use_extclk) begin
+      // Uart bus clock is in div4 domain
+      uart_clk_freq_khz = cfg.clk_freq_mhz * 1000 / 4;
+
+      if (extclk_low_speed_sel) uart_clk_freq_khz = uart_clk_freq_khz * 2;
+    end else begin
+      // internal uart bus clock is 24Mhz
+      uart_clk_freq_khz = 24_0000;
+    end
+    `uvm_info(`gfn,
+              $sformatf("External clock freq: %0dmhz, use_extclk: %0d, extclk_low_speed_sel: %0d",
+              cfg.clk_freq_mhz, use_extclk, extclk_low_speed_sel),
+              UVM_MEDIUM)
+  endfunction
+
   virtual task dut_init(string reset_kind = "HARD");
     super.dut_init(reset_kind);
     cfg.uart_baud_rate = baud_rate;
@@ -33,14 +52,32 @@
 
   virtual task cpu_init();
     // sw_symbol_backdoor_overwrite takes an array as the input
-    bit [7:0] uart_freq[8] = {<< byte {cfg.uart_baud_rate}};
+    bit [7:0] uart_freq_arr[8] = {<< byte {cfg.uart_baud_rate}};
 
     super.cpu_init();
-    $display("wcy0 %p", uart_freq);
-    sw_symbol_backdoor_overwrite("kUartBaudrate", uart_freq);
-    `uvm_info(`gfn, $sformatf("Configure uart core clk %0d Mhz, baud_rate: %s",
-              uart_clk_freq_mhz, baud_rate.name), UVM_LOW)
+    sw_symbol_backdoor_overwrite("kUartBaudrate", uart_freq_arr);
+    `uvm_info(`gfn, $sformatf("Backdoor_overwrite: configure uart core clk %0d khz, baud_rate: %s",
+              uart_clk_freq_khz, baud_rate.name), UVM_LOW)
 
+    if (use_extclk) begin
+      bit [7:0] use_extclk_arr[] = {use_extclk};
+      bit [7:0] low_speed_sel_arr[] = {extclk_low_speed_sel};
+      bit [7:0] uart_clk_freq_arr[8] = {<< byte {uart_clk_freq_khz * 1000}};
+
+      sw_symbol_backdoor_overwrite("kUseExtClk", use_extclk_arr);
+      `uvm_info(`gfn, $sformatf("Backdoor_overwrite: configure uart use_extclk: %0d", use_extclk),
+                UVM_LOW)
+
+      sw_symbol_backdoor_overwrite("kUseLowSpeedSel", low_speed_sel_arr);
+      `uvm_info(`gfn, $sformatf("Backdoor_overwrite: configure low_speed_sel: %0d",
+                extclk_low_speed_sel), UVM_LOW)
+
+      // SW relies on kClockFreqPeripheralHz to calcuate NCO and it's hard-coded to 24Mhz in SW.
+      // this value is changed when extclk is used
+      sw_symbol_backdoor_overwrite("kClockFreqPeripheralHz", uart_clk_freq_arr);
+      `uvm_info(`gfn, $sformatf("Backdoor_overwrite: configure uart use_extclk: %0d",
+                uart_clk_freq_khz), UVM_LOW)
+    end
   endtask
 
 endclass : chip_sw_uart_rand_baudrate_vseq
diff --git a/sw/device/lib/dif/dif_clkmgr.c b/sw/device/lib/dif/dif_clkmgr.c
index 35a93dc..e01b3aa 100644
--- a/sw/device/lib/dif/dif_clkmgr.c
+++ b/sw/device/lib/dif/dif_clkmgr.c
@@ -148,3 +148,21 @@
 
   return kDifOk;
 }
+
+dif_result_t dif_clkmgr_external_clock_set_enabled(const dif_clkmgr_t *clkmgr,
+                                                   bool is_low_speed) {
+  uint32_t extclk_ctrl_reg = 0;
+
+  if (clkmgr == NULL) {
+    return kDifBadArg;
+  }
+
+  extclk_ctrl_reg = bitfield_field32_write(
+      extclk_ctrl_reg, CLKMGR_EXTCLK_CTRL_SEL_FIELD, kMultiBitBool4True);
+  extclk_ctrl_reg = bitfield_field32_write(
+      extclk_ctrl_reg, CLKMGR_EXTCLK_CTRL_LOW_SPEED_SEL_FIELD,
+      is_low_speed ? kMultiBitBool4True : kMultiBitBool4False);
+  mmio_region_write32(clkmgr->base_addr, CLKMGR_EXTCLK_CTRL_REG_OFFSET,
+                      extclk_ctrl_reg);
+  return kDifOk;
+}
diff --git a/sw/device/lib/dif/dif_clkmgr.h b/sw/device/lib/dif/dif_clkmgr.h
index 53b06c7..f3f82fe 100644
--- a/sw/device/lib/dif/dif_clkmgr.h
+++ b/sw/device/lib/dif/dif_clkmgr.h
@@ -148,6 +148,21 @@
     const dif_clkmgr_t *clkmgr, dif_clkmgr_hintable_clock_t clock,
     dif_toggle_t *state);
 
+/**
+ * Enable chip to use the external clock.
+ *
+ * @param clkmgr Clock Manager Handle.
+ * @param is_low_speed External clock is low speed or high speed.
+ * High speed - external clock is close to nominal speeds (e.g. external clock
+ * is 96MHz and nominal frequency is 96MHz-100MHz). Low speed - external clock
+ * is half of nominal speeds (e.g. external clock is 48MHz and nominal frequency
+ * is 96MHz-100MHz).
+ *
+ */
+OT_WARN_UNUSED_RESULT
+dif_result_t dif_clkmgr_external_clock_set_enabled(const dif_clkmgr_t *clkmgr,
+                                                   bool is_low_speed);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif  // __cplusplus
diff --git a/sw/device/tests/sim_dv/meson.build b/sw/device/tests/sim_dv/meson.build
index afb23c9..3c40442 100644
--- a/sw/device/tests/sim_dv/meson.build
+++ b/sw/device/tests/sim_dv/meson.build
@@ -30,12 +30,16 @@
   link_with: static_library(
     'uart_tx_rx_test_lib',
     sources: [
+      hw_ip_lc_ctrl_reg_h,
+      hw_ip_clkmgr_reg_h,
       # TODO, remove it once pinout configuration is provided
       hw_top_earlgrey_pinmux_reg_h,
       'uart_tx_rx_test.c'],
     dependencies: [
       sw_lib_dif_uart,
       sw_lib_dif_rv_plic,
+      sw_lib_dif_lc_ctrl,
+      sw_lib_dif_clkmgr,
       sw_lib_irq,
       sw_lib_mmio,
       sw_lib_runtime_log,
diff --git a/sw/device/tests/sim_dv/uart_tx_rx_test.c b/sw/device/tests/sim_dv/uart_tx_rx_test.c
index 9f387d2..fdc046f 100644
--- a/sw/device/tests/sim_dv/uart_tx_rx_test.c
+++ b/sw/device/tests/sim_dv/uart_tx_rx_test.c
@@ -5,6 +5,8 @@
 #include "sw/device/lib/arch/device.h"
 #include "sw/device/lib/base/mmio.h"
 #include "sw/device/lib/dif/dif_base.h"
+#include "sw/device/lib/dif/dif_clkmgr.h"
+#include "sw/device/lib/dif/dif_lc_ctrl.h"
 #include "sw/device/lib/dif/dif_rv_plic.h"
 #include "sw/device/lib/dif/dif_uart.h"
 #include "sw/device/lib/irq.h"
@@ -65,6 +67,14 @@
  */
 static volatile const uint8_t kUartIdx = 0x0;
 
+/**
+ * Indicates if ext_clk is used and what speed.
+ *
+ * Similar to `kUartIdx`, this may be overridden in DV testbench
+ */
+static volatile const uint8_t kUseExtClk = 0x0;
+static volatile const uint8_t kUseLowSpeedSel = 0x0;
+
 // A set of bytes to be send out of TX.
 static const uint8_t kUartTxData[UART_DATASET_SIZE] = {
     0xe8, 0x50, 0xc6, 0xb4, 0xbe, 0x16, 0xed, 0x55, 0x16, 0x1d, 0xe6, 0x1c,
@@ -491,6 +501,28 @@
   return true;
 }
 
+void config_external_clock(void) {
+  dif_lc_ctrl_t lc;
+  dif_clkmgr_t clkmgr;
+  mmio_region_t lc_ctrl_base_addr =
+      mmio_region_from_addr(TOP_EARLGREY_LC_CTRL_BASE_ADDR);
+  mmio_region_t clkmgr_base_addr =
+      mmio_region_from_addr(TOP_EARLGREY_CLKMGR_AON_BASE_ADDR);
+
+  CHECK_DIF_OK(dif_lc_ctrl_init(lc_ctrl_base_addr, &lc));
+  CHECK_DIF_OK(dif_clkmgr_init(clkmgr_base_addr, &clkmgr));
+
+  LOG_INFO("Read and check LC state.");
+  dif_lc_ctrl_state_t curr_state;
+  CHECK_DIF_OK(dif_lc_ctrl_get_state(&lc, &curr_state));
+  CHECK(curr_state == kDifLcCtrlStateRma,
+        "LC State isn't in kDifLcCtrlStateRma!");
+
+  // Enable external clock
+  LOG_INFO("Configure clkmgr to enable external clock");
+  CHECK_DIF_OK(dif_clkmgr_external_clock_set_enabled(&clkmgr, kUseLowSpeedSel));
+}
+
 const test_config_t kTestConfig;
 
 bool test_main(void) {
@@ -512,6 +544,10 @@
       kTopEarlgreyPinmuxInselIor11, kTopEarlgreyPinmuxPeripheralInUart3Rx,
       kTopEarlgreyPinmuxMioOutIor12, kTopEarlgreyPinmuxOutselUart3Tx);
 
+  if (kUseExtClk) {
+    config_external_clock();
+  }
+
   // Initialize the UART.
   mmio_region_t chosen_uart_region = mmio_region_from_addr(uart_base_addr);
   uart_init_with_irqs(chosen_uart_region, &uart);