blob: c1b385b74d53a5510cf8eccd8255ab5a890a91b2 [file] [log] [blame]
// 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;
prim_diff_decode #(
.AsyncOn(AsyncOn)
) i_decode_ping (
.clk_i,
.rst_ni,
.diff_pi ( alert_rx_i.ping_p ),
.diff_ni ( alert_rx_i.ping_n ),
.level_o ( ),
.rise_o ( ),
.fall_o ( ),
.event_o ( ping_event ),
.sigint_o ( ping_sigint )
);
logic ack_sigint, ack_level;
prim_diff_decode #(
.AsyncOn(AsyncOn)
) i_decode_ack (
.clk_i,
.rst_ni,
.diff_pi ( alert_rx_i.ack_p ),
.diff_ni ( alert_rx_i.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,
SigInt,
Pause0,
Pause1
} state_e;
state_e state_d, state_q;
logic alert_p, alert_n, 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.
assign alert_req_trigger = alert_req_i | 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_p = 1'b0;
alert_n = 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_p = 1'b1;
alert_n = 1'b0;
end
end
// waiting for ack from receiver
AlertHsPhase1: begin
if (ack_level) begin
state_d = AlertHsPhase2;
end else begin
alert_p = 1'b1;
alert_n = 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_p = 1'b1;
alert_n = 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
// we have a signal integrity issue at one of
// the incoming diff pairs. this condition is
// signalled by setting the output diffpair
// to the same value and continuously toggling
// them.
SigInt: begin
state_d = Idle;
if (sigint_detected) begin
state_d = SigInt;
alert_p = ~alert_pq;
alert_n = ~alert_pq;
end
end
// catch parasitic states
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;
alert_p = 1'b0;
alert_n = 1'b0;
ping_clr = 1'b0;
alert_clr = 1'b0;
end
end
// This prevents further tool optimizations of the differential signal.
prim_buf u_prim_buf_p (
.in_i(alert_p),
.out_o(alert_pd)
);
prim_buf u_prim_buf_n (
.in_i(alert_n),
.out_o(alert_nd)
);
always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg
if (!rst_ni) begin
state_q <= Idle;
alert_pq <= 1'b0;
alert_nq <= 1'b1;
alert_set_q <= 1'b0;
alert_test_set_q <= 1'b0;
ping_set_q <= 1'b0;
end else begin
state_q <= state_d;
alert_pq <= alert_pd;
alert_nq <= alert_nd;
alert_set_q <= alert_set_d;
alert_test_set_q <= alert_test_set_d;
ping_set_q <= ping_set_d;
end
end
////////////////
// assertions //
////////////////
// check whether all outputs have a good known state after reset
`ASSERT_KNOWN(AlertPKnownO_A, alert_tx_o)
if (AsyncOn) begin : gen_async_assert
// check propagation of sigint issues to output within three cycles
`ASSERT(SigIntPing_A, alert_rx_i.ping_p == alert_rx_i.ping_n [*2] |->
##3 alert_tx_o.alert_p == alert_tx_o.alert_n)
`ASSERT(SigIntAck_A, alert_rx_i.ack_p == alert_rx_i.ack_n [*2] |->
##3 alert_tx_o.alert_p == alert_tx_o.alert_n)
// 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 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
// check propagation of sigint issues to output within one cycle
`ASSERT(SigIntPing_A, alert_rx_i.ping_p == alert_rx_i.ping_n |=>
alert_tx_o.alert_p == alert_tx_o.alert_n)
`ASSERT(SigIntAck_A, alert_rx_i.ack_p == alert_rx_i.ack_n |=>
alert_tx_o.alert_p == alert_tx_o.alert_n)
// 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))
endmodule : prim_alert_sender