From b4be0db1f4b60de32caabe0fcb3cd2455665ee33 Mon Sep 17 00:00:00 2001 From: Pascal Nasahl Date: Fri, 27 Sep 2024 09:32:21 +0000 Subject: [PATCH] [sram_ctrl,dv] Add sec_cm_mem_readback test This commit provides a new sec_cm_readback test that tests the SRAM readback FI countermeasure. In the test, a fault is injected during one of the read/write SRAM requests. The test checks, whether the expected alert fires. Closes #23322. Signed-off-by: Pascal Nasahl --- hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv | 18 ++- .../data/sram_ctrl_sec_cm_testplan.hjson | 5 +- .../dv/env/seq_lib/sram_ctrl_base_vseq.sv | 4 +- .../seq_lib/sram_ctrl_readback_err_vseq.sv | 139 ++++++++++++++++++ hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core | 1 + hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv | 3 + .../sram_ctrl/dv/env/sram_ctrl_scoreboard.sv | 5 +- .../sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson | 4 + 8 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_readback_err_vseq.sv diff --git a/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv b/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv index 4201f50646ae60..4abb1840fa1805 100644 --- a/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv +++ b/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv @@ -120,14 +120,19 @@ virtual class mem_bkdr_scb #(int AddrWidth = bus_params_pkg::BUS_AW, function void read_finish(mem_data_t act_data, mem_addr_t addr = 0, mem_mask_t mask = '1, - bit en_check_consistency = 1); + bit en_check_consistency = 1, + bit en_check_exp = 1); mem_item_t exp_item; `DV_CHECK_NE(read_item_q.size, 0) exp_item = read_item_q.pop_front(); act_data &= expand_bit_mask(mask); if (en_check_consistency) check_item_consistency(exp_item, addr, mask); - `DV_CHECK_EQ(act_data, exp_item.data, $sformatf("addr 0x%0h read out mismatch", exp_item.addr)) + + if (en_check_exp) begin + `DV_CHECK_EQ(act_data, exp_item.data, + $sformatf("addr 0x%0h read out mismatch", exp_item.addr)) + end `uvm_info(`gfn, $sformatf("read_finish: Addr[0x%0h], data[0x%0h], Mask[0x%0h]", exp_item.addr, act_data, exp_item.mask), UVM_MEDIUM) @@ -148,7 +153,8 @@ virtual class mem_bkdr_scb #(int AddrWidth = bus_params_pkg::BUS_AW, // write_item_q function void write_finish(mem_addr_t addr = 0, mem_mask_t mask = '1, - bit en_check_consistency = 1); + bit en_check_consistency = 1, + bit en_check_exp = 1); mem_data_t act_data, exp_data; mem_data_t bit_mask; mem_item_t exp_item; @@ -160,7 +166,11 @@ virtual class mem_bkdr_scb #(int AddrWidth = bus_params_pkg::BUS_AW, act_data = get_bkdr_val(exp_item.addr) & bit_mask; exp_data = exp_item.data & bit_mask; - `DV_CHECK_EQ(act_data, exp_data, $sformatf("addr 0x%0h write mismatch", exp_item.addr)) + + if (en_check_exp) begin + `DV_CHECK_EQ(act_data, exp_data, + $sformatf("addr 0x%0h write mismatch", exp_item.addr)) + end `uvm_info(`gfn, $sformatf("write_finish: Addr[0x%0h], data[0x%0h], Mask[0x%0h]", exp_item.addr, act_data, exp_item.mask), UVM_MEDIUM) diff --git a/hw/ip/sram_ctrl/data/sram_ctrl_sec_cm_testplan.hjson b/hw/ip/sram_ctrl/data/sram_ctrl_sec_cm_testplan.hjson index ac06c88ed59ff6..06fdf7ecf512fa 100644 --- a/hw/ip/sram_ctrl/data/sram_ctrl_sec_cm_testplan.hjson +++ b/hw/ip/sram_ctrl/data/sram_ctrl_sec_cm_testplan.hjson @@ -106,10 +106,11 @@ name: sec_cm_mem_readback desc: '''Verify the countermeasure(s) MEM.READBACK. - Test needs to be implemented, see lowRISC/opentitan#23322. + Injects bit-flips during memory reads/writes and checks whether + an error is triggered. ''' stage: V2S - tests: ["{name}_smoke"] + tests: ["{name}_readback_err"] } { name: sec_cm_mem_scramble diff --git a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv index a7fee563768f61..fffe5111d05cf4 100644 --- a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv +++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_base_vseq.sv @@ -58,8 +58,10 @@ class sram_ctrl_base_vseq #(parameter int AddrWidth = `SRAM_ADDR_WIDTH) extends readback_running = 1; if (uvm_re_match("*throughput*", get_type_name()) && uvm_re_match("*sec_cm*", get_type_name()) && + uvm_re_match("*readback_err*", get_type_name()) && uvm_re_match("*throughput*", common_seq_type) && - uvm_re_match("*sec_cm*", common_seq_type)) begin + uvm_re_match("*sec_cm*", common_seq_type) && + uvm_re_match("*readback_err*", common_seq_type)) begin // Configure the SRAM TLUL agent to wait at least 2 cycles before dropping // a request. cfg.m_tl_agent_cfgs[cfg.sram_ral_name].a_valid_len_min = 2; diff --git a/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_readback_err_vseq.sv b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_readback_err_vseq.sv new file mode 100644 index 00000000000000..234d001bc92892 --- /dev/null +++ b/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_readback_err_vseq.sv @@ -0,0 +1,139 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// readback_err_vseq +class sram_ctrl_readback_err_vseq extends sram_ctrl_base_vseq; + `uvm_object_utils(sram_ctrl_readback_err_vseq) + + `uvm_object_new + + // Indicates the number of memory accesses to be performed in this test. + rand int num_ops; + + // Indicates at which memory access the fault is injected. + rand int do_fi_op; + + constraint num_ops_c { + num_ops inside {[10 : 200]}; + } + + constraint do_fi_op_c { + do_fi_op inside {[10 : num_ops]}; + } + + int fi_iteration_position = 0; + + task drive_reqs(int iterations); + // Perform random read/write operations. + bit [TL_AW-1:0] addr; + bit [TL_AW-1:0] sram_addr_mask = cfg.ral_models[cfg.sram_ral_name].get_addr_mask(); + bit [TL_AW-1:0] max_offset = {sram_addr_mask[TL_AW-1:2], 2'd0}; + logic [TL_DW-1:0] rdata; + for (int iteration = 0; iteration < iterations; iteration++) begin + bit write = $urandom_range(0, 1); + `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(addr, (addr & sram_addr_mask) < max_offset;) + if (write) begin + do_single_write(.addr(addr), .data(iteration), .mask('1), .blocking(0)); + end else begin + do_single_read(.addr(addr), .mask('1), .check_rdata(0), .blocking(0), .rdata(rdata)); + end + end + endtask + + task inject_fault(int iterations, int iteration_fi_position, string fi_path, + string tlul_req_path); + // Inject a fault into the signal indicated by fi_path. + bit req; + for (int iteration = 0; iteration < iterations; iteration++) begin + // Wait until the TL-UL request arrives in the SRAM module. + `DV_SPINWAIT( + do begin + #1ps; + uvm_hdl_read(tlul_req_path, req); + end while(!req); + ) + + // Check if it is time to inject a fault. + if (iteration == iteration_fi_position) begin + `uvm_info(`gfn, $sformatf( + "Injecting fault into %s in memory operation %d", + fi_path, iteration_fi_position), + UVM_LOW) + `DV_CHECK(uvm_hdl_force(fi_path, 'hbeef)) + // Release the faulty signal after one clock cycle. + cfg.clk_rst_vif.wait_clks(1); + `DV_CHECK(uvm_hdl_release(fi_path)) + end + cfg.clk_rst_vif.wait_clks(1); + end + endtask + + task monitor_response(int iterations, int iteration_fi_position, string alert_signal, + string tlul_req_path); + // Monitor whether the alert_signal was triggered. + bit req; + for (int iteration = 0; iteration < iterations; iteration++) begin + // Wait until the TL-UL request arrives in the SRAM module. + `DV_SPINWAIT( + do begin + #1ps; + uvm_hdl_read(tlul_req_path, req); + end while(!req); + ) + // Check if it is time to monitor the response. + if (iteration == iteration_fi_position) begin + `uvm_info(`gfn, $sformatf("Checking the %s alert", alert_signal), UVM_LOW) + // Check if alert_signal was triggered. + cfg.scb.set_exp_alert(alert_signal, .is_fatal(1'b1), .max_delay(20)); + wait_alert_trigger (alert_signal, .max_wait_cycle(20), .wait_complete(0)); + // Reset to get the DUT out of terminal state. + apply_resets_concurrently(); + end + cfg.clk_rst_vif.wait_clks(1); + end + endtask + + task body(); + // Used as a FI target. + // TODO: add list of signals instead of using a single signal. Currently only + // the read or write address is faulted. + string fi_path = "tb.dut.sram_addr"; + // Used to keep track of the incoming TL-UL requests. + string tlul_req_path = "tb.dut.ram_tl_i.a_valid"; + // The readback feature raises the fatal_error alert on a mismatch. + string alert_signal = "fatal_error"; + + // Disable certain checks for FI. + cfg.is_fi_test = 1'b1; + + `DV_CHECK_MEMBER_RANDOMIZE_FATAL(num_ops) + `DV_CHECK_MEMBER_RANDOMIZE_FATAL(do_fi_op) + + // Request memory init & enable SRAM readback feature. + req_mem_init(); + csr_wr(.ptr(ral.readback), .value(MuBi4True)); + + // Sanity check some static paths we use in this test. + `DV_CHECK(uvm_hdl_check_path(fi_path), + $sformatf("Hierarchical path %0s appears to be invalid.", fi_path)) + `DV_CHECK(uvm_hdl_check_path(tlul_req_path), + $sformatf("Hierarchical path %0s appears to be invalid.", tlul_req_path)) + + // As we have at least 10 memory requests, correct the offset. + fi_iteration_position = do_fi_op - 10; + + // Start the parallel threads. + fork + // Driver task. + drive_reqs(num_ops); + + // Fault injector task. + inject_fault(num_ops, fi_iteration_position, fi_path, tlul_req_path); + + // Monitoring task. + monitor_response(num_ops, fi_iteration_position, alert_signal, tlul_req_path); + join_any + endtask : body + +endclass : sram_ctrl_readback_err_vseq diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core index 296f115fc03989..60958c5aaa9c12 100644 --- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core +++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env.core @@ -36,6 +36,7 @@ filesets: - seq_lib/sram_ctrl_regwen_vseq.sv: {is_include_file: true} - seq_lib/sram_ctrl_ram_cfg_vseq.sv: {is_include_file: true} - seq_lib/sram_ctrl_stress_all_vseq.sv: {is_include_file: true} + - seq_lib/sram_ctrl_readback_err_vseq.sv: {is_include_file: true} file_type: systemVerilogSource generate: diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv index 4686cd89a8bf8c..ac62b40b82b51d 100644 --- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv +++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_env_cfg.sv @@ -18,6 +18,9 @@ class sram_ctrl_env_cfg #(parameter int AddrWidth = 10) // This tracks when the ram is undergoing initialization in the scoreboard. bit in_init = 1'b0; + // Disables certain checks for FI tests. + bit is_fi_test = 1'b0; + // ext component cfgs rand push_pull_agent_cfg#(.DeviceDataWidth(KDI_DATA_SIZE)) m_kdi_cfg; diff --git a/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv index b189908f9ed709..03926fe48613a0 100644 --- a/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv +++ b/hw/ip/sram_ctrl/dv/env/sram_ctrl_scoreboard.sv @@ -267,7 +267,7 @@ class sram_ctrl_scoreboard #(parameter int AddrWidth = 10) extends cip_base_scor cfg.clk_rst_vif.wait_clks(1); #1ps; - mem_bkdr_scb.write_finish(decrypt_addr, item.mask); + mem_bkdr_scb.write_finish(decrypt_addr, item.mask, !cfg.is_fi_test, !cfg.is_fi_test); `uvm_info(`gfn, $sformatf("Currently num of pending write items is %0d", write_item_q.size), UVM_MEDIUM) end @@ -442,7 +442,8 @@ class sram_ctrl_scoreboard #(parameter int AddrWidth = 10) extends cip_base_scor `DV_CHECK_EQ(cfg.in_init, 0, "No item is accepted during init") if (status_lc_esc == EscNone && !item.is_write()) begin - mem_bkdr_scb.read_finish(item.d_data, simplify_addr(item.a_addr), item.a_mask); + mem_bkdr_scb.read_finish(item.d_data, simplify_addr(item.a_addr), + item.a_mask, !cfg.is_fi_test, !cfg.is_fi_test); end endtask diff --git a/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson b/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson index 0ef1cd95783ce7..9070afaaef1da3 100644 --- a/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson +++ b/hw/ip/sram_ctrl/dv/sram_ctrl_base_sim_cfg.hjson @@ -142,6 +142,10 @@ uvm_test_seq: "{name}_common_vseq" run_opts: ["+run_mem_partial_access"] } + { + name: "{name}_readback_err" + uvm_test_seq: sram_ctrl_readback_err_vseq + } ] // List of regressions.