| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| class entropy_src_scoreboard extends cip_base_scoreboard#( |
| .CFG_T(entropy_src_env_cfg), |
| .RAL_T(entropy_src_reg_block), |
| .COV_T(entropy_src_env_cov) |
| ); |
| |
| // TODO: Cleanup: remove all prim_mubi_pkg:: namespace identifiers (for consistency) |
| // As this changes many lines, we will do this cleanup in a separate PR |
| |
| `uvm_component_utils(entropy_src_scoreboard) |
| |
| // TODO (Cleanup): Synchronize these parameters with the DUT's parameters |
| localparam int SHACondWidth = 64; |
| localparam int ObserveFifoDepth = 64; |
| |
| intr_vif interrupt_vif; |
| virtual entropy_src_cov_if cov_vif; |
| |
| // used by health_test_scoring_thread to predict the FSMs phase |
| // when constructing seeds |
| int seed_idx = 0; |
| // number of seeds output since enable (or reset) |
| int seeds_out = 0; |
| |
| int entropy_data_seeds = 0; |
| int entropy_data_drops = 0; |
| int csrng_seeds = 0; |
| int csrng_drops = 0; |
| int observe_fifo_words = 0; |
| int observe_fifo_drops = 0; |
| |
| bit dut_pipeline_enabled = 0; |
| bit regwen_pending = 0; |
| bit ht_fips_mode = 0; |
| |
| // The FW_OV pipeline is controlled by two variables: SHA3_START and MODULE_ENABLE |
| // The MODULE_ENABLE signal has different delays when shutting down the FW_OV pipeline |
| // so we track it in a separate variable for fw_ov |
| bit fw_ov_sha_enabled = 0; |
| bit fw_ov_pipe_enabled = 0; |
| |
| // This scoreboard is not capable of anticipating with single-cycle accuracy whether the observe |
| // and entropy data FIFOs are empty. However, we can note when they have been explicitly cleared |
| // and use that to anticipate any alerts that may come about background diable events |
| bit fifos_cleared = 1; |
| |
| // Queue of RNG data for health testing |
| queue_of_rng_val_t health_test_data_q; |
| |
| // Queue of seeds for predicting reads to entropy_data CSR |
| bit [CSRNG_BUS_WIDTH - 1:0] entropy_data_q[$]; |
| |
| // Queue of TL_DW words for predicting outputs of the observe FIFO |
| bit [TL_DW - 1:0] observe_fifo_q[$]; |
| |
| // Queue of 64-bit words for inserting entropy input to the SHA (or raw) pipelines |
| bit [SHACondWidth - 1:0] sha_process_q[$]; |
| bit [SHACondWidth - 1:0] raw_process_q[$]; |
| |
| // Buffer to store SHA entropy when using FW_OV mode |
| bit [SHACondWidth - 1:0] repacked_entropy_fw_ov; |
| int repack_idx_fw_ov = 0; |
| |
| // The most recent candidate seed from entropy_data_q |
| // At each TL read the TL data item is compared to the appropriate |
| // 32-bit segment of this seed (as determented by seed_tl_read_cnt) |
| bit [CSRNG_BUS_WIDTH - 1:0] tl_best_seed_candidate; |
| |
| // The previous output seed (+ fips bit) We need to track this to determine whether to expect |
| // the bus_cmp recov_alert |
| bit [CSRNG_BUS_WIDTH : 0] prev_csrng_seed; |
| |
| // Number of 32-bit TL reads to the current (active) seed |
| // Ranges from 0 (no data read out) to CSRNG_BUS_WIDTH/TL_DW (seed fully read out) |
| int seed_tl_read_cnt = 0; |
| |
| bit [FIPS_CSRNG_BUS_WIDTH - 1:0] fips_csrng_q[$]; |
| |
| // TODO: Document Initial Conditions for health check. |
| // This should make no practical difference, but it is important for successful verification. |
| rng_val_t prev_rng_val = '0; |
| int repcnt [RNG_BUS_WIDTH]; |
| int repcnt_symbol; |
| |
| // Total number of repcnt OR repcnts failures for a particular sample. |
| // Some care is required in counting total failures as different |
| // types of failures happening in the same sample only get counted once. |
| int continuous_fail_count; |
| bit cont_fail_in_last_sample; |
| |
| // Ext. HT counters. |
| // Like the continuous tests, these failures can in principle happen many times |
| // per window, however only one failure per window gets registered toward the |
| // total alert count. |
| int extht_fail_count; |
| int extht_fail_in_last_sample; |
| |
| // TODO: Document method for clearing alerts in programmer's guide |
| // TODO: Add health check failure and clearing to integration tests. |
| bit threshold_alert_active = 1'b0; |
| |
| // Signal to indicate that the main sm is going into the error state |
| bit main_sm_escalates = 0; |
| |
| // Bit to signify that the module_enable bit is locked |
| bit dut_me_reglocked = 1'b0; |
| |
| // TLM agent fifos |
| uvm_tlm_analysis_fifo#(push_pull_item#(.HostDataWidth(FIPS_CSRNG_BUS_WIDTH))) |
| csrng_fifo; |
| uvm_tlm_analysis_fifo#(push_pull_item#(.HostDataWidth(RNG_BUS_WIDTH))) |
| rng_fifo; |
| uvm_tlm_analysis_fifo#(entropy_src_xht_item) xht_fifo; |
| |
| // Interrupt Management Variables |
| |
| // To track interrupt events we need to identify interupts have |
| // been previously observed to be high. |
| // |
| // An interrupt that was previously high is ignored until |
| // it is observed to be high again. |
| // |
| // Interrupts go high when a new interrupt is observed |
| // Interrupts should go low when an interrupt is cleared |
| bit [NumEntropySrcIntr - 1:0] known_intr_state = '0; |
| |
| bit [NumEntropySrcIntr - 1:0] intr_en_mask = '0; |
| bit [NumEntropySrcIntr - 1:0] intr_test = '0; |
| bit intr_test_active = '0; |
| |
| // Indicates that the observe fifo should have data in it. |
| // Switches to OBSERVE_FIFO_THRESHOLD when: |
| // A. A new observe fifo interrupt has been received. |
| // B. The interrupt has been cleared, but it persists a cycle later. |
| // Decrements by one when the fifo is read. |
| // Is cleared on Reset, or Disable, (stays cleared until enable) |
| int expected_obsfifo_entries_since_last_intr = 0; |
| |
| // Signal to communicate that TL data has inserted into the FW_OV FIFO at an invalid time. |
| // The DUT ignores such inputs and raises an alert. However in the interest of testing the |
| // response to the DUT to all the _ data that comes in, we mimic the DUT and ignore |
| // these data points once we notice one of these events. |
| bit ignore_fw_ov_data_pulse = 0; |
| |
| // Enabling, disabling and reset all have some effect in clearing the state of the DUT |
| // Due to subleties in timing, the DUT resets the Observe FIFO with a unique delay |
| typedef enum int { |
| HardReset, |
| Disable, |
| Enable, |
| FIFOClr, |
| FWOVDisable |
| } reset_event_e; |
| |
| `uvm_component_new |
| |
| function void build_phase(uvm_phase phase); |
| super.build_phase(phase); |
| |
| rng_fifo = new("rng_fifo", this); |
| csrng_fifo = new("csrng_fifo", this); |
| xht_fifo = new("xht_fifo", this); |
| |
| if (!uvm_config_db#(virtual entropy_src_cov_if)::get |
| (null, "*.env" , "entropy_src_cov_if", cov_vif)) begin |
| `uvm_fatal(`gfn, $sformatf("Failed to get entropy_src_cov_if from uvm_config_db")) |
| end |
| endfunction |
| |
| function void connect_phase(uvm_phase phase); |
| super.connect_phase(phase); |
| endfunction |
| |
| task run_phase(uvm_phase phase); |
| super.run_phase(phase); |
| if (cfg.en_scb) begin |
| fork |
| process_csrng(); |
| process_interrupts(); |
| process_fifo_exceptions(); |
| health_test_scoring_thread(); |
| join_none |
| end |
| endtask |
| |
| // Post simulation statistics reporting |
| function void report_phase(uvm_phase phase); |
| string fmt, msg; |
| |
| fmt = "Seeds read from entropy_data: %0d"; |
| msg = $sformatf(fmt, entropy_data_seeds); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| |
| fmt = "Seeds assumed dropped from entropy_data: %0d"; |
| msg = $sformatf(fmt, entropy_data_drops); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| |
| fmt = "Seeds read from csrng interface: %0d"; |
| msg = $sformatf(fmt, csrng_seeds); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| |
| fmt = "Seeds assumed dropped from csrng interface: %0d"; |
| msg = $sformatf(fmt, csrng_drops); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| |
| fmt = "Words read from observe fifo: %0d"; |
| msg = $sformatf(fmt, observe_fifo_words); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| |
| fmt = "Words assumed dropped from observe fifo: %0d"; |
| msg = $sformatf(fmt, observe_fifo_drops); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| endfunction |
| |
| // |
| // Health check test routines |
| // |
| |
| function void update_repcnts(bit fips_mode, rng_val_t rng_val); |
| int max_repcnt = 0; |
| bit repcnt_fail, repcnt_sym_fail; |
| uvm_reg_field alert_summary_field = ral.alert_summary_fail_counts.any_fail_count; |
| int any_fail_count_regval; |
| string fmt; |
| |
| for (int i = 0; i < RNG_BUS_WIDTH; i++) begin |
| if (rng_val[i] == prev_rng_val[i]) begin |
| repcnt[i]++; |
| end else begin |
| repcnt[i] = 1; |
| end |
| max_repcnt = (repcnt[i] > max_repcnt) ? repcnt[i] : max_repcnt; |
| end |
| `uvm_info(`gfn, $sformatf("max repcnt %0h", max_repcnt), UVM_DEBUG) |
| repcnt_fail = evaluate_repcnt_test(fips_mode, max_repcnt); |
| |
| if (rng_val == prev_rng_val) begin |
| repcnt_symbol++; |
| end else begin |
| repcnt_symbol = 1; |
| end |
| repcnt_sym_fail = evaluate_repcnt_symbol_test(fips_mode, repcnt_symbol); |
| |
| cont_fail_in_last_sample = repcnt_fail | repcnt_sym_fail; |
| continuous_fail_count += cont_fail_in_last_sample; |
| |
| any_fail_count_regval = `gmv(alert_summary_field); |
| any_fail_count_regval += cont_fail_in_last_sample; |
| `DV_CHECK_FATAL(alert_summary_field.predict(.value(any_fail_count_regval), |
| .kind(UVM_PREDICT_DIRECT))) |
| |
| if(cont_fail_in_last_sample) begin |
| fmt = "Predicted alert cnt for all tests: %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, any_fail_count_regval), UVM_HIGH) |
| end |
| |
| prev_rng_val = rng_val; |
| endfunction |
| |
| function int calc_adaptp_test(queue_of_rng_val_t window, output int maxval, output int minval); |
| int test_cnt[RNG_BUS_WIDTH]; |
| int minq[$], maxq[$]; |
| int result = '0; |
| for (int i = 0; i < window.size(); i++) begin |
| for (int j = 0; j < RNG_BUS_WIDTH; j++) begin |
| test_cnt[j] += window[i][j]; |
| end |
| end |
| maxq = test_cnt.max(); |
| maxval = maxq[0]; |
| minq = test_cnt.min(); |
| minval = minq[0]; |
| return test_cnt.sum(); |
| endfunction |
| |
| function int calc_bucket_test(queue_of_rng_val_t window); |
| int bin_count = (1 << RNG_BUS_WIDTH); |
| int result[$]; |
| |
| int buckets [] = new [bin_count]; |
| |
| for (int i = 0; i < window.size(); i++) begin |
| int elem = window[i]; |
| buckets[elem]++; |
| end |
| |
| for (int i = 0; i < bin_count; i++) begin |
| `uvm_info(`gfn, $sformatf("Bucket test. bin: %01h, value: %02h", i, buckets[i]), UVM_DEBUG) |
| end |
| |
| result = buckets.max(); |
| |
| `uvm_info(`gfn, $sformatf("Bucket test. max value: %02h", result[0]), UVM_FULL) |
| |
| return result[0]; |
| endfunction |
| |
| function int calc_markov_test(queue_of_rng_val_t window, output int maxval, output int minval); |
| int pair_cnt[RNG_BUS_WIDTH]; |
| int minq[$], maxq[$]; |
| for (int i = 0; i < window.size(); i += 2) begin |
| for (int j = 0; j < RNG_BUS_WIDTH; j++) begin |
| bit different = window[i][j] ^ window[i + 1][j]; |
| pair_cnt[j] += different; |
| end |
| end |
| maxq = pair_cnt.max(); |
| maxval = maxq[0]; |
| minq = pair_cnt.min(); |
| minval = minq[0]; |
| return pair_cnt.sum(); |
| endfunction |
| |
| function int calc_extht_test(queue_of_rng_val_t window); |
| // TODO |
| int result = 0; |
| return result; |
| endfunction |
| |
| // |
| // Debug tool: make sure that the following helper functions use proper names for health checks. |
| // |
| function void validate_test_name(string name); |
| bit is_valid; |
| is_valid = (name == "adaptp_hi") || |
| (name == "adaptp_lo") || |
| (name == "bucket" ) || |
| (name == "markov_hi") || |
| (name == "markov_lo") || |
| (name == "extht_hi" ) || |
| (name == "extht_lo" ) || |
| (name == "repcnt" ) || |
| (name == "repcnts" ); |
| `DV_CHECK_EQ(is_valid, 1, $sformatf("invalid test name: %s\n", name)) |
| endfunction |
| |
| function bit is_low_test(string name); |
| int len = name.len(); |
| return (name.substr(len - 3, len - 1) == "_lo"); |
| endfunction |
| |
| // Operate on the watermark for a given test, using the mirrored copy of the corresponding |
| // watermark register. |
| // |
| // If the value exceeds (or is less then) the latest watermark value, then update the prediction. |
| // |
| // Implements predictions for all registers named <test>_watermarks. |
| function void update_watermark(string test, bit fips_mode, int value); |
| string watermark_field_name; |
| string watermark_reg_name; |
| uvm_reg watermark_reg; |
| uvm_reg_field watermark_field; |
| int watermark_val; |
| bit low_test; |
| string fmt; |
| |
| validate_test_name(test); |
| |
| // The watermark registers for repcnt, repcnts and bucket tests deviate from the |
| // general convention of suppressing the "_hi" suffix for tests that do not have a low |
| // threshold. |
| if ((test == "repcnt") || (test == "repcnts") || (test == "bucket")) begin |
| test = {test, "_hi"}; |
| end |
| |
| watermark_field_name = fips_mode ? "fips_watermark" : "bypass_watermark"; |
| watermark_reg_name = $sformatf("%s_watermarks", test); |
| watermark_reg = ral.get_reg_by_name(watermark_reg_name); |
| watermark_field = watermark_reg.get_field_by_name(watermark_field_name); |
| watermark_val = watermark_field.get_mirrored_value(); |
| low_test = is_low_test(test); |
| |
| if (low_test) begin : low_watermark_check |
| if (value < watermark_val) begin |
| fmt = "Predicted LO watermark for \"%s\" test (FIPS? %d): %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, test, fips_mode, value), UVM_HIGH) |
| `DV_CHECK_FATAL(watermark_field.predict(.value(value), .kind(UVM_PREDICT_READ))) |
| end else begin |
| fmt = "LO watermark unchanged for \"%s\" test (FIPS? %d): %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, test, fips_mode, watermark_val), UVM_HIGH) |
| end |
| end else begin : high_watermark_check |
| if (value > watermark_val) begin |
| string fmt; |
| fmt = "Predicted HI watermark for \"%s\" test (FIPS? %d): %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, test, fips_mode, value), UVM_HIGH) |
| `DV_CHECK_FATAL(watermark_field.predict(.value(value), .kind(UVM_PREDICT_READ))) |
| end else begin |
| fmt = "HI watermark unchanged for \"%s\" test (FIPS? %d): %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, test, fips_mode, watermark_val), UVM_HIGH) |
| end |
| end : high_watermark_check |
| |
| endfunction |
| |
| // Compare a particular health check value against the corresponding thresholds. |
| // If the health check fails, log the failure and update our predictions for the alert registers. |
| // |
| // Implements predictions for all registers named: |
| // <test>_total_fails |
| // alert_fail_counts.<test>_fail_count |
| // extht_fail_counts.<test>_fail_count |
| // |
| // Because failing multiple tests for a single test only count as one total alert failure |
| // this routine does not update alert_summary_fail_counts |
| // |
| |
| function void predict_failure_logs(string test); |
| string total_fail_reg_name; |
| string total_fail_field_name; |
| string alert_cnt_reg_name; |
| string alert_cnt_field_name; |
| uvm_reg total_fail_reg; |
| uvm_reg alert_cnt_reg; |
| uvm_reg_field total_fail_field; |
| uvm_reg_field alert_cnt_field; |
| |
| bit [3:0] alert_cnt; |
| int fail_total; |
| |
| string fmt, msg; |
| |
| validate_test_name(test); |
| total_fail_reg_name = $sformatf("%s_total_fails", test); |
| total_fail_field_name = total_fail_reg_name; |
| |
| // Most tests have field in the alert_fail_counts register, except extht_fail_counts |
| if (test.substr(0, 5) == "extht_") begin |
| alert_cnt_reg_name = "extht_fail_counts"; |
| end else begin |
| alert_cnt_reg_name = "alert_fail_counts"; |
| end |
| alert_cnt_field_name = $sformatf("%s_fail_count", test); |
| |
| total_fail_reg = ral.get_reg_by_name(total_fail_reg_name); |
| total_fail_field = total_fail_reg.get_field_by_name(total_fail_field_name); |
| |
| alert_cnt_reg = ral.get_reg_by_name(alert_cnt_reg_name); |
| alert_cnt_field = alert_cnt_reg.get_field_by_name(alert_cnt_field_name); |
| |
| fail_total = total_fail_field.get_mirrored_value(); |
| alert_cnt = alert_cnt_field.get_mirrored_value(); |
| |
| // Update the predicted failure counters, noting that the DUT will not let these overflow |
| alert_cnt += (&alert_cnt) ? 0 : 1; |
| fail_total += (&fail_total) ? 0 : 1; |
| |
| fmt = "Previous alert cnt reg: %08h"; |
| msg = $sformatf(fmt, alert_cnt_reg.get_mirrored_value()); |
| `uvm_info(`gfn, msg, UVM_DEBUG) |
| |
| `DV_CHECK_FATAL(total_fail_field.predict(.value(fail_total), .kind(UVM_PREDICT_DIRECT))) |
| `DV_CHECK_FATAL( alert_cnt_field.predict(.value( alert_cnt), .kind(UVM_PREDICT_DIRECT))) |
| |
| fmt = "Predicted alert cnt for \"%s\" test: %04h"; |
| msg = $sformatf(fmt, test, alert_cnt_field.get_mirrored_value()); |
| `uvm_info(`gfn, msg, UVM_HIGH) |
| |
| fmt = "Entire alert cnt reg: %08h"; |
| msg = $sformatf(fmt, alert_cnt_reg.get_mirrored_value()); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| |
| fmt = "Predicted fail cnt for \"%s\" test: %01h"; |
| msg = $sformatf(fmt, test, total_fail_field.get_mirrored_value()); |
| `uvm_info(`gfn, msg, UVM_HIGH) |
| endfunction |
| |
| function bit check_threshold(string test, bit fips_mode, int value); |
| string threshold_reg_name; |
| string threshold_field_name; |
| uvm_reg threshold_reg; |
| uvm_reg_field threshold_field; |
| |
| int threshold_val; |
| bit continuous_test; |
| bit failure; |
| bit low_test; |
| string fmt, msg; |
| |
| validate_test_name(test); |
| low_test = is_low_test(test); |
| continuous_test = (test == "repcnt") || (test == "repcnts"); |
| |
| threshold_field_name = fips_mode ? "fips_thresh" : "bypass_thresh"; |
| threshold_reg_name = $sformatf("%s_thresholds", test); |
| |
| threshold_reg = ral.get_reg_by_name(threshold_reg_name); |
| threshold_field = threshold_reg.get_field_by_name(threshold_field_name); |
| |
| threshold_val = threshold_field.get_mirrored_value(); |
| |
| // Continuous tests are more rigorous about holding to the '>=' specified in NIST |
| // 800-90B. Meanwhile the windowed tests use "<" or ">" as this allows these tests |
| // to be temporarily disabled at boot, by choosing the maximal window size. |
| // TODO: Document this |
| if (continuous_test) begin |
| failure = (low_test && value <= threshold_val) || (!low_test && value >= threshold_val); |
| end else begin |
| failure = (low_test && value < threshold_val) || (!low_test && value > threshold_val); |
| end |
| |
| fmt = "Threshold for \"%s\" test (FIPS? %d): %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, test, fips_mode, threshold_val), UVM_FULL) |
| |
| fmt = "Observed value for \"%s\" test (FIPS? %d): %04h, %s"; |
| `uvm_info(`gfn, $sformatf(fmt, test, fips_mode, value, failure ? "FAIL" : "PASS"), UVM_FULL) |
| |
| return failure; |
| |
| endfunction |
| |
| |
| // Indicates that the health test is active |
| // Also indicates whether the sigma value in the dut_cfg is being employed |
| function bit ht_is_active(); |
| bit fw_insert, sigma_applied; |
| |
| fw_insert = (ral.fw_ov_control.fw_ov_mode.get_mirrored_value() == MuBi4True) && |
| (ral.fw_ov_control.fw_ov_entropy_insert.get_mirrored_value() == MuBi4True); |
| |
| // TODO (Priority 3): This use of the dut_cfg depends very much on the vseq being employed. |
| sigma_applied = !cfg.dut_cfg.default_ht_thresholds; |
| |
| return !fw_insert && sigma_applied; |
| |
| endfunction |
| |
| function bit evaluate_adaptp_test(queue_of_rng_val_t window, bit fips_mode); |
| int value, minval, maxval; |
| bit fail_hi, fail_lo; |
| bit total_scope; |
| int threshold_hi, threshold_lo; |
| real sigma_hi, sigma_lo; |
| |
| int window_size = fips_mode ? `gmv(ral.health_test_windows.fips_window) : |
| `gmv(ral.health_test_windows.bypass_window); |
| |
| threshold_hi = fips_mode ? `gmv(ral.adaptp_hi_thresholds.fips_thresh) : |
| `gmv(ral.adaptp_hi_thresholds.bypass_thresh); |
| |
| threshold_lo = fips_mode ? `gmv(ral.adaptp_lo_thresholds.fips_thresh) : |
| `gmv(ral.adaptp_lo_thresholds.bypass_thresh); |
| |
| total_scope = (ral.conf.threshold_scope.get_mirrored_value() == MuBi4True); |
| |
| sigma_hi = ideal_threshold_to_sigma(window_size, adaptp_ht, !total_scope, |
| high_test, threshold_hi); |
| sigma_lo = ideal_threshold_to_sigma(window_size, adaptp_ht, !total_scope, |
| low_test, threshold_lo); |
| |
| value = calc_adaptp_test(window, maxval, minval); |
| |
| update_watermark("adaptp_lo", fips_mode, total_scope ? value : minval); |
| update_watermark("adaptp_hi", fips_mode, total_scope ? value : maxval); |
| |
| fail_lo = check_threshold("adaptp_lo", fips_mode, total_scope ? value : minval); |
| if (fail_lo) predict_failure_logs("adaptp_lo"); |
| |
| fail_hi = check_threshold("adaptp_hi", fips_mode, total_scope ? value : maxval); |
| if (fail_hi) predict_failure_logs("adaptp_hi"); |
| |
| |
| if (ht_is_active()) begin |
| cov_vif.cg_win_ht_sample(adaptp_ht, high_test, window_size * RNG_BUS_WIDTH, fail_hi); |
| cov_vif.cg_win_ht_sample(adaptp_ht, low_test, window_size * RNG_BUS_WIDTH, fail_lo); |
| cov_vif.cg_win_ht_deep_threshold_sample(adaptp_ht, high_test, window_size * RNG_BUS_WIDTH, |
| !total_scope, sigma_hi, fail_hi); |
| cov_vif.cg_win_ht_deep_threshold_sample(adaptp_ht, low_test, window_size * RNG_BUS_WIDTH, |
| !total_scope, sigma_lo, fail_lo); |
| end |
| |
| return (fail_hi || fail_lo); |
| endfunction |
| |
| function bit evaluate_bucket_test(queue_of_rng_val_t window, bit fips_mode); |
| int value; |
| bit fail; |
| int threshold; |
| real sigma; |
| |
| int window_size = fips_mode ? `gmv(ral.health_test_windows.fips_window) : |
| `gmv(ral.health_test_windows.bypass_window); |
| |
| threshold = fips_mode ? `gmv(ral.bucket_thresholds.fips_thresh) : |
| `gmv(ral.bucket_thresholds.bypass_thresh); |
| |
| sigma = ideal_threshold_to_sigma(window_size, bucket_ht, 0, high_test, threshold); |
| |
| value = calc_bucket_test(window); |
| |
| update_watermark("bucket", fips_mode, value); |
| |
| fail = check_threshold("bucket", fips_mode, value); |
| if (fail) predict_failure_logs("bucket"); |
| |
| if (ht_is_active()) begin |
| cov_vif.cg_win_ht_sample(bucket_ht, high_test, window_size*RNG_BUS_WIDTH, fail); |
| cov_vif.cg_win_ht_deep_threshold_sample(bucket_ht, high_test, window_size*RNG_BUS_WIDTH, |
| 1'b0, sigma, fail); |
| end |
| |
| return fail; |
| endfunction |
| |
| function bit evaluate_markov_test(queue_of_rng_val_t window, bit fips_mode); |
| int value, minval, maxval; |
| bit fail_hi, fail_lo; |
| bit total_scope; |
| int threshold_hi, threshold_lo; |
| real sigma_hi, sigma_lo; |
| |
| int window_size = fips_mode ? `gmv(ral.health_test_windows.fips_window) : |
| `gmv(ral.health_test_windows.bypass_window); |
| |
| threshold_hi = fips_mode ? `gmv(ral.markov_hi_thresholds.fips_thresh) : |
| `gmv(ral.markov_hi_thresholds.bypass_thresh); |
| |
| threshold_lo = fips_mode ? `gmv(ral.markov_lo_thresholds.fips_thresh) : |
| `gmv(ral.markov_lo_thresholds.bypass_thresh); |
| |
| total_scope = (ral.conf.threshold_scope.get_mirrored_value() == prim_mubi_pkg::MuBi4True); |
| |
| sigma_hi = ideal_threshold_to_sigma(window_size, markov_ht, !total_scope, |
| high_test, threshold_hi); |
| sigma_lo = ideal_threshold_to_sigma(window_size, markov_ht, !total_scope, |
| low_test, threshold_lo); |
| |
| value = calc_markov_test(window, maxval, minval); |
| |
| update_watermark("markov_lo", fips_mode, total_scope ? value : minval); |
| update_watermark("markov_hi", fips_mode, total_scope ? value : maxval); |
| |
| fail_lo = check_threshold("markov_lo", fips_mode, total_scope ? value : minval); |
| if (fail_lo) predict_failure_logs("markov_lo"); |
| |
| fail_hi = check_threshold("markov_hi", fips_mode, total_scope ? value : maxval); |
| if (fail_hi) predict_failure_logs("markov_hi"); |
| |
| if (ht_is_active()) begin |
| cov_vif.cg_win_ht_sample(markov_ht, high_test, window_size*RNG_BUS_WIDTH, fail_hi); |
| cov_vif.cg_win_ht_sample(markov_ht, low_test, window_size*RNG_BUS_WIDTH, fail_lo); |
| cov_vif.cg_win_ht_deep_threshold_sample(markov_ht, high_test, window_size*RNG_BUS_WIDTH, |
| !total_scope, sigma_hi, fail_hi); |
| cov_vif.cg_win_ht_deep_threshold_sample(markov_ht, low_test, window_size*RNG_BUS_WIDTH, |
| !total_scope, sigma_hi, fail_lo); |
| end |
| |
| return (fail_hi || fail_lo); |
| endfunction |
| |
| function void evaluate_external_ht(entropy_src_xht_rsp_t xht_rsp, bit fips_mode); |
| int value_hi, value_lo; |
| bit fail_hi, fail_lo; |
| string msg; |
| |
| value_hi = xht_rsp.test_cnt_hi; |
| value_lo = xht_rsp.test_cnt_lo; |
| |
| fail_hi = xht_rsp.test_fail_hi_pulse; |
| fail_lo = xht_rsp.test_fail_lo_pulse; |
| |
| update_watermark("extht_lo", fips_mode, value_lo); |
| update_watermark("extht_hi", fips_mode, value_hi); |
| |
| if (fail_lo) predict_failure_logs("extht_lo"); |
| |
| if (fail_hi) predict_failure_logs("extht_hi"); |
| |
| extht_fail_count += (fail_hi || fail_lo); |
| extht_fail_in_last_sample = fail_hi || fail_lo; |
| endfunction |
| |
| // The repetition counts are always running |
| function bit evaluate_repcnt_test(bit fips_mode, int value); |
| bit fail; |
| bit rng_en = (`gmv(ral.conf.rng_bit_enable) == MuBi4True); |
| |
| update_watermark("repcnt", fips_mode, value); |
| |
| fail = check_threshold("repcnt", fips_mode, value); |
| if (fail) begin |
| `uvm_info(`gfn, "repcnt failure detected", UVM_FULL) |
| predict_failure_logs("repcnt"); |
| end |
| |
| if (ht_is_active()) begin |
| cov_vif.cg_cont_ht_sample(repcnt_ht, fips_mode, rng_en, `gmv(ral.conf.rng_bit_sel), |
| value, fail); |
| end |
| |
| return fail; |
| |
| endfunction |
| |
| function bit evaluate_repcnt_symbol_test(bit fips_mode, int value); |
| bit fail; |
| bit rng_en = (`gmv(ral.conf.rng_bit_enable) == MuBi4True); |
| |
| update_watermark("repcnts", fips_mode, value); |
| |
| fail = check_threshold("repcnts", fips_mode, value); |
| if (fail) begin |
| `uvm_info(`gfn, "repcnts failure detected", UVM_FULL) |
| predict_failure_logs("repcnts"); |
| end |
| |
| if (ht_is_active()) begin |
| cov_vif.cg_cont_ht_sample(repcnts_ht, fips_mode, rng_en, `gmv(ral.conf.rng_bit_sel), |
| value, fail); |
| end |
| |
| return fail; |
| endfunction |
| |
| function int health_check_rng_data(queue_of_rng_val_t window, |
| bit fips_mode); |
| int windowed_fail_count; |
| int total_fail_count; |
| int overcount; |
| bit sample_fail_count; |
| uvm_reg_field alert_summary_field = ral.alert_summary_fail_counts.any_fail_count; |
| int any_fail_count_regval; |
| string fmt; |
| |
| windowed_fail_count = evaluate_adaptp_test(window, fips_mode) + |
| evaluate_bucket_test(window, fips_mode) + |
| evaluate_markov_test(window, fips_mode); |
| |
| // Are there any failures this in the last sample (continuous or windowed)? |
| // Note: If the last sample in the window has a continuous HT failure, then |
| // this sample has already been added to the failure counts |
| sample_fail_count = (windowed_fail_count != 0); |
| |
| // Add the number of continuous fails (excluding the last sample) |
| // to any failure in this sample. |
| total_fail_count = sample_fail_count + continuous_fail_count + extht_fail_count; |
| |
| // Implementation artifact: |
| // Account for the fact that simultaneous failures for windowed, continuous and ExtHT tests |
| // only get counted once in the total failure count if they coincidentally occur in the last |
| // sample. |
| overcount = sample_fail_count + cont_fail_in_last_sample + extht_fail_in_last_sample; |
| overcount -= (overcount >= 1); |
| total_fail_count -= overcount; |
| |
| // To avoid double counting only mark a sample failure if there haven't |
| // already been continuous or extht failures at the same time. |
| if (sample_fail_count && !(cont_fail_in_last_sample || extht_fail_in_last_sample)) begin |
| any_fail_count_regval = `gmv(alert_summary_field); |
| // Just add any failure in the last sample as the previous samples |
| // were added as they occurred. |
| any_fail_count_regval++; |
| `DV_CHECK_FATAL(alert_summary_field.predict(.value(any_fail_count_regval), |
| .kind(UVM_PREDICT_DIRECT))) |
| fmt = "Predicted alert cnt for all tests: %04h"; |
| `uvm_info(`gfn, $sformatf(fmt, any_fail_count_regval), UVM_HIGH) |
| end |
| |
| continuous_fail_count = 0; |
| extht_fail_count = 0; |
| |
| return total_fail_count; |
| endfunction |
| |
| function void process_failures(int total_fail_count, |
| bit fw_ov_insert, |
| entropy_phase_e dut_phase, |
| int successive_win_fail_count); |
| |
| bit failure = 0; |
| |
| uvm_reg alert_fail_reg = ral.alert_fail_counts; |
| uvm_reg extht_fail_reg = ral.extht_fail_counts; |
| uvm_reg_field any_fail_count_fld = ral.alert_summary_fail_counts.any_fail_count; |
| string fmt; |
| int any_fail_count_regval; |
| int alert_threshold; |
| bit main_sm_exp_alert_cond; |
| |
| // TODO: If an alert is anticipated, we should check that (if necessary) this seed is |
| // stopped and no others are allowed to progress. |
| alert_threshold = `gmv(ral.alert_threshold.alert_threshold); |
| |
| fmt = "Predicting alert status with %0d new failures this window"; |
| `uvm_info(`gfn, $sformatf(fmt, total_fail_count), UVM_FULL) |
| |
| any_fail_count_regval = `gmv(any_fail_count_fld); |
| |
| failure = (total_fail_count != 0); |
| main_sm_exp_alert_cond = (dut_phase == STARTUP) ? |
| (successive_win_fail_count >= 2) : |
| (any_fail_count_regval >= alert_threshold); |
| |
| if (failure) begin : test_failure |
| if (main_sm_exp_alert_cond) begin |
| if (!fw_ov_insert && !threshold_alert_active && !main_sm_escalates) begin |
| if (dut_phase == STARTUP) begin |
| fmt = "New alert anticpated with >= 2 failing windows."; |
| fmt += "(supercedes count/threshold of %01d/%01d)"; |
| end else begin |
| fmt = "New alert anticpated! Fail count (%01d) >= threshold (%01d)"; |
| end |
| threshold_alert_active = 1; |
| `DV_CHECK_FATAL(ral.recov_alert_sts.es_main_sm_alert.predict(1'b1)); |
| set_exp_alert(.alert_name("recov_alert"), .is_fatal(0), .max_delay(cfg.alert_max_delay)); |
| // The DUT should either set the alert, or crash the sim. |
| // If we succeed, sample this alert_threshold as covered successfully. |
| cov_vif.cg_alert_cnt_sample(alert_threshold, 1); |
| end else if (main_sm_escalates) begin |
| fmt = "Main SM in error state, overrides recov alert (Fail cnt: %01d, thresh: %01d)"; |
| end else if(threshold_alert_active) begin |
| fmt = "Alert already signalled: Fail count (%01d) >= threshold (%01d)"; |
| end else begin |
| fmt = "FW_OV mode, alerts suppressed: Fail count (%01d) >= threshold (%01d)"; |
| end |
| `uvm_info(`gfn, $sformatf(fmt, any_fail_count_regval, alert_threshold), UVM_HIGH) |
| end else begin |
| fmt = "No alert anticpated. fail count (%01d) < threshold (%01d)"; |
| `uvm_info(`gfn, $sformatf(fmt, any_fail_count_regval, alert_threshold), UVM_HIGH) |
| end |
| end else begin : no_test_failure |
| if (!threshold_alert_active && !main_sm_escalates) begin |
| any_fail_count_regval = 0; |
| // Now we know that all tests have passed we can clear the failure counts |
| `DV_CHECK_FATAL(alert_fail_reg.predict(.value({TL_DW{1'b0}}), .kind(UVM_PREDICT_DIRECT))) |
| `DV_CHECK_FATAL(extht_fail_reg.predict(.value({TL_DW{1'b0}}), .kind(UVM_PREDICT_DIRECT))) |
| `DV_CHECK_FATAL(any_fail_count_fld.predict(.value('0), .kind(UVM_PREDICT_DIRECT))) |
| end else begin |
| fmt = "Alert state persists: Fail count (%01d) >= threshold (%01d)"; |
| `uvm_info(`gfn, $sformatf(fmt, any_fail_count_regval, alert_threshold), UVM_HIGH) |
| end |
| end : no_test_failure |
| |
| endfunction |
| |
| // |
| // Helper functions for process_entropy_data_csr_access |
| // |
| |
| function bit try_seed_tl(input bit [CSRNG_BUS_WIDTH - 1:0] new_candidate, |
| input bit [TL_DW - 1:0] tl_data, |
| output bit [TL_DW - 1:0] tl_prediction); |
| bit [CSRNG_BUS_WIDTH - 1:0] mask, new_seed_masked, best_seed_masked; |
| bit matches_prev_reads; |
| bit matches_tl_data; |
| string fmt; |
| |
| mask = '0; |
| |
| for(int i = 0; i < seed_tl_read_cnt; i++) begin |
| mask[i * TL_DW +: TL_DW] = {TL_DW{1'b1}}; |
| end |
| new_seed_masked = (new_candidate & mask); |
| best_seed_masked = (tl_best_seed_candidate & mask); |
| matches_prev_reads = (best_seed_masked == new_seed_masked); |
| |
| if (matches_prev_reads) begin |
| // Only log this if the new seed is different from the previous best: |
| if (new_candidate != tl_best_seed_candidate) begin |
| string fmt = "Found another match candidate after %01d total dropped seeds"; |
| `uvm_info(`gfn, $sformatf(fmt, entropy_data_drops), UVM_HIGH) |
| end |
| end else begin |
| `uvm_info(`gfn, "New candidate seed does not match previous segments", UVM_HIGH) |
| fmt = "New seed: %096h, Best seed: %096h"; |
| // In the log mask out portions that have not been compared yet, for contrast |
| `uvm_info(`gfn, $sformatf(fmt, new_seed_masked, best_seed_masked), UVM_HIGH) |
| return 0; |
| end |
| |
| tl_prediction = new_candidate[TL_DW * seed_tl_read_cnt +: TL_DW]; |
| matches_tl_data = (tl_prediction == tl_data); |
| |
| if (tl_prediction == tl_data) begin |
| tl_best_seed_candidate = new_candidate; |
| fmt = "Seed matches TL data after %d TL reads"; |
| `uvm_info(`gfn, $sformatf(fmt, seed_tl_read_cnt+1), UVM_HIGH) |
| return 1; |
| end else begin |
| fmt = "TL DATA (%08h) does not match predicted seed segment (%08h)"; |
| `uvm_info(`gfn, $sformatf(fmt, tl_data, tl_prediction), UVM_HIGH) |
| return 0; |
| end |
| |
| endfunction |
| |
| // Helper routine for process_tl_access |
| // |
| // Since the DUT may, by design, internally drop data, it is not sufficient to check against |
| // one seed, we compare TL data values against all available seeds. |
| // If no match is found then the access is in error. |
| // |
| task process_entropy_data_csr_access(tl_seq_item item, uvm_reg csr); |
| |
| bit [TL_DW - 1:0] ed_pred_data; |
| bit match_found; |
| bit entropy_data_reg_enable; |
| bit module_enabled; |
| entropy_data_reg_enable = (cfg.otp_en_es_fw_read == MuBi8True) && |
| (ral.conf.entropy_data_reg_enable.get_mirrored_value() == MuBi4True); |
| |
| module_enabled = (ral.module_enable.module_enable.get_mirrored_value() == MuBi4True); |
| match_found = 0; |
| |
| if(!entropy_data_reg_enable || !module_enabled) begin |
| // ignore the contents of the entropy_data fifo. |
| `DV_CHECK_FATAL(csr.predict(.value({TL_DW{1'b0}}), .kind(UVM_PREDICT_READ))) |
| match_found = 1; |
| end else begin |
| while (entropy_data_q.size() > 0) begin : seed_trial_loop |
| bit [TL_DW - 1:0] prediction; |
| |
| `uvm_info(`gfn, $sformatf("seed_tl_read_cnt: %01d", seed_tl_read_cnt), UVM_FULL) |
| match_found = try_seed_tl(entropy_data_q[0], item.d_data, prediction); |
| if (match_found) begin |
| bit full_seed_found = 0; |
| `DV_CHECK_FATAL(csr.predict(.value(prediction), .kind(UVM_PREDICT_READ))) |
| seed_tl_read_cnt++; |
| if (seed_tl_read_cnt == CSRNG_BUS_WIDTH / TL_DW) begin |
| bit [CSRNG_BUS_WIDTH - 1:0] full_seed; |
| |
| full_seed_found = 1; |
| seed_tl_read_cnt = 0; |
| full_seed = entropy_data_q.pop_front(); |
| entropy_data_seeds++; |
| |
| end else if (seed_tl_read_cnt > CSRNG_BUS_WIDTH / TL_DW) begin |
| `uvm_error(`gfn, "testbench error: too many segments read from candidate seed") |
| end |
| cov_vif.cg_seed_output_csr_sample( |
| prim_mubi_pkg::mubi4_t'(ral.conf.fips_enable.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.conf.threshold_scope.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.conf.rng_bit_enable.get_mirrored_value()), |
| ral.conf.rng_bit_sel.get_mirrored_value(), |
| prim_mubi_pkg::mubi4_t'(ral.entropy_control.es_route.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.entropy_control.es_type.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.conf.entropy_data_reg_enable.get_mirrored_value()), |
| prim_mubi_pkg::mubi8_t'(cfg.otp_en_es_fw_read), |
| prim_mubi_pkg::mubi4_t'(ral.fw_ov_control.fw_ov_mode.get_mirrored_value()), |
| prim_mubi_pkg::mubi8_t'(cfg.otp_en_es_fw_over), |
| prim_mubi_pkg::mubi4_t'(ral.fw_ov_control.fw_ov_entropy_insert.get_mirrored_value()), |
| full_seed_found |
| ); |
| break; |
| end else begin |
| void'(entropy_data_q.pop_front()); |
| entropy_data_drops++; |
| end |
| end : seed_trial_loop |
| end |
| |
| `DV_CHECK_EQ_FATAL(match_found, 1, |
| "All candidate seeds have been checked. ENTROPY_DATA does not match") |
| endtask |
| |
| function void clear_ht_stat_predictions(); |
| string stat_regs [] = '{ |
| "repcnt_hi_watermarks", "repcnts_hi_watermarks", "adaptp_hi_watermarks", |
| "adaptp_lo_watermarks", "extht_hi_watermarks", "extht_lo_watermarks", |
| "bucket_hi_watermarks", "markov_hi_watermarks", "markov_lo_watermarks", |
| "repcnt_total_fails", "repcnts_total_fails", "adaptp_hi_total_fails", |
| "adaptp_lo_total_fails", "bucket_total_fails", "markov_hi_total_fails", |
| "markov_lo_total_fails", "extht_hi_total_fails", "extht_lo_total_fails", |
| "alert_summary_fail_counts", "alert_fail_counts", "extht_fail_counts" |
| }; |
| foreach (stat_regs[i]) begin |
| uvm_reg csr = ral.get_reg_by_name(stat_regs[i]); |
| void'(csr.predict(.value(csr.get_reset()), .kind(UVM_PREDICT_READ))); |
| end |
| endfunction |
| |
| // Clear all relevant prediction variables for |
| // Reset, disable, enable and (delayed) FIFOClr reset events. |
| function void handle_disable_reset(reset_event_e rst_type); |
| bit is_fw_ov = (`gmv(ral.fw_ov_control.fw_ov_mode) == MuBi4True) && |
| (`gmv(ral.fw_ov_control.fw_ov_entropy_insert) == MuBi4True); |
| |
| if (rst_type == Enable) begin |
| clear_ht_stat_predictions(); |
| seeds_out = 0; |
| health_test_data_q.delete(); |
| end |
| |
| // Internal CSRNG stores are cleared on Disable and HardReset events |
| if( rst_type == Disable || rst_type == HardReset ) begin |
| fips_csrng_q.delete(); |
| end |
| |
| // The SHA3 engine is the one unit that is not always cleared on |
| // disable. |
| // It clears itself in on disable in normal RNG mode. |
| // However, in FW_OV mode it only clears itself |
| // when a digest is output or on hard reset so in this case, |
| // we leave the sha_process_q alone to represent the fact |
| // that there is still data in the SHA3 state. |
| // |
| // However any entropy absorbed in raw mode (and stashed in |
| // raw_process_q), will always be lost on disable. |
| if(rst_type == HardReset) begin |
| sha_process_q.delete(); |
| raw_process_q.delete(); |
| repack_idx_fw_ov = 0; |
| intr_test = '0; |
| intr_test_active = 0; |
| regwen_pending = 0; |
| end |
| |
| if (rst_type == Disable) begin |
| raw_process_q.delete(); |
| end |
| |
| if (rst_type == FWOVDisable) begin |
| // For FW_OV mode the 64 bit packer is also cleared. |
| repack_idx_fw_ov = 0; |
| end |
| // Note: For non-FW_OV mode, the repack_idx_tl and repack_idx_sha counters are stack variables |
| // which get reset automatically when the collect entropy task exits. |
| |
| if (rst_type == FIFOClr) begin |
| observe_fifo_q.delete(); |
| entropy_data_q.delete(); |
| end |
| |
| // reset all other statistics |
| threshold_alert_active = 0; |
| main_sm_escalates = 0; |
| |
| seed_idx = 0; |
| seed_tl_read_cnt = 0; |
| |
| for (int i = 0; i < RNG_BUS_WIDTH; i++) begin |
| `uvm_info(`gfn, "Clearing REPCNTS cntr", UVM_DEBUG) |
| repcnt[i] = 1; |
| end |
| repcnt_symbol = 1; |
| |
| prev_rng_val = '0; |
| |
| // Clear records of repcnt/repnts failures |
| continuous_fail_count = 0; |
| cont_fail_in_last_sample = 0; |
| |
| // Clear interrupt state |
| known_intr_state = 0; |
| intr_en_mask = 0; |
| |
| expected_obsfifo_entries_since_last_intr = 0; |
| |
| // After this event, all other inputs from the RNG interface will be discarded by the DUT |
| // so we flush this queue to reflect the fact that the DUT will not be folding these into |
| // outputs. |
| rng_fifo.flush(); |
| // Note the CSRNG TLM analysis fifo should NOT be flushed, as it contains actual DUT |
| // outputs which must be scoreboarded |
| |
| `uvm_info(`gfn, $sformatf("%s Detected", rst_type.name), UVM_MEDIUM) |
| endfunction |
| |
| // Update our behavioral predictions based on new interrupts |
| // from_csr: 1 if the new information was observed from the intr_state register |
| // 0 if it was observed from the interrupt pins |
| function void handle_new_interrupts(bit [NumEntropySrcIntr - 1:0] new_events, |
| bit from_csr); |
| string msg; |
| |
| if (new_events[ObserveFifoReady]) begin |
| bit [6:0] obs_fifo_threshold = |
| ral.observe_fifo_thresh.observe_fifo_thresh.get_mirrored_value(); |
| bit valid_thresh = ((obs_fifo_threshold <= ObserveFifoDepth) && (obs_fifo_threshold != 0)); |
| msg = $sformatf("No ObsFifo interrupts should be rec'd for threshold 0x%0h", |
| obs_fifo_threshold); |
| `DV_CHECK_FATAL(valid_thresh, msg) |
| expected_obsfifo_entries_since_last_intr = int'(obs_fifo_threshold); |
| msg = $sformatf("Expecting at least 0x%0h new obsfifo entries", |
| expected_obsfifo_entries_since_last_intr); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| end |
| known_intr_state = known_intr_state | new_events; |
| endfunction |
| |
| function void clear_interrupts(bit [NumEntropySrcIntr - 1:0] clear_mask); |
| known_intr_state &= ~clear_mask; |
| intr_test &= ~clear_mask; |
| `uvm_info(`gfn, $sformatf("clear_mask: %01h", clear_mask), UVM_FULL) |
| `uvm_info(`gfn, $sformatf("known_data: %01h", known_intr_state), UVM_FULL) |
| endfunction |
| |
| task process_interrupts(); |
| entropy_src_intr_e i; |
| bit [NumEntropySrcIntr - 1:0] new_intrs; |
| `uvm_info(`gfn, "STARTING INTERRUPT SCOREBOARD LOOP", UVM_DEBUG) |
| forever begin |
| `uvm_info(`gfn, "WAITING FOR INTERRUPT", UVM_DEBUG) |
| @(interrupt_vif.pins); |
| new_intrs = interrupt_vif.pins & ~known_intr_state; |
| handle_new_interrupts(new_intrs, 0); |
| for (i = i.first(); i < i.last(); i = i.next()) begin |
| cov.intr_pins_cg.sample(i, interrupt_vif.pins[i]); |
| if (new_intrs[i]) begin |
| `uvm_info(`gfn, $sformatf("INTERRUPT RECEIVED: %0s", i.name), UVM_FULL) |
| end |
| end |
| end |
| endtask |
| |
| // All the HT threshold registers are one-way: they can only become more strict unless |
| // the DUT is reset. This function encapsulates this behavior. |
| // |
| // This function operates on full TL_DW words, with some knowledge of the structure of each |
| // register. |
| // 1. These registers are consist of two 16b thresholds a bypass and a FIPS threshold. |
| // The one-way restriction is applied to them independently. |
| // 2. Both thresholds have the same directional restriction: both can go up or both can go down. |
| // If the structure of these registers ever becomes more varied we will have to generalize this |
| // function, using structural cues from the RAL model |
| // |
| // new_val: The value to be written to the register |
| // prev_val: The current value of the register |
| // increase_only: 1 if the register values are allowed to increase. |
| // |
| // Returns the new predicted value for the register. |
| function void predict_one_way_threshold(uvm_reg csr, |
| bit [TL_DW - 1:0] write_val, |
| bit increase_only); |
| |
| localparam int ThreshW = 16; |
| bit [TL_DW - 1:0] prev_val = `gmv(csr); |
| int offset = csr.get_offset(); |
| bit [ThreshW - 1:0] new_thresh, prev_thresh, thresh_out; |
| bit [TL_DW - 1:0] result; |
| int i; |
| string msg, fmt; |
| |
| for (i=0; i < TL_DW; i+=ThreshW) begin |
| bit is_fips_thresh = (i==0); |
| bit update_rejected; |
| new_thresh = write_val[i +: ThreshW]; |
| prev_thresh = prev_val[i +: ThreshW]; |
| thresh_out = increase_only ? (new_thresh > prev_thresh ? new_thresh : prev_thresh) : |
| (new_thresh < prev_thresh ? new_thresh : prev_thresh); |
| update_rejected = (thresh_out != new_thresh); |
| result[i +: ThreshW] = thresh_out; |
| cov_vif.cg_one_way_ht_threshold_reg_sample(offset, update_rejected, is_fips_thresh); |
| end |
| fmt = "Threshold Reg Update. Offset: %08x (%s), Orig: %08x, New: %08x, Final: %08x"; |
| msg = $sformatf(fmt, offset, prev_val, increase_only ? "INCREASES" : "DECREASES", |
| write_val, result); |
| `uvm_info(`gfn, msg, UVM_DEBUG); |
| void'(csr.predict(.value(result), .kind(UVM_PREDICT_WRITE))); |
| endfunction |
| |
| // Function to check for correct values to register fields with mandatory redundancy |
| // (i.e. MultiBit boolean values or the ALERT_THRESHOLD register). |
| // |
| // Performs several scoreboarding functions: |
| // It checks that the recently written (mirrored) value is valid. If invalid, the function: |
| // - Expects a recovereable alert |
| // - Updates the prediction for the RECOV_ALERT_STS register |
| // - Samples the relevant coverpoint for recoverable alert events. |
| // |
| // Arguments: |
| // reg_name: the register to check |
| // mubi_field: the specific field to examine (when checking for bad MuBi's) |
| // sts_field_name: the name of the field to assert |
| // which_mubi: The associated coverpoint value to assert when a bad |
| // redundancy is discovered. (includes bad writes to alert threshold |
| // as well as all MuBi fields). |
| virtual function void check_redundancy_val(string reg_name, string mubi_field, |
| string sts_field_name, invalid_mubi_e which_mubi); |
| bit bad_redundancy; |
| // Check the currently predicted value for the desired register and field |
| // |
| // Almost all of the redundant values are isolated MultiBit Booleans except for |
| // ALERT_THRESHOLD in which the threhold field must equal the inverse of the |
| // inverse threshold field. |
| if (reg_name != "alert_threshold") begin |
| bad_redundancy = mubi4_test_invalid( |
| prim_mubi_pkg::mubi4_t'(get_reg_fld_mirror_value(ral, reg_name, mubi_field))); |
| end else begin |
| bit [15:0] thresh = get_reg_fld_mirror_value(ral, "alert_threshold", "alert_threshold"); |
| bit [15:0] thresh_inv = get_reg_fld_mirror_value(ral, "alert_threshold", |
| "alert_threshold_inv"); |
| bad_redundancy = (thresh != ~thresh_inv); |
| end |
| |
| if (bad_redundancy) begin |
| uvm_reg_field sts_field = ral.recov_alert_sts.get_field_by_name(sts_field_name); |
| `DV_CHECK_FATAL(sts_field.predict(.value(1'b1), .kind(UVM_PREDICT_READ))) |
| set_exp_alert(.alert_name("recov_alert"), .is_fatal(0)); |
| cov_vif.cg_mubi_err_sample(which_mubi); |
| end |
| endfunction |
| |
| virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); |
| uvm_reg csr; |
| bit do_read_check = 1'b1; |
| // Identifies a register as a one-way threshold (they can only get tighter until reset) |
| bit one_way_threshold = 1'b0; |
| // Specifies the direction of the one-way threshold |
| bit threshold_increases = 1'b0; |
| bit locked_reg_access = 1'b0; |
| bit dut_reg_locked, sw_regupd, module_enabled; |
| bit write = item.is_write(); |
| uvm_reg_addr_t csr_addr = cfg.ral_models[ral_name].get_word_aligned_addr(item.a_addr); |
| string msg; |
| |
| dut_reg_locked = ~`gmv(ral.regwen.regwen); |
| |
| // if access was to a valid csr, get the csr handle |
| if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin |
| csr = cfg.ral_models[ral_name].default_map.get_reg_by_offset(csr_addr); |
| `DV_CHECK_NE_FATAL(csr, null) |
| end else begin |
| `uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr)) |
| end |
| |
| // process the csr req |
| // for write, update local variable and fifo at address phase |
| // for read, update predication at address phase and compare at data phase |
| case (csr.get_name()) |
| // add individual case item for each csr |
| "intr_state": begin |
| // We do not predict the interrupt_state, as there are too many |
| // asynchronous events. |
| // We also specially control the clearing of these bits |
| do_read_check = 1'b0; |
| end |
| "intr_enable": begin |
| end |
| "intr_test": begin |
| end |
| "me_regwen": begin |
| end |
| "sw_regupd": begin |
| end |
| "regwen": begin |
| end |
| "rev": begin |
| end |
| "module_enable": begin |
| end |
| "conf": begin |
| locked_reg_access = dut_reg_locked; |
| end |
| "rev": begin |
| end |
| "entropy_control": begin |
| locked_reg_access = dut_reg_locked; |
| end |
| "entropy_data": begin |
| end |
| "health_test_windows": begin |
| locked_reg_access = dut_reg_locked; |
| end |
| "repcnt_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 0; |
| end |
| "repcnts_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 0; |
| end |
| "adaptp_hi_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 0; |
| end |
| "adaptp_lo_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 1; |
| end |
| "bucket_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 0; |
| end |
| "markov_hi_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 0; |
| end |
| "markov_lo_thresholds": begin |
| locked_reg_access = dut_reg_locked; |
| one_way_threshold = 1; |
| threshold_increases = 1; |
| end |
| "repcnt_hi_watermarks": begin |
| // TODO: KNOWN ISSUE: pending resolution to #9819 |
| do_read_check = 1'b0; |
| end |
| "repcnts_hi_watermarks": begin |
| // TODO: KNOWN ISSUE: pending resolution to #9819 |
| do_read_check = 1'b0; |
| end |
| "adaptp_hi_watermarks": begin |
| end |
| "adaptp_lo_watermarks": begin |
| end |
| "bucket_hi_watermarks": begin |
| end |
| "markov_hi_watermarks": begin |
| end |
| "markov_lo_watermarks": begin |
| end |
| "extht_hi_watermarks": begin |
| end |
| "extht_lo_watermarks": begin |
| end |
| "repcnt_total_fails": begin |
| end |
| "repcnts_total_fails": begin |
| end |
| "adaptp_hi_total_fails": begin |
| end |
| "adaptp_lo_total_fails": begin |
| end |
| "bucket_total_fails": begin |
| end |
| "markov_hi_total_fails": begin |
| end |
| "markov_lo_total_fails": begin |
| end |
| "extht_hi_total_fails": begin |
| end |
| "extht_lo_total_fails": begin |
| end |
| "alert_threshold": begin |
| locked_reg_access = dut_reg_locked; |
| end |
| "alert_summary_fail_counts": begin |
| end |
| "alert_fail_counts": begin |
| end |
| "extht_fail_counts": begin |
| end |
| "fw_ov_control": begin |
| locked_reg_access = dut_reg_locked; |
| end |
| "fw_ov_sha3_start": begin |
| end |
| "fw_ov_rd_data": begin |
| end |
| "fw_ov_wr_data": begin |
| end |
| "fw_ov_wr_fifo_full": begin |
| end |
| "fw_ov_rd_fifo_overflow": begin |
| // TODO, need to predict this. |
| end |
| "observe_fifo_thresh": begin |
| locked_reg_access = dut_reg_locked; |
| end |
| "observe_fifo_depth": begin |
| end |
| "debug_status": begin |
| end |
| "recov_alert_sts": begin |
| end |
| "err_code": begin |
| end |
| "err_code_test": begin |
| end |
| "main_sm_state": begin |
| end |
| default: begin |
| `uvm_fatal(`gfn, $sformatf("invalid csr: %0s", csr.get_full_name())) |
| end |
| endcase |
| |
| if (channel == AddrChannel) begin |
| // if incoming access is a write to a valid csr, then make updates right away |
| if (write) begin |
| if (locked_reg_access) begin |
| string msg = $sformatf("Attempt to write while locked: %s", csr.get_name()); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| // Cover sw_update_sample if this if the new data represents an attempted change |
| // to the previous value (one that can be confirmed by a follow-up read). |
| if (item.a_data != `gmv(csr)) begin |
| cov_vif.cg_sw_update_sample( |
| csr.get_offset(), |
| ral.sw_regupd.sw_regupd.get_mirrored_value(), |
| ral.module_enable.module_enable.get_mirrored_value() == MuBi4True |
| ); |
| end |
| end else begin |
| string msg = $sformatf("Unlocked write to: %s", csr.get_name()); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| if (one_way_threshold) begin |
| predict_one_way_threshold(csr, item.a_data, threshold_increases); |
| end else begin |
| `DV_CHECK(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask))); |
| end |
| // Special handling for registers with broader impacts |
| case (csr.get_name()) |
| "intr_state": begin |
| `uvm_info(`gfn, $sformatf("Attempting to clear bits: %01h", item.a_data), UVM_FULL) |
| clear_interrupts(item.a_data); |
| // If an interrupt condition persists, it will clear at the |
| // pins for one cycle before reasserting. |
| // Thus there is no need to confirm that it had been immediately reasserted. |
| end |
| "intr_enable": begin |
| intr_en_mask = item.a_data; |
| end |
| "intr_test": begin |
| intr_test = item.a_data; |
| intr_test_active = 1; |
| end |
| "sw_regupd": begin |
| bit sw_regupd; |
| sw_regupd = `gmv(ral.sw_regupd.sw_regupd); |
| if (!sw_regupd) begin |
| `DV_CHECK_FATAL(ral.regwen.regwen.predict(.value(0), .kind(UVM_PREDICT_READ))); |
| end |
| end |
| "module_enable": begin |
| string msg; |
| bit do_disable, do_enable; |
| uvm_reg_field enable_field = csr.get_field_by_name("module_enable"); |
| prim_mubi_pkg::mubi4_t enable_mubi = |
| prim_mubi_pkg::mubi4_t'(enable_field.get_mirrored_value()); |
| do_enable = (enable_mubi == prim_mubi_pkg::MuBi4True); |
| // Though non-mubi values sent alerts, for the |
| // purposes of enablement, all invalid values are effectively disables. |
| do_disable = ~do_enable; |
| check_redundancy_val("module_enable", "module_enable", |
| "module_enable_field_alert", invalid_module_enable); |
| |
| msg = $sformatf("locked? %01d", dut_reg_locked); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| fork |
| if (do_enable) begin |
| cfg.clk_rst_vif.wait_clks(2); |
| fw_ov_pipe_enabled = 1; |
| end |
| if (do_enable && !dut_pipeline_enabled) begin |
| dut_pipeline_enabled = 1; |
| handle_disable_reset(Enable); |
| fifos_cleared = 0; |
| collect_entropy(); |
| // The DUT internals could take as long as three clocks to clear. |
| cfg.clk_rst_vif.wait_clks(3); |
| handle_disable_reset(Disable); |
| end |
| begin |
| bit regwen_obs, sw_regupd; |
| int obs_delay = 0; |
| `uvm_info(`gfn, "Waiting for regwen", UVM_FULL) |
| regwen_pending = 1; |
| // Don't update the regwen prediction immediately as the DUT will enforce |
| // delays in REGWEN until it has safely cleared its internal state. Silently |
| // peek-poll (with watchdog) here until the change in regwen occurs and then |
| // update the prediction. |
| `DV_SPINWAIT(forever begin |
| sw_regupd = `gmv(ral.sw_regupd.sw_regupd); |
| if (!sw_regupd) break; // Device will be conlusively locked via sw_regupd |
| csr_peek(.ptr(ral.regwen.regwen), .value(regwen_obs)); |
| if (regwen_obs == do_disable) break; |
| cfg.clk_rst_vif.wait_clks(1); |
| obs_delay++; |
| end) |
| msg = $sformatf("REGWEN update observed after %d clocks", obs_delay); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| `DV_CHECK_FATAL(ral.regwen.regwen.predict(.value(do_disable & sw_regupd), |
| .kind(UVM_PREDICT_READ))); |
| regwen_pending = 0; |
| `uvm_info(`gfn, "Waiting for regwen complete", UVM_FULL) |
| end |
| join_none |
| |
| if (do_disable && dut_pipeline_enabled) begin |
| // The DUT does not immediately turn off the RNG input. We wait a few cycles to |
| // let any last couple RNG samples come into the dut (so we know to delete them |
| // from our model of the DUT); |
| int base_shutdown_dly = cfg.tlul_to_rng_disable_delay; |
| |
| // Send shutdown signals to the various event loops, with delays as necessary |
| // to account for empirical pipeline delays in the DUT. |
| fork : background_process |
| begin |
| bit rng_bit_enable = (`gmv(ral.conf.rng_bit_enable) == MuBi4True); |
| int rng_shutdown_dly = base_shutdown_dly; |
| cfg.clk_rst_vif.wait_clks(rng_shutdown_dly); |
| dut_pipeline_enabled = 0; |
| end |
| begin |
| int fw_ov_shutdown_dly = base_shutdown_dly; |
| fw_ov_shutdown_dly += 3; |
| cfg.clk_rst_vif.wait_clks(fw_ov_shutdown_dly); |
| fw_ov_pipe_enabled = 0; |
| `uvm_info(`gfn, "FW_OV pipeline clearing", UVM_FULL) |
| handle_disable_reset(FWOVDisable); |
| end |
| begin |
| cfg.clk_rst_vif.wait_clks(cfg.tlul_to_fifo_clr_delay); |
| handle_disable_reset(FIFOClr); |
| fifos_cleared = 1; |
| end |
| join_none : background_process |
| end |
| end |
| "conf": begin |
| check_redundancy_val("conf", "fips_enable", "fips_enable_field_alert", |
| invalid_fips_enable); |
| check_redundancy_val("conf", "entropy_data_reg_enable", |
| "entropy_data_reg_en_field_alert", |
| invalid_entropy_data_reg_enable); |
| check_redundancy_val("conf", "threshold_scope", "threshold_scope_field_alert", |
| invalid_threshold_scope); |
| check_redundancy_val("conf", "rng_bit_enable", "rng_bit_enable_field_alert", |
| invalid_rng_bit_enable); |
| end |
| "entropy_control": begin |
| check_redundancy_val("entropy_control", "es_route", "es_route_field_alert", |
| invalid_es_route); |
| check_redundancy_val("entropy_control", "es_type", "es_type_field_alert", |
| invalid_es_type); |
| end |
| "alert_threshold": begin |
| cov_vif.cg_alert_cnt_sample(item.a_data, 0); |
| check_redundancy_val("alert_threshold", "", "es_thresh_cfg_alert", |
| invalid_alert_threshold); |
| end |
| "fw_ov_control": begin |
| check_redundancy_val("fw_ov_control", "fw_ov_mode", "fw_ov_mode_field_alert", |
| invalid_fw_ov_mode); |
| check_redundancy_val("fw_ov_control", "fw_ov_entropy_insert", |
| "fw_ov_entropy_insert_field_alert", |
| invalid_fw_ov_entropy_insert); |
| end |
| "fw_ov_sha3_start": begin |
| // The fw_ov_sha3_start field triggers the internal processing of SHA data |
| mubi4_t start_mubi = prim_mubi_pkg::mubi4_t'( |
| ral.fw_ov_sha3_start.fw_ov_insert_start.get_mirrored_value()); |
| bit fips_enabled = ral.conf.fips_enable.get_mirrored_value() == MuBi4True; |
| bit es_route = ral.entropy_control.es_route.get_mirrored_value() == MuBi4True; |
| bit es_type = ral.entropy_control.es_type.get_mirrored_value() == MuBi4True; |
| bit is_fips_mode = fips_enabled && !(es_route && es_type); |
| mubi4_t fw_ov_mubi = prim_mubi_pkg::mubi4_t'( |
| ral.fw_ov_control.fw_ov_mode.get_mirrored_value()); |
| |
| bit fw_ov_mode = (cfg.otp_en_es_fw_over == MuBi8True) && |
| (fw_ov_mubi == MuBi4True); |
| mubi4_t insert_mubi = prim_mubi_pkg::mubi4_t'( |
| ral.fw_ov_control.fw_ov_entropy_insert.get_mirrored_value()); |
| bit fw_ov_insert = fw_ov_mode && (insert_mubi == MuBi4True); |
| bit do_disable_sha = fw_ov_sha_enabled && (start_mubi == MuBi4False); |
| |
| bit write_forbidden = is_fips_mode ? cfg.precon_fifo_vif.write_forbidden : |
| cfg.bypass_fifo_vif.write_forbidden; |
| |
| check_redundancy_val("fw_ov_sha3_start", "fw_ov_insert_start", |
| "fw_ov_sha3_start_field_alert", invalid_fw_ov_insert_start); |
| |
| // Disabling the fw_ov_sha3_start field triggers the conditioner, but only |
| // if the DUT is configured properly. |
| if (is_fips_mode && fw_ov_insert && do_disable_sha) begin |
| uvm_reg_field recov_sts_fld = ral.recov_alert_sts.es_fw_ov_disable_alert; |
| if (fw_ov_pipe_enabled) begin |
| if (write_forbidden) begin |
| // SW _shouldn't_ turn off the SHA3 processing until the last data word |
| // has been processed. However if it _does_, we should note an alert. |
| // We can also make an accurate prediction of the output (to pass our sims). |
| // |
| // Process the entropy EXCEPT for the last stuck word |
| // which we load into the next round. |
| bit [SHACondWidth - 1:0] sha_temp = sha_process_q.pop_back(); |
| `uvm_info(`gfn, "SHA3 disabled for FW_OV (Illegally, data present)", UVM_FULL) |
| package_and_release_entropy(); |
| sha_process_q.push_back(sha_temp); |
| `uvm_info(`gfn, "SHA3 disabled while data pending, expecting alert", UVM_MEDIUM) |
| set_exp_alert(.alert_name("recov_alert"), .is_fatal(0)); |
| `DV_CHECK_FATAL(recov_sts_fld.predict(.value(1'b1), .kind(UVM_PREDICT_READ))); |
| end else begin |
| `uvm_info(`gfn, "SHA3 disabled for FW_OV (Legally)", UVM_FULL) |
| package_and_release_entropy(); |
| end |
| end else begin |
| // SHA is disabled while the DUT is disabled. |
| // Another Illegal use case, one that doesn't even process the data. |
| // Expect an alert even though the dut won't do anything with it |
| set_exp_alert(.alert_name("recov_alert"), .is_fatal(0)); |
| `DV_CHECK_FATAL(recov_sts_fld.predict(.value(1'b1), .kind(UVM_PREDICT_READ))); |
| `uvm_info(`gfn, "SHA3 disabled for FW_OV (Illegally, disabled)", UVM_FULL) |
| end |
| end |
| fw_ov_sha_enabled = (start_mubi == MuBi4True); |
| if (fw_ov_sha_enabled && fw_ov_insert) begin |
| `uvm_info(`gfn, "SHA3 enabled for FW_OV", UVM_HIGH) |
| end |
| end |
| "fw_ov_wr_data": begin |
| bit fips_enabled = ral.conf.fips_enable.get_mirrored_value() == MuBi4True; |
| bit es_route = ral.entropy_control.es_route.get_mirrored_value() == MuBi4True; |
| bit es_type = ral.entropy_control.es_type.get_mirrored_value() == MuBi4True; |
| bit is_fips_mode = fips_enabled && !(es_route && es_type); |
| |
| bit predict_conditioned = do_condition_data(); |
| |
| bit fw_ov_entropy_insert = |
| (cfg.otp_en_es_fw_over == MuBi8True) && |
| (ral.fw_ov_control.fw_ov_mode.get_mirrored_value() == MuBi4True) && |
| (ral.fw_ov_control.fw_ov_entropy_insert.get_mirrored_value() == MuBi4True); |
| |
| if (ignore_fw_ov_data_pulse) begin |
| msg = $sformatf("fw_ov_wr_data dropped: 0x%08x", item.a_data); |
| `uvm_info(`gfn, msg, UVM_LOW) |
| end else begin |
| msg = $sformatf("fw_ov_wr_data captured: 0x%08x", item.a_data); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| end |
| |
| if (fw_ov_pipe_enabled && fw_ov_entropy_insert && !ignore_fw_ov_data_pulse) begin |
| msg = $sformatf("Inserting word 0x%08x into pipeline", item.a_data); |
| `uvm_info(`gfn, msg, UVM_MEDIUM) |
| // Add this TL-word to the running SHA word |
| repacked_entropy_fw_ov = {item.a_data, |
| repacked_entropy_fw_ov[TL_DW +: (SHACondWidth - TL_DW)]}; |
| repack_idx_fw_ov++; |
| `uvm_info(`gfn, $sformatf("repack_idx_fw_ov: %016x", repack_idx_fw_ov), UVM_HIGH) |
| if (repack_idx_fw_ov == SHACondWidth/TL_DW) begin |
| repack_idx_fw_ov = 0; |
| msg = $sformatf("fw_ov SHA word: %016x", repacked_entropy_fw_ov); |
| `uvm_info(`gfn, msg, UVM_HIGH) |
| if (predict_conditioned) begin |
| sha_process_q.push_back(repacked_entropy_fw_ov); |
| end else begin |
| raw_process_q.push_back(repacked_entropy_fw_ov); |
| end |
| // In bypass mode, data is automatically released when a full seed is acquired |
| if (! predict_conditioned && |
| raw_process_q.size() == (CSRNG_BUS_WIDTH / SHACondWidth)) begin |
| package_and_release_entropy(); |
| end |
| end |
| end |
| end |
| "err_code_test": begin |
| uvm_reg_field err_code_test = csr.get_field_by_name("err_code_test"); |
| bit [TL_DW - 1:0] err_code = ral.err_code.get_mirrored_value(); |
| bit[4:0] bit_num = err_code_test.get_mirrored_value(); |
| bit [TL_DW - 1:0] mask = (32'h1 << bit_num); |
| bit is_fatal = 0; |
| bit is_logged = 0; |
| string msg; |
| msg = $sformatf("Received write to ERR_CODE_TEST: %d", bit_num); |
| `uvm_info(`gfn, msg, UVM_MEDIUM) |
| err_code = err_code | mask; |
| msg = $sformatf("Predicted value of ERR_CODE: %08x", err_code); |
| `uvm_info(`gfn, msg, UVM_MEDIUM) |
| case(bit_num) |
| 22: begin // es_cntr_err |
| is_fatal = 1; |
| is_logged = 1; |
| main_sm_escalates = 1; |
| end |
| 0, 1, 2, 20, 21, 28, 29, 30: begin // other valid err_code bits |
| // These test bits correspond to events that are always logged |
| // in err_code, but only create fatal alerts if they occur |
| // when the DUT is enabled |
| is_logged = 1; |
| is_fatal = (ral.module_enable.module_enable.get_mirrored_value() == MuBi4True); |
| end |
| default: begin // all others |
| is_fatal = 0; |
| is_logged = 0; |
| end |
| endcase |
| msg = $sformatf("FATAL: %d, LOGGED: %d", is_fatal, is_logged); |
| `uvm_info(`gfn, msg, UVM_MEDIUM) |
| if (is_logged) begin |
| `DV_CHECK_FATAL(ral.err_code.predict(.value(err_code), .kind(UVM_PREDICT_READ))); |
| end |
| if (is_fatal) begin |
| cov_vif.cg_err_test_sample(bit_num); |
| set_exp_alert(.alert_name("fatal_alert"), .is_fatal(1)); |
| end |
| fork |
| // Implementation timing detail: |
| // If a particular error is escalated it also becomes a main_sm error. |
| if (main_sm_escalates) begin |
| int main_sm_err_mask = 1 << 21; |
| cfg.clk_rst_vif.wait_clks(1); |
| err_code |= main_sm_err_mask; |
| `DV_CHECK_FATAL(ral.err_code.predict(.value(err_code), .kind(UVM_PREDICT_READ))); |
| end |
| join_none |
| end |
| default: begin |
| end |
| endcase |
| end |
| end |
| end |
| |
| // On reads, if do_read_check is set, then check mirrored_value against item.d_data |
| if (!write && channel == DataChannel) begin |
| case (csr.get_name()) |
| "intr_state": begin |
| // Though we do not predict the interrupt state we do stop here to process any |
| // new activity on any interrupt lines that happen to be disabled. |
| // (The enabled ones will be processed as soon as they are seen on the |
| // interrupt pins.) |
| bit [NumEntropySrcIntr - 1:0] new_events = '0; |
| bit [NumEntropySrcIntr - 1:0] to_handle = '0; |
| entropy_src_intr_e i; |
| new_events = item.d_data & ~known_intr_state; |
| to_handle = new_events & ~intr_en_mask; |
| for (i = i.first(); i < i.last(); i = i.next()) begin |
| // Don't sample int_cg if this was triggered as a test, only if it showed up |
| // in normal operation. |
| if(!intr_test_active) begin |
| cov.intr_cg.sample(i, intr_en_mask[i], item.d_data[i]); |
| end |
| if(intr_test_active) begin |
| cov.intr_test_cg.sample(i, intr_test[i], intr_en_mask[i], item.d_data[i]); |
| end |
| // Sample cov.inter_pins_cg in process_interrupts() |
| end |
| handle_new_interrupts(to_handle, 1); |
| end |
| "entropy_data": begin |
| // TODO(Enhancement): Ideally the scoreboard would have monitor access to the |
| // internal state of the entropy_data and observe FIFOs. At this point however |
| // the environment can run satisfactorily by checking whether the FIFOs have |
| // been cleared in a disable event. |
| if (fifos_cleared) begin |
| `DV_CHECK_FATAL(csr.predict(.value('0), .kind(UVM_PREDICT_READ))) |
| end else begin |
| process_entropy_data_csr_access(item, csr); |
| end |
| end |
| "fw_ov_rd_data": begin |
| if (fifos_cleared) begin |
| `DV_CHECK_FATAL(csr.predict(.value('0), .kind(UVM_PREDICT_READ))) |
| end else begin |
| process_observe_fifo_csr_access(item, csr); |
| // Assume (for now) that there is no underflow |
| // (We are not tracking it at this time, and so an underflow |
| // will kill the simulation.) |
| expected_obsfifo_entries_since_last_intr--; |
| if (expected_obsfifo_entries_since_last_intr == 0) begin |
| // We have successfully received an interrupt and |
| // read OBSERVE_FIFO_THRESH entries from the fifo. |
| // We can mark this value of OBSERVE_FIFO_THRESH |
| // as successful |
| bit [6:0] obs_fifo_threshold = |
| ral.observe_fifo_thresh.observe_fifo_thresh.get_mirrored_value(); |
| cov_vif.cg_observe_fifo_threshold_sample(obs_fifo_threshold); |
| end |
| end |
| end |
| "recov_alert_sts": begin |
| for (int i=0; i < TL_DW; i++) begin |
| if (item.d_data[i]) begin |
| cov_vif.cg_recov_alert_sample(i); |
| end |
| end |
| end |
| default: begin |
| end |
| endcase |
| |
| if (do_read_check) begin |
| `DV_CHECK_EQ(csr.get_mirrored_value(), item.d_data, |
| $sformatf("reg name: %0s", csr.get_full_name())) |
| end |
| end |
| endtask |
| |
| task monitor_fw_ov_write_exceptions(virtual entropy_subsys_fifo_exception_if#(1) vif, |
| bit active_in_fips_mode); |
| bit fw_ov_mode, fw_ov_insert, fips_enabled, es_route, es_type, is_fips_mode; |
| int i; |
| |
| forever begin |
| @(vif.mon_cb); |
| |
| fw_ov_mode = (cfg.otp_en_es_fw_over == MuBi8True) && |
| (`gmv(ral.fw_ov_control.fw_ov_mode) == MuBi4True); |
| fw_ov_insert = fw_ov_mode && (`gmv(ral.fw_ov_control.fw_ov_entropy_insert) == MuBi4True); |
| fips_enabled = `gmv(ral.conf.fips_enable) == MuBi4True; |
| es_route = `gmv(ral.entropy_control.es_route) == MuBi4True; |
| es_type = `gmv(ral.entropy_control.es_type) == MuBi4True; |
| is_fips_mode = fips_enabled && !(es_route && es_type); |
| |
| // If we are not in FW_OV mode at this time, then this error event doesn't matter. |
| // (Such events seem to happen in normal HW-driven operation, but they do not |
| // reflect errors, as the HW chain has proper flow control) |
| if (!fw_ov_insert) continue; |
| |
| // This fifo event also does matter if the FIFO is currently not active for FW_OV/FIPS mode |
| if (active_in_fips_mode ^ is_fips_mode) continue; |
| |
| for (i=0; i<N_FIFO_ERR_TYPES; i++) begin |
| if (vif.mon_cb.error_pulses[i]) begin |
| case (i) |
| FIFO_WRITE_ERR: begin |
| if (!under_alert_handshake["recov_alert"]) begin |
| uvm_reg_field fld = ral.recov_alert_sts.es_fw_ov_wr_alert; |
| `DV_CHECK_FATAL(fld.predict(1'b1, .kind(UVM_PREDICT_READ))); |
| set_exp_alert("recov_alert"); |
| end |
| // Make a single-clock pulse to tell the TL process that this error has been |
| // identified and the ongoing write should be ignored. |
| ignore_fw_ov_data_pulse = 1; |
| fork |
| begin |
| @(vif.mon_cb); |
| // Clear the last pulse (unless there is another event right behind the last |
| // one) |
| if (!vif.mon_cb.error_pulses[FIFO_WRITE_ERR]) begin |
| ignore_fw_ov_data_pulse = 0; |
| end |
| end |
| join_none |
| end |
| default: begin |
| // ignore other types as this FIFO has proper HW flow control at the other end. |
| end |
| endcase |
| end |
| end |
| end |
| endtask |
| |
| task process_fifo_exceptions(); |
| fork |
| // The FW_OV_WR_DATA register is connected to the precon fifo in FIPS mode and the bypass |
| // FIFO in bypass mode. Monitor them both for exceptions. |
| monitor_fw_ov_write_exceptions(cfg.precon_fifo_vif, 1); |
| monitor_fw_ov_write_exceptions(cfg.bypass_fifo_vif, 0); |
| join_none |
| endtask |
| |
| function bit [FIPS_BUS_WIDTH - 1:0] get_fips_compliance( |
| bit [FIPS_CSRNG_BUS_WIDTH - 1:0] fips_csrng); |
| return fips_csrng[CSRNG_BUS_WIDTH +: FIPS_BUS_WIDTH]; |
| endfunction |
| |
| function bit [CSRNG_BUS_WIDTH - 1:0] get_csrng_seed(bit [FIPS_CSRNG_BUS_WIDTH - 1:0] fips_csrng); |
| return fips_csrng[0 +: CSRNG_BUS_WIDTH]; |
| endfunction |
| |
| function bit do_condition_data(); |
| bit route_sw; |
| bit sw_bypass; |
| bit fips_enable; |
| bit is_fips_mode; |
| bit predict_conditioned; |
| |
| route_sw = (`gmv(ral.entropy_control.es_route) == MuBi4True); |
| sw_bypass = (`gmv(ral.entropy_control.es_type) == MuBi4True); |
| fips_enable = (`gmv(ral.conf.fips_enable) == MuBi4True); |
| |
| is_fips_mode = fips_enable && !(route_sw && sw_bypass); |
| |
| predict_conditioned = is_fips_mode; |
| |
| return predict_conditioned; |
| |
| endfunction |
| |
| |
| // Note: this routine is destructive in that it empties the input argument |
| function bit [FIPS_CSRNG_BUS_WIDTH - 1:0] predict_fips_csrng(); |
| bit [FIPS_CSRNG_BUS_WIDTH - 1:0] fips_csrng_data; |
| bit [CSRNG_BUS_WIDTH - 1:0] csrng_data; |
| bit [FIPS_BUS_WIDTH - 1:0] fips_data; |
| bit predict_conditioned; |
| prim_mubi_pkg::mubi4_t rng_single_bit; |
| |
| int sample_frames; |
| |
| string msg, fmt; |
| |
| predict_conditioned = do_condition_data(); |
| |
| rng_single_bit = prim_mubi_pkg::mubi4_t'(`gmv(ral.conf.rng_bit_enable)); |
| |
| sample_frames = predict_conditioned ? sha_process_q.size() : raw_process_q.size; |
| |
| fmt = "processing %01d 64-bit frames in %s mode"; |
| msg = $sformatf(fmt, sample_frames, predict_conditioned ? "FIPS" : "BYPASS"); |
| |
| `uvm_info(`gfn, msg, UVM_FULL); |
| |
| fips_data = predict_conditioned && (rng_single_bit == prim_mubi_pkg::MuBi4False); |
| |
| if (predict_conditioned) begin |
| localparam int BytesPerSHAWord = SHACondWidth / 8; |
| |
| bit [7:0] sha_msg[]; |
| bit [7:0] sha_digest[CSRNG_BUS_WIDTH / 8]; |
| longint msg_len = 0; |
| |
| sha_msg = new[sha_process_q.size() * BytesPerSHAWord]; |
| |
| // The DUT's SHA engine takes data in 64 (SHACondWidth) bit chunks, whereas the DPI call |
| // requires an array of bytes. Here we break the SHA-words into a stream of bytes |
| while (sha_process_q.size() > 0) begin |
| bit [SHACondWidth - 1:0] sha_word = '0; |
| bit [7:0] sha_byte = '0; |
| sha_word = sha_process_q.pop_front(); |
| for (int i = 0; i < BytesPerSHAWord; i++) begin |
| sha_byte = sha_word[ 0 +: 8]; |
| sha_word = sha_word >> 8; |
| `uvm_info(`gfn, $sformatf("msglen: %04h, byte: %02h", msg_len, sha_byte), UVM_FULL) |
| sha_msg[msg_len] = sha_byte; |
| msg_len++; |
| end |
| end |
| |
| `uvm_info(`gfn, $sformatf("DIGESTION COMMENCING of %d bytes", msg_len), UVM_FULL) |
| |
| digestpp_dpi_pkg::c_dpi_sha3_384(sha_msg, msg_len, sha_digest); |
| |
| `uvm_info(`gfn, "DIGESTING COMPLETE", UVM_FULL) |
| |
| csrng_data = '0; |
| for(int i = 0; i < CSRNG_BUS_WIDTH / 8; i++) begin |
| bit [7:0] sha_byte = sha_digest[i]; |
| |
| `uvm_info(`gfn, $sformatf("repacking: %02d", i), UVM_FULL) |
| |
| csrng_data = (csrng_data >> 8) | (sha_byte << (CSRNG_BUS_WIDTH - 8)); |
| end |
| `uvm_info(`gfn, $sformatf("Conditioned data: %096h", csrng_data), UVM_HIGH) |
| |
| end else begin |
| |
| while (raw_process_q.size() > 0) begin |
| bit [SHACondWidth - 1:0] word = raw_process_q.pop_front(); |
| string fmt; |
| |
| fmt = "sample size: %01d, last elem.: %016h"; |
| `uvm_info(`gfn, $sformatf(fmt, raw_process_q.size()+1, word), UVM_FULL) |
| |
| csrng_data = csrng_data >> SHACondWidth; |
| csrng_data[CSRNG_BUS_WIDTH - SHACondWidth +: SHACondWidth] = word; |
| end |
| `uvm_info(`gfn, $sformatf("Unconditioned data: %096h", csrng_data), UVM_HIGH) |
| end |
| |
| fips_csrng_data = {fips_data, csrng_data}; |
| |
| return fips_csrng_data; |
| endfunction |
| |
| task wait_enabled(); |
| if (!dut_pipeline_enabled) begin |
| wait(dut_pipeline_enabled); |
| `uvm_info(`gfn, "Enable detected", UVM_MEDIUM) |
| end |
| endtask |
| |
| // Wait on the RNG queue for rng sequence items |
| // |
| // If bit selection is enabled, wait for RNG_BUS_WIDTH items. Otherwise, return after one item. |
| // If dut_pipeline_enabled is deasserted before any data is found, this task |
| // halts and asserts disable_detected. |
| task wait_rng_queue(output rng_val_t val, output bit disable_detected); |
| push_pull_item#(.HostDataWidth(RNG_BUS_WIDTH)) rng_item; |
| bit bit_sel_enable = (`gmv(ral.conf.rng_bit_enable) == MuBi4True); |
| int n_items = bit_sel_enable ? RNG_BUS_WIDTH : 1; |
| disable_detected = 0; |
| |
| if(!dut_pipeline_enabled) begin |
| disable_detected = 1; |
| return; |
| end |
| |
| for (int i = 0; i < n_items; i++) begin : rng_loop |
| `DV_SPINWAIT_EXIT(rng_fifo.peek(rng_item);, |
| wait(!dut_pipeline_enabled);) |
| // Pop any data off the rng_fifo below to resolve potential |
| // race conditions if a new RNG word appears in the same |
| // cycle that the dut is disabled. |
| disable_detected = !rng_fifo.try_get(rng_item); |
| if (disable_detected) break; |
| |
| if (bit_sel_enable) begin |
| val[i] = rng_item.h_data[ral.conf.rng_bit_sel.get_mirrored_value()]; |
| end else begin |
| val = rng_item.h_data; |
| end |
| end : rng_loop |
| endtask |
| |
| task collect_entropy(); |
| // Two levels of repacking to mimic the structure of the DUT |
| // first RNG samples are packed into 32-bit TL DW's |
| // then those are packed into 64-bit chunks suitable |
| // for SHA3 input |
| bit [TL_DW - 1:0] repacked_entropy_tl; |
| bit [SHACondWidth:0] repacked_entropy_sha; |
| int repack_idx_tl = 0; |
| int repack_idx_sha = 0; |
| bit fw_ov_insert; |
| bit disable_detected; |
| rng_val_t rng_val; |
| string fmt, msg; |
| |
| bit predict_conditioning; |
| |
| localparam int RngPerTlDw = TL_DW / RNG_BUS_WIDTH; |
| |
| wait_enabled(); |
| |
| fw_ov_insert = (cfg.otp_en_es_fw_over == MuBi8True) && |
| (`gmv(ral.fw_ov_control.fw_ov_mode) == MuBi4True) && |
| (`gmv(ral.fw_ov_control.fw_ov_entropy_insert) == MuBi4True); |
| |
| forever begin : collect_entropy_loop |
| wait_rng_queue(rng_val, disable_detected); |
| |
| if (disable_detected) begin |
| // Exit this task. |
| return; |
| end else begin |
| // Add this data to health check windows |
| health_test_data_q.push_back(rng_val); |
| |
| // Pack this data for redistribution |
| repacked_entropy_tl = {rng_val, |
| repacked_entropy_tl[RNG_BUS_WIDTH +: (TL_DW - RNG_BUS_WIDTH)]}; |
| repack_idx_tl++; |
| `uvm_info(`gfn, $sformatf("repack_idx_tl: %0d", repack_idx_tl), UVM_DEBUG) |
| if (repack_idx_tl == RngPerTlDw) begin |
| repack_idx_tl = 0; |
| // publish this 32 bit word to the observe FIFO |
| observe_fifo_q.push_back(repacked_entropy_tl); |
| |
| // Now repack the TL_DW width blocks into the larger SHA blocks |
| // to publish to the correct processing fifo. |
| // Since the raw and sha-conditioned data are handled differently |
| // on disable events they go into different FIFOs |
| // |
| repacked_entropy_sha = {repacked_entropy_tl, |
| repacked_entropy_sha[TL_DW +: (SHACondWidth - TL_DW)]}; |
| repack_idx_sha++; |
| if (repack_idx_sha == SHACondWidth/TL_DW) begin |
| repack_idx_sha = 0; |
| if (!fw_ov_insert) begin |
| predict_conditioning = do_condition_data(); |
| if (predict_conditioning) begin |
| sha_process_q.push_back(repacked_entropy_sha); |
| msg = $sformatf("RNG SHA word: %016x, count: 0x%01x", |
| repacked_entropy_sha, sha_process_q.size()); |
| `uvm_info(`gfn, msg, UVM_HIGH) |
| end else begin |
| `uvm_info(`gfn, $sformatf("RNG RAW word: %016x", repacked_entropy_sha) , UVM_HIGH) |
| raw_process_q.push_back(repacked_entropy_sha); |
| end |
| end |
| end |
| end |
| end |
| end |
| endtask |
| |
| task health_test_scoring_thread(); |
| bit [15:0] window_size; |
| entropy_phase_e dut_fsm_phase; |
| int window_rng_frames; |
| int pass_requirement, pass_count, startup_fail_count; |
| bit fw_ov_insert; |
| bit is_fips_mode; |
| bit fips_enable, es_route, es_type; |
| int failures_in_window; |
| rng_val_t rng_val; |
| queue_of_rng_val_t window; |
| |
| string msg; |
| |
| forever begin : simulation_loop |
| entropy_src_xht_item xht_item; |
| bit disable_detected = 0; |
| |
| wait_enabled(); |
| |
| fips_enable = (`gmv(ral.conf.fips_enable) == MuBi4True); |
| es_route = (`gmv(ral.entropy_control.es_route) == MuBi4True); |
| es_type = (`gmv(ral.entropy_control.es_type) == MuBi4True); |
| |
| is_fips_mode = fips_enable && !(es_route && es_type); |
| |
| fw_ov_insert = (cfg.otp_en_es_fw_over == MuBi8True) && |
| (`gmv(ral.fw_ov_control.fw_ov_mode) == MuBi4True) && |
| (`gmv(ral.fw_ov_control.fw_ov_entropy_insert) == MuBi4True); |
| |
| pass_count = 0; |
| startup_fail_count = 0; |
| seed_idx = 0; |
| |
| forever begin : enabled_loop |
| |
| window.delete(); |
| |
| `uvm_info(`gfn, $sformatf("SEED_IDX: %01d", seed_idx), UVM_FULL) |
| |
| dut_fsm_phase = convert_seed_idx_to_phase(seed_idx, is_fips_mode, fw_ov_insert); |
| |
| case (dut_fsm_phase) |
| BOOT: begin |
| pass_requirement = 1; |
| ht_fips_mode = 0; |
| end |
| STARTUP: begin |
| pass_requirement = 2; |
| ht_fips_mode = 1; |
| end |
| CONTINUOUS: begin |
| pass_requirement = 1; |
| ht_fips_mode = 1; |
| end |
| HALTED: begin |
| // When in the post-boot, halted state the DUT will continue to monitor health checks, |
| // but not output CSRNG data or data to the TL ENTROPY_DATA register. |
| // In this cass the pass_requirement and ht_fips_mode values don't mean anything |
| pass_requirement = 0; |
| ht_fips_mode = 0; |
| end |
| default: begin |
| `uvm_fatal(`gfn, "Invalid predicted dut state (bug in environment)") |
| end |
| endcase |
| |
| `uvm_info(`gfn, $sformatf("phase: %s\n", dut_fsm_phase.name), UVM_HIGH) |
| |
| window_size = rng_window_size(seed_idx, is_fips_mode, fw_ov_insert, |
| `gmv(ral.health_test_windows.fips_window) * RNG_BUS_WIDTH); |
| |
| `uvm_info(`gfn, $sformatf("window_size: %08d\n", window_size), UVM_HIGH) |
| |
| // Note on RNG bit enable and window frame count: |
| // When rng_bit_enable is selected, the function below repacks the data so that |
| // the selected bit fills a whole frame. |
| // This mirrors the DUT's behavior of repacking the data before the health checks |
| // |
| // Thus the number of (repacked) window frames is the same regardless of whether |
| // the bit select is enabled. |
| |
| window_rng_frames = window_size / RNG_BUS_WIDTH; |
| |
| forever begin : window_loop |
| string fmt; |
| bit [RNG_BUS_WIDTH - 1:0] xht_bus_val; |
| |
| // For synchronization purposes we wait to process each sample until it is visible on the |
| // as an event on xht bus. We then perform checks to ensure that the xht interface dataz |
| // matches the RNG data and that the window boundaries (as seen on the XHT bus) appear |
| // at the correct times. |
| // |
| // TODO(V3): perform a more complete check of the other XHT outputs. |
| // |
| forever begin : sample_loop |
| // Wait either for the next xht_item, or wait at most two clocks |
| // after a disable event. |
| `DV_SPINWAIT_EXIT(xht_fifo.peek(xht_item);, |
| wait(!dut_pipeline_enabled); |
| cfg.clk_rst_vif.wait_clks(2);) |
| disable_detected = !xht_fifo.try_get(xht_item); |
| if (disable_detected) break; // No events. DUT has shutdown |
| if (!xht_item.req.clear) begin |
| evaluate_external_ht(xht_item.rsp, ht_fips_mode); |
| end |
| if (xht_item.req.entropy_bit_valid || xht_item.req.window_wrap_pulse) break; |
| end : sample_loop |
| |
| if (disable_detected) break; // No sample events. DUT has shutdown |
| |
| if (xht_item.req.window_wrap_pulse) begin |
| `DV_CHECK(window.size() == window_rng_frames) |
| break; |
| end else begin |
| `DV_CHECK(window.size() < window_rng_frames) |
| end |
| |
| // No shutdown, or window close pulse, must be a sample. |
| `DV_CHECK(xht_item.req.entropy_bit_valid) |
| |
| // Make sure that RNG data has been received and that it matches the |
| // ExtHT data |
| `DV_CHECK(health_test_data_q.size() > 0) |
| rng_val = health_test_data_q.pop_front(); |
| |
| `DV_CHECK(xht_item.req.entropy_bit == rng_val) |
| window.push_back(rng_val); |
| |
| fmt = "RNG element: %0x, idx: %0d"; |
| `uvm_info(`gfn, $sformatf(fmt, rng_val, window.size()), UVM_FULL) |
| |
| // Update the repetition counts, which are updated continuously. |
| // The other health checks only operate on complete windows, and are processed later. |
| update_repcnts(ht_fips_mode, rng_val); |
| end |
| |
| if (disable_detected) break; // No events. DUT has shutdown |
| |
| // Process end of window events |
| `DV_CHECK(xht_item.req.window_wrap_pulse) |
| |
| failures_in_window = health_check_rng_data(window, ht_fips_mode); |
| |
| if (failures_in_window > 0) begin |
| pass_count = 0; |
| // Most failures are handled in the alert counter registers |
| // However the startup phase has special handling. |
| startup_fail_count++; |
| end else begin |
| pass_count++; |
| if (startup_fail_count < 2) startup_fail_count = 0; |
| end |
| |
| process_failures(failures_in_window, fw_ov_insert, dut_fsm_phase, startup_fail_count); |
| window.delete(); |
| |
| // Once in the halted state, or in the fw_ov_insert_entropy mode, pre-tested data is |
| // discarded after the health checks |
| if ((dut_fsm_phase == HALTED) || fw_ov_insert) begin |
| continue; |
| end |
| |
| `uvm_info(`gfn, $sformatf("pass_requirement: %01d", pass_requirement), UVM_HIGH) |
| `uvm_info(`gfn, $sformatf("raw_process_q.size: %01d", raw_process_q.size()), UVM_HIGH) |
| `uvm_info(`gfn, $sformatf("sha_process_q.size: %01d", sha_process_q.size()), UVM_HIGH) |
| |
| if (pass_count >= pass_requirement && !threshold_alert_active && !main_sm_escalates) begin |
| package_and_release_entropy(); |
| // update counters for processing next seed: |
| pass_count = 0; |
| seed_idx++; |
| end : window_loop |
| end : enabled_loop |
| end : simulation_loop |
| endtask |
| |
| function void package_and_release_entropy(); |
| bit [FIPS_CSRNG_BUS_WIDTH - 1:0] fips_csrng; |
| bit [CSRNG_BUS_WIDTH - 1:0] csrng_seed; |
| bit entropy_data_reg_enable; |
| |
| entropy_data_reg_enable = (cfg.otp_en_es_fw_read == MuBi8True) && |
| (ral.conf.entropy_data_reg_enable.get_mirrored_value() == MuBi4True); |
| |
| `uvm_info(`gfn, $sformatf("raw_process_q.size(): %01d", raw_process_q.size()), UVM_FULL) |
| `uvm_info(`gfn, $sformatf("sha_process_q.size(): %01d", sha_process_q.size()), UVM_FULL) |
| fips_csrng = predict_fips_csrng(); |
| |
| // package data for routing to SW and to CSRNG: |
| csrng_seed = get_csrng_seed(fips_csrng); |
| |
| // Only inject entropy data if entropy data is enabled |
| if (entropy_data_reg_enable) begin |
| entropy_data_q.push_back(csrng_seed); |
| end |
| |
| fips_csrng_q.push_back(fips_csrng); |
| |
| endfunction |
| |
| virtual task process_csrng(); |
| push_pull_item#(.HostDataWidth(FIPS_CSRNG_BUS_WIDTH)) item; |
| `uvm_info(`gfn, "task \"process_csrng\" starting\n", UVM_FULL) |
| |
| forever begin |
| bit match_found = 0; |
| |
| csrng_fifo.get(item); |
| if(!cfg.en_scb) begin |
| continue; |
| end |
| `uvm_info(`gfn, $sformatf("process_csrng: new item: %096h\n", item.d_data), UVM_HIGH) |
| |
| // Check to see whether a recov_alert should be expected |
| if (seeds_out != 0 && get_csrng_seed(item.d_data) == prev_csrng_seed) begin |
| `uvm_info(`gfn, "Repeated seed, expecting recov_alert", UVM_MEDIUM) |
| `DV_CHECK_FATAL(ral.recov_alert_sts.es_bus_cmp_alert.predict(1'b1)); |
| set_exp_alert(.alert_name("recov_alert"), .is_fatal(0), .max_delay(cfg.alert_max_delay)); |
| end |
| |
| prev_csrng_seed = get_csrng_seed(item.d_data); |
| seeds_out++; |
| |
| while (fips_csrng_q.size() > 0) begin : seed_trial_loop |
| bit [FIPS_CSRNG_BUS_WIDTH - 1:0] prediction; |
| // Unlike in the TL case, there is no need to leave seed predictions in the queue. |
| prediction = fips_csrng_q.pop_front(); |
| if (prediction == item.d_data) begin |
| csrng_seeds++; |
| match_found = 1; |
| `uvm_info(`gfn, $sformatf("CSRNG Match found: %d\n", csrng_seeds), UVM_FULL) |
| break; |
| end else begin |
| csrng_drops++; |
| `uvm_info(`gfn, $sformatf("CSRNG Dropped seed: %d\n", csrng_drops), UVM_FULL) |
| `uvm_info(`gfn, $sformatf("item: %0x\n", item.d_data), UVM_FULL) |
| `uvm_info(`gfn, $sformatf("pred: %0x\n", prediction), UVM_FULL) |
| end |
| end : seed_trial_loop |
| `DV_CHECK_EQ_FATAL(match_found, 1, |
| "All candidate csrng seeds have been checked, with no match") |
| end |
| endtask |
| |
| virtual function void process_observe_fifo_csr_access(tl_seq_item item, uvm_reg csr); |
| bit [TL_DW - 1:0] csr_val; |
| bit match_found = 0; |
| string msg, fmt; |
| bit fw_ov_enabled = (cfg.otp_en_es_fw_over == MuBi8True) && |
| (ral.fw_ov_control.fw_ov_mode.get_mirrored_value() == MuBi4True); |
| |
| csr_val = item.d_data; |
| |
| fmt = "Predicting observe FIFO access, Looking for: %08x"; |
| msg = $sformatf(fmt, csr_val); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| if (!fw_ov_enabled) begin |
| // if fw_ov mode has never been enabled (and the programming model has been correctly |
| // applied) then the observe fifo should be empty and cleared. |
| msg = "Observe FIFO is disabled"; |
| `uvm_info(`gfn, msg, UVM_FULL) |
| `DV_CHECK_FATAL(csr.predict(.value('0), .kind(UVM_PREDICT_READ))) |
| end else begin |
| msg = $sformatf("Checking %0d candidate seeds", observe_fifo_q.size()); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| while (observe_fifo_q.size() > 0) begin : seed_trial_loop |
| bit [TL_DW - 1:0] prediction; |
| // Unlike the ENTROPY_DATA CSR case, there is no need to leave seed predictions |
| // in the queue. |
| prediction = observe_fifo_q.pop_front(); |
| if (prediction == csr_val) begin |
| `DV_CHECK_FATAL(csr.predict(.value(prediction), .kind(UVM_PREDICT_READ))) |
| observe_fifo_words++; |
| match_found = 1; |
| cov_vif.cg_observe_fifo_event_sample( |
| prim_mubi_pkg::mubi4_t'(ral.conf.fips_enable.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.conf.threshold_scope.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.conf.rng_bit_enable.get_mirrored_value()), |
| ral.conf.rng_bit_sel.get_mirrored_value(), |
| prim_mubi_pkg::mubi4_t'(ral.entropy_control.es_route.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.entropy_control.es_type.get_mirrored_value()), |
| prim_mubi_pkg::mubi4_t'(ral.conf.entropy_data_reg_enable.get_mirrored_value()), |
| prim_mubi_pkg::mubi8_t'(cfg.otp_en_es_fw_read), |
| prim_mubi_pkg::mubi4_t'(ral.fw_ov_control.fw_ov_mode.get_mirrored_value()), |
| prim_mubi_pkg::mubi8_t'(cfg.otp_en_es_fw_over), |
| prim_mubi_pkg::mubi4_t'(ral.fw_ov_control.fw_ov_entropy_insert.get_mirrored_value()) |
| ); |
| msg = $sformatf("Match found: %d\n", observe_fifo_words); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| break; |
| end else begin |
| observe_fifo_drops++; |
| msg = $sformatf("Dropped word: %d\n", observe_fifo_drops); |
| `uvm_info(`gfn, msg, UVM_FULL) |
| end |
| end : seed_trial_loop |
| `DV_CHECK_EQ_FATAL(match_found, 1, |
| "All candidate observe FIFO words have been checked, with no match") |
| end |
| endfunction |
| |
| virtual function void reset(string kind = "HARD"); |
| super.reset(kind); |
| |
| if(kind == "HARD") begin |
| // reset local fifos queues and variables |
| handle_disable_reset(HardReset); |
| // Immediately inform the collect_entropy process |
| // that the IP is disabled |
| dut_pipeline_enabled = 0; |
| end |
| endfunction |
| |
| function void check_phase(uvm_phase phase); |
| super.check_phase(phase); |
| // Normally at this point a simulation checks that all FIFOs and |
| // Queues are empty. However, for entropy_src, which has no 1-1 |
| // mapping between inputs and potential output, most of the simulations |
| // are time-based not transaction-based. |
| // |
| // The scoreboard FIFOs are allowed to have some entries at end of sim |
| // as these may represent: |
| // - unused RNG inputs |
| // - unused internal state corresponding to partial seeds |
| // - dropped outputs (due to finite buffer space inside the DUT) |
| // |
| // One can in principal, check that there are no dropped outputs in |
| // the limited case that: |
| // 1. less than four (4) seeds have been generated, since the |
| // last time the DUT was enabled, AND |
| // 2. all four have been given time to propagate through the DUT before |
| // it is disabled. |
| // |
| // This second condition may be harder to achieve. An easier alternative |
| // would be to perform a check that none of the first 4 generated seeds |
| // are dropped since the DUT was most recently enabled. |
| // |
| // TODO: Add this latter alternative check. |
| endfunction |
| |
| endclass |