|
| 1 | +// Copyright lowRISC contributors. |
| 2 | +// Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | +// SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +// A formal testbench for the ICache. This gets bound into the actual ICache DUT. |
| 6 | + |
| 7 | +`include "prim_assert.sv" |
| 8 | + |
| 9 | +// A macro to emulate |-> (a syntax that Yosys doesn't currently support). |
| 10 | +`define IMPLIES(a, b) ((b) || (!(a))) |
| 11 | + |
| 12 | +module formal_tb #( |
| 13 | + // DUT parameters |
| 14 | + parameter int unsigned BusWidth = 32, |
| 15 | + parameter int unsigned CacheSizeBytes = 4*1024, |
| 16 | + parameter bit ICacheECC = 1'b0, |
| 17 | + parameter int unsigned LineSize = 64, |
| 18 | + parameter int unsigned NumWays = 2, |
| 19 | + parameter bit SpecRequest = 1'b0, |
| 20 | + parameter bit BranchCache = 1'b0, |
| 21 | + |
| 22 | + // Internal parameters / localparams |
| 23 | + parameter int unsigned NUM_FB = 4 |
| 24 | +) ( |
| 25 | + // Top-level ports |
| 26 | + input logic clk_i, |
| 27 | + input logic rst_ni, |
| 28 | + input logic req_i, |
| 29 | + input logic branch_i, |
| 30 | + input logic branch_spec_i, |
| 31 | + input logic [31:0] addr_i, |
| 32 | + input logic ready_i, |
| 33 | + input logic valid_o, |
| 34 | + input logic [31:0] rdata_o, |
| 35 | + input logic [31:0] addr_o, |
| 36 | + input logic err_o, |
| 37 | + input logic err_plus2_o, |
| 38 | + input logic instr_req_o, |
| 39 | + input logic instr_gnt_i, |
| 40 | + input logic [31:0] instr_addr_o, |
| 41 | + input logic [BusWidth-1:0] instr_rdata_i, |
| 42 | + input logic instr_err_i, |
| 43 | + input logic instr_pmp_err_i, |
| 44 | + input logic instr_rvalid_i, |
| 45 | + input logic icache_enable_i, |
| 46 | + input logic icache_inval_i, |
| 47 | + input logic busy_o, |
| 48 | + |
| 49 | + // Internal signals |
| 50 | + input logic [NUM_FB-1:0] fill_busy_q, |
| 51 | + input logic [NUM_FB-1:0][NUM_FB-1:0] fill_older_q |
| 52 | +); |
| 53 | + |
| 54 | + // We are bound into the DUT. This means we don't control the clock and reset directly, but we |
| 55 | + // still want to constrain rst_ni to reset the module at the start of time (for one cycle) and |
| 56 | + // then stay high. |
| 57 | + // |
| 58 | + // Note that having a cycle with rst_ni low at the start of time means that we can safely use |
| 59 | + // $past, $rose and $fell in calls to `ASSERT without any need for an "f_past_valid signal": they |
| 60 | + // will only be evaluated from cycle 2 onwards. |
| 61 | + logic [1:0] f_startup_count = 2'd0; |
| 62 | + always_ff @(posedge clk_i) begin : reset_assertion |
| 63 | + f_startup_count <= f_startup_count + ((f_startup_count == 2'd3) ? 2'd0 : 2'd1); |
| 64 | + |
| 65 | + // Assume that rst_ni is low for the first cycle and not true after that. |
| 66 | + assume (~((f_startup_count == 2'd0) ^ ~rst_ni)); |
| 67 | + |
| 68 | + // There is a feed-through path from branch_i to req_o which isn't squashed when in reset. Assume |
| 69 | + // that branch_i isn't asserted when in reset. |
| 70 | + assume (`IMPLIES(!rst_ni, !branch_i)); |
| 71 | + end |
| 72 | + |
| 73 | + // Top-level assertions |
| 74 | + // |
| 75 | + // This section contains the assertions that prove the properties we care about. All should be |
| 76 | + // about observable signals (so shouldn't contain any references to anything that isn't exposed as |
| 77 | + // an input port). |
| 78 | + |
| 79 | + // REQ stays high until GNT |
| 80 | + // |
| 81 | + // If instr_req_o goes high, we won't drive it low again until instr_gnt_i or instr_pmp_err_i is |
| 82 | + // high (the latter signals that the outgoing request got squashed, so we can forget about it). |
| 83 | + // |
| 84 | + // Read this as "a negedge of instr_req_o implies that the transaction was granted or squashed on |
| 85 | + // the previous cycle". |
| 86 | + `ASSERT(req_to_gnt, |
| 87 | + `IMPLIES($fell(instr_req_o), $past(instr_gnt_i | instr_pmp_err_i))) |
| 88 | + |
| 89 | + // ADDR stability |
| 90 | + // |
| 91 | + // If instr_req_o goes high, the address at instr_addr_o will stay constant until the request is |
| 92 | + // squashed or granted. The encoding below says "either the address is stable, the request has |
| 93 | + // been squashed, we've had a grant or this is a new request". |
| 94 | + `ASSERT(req_addr_stable, |
| 95 | + $stable(instr_addr_o) | $past(instr_gnt_i | instr_pmp_err_i | ~instr_req_o)) |
| 96 | + |
| 97 | + |
| 98 | + // Internal (induction) assertions |
| 99 | + // |
| 100 | + // Code below this line can refer to internal signals of the DUT. The assertions shouldn't be |
| 101 | + // needed for BMC checks, but will be required to constrain the state space used for k-induction. |
| 102 | + |
| 103 | + for (genvar fb = 0; fb < NUM_FB; fb++) begin : g_fb_older_asserts |
| 104 | + // If fill buffer i is busy then fill_older_q[i][j] means that that fill buffer j has an |
| 105 | + // outstanding request which started before us (and should take precedence). We should check |
| 106 | + // that this only happens if buffer j is indeed busy. |
| 107 | + // |
| 108 | + // fill_busy_q[i] -> fill_older_q[i][j] -> fill_busy_q[j] |
| 109 | + // |
| 110 | + // which we can encode as |
| 111 | + // |
| 112 | + // (fill_older_q[i][j] -> fill_busy_q[j]) | ~fill_busy_q[i] |
| 113 | + // = (fill_busy_q[j] | ~fill_older_q[i][j]) | ~fill_busy_q[i] |
| 114 | + // |
| 115 | + // Grouping by j, we can rewrite this as: |
| 116 | + `ASSERT(older_is_busy, &(fill_busy_q | ~fill_older_q[fb]) | ~fill_busy_q[fb]) |
| 117 | + |
| 118 | + // No fill buffer should ever think that it's older than itself |
| 119 | + `ASSERT(older_anti_refl, !fill_older_q[fb][fb]) |
| 120 | + |
| 121 | + // The "older" relation should be anti-symmetric (a fill buffer can't be both older than, and |
| 122 | + // younger than, another). This takes NUM_FB*(NUM_FB-1)/2 assertions, comparing each pair of |
| 123 | + // buffers. Here, we do this by looping over the indices below fb. |
| 124 | + // |
| 125 | + // If I and J both think the other is older, then fill_older_q[I][J] and fill_older_q[J][I] will |
| 126 | + // both be true. Check that doesn't happen. |
| 127 | + for (genvar fb2 = 0; fb2 < fb; fb2++) begin : g_older_anti_symm_asserts |
| 128 | + `ASSERT(older_anti_symm, ~(fill_older_q[fb][fb2] & fill_older_q[fb2][fb])) |
| 129 | + end |
| 130 | + |
| 131 | + // The older relation should be transitive (if i is older than j and j is older than k, then i |
| 132 | + // is older than k). That is: |
| 133 | + // |
| 134 | + // (fill_busy_q[i] & fill_older_q[i][j]) -> |
| 135 | + // (fill_busy_q[j] & fill_older_q[j][k]) -> |
| 136 | + // (fill_busy_q[i] & fill_older_q[i][k]) |
| 137 | + // |
| 138 | + // Note that the second fill_busy_q[i] holds trivially and fill_busy_q[j] holds because of |
| 139 | + // order_is_busy, so this can be rewritten as: |
| 140 | + // |
| 141 | + // fill_busy_q[i] & fill_older_q[i][j] -> fill_older_q[j][k] -> fill_older_q[i][k] |
| 142 | + // |
| 143 | + // Converting A->B->C into (A&B)->C and then rewriting A->B as B|~A, this is equivalent to |
| 144 | + // |
| 145 | + // (fill_older_q[i][k] | ~fill_older_q[j][k]) | ~(fill_busy_q[i] & fill_older_q[i][j]) |
| 146 | + // |
| 147 | + // Looping over i and j, we can simplify this as |
| 148 | + // |
| 149 | + // &(fill_older_q[i] | ~fill_older_q[j]) | ~(fill_busy_q[i] & fill_older_q[i][j]) |
| 150 | + // |
| 151 | + for (genvar fb2 = 0; fb2 < NUM_FB; fb2++) begin : g_older_transitive_asserts |
| 152 | + `ASSERT(older_transitive, |
| 153 | + (&(fill_older_q[fb] | ~fill_older_q[fb2]) | |
| 154 | + ~(fill_busy_q[fb] & fill_older_q[fb][fb2]))) |
| 155 | + end |
| 156 | + |
| 157 | + // The older relation should be total. This is a bit finicky because of fill buffers that aren't |
| 158 | + // currently busy. Specifically, we want |
| 159 | + // |
| 160 | + // i != j -> fill_busy_q[i] -> fill_busy_q[j] -> (fill_older_q[i][j] | fill_older_q[j][i]) |
| 161 | + // |
| 162 | + for (genvar fb2 = 0; fb2 < fb; fb2++) begin : g_older_total_asserts |
| 163 | + `ASSERT(older_total, |
| 164 | + `IMPLIES(fill_busy_q[fb] & fill_busy_q[fb2], |
| 165 | + fill_older_q[fb][fb2] | fill_older_q[fb2][fb])) |
| 166 | + end |
| 167 | + end |
| 168 | + |
| 169 | +endmodule |
0 commit comments