[clkmgr / top] Add clock divider step down to support lc_ctrl transition

Signed-off-by: Timothy Chen <timothytim@google.com>

[clkmgr] updates per review comments

Signed-off-by: Timothy Chen <timothytim@google.com>

[clkmgr] update waiver

Signed-off-by: Timothy Chen <timothytim@google.com>

[top] Auto generate files

Signed-off-by: Timothy Chen <timothytim@google.com>

[top] Auto generate

Signed-off-by: Timothy Chen <timothytim@google.com>
diff --git a/hw/ip/clkmgr/data/clkmgr.hjson.tpl b/hw/ip/clkmgr/data/clkmgr.hjson.tpl
index 1ee888e..cddbb91 100644
--- a/hw/ip/clkmgr/data/clkmgr.hjson.tpl
+++ b/hw/ip/clkmgr/data/clkmgr.hjson.tpl
@@ -48,6 +48,20 @@
       package: "clkmgr_pkg",
     },
 
+    { struct:  "lc_tx",
+      type:    "uni",
+      name:    "ast_clk_bypass_ack",
+      act:     "rcv",
+      package: "lc_ctrl_pkg",
+    },
+
+    { struct:  "lc_tx",
+      type:    "uni",
+      name:    "lc_clk_bypass_ack",
+      act:     "req",
+      package: "lc_ctrl_pkg",
+    },
+
   // All clock inputs
 % for src in srcs:
     { struct:  "logic",
diff --git a/hw/ip/clkmgr/data/clkmgr.sv.tpl b/hw/ip/clkmgr/data/clkmgr.sv.tpl
index 36e3d5a..03c708f 100644
--- a/hw/ip/clkmgr/data/clkmgr.sv.tpl
+++ b/hw/ip/clkmgr/data/clkmgr.sv.tpl
@@ -46,6 +46,10 @@
   // idle hints
   input [${len(hint_clks)-1}:0] idle_i,
 
+  // clock bypass control
+  input lc_ctrl_pkg::lc_tx_t ast_clk_bypass_ack_i,
+  output lc_ctrl_pkg::lc_tx_t lc_clk_bypass_ack_o,
+
   // clock output interface
 % for intf in export_clks:
   output clkmgr_${intf}_out_t clocks_${intf}_o,
@@ -74,6 +78,17 @@
   ////////////////////////////////////////////////////
   // Divided clocks
   ////////////////////////////////////////////////////
+
+  lc_ctrl_pkg::lc_tx_t step_down_req;
+  logic [${len(div_srcs)-1}:0] step_down_acks;
+
+  prim_lc_sync u_rcv (
+    .clk_i,
+    .rst_ni,
+    .lc_en_i(ast_clk_bypass_ack_i),
+    .lc_en_o(step_down_req)
+  );
+
 % for src in div_srcs:
   logic clk_${src['name']}_i;
 % endfor
@@ -84,11 +99,19 @@
   ) u_${src['name']}_div (
     .clk_i(clk_${src['src']}_i),
     .rst_ni(rst_${src['src']}_ni),
+    .step_down_req_i(step_down_req == lc_ctrl_pkg::On),
+    .step_down_ack_o(step_down_acks[${loop.index}]),
     .test_en_i(scanmode_i),
     .clk_o(clk_${src['name']}_i)
   );
 % endfor
 
+  prim_lc_sender u_send (
+   .clk_i,
+   .rst_ni,
+   .lc_en_i(&step_down_acks ? lc_ctrl_pkg::On : lc_ctrl_pkg::Off),
+   .lc_en_o(lc_clk_bypass_ack_o)
+  );
 
   ////////////////////////////////////////////////////
   // Feed through clocks
diff --git a/hw/ip/prim/lint/prim_clock_div.waiver b/hw/ip/prim/lint/prim_clock_div.waiver
index bd70fff..c9b4a73 100644
--- a/hw/ip/prim/lint/prim_clock_div.waiver
+++ b/hw/ip/prim/lint/prim_clock_div.waiver
@@ -3,3 +3,6 @@
 # SPDX-License-Identifier: Apache-2.0
 #
 # waiver file for prim_clock_div
+
+waive -rules DUAL_EDGE_CLOCK -location {prim_clock_div.sv} -regexp {.*} \
+      -comment "The clock switch signal is synchronized on negative edge to ensure it is away from any transition"
diff --git a/hw/ip/prim/prim_clock_div.core b/hw/ip/prim/prim_clock_div.core
index 7bd9bfb..ab031d7 100644
--- a/hw/ip/prim/prim_clock_div.core
+++ b/hw/ip/prim/prim_clock_div.core
@@ -16,7 +16,16 @@
       - rtl/prim_clock_div.sv
     file_type: systemVerilogSource
 
+  files_ascentlint_waiver:
+    depend:
+      # common waivers
+      - lowrisc:lint:common
+    files:
+      - lint/prim_clock_div.waiver
+    file_type: waiver
+
 targets:
   default:
     filesets:
+      - tool_ascentlint ? (files_ascentlint_waiver)
       - files_rtl
diff --git a/hw/ip/prim/rtl/prim_clock_div.sv b/hw/ip/prim/rtl/prim_clock_div.sv
index 35dbbba..838d146 100644
--- a/hw/ip/prim/rtl/prim_clock_div.sv
+++ b/hw/ip/prim/rtl/prim_clock_div.sv
@@ -10,11 +10,17 @@
 ) (
   input clk_i,
   input rst_ni,
+  input step_down_req_i, // step down divisor by 2x
+  output logic step_down_ack_o, // step down acknowledge
   input test_en_i,
   output logic clk_o
 );
 
 
+  // Only even divide is supported at the moment
+  // For odd divide we need to introduce more parameters to control duty cycle
+  `ASSERT_INIT(DivEven_A, (Divisor % 2) == 0)
+
   logic clk_int;
 
   if (Divisor == 2) begin : gen_div2
@@ -38,28 +44,56 @@
       .clk_no(q_n)
     );
 
-    assign clk_int = q_p;
+    logic step_down_nq;
+    always_ff @(negedge clk_i or negedge rst_ni) begin
+      if (!rst_ni) begin
+        step_down_nq <= 1'b0;
+      end else begin
+        step_down_nq <= step_down_req_i;
+      end
+    end
+
+    // make sure selection point is away from both edges
+    prim_clock_mux2 #(
+      .NoFpgaBufG(1'b1)
+    ) u_step_down_mux (
+      .clk0_i(q_p),
+      .clk1_i(clk_i),
+      .sel_i(step_down_nq),
+      .clk_o(clk_int)
+    );
+
+  assign step_down_ack_o = step_down_nq;
 
   end else begin : gen_div
-    // Only even divide is supported at the moment
-    // For odd divide we need to introduce more parameters to control duty cycle
-    `ASSERT_INIT(DivEven_A, (Divisor % 2) == 0)
 
     localparam int ToggleCnt = Divisor / 2;
     localparam int CntWidth = $clog2(ToggleCnt);
     logic [CntWidth-1:0] cnt;
+    logic [CntWidth-1:0] limit;
+
+    assign limit = !step_down_req_i     ? ToggleCnt - 1 :
+                   (ToggleCnt / 2) == 2 ? '0 : (ToggleCnt / 2) - 1;
 
     always_ff @(posedge clk_i or negedge rst_ni) begin
       if (!rst_ni) begin
         cnt <= '0;
         clk_int <= ResetValue;
-      end else if (cnt == ToggleCnt-1) begin
+      end else if (cnt >= limit) begin
         cnt <= '0;
         clk_int <= ~clk_o;
       end else begin
         cnt <= cnt + 1'b1;
       end
     end
+
+    always_ff @(posedge clk_i or negedge rst_ni) begin
+      if (!rst_ni) begin
+        step_down_ack_o <= 1'b0;
+      end else begin
+        step_down_ack_o <= step_down_req_i;
+      end
+    end
   end
 
   // when in scanmode, bypass the dividers completely
diff --git a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
index b73e62a..d573698 100644
--- a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
+++ b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
@@ -1684,6 +1684,9 @@
           default: lc_ctrl_pkg::Off
           package: lc_ctrl_pkg
           inst_name: lc_ctrl
+          width: 1
+          top_type: broadcast
+          top_signame: lc_ctrl_lc_clk_byp_ack
           index: -1
         }
         {
@@ -2541,6 +2544,27 @@
           index: -1
         }
         {
+          struct: lc_tx
+          type: uni
+          name: ast_clk_bypass_ack
+          act: rcv
+          package: lc_ctrl_pkg
+          inst_name: clkmgr
+          index: -1
+        }
+        {
+          struct: lc_tx
+          type: uni
+          name: lc_clk_bypass_ack
+          act: req
+          package: lc_ctrl_pkg
+          inst_name: clkmgr
+          width: 1
+          default: ""
+          top_signame: lc_ctrl_lc_clk_byp_ack
+          index: -1
+        }
+        {
           struct: logic
           type: uni
           name: clk_main
@@ -5641,6 +5665,10 @@
         otp_ctrl.lc_check_byp_en
       ]
       lc_ctrl.lc_clk_byp_req: []
+      lc_ctrl.lc_clk_byp_ack:
+      [
+        clkmgr.lc_clk_bypass_ack
+      ]
       lc_ctrl.lc_creator_seed_sw_rw_en:
       [
         otp_ctrl.lc_creator_seed_sw_rw_en
@@ -9163,6 +9191,9 @@
         default: lc_ctrl_pkg::Off
         package: lc_ctrl_pkg
         inst_name: lc_ctrl
+        width: 1
+        top_type: broadcast
+        top_signame: lc_ctrl_lc_clk_byp_ack
         index: -1
       }
       {
@@ -9677,6 +9708,27 @@
         index: -1
       }
       {
+        struct: lc_tx
+        type: uni
+        name: ast_clk_bypass_ack
+        act: rcv
+        package: lc_ctrl_pkg
+        inst_name: clkmgr
+        index: -1
+      }
+      {
+        struct: lc_tx
+        type: uni
+        name: lc_clk_bypass_ack
+        act: req
+        package: lc_ctrl_pkg
+        inst_name: clkmgr
+        width: 1
+        default: ""
+        top_signame: lc_ctrl_lc_clk_byp_ack
+        index: -1
+      }
+      {
         struct: logic
         type: uni
         name: clk_main
@@ -11875,6 +11927,14 @@
       {
         package: lc_ctrl_pkg
         struct: lc_tx
+        signame: lc_ctrl_lc_clk_byp_ack
+        width: 1
+        type: uni
+        default: lc_ctrl_pkg::Off
+      }
+      {
+        package: lc_ctrl_pkg
+        struct: lc_tx
         signame: lc_ctrl_lc_creator_seed_sw_rw_en
         width: 1
         type: uni
diff --git a/hw/top_earlgrey/data/top_earlgrey.hjson b/hw/top_earlgrey/data/top_earlgrey.hjson
index a5432ac..c474cba 100755
--- a/hw/top_earlgrey/data/top_earlgrey.hjson
+++ b/hw/top_earlgrey/data/top_earlgrey.hjson
@@ -641,8 +641,8 @@
 
       'lc_ctrl.lc_check_byp_en'    : ['otp_ctrl.lc_check_byp_en'],
       // TODO: OTP Clock bypass signal going from LC to AST/clkmgr
-      'lc_ctrl.lc_clk_byp_req'  : [],
-      //'lc_ctrl.lc_clk_byp_ack'  : [],
+      'lc_ctrl.lc_clk_byp_req'     : [],
+      'lc_ctrl.lc_clk_byp_ack'     : ['clkmgr.lc_clk_bypass_ack'],
 
       // LC access control signal broadcast
       'lc_ctrl.lc_creator_seed_sw_rw_en'   : ['otp_ctrl.lc_creator_seed_sw_rw_en',
diff --git a/hw/top_earlgrey/ip/clkmgr/clkmgr.core b/hw/top_earlgrey/ip/clkmgr/clkmgr.core
index dbaa2f6..b3067b4 100644
--- a/hw/top_earlgrey/ip/clkmgr/clkmgr.core
+++ b/hw/top_earlgrey/ip/clkmgr/clkmgr.core
@@ -13,6 +13,7 @@
       - lowrisc:prim:clock_gating
       - lowrisc:prim:clock_buf
       - lowrisc:prim:clock_div
+      - lowrisc:ip:lc_ctrl_pkg
       - lowrisc:ip:pwrmgr_pkg
       - lowrisc:systems:clkmgr_pkg
     files:
diff --git a/hw/top_earlgrey/ip/clkmgr/data/autogen/clkmgr.hjson b/hw/top_earlgrey/ip/clkmgr/data/autogen/clkmgr.hjson
index b840cd2..214722c 100644
--- a/hw/top_earlgrey/ip/clkmgr/data/autogen/clkmgr.hjson
+++ b/hw/top_earlgrey/ip/clkmgr/data/autogen/clkmgr.hjson
@@ -46,6 +46,20 @@
       package: "clkmgr_pkg",
     },
 
+    { struct:  "lc_tx",
+      type:    "uni",
+      name:    "ast_clk_bypass_ack",
+      act:     "rcv",
+      package: "lc_ctrl_pkg",
+    },
+
+    { struct:  "lc_tx",
+      type:    "uni",
+      name:    "lc_clk_bypass_ack",
+      act:     "req",
+      package: "lc_ctrl_pkg",
+    },
+
   // All clock inputs
     { struct:  "logic",
       type:    "uni",
diff --git a/hw/top_earlgrey/ip/clkmgr/rtl/autogen/clkmgr.sv b/hw/top_earlgrey/ip/clkmgr/rtl/autogen/clkmgr.sv
index e5d5414..fcb74bd 100644
--- a/hw/top_earlgrey/ip/clkmgr/rtl/autogen/clkmgr.sv
+++ b/hw/top_earlgrey/ip/clkmgr/rtl/autogen/clkmgr.sv
@@ -49,6 +49,10 @@
   // idle hints
   input [3:0] idle_i,
 
+  // clock bypass control
+  input lc_ctrl_pkg::lc_tx_t ast_clk_bypass_ack_i,
+  output lc_ctrl_pkg::lc_tx_t lc_clk_bypass_ack_o,
+
   // clock output interface
   output clkmgr_ast_out_t clocks_ast_o,
   output clkmgr_out_t clocks_o
@@ -75,6 +79,17 @@
   ////////////////////////////////////////////////////
   // Divided clocks
   ////////////////////////////////////////////////////
+
+  lc_ctrl_pkg::lc_tx_t step_down_req;
+  logic [1:0] step_down_acks;
+
+  prim_lc_sync u_rcv (
+    .clk_i,
+    .rst_ni,
+    .lc_en_i(ast_clk_bypass_ack_i),
+    .lc_en_o(step_down_req)
+  );
+
   logic clk_io_div2_i;
   logic clk_io_div4_i;
 
@@ -83,6 +98,8 @@
   ) u_io_div2_div (
     .clk_i(clk_io_i),
     .rst_ni(rst_io_ni),
+    .step_down_req_i(step_down_req == lc_ctrl_pkg::On),
+    .step_down_ack_o(step_down_acks[0]),
     .test_en_i(scanmode_i),
     .clk_o(clk_io_div2_i)
   );
@@ -91,10 +108,18 @@
   ) u_io_div4_div (
     .clk_i(clk_io_i),
     .rst_ni(rst_io_ni),
+    .step_down_req_i(step_down_req == lc_ctrl_pkg::On),
+    .step_down_ack_o(step_down_acks[1]),
     .test_en_i(scanmode_i),
     .clk_o(clk_io_div4_i)
   );
 
+  prim_lc_sender u_send (
+   .clk_i,
+   .rst_ni,
+   .lc_en_i(&step_down_acks ? lc_ctrl_pkg::On : lc_ctrl_pkg::Off),
+   .lc_en_o(lc_clk_bypass_ack_o)
+  );
 
   ////////////////////////////////////////////////////
   // Feed through clocks
diff --git a/hw/top_earlgrey/lint/top_earlgrey.waiver b/hw/top_earlgrey/lint/top_earlgrey.waiver
index 3762ff5..5420f35 100644
--- a/hw/top_earlgrey/lint/top_earlgrey.waiver
+++ b/hw/top_earlgrey/lint/top_earlgrey.waiver
@@ -6,7 +6,7 @@
 
 # dedicated reset drivers / muxes
 set_reset_drivers prim_clock_mux2 prim_flop_2sync prim_flop
-set_clock_drivers prim_clock_buf
+set_clock_drivers prim_clock_buf prim_clock_mux2
 
 # All leaf resets have a reset multiplex
 waive -rules RESET_MUX -location {top_earlgrey.sv} -regexp {Asynchronous reset .*rstmgr_resets\.rst.* is driven by a multiplexer} \
diff --git a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
index 2279807..a67f802 100644
--- a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
+++ b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
@@ -304,6 +304,7 @@
   lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_escalate_en;
   lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_check_byp_en;
   lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_clk_byp_req;
+  lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_clk_byp_ack;
   lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_creator_seed_sw_rw_en;
   lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_owner_seed_sw_rw_en;
   lc_ctrl_pkg::lc_tx_t       lc_ctrl_lc_iso_part_sw_rd_en;
@@ -924,7 +925,7 @@
       .lc_keymgr_en_o(),
       .lc_escalate_en_o(lc_ctrl_lc_escalate_en),
       .lc_clk_byp_req_o(lc_ctrl_lc_clk_byp_req),
-      .lc_clk_byp_ack_i(lc_ctrl_pkg::Off),
+      .lc_clk_byp_ack_i(lc_ctrl_lc_clk_byp_ack),
       .lc_flash_rma_req_o(flash_ctrl_rma_req),
       .lc_flash_rma_seed_o(flash_ctrl_rma_seed),
       .lc_flash_rma_ack_i(flash_ctrl_rma_ack),
@@ -1045,6 +1046,8 @@
 
       // Inter-module signals
       .clocks_o(clkmgr_clocks),
+      .ast_clk_bypass_ack_i(lc_ctrl_pkg::LC_TX_DEFAULT),
+      .lc_clk_bypass_ack_o(lc_ctrl_lc_clk_byp_ack),
       .clk_main_i(clk_main_i),
       .clk_io_i(clk_io_i),
       .clk_usb_i(clk_usb_i),