| // 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_req_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 |
| |
| `include "prim_assert.sv" |
| |
| module prim_esc_sender |
| import prim_esc_pkg::*; |
| ( |
| input clk_i, |
| input rst_ni, |
| // this triggers a ping test. keep asserted until ping_ok_o is pulsed high. |
| input ping_req_i, |
| output logic ping_ok_o, |
| // asserted if signal integrity issue detected |
| output logic integ_fail_o, |
| // escalation request signal |
| input esc_req_i, |
| // escalation / ping response |
| input esc_rx_t esc_rx_i, |
| // escalation output diff pair |
| output esc_tx_t esc_tx_o |
| ); |
| |
| ///////////////////////////////// |
| // decode differential signals // |
| ///////////////////////////////// |
| |
| logic resp, resp_n, resp_p, sigint_detected; |
| |
| // This prevents further tool optimizations of the differential signal. |
| prim_sec_anchor_buf #( |
| .Width(2) |
| ) u_prim_buf_resp ( |
| .in_i({esc_rx_i.resp_n, |
| esc_rx_i.resp_p}), |
| .out_o({resp_n, |
| resp_p}) |
| ); |
| |
| prim_diff_decode #( |
| .AsyncOn(1'b0) |
| ) u_decode_resp ( |
| .clk_i, |
| .rst_ni, |
| .diff_pi ( resp_p ), |
| .diff_ni ( resp_n ), |
| .level_o ( resp ), |
| .rise_o ( ), |
| .fall_o ( ), |
| .event_o ( ), |
| .sigint_o ( sigint_detected ) |
| ); |
| |
| ////////////// |
| // TX Logic // |
| ////////////// |
| |
| logic ping_req_d, ping_req_q; |
| logic esc_req_d, esc_req_q, esc_req_q1; |
| |
| assign ping_req_d = ping_req_i; |
| assign esc_req_d = esc_req_i; |
| |
| // ping enable is 1 cycle pulse |
| // escalation pulse is always longer than 2 cycles |
| logic esc_p; |
| assign esc_p = esc_req_i | esc_req_q | (ping_req_d & ~ping_req_q); |
| |
| // This prevents further tool optimizations of the differential signal. |
| prim_sec_anchor_buf #( |
| .Width(2) |
| ) u_prim_buf_esc ( |
| .in_i({~esc_p, |
| esc_p}), |
| .out_o({esc_tx_o.esc_n, |
| esc_tx_o.esc_p}) |
| ); |
| |
| ////////////// |
| // 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_req_i) begin |
| state_d = CheckEscRespHi; |
| end else if (ping_req_d & ~ping_req_q) 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_tx_o.esc_p || 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_tx_o.esc_p || !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_req_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_req_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_req_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_req_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_req_i; |
| end |
| end |
| default : state_d = Idle; |
| endcase |
| |
| // 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 |
| |
| // escalation takes precedence, |
| // immediately return ok in that case |
| if ((esc_req_i || esc_req_q || esc_req_q1) && ping_req_i) begin |
| ping_ok_o = 1'b1; |
| end |
| end |
| |
| /////////////// |
| // Registers // |
| /////////////// |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs |
| if (!rst_ni) begin |
| state_q <= Idle; |
| esc_req_q <= 1'b0; |
| esc_req_q1 <= 1'b0; |
| ping_req_q <= 1'b0; |
| end else begin |
| state_q <= state_d; |
| esc_req_q <= esc_req_d; |
| esc_req_q1 <= esc_req_q; |
| ping_req_q <= ping_req_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(EscPKnownO_A, esc_tx_o) |
| |
| // diff encoding of output |
| `ASSERT(DiffEncCheck_A, esc_tx_o.esc_p ^ esc_tx_o.esc_n) |
| // signal integrity check propagation |
| `ASSERT(SigIntCheck0_A, esc_rx_i.resp_p == esc_rx_i.resp_n |-> integ_fail_o) |
| // this happens in case we did not get a correct escalation response |
| `ASSERT(SigIntCheck1_A, ##1 $rose(esc_req_i) && |
| state_q inside {Idle, CheckPingResp1, CheckPingResp3} ##1 !esc_rx_i.resp_p |-> |
| integ_fail_o, clk_i, !rst_ni || (esc_rx_i.resp_p == esc_rx_i.resp_n) || |
| (state_q == Idle && resp)) |
| `ASSERT(SigIntCheck2_A, ##1 $rose(esc_req_i) && |
| state_q inside {CheckPingResp0, CheckPingResp2} ##1 esc_rx_i.resp_p |-> |
| integ_fail_o, clk_i, !rst_ni || (esc_rx_i.resp_p == esc_rx_i.resp_n) || |
| (state_q == Idle && resp)) |
| // unexpected response |
| `ASSERT(SigIntCheck3_A, state_q == Idle && resp |-> integ_fail_o) |
| // signal_int_backward_check |
| `ASSERT(SigIntBackCheck_A, integ_fail_o |-> (esc_rx_i.resp_p == esc_rx_i.resp_n) || |
| (esc_rx_i.resp_p && !(state_q == CheckEscRespHi)) || |
| (!esc_rx_i.resp_p && !(state_q == CheckEscRespLo))) |
| // state machine CheckEscRespLo and Hi as they are ideal resp signals |
| `ASSERT(StateEscRespHiCheck_A, state_q == CheckEscRespLo && esc_tx_o.esc_p && !integ_fail_o |=> |
| state_q == CheckEscRespHi) |
| `ASSERT(StateEscRespLoCheck_A, state_q == CheckEscRespHi && esc_tx_o.esc_p && !integ_fail_o |=> |
| state_q == CheckEscRespLo) |
| `ASSERT(StateEscRespHiBackCheck_A, state_q == CheckEscRespHi |-> $past(esc_tx_o.esc_p)) |
| `ASSERT(StateEscRespLoBackCheck_A, state_q == CheckEscRespLo |-> $past(esc_tx_o.esc_p)) |
| // check that escalation signal is at least 2 cycles high |
| `ASSERT(EscCheck_A, esc_req_i |-> esc_tx_o.esc_p [*2] ) |
| // escalation / ping collision |
| `ASSERT(EscPingCheck_A, esc_req_i && ping_req_i |-> ping_ok_o) |
| // check that ping request results in only a single cycle pulse |
| `ASSERT(PingCheck_A, ##1 $rose(ping_req_i) |-> esc_tx_o.esc_p ##1 !esc_tx_o.esc_p , clk_i, |
| !rst_ni || esc_req_i || integ_fail_o) |
| |
| endmodule : prim_esc_sender |