diff --git a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv index 34731dd1f83fb..eccb95bc67ae1 100644 --- a/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv +++ b/hw/ip/otbn/dv/uvm/env/seq_lib/otbn_stack_addr_integ_chk_vseq.sv @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -// A sequence to verify the countermeasure(s) OTBN.*_STACK.ADDR.INTEGRITY +// A sequence to verify the countermeasure(s) OTBN.*_STACK.ADDR.INTEGRITY. class otbn_stack_addr_integ_chk_vseq extends otbn_single_vseq; `uvm_object_utils(otbn_stack_addr_integ_chk_vseq) @@ -13,120 +13,181 @@ class otbn_stack_addr_integ_chk_vseq extends otbn_single_vseq; task body(); do_end_addr_check = 0; fork - begin - super.body(); - end - begin - inject_integ_err(); - end + super.body(); + inject_integ_err(); join endtask: body - task inject_integ_err(); - bit err_type; - string err_path; - string top_valid_path; - string stack_read_path; - string stack_wr_idx_path; - string stack_write_path; - bit stack_write; - bit top_valid; - bit stack_read; - bit [31:0] err_val = 32'd1 << 20; - `DV_CHECK_STD_RANDOMIZE_FATAL(err_type) - if (err_type) begin // err_type = 1 -> call stack error injection - top_valid_path = "tb.dut.u_otbn_core.u_otbn_rf_base.u_call_stack.top_valid_o"; - stack_wr_idx_path = "tb.dut.u_otbn_core.u_otbn_rf_base.u_call_stack.stack_wr_idx"; - stack_read_path = - "tb.dut.u_otbn_core.u_otbn_instruction_fetch.ctrl_flow_predec_o.call_stack_pop"; - stack_write_path = "tb.dut.u_otbn_core.u_otbn_rf_base.u_call_stack.stack_write_o"; - end else begin // err_type = 0 -> loop stack error injection - top_valid_path = - "tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.loop_info_stack.top_valid_o"; - stack_wr_idx_path = - "tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.loop_info_stack.stack_wr_idx[2:0]"; - stack_read_path = - "tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.loop_info_stack.stack_read_o"; - stack_write_path = - "tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.loop_info_stack.stack_write"; - end - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - `DV_CHECK_FATAL(uvm_hdl_read(top_valid_path, top_valid)); - end while (!top_valid); - ) + // Wait until the value at path is nonzero or until OTBN finishes execution. + task wait_for_flag(string path); + fork begin : isolation_fork + fork + // This is the main process, which repeatedly reads the value at path and completes if the + // values becomes nonzero. + begin + uvm_hdl_data_t value = 0; + `DV_SPINWAIT(do begin + @(cfg.clk_rst_vif.cb); + `DV_CHECK_FATAL(uvm_hdl_read(path, value)); + end while (!value);) + end + // This process waits until OTBN completes execution or until reset. + wait_for_run_completion(UVM_HIGH); + + join_any + disable fork; + end join + endtask + + // Wait until a short time after stack path becomes nonempty, then wait a short time. If it is + // still nonempty, write 1 to still_nonzero. + task wait_until_after_nonempty(input string stack_path, output bit still_nonzero); + string top_valid_path = {stack_path, ".top_valid_o"}; + logic top_valid; + + // Wait until the stack is nonempty + wait_for_flag(top_valid_path); + + // Now wait a short time more (to avoid triggering just after the stack is first written to) cfg.clk_rst_vif.wait_clks($urandom_range(10, 100)); + + // Check again to see whether the stack is nonempty. Hopefully it is (if not, we give up) `DV_CHECK_FATAL(uvm_hdl_read(top_valid_path, top_valid)); - if (top_valid) begin + still_nonzero = (top_valid != 0); + endtask + + task send_escalation_to_model(); + `uvm_info(`gfn, "Telling model agent to take an error escalation", UVM_MEDIUM) + cfg.model_agent_cfg.vif.send_err_escalation(32'd1 << 20); + endtask + + task inject_integ_err(); + string stack_path; + bit stack_nonempty; + int unsigned msb; + string forced_paths[$]; + + // This flag chooses whether we inject an error into the call stack (err_type=1) or the loop + // stack (err_type=0). + bit err_type = $urandom_range(0, 1); + + if (err_type) begin + // We want to inject an error into the call stack (the data backing x1) + stack_path = "tb.dut.u_otbn_core.u_otbn_rf_base.u_call_stack"; + msb = 38; + end else begin + // We want to inject an error into the loop stack + stack_path = {"tb.dut.u_otbn_core.", + "u_otbn_controller.u_otbn_loop_controller.loop_info_stack"}; + msb = 34; + end + + wait_until_after_nonempty(stack_path, stack_nonempty); + // Looks like the stack emptied again. Give up. + if (!stack_nonempty) return; + + fork begin: isolation_fork fork - begin: isolation_fork - fork - begin - if (err_type) begin - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - `DV_CHECK_FATAL(uvm_hdl_read(stack_read_path, stack_read)); - end while (!stack_read); - ) - cfg.model_agent_cfg.vif.send_err_escalation(err_val); - end else begin - // error is injected in the corrupt_stack task. - // We wait here till the otbn is locked so that we don't exit the fork early. - `DV_WAIT(end_test) - @(cfg.clk_rst_vif.cb); - end - end - begin - corrupt_stack(stack_wr_idx_path, err_type, err_path); - forever begin - `DV_SPINWAIT( - do begin - @(cfg.clk_rst_vif.cb); - `DV_CHECK_FATAL(uvm_hdl_read(stack_write_path, stack_write)); - end while (!stack_write); - ) - @(cfg.clk_rst_vif.cb); - corrupt_stack(stack_wr_idx_path, err_type, err_path); - end // forever begin - end // fork begin - join_any - disable fork; - end: isolation_fork - join - `DV_WAIT(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked) - `DV_CHECK_FATAL(uvm_hdl_release(err_path) == 1); - reset_if_locked(); + // This process injects an error by corrupting every value just after the RTL has written it + // to the stack. We expect the RTL to detect the error when it reads the value back out + // again. + begin + corrupt_stack(stack_path, err_type == 0, msb, forced_paths); + forever begin + wait_for_flag({stack_path, ".stack_write_o"}); + @(cfg.clk_rst_vif.cb); + corrupt_stack(stack_path, err_type == 0, msb, forced_paths); + end + end + + // Wait until the next read of the stack. We know that we are corrupting every value written + // into the stack, so we expect the RTL to detect an error as soon as we read something back + // out again. + begin + if (err_type) begin + // We have just injected an error into the call stack. Wait until it gets read back + // again and then tell the model about the expected error. + wait_for_flag({stack_path, ".stack_read_o"}); + send_escalation_to_model(); + end else begin + // A loop stack corruption isn't so easy to wait for, so we actually do the waiting at + // the end of corrupt_stack. Wait here until that task has finished, to avoid exiting + // the fork early. + `DV_WAIT(end_test) + @(cfg.clk_rst_vif.cb); + end + end + + join_any + disable fork; + end: isolation_fork + join + `DV_WAIT(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked) + foreach (forced_paths[i]) begin + `DV_CHECK_FATAL(uvm_hdl_release(forced_paths[i]) == 1); end + reset_if_locked(); endtask: inject_integ_err - task corrupt_stack(string stack_wr_idx_path, bit err_type, output string err_path); + // Corrupt a value at path by XOR'ing with a value that has 1 or 2 bits set and forcing the + // resulting value. The msb argument gives the index of the MSB of the variable value that is + // being forced. + task corrupt_at_path(string path, int unsigned msb); + bit [38:0] mask; + bit [38:0] good_data, bad_data; + + `DV_CHECK_FATAL(msb <= 38); + + `DV_CHECK_FATAL(uvm_hdl_read(path, good_data)); + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, + $countones(mask) inside {[1:2]}; + mask >> (msb + 1) == 0;) + bad_data = good_data ^ mask; + + `uvm_info(`gfn, + $sformatf({"Corrupting stack entry:\n", + " path = %0s.\n", + " %10x -> %10x"}, + path, good_data, bad_data), + UVM_MEDIUM) + + `DV_CHECK_FATAL(uvm_hdl_force(path, bad_data)); + endtask + + // Corrupt the top of the stack. + // + // This uses uvm_hdl_force to force the signal and writes the path of each value forced to + // forced_path (to be released by the caller). + // + // If immediately_escalate is true, send an error escalation to the model immediately and then + // wait until it locks itself. + // + // MSB gives the msb of the stack elements (at most 38 is supported). + task corrupt_stack(string stack_path, + bit immediately_escalate, + int unsigned msb, + string forced_paths[$]); + string err_path; bit [2:0] stack_wr_idx; - bit [38:0] good_data; - bit [38:0] bad_data; - bit [31:0] mask; - bit [31:0] err_val = 32'd1 << 20; + string stack_wr_idx_path = {stack_path, ".stack_wr_idx"}; + + // Read the write index for the stack. If the write index happens to be zero then the stack must + // be empty. Exit without doing anything. `DV_CHECK_FATAL(uvm_hdl_read(stack_wr_idx_path, stack_wr_idx)); - if (stack_wr_idx != 0) begin - if (err_type) begin - $sformat(err_path, "tb.dut.u_otbn_core.u_otbn_rf_base.u_call_stack.stack_storage[%0d]", - (stack_wr_idx-1)); - end else begin - `DV_CHECK_FATAL(uvm_hdl_read(stack_wr_idx_path, stack_wr_idx)); - $sformat(err_path, - "tb.dut.u_otbn_core.u_otbn_controller.u_otbn_loop_controller.loop_info_stack.stack_storage[%0d]" - , (stack_wr_idx - 1)); - end - `DV_CHECK_FATAL(uvm_hdl_read(err_path, good_data)); - `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(mask, $countones(mask) inside {[1:2]};) - bad_data = good_data ^ mask; - `DV_CHECK_FATAL(uvm_hdl_force(err_path, bad_data)) - if (!err_type) begin - cfg.model_agent_cfg.vif.send_err_escalation(err_val); - `DV_WAIT(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked) - end_test = 1; - end + if (stack_wr_idx == 0) return; + + // If the write index is positive, the top value that has been written should be at position + // stack_wr_idx - 1. + $sformat(err_path, "%s.stack_storage[%0d]", stack_path, stack_wr_idx - 1); + + corrupt_at_path(err_path, msb); + + forced_paths.push_back(err_path); + + if (immediately_escalate) begin + send_escalation_to_model(); + `DV_WAIT(cfg.model_agent_cfg.vif.status == otbn_pkg::StatusLocked) + end_test = 1; end endtask: corrupt_stack endclass : otbn_stack_addr_integ_chk_vseq