| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Bound into the otbn_loop_controller and used to help collect loop information for coverage. |
| |
| `include "prim_assert.sv" |
| |
| interface otbn_loop_if #( |
| localparam int LoopStackIdxWidth = prim_util_pkg::vbits(otbn_pkg::LoopStackDepth) |
| ) ( |
| input clk_i, |
| input rst_ni, |
| |
| // Signal names from the otbn_loop_controller module (where we are bound) |
| input logic [31:0] insn_addr_i, |
| input logic at_current_loop_end_insn, |
| input logic current_loop_valid, |
| input logic loop_stack_full, |
| input logic current_loop_finish, |
| input logic next_loop_valid, |
| input logic loop_start_req_i, |
| input logic loop_start_commit_i, |
| input logic [31:0] loop_iterations_i, |
| input logic otbn_stall_i, |
| |
| input logic [31:0] current_loop_start, |
| input logic [31:0] current_loop_end, |
| input logic [31:0] next_loop_end, |
| |
| input logic [31:0] current_loop_d_iterations, |
| input logic [31:0] current_loop_q_iterations, |
| |
| input logic [LoopStackIdxWidth-1:0] loop_stack_rd_idx, |
| |
| input logic loop_stack_push, |
| input logic loop_stack_pop |
| ); |
| |
| function automatic otbn_env_pkg::stack_fullness_e get_fullness(); |
| if (loop_stack_full) begin |
| return otbn_env_pkg::StackFull; |
| end |
| if (current_loop_valid) begin |
| return otbn_env_pkg::StackPartial; |
| end |
| return otbn_env_pkg::StackEmpty; |
| endfunction |
| |
| // Are assertions in the loop counters currently enabled? |
| bit loop_counter_assertions_enabled = 1'b1; |
| |
| // Enables or disables all assertions in the loop controller. One assertion in the counter |
| // (OutSet_A) is not compatible with loop warping and a more targetted disable for that assertion |
| // does not work under Xcelium. |
| function automatic void control_loop_counters_out_set_assertion(bit enable); |
| if (enable == loop_counter_assertions_enabled) begin |
| return; |
| end |
| if (enable) begin |
| $asserton(0, tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller); |
| end else begin |
| $assertoff(0, tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller); |
| end |
| loop_counter_assertions_enabled = enable; |
| endfunction |
| |
| // Track completing some loop. This is implied by the next item, but much easier to hit so maybe |
| // worth covering separately. |
| `COVER(LoopEnd_C, current_loop_finish) |
| |
| // A property that tracks us popping the last loop after we've filled the loop stack. Since we're |
| // just using this for coverage, we use first_match on the antecedent to avoid multiple threads if |
| // we spend several cycles full. |
| `COVER(FullToEmpty_C, |
| first_match(loop_stack_full) ##[1:$] |
| (current_loop_finish && !next_loop_valid)) |
| |
| // A property that tracks us completing a one-instruction loop with an iteration count of one (the |
| // point being that this is the quickest "in and out"). To spot this happening, we just look for a |
| // loop start and then a loop finish on the following cycle. |
| `COVER(ShortestLoop_C, (loop_start_req_i && loop_start_commit_i) ##1 current_loop_finish) |
| |
| // A property that tracks us running a loop to completion with the maximal number of iterations. |
| // (To hit this, we're going to need to force some signals!) This sequence is actually slightly |
| // more specific: it asks that this maximal number of iterations should also occur in the |
| // innermost loop. This is much easier to track and, in practice, it's going to be the easiest way |
| // to try to hit things too. |
| `COVER(MaximalLoop_C, |
| (loop_start_req_i && loop_start_commit_i && (loop_iterations_i == '1)) ##1 |
| !(loop_start_req_i && loop_start_commit_i) [*0:$] ##1 |
| current_loop_finish) |
| |
| // Try to see loops with "bad nesting", where the final instruction address for the innermost loop |
| // matches the final instruction address for the next one out. This property will trigger on the |
| // final instruction of the inner loop for the last time (to make sure we actually get there, |
| // where a bug would cause the fireworks). |
| `COVER(BadNestingEnd_C, |
| current_loop_valid && next_loop_valid && |
| current_loop_finish && (current_loop_end == next_loop_end)) |
| |
| // Try to see loops with "bad nesting", where the final instruction for an outer loop occurs in |
| // the middle of the innermost loop. This property triggers when we execute the instruction at the |
| // end of the outer loop (and hopefully nothing exciting happens). We condition this on not |
| // stalling (because loop-based redirects only happen when the instruction isn't stalled) but |
| // don't condition on next_loop.loop_iterations: even if there are several more iterations left, |
| // we'd expect to see a back edge on a spurious match, so it shouldn't matter. |
| `COVER(BadNestingMiddle_C, |
| current_loop_valid && next_loop_valid && |
| (insn_addr_i != current_loop_end) && |
| (insn_addr_i == next_loop_end) && |
| !otbn_stall_i) |
| |
| // Jump into a loop body from outside. We don't bother checking that this is a jump: since the |
| // code sequence is fixed, we know we can't get here through a straight line instruction because |
| // we check that !loop_start_req_i. |
| `COVER(JumpIntoLoop_C, |
| (!loop_start_req_i && current_loop_valid && |
| !((current_loop_start <= insn_addr_i) && (insn_addr_i <= current_loop_end))) ##1 |
| ((current_loop_start <= insn_addr_i) && (insn_addr_i <= current_loop_end))) |
| |
| // Jump to the last instruction of a loop body from outside. This is a stronger version of |
| // JumpIntoLoop_C. |
| `COVER(JumpToLoopEnd_C, |
| (!loop_start_req_i && current_loop_valid && |
| !((current_loop_start <= insn_addr_i) && (insn_addr_i <= current_loop_end))) ##1 |
| (insn_addr_i == current_loop_end)) |
| |
| // Loop length tracking. If we want to convert between the current "iteration count" as stored by |
| // the RTL (which counts down from the initial count to 1) and the iteration count as used in the |
| // ISS or spec, we need to know the total number of iterations for this loop. Of course, the RTL |
| // doesn't store that (since it doesn't need it), so we have to reconstruct it here. |
| logic [31:0] lengths[$]; |
| always @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| lengths.delete(); |
| end else begin |
| if (current_loop_finish && lengths.size()) begin |
| void'(lengths.pop_front()); |
| end |
| if (loop_start_req_i && loop_start_commit_i) begin |
| lengths.push_front(loop_iterations_i); |
| end |
| end |
| end |
| |
| // Convert from the RTL-level view of the iteration counter (starting at the number of iterations |
| // and counting down to 1) to the ISA-level view (starting at zero and counting up). If iters is |
| // greater than or equal to the surrounding loop count, returns 0: the index of the first |
| // iteration. |
| function logic [31:0] loop_iters_to_count(logic [31:0] iters); |
| if (!lengths.size()) return 0; |
| return (iters < lengths[0]) ? lengths[0] - iters : 32'd0; |
| endfunction |
| |
| // Convert from the ISA-level view (starting at zero and counting up) of the iteration counter to |
| // the RTL-level view (starting at the number of iterations and counting down to 1). If count is |
| // greater than or equal to the surrounding loop count, returns 1: the index of the last |
| // iteration. |
| function logic [31:0] loop_count_to_iters(logic [31:0] count); |
| if (!lengths.size()) return 0; |
| return (count < lengths[0]) ? lengths[0] - count : 32'd1; |
| endfunction |
| |
| endinterface |