////////////////////////////////////////////////////////////////////////////////
//
// Filename:	rtl/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-2025, 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
		// }}}
	) (
		// {{{
		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	LGPKTLEN = $clog2(OPT_PACKET_LENGTH+1);
	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
		// {{{
		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
		// }}}

		// f_stall_count
		// {{{
		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
		// }}}

		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
