[pinmux] Update pinmux implementation and add sleep/strap sampling features

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/pinmux/pinmux.core b/hw/ip/pinmux/pinmux.core
index 3e7d7e5..e32e3b8 100644
--- a/hw/ip/pinmux/pinmux.core
+++ b/hw/ip/pinmux/pinmux.core
@@ -11,7 +11,6 @@
       - lowrisc:ip:pinmux_reg
       - lowrisc:ip:pinmux_component
 
-
 parameters:
   SYNTHESIS:
     datatype: bool
diff --git a/hw/ip/pinmux/pinmux_component.core b/hw/ip/pinmux/pinmux_component.core
index e405669..f130a7f 100644
--- a/hw/ip/pinmux/pinmux_component.core
+++ b/hw/ip/pinmux/pinmux_component.core
@@ -11,6 +11,8 @@
       - lowrisc:ip:tlul
       - lowrisc:prim:all
     files:
+      - rtl/pinmux_pkg.sv
+      - rtl/pinmux_wkup.sv
       - rtl/pinmux.sv
     file_type: systemVerilogSource
 
diff --git a/hw/ip/pinmux/rtl/pinmux.sv b/hw/ip/pinmux/rtl/pinmux.sv
index bd6993d..bf79015 100644
--- a/hw/ip/pinmux/rtl/pinmux.sv
+++ b/hw/ip/pinmux/rtl/pinmux.sv
@@ -7,27 +7,59 @@
 
 `include "prim_assert.sv"
 
-module pinmux (
-  input                                         clk_i,
-  input                                         rst_ni,
+module pinmux import pinmux_pkg::*; import pinmux_reg_pkg::*; (
+  input                            clk_i,
+  input                            rst_ni,
+  // Slow always-on clock
+  input                            clk_aon_i,
+  input                            rst_aon_ni,
+  // Wakeup request, running on clk_aon_i
+  output logic                     aon_wkup_req_o,
+  // Sleep enable, running on clk_i
+  input                            sleep_en_i,
+  // Strap sample request
+  input  lc_strap_req_t            lc_pinmux_strap_i,
+  output lc_strap_rsp_t            lc_pinmux_strap_o,
   // Bus Interface (device)
-  input  tlul_pkg::tl_h2d_t                     tl_i,
-  output tlul_pkg::tl_d2h_t                     tl_o,
-  // Peripheral side
-  input        [pinmux_reg_pkg::NPeriphOut-1:0] periph_to_mio_i,
-  input        [pinmux_reg_pkg::NPeriphOut-1:0] periph_to_mio_oe_i,
-  output logic [pinmux_reg_pkg::NPeriphIn-1:0]  mio_to_periph_o,
+  input  tlul_pkg::tl_h2d_t        tl_i,
+  output tlul_pkg::tl_d2h_t        tl_o,
+  // Muxed Peripheral side
+  input        [NMioPeriphOut-1:0] periph_to_mio_i,
+  input        [NMioPeriphOut-1:0] periph_to_mio_oe_i,
+  output logic [NMioPeriphIn-1:0]  mio_to_periph_o,
+  // Dedicated Peripheral side
+  input        [NDioPads-1:0]      periph_to_dio_i,
+  input        [NDioPads-1:0]      periph_to_dio_oe_i,
+  output logic [NDioPads-1:0]      dio_to_periph_o,
   // Pad side
-  output logic [pinmux_reg_pkg::NMioPads-1:0]   mio_out_o,
-  output logic [pinmux_reg_pkg::NMioPads-1:0]   mio_oe_o,
-  input        [pinmux_reg_pkg::NMioPads-1:0]   mio_in_i
+  // MIOs
+  output logic [NMioPads-1:0]      mio_out_o,
+  output logic [NMioPads-1:0]      mio_oe_o,
+  input        [NMioPads-1:0]      mio_in_i,
+  // DIOs
+  output logic [NDioPads-1:0]      dio_out_o,
+  output logic [NDioPads-1:0]      dio_oe_o,
+  input        [NDioPads-1:0]      dio_in_i
 );
 
+  ////////////////////////////
+  // Parameters / Constants //
+  ////////////////////////////
+
+  // TODO: these need to be parameterizable via topgen at some point.
+  // They have been placed here such that they do not generate
+  // warnings in the C header generation step, since logic is not supported
+  // as a data type yet.
+  localparam logic [pinmux_reg_pkg::NMioPeriphOut-1:0] MioPeriphHasSleepMode = '1;
+  localparam logic [pinmux_reg_pkg::NDioPads-1:0]      DioPeriphHasSleepMode = '1;
+  localparam logic [pinmux_reg_pkg::NDioPads-1:0]      DioPeriphHasWkup      = '1;
+
   //////////////////////////////////
   // Regfile Breakout and Mapping //
   //////////////////////////////////
 
-  pinmux_reg_pkg::pinmux_reg2hw_t reg2hw;
+  pinmux_reg2hw_t reg2hw;
+  pinmux_hw2reg_t hw2reg;
 
   pinmux_reg_top i_reg_top (
     .clk_i  ,
@@ -35,15 +67,91 @@
     .tl_i   ,
     .tl_o   ,
     .reg2hw ,
+    .hw2reg ,
     .devmode_i(1'b1)
   );
 
+  /////////////////////
+  // Sleep registers //
+  /////////////////////
+
+  logic sleep_en_q;
+  logic [NMioPads-1:0] mio_out_sleep_d, mio_oe_sleep_d;
+  logic [NMioPads-1:0] mio_out_sleep_q, mio_oe_sleep_q;
+  logic [NDioPads-1:0] dio_out_sleep_d, dio_oe_sleep_d;
+  logic [NDioPads-1:0] dio_out_sleep_q, dio_oe_sleep_q;
+  // these are external due to their WARL behavior
+  logic [NDioPads-1:0][1:0] dio_out_sleep_val_d, dio_out_sleep_val_q;
+
+  // latch MIO/DIO state when going to sleep
+  // 0: drive low
+  // 1: drive high
+  // 2: high-z
+  // 3: previous value
+  for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_sleep
+    assign mio_out_sleep_d[k] = (reg2hw.mio_out_sleep_val[k].q == 0) ? 1'b0 :
+                                (reg2hw.mio_out_sleep_val[k].q == 1) ? 1'b1 :
+                                (reg2hw.mio_out_sleep_val[k].q == 2) ? 1'b0 : mio_out_o[k];
+
+    assign mio_oe_sleep_d[k] = (reg2hw.mio_out_sleep_val[k].q == 0) ? 1'b1 :
+                               (reg2hw.mio_out_sleep_val[k].q == 1) ? 1'b1 :
+                               (reg2hw.mio_out_sleep_val[k].q == 2) ? 1'b0 : mio_oe_o[k];
+  end
+
+  // since DIO pads are permanently mapped to a specific peripheral,
+  // we only need to support retention regs on non-always on peripherals,
+  // outputs / inouts.
+  for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_sleep
+    if (DioPeriphHasSleepMode[k]) begin : gen_warl_connect
+      assign hw2reg.dio_out_sleep_val[k].d = dio_out_sleep_val_q[k];
+
+      assign dio_out_sleep_val_d[k] = (reg2hw.dio_out_sleep_val[k].qe) ?
+                                      reg2hw.dio_out_sleep_val[k].q :
+                                      dio_out_sleep_val_q[k];
+
+      assign dio_out_sleep_d[k] = (dio_out_sleep_val_q[k] == 0) ? 1'b0 :
+                                  (dio_out_sleep_val_q[k] == 1) ? 1'b1 :
+                                  (dio_out_sleep_val_q[k] == 2) ? 1'b0 : dio_out_o[k];
+
+      assign dio_oe_sleep_d[k] = (dio_out_sleep_val_q[k] == 0) ? 1'b1 :
+                                 (dio_out_sleep_val_q[k] == 1) ? 1'b1 :
+                                 (dio_out_sleep_val_q[k] == 2) ? 1'b0 : dio_oe_o[k];
+    end else begin : gen_warl_tie0
+      // these signals will be unused
+      assign hw2reg.dio_out_sleep_val[k].d = 2'b10; // default value defined in hjson
+      assign dio_out_sleep_val_d[k] = 2'b10; // default value defined in hjson
+      assign dio_out_sleep_d[k]     = '0;
+      assign dio_oe_sleep_d[k]      = '0;
+    end
+  end
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin : p_sleep
+    if (!rst_ni) begin
+      sleep_en_q          <= 1'b0;
+      dio_out_sleep_val_q <= {NDioPads{2'b10}}; // default value defined in hjson
+      mio_out_sleep_q     <= '0;
+      mio_oe_sleep_q      <= '0;
+      dio_out_sleep_q     <= '0;
+      dio_oe_sleep_q      <= '0;
+    end else begin
+      sleep_en_q          <= sleep_en_i;
+      dio_out_sleep_val_q <= dio_out_sleep_val_d;
+
+      if (sleep_en_i & !sleep_en_q) begin
+        mio_out_sleep_q <= mio_out_sleep_d;
+        mio_oe_sleep_q  <= mio_oe_sleep_d;
+        dio_out_sleep_q <= dio_out_sleep_d;
+        dio_oe_sleep_q  <= dio_oe_sleep_d;
+      end
+    end
+  end
+
   ///////////////
   // Input Mux //
   ///////////////
 
-  for (genvar k = 0; k < pinmux_reg_pkg::NPeriphIn; k++) begin : gen_periph_in
-    logic [2**$clog2(pinmux_reg_pkg::NMioPads+2)-1:0] data_mux;
+  for (genvar k = 0; k < NMioPeriphIn; k++) begin : gen_mio_periph_in
+    logic [2**$clog2(NMioPads+2)-1:0] data_mux;
     // stack input and default signals for convenient indexing below
     // possible defaults: constant 0 or 1
     assign data_mux = $bits(data_mux)'({mio_in_i, 1'b1, 1'b0});
@@ -55,15 +163,110 @@
   // Output Mux //
   ////////////////
 
-  for (genvar k = 0; k < pinmux_reg_pkg::NMioPads; k++) begin : gen_mio_out
-    logic [2**$clog2(pinmux_reg_pkg::NPeriphOut+3)-1:0] data_mux, oe_mux;
+  for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_out
+    logic sleep_en;
+    logic [2**$clog2(NMioPeriphOut+3)-1:0] data_mux, oe_mux, sleep_mux;
     // stack output data/enable and default signals for convenient indexing below
     // possible defaults: 0, 1 or 2 (high-Z)
-    assign data_mux = $bits(data_mux)'({periph_to_mio_i, 1'b0, 1'b1, 1'b0});
-    assign oe_mux   = $bits(oe_mux)'({periph_to_mio_oe_i,  1'b0, 1'b1, 1'b1});
+    assign data_mux  = $bits(data_mux)'({periph_to_mio_i, 1'b0, 1'b1, 1'b0});
+    assign oe_mux    = $bits(oe_mux)'({periph_to_mio_oe_i,  1'b0, 1'b1, 1'b1});
+    assign sleep_mux = $bits(sleep_mux)'({MioPeriphHasSleepMode,  1'b1, 1'b1, 1'b1});
+
+    // check whether this peripheral can actually go to sleep
+    assign sleep_en = sleep_mux[reg2hw.mio_outsel[k].q] & sleep_en_q;
     // index using configured outsel
-    assign mio_out_o[k] = data_mux[reg2hw.mio_outsel[k].q];
-    assign mio_oe_o[k]  = oe_mux[reg2hw.mio_outsel[k].q];
+    assign mio_out_o[k] = (sleep_en) ? mio_out_sleep_q[k] : data_mux[reg2hw.mio_outsel[k].q];
+    assign mio_oe_o[k]  = (sleep_en) ? mio_oe_sleep_q[k]  : oe_mux[reg2hw.mio_outsel[k].q];
+  end
+
+  /////////////////////
+  // DIO connections //
+  /////////////////////
+
+  // Inputs are just fed through
+  assign dio_to_periph_o = dio_in_i;
+
+  for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_out
+    // Since this is a DIO, this can be determined at design time
+    if (DioPeriphHasSleepMode[k]) begin : gen_sleep
+      assign dio_out_o[k] = (sleep_en_q) ? dio_out_sleep_q[k] : periph_to_dio_i[k];
+      assign dio_oe_o[k]  = (sleep_en_q) ? dio_oe_sleep_q[k]  : periph_to_dio_oe_i[k];
+    end else begin : gen_nosleep
+      assign dio_out_o[k] = periph_to_dio_i[k];
+      assign dio_oe_o[k]  = periph_to_dio_oe_i[k];
+    end
+  end
+
+  //////////////////////
+  // Wakeup detectors //
+  //////////////////////
+
+  localparam int AlignedMuxSize = (NMioPads + 2 > NDioPads) ? 2**$clog2(NMioPads + 2) :
+                                                              2**$clog2(NDioPads);
+  logic [NWkupDetect-1:0] aon_wkup_req;
+  logic [AlignedMuxSize-1:0] dio_data_mux, mio_data_mux;
+  assign mio_data_mux = AlignedMuxSize'({mio_in_i, 1'b0, 1'b0});
+
+  // Only connect DIOs that are not excempt
+  for (genvar k = 0; k < AlignedMuxSize; k++) begin : gen_dio_wkup
+    if (k < NDioPads && DioPeriphHasWkup[k]) begin : gen_dio_wkup_connect
+      assign dio_data_mux[k] = dio_in_i[k];
+    end else begin : gen_dio_tie_off
+      assign dio_data_mux[k] = 1'b0;
+    end
+  end
+
+  for (genvar k = 0; k < NWkupDetect; k++) begin : gen_wkup_detect
+    logic pin_value;
+    assign pin_value = (reg2hw.wkup_detector[k].miodio.q)           ?
+                       dio_data_mux[reg2hw.wkup_detector_padsel[k]] :
+                       mio_data_mux[reg2hw.wkup_detector_padsel[k]];
+
+    pinmux_wkup i_pinmux_wkup (
+      .clk_i,
+      .rst_ni,
+      .clk_aon_i,
+      .rst_aon_ni,
+      // config signals. these are synched to clk_aon internally
+      .wkup_en_i          ( reg2hw.wkup_detector_en[k].q     ),
+      .filter_en_i        ( reg2hw.wkup_detector[k].filter.q ),
+      .wkup_mode_i        ( reg2hw.wkup_detector[k].mode.q   ),
+      .wkup_cnt_th_i      ( reg2hw.wkup_detector_cnt_th[k].q ),
+      .pin_value_i        ( pin_value                        ),
+      // cause reg signals. these are synched from/to clk_aon internally
+      .wkup_cause_valid_i ( reg2hw.wkup_cause[k].qe          ),
+      .wkup_cause_data_i  ( reg2hw.wkup_cause[k].q           ),
+      .wkup_cause_data_o  ( hw2reg.wkup_cause[k].d           ),
+      // wakeup request signals on clk_aon (level encoded)
+      .aon_wkup_req_o     ( aon_wkup_req[k]                  )
+    );
+  end
+
+  // OR' together all wakeup requests
+  assign aon_wkup_req_o = |aon_wkup_req;
+
+  ////////////////////
+  // Strap Sampling //
+  ////////////////////
+
+  logic [NStraps-1:0] lc_strap_taps;
+  lc_strap_rsp_t lc_strap_d, lc_strap_q;
+
+  for (genvar k = 0; k < NStraps; k++) begin : gen_strap_taps
+    assign lc_strap_taps[k] = mio_in_i[MioStrapPos[k]];
+  end
+
+  assign lc_pinmux_strap_o = lc_strap_q;
+  assign lc_strap_d = (lc_pinmux_strap_i.sample_pulse)      ?
+                      '{valid: 1'b1, straps: lc_strap_taps} :
+                      lc_strap_q;
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin : p_strap_sample
+    if (!rst_ni) begin
+      lc_strap_q <= '0;
+    end else begin
+      lc_strap_q <= lc_strap_d;
+    end
   end
 
   ////////////////
@@ -72,8 +275,22 @@
 
   `ASSERT_KNOWN(TlDValidKnownO_A, tl_o.d_valid)
   `ASSERT_KNOWN(TlAReadyKnownO_A, tl_o.a_ready)
-  `ASSERT_KNOWN(MioToPeriphKnownO_A, mio_to_periph_o)
-  `ASSERT_KNOWN(MioOutKnownO_A, mio_out_o)
+  // `ASSERT_KNOWN(MioToPeriphKnownO_A, mio_to_periph_o)
   `ASSERT_KNOWN(MioOeKnownO_A, mio_oe_o)
+  // `ASSERT_KNOWN(DioToPeriphKnownO_A, dio_to_periph_o)
+  `ASSERT_KNOWN(DioOeKnownO_A, dio_oe_o)
+  `ASSERT_KNOWN(LcPinmuxStrapKnownO_A, lc_pinmux_strap_o)
+
+  // TODO: need to check why some outputs are not valid (e.g. SPI device MISO)
+  // for (genvar k = 0; k < NMioPads; k++) begin : gen_mio_known_if
+  //   `ASSERT_KNOWN_IF(MioOutKnownO_A, mio_out_o[k], mio_oe_o[k])
+  // end
+
+  // for (genvar k = 0; k < NDioPads; k++) begin : gen_dio_known_if
+  //   `ASSERT_KNOWN_IF(DioOutKnownO_A, dio_out_o[k], dio_oe_o[k])
+  // end
+
+  // running on slow AON clock
+  `ASSERT_KNOWN(AonWkupReqKnownO_A, aon_wkup_req_o, !rst_aon_ni, clk_aon_i)
 
 endmodule : pinmux
diff --git a/hw/ip/pinmux/rtl/pinmux_pkg.sv b/hw/ip/pinmux/rtl/pinmux_pkg.sv
new file mode 100644
index 0000000..fe73341
--- /dev/null
+++ b/hw/ip/pinmux/rtl/pinmux_pkg.sv
@@ -0,0 +1,35 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package pinmux_pkg;
+
+  // Wakeup Detector Modes
+  typedef enum logic [2:0] {
+    Disabled  = 3'b000,
+    Negedge   = 3'b001,
+    Posedge   = 3'b010,
+    Edge      = 3'b011,
+    LowTimed  = 3'b100,
+    HighTimed = 3'b101
+  } wkup_mode_e;
+
+  // Interface with LC controller
+  parameter int NStraps  = 2;
+  // Strap sampling is only supported on MIOs at the moment
+  parameter int MioStrapPos [0:NStraps-1] = '{1, 0};
+
+  typedef struct packed {
+    logic sample_pulse;
+  } lc_strap_req_t;
+
+  parameter lc_strap_req_t LC_PINMUX_STRAP_REQ_DEFAULT = '{
+    sample_pulse: 1'b0
+  };
+
+  typedef struct packed {
+    logic               valid;
+    logic [NStraps-1:0] straps;
+  } lc_strap_rsp_t;
+
+endpackage : pinmux_pkg
diff --git a/hw/ip/pinmux/rtl/pinmux_wkup.sv b/hw/ip/pinmux/rtl/pinmux_wkup.sv
new file mode 100644
index 0000000..58475e2
--- /dev/null
+++ b/hw/ip/pinmux/rtl/pinmux_wkup.sv
@@ -0,0 +1,198 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+module pinmux_wkup import pinmux_pkg::*; import pinmux_reg_pkg::*; #(
+  parameter Cycles = 4
+) (
+  input                    clk_i,
+  input                    rst_ni,
+  // Always on clock / reset
+  input                    clk_aon_i,
+  input                    rst_aon_ni,
+  // These signals get synchronized to the
+  // slow AON clock within this module.
+  // Note that wkup_en_i is assumed to be level encoded.
+  input                    wkup_en_i,
+  input                    filter_en_i,
+  input wkup_mode_e        wkup_mode_i,
+  input [WkupCntWidth-1:0] wkup_cnt_th_i,
+  input                    pin_value_i,
+  // Signals to/from cause register.
+  // They are synched to/from the AON clock internally
+  input                    wkup_cause_valid_i,
+  input                    wkup_cause_data_i,
+  output                   wkup_cause_data_o,
+  // This signal is running on the AON clock
+  // and is held high as long as the cause register
+  // has not been cleared.
+  output logic             aon_wkup_req_o
+);
+
+  ///////////////////////////
+  // Input Synchronization //
+  ///////////////////////////
+
+  // Synchronize configuration to slow clock
+  wkup_mode_e aon_wkup_mode_q;
+  logic aon_filter_en_q;
+  logic aon_wkup_en_d, aon_wkup_en_q;
+  logic [WkupCntWidth-1:0] aon_wkup_cnt_th_q;
+
+  prim_flop_2sync #(
+    .Width(1)
+  ) i_prim_flop_2sync_config (
+    .clk_i  ( clk_aon_i      ),
+    .rst_ni ( rst_aon_ni     ),
+    .d      ( wkup_en_i     ),
+    .q      ( aon_wkup_en_d )
+  );
+
+  always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : p_sync
+    if (!rst_aon_ni) begin
+      aon_wkup_en_q     <= 1'b0;
+      aon_wkup_mode_q   <= Disabled;
+      aon_filter_en_q   <= 1'b0;
+      aon_wkup_cnt_th_q <= '0;
+    end else begin
+      aon_wkup_en_q <= aon_wkup_en_d;
+      // latch these when going into sleep. note that these
+      // config signals should be stable at this point, since
+      // SW has configured them many cycles ago. hence no
+      // explicit multibit consistency check is performed.
+      if (aon_wkup_en_d & !aon_wkup_en_q) begin
+        aon_wkup_mode_q   <= wkup_mode_i;
+        aon_filter_en_q   <= filter_en_i;
+        aon_wkup_cnt_th_q <= wkup_cnt_th_i;
+      end
+    end
+  end
+
+  ////////////////////////////
+  // Optional Signal Filter //
+  ////////////////////////////
+
+  // This uses a lower value for filtering than GPIO since
+  // the always-on clock is slower. This can be disabled,
+  // in which case the signal is just combinationally bypassed.
+  logic aon_filter_out, aon_filter_out_d, aon_filter_out_q;
+  prim_filter #(
+    .Cycles(Cycles)
+  ) i_prim_filter (
+    .clk_i    ( clk_aon_i       ),
+    .rst_ni   ( rst_aon_ni      ),
+    .enable_i ( aon_filter_en_q ),
+    .filter_i ( pin_value_i     ),
+    .filter_o ( aon_filter_out  )
+  );
+
+  // Run this through a 2 stage synchronizer to
+  // prevent metastability.
+  prim_flop_2sync #(
+    .Width(1)
+  ) i_prim_flop_2sync_filter (
+    .clk_i  ( clk_aon_i  ),
+    .rst_ni ( rst_aon_ni ),
+    .d      ( aon_filter_out ),
+    .q      ( aon_filter_out_d )
+  );
+
+  //////////////////////
+  // Pattern Matching //
+  //////////////////////
+
+  logic aon_rising, aon_falling;
+  assign aon_falling = ~aon_filter_out_d &  aon_filter_out_q;
+  assign aon_rising  =  aon_filter_out_d & ~aon_filter_out_q;
+
+  logic aon_cnt_en, aon_cnt_eq_th;
+  logic [WkupCntWidth-1:0] aon_cnt_d, aon_cnt_q;
+  assign aon_cnt_d = (aon_cnt_eq_th) ? '0                :
+                     (aon_cnt_en)    ?  aon_cnt_q + 1'b1 : '0;
+
+  assign aon_cnt_eq_th = aon_cnt_q == aon_wkup_cnt_th_q;
+
+  logic aon_wkup_pulse;
+  always_comb begin : p_mode
+    aon_wkup_pulse = 1'b0;
+    aon_cnt_en     = 1'b0;
+    if (aon_wkup_en_q) begin
+      unique case (aon_wkup_mode_q)
+        Negedge:   aon_wkup_pulse = aon_falling;
+        Posedge:   aon_wkup_pulse = aon_rising;
+        Edge:      aon_wkup_pulse = aon_rising | aon_falling;
+        LowTimed: begin
+          aon_cnt_en = ~aon_filter_out_d;
+          aon_wkup_pulse = aon_cnt_eq_th;
+        end
+        HighTimed: begin
+          aon_cnt_en = aon_filter_out_d;
+          aon_wkup_pulse = aon_cnt_eq_th;
+        end
+        default: ; // also covers "Disabled"
+      endcase
+    end
+  end
+
+  always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : p_aon_pattern
+    if (!rst_aon_ni) begin
+      aon_filter_out_q <= 1'b0;
+      aon_cnt_q        <= '0;
+    end else begin
+      aon_filter_out_q <= aon_filter_out_d;
+      aon_cnt_q        <= aon_cnt_d;
+    end
+  end
+
+  ////////////////////
+  // Cause register //
+  ////////////////////
+
+  // to AON domain
+  logic aon_wkup_cause_valid, aon_wkup_cause_data;
+  logic aon_wkup_cause_d, aon_wkup_cause_q;
+
+  prim_flop_2sync #(
+    .Width(1)
+  ) i_prim_flop_2sync_cause_in (
+    .clk_i  ( clk_aon_i  ),
+    .rst_ni ( rst_aon_ni ),
+    .d      ( wkup_cause_data_i   ),
+    .q      ( aon_wkup_cause_data )
+  );
+
+  prim_pulse_sync i_prim_pulse_sync_cause (
+    .clk_src_i   ( clk_i                ),
+    .rst_src_ni  ( rst_ni               ),
+    .src_pulse_i ( wkup_cause_valid_i   ),
+    .clk_dst_i   ( clk_aon_i            ),
+    .rst_dst_ni  ( rst_aon_ni           ),
+    .dst_pulse_o ( aon_wkup_cause_valid )
+  );
+
+  // note that aon_wkup_pulse will not be asserted when not in sleep mode
+  assign aon_wkup_cause_d = (aon_wkup_cause_valid) ? aon_wkup_cause_q & aon_wkup_cause_data :
+                                                     aon_wkup_cause_q | aon_wkup_pulse;
+
+  // output to power manager
+  assign aon_wkup_req_o = aon_wkup_cause_q;
+
+  // output to CSR
+  prim_flop_2sync #(
+    .Width(1)
+  ) i_prim_flop_2sync_cause_out (
+    .clk_i,
+    .rst_ni,
+    .d      ( aon_wkup_cause_q  ),
+    .q      ( wkup_cause_data_o )
+  );
+
+  always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : p_aon_cause
+    if (!rst_aon_ni) begin
+      aon_wkup_cause_q <= 1'b0;
+    end else begin
+      aon_wkup_cause_q <= aon_wkup_cause_d;
+    end
+  end
+
+endmodule : pinmux_wkup