blob: faa242a9c1163932d0995824b865f511d62aa3d1 [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_i signal may
// be continuously asserted, in which case the alert signalling handshake
// will be repeatedly initiated.
//
// 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
) (
input clk_i,
input rst_ni,
// native alert from the peripheral
input alert_i,
// 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, HsPhase1, HsPhase2, SigInt, Pause0, Pause1} state_e;
state_e state_d, state_q;
logic 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 ping_set_d, ping_set_q, ping_clr;
assign alert_set_d = (alert_clr) ? 1'b0 : (alert_set_q | alert_i);
assign ping_set_d = (ping_clr) ? 1'b0 : (ping_set_q | ping_event);
// 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_pd = 1'b0;
alert_nd = 1'b1;
ping_clr = 1'b0;
alert_clr = 1'b0;
unique case (state_q)
Idle: begin
// alert always takes precedence
if (alert_i || alert_set_q || ping_event || ping_set_q) begin
state_d = HsPhase1;
alert_pd = 1'b1;
alert_nd = 1'b0;
if (ping_event || ping_set_q) begin
ping_clr = 1'b1;
end else begin
alert_clr = 1'b1;
end
end
end
// waiting for ack from receiver
HsPhase1: begin
if (ack_level) begin
state_d = HsPhase2;
end else begin
alert_pd = 1'b1;
alert_nd = 1'b0;
end
end
// wait for deassertion of ack
HsPhase2: begin
if (!ack_level) begin
state_d = Pause0;
end
end
// pause cycles between back-to-back handshakes
Pause0: state_d = Pause1;
Pause1: state_d = Idle;
// 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_pd = ~alert_pq;
alert_nd = ~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_pd = 1'b0;
alert_nd = 1'b0;
ping_clr = 1'b0;
alert_clr = 1'b0;
end
end
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;
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;
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
// if alert_i is true, handshakes should be continuously repeated
`ASSERT(AlertHs_A, alert_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