| // 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_p/n and ack_p/n. The alert_req_i signal may |
| // be continuously asserted, in which case the alert signalling handshake |
| // will be repeatedly initiated. |
| // |
| // The alert_req_i signal may also be used as part of req/ack. The parent module |
| // can keep alert_req_i asserted until it has been ack'd (transferred to the alert |
| // receiver). The parent module is not required to use this. |
| // |
| // In case the alert sender parameter IsFatal is set to 1, an incoming alert |
| // alert_req_i is latched in a local register until the next reset, causing the |
| // alert sender to behave as if alert_req_i were continously asserted. |
| // The alert_state_o output reflects the state of this internal latching register. |
| // |
| // The alert sender also exposes an alert test input, which can be used to trigger |
| // single alert handshakes. This input behaves exactly the same way as the |
| // alert_req_i input with IsFatal set to 0. Test alerts do not cause alert_ack_o |
| // to be asserted, nor are they latched until reset (regardless of the value of the |
| // IsFatal parameter). |
| // |
| // Further, this module supports in-band ping testing, which means that a level |
| // change on the ping_p/n diff pair will result in a full-handshake response |
| // on alert_p/n and ack_p/n. |
| // |
| // 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 |
| |
| `include "prim_assert.sv" |
| |
| module prim_alert_sender |
| import prim_alert_pkg::*; |
| #( |
| // enables additional synchronization logic |
| parameter bit AsyncOn = 1'b1, |
| // alert sender will latch the incoming alert event permanently and |
| // keep on sending alert events until the next reset. |
| parameter bit IsFatal = 1'b0 |
| ) ( |
| input clk_i, |
| input rst_ni, |
| // alert test trigger (this will never be latched, even if IsFatal == 1) |
| input alert_test_i, |
| // native alert from the peripheral |
| input alert_req_i, |
| output logic alert_ack_o, |
| // state of the alert latching register |
| output logic alert_state_o, |
| // ping input diff pair and ack diff pair |
| input alert_rx_t alert_rx_i, |
| // alert output diff pair |
| output alert_tx_t alert_tx_o |
| ); |
| |
| |
| ///////////////////////////////// |
| // decode differential signals // |
| ///////////////////////////////// |
| logic ping_sigint, ping_event, ping_n, ping_p; |
| |
| // This prevents further tool optimizations of the differential signal. |
| prim_sec_anchor_buf #( |
| .Width(2) |
| ) u_prim_buf_ping ( |
| .in_i({alert_rx_i.ping_n, |
| alert_rx_i.ping_p}), |
| .out_o({ping_n, |
| ping_p}) |
| ); |
| |
| prim_diff_decode #( |
| .AsyncOn(AsyncOn) |
| ) u_decode_ping ( |
| .clk_i, |
| .rst_ni, |
| .diff_pi ( ping_p ), |
| .diff_ni ( ping_n ), |
| .level_o ( ), |
| .rise_o ( ), |
| .fall_o ( ), |
| .event_o ( ping_event ), |
| .sigint_o ( ping_sigint ) |
| ); |
| |
| logic ack_sigint, ack_level, ack_n, ack_p; |
| |
| // This prevents further tool optimizations of the differential signal. |
| prim_sec_anchor_buf #( |
| .Width(2) |
| ) u_prim_buf_ack ( |
| .in_i({alert_rx_i.ack_n, |
| alert_rx_i.ack_p}), |
| .out_o({ack_n, |
| ack_p}) |
| ); |
| |
| prim_diff_decode #( |
| .AsyncOn(AsyncOn) |
| ) u_decode_ack ( |
| .clk_i, |
| .rst_ni, |
| .diff_pi ( ack_p ), |
| .diff_ni ( ack_n ), |
| .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, |
| AlertHsPhase1, |
| AlertHsPhase2, |
| PingHsPhase1, |
| PingHsPhase2, |
| 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_tx_o.alert_p = alert_pq; |
| assign alert_tx_o.alert_n = alert_nq; |
| |
| // alert and ping set regs |
| logic alert_set_d, alert_set_q, alert_clr; |
| logic alert_test_set_d, alert_test_set_q; |
| logic ping_set_d, ping_set_q, ping_clr; |
| logic alert_req_trigger, alert_test_trigger, ping_trigger; |
| |
| // if handshake is ongoing, capture additional alert requests. |
| logic alert_req; |
| prim_sec_anchor_buf #( |
| .Width(1) |
| ) u_prim_buf_in_req ( |
| .in_i(alert_req_i), |
| .out_o(alert_req) |
| ); |
| |
| assign alert_req_trigger = alert_req | alert_set_q; |
| if (IsFatal) begin : gen_fatal |
| assign alert_set_d = alert_req_trigger; |
| end else begin : gen_recov |
| assign alert_set_d = (alert_clr) ? 1'b0 : alert_req_trigger; |
| end |
| |
| // the alert test request is always cleared. |
| assign alert_test_trigger = alert_test_i | alert_test_set_q; |
| assign alert_test_set_d = (alert_clr) ? 1'b0 : alert_test_trigger; |
| |
| logic alert_trigger; |
| assign alert_trigger = alert_req_trigger | alert_test_trigger; |
| |
| assign ping_trigger = ping_set_q | ping_event; |
| assign ping_set_d = (ping_clr) ? 1'b0 : ping_trigger; |
| |
| |
| // alert event acknowledge and state (not affected by alert_test_i) |
| assign alert_ack_o = alert_clr & alert_set_q; |
| assign alert_state_o = alert_set_q; |
| |
| // 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_trigger || ping_trigger) begin |
| state_d = (alert_trigger) ? AlertHsPhase1 : PingHsPhase1; |
| alert_pd = 1'b1; |
| alert_nd = 1'b0; |
| end |
| end |
| // waiting for ack from receiver |
| AlertHsPhase1: begin |
| if (ack_level) begin |
| state_d = AlertHsPhase2; |
| end else begin |
| alert_pd = 1'b1; |
| alert_nd = 1'b0; |
| end |
| end |
| // wait for deassertion of ack |
| AlertHsPhase2: begin |
| if (!ack_level) begin |
| state_d = Pause0; |
| alert_clr = 1'b1; |
| end |
| end |
| // waiting for ack from receiver |
| PingHsPhase1: begin |
| if (ack_level) begin |
| state_d = PingHsPhase2; |
| end else begin |
| alert_pd = 1'b1; |
| alert_nd = 1'b0; |
| end |
| end |
| // wait for deassertion of ack |
| PingHsPhase2: begin |
| if (!ack_level) begin |
| ping_clr = 1'b1; |
| state_d = Pause0; |
| end |
| end |
| // pause cycles between back-to-back handshakes |
| Pause0: begin |
| state_d = Pause1; |
| end |
| // clear and ack alert request if it was set |
| Pause1: begin |
| state_d = Idle; |
| end |
| // catch parasitic states |
| default : state_d = Idle; |
| endcase |
| |
| // we have a signal integrity issue at one of the incoming diff pairs. this condition is |
| // signalled by setting the output diffpair to zero. If the sigint has disappeared, we clear |
| // the ping request state of this sender and go back to idle. |
| if (sigint_detected) begin |
| state_d = Idle; |
| alert_pd = 1'b0; |
| alert_nd = 1'b0; |
| ping_clr = 1'b1; |
| alert_clr = 1'b0; |
| end |
| end |
| |
| // This prevents further tool optimizations of the differential signal. |
| prim_sec_anchor_flop #( |
| .Width (2), |
| .ResetValue(2'b10) |
| ) u_prim_flop_alert ( |
| .clk_i, |
| .rst_ni, |
| .d_i({alert_nd, alert_pd}), |
| .q_o({alert_nq, alert_pq}) |
| ); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg |
| if (!rst_ni) begin |
| state_q <= Idle; |
| alert_set_q <= 1'b0; |
| alert_test_set_q <= 1'b0; |
| ping_set_q <= 1'b0; |
| end else begin |
| state_q <= state_d; |
| alert_set_q <= alert_set_d; |
| alert_test_set_q <= alert_test_set_d; |
| ping_set_q <= ping_set_d; |
| end |
| end |
| |
| |
| //////////////// |
| // assertions // |
| //////////////// |
| |
| // however, since we use sequence constructs below, we need to wrap the entire block again. |
| // typically, the ASSERT macros already contain this INC_ASSERT macro. |
| `ifdef INC_ASSERT |
| // check whether all outputs have a good known state after reset |
| `ASSERT_KNOWN(AlertPKnownO_A, alert_tx_o) |
| |
| if (AsyncOn) begin : gen_async_assert |
| sequence PingSigInt_S; |
| alert_rx_i.ping_p == alert_rx_i.ping_n [*2]; |
| endsequence |
| sequence AckSigInt_S; |
| alert_rx_i.ping_p == alert_rx_i.ping_n [*2]; |
| endsequence |
| |
| `ifndef FPV_ALERT_NO_SIGINT_ERR |
| // check propagation of sigint issues to output within three cycles |
| // shift sequence to the right to avoid reset effects. |
| `ASSERT(SigIntPing_A, ##1 PingSigInt_S |-> |
| ##3 alert_tx_o.alert_p == alert_tx_o.alert_n) |
| `ASSERT(SigIntAck_A, ##1 AckSigInt_S |-> |
| ##3 alert_tx_o.alert_p == alert_tx_o.alert_n) |
| `endif |
| |
| // Test in-band FSM reset request (via signal integrity error) |
| `ASSERT(InBandInitFsm_A, PingSigInt_S or AckSigInt_S |-> ##3 state_q == Idle) |
| `ASSERT(InBandInitPing_A, PingSigInt_S or AckSigInt_S |-> ##3 !ping_set_q) |
| // output must be driven diff unless sigint issue detected |
| `ASSERT(DiffEncoding_A, (alert_rx_i.ack_p ^ alert_rx_i.ack_n) && |
| (alert_rx_i.ping_p ^ alert_rx_i.ping_n) |-> |
| ##[3:4] alert_tx_o.alert_p ^ alert_tx_o.alert_n) |
| |
| // 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(alert_rx_i.ping_p) && |
| (alert_rx_i.ping_p ^ alert_rx_i.ping_n) ##2 state_q == Idle |=> |
| $rose(alert_tx_o.alert_p), clk_i, !rst_ni || (alert_tx_o.alert_p == alert_tx_o.alert_n)) |
| end else begin : gen_sync_assert |
| sequence PingSigInt_S; |
| alert_rx_i.ping_p == alert_rx_i.ping_n; |
| endsequence |
| sequence AckSigInt_S; |
| alert_rx_i.ping_p == alert_rx_i.ping_n; |
| endsequence |
| |
| `ifndef FPV_ALERT_NO_SIGINT_ERR |
| // check propagation of sigint issues to output within one cycle |
| `ASSERT(SigIntPing_A, PingSigInt_S |=> |
| alert_tx_o.alert_p == alert_tx_o.alert_n) |
| `ASSERT(SigIntAck_A, AckSigInt_S |=> |
| alert_tx_o.alert_p == alert_tx_o.alert_n) |
| `endif |
| |
| // Test in-band FSM reset request (via signal integrity error) |
| `ASSERT(InBandInitFsm_A, PingSigInt_S or AckSigInt_S |=> state_q == Idle) |
| `ASSERT(InBandInitPing_A, PingSigInt_S or AckSigInt_S |=> !ping_set_q) |
| // output must be driven diff unless sigint issue detected |
| `ASSERT(DiffEncoding_A, (alert_rx_i.ack_p ^ alert_rx_i.ack_n) && |
| (alert_rx_i.ping_p ^ alert_rx_i.ping_n) |=> alert_tx_o.alert_p ^ alert_tx_o.alert_n) |
| // 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(alert_rx_i.ping_p) && state_q == Idle |=> |
| $rose(alert_tx_o.alert_p), clk_i, !rst_ni || (alert_tx_o.alert_p == alert_tx_o.alert_n)) |
| end |
| |
| // Test the alert state output. |
| `ASSERT(AlertState0_A, alert_set_q === alert_state_o) |
| |
| if (IsFatal) begin : gen_fatal_assert |
| `ASSERT(AlertState1_A, alert_req_i |=> alert_state_o) |
| `ASSERT(AlertState2_A, alert_state_o |=> $stable(alert_state_o)) |
| `ASSERT(AlertState3_A, alert_ack_o |=> alert_state_o) |
| end else begin : gen_recov_assert |
| `ASSERT(AlertState1_A, alert_req_i && !alert_clr |=> alert_state_o) |
| `ASSERT(AlertState2_A, alert_req_i && alert_ack_o |=> !alert_state_o) |
| end |
| |
| // The alert test input should not set the alert state register. |
| `ASSERT(AlertTest1_A, alert_test_i && !alert_req_i && !alert_state_o |=> $stable(alert_state_o)) |
| |
| // if alert_req_i is true, handshakes should be continuously repeated |
| `ASSERT(AlertHs_A, alert_req_i && state_q == Idle |=> $rose(alert_tx_o.alert_p), |
| clk_i, !rst_ni || (alert_tx_o.alert_p == alert_tx_o.alert_n)) |
| |
| // if alert_test_i is true, handshakes should be continuously repeated |
| `ASSERT(AlertTestHs_A, alert_test_i && state_q == Idle |=> $rose(alert_tx_o.alert_p), |
| clk_i, !rst_ni || (alert_tx_o.alert_p == alert_tx_o.alert_n)) |
| `endif |
| |
| `ifdef FPV_ALERT_NO_SIGINT_ERR |
| // Assumptions for FPV security countermeasures to ensure the alert protocol functions collectly. |
| `ASSUME_FPV(AckPFollowsAlertP_S, alert_rx_i.ack_p == $past(alert_tx_o.alert_p)) |
| `ASSUME_FPV(AckNFollowsAlertN_S, alert_rx_i.ack_n == $past(alert_tx_o.alert_n)) |
| `ASSUME_FPV(TriggerAlertInit_S, $stable(rst_ni) == 0 |=> alert_rx_i.ping_p == alert_rx_i.ping_n) |
| `ASSUME_FPV(PingDiffPair_S, ##2 alert_rx_i.ping_p != alert_rx_i.ping_n) |
| `endif |
| endmodule : prim_alert_sender |