summaryrefslogtreecommitdiff
path: root/rtl/wb2axip/wbsafety.v
diff options
context:
space:
mode:
Diffstat (limited to 'rtl/wb2axip/wbsafety.v')
-rw-r--r--rtl/wb2axip/wbsafety.v587
1 files changed, 587 insertions, 0 deletions
diff --git a/rtl/wb2axip/wbsafety.v b/rtl/wb2axip/wbsafety.v
new file mode 100644
index 0000000..2636f11
--- /dev/null
+++ b/rtl/wb2axip/wbsafety.v
@@ -0,0 +1,587 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Filename: wbsafety.v
+// {{{
+// Project: WB2AXIPSP: bus bridges and other odds and ends
+//
+// Purpose: A WB bus fault isolator. This core will isolate any downstream
+// WB slave faults from the upstream channel. It sits as a bump
+// in the wire between upstream and downstream channels, and so it will
+// consume two clocks--slowing down the slave, but potentially allowing
+// the developer to recover in case of a fault.
+//
+// This core is configured by a couple parameters, which are key to its
+// functionality.
+//
+// OPT_TIMEOUT Set this to a number to be roughly the longest time
+// period you expect the slave to stall the bus, or likewise
+// the longest time period you expect it to wait for a response.
+// If the slave takes longer for either task, a fault will be
+// detected and reported.
+//
+// OPT_SELF_RESET If set, this will send a reset signal to the downstream
+// core so that you can attempt to restart it without reloading
+// the FPGA. If set, the o_reset signal will be used to reset
+// the downstream core.
+//
+// A second key feature of this core is the outgoing fault detector,
+// o_fault. If this signal is ever raised, the slave has (somehow)
+// violated protocol. Such a violation may (or may not) return an
+// error upstream. For example, if the slave returns a response
+// following no requests from the master, then no error will be returned
+// up stream (doing so would be a protocol violation), but a fault will
+// be detected. Use this line to trigger any internal logic analyzers.
+//
+// Creator: Dan Gisselquist, Ph.D.
+// Gisselquist Technology, LLC
+//
+////////////////////////////////////////////////////////////////////////////////
+// }}}
+// Copyright (C) 2019-2024, 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 wbsafety #(
+ // {{{
+ parameter AW = 28, DW = 32,
+ parameter OPT_TIMEOUT = 12,
+ parameter MAX_DEPTH = (OPT_TIMEOUT),
+ parameter [0:0] OPT_SELF_RESET = 1'b1,
+ parameter [0:0] F_OPT_FAULTLESS = 1'b1
+ // }}}
+ ) (
+ // {{{
+ input wire i_clk, i_reset,
+ //
+ // The incoming WB interface from the (trusted) master
+ // {{{
+ // This interface is guaranteed to follow the protocol.
+ input wire i_wb_cyc, i_wb_stb, i_wb_we,
+ input wire [AW-1:0] i_wb_addr,
+ input wire [DW-1:0] i_wb_data,
+ input wire [DW/8-1:0] i_wb_sel,
+ output reg o_wb_stall, o_wb_ack,
+ output reg [DW-1:0] o_wb_idata,
+ output reg o_wb_err,
+ // }}}
+ //
+ // The outgoing interface to the untrusted slave
+ // {{{
+ // This interface may or may not follow the WB protocol
+ output reg o_reset,
+ output reg o_wb_cyc, o_wb_stb, o_wb_we,
+ output reg [AW-1:0] o_wb_addr,
+ output reg [DW-1:0] o_wb_data,
+ output reg [DW/8-1:0] o_wb_sel,
+ input wire i_wb_stall, i_wb_ack,
+ input wire [DW-1:0] i_wb_idata,
+ input wire i_wb_err,
+ // }}}
+ //
+ // The fault signal, indicating the downstream slave was
+ // misbehaving
+ output reg o_fault
+ // }}}
+ );
+
+ // Declarations
+ // {{{
+ localparam LGTIMEOUT = $clog2(OPT_TIMEOUT+1);
+ localparam LGDEPTH = $clog2(MAX_DEPTH+1);
+ reg none_expected;
+ reg [LGDEPTH-1:0] expected_returns;
+ reg [LGTIMEOUT-1:0] stall_timer, wait_timer;
+ reg timeout;
+ reg faulty_return;
+
+ wire skd_stb, skd_o_ready, skd_we;
+ reg skd_stall;
+ wire [AW-1:0] skd_addr;
+ wire [DW-1:0] skd_data;
+ wire [DW/8-1:0] skd_sel;
+ // }}}
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Start with a skid buffer on all incoming signals
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+`ifdef FORMAL
+ // {{{
+ // We know the skid buffer works. It's irrelevant to our proof.
+ // Therefore, remove it during formal testing, lest we need to
+ // check it as well. Further, we make this a parameter--but only
+ // when FORMAL is defined--so that it may be overridden in that case.
+ //
+ parameter [0:0] SKID_PASSTHROUGH = 1'b1;
+`else
+ localparam [0:0] SKID_PASSTHROUGH = 1'b0;
+ // }}}
+`endif
+
+ skidbuffer #(
+ // {{{
+ .DW(1+AW+DW+(DW/8)),
+ .OPT_PASSTHROUGH(SKID_PASSTHROUGH)
+ // }}}
+ ) skd(
+ // {{{
+ .i_clk(i_clk), .i_reset(i_reset || !i_wb_cyc),
+ .i_valid(i_wb_stb), .o_ready(skd_o_ready),
+ .i_data({ i_wb_we, i_wb_addr, i_wb_data, i_wb_sel }),
+ .o_valid(skd_stb), .i_ready(!skd_stall),
+ .o_data({ skd_we, skd_addr, skd_data, skd_sel })
+ // }}}
+ );
+
+ always @(*)
+ o_wb_stall = !skd_o_ready;
+
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Timeout checking
+ //
+
+
+ //
+ // Insist on a maximum number of downstream stalls
+ //
+ initial stall_timer = 0;
+ always @(posedge i_clk)
+ if (!i_reset && o_wb_stb && i_wb_stall)
+ begin
+ if (stall_timer <= OPT_TIMEOUT)
+ stall_timer <= stall_timer + 1;
+ end else
+ stall_timer <= 0;
+
+ //
+ // Insist on a maximum number cyles waiting for an acknowledgment
+ //
+ initial wait_timer = 0;
+ always @(posedge i_clk)
+ if (!i_reset && o_wb_cyc && !o_wb_stb && !i_wb_ack && !i_wb_err
+ && !none_expected)
+ begin
+ if (wait_timer <= OPT_TIMEOUT)
+ wait_timer <= wait_timer + 1;
+ end else
+ wait_timer <= 0;
+
+ //
+ // Generate a timeout signal on any error
+ //
+ initial timeout = 0;
+ always @(posedge i_clk)
+ if (timeout && o_wb_err)
+ timeout <= 0;
+ else
+ timeout <= (i_wb_stall)&&(stall_timer >= OPT_TIMEOUT)
+ || ((!i_wb_ack && !i_wb_err)&&(wait_timer >= OPT_TIMEOUT));
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Return counting
+ // {{{
+
+ initial none_expected = 1;
+ initial expected_returns = 0;
+ always @(posedge i_clk)
+ if (i_reset || o_reset || o_wb_err || !i_wb_cyc)
+ begin
+ expected_returns <= 0;
+ none_expected <= 1;
+ end else case({skd_stb && !skd_stall, o_wb_ack })
+ 2'b10: begin
+ expected_returns <= expected_returns + 1;
+ none_expected <= 1'b0;
+ end
+ 2'b01: begin
+ expected_returns <= expected_returns - 1;
+ none_expected <= (expected_returns == 1);
+ end
+ default: begin end
+ endcase
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Downstream reset generation
+ // {{{
+ generate if (OPT_SELF_RESET)
+ begin : SELF_RESET
+
+ initial o_reset = 1;
+ always @(posedge i_clk)
+ if (i_reset || o_fault)
+ o_reset <= 1;
+ else begin
+ o_reset <= 0;
+ if (o_wb_cyc && none_expected
+ &&(i_wb_ack || i_wb_err))
+ o_reset <= 1;
+ if (timeout)
+ o_reset <= 1;
+ end
+ end else begin : FORWARD_RESET
+
+ always @(*)
+ o_reset = i_reset || o_fault;
+
+ end endgenerate
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Fault detection
+ // {{{
+
+ // faulty_return
+ // {{{
+ // A faulty return is a response from the slave at a time, or in
+ // a fashion that it unexpected and violates protocol
+ //
+ always @(*)
+ begin
+ faulty_return = 0;
+ if (expected_returns <= ((o_wb_stb && i_wb_stall) ? 1:0)
+ + ((o_wb_ack || o_wb_err) ? 1:0))
+ faulty_return = i_wb_ack || i_wb_err;
+ if (i_wb_ack && i_wb_err)
+ faulty_return = 1;
+ if (!i_wb_cyc || !o_wb_cyc)
+ faulty_return = 0;
+ end
+ // }}}
+
+ // o_fault
+ // {{{
+ initial o_fault = 0;
+ always @(posedge i_clk)
+ if (o_reset && !i_wb_cyc)
+ o_fault <= 0;
+ else begin
+ if (o_wb_cyc && faulty_return)
+ o_fault <= 1;
+ if (i_wb_cyc && timeout)
+ o_fault <= 1;
+ end
+ // }}}
+
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Downstream bus signal generation
+ //
+
+ // o_wb_cyc
+ // {{{
+ initial o_wb_cyc = 1'b0;
+ always @(posedge i_clk)
+ if (i_reset || (o_wb_cyc && i_wb_err) || o_reset || o_fault
+ || (i_wb_cyc && o_wb_err))
+ o_wb_cyc <= 1'b0;
+ else
+ o_wb_cyc <= i_wb_cyc && (o_wb_cyc || i_wb_stb);
+ // }}}
+
+ // o_wb_stb
+ // {{{
+ initial o_wb_stb = 1'b0;
+ always @(posedge i_clk)
+ if (i_reset || (o_wb_cyc && i_wb_err) || o_reset || o_fault || !i_wb_cyc
+ || (i_wb_cyc && o_wb_err))
+ o_wb_stb <= 1'b0;
+ else if (!o_wb_stb || !i_wb_stall)
+ o_wb_stb <= skd_stb;
+ // }}}
+
+ // o_wb_we, o_wb_addr, o_wb_data, o_wb_sel
+ // {{{
+ always @(posedge i_clk)
+ if (!o_wb_stb || !i_wb_stall)
+ begin
+ o_wb_we <= skd_we;
+ o_wb_addr <= skd_addr;
+ o_wb_data <= skd_data;
+ o_wb_sel <= skd_sel;
+ end
+ // }}}
+
+ // o_wb_idata
+ // {{{
+ always @(posedge i_clk)
+ o_wb_idata <= i_wb_idata;
+ // }}}
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Return signal generation
+ //
+
+ // skd_stall
+ // {{{
+ always @(*)
+ begin
+ skd_stall = (o_wb_stb && i_wb_stall);
+ if (i_reset)
+ skd_stall = 1'b1;
+ if (o_fault)
+ skd_stall = 1'b0;
+ else if (o_reset)
+ skd_stall = 1'b1;
+ end
+ // }}}
+
+ // o_wb_ack, o_wb_err
+ // {{{
+ initial o_wb_ack = 0;
+ initial o_wb_err = 0;
+ always @(posedge i_clk)
+ if (i_reset || !i_wb_cyc)
+ begin
+ o_wb_ack <= 1'b0;
+ o_wb_err <= 1'b0;
+ end else if (!o_reset && !o_fault)
+ begin
+ if (timeout || faulty_return || i_wb_err)
+ begin
+ o_wb_ack <= 1'b0;
+ o_wb_err <= (expected_returns > ((o_wb_ack||o_wb_err) ? 1:0));
+ end else begin
+ o_wb_ack <= o_wb_cyc && i_wb_ack && !i_wb_err;
+ o_wb_err <= 1'b0;
+ end
+ end else begin
+ o_wb_ack <= 1'b0;
+ o_wb_err <= (i_wb_stb && !skd_stall);
+ end
+ // }}}
+
+ // Make Verilator happy
+ // {{{
+ // Verilator lint_off UNUSED
+ wire unused;
+ assign unused = &{ 1'b0, F_OPT_FAULTLESS };
+ // Verilator lint_on UNUSED
+ // }}}
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+//
+// Formal property section
+// {{{
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+`ifdef FORMAL
+ //
+ // The following proof comes in several parts.
+ //
+ // 1. PROVE that the upstream properties will hold independent of
+ // what the downstream slave ever does.
+ //
+ // 2. PROVE that if the downstream slave follows protocol, then
+ // o_fault will never get raised.
+ //
+ // We then repeat these proofs again with both OPT_SELF_RESET set and
+ // clear. Which of the four proofs is accomplished is dependent upon
+ // parameters set by the formal engine.
+ //
+ //
+ localparam DOWNSTREAM_ACK_DELAY = OPT_TIMEOUT/2-1;
+ localparam UPSTREAM_ACK_DELAY = OPT_TIMEOUT + 3;
+ wire [LGDEPTH-1:0] fwbs_nreqs, fwbs_nacks, fwbs_outstanding;
+
+ reg f_past_valid;
+ initial f_past_valid = 0;
+ always @(posedge i_clk)
+ f_past_valid <= 1;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Upstream master Bus properties
+ //
+ always @(*)
+ if (!f_past_valid)
+ assume(i_reset);
+
+ fwb_slave #(
+ // {{{
+ .AW(AW), .DW(DW),
+ // .F_MAX_ACK_DELAY(UPSTREAM_ACK_DELAY),
+ .F_LGDEPTH(LGDEPTH),
+ .F_OPT_DISCONTINUOUS(1),
+ .F_OPT_MINCLOCK_DELAY(0)
+ // }}}
+ ) wbs (
+ // {{{
+ i_clk, i_reset,
+ i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel,
+ o_wb_ack, o_wb_stall, o_wb_idata, o_wb_err,
+ fwbs_nreqs, fwbs_nacks, fwbs_outstanding
+ // }}}
+ );
+
+ always @(*)
+ assert(none_expected == (expected_returns == 0));
+
+ // Just so we pass the skid buffer's assumptions ...
+ always @(posedge i_clk)
+ if (f_past_valid && $past(i_wb_stb && o_wb_stall))
+ assume($stable(i_wb_data));
+
+ always @(*)
+ if (i_wb_cyc && !o_wb_err && !o_fault)
+ assert(expected_returns == fwbs_outstanding);
+
+ generate if (F_OPT_FAULTLESS)
+ begin : FAULTLESS_PROPERTIES
+ // {{{
+ ////////////////////////////////////////////////////////////////
+ //
+ // Assume the downstream core is protocol compliant, and
+ // prove that o_fault stays low.
+ //
+ wire [LGDEPTH-1:0] fwbm_nreqs, fwbm_nacks,fwbm_outstanding;
+ reg [LGDEPTH-1:0] mreqs, sacks;
+
+
+ fwb_master #(
+ // {{{
+ .AW(AW), .DW(DW),
+ .F_MAX_ACK_DELAY(DOWNSTREAM_ACK_DELAY),
+ .F_MAX_STALL(DOWNSTREAM_ACK_DELAY),
+ .F_LGDEPTH(LGDEPTH),
+ .F_OPT_DISCONTINUOUS(1),
+ .F_OPT_MINCLOCK_DELAY(0)
+ // }}}
+ ) wbm (
+ // {{{
+ i_clk, o_reset,
+ o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data,
+ o_wb_sel,
+ i_wb_ack, i_wb_stall, i_wb_idata, i_wb_err,
+ fwbm_nreqs, fwbm_nacks, fwbm_outstanding
+ // }}}
+ );
+
+ //
+ // Here's the big proof
+ always @(*)
+ assert(!o_fault);
+
+ ////////////////////////////////////////////////////////////////
+ //
+ // The following properties are necessary for passing induction
+ //
+ always @(*)
+ if (!i_reset && i_wb_cyc && o_wb_cyc)
+ assert(expected_returns == fwbm_outstanding
+ + (o_wb_stb ? 1:0)
+ + ((o_wb_ack|o_wb_err) ? 1:0));
+
+ always @(*)
+ assert(!timeout);
+
+ always @(*)
+ if (o_wb_err)
+ assert(!o_wb_cyc);
+
+ always @(*)
+ sacks = fwbs_nacks + (o_wb_ack ? 1:0);
+
+ always @(*)
+ if (!o_wb_err && i_wb_cyc && o_wb_cyc)
+ assert(sacks == fwbm_nacks);
+
+ always @(posedge i_clk)
+ if (!i_reset && i_wb_cyc && expected_returns > 0)
+ assert(o_wb_cyc || o_wb_err);
+
+ always @(*)
+ mreqs = fwbm_nreqs + (o_wb_stb ? 1:0);
+
+ always @(*)
+ if (!o_wb_err && i_wb_cyc && o_wb_cyc)
+ assert(fwbs_nreqs == mreqs);
+
+ always @(*)
+ if (i_wb_cyc && o_wb_cyc && fwbs_outstanding > 0)
+ assert(i_wb_we == o_wb_we);
+
+ always @(*)
+ if (fwbs_nacks != 0 && i_wb_cyc)
+ assert(o_wb_cyc || o_wb_err);
+ // }}}
+ end else begin
+ // {{{
+ ////////////////////////////////////////////////////////////////
+ //
+ // cover() checks, checks that only make sense if faults are
+ // possible
+ //
+
+ always @(posedge i_clk)
+ cover(o_fault);
+
+ always @(posedge i_clk)
+ if (f_past_valid && $past(faulty_return))
+ cover(o_fault);
+
+ always @(posedge i_clk)
+ if (f_past_valid && $past(timeout))
+ cover(o_fault);
+
+ if (OPT_SELF_RESET)
+ begin
+ ////////////////////////////////////////////////////////
+ //
+ // Prove that we can actually reset the downstream
+ // bus/core as desired
+ //
+ reg faulted;
+
+ initial faulted = 0;
+ always @(posedge i_clk)
+ if (i_reset)
+ faulted <= 0;
+ else if (o_fault)
+ faulted <= 1;
+
+
+ always @(posedge i_clk)
+ cover(faulted && $fell(o_reset));
+
+ always @(posedge i_clk)
+ cover(faulted && !o_reset && o_wb_ack);
+
+ end
+ // }}}
+ end endgenerate
+
+ always @(*)
+ cover(!i_reset && fwbs_nacks > 4);
+
+`endif
+// }}}
+endmodule