[alert_handler] Add main alert_handler RTL files
diff --git a/hw/ip/alert_handler/rtl/alert_handler.sv b/hw/ip/alert_handler/rtl/alert_handler.sv
new file mode 100644
index 0000000..a72da64
--- /dev/null
+++ b/hw/ip/alert_handler/rtl/alert_handler.sv
@@ -0,0 +1,208 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Alert handler top.
+//
+// Note that the alert_pkg, the regfile and alert_handler_reg_wrap
+// have to be generated using the reg_alert_handler.py script.
+//
+
+module alert_handler (
+ input clk_i,
+ input rst_ni,
+ // Bus Interface (device)
+ input tlul_pkg::tl_h2d_t tl_i,
+ output tlul_pkg::tl_d2h_t tl_o,
+ // Interrupt Requests
+ output logic [alert_pkg::N_CLASSES-1:0] irq_o,
+ // Entropy Input from TRNG
+ input entropy_i,
+ // Alert Sources
+ input [alert_pkg::NAlerts-1:0] alert_pi,
+ input [alert_pkg::NAlerts-1:0] alert_ni,
+ output logic [alert_pkg::NAlerts-1:0] ack_po,
+ output logic [alert_pkg::NAlerts-1:0] ack_no,
+ output logic [alert_pkg::NAlerts-1:0] ping_po,
+ output logic [alert_pkg::NAlerts-1:0] ping_no,
+ // Escalation outputs
+ output logic [alert_pkg::N_ESC_SEV-1:0] esc_po,
+ output logic [alert_pkg::N_ESC_SEV-1:0] esc_no,
+ input [alert_pkg::N_ESC_SEV-1:0] resp_pi,
+ input [alert_pkg::N_ESC_SEV-1:0] resp_ni
+);
+
+ //////////////////////////////////////////////////////
+ // Regfile Breakout and Mapping
+ //////////////////////////////////////////////////////
+
+ alert_pkg::hw2reg_wrap_t hw2reg_wrap;
+ alert_pkg::reg2hw_wrap_t reg2hw_wrap;
+
+ alert_handler_reg_wrap i_reg_wrap (
+ .clk_i ,
+ .rst_ni ,
+ .tl_i ,
+ .tl_o ,
+ .irq_o ,
+ .hw2reg_wrap ,
+ .reg2hw_wrap
+ );
+
+ //////////////////////////////////////////////////////
+ // Ping Timer
+ //////////////////////////////////////////////////////
+
+ logic [alert_pkg::N_LOC_ALERT-1:0] loc_alert_trig;
+
+ logic [alert_pkg::NAlerts-1:0] alert_ping_en;
+ logic [alert_pkg::NAlerts-1:0] alert_ping_ok;
+ logic [alert_pkg::N_ESC_SEV-1:0] esc_ping_en;
+ logic [alert_pkg::N_ESC_SEV-1:0] esc_ping_ok;
+
+ alert_handler_ping_timer i_ping_timer (
+ .clk_i,
+ .rst_ni,
+ .entropy_i,
+ // we enable ping testing as soon as the config
+ // regs have been locked
+ .en_i ( reg2hw_wrap.config_locked ),
+ .alert_en_i ( reg2hw_wrap.alert_en ),
+ .ping_timeout_cyc_i ( reg2hw_wrap.ping_timeout_cyc ),
+ .alert_ping_en_o ( alert_ping_en ),
+ .esc_ping_en_o ( esc_ping_en ),
+ .alert_ping_ok_i ( alert_ping_ok ),
+ .esc_ping_ok_i ( esc_ping_ok ),
+ .alert_ping_fail_o ( loc_alert_trig[0] ),
+ .esc_ping_fail_o ( loc_alert_trig[1] )
+ );
+
+ //////////////////////////////////////////////////////
+ // Alert Receivers
+ //////////////////////////////////////////////////////
+
+ logic [alert_pkg::NAlerts-1:0] alert_integfail;
+ logic [alert_pkg::NAlerts-1:0] alert_trig;
+
+ // Target interrupt notification
+ for (genvar k = 0 ; k < alert_pkg::NAlerts ; k++) begin : gen_alerts
+ prim_alert_receiver #(
+ .AsyncOn(alert_pkg::AsyncOn[k])
+ ) i_alert_receiver (
+ .clk_i ,
+ .rst_ni ,
+ .ping_en_i ( alert_ping_en[k] ),
+ .ping_ok_o ( alert_ping_ok[k] ),
+ .integ_fail_o ( alert_integfail[k] ),
+ .alert_o ( alert_trig[k] ),
+ .ping_po ( ping_po[k] ),
+ .ping_no ( ping_no[k] ),
+ .ack_po ( ack_po[k] ),
+ .ack_no ( ack_no[k] ),
+ .alert_pi ( alert_pi[k] ),
+ .alert_ni ( alert_ni[k] )
+ );
+ end
+
+ assign loc_alert_trig[2] = |(reg2hw_wrap.alert_en & alert_integfail);
+
+ //////////////////////////////////////////////////////
+ // Set alert cause bits and classify
+ //////////////////////////////////////////////////////
+
+ alert_handler_class i_class (
+ .alert_trig_i ( alert_trig ),
+ .loc_alert_trig_i ( loc_alert_trig ),
+ .alert_en_i ( reg2hw_wrap.alert_en ),
+ .loc_alert_en_i ( reg2hw_wrap.loc_alert_en ),
+ .alert_class_i ( reg2hw_wrap.alert_class ),
+ .loc_alert_class_i ( reg2hw_wrap.loc_alert_class ),
+ .class_en_i ( reg2hw_wrap.class_en ),
+ .alert_cause_o ( hw2reg_wrap.alert_cause ),
+ .loc_alert_cause_o ( hw2reg_wrap.loc_alert_cause ),
+ .class_trig_o ( hw2reg_wrap.class_trig )
+ );
+
+ //////////////////////////////////////////////////////
+ // Escalation Handling of Classes
+ //////////////////////////////////////////////////////
+
+ logic [alert_pkg::N_CLASSES-1:0] class_accum_trig;
+ logic [alert_pkg::N_CLASSES-1:0][alert_pkg::N_ESC_SEV-1:0] class_esc_sig_en;
+
+ for (genvar k = 0; k < alert_pkg::N_CLASSES; k++) begin : gen_classes
+ alert_handler_accu i_accu (
+ .clk_i,
+ .rst_ni,
+ .clr_i ( reg2hw_wrap.class_clr[k] ),
+ .trig_i ( hw2reg_wrap.class_trig[k] ),
+ .thresh_i ( reg2hw_wrap.class_accum_thresh[k] ),
+ .cnt_o ( hw2reg_wrap.class_accum_cnt[k] ),
+ .trig_o ( class_accum_trig[k] )
+ );
+
+ alert_handler_esc_timer i_esc_timer (
+ .clk_i,
+ .rst_ni,
+ .en_i ( reg2hw_wrap.class_en[k] ),
+ // this clear does not apply to interrupts
+ .clr_i ( reg2hw_wrap.class_clr[k] ),
+ // an interrupt enables the timeout
+ .timeout_en_i ( irq_o[k] ),
+ .accum_trig_i ( class_accum_trig[k] ),
+ .timeout_cyc_i ( reg2hw_wrap.class_timeout_cyc[k] ),
+ .esc_en_i ( reg2hw_wrap.class_esc_en[k] ),
+ .esc_map_i ( reg2hw_wrap.class_esc_map[k] ),
+ .phase_cyc_i ( reg2hw_wrap.class_phase_cyc[k] ),
+ .esc_trig_o ( hw2reg_wrap.class_esc_trig[k] ),
+ .esc_cnt_o ( hw2reg_wrap.class_esc_cnt[k] ),
+ .esc_state_o ( hw2reg_wrap.class_esc_state[k] ),
+ .esc_sig_en_o ( class_esc_sig_en[k] )
+ );
+ end
+
+ //////////////////////////////////////////////////////
+ // Escalation Senders
+ //////////////////////////////////////////////////////
+
+ logic [alert_pkg::N_ESC_SEV-1:0] esc_sig_en;
+ logic [alert_pkg::N_ESC_SEV-1:0] esc_integfail;
+ logic [alert_pkg::N_ESC_SEV-1:0][alert_pkg::N_CLASSES-1:0] esc_sig_en_trsp;
+
+ for (genvar k = 0; k < alert_pkg::N_ESC_SEV; k++) begin : gen_esc_sev
+ for (genvar j = 0; j < alert_pkg::N_CLASSES; j++) begin : gen_transp
+ assign esc_sig_en_trsp[k][j] = class_esc_sig_en[j][k];
+ end
+
+ assign esc_sig_en[k] = |esc_sig_en_trsp[k];
+
+ prim_esc_sender i_esc_sender (
+ .clk_i,
+ .rst_ni,
+ .ping_en_i ( esc_ping_en[k] ),
+ .ping_ok_o ( esc_ping_ok[k] ),
+ .integ_fail_o ( esc_integfail[k] ),
+ .esc_en_i ( esc_sig_en[k] ),
+ .resp_pi ( resp_pi[k] ),
+ .resp_ni ( resp_ni[k] ),
+ .esc_po ( esc_po[k] ),
+ .esc_no ( esc_no[k] )
+ );
+ end
+
+ assign loc_alert_trig[3] = |esc_integfail;
+
+ //////////////////////////////////////////////////////
+ // Assertions
+ //////////////////////////////////////////////////////
+
+ // this restriction is due to specifics in the ping selection mechanism
+ `ASSERT_INIT(CheckNAlerts,
+ alert_pkg::NAlerts < (256 - alert_pkg::N_CLASSES))
+ `ASSERT_INIT(CheckEscCntDw, alert_pkg::EscCntDw <= 32)
+ `ASSERT_INIT(CheckAccuCntDw, alert_pkg::AccuCntDw <= 32)
+ `ASSERT_INIT(CheckNClasses, alert_pkg::N_CLASSES <= 8)
+ `ASSERT_INIT(CheckNEscSev, alert_pkg::N_ESC_SEV <= 8)
+
+endmodule
+
diff --git a/hw/ip/alert_handler/rtl/alert_handler_accu.sv b/hw/ip/alert_handler/rtl/alert_handler_accu.sv
new file mode 100644
index 0000000..74671f4
--- /dev/null
+++ b/hw/ip/alert_handler/rtl/alert_handler_accu.sv
@@ -0,0 +1,41 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// This module accumulates incoming alert triggers. Once the current accumulator
+// value is greater or equal the accumulator threshold, the next occurence of
+// trig_i will trigger escalation.
+//
+// Note that the accumulator is implemented using a saturation counter which
+// does not wrap around.
+//
+
+module alert_handler_accu (
+ input clk_i,
+ input rst_ni,
+ input clr_i, // clear the accumulator
+ input trig_i, // increments the accu
+ input [alert_pkg::AccuCntDw-1:0] thresh_i, // escalation trigger threshold
+ output logic [alert_pkg::AccuCntDw-1:0] cnt_o, // output of current accu value
+ output logic trig_o // escalation trigger output
+);
+
+ logic [alert_pkg::AccuCntDw-1:0] accu_d, accu_q;
+
+ assign accu_d = (clr_i) ? '0 : // clear
+ (trig_i && !(&accu_q)) ? accu_q + 1'b1 : // saturate counter at maximum
+ accu_q;
+
+ assign trig_o = (accu_q >= thresh_i) & trig_i;
+
+ assign cnt_o = accu_q;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ accu_q <= '0;
+ end else begin
+ accu_q <= accu_d;
+ end
+ end
+
+endmodule : alert_handler_accu
diff --git a/hw/ip/alert_handler/rtl/alert_handler_class.sv b/hw/ip/alert_handler/rtl/alert_handler_class.sv
new file mode 100644
index 0000000..28020aa
--- /dev/null
+++ b/hw/ip/alert_handler/rtl/alert_handler_class.sv
@@ -0,0 +1,56 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// This module gates the alert triggers with their enable bits, and correctly bins
+// the enabled alerts into the class that they have been assigned to. The module
+// produces the alert cause and class trigger signals.
+//
+
+module alert_handler_class (
+ input [alert_pkg::NAlerts-1:0] alert_trig_i, // alert trigger
+ input [alert_pkg::N_LOC_ALERT-1:0] loc_alert_trig_i, // alert trigger
+ input [alert_pkg::NAlerts-1:0] alert_en_i, // alert enable
+ input [alert_pkg::N_LOC_ALERT-1:0] loc_alert_en_i, // alert enable
+ input [alert_pkg::NAlerts-1:0] [alert_pkg::CLASS_DW-1:0] alert_class_i, // class assignment
+ input [alert_pkg::N_LOC_ALERT-1:0][alert_pkg::CLASS_DW-1:0] loc_alert_class_i, // class assignment
+ input [alert_pkg::N_CLASSES-1:0] class_en_i, // class enables
+
+ output logic [alert_pkg::NAlerts-1:0] alert_cause_o, // alert cause
+ output logic [alert_pkg::N_LOC_ALERT-1:0] loc_alert_cause_o, // alert cause
+ output logic [alert_pkg::N_CLASSES-1:0] class_trig_o // class triggered
+);
+
+ // assign alert cause
+ assign alert_cause_o = alert_en_i & alert_trig_i;
+ assign loc_alert_cause_o = loc_alert_en_i & loc_alert_trig_i;
+
+ // classification mapping
+ logic [alert_pkg::N_CLASSES-1:0][alert_pkg::NAlerts-1:0] class_masks;
+ logic [alert_pkg::N_CLASSES-1:0][alert_pkg::N_LOC_ALERT-1:0] loc_class_masks;
+
+ // this is basically an address to onehot0 decoder
+ always_comb begin : p_class_mask
+ class_masks = '0;
+ loc_class_masks = '0;
+ for (int unsigned kk = 0; kk < alert_pkg::NAlerts; kk++) begin
+ class_masks[alert_class_i[kk]][kk] = 1'b1;
+ end
+ for (int unsigned kk = 0; kk < alert_pkg::N_LOC_ALERT; kk++) begin
+ loc_class_masks[loc_alert_class_i[kk]][kk] = 1'b1;
+ end
+ end
+
+ // mask and OR reduction, followed by class enable gating
+ for (genvar k = 0; k < alert_pkg::N_CLASSES; k++) begin : gen_classifier
+ assign class_trig_o[k] = class_en_i[k] &
+ (|{ alert_cause_o & class_masks[k],
+ loc_alert_cause_o & loc_class_masks[k] });
+ end
+
+endmodule : alert_handler_class
+
+
+
+
+
diff --git a/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv b/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv
new file mode 100644
index 0000000..229ff84
--- /dev/null
+++ b/hw/ip/alert_handler/rtl/alert_handler_esc_timer.sv
@@ -0,0 +1,241 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// This module implements the escalation timer, which times the four escalation
+// phases. There are two mechanisms that can trigger the escalation protocol:
+//
+// 1) via accum_trigger_i, which will be asserted once the accumulator value
+// exceeds a programmable amount of alert occurences.
+//
+// 2) via an interrupt timeout, if this is enabled. If this functionality is
+// enabled, the internal escalation counter is reused to check whether the
+// interrupt times out. If it does time out, the outcome is the same as if
+// accum_trigger_i where asserted.
+//
+// Note that escalation always takes precedence over the interrupt timeout.
+//
+
+module alert_handler_esc_timer (
+ input clk_i,
+ input rst_ni,
+ input en_i, // enables timeout/escalation
+ input clr_i, // aborts escalation
+ input accum_trig_i, // this will trigger escalation
+ input timeout_en_i, // enables timeout
+ input [alert_pkg::EscCntDw-1:0] timeout_cyc_i, // interrupt timeout. 0 = disabled
+ input [alert_pkg::N_ESC_SEV-1:0] esc_en_i, // escalation signal enables a
+ input [alert_pkg::N_ESC_SEV-1:0]
+ [alert_pkg::PHASE_DW-1:0] esc_map_i, // escalation signal / phase map
+ input [alert_pkg::N_PHASES-1:0]
+ [alert_pkg::EscCntDw-1:0] phase_cyc_i, // cycle counts of individual phases
+ output logic esc_trig_o, // asserted if escalation triggers
+ output logic [alert_pkg::EscCntDw-1:0] esc_cnt_o, // current timeout / escalation count
+ output logic [alert_pkg::N_ESC_SEV-1:0] esc_sig_en_o, // escalation signal outputs
+ // current state output
+ // 000: idle, 001: irq timeout counting 100: phase0, 101: phase1, 110: phase2, 111: phase3
+ output alert_pkg::cstate_e esc_state_o
+);
+
+ //////////////////////////////////////////////////////
+ // Counter
+ //////////////////////////////////////////////////////
+
+ alert_pkg::cstate_e state_d, state_q;
+
+ logic cnt_en, cnt_clr, cnt_ge;
+ logic [alert_pkg::EscCntDw-1:0] cnt_d, cnt_q;
+
+ // escalation counter, used for all phases and the timeout
+ assign cnt_d = (cnt_en && cnt_clr) ? alert_pkg::EscCntDw'(1'b1) :
+ (cnt_clr) ? '0 :
+ (cnt_en) ? cnt_q + 1'b1 :
+ cnt_q;
+
+ // current state output
+ assign esc_state_o = state_q;
+ assign esc_cnt_o = cnt_q;
+
+ // threshold test, mux the thresholds depending on the current state
+ logic [alert_pkg::EscCntDw-1:0] thresh;
+ logic esc_is_on;
+ logic [1:0] phase_idx;
+ assign esc_is_on = state_q[2];
+ assign phase_idx = state_q[1:0];
+ assign thresh = (esc_is_on) ? phase_cyc_i[phase_idx] : timeout_cyc_i;
+ assign cnt_ge = (cnt_q >= thresh);
+
+ //////////////////////////////////////////////////////
+ // Main FSM
+ //////////////////////////////////////////////////////
+
+ always_comb begin : p_fsm
+ // default
+ state_d = state_q;
+ cnt_en = 1'b0;
+ cnt_clr = 1'b0;
+ esc_trig_o = 1'b0;
+
+ unique case (state_q)
+ ////////////////////////////////////
+ // wait for an escalation trigger or an alert trigger
+ // the latter will trigger an interrupt timeout
+ alert_pkg::Idle: begin
+ if (accum_trig_i && en_i) begin
+ state_d = alert_pkg::Phase0;
+ cnt_en = 1'b1;
+ esc_trig_o = 1'b1;
+ // the counter is zero in this state. so if the
+ // timeout count is zero (==disabled), cnt_ge will be true.
+ end else if (timeout_en_i && !cnt_ge && en_i) begin
+ cnt_en = 1'b1;
+ state_d = alert_pkg::Timeout;
+ end else begin
+ cnt_clr = 1'b1;
+ end
+ end
+ ////////////////////////////////////
+ // we are in interrupt timeout state
+ // in case an escalation comes in, we immediately have to
+ // switch over to the first escalation phase.
+ // in case the interrupt timeout hits it's cycle count, we
+ // also enter escalation phase0.
+ // ongoing timeouts can always be cleared.
+ alert_pkg::Timeout: begin
+ if (accum_trig_i || (cnt_ge && timeout_en_i)) begin
+ state_d = alert_pkg::Phase0;
+ cnt_en = 1'b1;
+ cnt_clr = 1'b1;
+ esc_trig_o = 1'b1;
+ // the timeout enable is connected to the irq state
+ // if that is cleared, stop the timeout counter
+ end else if (timeout_en_i) begin
+ cnt_en = 1'b1;
+ end else begin
+ state_d = alert_pkg::Idle;
+ cnt_clr = 1'b1;
+ end
+ end
+ ////////////////////////////////////
+ // note: autolocking the clear signal is done in the regfile
+ alert_pkg::Phase0: begin
+ if (clr_i) begin
+ state_d = alert_pkg::Idle;
+ cnt_clr = 1'b1;
+ end else if (cnt_ge) begin
+ state_d = alert_pkg::Phase1;
+ cnt_clr = 1'b1;
+ cnt_en = 1'b1;
+ end else begin
+ cnt_en = 1'b1;
+ end
+ end
+ ////////////////////////////////////
+ alert_pkg::Phase1: begin
+ if (clr_i) begin
+ state_d = alert_pkg::Idle;
+ cnt_clr = 1'b1;
+ end else if (cnt_ge) begin
+ state_d = alert_pkg::Phase2;
+ cnt_clr = 1'b1;
+ cnt_en = 1'b1;
+ end else begin
+ cnt_en = 1'b1;
+ end
+ end
+ ////////////////////////////////////
+ alert_pkg::Phase2: begin
+ if (clr_i) begin
+ state_d = alert_pkg::Idle;
+ cnt_clr = 1'b1;
+ end else if (cnt_ge) begin
+ state_d = alert_pkg::Phase3;
+ cnt_clr = 1'b1;
+ cnt_en = 1'b1;
+ end else begin
+ cnt_en = 1'b1;
+ end
+ end
+ ////////////////////////////////////
+ alert_pkg::Phase3: begin
+ if (clr_i) begin
+ state_d = alert_pkg::Idle;
+ cnt_clr = 1'b1;
+ end else if (cnt_ge) begin
+ state_d = alert_pkg::Terminal;
+ cnt_clr = 1'b1;
+ end else begin
+ cnt_en = 1'b1;
+ end
+ end
+ ////////////////////////////////////
+ // final, terminal state after escalation.
+ // if clr is locked down, only a system reset
+ // will get us out of this state
+ alert_pkg::Terminal: begin
+ if (clr_i) begin
+ state_d = alert_pkg::Idle;
+ end
+ end
+ ////////////////////////////////////
+ default : state_d = alert_pkg::Idle;
+ endcase
+ end
+
+ for (genvar k = 0; k < alert_pkg::N_ESC_SEV; k++) begin : gen_esc_en
+ // check whether we are in the corresponding phase and whether
+ // the signal is enabled
+ assign esc_sig_en_o[k] = esc_en_i[k] &
+ (alert_pkg::cstate_e'({1'b1, esc_map_i[k]}) == state_q);
+ end
+
+ //////////////////////////////////////////////////////
+ // Regs
+ //////////////////////////////////////////////////////
+
+ // switch interrupt / escalation mode
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ state_q <= alert_pkg::Idle;
+ cnt_q <= '0;
+ end else begin
+ state_q <= state_d;
+ cnt_q <= cnt_d;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // Assertions
+ //////////////////////////////////////////////////////
+
+ // a clear should always bring us back to idle
+ `ASSERT(CheckClr, clr_i |=> state_q == alert_pkg::Idle, clk_i, !rst_ni)
+ // if currently in idle and not enabled, must remain here
+ `ASSERT(CheckEn, state_q == alert_pkg::Idle && !en_i |=>
+ state_q == alert_pkg::Idle, clk_i, !rst_ni)
+ // Check if accumulation trigger correctly captured
+ `ASSERT(CheckAccumTrig0, accum_trig_i && state_q == alert_pkg::Idle && !en_i |=>
+ state_q == alert_pkg::Phase0, clk_i, !rst_ni)
+ `ASSERT(CheckAccumTrig1, accum_trig_i && state_q == alert_pkg::Timeout |=>
+ state_q == alert_pkg::Phase0, clk_i, !rst_ni)
+ // Check if timeout correctly captured
+ `ASSERT(CheckTimeout0, !accum_trig_i && state_q == alert_pkg::Idle && timeout_en_i |=>
+ state_q == alert_pkg::Timeout, clk_i, !rst_ni)
+ `ASSERT(CheckTimeout1, !accum_trig_i && state_q == alert_pkg::Timeout && timeout_en_i |=>
+ state_q == alert_pkg::Timeout, clk_i, !rst_ni)
+ `ASSERT(CheckTimeout2, !accum_trig_i && state_q == alert_pkg::Timeout && !timeout_en_i |=>
+ state_q == alert_pkg::Idle, clk_i, !rst_ni)
+ // Check if timeout correctly triggers escalation
+ `ASSERT(CheckTimeoutTrig, state_q == alert_pkg::Timeout && timeout_en_i &&
+ cnt_q == timeout_cyc_i |=> state_q == alert_pkg::Phase0, clk_i, !rst_ni)
+ // Check whether escalation phases are correctly switched
+ `ASSERT(CheckPhase0, state_q == alert_pkg::Phase0 && !clr_i && cnt_q == phase_cyc_i[0] |=>
+ state_q == alert_pkg::Phase1, clk_i, !rst_ni)
+ `ASSERT(CheckPhase1, state_q == alert_pkg::Phase1 && !clr_i && cnt_q == phase_cyc_i[1] |=>
+ state_q == alert_pkg::Phase2, clk_i, !rst_ni)
+ `ASSERT(CheckPhase2, state_q == alert_pkg::Phase2 && !clr_i && cnt_q == phase_cyc_i[2] |=>
+ state_q == alert_pkg::Phase3, clk_i, !rst_ni)
+ `ASSERT(CheckPhase3, state_q == alert_pkg::Phase3 && !clr_i && cnt_q == phase_cyc_i[3] |=>
+ state_q == alert_pkg::Terminal, clk_i, !rst_ni)
+
+endmodule : alert_handler_esc_timer
diff --git a/hw/ip/alert_handler/rtl/alert_handler_ping_timer.sv b/hw/ip/alert_handler/rtl/alert_handler_ping_timer.sv
new file mode 100644
index 0000000..1196089
--- /dev/null
+++ b/hw/ip/alert_handler/rtl/alert_handler_ping_timer.sv
@@ -0,0 +1,222 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// This module implements the ping mechanism. Once enabled, this module uses an
+// LFSR-based PRNG to
+//
+// a) determine the next peripheral index to be pinged (can be an alert receiver or an
+// escalation sender). it it is detected that this particular peripheral is disabled,
+// another index will be drawn from the PRNG.
+//
+// b) determine the amount of pause cycles to wait before pinging the peripheral selected in a).
+//
+// Once the ping timer waited for the amount of pause cycles determined in b), it asserts
+// the ping enable signal of the peripheral determined in a). If that peripheral does
+// not respond within the ping timeout window, an internal alert will be raised.
+//
+// Further, if a spurious ping_ok signal is detected (i.e., a ping ok that has not been
+// requested), the ping timer will also raise an internal alert.
+//
+
+module alert_handler_ping_timer (
+ input clk_i,
+ input rst_ni,
+ input entropy_i, // from TRNG
+ input en_i, // enable ping testing
+ input [alert_pkg::NAlerts-1:0] alert_en_i, // determines which alerts to ping
+ input [alert_pkg::PING_CNT_DW-1:0] ping_timeout_cyc_i, // timeout in cycles
+ output logic [alert_pkg::NAlerts-1:0] alert_ping_en_o, // enable to alert receivers
+ output logic [alert_pkg::N_ESC_SEV-1:0] esc_ping_en_o, // enable to esc senders
+ input [alert_pkg::NAlerts-1:0] alert_ping_ok_i, // response from alert receivers
+ input [alert_pkg::N_ESC_SEV-1:0] esc_ping_ok_i, // response from esc senders
+ output logic alert_ping_fail_o, // any of the alert receivers failed
+ output logic esc_ping_fail_o // any of the esc senders failed
+);
+
+ localparam int unsigned NModsToPing = alert_pkg::NAlerts + alert_pkg::N_ESC_SEV;
+ localparam int unsigned IdDw = $clog2(NModsToPing);
+
+ // this defines a random permutation
+ localparam int unsigned perm [0:31] = '{ 4, 11, 25, 3,
+ 15, 16, 1, 10,
+ 2, 22, 7, 0,
+ 23, 28, 30, 19,
+ 27, 12, 24, 26,
+ 14, 21, 18, 5,
+ 13, 8, 29, 31,
+ 20, 6, 9, 17};
+
+ //////////////////////////////////////////////////////
+ // PRNG
+ //////////////////////////////////////////////////////
+
+ logic lfsr_en;
+ logic [31:0] lfsr_state, perm_state;
+
+ prim_lfsr #(
+ .LfsrDw ( 32 ),
+ .InDw ( 1 ),
+ .OutDw ( 32 ),
+ .Seed ( alert_pkg::LfsrSeed )
+ ) i_prim_lfsr (
+ .clk_i,
+ .rst_ni,
+ .en_i ( lfsr_en ),
+ .data_i ( entropy_i ),
+ .data_o ( lfsr_state )
+ );
+
+ for (genvar k = 0; k < 32; k++) begin : gen_perm
+ assign perm_state[k] = lfsr_state[perm[k]];
+ end
+
+ logic [IdDw-1:0] id_to_ping;
+ logic [alert_pkg::PING_CNT_DW-1:0] wait_cyc;
+ // we only use bits up to 23, as IdDw is 8bit maximum
+ assign id_to_ping = perm_state[16 +: IdDw];
+ // concatenate with constant offset, introduce some stagger
+ // by concatenating the lower bits below
+ assign wait_cyc = {perm_state[15:2], 8'h01, perm_state[1:0]};
+
+ logic [2**IdDw-1:0] enable_mask;
+ always_comb begin : p_enable_mask
+ enable_mask = '0; // tie off unused
+ enable_mask[alert_pkg::NAlerts-1:0] = alert_en_i; // alerts
+ enable_mask[NModsToPing-1:alert_pkg::NAlerts] = '1; // escalation senders
+ end
+
+ logic id_vld;
+ // check if the randomly drawn ID is actually valid and the alert is enabled
+ assign id_vld = enable_mask[id_to_ping];
+
+ //////////////////////////////////////////////////////
+ // Counter
+ //////////////////////////////////////////////////////
+
+ logic [alert_pkg::PING_CNT_DW-1:0] cnt_d, cnt_q;
+ logic cnt_en, cnt_clr;
+ logic wait_ge, timeout_ge;
+
+ assign cnt_d = (cnt_clr) ? '0 :
+ (cnt_en) ? cnt_q + 1'b1 :
+ cnt_q;
+
+ assign wait_ge = (cnt_q >= wait_cyc);
+ assign timeout_ge = (cnt_q >= ping_timeout_cyc_i);
+
+ //////////////////////////////////////////////////////
+ // Ping and Timeout Logic
+ //////////////////////////////////////////////////////
+
+ typedef enum logic [1:0] {Init, RespWait, DoPing} state_e;
+ state_e state_d, state_q;
+ logic ping_en, ping_ok;
+ logic [NModsToPing-1:0] ping_sel;
+ logic [NModsToPing-1:0] spurious_ping;
+ logic spurious_alert_ping, spurious_esc_ping;
+
+ // generate ping enable vector
+ assign ping_sel = (alert_pkg::NAlerts+alert_pkg::N_ESC_SEV)'(ping_en) << id_to_ping;
+ assign alert_ping_en_o = ping_sel[alert_pkg::NAlerts-1:0];
+ assign esc_ping_en_o = ping_sel[NModsToPing-1:alert_pkg::NAlerts];
+
+ // mask out response
+ assign ping_ok = |({esc_ping_ok_i, alert_ping_ok_i} & ping_sel);
+ assign spurious_ping = ({esc_ping_ok_i, alert_ping_ok_i} & ~ping_sel);
+ // under normal operation, these signals should never be asserted.
+ // double check that these signals are not optimized away during synthesis.
+ // this may need "don't touch" or "no boundary optimization" constraints
+ assign spurious_alert_ping = |spurious_ping[alert_pkg::NAlerts-1:0];
+ assign spurious_esc_ping = |spurious_ping[NModsToPing-1:alert_pkg::NAlerts];
+
+ always_comb begin : p_fsm
+ // default
+ state_d = state_q;
+ cnt_en = 1'b0;
+ cnt_clr = 1'b0;
+ lfsr_en = 1'b0;
+ ping_en = 1'b0;
+ // this captures spurious
+ alert_ping_fail_o = spurious_alert_ping;
+ esc_ping_fail_o = spurious_esc_ping;
+
+ unique case (state_q)
+ ///////////////////////////
+ // wait until activiated
+ // we never return to this state
+ // once activated!
+ Init: begin
+ cnt_clr = 1'b0;
+ if (en_i) begin
+ state_d = RespWait;
+ end
+ end
+ ///////////////////////////
+ // wait for random amount of cycles
+ // draw another ID/wait count if the
+ // peripheral ID is not valid
+ RespWait: begin
+ if (!id_vld) begin
+ lfsr_en = 1'b1;
+ cnt_clr = 1'b1;
+ end else if (wait_ge) begin
+ state_d = DoPing;
+ lfsr_en = 1'b1;
+ cnt_clr = 1'b1;
+ end else begin
+ cnt_en = 1'b1;
+ end
+ end
+ ///////////////////////////
+ // send out ping request and wait for a ping
+ // response or a ping timeout (whatever comes first)
+ DoPing: begin
+ cnt_en = 1'b1;
+ ping_en = 1'b1;
+ if (timeout_ge || ping_ok) begin
+ state_d = RespWait;
+ cnt_clr = 1'b1;
+ if (timeout_ge) begin
+ if (id_to_ping < alert_pkg::NAlerts) begin
+ alert_ping_fail_o = 1'b1;
+ end else begin
+ esc_ping_fail_o = 1'b1;
+ end
+ end
+ end
+ end
+ ///////////////////////////
+ default : state_d = Init;
+ endcase
+
+ end
+
+ //////////////////////////////////////////////////////
+ // Flops
+ //////////////////////////////////////////////////////
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ state_q <= Init;
+ cnt_q <= '0;
+ end else begin
+ state_q <= state_d;
+ cnt_q <= cnt_d;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // Assertions
+ //////////////////////////////////////////////////////
+
+ // internals
+ `ASSERT(PingOH0, $onehot0(ping_sel), clk_i, !rst_ni)
+ // we should never get into the ping state without knowing
+ // which module to ping
+ `ASSERT(PingOH, ping_en |-> $onehot(ping_sel), clk_i, !rst_ni)
+
+ // TODO: add some cover metrics to check whether all devices
+ // are pinged eventually.
+
+endmodule : alert_handler_ping_timer