[alert_handler/prim] Add differential sender/receiver primitives
diff --git a/hw/ip/prim/prim.core b/hw/ip/prim/prim.core
index f810c76..591dbf6 100644
--- a/hw/ip/prim/prim.core
+++ b/hw/ip/prim/prim.core
@@ -11,9 +11,14 @@
depend:
- lowrisc:prim:ram_2p # for prim_ram_2p_adv
- lowrisc:prim:assert
+ - lowrisc:prim:diff_decode # for prim_alert_sender/receiver
files:
- rtl/prim_clock_inverter.sv
+ - rtl/prim_alert_receiver.sv
+ - rtl/prim_alert_sender.sv
- rtl/prim_arbiter.sv
+ - rtl/prim_esc_receiver.sv
+ - rtl/prim_esc_sender.sv
- rtl/prim_sram_arbiter.sv
- rtl/prim_fifo_async.sv
- rtl/prim_fifo_sync.sv
diff --git a/hw/ip/prim/prim_diff_decode.core b/hw/ip/prim/prim_diff_decode.core
new file mode 100644
index 0000000..eda0fe7
--- /dev/null
+++ b/hw/ip/prim/prim_diff_decode.core
@@ -0,0 +1,19 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+name: "lowrisc:prim:diff_decode"
+description: "prim"
+filesets:
+ files_rtl:
+ depend:
+ - lowrisc:prim:assert
+ files:
+ - rtl/prim_diff_decode.sv
+ file_type: systemVerilogSource
+
+targets:
+ default:
+ filesets:
+ - files_rtl
diff --git a/hw/ip/prim/rtl/prim_alert_receiver.sv b/hw/ip/prim/rtl/prim_alert_receiver.sv
new file mode 100644
index 0000000..3093eb5
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_alert_receiver.sv
@@ -0,0 +1,206 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// The alert receiver primitive decodes alerts that have been differentially
+// encoded and transmitted via a handshake protocol on alert_pi/ni and
+// ack_po/no. In case an alert handshake is initiated, the output alert_o will
+// immediately be asserted (even before completion of the handshake).
+//
+// In case the differential input is not correctly encoded, this module will
+// raise an error by asserting integ_fail_o.
+//
+// Further, the module supports ping testing of the alert diff pair. In order to
+// initiate a ping test, ping_en_i shall be set to 1'b1 until ping_ok_o is
+// asserted for one cycle. The signal may be de-asserted (e.g. after a long)
+// timeout period. However note that all ping responses that come in after
+// deasserting ping_en_i will be treated as native alerts.
+//
+// The protocol works in both asynchronous and synchronous cases. In the
+// asynchronous case, the parameter AsyncOn must be set to 1'b1 in order to
+// instantiate additional synchronization logic. Further, it must be ensured
+// that the timing skew between all diff pairs is smaller than the shortest
+// clock period of the involved clocks.
+//
+// Note that in case of synchronous operation, alerts on the diffpair are
+// decoded combinationally and forwarded on alert_o within the same cycle.
+//
+// See also: prim_alert_sender, prim_diff_decode, alert_handler
+
+module prim_alert_receiver #(
+ // enables additional synchronization logic
+ parameter bit AsyncOn = 1'b0
+) (
+ input clk_i,
+ input rst_ni,
+ // this triggers a ping test. keep asserted
+ // until ping_ok_o is asserted.
+ input ping_en_i,
+ output logic ping_ok_o,
+ // asserted if signal integrity issue detected
+ output logic integ_fail_o,
+ // alert output (pulsed high) if a handshake is initiated
+ // on alert_p/n and no ping request is outstanding
+ output logic alert_o,
+ // ping output diff pair
+ output logic ping_po,
+ output logic ping_no,
+ // ack output diff pair
+ output logic ack_po,
+ output logic ack_no,
+ // alert input diff pair
+ input alert_pi,
+ input alert_ni
+);
+
+ //////////////////////////////////////////////////////
+ // decode differential signals
+ //////////////////////////////////////////////////////
+ logic alert_level, alert_sigint;
+
+ prim_diff_decode #(
+ .AsyncOn(AsyncOn)
+ ) i_decode_alert (
+ .clk_i,
+ .rst_ni,
+ .diff_pi ( alert_pi ),
+ .diff_ni ( alert_ni ),
+ .level_o ( alert_level ),
+ .rise_o ( ),
+ .fall_o ( ),
+ .event_o ( ),
+ .sigint_o ( alert_sigint )
+ );
+
+ //////////////////////////////////////////////////////
+ // main protocol FSM that drives the diff outputs
+ //////////////////////////////////////////////////////
+ typedef enum logic [1:0] {Idle, HsAckWait, Pause0, Pause1} state_e;
+ state_e state_d, state_q;
+ logic ping_rise;
+ logic ping_tog_d, ping_tog_q, ack_d, ack_q;
+ logic ping_en_d, ping_en_q;
+ logic ping_pending_d, ping_pending_q;
+
+ // signal ping request upon positive transition on ping_en_i
+ // signalling is performed by a level change event on the diff output
+ assign ping_en_d = ping_en_i;
+ assign ping_rise = ping_en_i && !ping_en_q;
+ assign ping_tog_d = (ping_rise) ? ~ping_tog_q : ping_tog_q;
+
+ // the ping pending signal is used to in the FSM to distinguish whether the
+ // incoming handshake shall be treated as an alert or a ping response.
+ // it is important that this is only set on a rising ping_en level change, since
+ // otherwise the ping enable signal could be abused to "mask" all native alerts
+ // as ping responses by constantly tying it to 1.
+ assign ping_pending_d = ping_rise | ((~ping_ok_o) & ping_en_i & ping_pending_q);
+
+ // diff pair outputs
+ assign ack_po = ack_q;
+ assign ack_no = ~ack_q;
+ assign ping_po = ping_tog_q;
+ assign ping_no = ~ping_tog_q;
+
+ // this FSM receives the four phase handshakes from the alert receiver
+ // note that the latency of the alert_p/n input diff pair is at least one
+ // cycle until it enters the receiver FSM. the same holds for the ack_* diff
+ // pair outputs.
+ always_comb begin : p_fsm
+ // default
+ state_d = state_q;
+ ack_d = 1'b0;
+ ping_ok_o = 1'b0;
+ integ_fail_o = 1'b0;
+ alert_o = 1'b0;
+
+ unique case (state_q)
+ Idle: begin
+ // wait for handshake to be initiated
+ if (alert_level) begin
+ state_d = HsAckWait;
+ ack_d = 1'b1;
+ // signal either an alert or ping received on the output
+ if (ping_pending_q) begin
+ ping_ok_o = 1'b1;
+ end else begin
+ alert_o = 1'b1;
+ end
+ end
+ end
+ // waiting for deassertion of alert to complete HS
+ HsAckWait: begin
+ if (!alert_level) begin
+ state_d = Pause0;
+ end else begin
+ ack_d = 1'b1;
+ end
+ end
+ // pause cycles between back-to-back handshakes
+ Pause0: state_d = Pause1;
+ Pause1: state_d = Idle;
+ default : ; // full case
+ endcase
+
+ // override in case of sigint
+ if (alert_sigint) begin
+ state_d = Idle;
+ ack_d = 1'b0;
+ ping_ok_o = 1'b0;
+ integ_fail_o = 1'b1;
+ alert_o = 1'b0;
+ end
+ end
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg
+ if (!rst_ni) begin
+ state_q <= Idle;
+ ack_q <= 1'b0;
+ ping_tog_q <= 1'b0;
+ ping_en_q <= 1'b0;
+ ping_pending_q <= 1'b0;
+ end else begin
+ state_q <= state_d;
+ ack_q <= ack_d;
+ ping_tog_q <= ping_tog_d;
+ ping_en_q <= ping_en_d;
+ ping_pending_q <= ping_pending_d;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // assertions
+ //////////////////////////////////////////////////////
+
+ // shared assertions
+ // check encoding of outgoing diffpairs
+ `ASSERT(PingDiffOk_A, ping_po ^ ping_no, clk_i, !rst_ni)
+ `ASSERT(AckDiffOk_A, ack_po ^ ack_no, clk_i, !rst_ni)
+ // ping request at input -> need to see encoded ping request
+ `ASSERT(PingRequest0_A, ##1 $rose(ping_en_i) |=> $changed(ping_po), clk_i, !rst_ni)
+ // ping response implies it has been requested
+ `ASSERT(PingResponse0_A, ping_ok_o |-> ping_pending_q, clk_i, !rst_ni)
+ // correctly latch ping request
+ `ASSERT(PingPending_A, ##1 $rose(ping_en_i) |=> ping_pending_q, clk_i, !rst_ni)
+
+ if (AsyncOn) begin : gen_async_assert
+ // signal integrity check propagation
+ `ASSERT(SigInt_A, alert_pi == alert_ni [*2] |-> ##2 integ_fail_o, clk_i, !rst_ni)
+ // TODO: need to add skewed cases as well, the assertions below assume no skew at the moment
+ // ping response
+ `ASSERT(PingResponse1_A, ##1 $rose(alert_pi) && (alert_pi ^ alert_ni) ##2
+ state_q == Idle && ping_pending_q |-> ping_ok_o, clk_i, !rst_ni || integ_fail_o)
+ // alert
+ `ASSERT(Alert_A, ##1 $rose(alert_pi) && (alert_pi ^ alert_ni) ##2
+ state_q == Idle && !ping_pending_q |-> alert_o, clk_i, !rst_ni || integ_fail_o)
+ end else begin : gen_sync_assert
+ // signal integrity check propagation
+ `ASSERT(SigInt_A, alert_pi == alert_ni |-> integ_fail_o, clk_i, !rst_ni)
+ // ping response
+ `ASSERT(PingResponse1_A, ##1 $rose(alert_pi) && state_q == Idle && ping_pending_q |->
+ ping_ok_o, clk_i, !rst_ni || integ_fail_o)
+ // alert
+ `ASSERT(Alert_A, ##1 $rose(alert_pi) && state_q == Idle && !ping_pending_q |->
+ alert_o, clk_i, !rst_ni || integ_fail_o)
+ end
+
+endmodule : prim_alert_receiver
diff --git a/hw/ip/prim/rtl/prim_alert_sender.sv b/hw/ip/prim/rtl/prim_alert_sender.sv
new file mode 100644
index 0000000..814a5cc
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_alert_sender.sv
@@ -0,0 +1,225 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// The alert sender primitive module differentially encodes and transmits an
+// alert signal to the prim_alert_receiver module. An alert will be signalled
+// by a full handshake on alert_po/no and ack_pi/ni. The alert_i signal may
+// be continuously asserted, in which case the alert signalling handshake
+// will be repeatedly initiated.
+//
+// Further, this module supports in-band ping testing, which means that a level
+// change on the ping_pi/ni diff pair will result in a full-handshake response
+// on alert_po/no and ack_pi/ni.
+//
+// The protocol works in both asynchronous and synchronous cases. In the
+// asynchronous case, the parameter AsyncOn must be set to 1'b1 in order to
+// instantiate additional synchronization logic. Further, it must be ensured
+// that the timing skew between all diff pairs is smaller than the shortest
+// clock period of the involved clocks.
+//
+// Incorrectly encoded diff inputs can be detected and will be signalled
+// to the receiver by placing an inconsistent diff value on the differential
+// output (and continuously toggling it).
+//
+// See also: prim_alert_receiver, prim_diff_decode, alert_handler
+
+module prim_alert_sender #(
+ // enables additional synchronization logic
+ parameter bit AsyncOn = 1'b1
+) (
+ input clk_i,
+ input rst_ni,
+ // native alert from the peripheral
+ input alert_i,
+ // ping input diff pair
+ input ping_pi,
+ input ping_ni,
+ // alert input diff pair
+ input ack_pi,
+ input ack_ni,
+ // alert output diff pair
+ output logic alert_po,
+ output logic alert_no
+);
+
+ //////////////////////////////////////////////////////
+ // decode differential signals
+ //////////////////////////////////////////////////////
+ logic ping_sigint, ping_event;
+
+ prim_diff_decode #(
+ .AsyncOn(AsyncOn)
+ ) i_decode_ping (
+ .clk_i,
+ .rst_ni,
+ .diff_pi ( ping_pi ),
+ .diff_ni ( ping_ni ),
+ .level_o ( ),
+ .rise_o ( ),
+ .fall_o ( ),
+ .event_o ( ping_event ),
+ .sigint_o ( ping_sigint )
+ );
+
+ logic ack_sigint, ack_level;
+
+ prim_diff_decode #(
+ .AsyncOn(AsyncOn)
+ ) i_decode_ack (
+ .clk_i,
+ .rst_ni,
+ .diff_pi ( ack_pi ),
+ .diff_ni ( ack_ni ),
+ .level_o ( ack_level ),
+ .rise_o ( ),
+ .fall_o ( ),
+ .event_o ( ),
+ .sigint_o ( ack_sigint )
+ );
+
+ //////////////////////////////////////////////////////
+ // main protocol FSM that drives the diff output
+ //////////////////////////////////////////////////////
+ typedef enum logic [2:0] {Idle, HsPhase1, HsPhase2, SigInt, Pause0, Pause1} state_e;
+ state_e state_d, state_q;
+ logic alert_pq, alert_nq, alert_pd, alert_nd;
+ logic sigint_detected;
+
+ assign sigint_detected = ack_sigint | ping_sigint;
+
+ // diff pair output
+ assign alert_po = alert_pq;
+ assign alert_no = alert_nq;
+
+ // alert and ping set regs
+ logic alert_set_d, alert_set_q, alert_clr;
+ logic ping_set_d, ping_set_q, ping_clr;
+ assign alert_set_d = (alert_clr) ? 1'b0 : (alert_set_q | alert_i);
+ assign ping_set_d = (ping_clr) ? 1'b0 : (ping_set_q | ping_event);
+
+ // this FSM performs a full four phase handshake upon a ping or alert trigger.
+ // note that the latency of the alert_p/n diff pair is at least one cycle
+ // until it enters the receiver FSM. the same holds for the ack_* diff pair
+ // input. in case a signal integrity issue is detected, the FSM bails out,
+ // sets the alert_p/n diff pair to the same value and toggles it in order to
+ // signal that condition over to the receiver.
+ always_comb begin : p_fsm
+ // default
+ state_d = state_q;
+ alert_pd = 1'b0;
+ alert_nd = 1'b1;
+ ping_clr = 1'b0;
+ alert_clr = 1'b0;
+
+ unique case (state_q)
+ Idle: begin
+ // alert always takes precedence
+ if (alert_i || alert_set_q || ping_event || ping_set_q) begin
+ state_d = HsPhase1;
+ alert_pd = 1'b1;
+ alert_nd = 1'b0;
+ if (ping_event || ping_set_q) begin
+ ping_clr = 1'b1;
+ end else begin
+ alert_clr = 1'b1;
+ end
+ end
+ end
+ // waiting for ack from receiver
+ HsPhase1: begin
+ if (ack_level) begin
+ state_d = HsPhase2;
+ end else begin
+ alert_pd = 1'b1;
+ alert_nd = 1'b0;
+ end
+ end
+ // wait for deassertion of ack
+ HsPhase2: begin
+ if (!ack_level) begin
+ state_d = Pause0;
+ end
+ end
+ // pause cycles between back-to-back handshakes
+ Pause0: state_d = Pause1;
+ Pause1: state_d = Idle;
+ // we have a signal integrity issue at one of
+ // the incoming diff pairs. this condition is
+ // signalled by setting the output diffpair
+ // to the same value and continuously toggling
+ // them.
+ SigInt: begin
+ state_d = Idle;
+ if (sigint_detected) begin
+ state_d = SigInt;
+ alert_pd = ~alert_pq;
+ alert_nd = ~alert_pq;
+ end
+ end
+ // catch parasitic states
+ default : state_d = Idle;
+ endcase
+ // bail out if a signal integrity issue has been detected
+ if (sigint_detected && (state_q != SigInt)) begin
+ state_d = SigInt;
+ alert_pd = 1'b0;
+ alert_nd = 1'b0;
+ ping_clr = 1'b0;
+ alert_clr = 1'b0;
+ end
+ end
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg
+ if (!rst_ni) begin
+ state_q <= Idle;
+ alert_pq <= 1'b0;
+ alert_nq <= 1'b1;
+ alert_set_q <= 1'b0;
+ ping_set_q <= 1'b0;
+ end else begin
+ state_q <= state_d;
+ alert_pq <= alert_pd;
+ alert_nq <= alert_nd;
+ alert_set_q <= alert_set_d;
+ ping_set_q <= ping_set_d;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // assertions
+ //////////////////////////////////////////////////////
+
+ if (AsyncOn) begin : gen_async_assert
+ // check propagation of sigint issues to output within three cycles
+ `ASSERT(SigIntPing_A, ping_pi == ping_ni [*2] |-> ##3 alert_po == alert_no, clk_i, !rst_ni)
+ `ASSERT(SigIntAck_A, ack_pi == ack_ni [*2] |-> ##3 alert_po == alert_no, clk_i, !rst_ni)
+ // output must be driven diff unless sigint issue detected
+ `ASSERT(DiffEncoding_A, (ack_pi ^ ack_ni) && (ping_pi ^ ping_ni) |->
+ ##3 alert_po ^ alert_no, clk_i, !rst_ni)
+
+ // handshakes can take indefinite time if blocked due to sigint on outgoing
+ // lines (which is not visible here). thus, we only check whether the handshake is correctly initiated
+ // and defer the full handshake checking to the testbench.
+ // TODO: add the staggered cases as well
+ `ASSERT(PingHs_A, ##1 $changed(ping_pi) && (ping_pi ^ ping_ni) ##2 state_q == Idle |=>
+ $rose(alert_po), clk_i, !rst_ni || (alert_po == alert_no))
+ end else begin : gen_sync_assert
+ // check propagation of sigint issues to output within one cycle
+ `ASSERT(SigIntPing_A, ping_pi == ping_ni |=> alert_po == alert_no, clk_i, !rst_ni)
+ `ASSERT(SigIntAck_A, ack_pi == ack_ni |=> alert_po == alert_no, clk_i, !rst_ni)
+ // output must be driven diff unless sigint issue detected
+ `ASSERT(DiffEncoding_A, (ack_pi ^ ack_ni) && (ping_pi ^ ping_ni) |=> alert_po ^ alert_no,
+ clk_i, !rst_ni)
+ // handshakes can take indefinite time if blocked due to sigint on outgoing
+ // lines (which is not visible here). thus, we only check whether the handshake is correctly initiated
+ // and defer the full handshake checking to the testbench.
+ `ASSERT(PingHs_A, ##1 $changed(ping_pi) && state_q == Idle |=> $rose(alert_po),
+ clk_i, !rst_ni || (alert_po == alert_no))
+ end
+
+ // if alert_i is true, handshakes should be continuously repeated
+ `ASSERT(AlertHs_A, alert_i && state_q == Idle |=> $rose(alert_po),
+ clk_i, !rst_ni || (alert_po == alert_no))
+
+endmodule : prim_alert_sender
diff --git a/hw/ip/prim/rtl/prim_diff_decode.sv b/hw/ip/prim/rtl/prim_diff_decode.sv
new file mode 100644
index 0000000..8efa703
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_diff_decode.sv
@@ -0,0 +1,262 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// This module decodes a differentially encoded signal and detects
+// incorrectly encoded differential states.
+//
+// In case the differential pair crosses an asynchronous boundary, it has
+// to be re-synchronized to the local clock. This can be achieved by
+// setting the AsyncOn parameter to 1'b1. In that case, two additional
+// input registers are added (to counteract metastability), and
+// a pattern detector is instantiated that detects skewed level changes on
+// the differential pair (i.e., when level changes on the diff pair are
+// sampled one cycle apart due to a timing skew between the two wires).
+//
+// See also: prim_alert_sender, prim_alert_receiver, alert_handler
+
+module prim_diff_decode #(
+ // enables additional synchronization logic
+ parameter bit AsyncOn = 1'b0
+) (
+ input clk_i,
+ input rst_ni,
+ // input diff pair
+ input diff_pi,
+ input diff_ni,
+ // logical level and
+ // detected edges
+ output logic level_o,
+ output logic rise_o,
+ output logic fall_o,
+ // either rise or fall
+ output logic event_o,
+ //signal integrity issue detected
+ output logic sigint_o
+);
+
+ logic level_d, level_q;
+
+ //////////////////////////////////////////////////////
+ // synchronization regs for incoming diff pair
+ // (if required)
+ //////////////////////////////////////////////////////
+
+ if (AsyncOn) begin : gen_async
+
+ typedef enum logic [1:0] {IsStd, IsSkewed, SigInt} state_e;
+ state_e state_d, state_q;
+ logic diff_p_edge, diff_n_edge, diff_check_ok, level;
+
+ // 2 sync regs, one reg for edge detection
+ logic diff_pq, diff_nq, diff_pd, diff_nd;
+
+ prim_flop_2sync #(
+ .Width(1),
+ .ResetValue(0)
+ ) i_sync_p (
+ .clk_i,
+ .rst_ni,
+ .d(diff_pi),
+ .q(diff_pd)
+ );
+
+ prim_flop_2sync #(
+ .Width(1),
+ .ResetValue(1)
+ ) i_sync_n (
+ .clk_i,
+ .rst_ni,
+ .d(diff_ni),
+ .q(diff_nd)
+ );
+
+ // detect level transitions
+ assign diff_p_edge = diff_pq ^ diff_pd;
+ assign diff_n_edge = diff_nq ^ diff_nd;
+
+ // detect sigint issue
+ assign diff_check_ok = diff_pd ^ diff_nd;
+
+ // this is the current logical level
+ assign level = diff_pd;
+
+ // outputs
+ assign level_o = level_d;
+ assign event_o = rise_o | fall_o;
+
+ // sigint detection is a bit more involved in async case since
+ // we might have skew on the diff pair, which can result in a
+ // one cycle sampling delay between the two wires
+ // so we need a simple pattern matcher
+ // the following waves are legal
+ // clk | | | | | | | |
+ // _______ _______
+ // p _______/ ... \________
+ // _______ ________
+ // n \_______ ... _______/
+ // ____ ___
+ // p __________/ ... \________
+ // _______ ________
+ // n \_______ ... _______/
+ //
+ // i.e., level changes may be off by one cycle - which is permissible
+ // as long as this condition is only one cycle long.
+
+
+ always_comb begin : p_diff_fsm
+ // default
+ state_d = state_q;
+ level_d = level_q;
+ rise_o = 1'b0;
+ fall_o = 1'b0;
+ sigint_o = 1'b0;
+
+ unique case (state_q)
+ // we remain here as long as
+ // the diff pair is correctly encoded
+ IsStd: begin
+ if (diff_check_ok) begin
+ level_d = level;
+ if (diff_p_edge && diff_n_edge) begin
+ if (level) begin
+ rise_o = 1'b1;
+ end else begin
+ fall_o = 1'b1;
+ end
+ end
+ end else begin
+ if (diff_p_edge || diff_n_edge) begin
+ state_d = IsSkewed;
+ end else begin
+ state_d = SigInt;
+ sigint_o = 1'b1;
+ end
+ end
+ end
+ // diff pair must be correctly encoded, otherwise we got a sigint
+ IsSkewed: begin
+ if (diff_check_ok) begin
+ state_d = IsStd;
+ level_d = level;
+ if (level) rise_o = 1'b1;
+ else fall_o = 1'b1;
+ end else begin
+ state_d = SigInt;
+ sigint_o = 1'b1;
+ end
+ end
+ // Signal integrity issue detected, remain here
+ // until resolved
+ SigInt: begin
+ sigint_o = 1'b1;
+ if (diff_check_ok) begin
+ state_d = IsStd;
+ sigint_o = 1'b0;
+ end
+ end
+ default : ;
+ endcase
+ end
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_sync_reg
+ if (!rst_ni) begin
+ state_q <= IsStd;
+ diff_pq <= 1'b0;
+ diff_nq <= 1'b1;
+ level_q <= 1'b0;
+ end else begin
+ state_q <= state_d;
+ diff_pq <= diff_pd;
+ diff_nq <= diff_nd;
+ level_q <= level_d;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // fully synchronous case
+ // no skew present in this case
+ //////////////////////////////////////////////////////
+
+ end else begin : gen_no_async
+ logic diff_pq, diff_pd;
+
+ // one reg for edge detection
+ assign diff_pd = diff_pi;
+
+ // incorrect encoding -> signal integrity issue
+ assign sigint_o = ~(diff_pi ^ diff_ni);
+
+ assign level_o = (sigint_o) ? level_q : diff_pi;
+ assign level_d = level_o;
+
+ // detect level transitions
+ assign rise_o = (~diff_pq & diff_pi) & ~sigint_o;
+ assign fall_o = ( diff_pq & ~diff_pi) & ~sigint_o;
+ assign event_o = rise_o | fall_o;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_edge_reg
+ if (!rst_ni) begin
+ diff_pq <= 1'b0;
+ level_q <= 1'b0;
+ end else begin
+ diff_pq <= diff_pd;
+ level_q <= level_d;
+ end
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // assertions
+ //////////////////////////////////////////////////////
+
+ // shared assertions
+ // sigint -> level stays the same during sigint
+ // $isunknown is needed to avoid false assertion in first clock cycle
+ `ASSERT(SigintLevelCheck_A, ##1 sigint_o |-> $stable(level_o), clk_i, !rst_ni)
+ // sigint -> no additional events asserted at output
+ `ASSERT(SigintEventCheck_A, sigint_o |-> !event_o, clk_i, !rst_ni)
+ `ASSERT(SigintRiseCheck_A, sigint_o |-> !rise_o, clk_i, !rst_ni)
+ `ASSERT(SigintFallCheck_A, sigint_o |-> !fall_o, clk_i, !rst_ni)
+
+ if (AsyncOn) begin : gen_async_assert
+ // assertions for asynchronous case
+ // correctly detect sigint issue (only one transition cycle of permissible due to skew)
+ `ASSERT(SigintCheck0_A, diff_pi == diff_ni [*2] |-> ##[1:2] sigint_o, clk_i, !rst_ni)
+ // the synchronizer adds 2 cycles of latency
+ `ASSERT(SigintCheck1_A, ##1 (diff_pi ^ diff_ni) && $stable(diff_pi) && $stable(diff_ni) ##1
+ $rose(diff_pi) && $stable(diff_ni) ##1 $stable(diff_pi) && $fell(diff_ni) |->
+ ##2 rise_o, clk_i, !rst_ni)
+ `ASSERT(SigintCheck2_A, ##1 (diff_pi ^ diff_ni) && $stable(diff_pi) && $stable(diff_ni) ##1
+ $fell(diff_pi) && $stable(diff_ni) ##1 $stable(diff_pi) && $rose(diff_ni) |->
+ ##2 fall_o, clk_i, !rst_ni)
+ `ASSERT(SigintCheck3_A, ##1 (diff_pi ^ diff_ni) && $stable(diff_pi) && $stable(diff_ni) ##1
+ $rose(diff_ni) && $stable(diff_pi) ##1 $stable(diff_ni) && $fell(diff_pi) |->
+ ##2 fall_o, clk_i, !rst_ni)
+ `ASSERT(SigintCheck4_A, ##1 (diff_pi ^ diff_ni) && $stable(diff_pi) && $stable(diff_ni) ##1
+ $fell(diff_ni) && $stable(diff_pi) ##1 $stable(diff_ni) && $rose(diff_pi) |->
+ ##2 rise_o, clk_i, !rst_ni)
+ // correctly detect edges
+ `ASSERT(RiseCheck_A, ##1 $rose(diff_pi) && (diff_pi ^ diff_ni) |->
+ ##[2:3] rise_o, clk_i, !rst_ni || sigint_o)
+ `ASSERT(FallCheck_A, ##1 $fell(diff_pi) && (diff_pi ^ diff_ni) |->
+ ##[2:3] fall_o, clk_i, !rst_ni || sigint_o)
+ `ASSERT(EventCheck_A, ##1 $changed(diff_pi) && (diff_pi ^ diff_ni) |->
+ ##[2:3] event_o, clk_i, !rst_ni || sigint_o)
+ // correctly detect level
+ `ASSERT(LevelCheck0_A, !sigint_o && (diff_pi ^ diff_ni) [*3] |=> $past(diff_pi, 2) == level_o,
+ clk_i, !rst_ni || sigint_o)
+
+ end else begin : gen_sync_assert
+ // assertions for synchronous case
+ // correctly detect sigint issue
+ `ASSERT(SigintCheck_A, diff_pi == diff_ni |-> sigint_o, clk_i, !rst_ni)
+ // correctly detect edges
+ `ASSERT(RiseCheck_A, ##1 $rose(diff_pi) && (diff_pi ^ diff_ni) |-> rise_o, clk_i, !rst_ni)
+ `ASSERT(FallCheck_A, ##1 $fell(diff_pi) && (diff_pi ^ diff_ni) |-> fall_o, clk_i, !rst_ni)
+ `ASSERT(EventCheck_A, ##1 $changed(diff_pi) && (diff_pi ^ diff_ni) |-> event_o, clk_i, !rst_ni)
+ // correctly detect level
+ `ASSERT(LevelCheck_A, (diff_pi ^ diff_ni) |-> diff_pi == level_o, clk_i, !rst_ni)
+ end
+
+endmodule : prim_diff_decode
diff --git a/hw/ip/prim/rtl/prim_esc_receiver.sv b/hw/ip/prim/rtl/prim_esc_receiver.sv
new file mode 100644
index 0000000..4c5ac7d
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_esc_receiver.sv
@@ -0,0 +1,175 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// This module decodes escalation enable pulses that have been encoded using
+// the prim_esc_sender module.
+//
+// The module supports in-band ping testing of the escalation
+// wires. This is accomplished by the sender module that places a single-cycle,
+// differentially encoded pulse on esc_p/n which will be interpreted as a ping
+// request by the receiver module. The receiver module responds by sending back
+// the response pattern "1010".
+//
+// Native escalation enable pulses are differentiated from ping
+// requests by making sure that these pulses are always longer than 1 cycle.
+//
+// See also: prim_esc_sender, prim_diff_decode, alert_handler
+
+module prim_esc_receiver (
+ input clk_i,
+ input rst_ni,
+ // escalation enable
+ output logic esc_en_o,
+ // escalation / ping response
+ output logic resp_po,
+ output logic resp_no,
+ // escalation output diff pair
+ input esc_pi,
+ input esc_ni
+);
+
+ //////////////////////////////////////////////////////
+ // decode differential signals
+ //////////////////////////////////////////////////////
+
+ logic esc_level, sigint_detected;
+
+ prim_diff_decode #(
+ .AsyncOn(1'b0)
+ ) i_decode_esc (
+ .clk_i,
+ .rst_ni,
+ .diff_pi ( esc_pi ),
+ .diff_ni ( esc_ni ),
+ .level_o ( esc_level ),
+ .rise_o ( ),
+ .fall_o ( ),
+ .event_o ( ),
+ .sigint_o ( sigint_detected )
+ );
+
+ //////////////////////////////////////////////////////
+ // RX/TX Logic
+ //////////////////////////////////////////////////////
+
+ typedef enum logic [2:0] {Idle, Check, PingResp, EscResp, SigInt} state_e;
+ state_e state_d, state_q;
+ logic resp_pd, resp_pq, resp_nd, resp_nq;
+
+ assign resp_po = resp_pq;
+ assign resp_no = resp_nq;
+
+
+ always_comb begin : p_fsm
+ // default
+ state_d = state_q;
+ resp_pd = 1'b0;
+ resp_nd = 1'b1;
+ esc_en_o = 1'b0;
+
+ unique case (state_q)
+ ///////////////////////////////////
+ // wait for the esc_p/n diff pair
+ Idle: begin
+ if (esc_level) begin
+ state_d = Check;
+ resp_pd = 1'b1;
+ resp_nd = 1'b0;
+ end
+ end
+ ///////////////////////////////////
+ // we decide here whether this is only a ping request or
+ // whether this is an escalation enable
+ Check: begin
+ state_d = PingResp;
+ if (esc_level) begin
+ state_d = EscResp;
+ esc_en_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ // finish ping response. in case esc_level is again asserted,
+ // we got an escalation signal (pings cannot occur back to back)
+ PingResp: begin
+ state_d = Idle;
+ resp_pd = 1'b1;
+ resp_nd = 1'b0;
+ if (esc_level) begin
+ state_d = EscResp;
+ esc_en_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ // we have got an escalation enable pulse,
+ // keep on toggling the outputs
+ EscResp: begin
+ state_d = Idle;
+ if (esc_level) begin
+ state_d = EscResp;
+ resp_pd = ~resp_pq;
+ resp_nd = resp_pq;
+ esc_en_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ // we have a signal integrity issue at one of
+ // the incoming diff pairs. this condition is
+ // signalled to the sender by setting the resp
+ // diffpair to the same value and continuously
+ // toggling them.
+ SigInt: begin
+ state_d = Idle;
+ if (sigint_detected) begin
+ state_d = SigInt;
+ resp_pd = ~resp_pq;
+ resp_nd = ~resp_pq;
+ end
+ end
+ ///////////////////////////////////
+ default : state_d = Idle;
+ endcase
+
+ // bail out if a signal integrity issue has been detected
+ if (sigint_detected && (state_q != SigInt)) begin
+ state_d = SigInt;
+ resp_pd = 1'b0;
+ resp_nd = 1'b0;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // Flops
+ //////////////////////////////////////////////////////
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ state_q <= Idle;
+ resp_pq <= 1'b0;
+ resp_nq <= 1'b1;
+ end else begin
+ state_q <= state_d;
+ resp_pq <= resp_pd;
+ resp_nq <= resp_nd;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // assertions
+ //////////////////////////////////////////////////////
+
+ `ASSERT(SigIntCheck0_A, esc_pi == esc_ni |=> resp_po == resp_no, clk_i, !rst_ni)
+ `ASSERT(SigIntCheck1_A, esc_pi == esc_ni |=> state_q == SigInt, clk_i, !rst_ni)
+ // correct diff encoding
+ `ASSERT(DiffEncCheck_A, esc_pi ^ esc_ni |=> resp_po ^ resp_no, clk_i, !rst_ni)
+ // disable in case of ping integrity issue
+ `ASSERT(PingRespCheck_A, $rose(esc_pi) |=> $fell(esc_pi) |-> $rose(resp_po) |=> $fell(resp_po),
+ clk_i, !rst_ni || (esc_pi == esc_ni))
+ // escalation response needs to continuously toggle
+ `ASSERT(EscRespCheck_A, esc_pi && $past(esc_pi) && (esc_pi ^ esc_ni) && $past(esc_pi ^ esc_ni)
+ |=> resp_po != $past(resp_po), clk_i, !rst_ni)
+ // detect escalation pulse
+ `ASSERT(EscEnCheck_A, esc_pi && (esc_pi ^ esc_ni) && state_q != SigInt |=>
+ esc_pi && (esc_pi ^ esc_ni) |-> esc_en_o, clk_i, !rst_ni )
+
+endmodule : prim_esc_receiver
diff --git a/hw/ip/prim/rtl/prim_esc_sender.sv b/hw/ip/prim/rtl/prim_esc_sender.sv
new file mode 100644
index 0000000..bf8affb
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_esc_sender.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 differentially encodes an escalation enable pulse
+// of arbitrary width.
+//
+// The module supports in-band ping testing of the escalation
+// wires. This is accomplished by sending out a single, differentially
+// encoded pulse on esc_p/n which will be interpreted as a ping
+// request by the escalation receiver. Note that ping_en_i shall
+// be held high until either ping_ok_o or integ_fail_o is asserted.
+//
+// Native escalation enable pulses are differentiated from ping
+// requests by making sure that these pulses are always longer than 1 cycle.
+//
+// If there is a differential encoding error, integ_fail_o
+// will be asserted.
+//
+// See also: prim_esc_receiver, prim_diff_decode, alert_handler
+
+module prim_esc_sender (
+ input clk_i,
+ input rst_ni,
+ // this triggers a ping test. keep asserted
+ // until either ping_ok_o or ping_fail_o is asserted.
+ input ping_en_i,
+ output logic ping_ok_o,
+ // asserted if signal integrity issue detected
+ output logic integ_fail_o,
+ // escalation enable signal
+ input esc_en_i,
+ // escalation / ping response
+ input resp_pi,
+ input resp_ni,
+ // escalation output diff pair
+ output logic esc_po,
+ output logic esc_no
+);
+
+ //////////////////////////////////////////////////////
+ // decode differential signals
+ //////////////////////////////////////////////////////
+
+ logic resp, sigint_detected;
+
+ prim_diff_decode #(
+ .AsyncOn(1'b0)
+ ) i_decode_resp (
+ .clk_i,
+ .rst_ni,
+ .diff_pi ( resp_pi ),
+ .diff_ni ( resp_ni ),
+ .level_o ( resp ),
+ .rise_o ( ),
+ .fall_o ( ),
+ .event_o ( ),
+ .sigint_o ( sigint_detected )
+ );
+
+ //////////////////////////////////////////////////////
+ // TX Logic
+ //////////////////////////////////////////////////////
+
+ logic ping_en_d, ping_en_q;
+ logic esc_en_d, esc_en_q, esc_en_q1;
+
+ assign ping_en_d = ping_en_i;
+ assign esc_en_d = esc_en_i;
+
+ // ping enable is 1 cycle pulse
+ // escalation pulse is always longer than 2 cycles
+ assign esc_po = esc_en_i | esc_en_q | ( ping_en_d & ~ping_en_q);
+ assign esc_no = ~esc_po;
+
+ //////////////////////////////////////////////////////
+ // RX Logic
+ //////////////////////////////////////////////////////
+
+ typedef enum logic [2:0] {Idle, CheckEscRespLo, CheckEscRespHi,
+ CheckPingResp0, CheckPingResp1, CheckPingResp2, CheckPingResp3} fsm_e;
+
+ fsm_e state_d, state_q;
+
+ always_comb begin : p_fsm
+ // default
+ state_d = state_q;
+ ping_ok_o = 1'b0;
+ integ_fail_o = sigint_detected;
+
+ unique case (state_q)
+ ///////////////////////////////////
+ // wait for ping or escalation enable
+ Idle: begin
+ if (esc_en_i) begin
+ state_d = CheckEscRespHi;
+ end else if (ping_en_i) begin
+ state_d = CheckPingResp0;
+ end
+ // any assertion of the response signal
+ // signal here will trigger a sigint error
+ if (resp) begin
+ integ_fail_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ // check whether response is 0
+ CheckEscRespLo: begin
+ state_d = CheckEscRespHi;
+ if (!esc_en_i || resp) begin
+ state_d = Idle;
+ integ_fail_o = sigint_detected | resp;
+ end
+ end
+ ///////////////////////////////////
+ // check whether response is 1
+ CheckEscRespHi: begin
+ state_d = CheckEscRespLo;
+ if (!esc_en_i || !resp) begin
+ state_d = Idle;
+ integ_fail_o = sigint_detected | ~resp;
+ end
+ end
+ ///////////////////////////////////
+ // start of ping response sequence
+ // we expect the sequence "1010"
+ CheckPingResp0: begin
+ state_d = CheckPingResp1;
+ // abort sequence immediately if escalation is signalled,
+ // jump to escalation response checking (lo state)
+ if (esc_en_i) begin
+ state_d = CheckEscRespLo;
+ // abort if response is wrong
+ end else if (!resp) begin
+ state_d = Idle;
+ integ_fail_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ CheckPingResp1: begin
+ state_d = CheckPingResp2;
+ // abort sequence immediately if escalation is signalled,
+ // jump to escalation response checking (hi state)
+ if (esc_en_i) begin
+ state_d = CheckEscRespHi;
+ // abort if response is wrong
+ end else if (resp) begin
+ state_d = Idle;
+ integ_fail_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ CheckPingResp2: begin
+ state_d = CheckPingResp3;
+ // abort sequence immediately if escalation is signalled,
+ // jump to escalation response checking (lo state)
+ if (esc_en_i) begin
+ state_d = CheckEscRespLo;
+ // abort if response is wrong
+ end else if (!resp) begin
+ state_d = Idle;
+ integ_fail_o = 1'b1;
+ end
+ end
+ ///////////////////////////////////
+ CheckPingResp3: begin
+ state_d = Idle;
+ // abort sequence immediately if escalation is signalled,
+ // jump to escalation response checking (hi state)
+ if (esc_en_i) begin
+ state_d = CheckEscRespHi;
+ // abort if response is wrong
+ end else if (resp) begin
+ integ_fail_o = 1'b1;
+ end else begin
+ ping_ok_o = ping_en_i;
+ end
+ end
+ ///////////////////////////////////
+ default : state_d = Idle;
+ endcase
+
+ // escalation takes precedence,
+ // immediately return ok in that case
+ if ((esc_en_i || esc_en_q || esc_en_q1) && ping_en_i) begin
+ ping_ok_o = 1'b1;
+ end
+
+ // a sigint error will reset the state machine
+ // and have it pause for two cycles to let the
+ // receiver recover
+ if (sigint_detected) begin
+ ping_ok_o = 1'b0;
+ state_d = Idle;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // Flops
+ //////////////////////////////////////////////////////
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
+ if (!rst_ni) begin
+ state_q <= Idle;
+ esc_en_q <= 1'b0;
+ esc_en_q1 <= 1'b0;
+ ping_en_q <= 1'b0;
+ end else begin
+ state_q <= state_d;
+ esc_en_q <= esc_en_d;
+ esc_en_q1 <= esc_en_q;
+ ping_en_q <= ping_en_d;
+ end
+ end
+
+ //////////////////////////////////////////////////////
+ // assertions
+ //////////////////////////////////////////////////////
+
+ // diff encoding of output
+ `ASSERT(DiffEncCheck_A, esc_po ^ esc_no, clk_i, !rst_ni)
+ // signal integrity check propagation
+ `ASSERT(SigIntCheck0_A, resp_pi == resp_ni |-> integ_fail_o, clk_i, !rst_ni)
+ // this happens in case we did not get a correct escalation response
+ `ASSERT(SigIntCheck1_A, ##1 $rose(esc_en_i) &&
+ state_q inside {Idle, CheckPingResp1, CheckPingResp3} ##1 !resp_pi |->
+ integ_fail_o, clk_i, !rst_ni || (resp_pi == resp_ni) || (state_q == Idle && resp))
+ `ASSERT(SigIntCheck2_A, ##1 $rose(esc_en_i) &&
+ state_q inside {CheckPingResp0, CheckPingResp2} ##1 resp_pi |->
+ integ_fail_o, clk_i, !rst_ni || (resp_pi == resp_ni) || (state_q == Idle && resp))
+ // unexpected response
+ `ASSERT(SigIntCheck3_A, state_q == Idle && resp |-> integ_fail_o, clk_i, !rst_ni)
+ // check that escalation signal is at least 2 cycles high
+ `ASSERT(EscCheck_A, esc_en_i |-> esc_po [*2] , clk_i, !rst_ni)
+ // escalation / ping collision
+ `ASSERT(EscPingCheck_A, esc_en_i && ping_en_i |-> ping_ok_o, clk_i, !rst_ni || integ_fail_o)
+ // check that ping request results in only a single cycle pulse
+ `ASSERT(PingCheck_A, ##1 $rose(ping_en_i) |-> esc_po ##1 !esc_po , clk_i,
+ !rst_ni || esc_en_i || integ_fail_o)
+
+endmodule : prim_esc_sender