|  | // 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_p/n and | 
|  | // ack_p/n. 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_req_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_req_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 | 
|  |  | 
|  | `include "prim_assert.sv" | 
|  |  | 
|  | module prim_alert_receiver | 
|  | import prim_alert_pkg::*; | 
|  | import prim_mubi_pkg::mubi4_t; | 
|  | #( | 
|  | // enables additional synchronization logic | 
|  | parameter bit AsyncOn = 1'b0 | 
|  | ) ( | 
|  | input             clk_i, | 
|  | input             rst_ni, | 
|  | // if set to lc_ctrl_pkg::On, this triggers the in-band alert channel | 
|  | // reset, which resets both the sender and receiver FSMs into IDLE. | 
|  | input mubi4_t     init_trig_i, | 
|  | // this triggers a ping test. keep asserted | 
|  | // until ping_ok_o is asserted. | 
|  | input             ping_req_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 input diff pair and ack diff pair | 
|  | output alert_rx_t alert_rx_o, | 
|  | // alert output diff pair | 
|  | input alert_tx_t  alert_tx_i | 
|  | ); | 
|  |  | 
|  | import prim_mubi_pkg::mubi4_test_true_strict; | 
|  | import prim_mubi_pkg::mubi4_test_false_loose; | 
|  |  | 
|  | ///////////////////////////////// | 
|  | // decode differential signals // | 
|  | ///////////////////////////////// | 
|  | logic alert_level, alert_sigint, alert_p, alert_n; | 
|  |  | 
|  | // This prevents further tool optimizations of the differential signal. | 
|  | prim_sec_anchor_buf #( | 
|  | .Width(2) | 
|  | ) u_prim_buf_in ( | 
|  | .in_i({alert_tx_i.alert_n, | 
|  | alert_tx_i.alert_p}), | 
|  | .out_o({alert_n, | 
|  | alert_p}) | 
|  | ); | 
|  |  | 
|  | prim_diff_decode #( | 
|  | .AsyncOn(AsyncOn) | 
|  | ) u_decode_alert ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .diff_pi  ( alert_p            ), | 
|  | .diff_ni  ( alert_n            ), | 
|  | .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 [2:0] {Idle, HsAckWait, Pause0, Pause1, InitReq, InitAckWait} state_e; | 
|  | state_e state_d, state_q; | 
|  | logic ping_rise; | 
|  | logic ping_tog_pd, ping_tog_pq, ping_tog_dn, ping_tog_nq; | 
|  | logic ack_pd, ack_pq, ack_dn, ack_nq; | 
|  | logic ping_req_d, ping_req_q; | 
|  | logic ping_pending_d, ping_pending_q; | 
|  | logic send_init; | 
|  |  | 
|  | // signal ping request upon positive transition on ping_req_i | 
|  | // signalling is performed by a level change event on the diff output | 
|  | assign ping_req_d  = ping_req_i; | 
|  | assign ping_rise   = ping_req_i && !ping_req_q; | 
|  | assign ping_tog_pd = (send_init) ? 1'b0         : | 
|  | (ping_rise) ? ~ping_tog_pq : ping_tog_pq; | 
|  |  | 
|  | // in-band reset is performed by sending out an integrity error on purpose. | 
|  | assign ack_dn      = (send_init) ? ack_pd      : ~ack_pd; | 
|  | assign ping_tog_dn = (send_init) ? ping_tog_pd : ~ping_tog_pd; | 
|  |  | 
|  | // This prevents further tool optimizations of the differential signal. | 
|  | prim_sec_anchor_flop #( | 
|  | .Width     (2), | 
|  | .ResetValue(2'b10) | 
|  | ) u_prim_generic_flop_ack ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .d_i({ack_dn, | 
|  | ack_pd}), | 
|  | .q_o({ack_nq, | 
|  | ack_pq}) | 
|  | ); | 
|  |  | 
|  | prim_sec_anchor_flop #( | 
|  | .Width     (2), | 
|  | .ResetValue(2'b10) | 
|  | ) u_prim_generic_flop_ping ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .d_i({ping_tog_dn, | 
|  | ping_tog_pd}), | 
|  | .q_o({ping_tog_nq, | 
|  | ping_tog_pq}) | 
|  | ); | 
|  |  | 
|  | // 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_req_i & ping_pending_q); | 
|  |  | 
|  | // diff pair outputs | 
|  | assign alert_rx_o.ack_p = ack_pq; | 
|  | assign alert_rx_o.ack_n = ack_nq; | 
|  |  | 
|  | assign alert_rx_o.ping_p = ping_tog_pq; | 
|  | assign alert_rx_o.ping_n = ping_tog_nq; | 
|  |  | 
|  | // 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_pd       = 1'b0; | 
|  | ping_ok_o    = 1'b0; | 
|  | integ_fail_o = 1'b0; | 
|  | alert_o      = 1'b0; | 
|  | send_init    = 1'b0; | 
|  |  | 
|  | unique case (state_q) | 
|  | Idle: begin | 
|  | // wait for handshake to be initiated | 
|  | if (alert_level) begin | 
|  | state_d = HsAckWait; | 
|  | ack_pd  = 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_pd = 1'b1; | 
|  | end | 
|  | end | 
|  | // pause cycles between back-to-back handshakes | 
|  | Pause0: state_d = Pause1; | 
|  | Pause1: state_d = Idle; | 
|  | // this state is only reached if an in-band reset is | 
|  | // requested via the low-power logic. | 
|  | InitReq: begin | 
|  | // we deliberately place a sigint error on the ack and ping lines in this case. | 
|  | send_init = 1'b1; | 
|  | // As long as init req is asserted, we remain in this state and acknowledge all incoming | 
|  | // ping requests. As soon as the init request is dropped however, ping requests are not | 
|  | // acked anymore such that the ping mechanism can also flag alert channels that got stuck | 
|  | // in the initialization sequence. | 
|  | if (mubi4_test_true_strict(init_trig_i)) begin | 
|  | ping_ok_o = ping_pending_q; | 
|  | // the sender will respond to the sigint error above with a sigint error on the alert lines. | 
|  | // hence we treat the alert_sigint like an acknowledgement in this case. | 
|  | end else if (alert_sigint) begin | 
|  | state_d = InitAckWait; | 
|  | end | 
|  | end | 
|  | // We get here if the sender has responded with alert_sigint, and init_trig_i==lc_ctrl_pkg::On | 
|  | // has been deasserted. At this point, we need to wait for the alert_sigint to drop again | 
|  | // before resuming normal operation. | 
|  | InitAckWait: begin | 
|  | if (!alert_sigint) begin | 
|  | state_d = Pause0; | 
|  | end | 
|  | end | 
|  | default: state_d = Idle; | 
|  | endcase | 
|  |  | 
|  | // once the initialization sequence has been triggered, | 
|  | // overrides are not allowed anymore until the initialization has been completed. | 
|  | if (!(state_q inside {InitReq, InitAckWait})) begin | 
|  | // in this case, abort and jump into the initialization sequence | 
|  | if (mubi4_test_true_strict(init_trig_i)) begin | 
|  | state_d      = InitReq; | 
|  | ack_pd       = 1'b0; | 
|  | ping_ok_o    = 1'b0; | 
|  | integ_fail_o = 1'b0; | 
|  | alert_o      = 1'b0; | 
|  | send_init    = 1'b1; | 
|  | // if we're not busy with an init request, we clamp down all outputs | 
|  | // and indicate an integrity failure. | 
|  | end else if (alert_sigint) begin | 
|  | state_d      = Idle; | 
|  | ack_pd       = 1'b0; | 
|  | ping_ok_o    = 1'b0; | 
|  | integ_fail_o = 1'b1; | 
|  | alert_o      = 1'b0; | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg | 
|  | if (!rst_ni) begin | 
|  | // Reset into the init request so that an alert handler reset implicitly | 
|  | // triggers an in-band reset of all alert channels. | 
|  | state_q        <= InitReq; | 
|  | ping_req_q     <= 1'b0; | 
|  | ping_pending_q <= 1'b0; | 
|  | end else begin | 
|  | state_q        <= state_d; | 
|  | ping_req_q     <= ping_req_d; | 
|  | ping_pending_q <= ping_pending_d; | 
|  | end | 
|  | end | 
|  |  | 
|  |  | 
|  | //////////////// | 
|  | // assertions // | 
|  | //////////////// | 
|  |  | 
|  | // check whether all outputs have a good known state after reset | 
|  | `ASSERT_KNOWN(PingOkKnownO_A, ping_ok_o) | 
|  | `ASSERT_KNOWN(IntegFailKnownO_A, integ_fail_o) | 
|  | `ASSERT_KNOWN(AlertKnownO_A, alert_o) | 
|  | `ASSERT_KNOWN(PingPKnownO_A, alert_rx_o) | 
|  |  | 
|  | // check encoding of outgoing diffpairs. note that during init, the outgoing diffpairs are | 
|  | // supposed to be incorrectly encoded on purpose. | 
|  | // shift sequence two cycles to the right to avoid reset effects. | 
|  | `ASSERT(PingDiffOk_A, ##2 $past(send_init) ^ alert_rx_o.ping_p ^ alert_rx_o.ping_n) | 
|  | `ASSERT(AckDiffOk_A, ##2 $past(send_init) ^ alert_rx_o.ack_p ^ alert_rx_o.ack_n) | 
|  | // ping request at input -> need to see encoded ping request | 
|  | `ASSERT(PingRequest0_A, ##1 $rose(ping_req_i) && !send_init |=> $changed(alert_rx_o.ping_p)) | 
|  | // ping response implies it has been requested | 
|  | `ASSERT(PingResponse0_A, ping_ok_o |-> ping_pending_q) | 
|  | // correctly latch ping request | 
|  | `ASSERT(PingPending_A, ##1 $rose(ping_req_i) |=> ping_pending_q) | 
|  |  | 
|  | if (AsyncOn) begin : gen_async_assert | 
|  | // signal integrity check propagation | 
|  | `ASSERT(SigInt_A, | 
|  | alert_tx_i.alert_p == alert_tx_i.alert_n [*2] ##2 | 
|  | !(state_q inside {InitReq, InitAckWait}) && | 
|  | mubi4_test_false_loose(init_trig_i) | 
|  | |-> | 
|  | integ_fail_o) | 
|  | // 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_tx_i.alert_p) && | 
|  | (alert_tx_i.alert_p ^ alert_tx_i.alert_n) ##2 | 
|  | state_q == Idle && ping_pending_q | 
|  | |-> | 
|  | ping_ok_o, | 
|  | clk_i, !rst_ni || integ_fail_o || mubi4_test_true_strict(init_trig_i)) | 
|  | // alert | 
|  | `ASSERT(Alert_A, | 
|  | ##1 $rose(alert_tx_i.alert_p) && | 
|  | (alert_tx_i.alert_p ^ alert_tx_i.alert_n) ##2 | 
|  | state_q == Idle && | 
|  | !ping_pending_q | 
|  | |-> | 
|  | alert_o, | 
|  | clk_i, !rst_ni || integ_fail_o || mubi4_test_true_strict(init_trig_i)) | 
|  | end else begin : gen_sync_assert | 
|  | // signal integrity check propagation | 
|  | `ASSERT(SigInt_A, | 
|  | alert_tx_i.alert_p == alert_tx_i.alert_n && | 
|  | !(state_q inside {InitReq, InitAckWait}) && | 
|  | mubi4_test_false_loose(init_trig_i) | 
|  | |-> | 
|  | integ_fail_o) | 
|  | // ping response | 
|  | `ASSERT(PingResponse1_A, | 
|  | ##1 $rose(alert_tx_i.alert_p) && | 
|  | state_q == Idle && | 
|  | ping_pending_q | 
|  | |-> | 
|  | ping_ok_o, | 
|  | clk_i, !rst_ni || integ_fail_o || mubi4_test_true_strict(init_trig_i)) | 
|  | // alert | 
|  | `ASSERT(Alert_A, | 
|  | ##1 $rose(alert_tx_i.alert_p) && | 
|  | state_q == Idle && | 
|  | !ping_pending_q | 
|  | |-> | 
|  | alert_o, | 
|  | clk_i, !rst_ni || integ_fail_o || mubi4_test_true_strict(init_trig_i)) | 
|  | end | 
|  |  | 
|  | // check in-band init request is always accepted | 
|  | `ASSERT(InBandInitRequest_A, | 
|  | mubi4_test_true_strict(init_trig_i) && | 
|  | state_q != InitAckWait | 
|  | |=> | 
|  | state_q == InitReq) | 
|  | // check in-band init sequence moves FSM into IDLE state | 
|  | `ASSERT(InBandInitSequence_A, | 
|  | (state_q == InitReq && | 
|  | mubi4_test_true_strict(init_trig_i) [*1:$]) ##1 | 
|  | (alert_sigint && | 
|  | mubi4_test_false_loose(init_trig_i)) [*1:$] ##1 | 
|  | (!alert_sigint && | 
|  | mubi4_test_false_loose(init_trig_i)) [*3] | 
|  | |=> | 
|  | state_q == Idle) | 
|  | // check there are no spurious alerts during init | 
|  | `ASSERT(NoSpuriousAlertsDuringInit_A, | 
|  | mubi4_test_true_strict(init_trig_i) || | 
|  | (state_q inside {InitReq, InitAckWait}) | 
|  | |-> | 
|  | !alert_o) | 
|  | // check that there are no spurious ping OKs | 
|  | `ASSERT(NoSpuriousPingOksDuringInit_A, | 
|  | (mubi4_test_true_strict(init_trig_i) || | 
|  | (state_q inside {InitReq, InitAckWait})) && | 
|  | !ping_pending_q | 
|  | |-> | 
|  | !ping_ok_o) | 
|  | // check ping request is bypassed when in init state | 
|  | `ASSERT(PingOkBypassDuringInit_A, | 
|  | $rose(ping_req_i) ##1 | 
|  | state_q == InitReq && | 
|  | mubi4_test_true_strict(init_trig_i) | 
|  | |-> | 
|  | ping_ok_o) | 
|  |  | 
|  | endmodule : prim_alert_receiver |