blob: 24aeb4ca0621f3f674a02eca5373dde5316db74f [file] [log] [blame]
// 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
`include "prim_assert.sv"
module prim_esc_receiver
import prim_esc_pkg::*;
#(
// The number of escalation severities. Should be set to the Alert Handler's N_ESC_SEV when this
// primitive is instantiated.
parameter int N_ESC_SEV = 4,
// The width of the Alert Handler's ping counter. Should be set to the Alert Handler's PING_CNT_DW
// when this primitive is instantiated.
parameter int PING_CNT_DW = 16,
// This counter monitors incoming ping requests and auto-escalates if the alert handler
// ceases to send them regularly. The maximum number of cycles between subsequent ping requests
// is N_ESC_SEV x (2 x 2 x 2**PING_CNT_DW), see also implementation of the ping timer
// (alert_handler_ping_timer.sv). The timeout counter below uses a timeout that is 4x larger than
// that in order to incorporate some margin.
//
// Do NOT modify this counter value, when instantiating it in the design. It is only exposed to
// reduce the state space in the FPV testbench.
localparam int MarginFactor = 4,
localparam int NumWaitCounts = 2,
localparam int NumTimeoutCounts = 2,
parameter int TimeoutCntDw = $clog2(MarginFactor) +
$clog2(N_ESC_SEV) +
$clog2(NumWaitCounts) +
$clog2(NumTimeoutCounts) +
PING_CNT_DW
) (
input clk_i,
input rst_ni,
// escalation enable
output logic esc_req_o,
// escalation / ping response
output esc_rx_t esc_rx_o,
// escalation output diff pair
input esc_tx_t esc_tx_i
);
/////////////////////////////////
// decode differential signals //
/////////////////////////////////
logic esc_level, esc_p, esc_n, sigint_detected;
// This prevents further tool optimizations of the differential signal.
prim_buf #(
.Width(2)
) u_prim_buf_esc (
.in_i({esc_tx_i.esc_n,
esc_tx_i.esc_p}),
.out_o({esc_n,
esc_p})
);
prim_diff_decode #(
.AsyncOn(1'b0)
) u_decode_esc (
.clk_i,
.rst_ni,
.diff_pi ( esc_p ),
.diff_ni ( esc_n ),
.level_o ( esc_level ),
.rise_o ( ),
.fall_o ( ),
.event_o ( ),
.sigint_o ( sigint_detected )
);
////////////////////////////////////////////
// Ping Monitor Counter / Auto Escalation //
////////////////////////////////////////////
// The timeout counter is kicked off when the first ping occurs, and subsequent pings reset
// the counter to 1. The counter keeps on counting when it is nonzero, and saturates when it
// has reached its maximum (this state is terminal).
logic ping_en, timeout_cnt_error;
logic timeout_cnt_set, timeout_cnt_en;
logic [TimeoutCntDw-1:0] timeout_cnt;
assign timeout_cnt_set = (ping_en && !(&timeout_cnt));
assign timeout_cnt_en = ((timeout_cnt > '0) && !(&timeout_cnt));
prim_count #(
.Width(TimeoutCntDw),
// The escalation receiver behaves differently than other comportable IP. I.e., instead of
// sending out an alert signal, this condition is handled internally in the alert handler.
.EnableAlertTriggerSVA(0)
) u_prim_count (
.clk_i,
.rst_ni,
.clr_i(1'b0),
.set_i(timeout_cnt_set),
.set_cnt_i(TimeoutCntDw'(1)),
.incr_en_i(timeout_cnt_en),
.decr_en_i(1'b0),
.step_i(TimeoutCntDw'(1)),
.cnt_o(timeout_cnt),
.cnt_next_o(),
.err_o(timeout_cnt_error)
);
// Escalation is asserted if
// - requested via the escalation sender/receiver path,
// - the ping monitor timeout is reached,
// - the two ping monitor counters are in an inconsistent state.
logic esc_req;
prim_sec_anchor_buf #(
.Width(1)
) u_prim_buf_esc_req (
.in_i(esc_req || (&timeout_cnt) || timeout_cnt_error),
.out_o(esc_req_o)
);
/////////////////
// 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;
logic resp_nd, resp_nq;
// This prevents further tool optimizations of the differential signal.
prim_sec_anchor_flop #(
.Width(2),
.ResetValue(2'b10)
) u_prim_flop_esc (
.clk_i,
.rst_ni,
.d_i({resp_nd, resp_pd}),
.q_o({resp_nq, resp_pq})
);
assign esc_rx_o.resp_p = resp_pq;
assign esc_rx_o.resp_n = resp_nq;
always_comb begin : p_fsm
// default
state_d = state_q;
resp_pd = 1'b0;
resp_nd = 1'b1;
esc_req = 1'b0;
ping_en = 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 = ~resp_pq;
resp_nd = resp_pq;
end
end
// we decide here whether this is only a ping request or
// whether this is an escalation enable
Check: begin
state_d = PingResp;
resp_pd = ~resp_pq;
resp_nd = resp_pq;
if (esc_level) begin
state_d = EscResp;
esc_req = 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 = ~resp_pq;
resp_nd = resp_pq;
ping_en = 1'b1;
if (esc_level) begin
state_d = EscResp;
esc_req = 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_req = 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;
esc_req = 1'b1;
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
///////////////
// Registers //
///////////////
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
if (!rst_ni) begin
state_q <= Idle;
end else begin
state_q <= state_d;
end
end
////////////////
// assertions //
////////////////
// check whether all outputs have a good known state after reset
`ASSERT_KNOWN(EscEnKnownO_A, esc_req_o)
`ASSERT_KNOWN(RespPKnownO_A, esc_rx_o)
`ASSERT(SigIntCheck0_A, esc_tx_i.esc_p == esc_tx_i.esc_n |=> esc_rx_o.resp_p == esc_rx_o.resp_n)
`ASSERT(SigIntCheck1_A, esc_tx_i.esc_p == esc_tx_i.esc_n |=> state_q == SigInt)
// auto-escalate in case of signal integrity issue
`ASSERT(SigIntCheck2_A, esc_tx_i.esc_p == esc_tx_i.esc_n |=> esc_req_o)
// correct diff encoding
`ASSERT(DiffEncCheck_A, esc_tx_i.esc_p ^ esc_tx_i.esc_n |=> esc_rx_o.resp_p ^ esc_rx_o.resp_n)
// disable in case of signal integrity issue
`ASSERT(PingRespCheck_A, state_q == Idle ##1 $rose(esc_tx_i.esc_p) ##1 $fell(esc_tx_i.esc_p) |->
$rose(esc_rx_o.resp_p) ##1 $fell(esc_rx_o.resp_p),
clk_i, !rst_ni || (esc_tx_i.esc_p == esc_tx_i.esc_n))
// escalation response needs to continuously toggle
`ASSERT(EscRespCheck_A, ##1 esc_tx_i.esc_p && $past(esc_tx_i.esc_p) &&
(esc_tx_i.esc_p ^ esc_tx_i.esc_n) && $past(esc_tx_i.esc_p ^ esc_tx_i.esc_n)
|=> esc_rx_o.resp_p != $past(esc_rx_o.resp_p))
// detect escalation pulse
`ASSERT(EscEnCheck_A,
esc_tx_i.esc_p && (esc_tx_i.esc_p ^ esc_tx_i.esc_n) && state_q != SigInt
##1 esc_tx_i.esc_p && (esc_tx_i.esc_p ^ esc_tx_i.esc_n) |-> esc_req_o)
// make sure the counter does not wrap around
`ASSERT(EscCntWrap_A, &timeout_cnt |=> timeout_cnt != 0)
// if the counter expires, escalation should be asserted
`ASSERT(EscCntEsc_A, &timeout_cnt |-> esc_req_o)
endmodule : prim_esc_receiver