blob: 929e75b66147dddb5f4e536de8bb9bde86c81174 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Filename: axissafety.v
// {{{
// Project: WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose: A firewall for the AXI-stream protocol. Only handles TVALID,
// TREADY, TDATA, TLAST and TUSER signals.
//
// This module is designed to be a "bump in the stream" of the stream
// protocol--a bump that can be used to detect AXI stream errors.
// Specifically, this firewall can detect one of three errors:
//
// 1. CHANGE The stream is stalled and something changes. This
// could easily be caused by a stream overflow.
// 2. PACKET LENGTH If OPT_PACKET_LENGTH != 0, then all stream
// packets are assumed to be OPT_PACKET_LENGTH in length.
// Packets that are not OPT_PACKET_LENGTH in length will
// generate a fault.
//
// This core cannot handle dynamic packet lengths, but
// should do just fine with detecting errors in fixed
// length packets--such as an FFT might use.
// 3. TOO MANY STALLS
// If OPT_MAX_STALL != 0, and the master stalls an input more than
// OPT_MAX_STALL cycles, then a fault will be declared.
//
// 4. (Sorry--I haven't implemented a video firewall. Video images
// having TUSER set to start of frame will not be guaranteed
// video stream compliant, since TUSER might still be set to the
// wrong number.)
//
// If a fault is detected, o_fault will be set true. This is designed so
// that you can trigger an ILA off of a protocol error, and then see what
// caused the fault.
//
// If OPT_SELF_RESET is set, then o_fault will be self clearing. Once
// a fault is detected, the current packet will be flushed, and the
// firewall will become idle (holding TREADY high) until S_AXIS_TVALID
// and S_AXIS_TLAST--marking the end of a broken packet. This will
// resynchronize the core, following which packets may pass through again.
//
// If you aren't using TLAST, set it to one.
//
// If you aren't using TUSER, set the width to 1 and the input to a
// constant zero.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
// }}}
// Copyright (C) 2019-2020, Gisselquist Technology, LLC
// {{{
// This file is part of the WB2AXIP project.
//
// The WB2AXIP project contains free software and gateware, licensed under the
// Apache License, Version 2.0 (the "License"). You may not use this project,
// or this file, except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
////////////////////////////////////////////////////////////////////////////////
//
`default_nettype none
//
// }}}
module axissafety #(
// {{{
`ifdef FORMAL
parameter [0:0] F_OPT_FAULTLESS = 0,
`endif
parameter C_AXIS_DATA_WIDTH = 8,
parameter C_AXIS_USER_WIDTH = 1,
parameter OPT_MAX_STALL = 0,
parameter OPT_PACKET_LENGTH = 0,
parameter [0:0] OPT_SELF_RESET = 0,
localparam LGPKTLEN = $clog2(OPT_PACKET_LENGTH+1)
// }}}
) (
// {{{
output reg o_fault,
input wire S_AXI_ACLK,
input wire S_AXI_ARESETN,
// AXI-stream slave (incoming) port
// {{{
input wire S_AXIS_TVALID,
output wire S_AXIS_TREADY,
input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA,
input wire S_AXIS_TLAST, // = 1,
input wire [C_AXIS_USER_WIDTH-1:0] S_AXIS_TUSER, // = 0,
// }}}
// AXI-stream master (outgoing) port
// {{{
output reg M_AXIS_TVALID,
input wire M_AXIS_TREADY,
output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA,
output reg M_AXIS_TLAST,
output reg [C_AXIS_USER_WIDTH-1:0] M_AXIS_TUSER
// }}}
// }}}
);
// Parameter/register declarations
// {{{
localparam LGSTALLCOUNT = $clog2(OPT_MAX_STALL+1);
reg skdr_valid, skd_valid, skd_ready;
reg [C_AXIS_DATA_WIDTH+1+C_AXIS_USER_WIDTH-1:0] idata,skdr_data;
reg [C_AXIS_DATA_WIDTH-1:0] skd_data;
reg [C_AXIS_USER_WIDTH-1:0] skd_user;
reg skd_last;
reg r_stalled;
reg packet_fault, change_fault, stall_fault;
reg [C_AXIS_DATA_WIDTH+1+C_AXIS_USER_WIDTH-1:0] past_data;
reg [LGSTALLCOUNT-1:0] stall_count;
reg [LGPKTLEN-1:0] s_packet_counter;
reg [LGPKTLEN-1:0] m_packet_count;
reg m_end_of_packet, m_idle;
// }}}
////////////////////////////////////////////////////////////////////////
//
// Implement a skid buffer with no assumptions
// {{{
////////////////////////////////////////////////////////////////////////
//
//
always @(*)
idata = { S_AXIS_TUSER, S_AXIS_TLAST, S_AXIS_TDATA };
initial skdr_valid = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
skdr_valid <= 0;
else if (S_AXIS_TVALID && S_AXIS_TREADY && (skd_valid && !skd_ready))
skdr_valid <= 1;
else if (skd_ready)
skdr_valid <= 0;
initial skdr_data = 0; // Allow data to be optimized away if always clr
always @(posedge S_AXI_ACLK)
if (S_AXIS_TVALID && S_AXIS_TREADY)
skdr_data <= idata;
always @(*)
if (S_AXIS_TREADY)
{ skd_user, skd_last, skd_data } = idata;
else
{ skd_user, skd_last, skd_data } = skdr_data;
always @(*)
skd_valid = S_AXIS_TVALID || skdr_valid;
assign S_AXIS_TREADY = !skdr_valid;
// }}}
////////////////////////////////////////////////////////////////////////
//
// Fault detection
// {{{
////////////////////////////////////////////////////////////////////////
//
//
// r_stalled
// {{{
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
r_stalled <= 0;
else
r_stalled <= (S_AXIS_TVALID && !S_AXIS_TREADY);
// }}}
// past_data
// {{{
always @(posedge S_AXI_ACLK)
past_data <= idata;
// }}}
always @(*)
change_fault = (r_stalled && (past_data != idata));
// packet_fault
// {{{
generate if (OPT_PACKET_LENGTH != 0)
begin : S_PACKET_COUNT
// {{{
// s_packet_counter
// {{{
initial s_packet_counter = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
s_packet_counter <= 0;
else if (o_fault)
s_packet_counter <= 0;
else if (S_AXIS_TVALID && S_AXIS_TREADY)
begin
s_packet_counter <= s_packet_counter + 1;
if (S_AXIS_TLAST)
s_packet_counter <= 0;
end
// }}}
// packet_fault
// {{{
always @(*)
begin
packet_fault = (S_AXIS_TLAST
!= (s_packet_counter == OPT_PACKET_LENGTH-1));
if (!S_AXIS_TVALID)
packet_fault = 0;
end
// }}}
// }}}
end else begin
// {{{
always @(*)
packet_fault = 1'b0;
always @(*)
s_packet_counter = 0;
// Verilator lint_off UNUSED
wire unused_pkt_counter;
assign unused_pkt_counter = &{ 1'b0, s_packet_counter };
// Verilator lint_on UNUSED
// }}}
end endgenerate
// }}}
// stall_fault
// {{{
generate if (OPT_MAX_STALL != 0)
begin : S_STALL_COUNT
// {{{
initial stall_count = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
stall_count <= 0;
else if (!S_AXIS_TVALID || S_AXIS_TREADY)
stall_count <= 0;
else
stall_count <= stall_count + 1;
always @(*)
stall_fault = (stall_count > OPT_MAX_STALL);
// }}}
end else begin
// {{{
always @(*)
stall_fault = 0;
always @(*)
stall_count = 0;
// }}}
end endgenerate
// }}}
// o_fault
// {{{
initial o_fault = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
o_fault <= 0;
else if (!o_fault)
o_fault <= change_fault || stall_fault || packet_fault;
else if (OPT_SELF_RESET)
begin
if (skd_last && skd_ready && m_idle)
o_fault <= 1'b0;
end
// }}}
// }}}
////////////////////////////////////////////////////////////////////////
//
// The outgoing AXI-stream port
// {{{
////////////////////////////////////////////////////////////////////////
//
//
// skd_ready
// {{{
always @(*)
if (!o_fault && !change_fault && !stall_fault && !packet_fault)
skd_ready = !M_AXIS_TVALID || M_AXIS_TREADY;
else if (!o_fault)
skd_ready = 1'b0;
else
skd_ready = 1'b1;
// }}}
// m_end_of_packet, m_packet_count
// {{{
generate if (OPT_PACKET_LENGTH != 0)
begin
// {{{
initial m_packet_count = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
m_packet_count <= 0;
else if (M_AXIS_TVALID && M_AXIS_TREADY)
begin
m_packet_count <= m_packet_count + 1;
if (M_AXIS_TLAST)
m_packet_count <= 0;
end
// initial m_end_of_packet = 0;
// always @(posedge S_AXI_ACLK)
// if (!S_AXI_ARESETN)
// m_end_of_packet <= 0;
// else if (M_AXIS_TVALID && M_AXIS_TREADY)
// m_end_of_packet <= (m_packet_count >= (OPT_PACKET_LENGTH-3));
always @(*)
begin
m_end_of_packet = (m_packet_count == (OPT_PACKET_LENGTH-1));
if (M_AXIS_TVALID)
m_end_of_packet = (m_packet_count == (OPT_PACKET_LENGTH-2));
end
// }}}
end else begin
// {{{
always @(*)
m_end_of_packet = 1'b1;
always @(*)
m_packet_count = 0;
// Verilator lint_off UNUSED
wire unused_mpkt_counter;
assign unused_mpkt_counter = &{ 1'b0, m_packet_count };
// Verilator lint_on UNUSED
// }}}
end endgenerate
initial m_idle = 1;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
m_idle <= 1;
else if (!M_AXIS_TVALID || M_AXIS_TREADY)
begin
if (M_AXIS_TVALID && M_AXIS_TLAST)
m_idle <= o_fault || (!skd_valid || !skd_ready);
else if (m_idle && !o_fault)
m_idle <= (!skd_valid || !skd_ready);
end
// }}}
// M_AXIS_TVALID
// {{{
initial M_AXIS_TVALID = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
M_AXIS_TVALID <= 1'b0;
else if (!M_AXIS_TVALID || M_AXIS_TREADY)
begin
M_AXIS_TVALID <= 1'b0;
if (!o_fault && skd_ready)
M_AXIS_TVALID <= skd_valid;
else if (o_fault)
begin
if (m_idle)
M_AXIS_TVALID <= 1'b0;
else if (!M_AXIS_TVALID || M_AXIS_TLAST)
M_AXIS_TVALID <= 1'b0;
else
M_AXIS_TVALID <= 1'b1;
end
end
// }}}
// M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA
// {{{
initial { M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA } <= 0;
always @(posedge S_AXI_ACLK)
begin
if (!M_AXIS_TVALID || M_AXIS_TREADY)
begin
if (o_fault || change_fault || packet_fault)
begin
{ M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA } <= 0;
// if (!M_AXIS_TLAST)
// M_AXIS_TLAST <= m_end_of_packet;
end else
{ M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA }
<= { skd_user, skd_last, skd_data };
if (OPT_PACKET_LENGTH != 0)
M_AXIS_TLAST <= m_end_of_packet;
else if (o_fault && !m_idle)
M_AXIS_TLAST <= 1'b1;
end
if (!S_AXI_ARESETN)
M_AXIS_TLAST <= 0;
end
// }}}
// }}}
// Make Verilator happy
// {{{
// Verilator lint_off UNUSED
wire unused = &{ 1'b0 };
// Verilator lint_on UNUSED
// }}}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Formal properties
// {{{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
`ifdef FORMAL
reg f_past_valid;
reg [LGPKTLEN-1:0] fm_packet_counter;
reg [LGSTALLCOUNT-1:0] fm_stall_count;
initial f_past_valid = 1'b0;
always @(posedge S_AXI_ACLK)
f_past_valid <= 1'b1;
always @(*)
if (!f_past_valid)
assume(!S_AXI_ARESETN);
// Stability
// {{{
always @(posedge S_AXI_ACLK)
if (!f_past_valid || !$past(S_AXI_ARESETN))
assert(!M_AXIS_TVALID);
else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY))
begin
assert(M_AXIS_TVALID);
assert($stable(M_AXIS_TDATA));
assert($stable(M_AXIS_TLAST));
assert($stable(M_AXIS_TUSER));
end
// }}}
// Packet counter, assertions on the output
// {{{
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
fm_packet_counter <= 0;
else if (M_AXIS_TVALID && M_AXIS_TREADY)
begin
fm_packet_counter <= fm_packet_counter + 1;
if (M_AXIS_TLAST)
fm_packet_counter <= 0;
end
always @(*)
if (S_AXI_ARESETN && OPT_PACKET_LENGTH != 0)
begin
assert(fm_packet_counter < OPT_PACKET_LENGTH);
assert(M_AXIS_TLAST == (fm_packet_counter == OPT_PACKET_LENGTH-1));
assert(m_packet_count == fm_packet_counter);
if (!o_fault)
begin
if (skdr_valid && skd_last)
begin
assert(s_packet_counter == 0);
assert(m_packet_count == OPT_PACKET_LENGTH-2);
end else if (M_AXIS_TVALID && M_AXIS_TLAST)
begin
assert(s_packet_counter == (skdr_valid ? 1:0));
assert(m_packet_count == OPT_PACKET_LENGTH-1);
end else
assert(s_packet_counter == m_packet_count
+ (skdr_valid ? 1:0)
+ (M_AXIS_TVALID ? 1:0));
end
end
always @(*)
if (S_AXI_ARESETN && !o_fault && OPT_PACKET_LENGTH > 0)
assert(s_packet_counter < OPT_PACKET_LENGTH);
always @(*)
if (S_AXI_ARESETN && OPT_PACKET_LENGTH > 0)
assert(m_idle == (m_packet_count == 0 && !M_AXIS_TVALID));
// }}}
// Input stall counting
// {{{
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
fm_stall_count <= 0;
else if (!S_AXIS_TVALID || S_AXIS_TREADY)
fm_stall_count <= 0;
always @(*)
if (S_AXI_ARESETN && OPT_MAX_STALL > 0)
assert(fm_stall_count < OPT_MAX_STALL);
// }}}
generate if (F_OPT_FAULTLESS)
begin
// {{{
// Stall and stability properties
// {{{
always @(posedge S_AXI_ACLK)
if (!f_past_valid || !$past(S_AXI_ARESETN))
assume(!S_AXIS_TVALID);
else if ($past(S_AXIS_TVALID && !S_AXIS_TREADY))
begin
assume(S_AXIS_TVALID);
assume($stable(S_AXIS_TDATA));
assume($stable(S_AXIS_TLAST));
assume($stable(S_AXIS_TUSER));
end
// }}}
// f_packet_counter
// {{{
generate if (OPT_PACKET_LENGTH != 0)
begin
// {{{
reg [LGPKTLEN-1:0] fs_packet_counter;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
fs_packet_counter <= 0;
else if (S_AXIS_TVALID && S_AXIS_TREADY)
begin
fs_packet_counter <= fs_packet_counter + 1;
if (S_AXIS_TLAST)
fs_packet_counter <= 0;
end
always @(*)
if (S_AXI_ARESETN)
begin
assert(s_packet_counter == fs_packet_counter);
if (skdr_valid && skd_last)
assert(s_packet_counter == 0);
else if (M_AXIS_TVALID && M_AXIS_TLAST)
assert(s_packet_counter == (skdr_valid ? 1:0));
else // if (!M_AXIS_TVALID && !M_AXIS_TLAST)
assert(s_packet_counter
== fm_packet_counter
+ (skdr_valid ? 1:0)
+ (M_AXIS_TVALID ? 1:0));
end
always @(*)
if (S_AXI_ARESETN && S_AXIS_TVALID)
assume(S_AXIS_TLAST == (fs_packet_counter == OPT_PACKET_LENGTH-1));
// }}}
end endgenerate
// }}}
// f_stall_count
// {{{
generate if (OPT_MAX_STALL != 0)
begin
// {{{
reg [LGSTALLCOUNT-1:0] f_stall_count;
initial stall_count = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
f_stall_count <= 0;
else if (!S_AXIS_TVALID || S_AXIS_TREADY)
f_stall_count <= 0;
else
f_stall_count <= f_stall_count + 1;
always @(*)
if (S_AXI_ARESETN)
assert(stall_count == f_stall_count);
always @(*)
assume(f_stall_count <= OPT_MAX_STALL);
// }}}
end endgenerate
// }}}
always @(*)
assert(!o_fault);
// }}}
end endgenerate
////////////////////////////////////////////////////////////////////////
//
// Cover checks
// {{{
////////////////////////////////////////////////////////////////////////
//
generate if (F_OPT_FAULTLESS)
begin
end else begin
reg cvr_stall_fault, cvr_packet_fault, cvr_change_fault,
cvr_last;
initial cvr_last = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
cvr_last <= 1'b0;
else if (o_fault && $stable(o_fault)
&& $rose(M_AXIS_TVALID && M_AXIS_TLAST))
cvr_last <= 1'b1;
initial cvr_change_fault = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
cvr_change_fault <= 1'b0;
else if (!o_fault && change_fault && !packet_fault && !stall_fault)
cvr_change_fault <= 1'b1;
else if (!o_fault)
cvr_change_fault <= 1'b0;
if (OPT_PACKET_LENGTH > 0)
begin
initial cvr_packet_fault = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
cvr_packet_fault <= 1'b0;
else if (!o_fault && !change_fault && packet_fault && !stall_fault)
cvr_packet_fault <= 1'b1;
else if (!o_fault)
cvr_packet_fault <= 1'b0;
always @(posedge S_AXI_ACLK)
if (S_AXI_ARESETN && $past(S_AXI_ARESETN))
begin
cover($fell(o_fault) && cvr_packet_fault);
cover($fell(o_fault) && cvr_packet_fault && cvr_last);
end
end
if (OPT_MAX_STALL > 0)
begin
initial cvr_stall_fault = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
cvr_stall_fault <= 1'b0;
else if (!o_fault && !change_fault && !packet_fault && stall_fault)
cvr_stall_fault <= 1'b1;
else if (!o_fault)
cvr_stall_fault <= 1'b0;
always @(posedge S_AXI_ACLK)
if (S_AXI_ARESETN && $past(S_AXI_ARESETN))
begin
cover($fell(o_fault) && cvr_stall_fault);
cover($fell(o_fault) && cvr_stall_fault && cvr_last);
end
end
always @(posedge S_AXI_ACLK)
if (S_AXI_ARESETN && $past(S_AXI_ARESETN))
begin
cover($fell(o_fault));
cover($fell(o_fault) && cvr_change_fault);
cover($fell(o_fault) && cvr_change_fault && cvr_last);
end
end endgenerate
// }}}
`endif
// }}}
endmodule