// 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
