blob: cdef6495b01c9a311a253d7d444a55b4b5be6825 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Breakout / remapping wrapper for register file.
module alert_handler_reg_wrap import alert_pkg::*; (
input clk_i,
input rst_ni,
input rst_shadowed_ni,
// Bus Interface (device)
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// interrupt
output logic [N_CLASSES-1:0] irq_o,
// State information for HW crashdump
input [N_CLASSES-1:0] latch_crashdump_i,
output alert_crashdump_t crashdump_o,
// hw2reg
input hw2reg_wrap_t hw2reg_wrap,
// reg2hw
output reg2hw_wrap_t reg2hw_wrap,
// bus integrity alert
output logic fatal_integ_alert_o
);
//////////////////
// reg instance //
//////////////////
logic [N_CLASSES-1:0] class_autolock_en;
alert_handler_reg_pkg::alert_handler_reg2hw_t reg2hw;
alert_handler_reg_pkg::alert_handler_hw2reg_t hw2reg;
alert_handler_reg_top u_reg (
.clk_i,
.rst_ni,
.rst_shadowed_ni,
.tl_i,
.tl_o,
.reg2hw,
.hw2reg,
.shadowed_storage_err_o(reg2hw_wrap.shadowed_err_storage),
.shadowed_update_err_o(reg2hw_wrap.shadowed_err_update),
.intg_err_o(fatal_integ_alert_o),
.devmode_i(1'b1)
);
////////////////
// interrupts //
////////////////
prim_intr_hw #(
.Width(1)
) u_irq_classa (
.clk_i,
.rst_ni,
.event_intr_i ( hw2reg_wrap.class_trig[0] ),
.reg2hw_intr_enable_q_i ( reg2hw.intr_enable.classa.q ),
.reg2hw_intr_test_q_i ( reg2hw.intr_test.classa.q ),
.reg2hw_intr_test_qe_i ( reg2hw.intr_test.classa.qe ),
.reg2hw_intr_state_q_i ( reg2hw.intr_state.classa.q ),
.hw2reg_intr_state_de_o ( hw2reg.intr_state.classa.de ),
.hw2reg_intr_state_d_o ( hw2reg.intr_state.classa.d ),
.intr_o ( irq_o[0] )
);
prim_intr_hw #(
.Width(1)
) u_irq_classb (
.clk_i,
.rst_ni,
.event_intr_i ( hw2reg_wrap.class_trig[1] ),
.reg2hw_intr_enable_q_i ( reg2hw.intr_enable.classb.q ),
.reg2hw_intr_test_q_i ( reg2hw.intr_test.classb.q ),
.reg2hw_intr_test_qe_i ( reg2hw.intr_test.classb.qe ),
.reg2hw_intr_state_q_i ( reg2hw.intr_state.classb.q ),
.hw2reg_intr_state_de_o ( hw2reg.intr_state.classb.de ),
.hw2reg_intr_state_d_o ( hw2reg.intr_state.classb.d ),
.intr_o ( irq_o[1] )
);
prim_intr_hw #(
.Width(1)
) u_irq_classc (
.clk_i,
.rst_ni,
.event_intr_i ( hw2reg_wrap.class_trig[2] ),
.reg2hw_intr_enable_q_i ( reg2hw.intr_enable.classc.q ),
.reg2hw_intr_test_q_i ( reg2hw.intr_test.classc.q ),
.reg2hw_intr_test_qe_i ( reg2hw.intr_test.classc.qe ),
.reg2hw_intr_state_q_i ( reg2hw.intr_state.classc.q ),
.hw2reg_intr_state_de_o ( hw2reg.intr_state.classc.de ),
.hw2reg_intr_state_d_o ( hw2reg.intr_state.classc.d ),
.intr_o ( irq_o[2] )
);
prim_intr_hw #(
.Width(1)
) u_irq_classd (
.clk_i,
.rst_ni,
.event_intr_i ( hw2reg_wrap.class_trig[3] ),
.reg2hw_intr_enable_q_i ( reg2hw.intr_enable.classd.q ),
.reg2hw_intr_test_q_i ( reg2hw.intr_test.classd.q ),
.reg2hw_intr_test_qe_i ( reg2hw.intr_test.classd.qe ),
.reg2hw_intr_state_q_i ( reg2hw.intr_state.classd.q ),
.hw2reg_intr_state_de_o ( hw2reg.intr_state.classd.de ),
.hw2reg_intr_state_d_o ( hw2reg.intr_state.classd.d ),
.intr_o ( irq_o[3] )
);
/////////////////////
// hw2reg mappings //
/////////////////////
// if an alert is enabled and it fires,
// we have to set the corresponding cause bit
for (genvar k = 0; k < NAlerts; k++) begin : gen_alert_cause
assign hw2reg.alert_cause[k].d = 1'b1;
assign hw2reg.alert_cause[k].de = reg2hw.alert_cause[k].q |
hw2reg_wrap.alert_cause[k];
end
// if a local alert is enabled and it fires,
// we have to set the corresponding cause bit
for (genvar k = 0; k < N_LOC_ALERT; k++) begin : gen_loc_alert_cause
assign hw2reg.loc_alert_cause[k].d = 1'b1;
assign hw2reg.loc_alert_cause[k].de = reg2hw.loc_alert_cause[k].q |
hw2reg_wrap.loc_alert_cause[k];
end
// ping timeout in cycles
// autolock can clear these regs automatically upon entering escalation
// note: the class must be activated for this to occur
assign { hw2reg.classd_clr_regwen.d,
hw2reg.classc_clr_regwen.d,
hw2reg.classb_clr_regwen.d,
hw2reg.classa_clr_regwen.d } = '0;
assign { hw2reg.classd_clr_regwen.de,
hw2reg.classc_clr_regwen.de,
hw2reg.classb_clr_regwen.de,
hw2reg.classa_clr_regwen.de } = hw2reg_wrap.class_esc_trig &
class_autolock_en &
reg2hw_wrap.class_en;
// current accumulator counts
assign { hw2reg.classd_accum_cnt.d,
hw2reg.classc_accum_cnt.d,
hw2reg.classb_accum_cnt.d,
hw2reg.classa_accum_cnt.d } = hw2reg_wrap.class_accum_cnt;
// current accumulator counts
assign { hw2reg.classd_esc_cnt.d,
hw2reg.classc_esc_cnt.d,
hw2reg.classb_esc_cnt.d,
hw2reg.classa_esc_cnt.d } = hw2reg_wrap.class_esc_cnt;
// current accumulator counts
assign { hw2reg.classd_state.d,
hw2reg.classc_state.d,
hw2reg.classb_state.d,
hw2reg.classa_state.d } = hw2reg_wrap.class_esc_state;
/////////////////////
// reg2hw mappings //
/////////////////////
// config register lock
assign reg2hw_wrap.ping_enable = reg2hw.ping_timer_en_shadowed.q;
// alert enable and class assignments
for (genvar k = 0; k < NAlerts; k++) begin : gen_alert_en_class
// we only ping enabled alerts that are locked
assign reg2hw_wrap.alert_ping_en[k] = reg2hw.alert_en_shadowed[k].q &
~reg2hw.alert_regwen[k].q;
assign reg2hw_wrap.alert_en[k] = reg2hw.alert_en_shadowed[k].q;
assign reg2hw_wrap.alert_class[k] = reg2hw.alert_class_shadowed[k].q;
end
// local alert enable and class assignments
for (genvar k = 0; k < N_LOC_ALERT; k++) begin : gen_loc_alert_en_class
assign reg2hw_wrap.loc_alert_en[k] = reg2hw.loc_alert_en_shadowed[k].q;
assign reg2hw_wrap.loc_alert_class[k] = reg2hw.loc_alert_class_shadowed[k].q;
end
assign reg2hw_wrap.ping_timeout_cyc = reg2hw.ping_timeout_cyc_shadowed.q;
// class enable
// we require that at least one of the enable signals is
// set for a class to be enabled
assign reg2hw_wrap.class_en = {
reg2hw.classd_ctrl_shadowed.en.q & ( reg2hw.classd_ctrl_shadowed.en_e3.q |
reg2hw.classd_ctrl_shadowed.en_e2.q |
reg2hw.classd_ctrl_shadowed.en_e1.q |
reg2hw.classd_ctrl_shadowed.en_e0.q ),
//
reg2hw.classc_ctrl_shadowed.en.q & ( reg2hw.classc_ctrl_shadowed.en_e3.q |
reg2hw.classc_ctrl_shadowed.en_e2.q |
reg2hw.classc_ctrl_shadowed.en_e1.q |
reg2hw.classc_ctrl_shadowed.en_e0.q ),
//
reg2hw.classb_ctrl_shadowed.en.q & ( reg2hw.classb_ctrl_shadowed.en_e3.q |
reg2hw.classb_ctrl_shadowed.en_e2.q |
reg2hw.classb_ctrl_shadowed.en_e1.q |
reg2hw.classb_ctrl_shadowed.en_e0.q ),
//
reg2hw.classa_ctrl_shadowed.en.q & ( reg2hw.classa_ctrl_shadowed.en_e3.q |
reg2hw.classa_ctrl_shadowed.en_e2.q |
reg2hw.classa_ctrl_shadowed.en_e1.q |
reg2hw.classa_ctrl_shadowed.en_e0.q )
};
// autolock enable
assign class_autolock_en = { reg2hw.classd_ctrl_shadowed.lock.q,
reg2hw.classc_ctrl_shadowed.lock.q,
reg2hw.classb_ctrl_shadowed.lock.q,
reg2hw.classa_ctrl_shadowed.lock.q };
// escalation signal enable
assign reg2hw_wrap.class_esc_en = { reg2hw.classd_ctrl_shadowed.en_e3.q,
reg2hw.classd_ctrl_shadowed.en_e2.q,
reg2hw.classd_ctrl_shadowed.en_e1.q,
reg2hw.classd_ctrl_shadowed.en_e0.q,
//
reg2hw.classc_ctrl_shadowed.en_e3.q,
reg2hw.classc_ctrl_shadowed.en_e2.q,
reg2hw.classc_ctrl_shadowed.en_e1.q,
reg2hw.classc_ctrl_shadowed.en_e0.q,
//
reg2hw.classb_ctrl_shadowed.en_e3.q,
reg2hw.classb_ctrl_shadowed.en_e2.q,
reg2hw.classb_ctrl_shadowed.en_e1.q,
reg2hw.classb_ctrl_shadowed.en_e0.q,
//
reg2hw.classa_ctrl_shadowed.en_e3.q,
reg2hw.classa_ctrl_shadowed.en_e2.q,
reg2hw.classa_ctrl_shadowed.en_e1.q,
reg2hw.classa_ctrl_shadowed.en_e0.q };
// escalation phase to escalation signal mapping
assign reg2hw_wrap.class_esc_map = { reg2hw.classd_ctrl_shadowed.map_e3.q,
reg2hw.classd_ctrl_shadowed.map_e2.q,
reg2hw.classd_ctrl_shadowed.map_e1.q,
reg2hw.classd_ctrl_shadowed.map_e0.q,
//
reg2hw.classc_ctrl_shadowed.map_e3.q,
reg2hw.classc_ctrl_shadowed.map_e2.q,
reg2hw.classc_ctrl_shadowed.map_e1.q,
reg2hw.classc_ctrl_shadowed.map_e0.q,
//
reg2hw.classb_ctrl_shadowed.map_e3.q,
reg2hw.classb_ctrl_shadowed.map_e2.q,
reg2hw.classb_ctrl_shadowed.map_e1.q,
reg2hw.classb_ctrl_shadowed.map_e0.q,
//
reg2hw.classa_ctrl_shadowed.map_e3.q,
reg2hw.classa_ctrl_shadowed.map_e2.q,
reg2hw.classa_ctrl_shadowed.map_e1.q,
reg2hw.classa_ctrl_shadowed.map_e0.q };
// Determines in which phase to latch the crashdump.
assign reg2hw_wrap.class_crashdump_phase = { reg2hw.classd_crashdump_trigger_shadowed.q,
reg2hw.classc_crashdump_trigger_shadowed.q,
reg2hw.classb_crashdump_trigger_shadowed.q,
reg2hw.classa_crashdump_trigger_shadowed.q };
// writing 1b1 to a class clr register clears the accumulator and
// escalation state if autolock is not asserted
assign reg2hw_wrap.class_clr = { reg2hw.classd_clr_shadowed.q & reg2hw.classd_clr_shadowed.qe,
reg2hw.classc_clr_shadowed.q & reg2hw.classc_clr_shadowed.qe,
reg2hw.classb_clr_shadowed.q & reg2hw.classb_clr_shadowed.qe,
reg2hw.classa_clr_shadowed.q & reg2hw.classa_clr_shadowed.qe };
// accumulator thresholds
assign reg2hw_wrap.class_accum_thresh = { reg2hw.classd_accum_thresh_shadowed.q,
reg2hw.classc_accum_thresh_shadowed.q,
reg2hw.classb_accum_thresh_shadowed.q,
reg2hw.classa_accum_thresh_shadowed.q };
// interrupt timeout lengths
assign reg2hw_wrap.class_timeout_cyc = { reg2hw.classd_timeout_cyc_shadowed.q,
reg2hw.classc_timeout_cyc_shadowed.q,
reg2hw.classb_timeout_cyc_shadowed.q,
reg2hw.classa_timeout_cyc_shadowed.q };
// escalation phase lengths
assign reg2hw_wrap.class_phase_cyc = { reg2hw.classd_phase3_cyc_shadowed.q,
reg2hw.classd_phase2_cyc_shadowed.q,
reg2hw.classd_phase1_cyc_shadowed.q,
reg2hw.classd_phase0_cyc_shadowed.q,
//
reg2hw.classc_phase3_cyc_shadowed.q,
reg2hw.classc_phase2_cyc_shadowed.q,
reg2hw.classc_phase1_cyc_shadowed.q,
reg2hw.classc_phase0_cyc_shadowed.q,
//
reg2hw.classb_phase3_cyc_shadowed.q,
reg2hw.classb_phase2_cyc_shadowed.q,
reg2hw.classb_phase1_cyc_shadowed.q,
reg2hw.classb_phase0_cyc_shadowed.q,
//
reg2hw.classa_phase3_cyc_shadowed.q,
reg2hw.classa_phase2_cyc_shadowed.q,
reg2hw.classa_phase1_cyc_shadowed.q,
reg2hw.classa_phase0_cyc_shadowed.q};
//////////////////////
// crashdump output //
//////////////////////
logic [N_CLASSES-1:0] crashdump_latched_q;
alert_crashdump_t crashdump_d, crashdump_q;
// alert cause output
for (genvar k = 0; k < NAlerts; k++) begin : gen_alert_cause_dump
assign crashdump_d.alert_cause[k] = reg2hw.alert_cause[k].q;
end
// local alert cause register output
for (genvar k = 0; k < N_LOC_ALERT; k++) begin : gen_loc_alert_cause_dump
assign crashdump_d.loc_alert_cause[k] = reg2hw.loc_alert_cause[k].q;
end
assign crashdump_d.class_accum_cnt = hw2reg_wrap.class_accum_cnt;
assign crashdump_d.class_esc_cnt = hw2reg_wrap.class_esc_cnt;
assign crashdump_d.class_esc_state = hw2reg_wrap.class_esc_state;
// We latch the crashdump upon triggering any of the escalation protocols. The reason for this is
// that during escalation, certain alert senders may start to trigger due to FSMs being moved
// into escalation mode - thereby masking the actual alert reasons exposed in the cause
// registers.
always_ff @(posedge clk_i or negedge rst_ni) begin : p_crashdump
if (!rst_ni) begin
crashdump_latched_q <= '0;
crashdump_q <= '0;
end else begin
// We track which class has been escalated so that the crashdump latching mechanism cannot be
// re-armed by clearing another class that has not escalated yet. This also implies that if
// an unclearable class has escalated, the crashdump latching mechanism cannot be re-armed.
crashdump_latched_q <= (crashdump_latched_q & ~reg2hw_wrap.class_clr) | latch_crashdump_i;
// The alert handler only captures the first escalation event that asserts a latch_crashdump_i
// signal, unless all classes are cleared, in which case the crashdump latching mechanism is
// re-armed. In other words, we latch the crashdump if any of the latch_crashdump_i bits is
// asserted, and no crashdump has been latched yet.
if (|latch_crashdump_i && !(|crashdump_latched_q)) begin
crashdump_q <= crashdump_d;
end
end
end
// As long as the crashdump has not been latched yet, we output the current alert handler state.
// Once any of the classes has triggered the latching, we switch to the latched snapshot.
assign crashdump_o = (|crashdump_latched_q) ? crashdump_q : crashdump_d;
endmodule : alert_handler_reg_wrap