| // 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; |
| |
| ///////////////////////////////// |
| // 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; |
| logic send_ping; |
| |
| // 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_d && !ping_req_q; |
| assign ping_tog_pd = (send_init) ? 1'b0 : |
| (send_ping) ? ~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 = ~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 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; |
| // by default, a ping request leads to a toogle on the differential ping pair |
| send_ping = ping_rise; |
| |
| 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; |
| // suppress any toggles on the ping line while we are in the init phase. |
| send_ping = 1'b0; |
| // 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 |
| // suppress any toggles on the ping line while we are in the init phase. |
| send_ping = 1'b0; |
| if (!alert_sigint) begin |
| state_d = Pause0; |
| // If we get a ping request in this cycle, or if we realize that there is an unhandled |
| // ping request that came in during initialization (but after init_trig_i has been |
| // deasserted), we signal this to the alert sender by toggling the request line. |
| send_ping = ping_rise || ping_pending_q; |
| 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 // |
| //////////////// |
| |
| `ifdef INC_ASSERT |
| import prim_mubi_pkg::mubi4_test_false_loose; |
| `endif |
| |
| // 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, 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) |
| `ASSERT(InitReq_A, mubi4_test_true_strict(init_trig_i) && |
| !(state_q inside {InitReq, InitAckWait}) |=> send_init) |
| |
| // ping request at input -> need to see encoded ping request |
| `ASSERT(PingRequest0_A, ##1 $rose(ping_req_i) && !state_q inside {InitReq, InitAckWait} |
| |=> $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 |
| |-> |
| ##[0:1] 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 |
| (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 |