blob: f5496b622c97691c658db1c230ee0c9a4da2420e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Life cycle gating module for TL-UL protocol.
// Transactions are passed through when lc_en_i == ON.
// In all other cases (lc_en_i != ON) incoming transactions return a bus error.
//
// Note that the lc_en_i should be synchronized and buffered outside of this module using
// an instance of prim_lc_sync.
module tlul_lc_gate
import tlul_pkg::*;
import lc_ctrl_pkg::*;
#(
// Number of LC gating muxes in each direction.
// It is recommended to set this parameter to 2, which results
// in a total of 4 gating muxes.
parameter int NumGatesPerDirection = 2
) (
input clk_i,
input rst_ni,
// To host
input tl_h2d_t tl_h2d_i,
output tl_d2h_t tl_d2h_o,
// To device
output tl_h2d_t tl_h2d_o,
input tl_d2h_t tl_d2h_i,
// Flush control signaling
input flush_req_i,
output logic flush_ack_o,
// Indicates whether there are pending responses on the device side.
output logic resp_pending_o,
// LC control signal
input lc_tx_t lc_en_i,
output logic err_o
);
//////////////////
// Access Gates //
//////////////////
lc_tx_t err_en;
lc_tx_t [NumGatesPerDirection-1:0] err_en_buf;
prim_lc_sync #(
.NumCopies(NumGatesPerDirection),
.AsyncOn(0)
) u_err_en_sync (
.clk_i,
.rst_ni,
.lc_en_i(err_en),
.lc_en_o(err_en_buf)
);
tl_h2d_t tl_h2d_int [NumGatesPerDirection+1];
tl_d2h_t tl_d2h_int [NumGatesPerDirection+1];
for (genvar k = 0; k < NumGatesPerDirection; k++) begin : gen_lc_gating_muxes
// H -> D path.
prim_blanker #(
.Width($bits(tl_h2d_t))
) u_prim_blanker_h2d (
.in_i(tl_h2d_int[k]),
.en_i(lc_tx_test_false_strict(err_en_buf[k])),
.out_o(tl_h2d_int[k+1])
);
// D -> H path.
prim_blanker #(
.Width($bits(tl_d2h_t))
) u_prim_blanker_d2h (
.in_i(tl_d2h_int[k+1]),
.en_i(lc_tx_test_false_strict(err_en_buf[k])),
.out_o(tl_d2h_int[k])
);
end
// Assign signals on the device side.
assign tl_h2d_o = tl_h2d_int[NumGatesPerDirection];
assign tl_d2h_int[NumGatesPerDirection] = tl_d2h_i;
///////////////////////////
// Host Side Interposing //
///////////////////////////
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 4 -n 8 \
// -s 3379253306 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (66.67%)
// 6: |||||||||| (33.33%)
// 7: --
// 8: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 6
// Minimum Hamming weight: 3
// Maximum Hamming weight: 5
//
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 5 -n 9 \
// -s 686407169 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (60.00%)
// 6: ||||||||||||| (40.00%)
// 7: --
// 8: --
// 9: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 6
// Minimum Hamming weight: 3
// Maximum Hamming weight: 6
//
localparam int StateWidth = 9;
typedef enum logic [StateWidth-1:0] {
StActive = 9'b100100001,
StOutstanding = 9'b011100111,
StFlush = 9'b001001100,
StError = 9'b010111010,
StErrorOutstanding = 9'b100010110
} state_e;
state_e state_d, state_q;
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, StError)
logic [1:0] outstanding_txn;
logic a_ack;
logic d_ack;
assign a_ack = tl_h2d_i.a_valid & tl_d2h_o.a_ready;
assign d_ack = tl_h2d_i.d_ready & tl_d2h_o.d_valid;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
outstanding_txn <= '0;
end else if (a_ack && !d_ack) begin
outstanding_txn <= outstanding_txn + 1'b1;
end else if (d_ack && !a_ack) begin
outstanding_txn <= outstanding_txn - 1'b1;
end
end
logic block_cmd;
always_comb begin
block_cmd = '0;
state_d = state_q;
err_en = Off;
err_o = '0;
flush_ack_o = '0;
resp_pending_o = 1'b0;
unique case (state_q)
StActive: begin
if (lc_tx_test_false_loose(lc_en_i) || flush_req_i) begin
state_d = StOutstanding;
end
if (outstanding_txn != '0) begin
resp_pending_o = 1'b1;
end
end
StOutstanding: begin
block_cmd = 1'b1;
if (outstanding_txn == '0) begin
state_d = lc_tx_test_false_loose(lc_en_i) ? StError : StFlush;
end else begin
resp_pending_o = 1'b1;
end
end
StFlush: begin
block_cmd = 1'b1;
flush_ack_o = 1'b1;
if (lc_tx_test_false_loose(lc_en_i)) begin
state_d = StError;
end else if (!flush_req_i) begin
state_d = StActive;
end
end
StError: begin
err_en = On;
if (lc_tx_test_true_strict(lc_en_i)) begin
state_d = StErrorOutstanding;
end
end
StErrorOutstanding: begin
err_en = On;
block_cmd = 1'b1;
if (outstanding_txn == '0) begin
state_d = StActive;
end
end
default: begin
err_o = 1'b1;
err_en = On;
end
endcase // unique case (state_q)
end
// At the host side, we interpose the ready / valid signals so that we can return a bus error
// in case the lc signal is not set to ON. Note that this logic does not have to be duplicated
// since erroring back is considered a convenience feature so that the bus does not lock up.
tl_h2d_t tl_h2d_error;
tl_d2h_t tl_d2h_error;
always_comb begin
tl_h2d_int[0] = tl_h2d_i;
tl_d2h_o = tl_d2h_int[0];
tl_h2d_error = '0;
if (lc_tx_test_true_loose(err_en)) begin
tl_h2d_error = tl_h2d_i;
tl_d2h_o = tl_d2h_error;
end
if (block_cmd) begin
tl_d2h_o.a_ready = 1'b0;
tl_h2d_int[0].a_valid = 1'b0;
tl_h2d_error.a_valid = 1'b0;
end
end
tlul_err_resp u_tlul_err_resp (
.clk_i,
.rst_ni,
.tl_h_i(tl_h2d_error),
.tl_h_o(tl_d2h_error)
);
// Add assertion
`ASSERT(OutStandingOvfl_A, &outstanding_txn |-> ~a_ack)
endmodule : tlul_lc_gate