From a23a87069dbf896a35156e6175e77c2f3fc81ab2 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Zambrana Date: Thu, 16 Jan 2025 18:13:12 +0000 Subject: [PATCH 1/5] [dv, aon_timer] Add convenience functions to cfg object for reuse These functions are used to read RTL signals and they are called in VSEQs and in the scoreboard Signed-off-by: Antonio Martinez Zambrana --- hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv b/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv index 63bf597fa631c..5720b8c1aaca9 100644 --- a/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv +++ b/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv @@ -10,6 +10,7 @@ class aon_timer_env_cfg extends cip_base_env_cfg #(.RAL_T(aon_timer_reg_block)); virtual pins_if #(1) sleep_vif; // ext component cfgs + string path_to_rtl = "tb.dut"; `uvm_object_utils_begin(aon_timer_env_cfg) `uvm_object_utils_end @@ -17,6 +18,17 @@ class aon_timer_env_cfg extends cip_base_env_cfg #(.RAL_T(aon_timer_reg_block)); extern function new (string name=""); extern virtual function void initialize(bit [31:0] csr_base_addr = '1); + // Convenience wrapper function to avoid calling explictly uvm_hdl_read multiple times + // It takes a path relative to the RTL and appends the RTL instance to it + extern function bit hdl_read_bit(string path); + // Waits for AON signal to become 'value' + extern task wait_for_aon_signal(string path, bit value); + // Waits for the WE to rise and fall after a TL-UL write access + // Does HDL reads to be in sync with when the values kick-in the RTL + // It needs to be called the moment the TL-access occurs, otherwise the thread may hang if the WE + // has risen already + extern task wait_for_we_pulse(input string path); + endclass : aon_timer_env_cfg function aon_timer_env_cfg::new (string name=""); @@ -32,3 +44,24 @@ function void aon_timer_env_cfg::initialize(bit [31:0] csr_base_addr = '1); // set num_interrupts & num_alerts num_interrupts = ral.intr_state.get_n_used_bits(); endfunction : initialize + +function bit aon_timer_env_cfg::hdl_read_bit(string path); + bit hdl_bit; + if (! uvm_hdl_read({path_to_rtl,path}, hdl_bit)) + `uvm_error ($sformatf("%m"), $sformatf("HDL Read from %s failed", path)) + return hdl_bit; +endfunction + +task aon_timer_env_cfg::wait_for_aon_signal(string path, bit value); + bit actual_value; + do begin + actual_value = hdl_read_bit(path); + if (actual_value !== value) + aon_clk_rst_vif.wait_clks(1); + end while (actual_value !== value); +endtask + +task aon_timer_env_cfg::wait_for_we_pulse(input string path); + wait_for_aon_signal(path, 1); + wait_for_aon_signal(path, 0); +endtask From 5125b12b4c7972c31080f2b2fed1519d5a7f6fba Mon Sep 17 00:00:00 2001 From: Antonio Martinez Zambrana Date: Wed, 15 Jan 2025 11:38:56 +0000 Subject: [PATCH 2/5] [dv, aon_timer] Enabling the prediction mask for cip_scb comparison Signed-off-by: Antonio Martinez Zambrana --- hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv b/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv index 5720b8c1aaca9..785c32acf5f7e 100644 --- a/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv +++ b/hw/ip/aon_timer/dv/env/aon_timer_env_cfg.sv @@ -17,6 +17,9 @@ class aon_timer_env_cfg extends cip_base_env_cfg #(.RAL_T(aon_timer_reg_block)); extern function new (string name=""); extern virtual function void initialize(bit [31:0] csr_base_addr = '1); + // Set the 'has_prediction' field flag for all the intr_state fields so the comparison + // on reads can be carried out in cip_base_scoreboard + extern function void set_intr_state_has_prediction(); // Convenience wrapper function to avoid calling explictly uvm_hdl_read multiple times // It takes a path relative to the RTL and appends the RTL instance to it @@ -24,9 +27,9 @@ class aon_timer_env_cfg extends cip_base_env_cfg #(.RAL_T(aon_timer_reg_block)); // Waits for AON signal to become 'value' extern task wait_for_aon_signal(string path, bit value); // Waits for the WE to rise and fall after a TL-UL write access - // Does HDL reads to be in sync with when the values kick-in the RTL + // Does HDL reads to be in sync with when the values are visible in the RTL // It needs to be called the moment the TL-access occurs, otherwise the thread may hang if the WE - // has risen already + // has risen and dropped again already extern task wait_for_we_pulse(input string path); endclass : aon_timer_env_cfg @@ -43,6 +46,7 @@ function void aon_timer_env_cfg::initialize(bit [31:0] csr_base_addr = '1); // set num_interrupts & num_alerts num_interrupts = ral.intr_state.get_n_used_bits(); + set_intr_state_has_prediction(); endfunction : initialize function bit aon_timer_env_cfg::hdl_read_bit(string path); @@ -65,3 +69,18 @@ task aon_timer_env_cfg::wait_for_we_pulse(input string path); wait_for_aon_signal(path, 1); wait_for_aon_signal(path, 0); endtask + +function void aon_timer_env_cfg::set_intr_state_has_prediction(); + foreach (ral_model_names[i]) begin + dv_base_reg regs_q[$]; + ral_models[ral_model_names[i]].get_dv_base_regs(regs_q); + foreach (regs_q[j]) begin + if (!uvm_re_match("intr_state*", regs_q[j].get_name())) begin + dv_base_reg_field fields_q[$]; + regs_q[j].get_dv_base_reg_fields(fields_q); + foreach (fields_q[k])// Setting all fields to be compared in the base scoreboard + fields_q[k].has_prediction = 1; + end + end + end +endfunction From 0ec4fe3bb337f551e9dd9a2aa24ec4b5d60a0bcd Mon Sep 17 00:00:00 2001 From: Antonio Martinez Zambrana Date: Mon, 9 Dec 2024 19:06:13 +0000 Subject: [PATCH 3/5] [dv, aon_timer] Wait for writes to cross the CDC boundary In order to simplify the TB model the sequences wait until the changes make it to the AON-side Signed-off-by: Antonio Martinez Zambrana --- .../dv/env/seq_lib/aon_timer_base_vseq.sv | 67 ++++++++++++++++--- .../dv/env/seq_lib/aon_timer_jump_vseq.sv | 4 +- .../env/seq_lib/aon_timer_prescaler_vseq.sv | 6 ++ 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv index f7d3fdc36832d..fd5fe257f6109 100644 --- a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv +++ b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv @@ -59,6 +59,7 @@ class aon_timer_base_vseq extends cip_base_vseq #( extern virtual task apply_reset(string kind = "HARD"); extern virtual task apply_resets_concurrently(int reset_duration_ps = 0); extern task wait_for_interrupt(bit intr_state_read = 1); + extern task write_wkup_reg(input uvm_object ptr, input uvm_reg_data_t value); endclass : aon_timer_base_vseq @@ -98,13 +99,16 @@ task aon_timer_base_vseq::aon_timer_shutdown(); `uvm_info(`gfn, "Shutting down AON Timer...", UVM_LOW) `uvm_info(`gfn, "Writing 0 to WKUP_CTRL and WDOG_CTRL to disable AON timer", UVM_HIGH) - csr_utils_pkg::csr_wr(ral.wkup_ctrl.enable, 1'b0); + write_wkup_reg(ral.wkup_ctrl.enable, 1'b0); + `uvm_info(`gfn, "write_reg wdog_ctr.enable", UVM_DEBUG); csr_utils_pkg::csr_wr(ral.wdog_ctrl.enable, 1'b0); `uvm_info(`gfn, "Clearing interrupts, count registers and wakeup request.", UVM_HIGH) // Clear wake-up request if we have any csr_utils_pkg::csr_wr(ral.wkup_cause, 1'b0); + // We need to ensure the prediction has kicked in before we read the intr_state + wait (ral.intr_state.is_busy() == 0); // Clear the interrupts csr_utils_pkg::csr_wr(ral.intr_state, 2'b11); @@ -113,9 +117,9 @@ task aon_timer_base_vseq::aon_timer_shutdown(); "to WDOG_COUNT."}, wkup_count, wdog_count), UVM_LOW) // Register Write - csr_utils_pkg::csr_wr(ral.wkup_count_lo, wkup_count[31:0]); - csr_utils_pkg::csr_wr(ral.wkup_count_hi, wkup_count[63:32]); - csr_utils_pkg::csr_wr(ral.wdog_count, wdog_count); + write_wkup_reg(ral.wkup_count_lo, wkup_count[31:0]); + write_wkup_reg(ral.wkup_count_hi, wkup_count[63:32]); + write_wkup_reg(ral.wdog_count, wdog_count); // Wait to settle registers on AON timer domain cfg.aon_clk_rst_vif.wait_clks(5); @@ -123,27 +127,27 @@ endtask : aon_timer_shutdown // setup basic aon_timer features task aon_timer_base_vseq::aon_timer_init(); - + bit wkup_thold_lo_we, wkup_thold_hi_we; // Clear the interrupts csr_utils_pkg::csr_wr(ral.intr_state, 2'b11); `uvm_info(`gfn, "Initializating AON Timer. Writing 0 to WKUP_COUNT and WDOG_COUNT", UVM_LOW) // Register Write - csr_utils_pkg::csr_wr(ral.wkup_count_lo, 32'h0000_0000); - csr_utils_pkg::csr_wr(ral.wkup_count_hi, 32'h0000_0000); + write_wkup_reg(ral.wkup_count_lo, 32'h0000_0000); + write_wkup_reg(ral.wkup_count_hi, 32'h0000_0000); csr_utils_pkg::csr_wr(ral.wdog_count, 32'h0000_0000); `uvm_info(`gfn, "Randomizing AON Timer thresholds", UVM_HIGH) `uvm_info(`gfn, $sformatf("Writing 0x%0h to wkup_thold", wkup_thold), UVM_HIGH) - csr_utils_pkg::csr_wr(ral.wkup_thold_lo, wkup_thold[31:0]); - csr_utils_pkg::csr_wr(ral.wkup_thold_hi, wkup_thold[63:32]); + write_wkup_reg(ral.wkup_thold_lo, wkup_thold[31:0]); + write_wkup_reg(ral.wkup_thold_hi, wkup_thold[63:32]); `uvm_info(`gfn, $sformatf("Writing 0x%0h to wdog_bark_thold", wdog_bark_thold), UVM_HIGH) - csr_utils_pkg::csr_wr(ral.wdog_bark_thold, wdog_bark_thold); + write_wkup_reg(ral.wdog_bark_thold, wdog_bark_thold); `uvm_info(`gfn, $sformatf("Writing 0x%0h to wdog_bite_thold", wdog_bite_thold), UVM_HIGH) - csr_utils_pkg::csr_wr(ral.wdog_bite_thold, wdog_bite_thold); + write_wkup_reg(ral.wdog_bite_thold, wdog_bite_thold); cfg.lc_escalate_en_vif.drive(0); @@ -188,6 +192,9 @@ task aon_timer_base_vseq::wait_for_interrupt(bit intr_state_read = 1); if (intr_state_read) begin // Wait 2 clocks to ensure interrupt is visible on intr_state read cfg.aon_clk_rst_vif.wait_clks(2); + + // We need to ensure the prediction has kicked in before we read the intr_state + wait (ral.intr_state.is_busy()==0); csr_utils_pkg::csr_rd(ral.intr_state, intr_state_value); end @@ -197,3 +204,41 @@ task aon_timer_base_vseq::wait_for_interrupt(bit intr_state_read = 1); end end endtask : wait_for_interrupt + +// Use only to write wkup regs, since some wdog regs won't see the WE signal go high until REGWEN +// is sent +task aon_timer_base_vseq::write_wkup_reg(input uvm_object ptr, input uvm_reg_data_t value); + bit we; + string path_to_we; + csr_field_t csr_or_fld = decode_csr_or_field(ptr); + + if (csr_or_fld.csr == null) + `uvm_fatal(`gfn, "Couldn't decode argument into CSR reg") + path_to_we = {"tb.dut.u_reg.aon_", csr_or_fld.csr.get_name(), "_we"}; + // Fork killable by reset + fork + begin : iso_fork + fork + wait(cfg.under_reset); + begin + csr_utils_pkg::csr_wr(ptr, value); + // After write we wait for WE to go high and then low + do begin + if (! uvm_hdl_read(path_to_we, we)) + `uvm_error (`gfn, $sformatf("HDL Read from %s failed", path_to_we)) + if (we === 0) + cfg.aon_clk_rst_vif.wait_clks(1); // enabled is synchronised to the aon domain + end while (!we); + do begin + if (! uvm_hdl_read(path_to_we, we)) + `uvm_error (`gfn, $sformatf("HDL Read from %s failed", path_to_we)) + if (we === 1) + cfg.aon_clk_rst_vif.wait_clks(1); // enabled is synchronised to the aon domain + end while (we); + end + join_any + disable fork; + end : iso_fork + join + +endtask : write_wkup_reg diff --git a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_jump_vseq.sv b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_jump_vseq.sv index e32e57709e217..0a3fcd8cef31a 100644 --- a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_jump_vseq.sv +++ b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_jump_vseq.sv @@ -41,8 +41,8 @@ endtask : body task aon_timer_jump_vseq::jump_configure(); // Write random value to the COUNT registers - csr_utils_pkg::csr_wr(ral.wkup_count_lo, wkup_count[31:0]); - csr_utils_pkg::csr_wr(ral.wkup_count_hi, wkup_count[63:32]); + write_wkup_reg(ral.wkup_count_lo, wkup_count[31:0]); + write_wkup_reg(ral.wkup_count_hi, wkup_count[63:32]); `uvm_info(`gfn, $sformatf("\n\t Writing random COUNT value of %d to WKUP", wkup_count), UVM_HIGH) diff --git a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv index 22149bdef6a48..29c3acbc61786 100644 --- a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv +++ b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv @@ -18,6 +18,7 @@ class aon_timer_prescaler_vseq extends aon_timer_base_vseq; endclass : aon_timer_prescaler_vseq + constraint aon_timer_prescaler_vseq::thold_count_c { wkup_thold inside {[1:2]}; wdog_bark_thold inside {[1:2]}; @@ -31,10 +32,12 @@ function aon_timer_prescaler_vseq::new (string name=""); endfunction : new task aon_timer_prescaler_vseq::body(); + aon_timer_init(); prescaler_configure(); wait_for_interrupt(); aon_timer_shutdown(); + endtask : body task aon_timer_prescaler_vseq::prescaler_configure(); @@ -45,6 +48,9 @@ task aon_timer_prescaler_vseq::prescaler_configure(); $sformatf("\n\t Writing random prescaler value of %d to WKUP CTRL", prescaler), UVM_HIGH) + csr_utils_pkg::csr_spinwait(.ptr(ral.wkup_ctrl.prescaler), .exp_data(prescaler), .backdoor(1)); + `uvm_info(`gfn, "Written values (wkup_prescaler) has propagated through the CDC", UVM_DEBUG) + `uvm_info(`gfn, "Enabling AON Timer (WKUP ONLY). Writing 1 to WKUP_CTRL", UVM_HIGH) csr_utils_pkg::csr_wr(ral.wkup_ctrl.enable, 1'b1); From f02bad2236b64247c43fb0ef8aeed961cd74cc0c Mon Sep 17 00:00:00 2001 From: Antonio Martinez Zambrana Date: Mon, 9 Dec 2024 18:52:45 +0000 Subject: [PATCH 4/5] [dv, aon_timer] Adding CDC-aware intr_state register In order to improve the model, the TB needs to keep track of the backdoor value of intr_state fields, otherwise there are mismatches. The CDC-timed registers from USBDEV have been re-used for this pupose. Signed-off-by: Antonio Martinez Zambrana --- hw/ip/aon_timer/dv/env/aon_timer_env.core | 2 + hw/ip/aon_timer/dv/env/aon_timer_env_pkg.sv | 3 +- .../dv/env/aon_timer_intr_timed_regs.sv | 96 +++++++++++++++++++ .../dv/env/seq_lib/aon_timer_base_vseq.sv | 17 +--- .../env/seq_lib/aon_timer_prescaler_vseq.sv | 2 - 5 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 hw/ip/aon_timer/dv/env/aon_timer_intr_timed_regs.sv diff --git a/hw/ip/aon_timer/dv/env/aon_timer_env.core b/hw/ip/aon_timer/dv/env/aon_timer_env.core index 84b2fe4219cef..02a6f3091d56e 100644 --- a/hw/ip/aon_timer/dv/env/aon_timer_env.core +++ b/hw/ip/aon_timer/dv/env/aon_timer_env.core @@ -9,11 +9,13 @@ filesets: depend: - lowrisc:dv:ralgen - lowrisc:dv:cip_lib + - lowrisc:dv:usbdev_env files: - aon_timer_env_pkg.sv - aon_timer_env_cfg.sv: {is_include_file: true} - aon_timer_env_cov.sv: {is_include_file: true} - aon_timer_virtual_sequencer.sv: {is_include_file: true} + - aon_timer_intr_timed_regs.sv: {is_include_file: true} - aon_timer_scoreboard.sv: {is_include_file: true} - aon_timer_env.sv: {is_include_file: true} - seq_lib/aon_timer_vseq_list.sv: {is_include_file: true} diff --git a/hw/ip/aon_timer/dv/env/aon_timer_env_pkg.sv b/hw/ip/aon_timer/dv/env/aon_timer_env_pkg.sv index dd7f14f70cf9b..d0f3f25411789 100644 --- a/hw/ip/aon_timer/dv/env/aon_timer_env_pkg.sv +++ b/hw/ip/aon_timer/dv/env/aon_timer_env_pkg.sv @@ -13,7 +13,7 @@ package aon_timer_env_pkg; import dv_base_reg_pkg::*; import csr_utils_pkg::*; import aon_timer_ral_pkg::*; - + import usbdev_env_pkg::*; // macro includes `include "uvm_macros.svh" `include "dv_macros.svh" @@ -31,6 +31,7 @@ package aon_timer_env_pkg; `include "aon_timer_env_cfg.sv" `include "aon_timer_env_cov.sv" `include "aon_timer_virtual_sequencer.sv" + `include "aon_timer_intr_timed_regs.sv" `include "aon_timer_scoreboard.sv" `include "aon_timer_env.sv" `include "aon_timer_vseq_list.sv" diff --git a/hw/ip/aon_timer/dv/env/aon_timer_intr_timed_regs.sv b/hw/ip/aon_timer/dv/env/aon_timer_intr_timed_regs.sv new file mode 100644 index 0000000000000..10507e6449825 --- /dev/null +++ b/hw/ip/aon_timer/dv/env/aon_timer_intr_timed_regs.sv @@ -0,0 +1,96 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// `Timed registers` cannot be guaranteed to change at an exact time, so a certain amount of +// variability must be expected in the timing. +class aon_timer_intr_timed_regs extends uvm_object; + `uvm_object_utils(aon_timer_intr_timed_regs) + + extern function new (string name=""); + + typedef enum { + TimedIntrStateWkupExpired = 0, + TimedIntrStateWdogBark + } timed_reg_e; + + // Access to DUT clock. + virtual clk_rst_if clk_rst_vif; + // Access to DUT registers. + aon_timer_reg_block ral; + + timed_reg timed[timed_reg_e]; + + // Monotonic, wrapping cycle count; used to detect when expectations have not been met. + // Incremented in 'check_predictions' task + int time_now = 0; + + // Perform a read of the actual DUT register state, for checking against expectations. + // Note: we perform a backdoor read to avoid impacting the timing of the DUT and DV. + extern task read_act_data(timed_reg_e r, output uvm_reg_data_t act_data); + // Add a timed, predicted state change to the list of expectations for the given register. + extern function void predict(timed_reg_e r, uvm_reg_data_t prev_data, uvm_reg_data_t new_data); + // Check a DUT read from the specified register against any timed expectations. + extern function uvm_reg_data_t read(timed_reg_e r, uvm_reg_data_t act_data); + // This process runs forever checking every prediction that is made, using backdoor csr_rd in zero + // time to avoid interfering with actual CSR reads and the timing of the simulation. + // It also resets the registers if a reset is seen. + extern task check_predictions(ref bit under_reset); + +endclass : aon_timer_intr_timed_regs + +function aon_timer_intr_timed_regs::new (string name=""); + super.new(name); +endfunction : new + +task aon_timer_intr_timed_regs::read_act_data(timed_reg_e r, output uvm_reg_data_t act_data); + case (r) + TimedIntrStateWkupExpired : begin + csr_rd(.ptr(ral.intr_state.wkup_timer_expired), .value(act_data), .backdoor(1)); + end + TimedIntrStateWdogBark : begin + csr_rd(.ptr(ral.intr_state.wdog_timer_bark), .value(act_data), .backdoor(1)); + end + default: `uvm_fatal(`gfn, "Invalid/unrecognized register") + endcase + `uvm_info(`gfn, $sformatf("Backdoor read of reg %p yielded 0x%0x", r, act_data), UVM_HIGH) +endtask : read_act_data + +function void aon_timer_intr_timed_regs::predict(timed_reg_e r, uvm_reg_data_t prev_data, + uvm_reg_data_t new_data); + `uvm_info(`gfn, $sformatf("Expecting reg %p <= 0x%0x, from 0x%0x (mask 0x%0x), time_now %0d", + r, new_data, prev_data, prev_data ^ new_data, time_now), UVM_MEDIUM) + timed[r].predict(time_now, new_data, prev_data); +endfunction : predict + +function uvm_reg_data_t aon_timer_intr_timed_regs::read(timed_reg_e r, uvm_reg_data_t act_data); + `uvm_info(`gfn, $sformatf("Producing prediction for %p, act_data 0x%0x", r, act_data), + UVM_MEDIUM) + return timed[r].read(time_now, act_data); +endfunction : read + +task aon_timer_intr_timed_regs::check_predictions(ref bit under_reset); + // Collect the initial values post-reset. + wait (clk_rst_vif.rst_n === 1'b1); + forever begin + timed_reg_e r; + // Check on every negedge to avoid races with CSR changes + @(negedge clk_rst_vif.clk); + time_now++; + // Check each of the timed registers for expired expectations. + r = r.first(); + for (int i=0; i < r.num(); i++) begin + if (under_reset) begin + `uvm_info(`gfn, $sformatf("Resetting timed register predictions at 0x%0x", time_now), + UVM_MEDIUM) + timed[r].reset(); + end else if (timed[r].preds_pending()) begin + uvm_reg_data_t act_data; + // Something is expected to happen to this register field. + read_act_data(r, act_data); + timed[r].check_pred(time_now, $sformatf("%p", r), act_data); + end + r = r.next(); + end // for (int i=0; i < r.num(); i++) + end +endtask : check_predictions diff --git a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv index fd5fe257f6109..03f33dd866441 100644 --- a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv +++ b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_base_vseq.sv @@ -214,27 +214,16 @@ task aon_timer_base_vseq::write_wkup_reg(input uvm_object ptr, input uvm_reg_dat if (csr_or_fld.csr == null) `uvm_fatal(`gfn, "Couldn't decode argument into CSR reg") - path_to_we = {"tb.dut.u_reg.aon_", csr_or_fld.csr.get_name(), "_we"}; + path_to_we = {".u_reg.aon_", csr_or_fld.csr.get_name(), "_we"}; + csr_utils_pkg::csr_wr(ptr, value); // Fork killable by reset fork begin : iso_fork fork wait(cfg.under_reset); begin - csr_utils_pkg::csr_wr(ptr, value); // After write we wait for WE to go high and then low - do begin - if (! uvm_hdl_read(path_to_we, we)) - `uvm_error (`gfn, $sformatf("HDL Read from %s failed", path_to_we)) - if (we === 0) - cfg.aon_clk_rst_vif.wait_clks(1); // enabled is synchronised to the aon domain - end while (!we); - do begin - if (! uvm_hdl_read(path_to_we, we)) - `uvm_error (`gfn, $sformatf("HDL Read from %s failed", path_to_we)) - if (we === 1) - cfg.aon_clk_rst_vif.wait_clks(1); // enabled is synchronised to the aon domain - end while (we); + cfg.wait_for_we_pulse(path_to_we); end join_any disable fork; diff --git a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv index 29c3acbc61786..f4ede647fcfca 100644 --- a/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv +++ b/hw/ip/aon_timer/dv/env/seq_lib/aon_timer_prescaler_vseq.sv @@ -32,12 +32,10 @@ function aon_timer_prescaler_vseq::new (string name=""); endfunction : new task aon_timer_prescaler_vseq::body(); - aon_timer_init(); prescaler_configure(); wait_for_interrupt(); aon_timer_shutdown(); - endtask : body task aon_timer_prescaler_vseq::prescaler_configure(); From b664bcc782abcc731d54f097256f4ce773988c89 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Zambrana Date: Mon, 9 Dec 2024 19:11:31 +0000 Subject: [PATCH 5/5] [dv, aon_timer] scoreboard model and checks refactor The scoreboard now checks when different configuration values have kicked-in and then computes the number of cycles it takes for any of the interrupts to raise. In addition, the TB now aims to predict the mirrored value of interrupt state register in order to improve the passing rate (specially those related to the buil-in intr_tests in the cip_base_vseq) Signed-off-by: Antonio Martinez Zambrana --- .../aon_timer/dv/env/aon_timer_scoreboard.sv | 1255 +++++++++++++++-- 1 file changed, 1101 insertions(+), 154 deletions(-) diff --git a/hw/ip/aon_timer/dv/env/aon_timer_scoreboard.sv b/hw/ip/aon_timer/dv/env/aon_timer_scoreboard.sv index 9e51ad415fa12..44d5841f39754 100644 --- a/hw/ip/aon_timer/dv/env/aon_timer_scoreboard.sv +++ b/hw/ip/aon_timer/dv/env/aon_timer_scoreboard.sv @@ -20,6 +20,7 @@ class aon_timer_scoreboard extends cip_base_scoreboard #( local bit wdog_regwen; local bit wdog_pause_in_sleep; local bit wdog_num_update_due; + local uint wdog_count; local uint bark_thold; local uint bite_thold; @@ -30,7 +31,7 @@ class aon_timer_scoreboard extends cip_base_scoreboard #( local bit [31:0] wdog_bite_num; // expected values - local bit intr_status_exp [2]; + local bit [1:0] intr_status_exp; local bit wdog_rst_req_exp = 0; typedef enum logic { @@ -38,38 +39,287 @@ class aon_timer_scoreboard extends cip_base_scoreboard #( WDOG = 1'b1 } timers_e; - // TLM agent fifos + // Prediction and checking of the loosely-timed registers. + aon_timer_intr_timed_regs timed_regs; + + // BFM state information for the loosely-timed registers. + typedef struct { + uvm_reg_data_t r[aon_timer_intr_timed_regs::timed_reg_e]; + } bfm_timed_regs_t; + + // Previous state + bfm_timed_regs_t prev_timed_regs; + + // UVM events to trigger the count for WDOG/WKUP timer to avoid any potential race condition + uvm_event wkup_count_ev = new(); + uvm_event wdog_count_ev = new(); - // local queues to hold incoming packets pending comparison + bit predicted_wkup_intr_q[$]; + bit predicted_wdog_intr_q[$]; + bit ongoing_intr_state_read; + // A write of 0 to wkup_cause may take some delay to arrive because of CDC crossing (from the TL + // bus on the main clock to the register on the aon clock). This timing might overlap with an + // expected watchdog interrupt. To avoid this causing a problem, we avoid checking the predicted + // value (in run_wdog_bark_timer) if the timing collides. + // + // aon_clk_cycle is a counter of AON cycles which is kept in sync by track_aon_clk_cycle. It gets + // snapshotted to last_wkup_cause_write_aon_clk_cycle in predict_wkup_cause when the write enable + // for the aon_wkup_cause register drops. We can see that there has been a collision if the two + // values are equal. + // last_wkup_cause_write_aon_clk_cycle variable doesn't have a race condition even if both SYS and + // AON clock have the pos-edge at the same time, since the variable is used a couple of sys-clock + // ticks (or more, depending on CDC crossing) after the AON clock cycle because that's when the + // interrupt check is carried out. + int unsigned aon_clk_cycle = 0; + int unsigned last_wkup_cause_write_aon_clk_cycle = 0; extern function new (string name="", uvm_component parent=null); + extern function void build_phase(uvm_phase phase); extern task run_phase(uvm_phase phase); - extern task monitor_interrupts(); + // Convenience task which keeps count of a given AON clock cycle. Currently only used to + // distinguish the case when a write to wkup_cause occurs at the same time as a interrupt being + // high + extern task track_aon_clk_cycle(); + // Collects interrupt coverage from RTL interrupts by directly sampling the RTL output + extern task collect_fcov_from_rtl_interrupts(); extern virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); + // Model the timers and interrupts for WKUP/WDOG and compare them against the actual values + // the task doesn't return extern virtual task check_interrupt(); + // This tasks runs forever and it calculates the number of clock cycles until an interrupt is + // raised. The number of clock cycles is stored in variables wkup_num and wdog_bark/bite_num. + // The WKUP timer takes the prescaler into consideration when it comes to calculating clock cycles extern virtual task compute_num_clks(); + // Blocks when the 'pause_in_sleep' signal is set as well as the internal RTL aon_sleep_mode + // signal. Otherwise, the predictions may be late/early with regards to the RTL. extern virtual task wait_for_sleep(); + // The run_wkup_timer and run_wdog_bark/bite_timer are independent tasks which run forever and + // can safely be killed. + // Run wkup/wdog timers predicts the interrupt of each kind (or subkind for WDOG timer with bite + // and bark) and check against the actual value when an interrupt is due. + // The threads also contribute towards coverage and get killed whenever there is a reset or the + // enable bit for their respective counter is not set extern virtual task run_wkup_timer(); extern virtual task run_wdog_bark_timer(); extern virtual task run_wdog_bite_timer(); + // The three collect_*_coverage below tasks run forever and can be safely killed at any time. + // + // collect_wkup_timer_coverage: Whenever the sample_coverage event fires, sample the 64-bit + // wkup_count counter, threshold value and interrupt for wake_up_timer_thold_hit_cg covergroup. + extern task collect_wkup_timer_coverage(ref event sample_coverage); + // collect_wdog_bark_timer_coverage: Whenever the sample_coverage event fires, sample the 32-bit + // wdog_count counter, threshold value and interrupt for watchdog_timer_bark_thold_hit_cg + // covergroup. + extern task collect_wdog_bark_timer_coverage(ref event sample_coverage); + // collect_wdog_bite_timer_coverage: Whenever the sample_coverage event fires, sample the 32-bit + // wdog_count counter, threshold value and interrupt for watchdog_timer_bite_thold_hit_cg + // covergroup. + extern task collect_wdog_bite_timer_coverage(ref event sample_coverage); + + // Sets both predicted timed register (WKUP/WDOG) values, captured from intr_state_exp. + extern function void capture_timed_regs(output bfm_timed_regs_t state); + // The argument passed by reference `state` stores the value predicted for intr_state for each + // type of timer, `r` and tmr refer to the type of timer to predict for the timed and the expected + // interrupts respectively + extern function void capture_timed_regs_independently(ref bfm_timed_regs_t state, + aon_timer_intr_timed_regs::timed_reg_e r, + timers_e tmr); + // Initialise the timed regs (called in the build_phase) + extern function void init_timed_regs(); + // need separate wdog/wkup update functions in case they are called at the same time + extern function void update_timed_regs_independently(timers_e tmr); + // Update the model side of the WKUP/WDOG(or both) timed registers + extern function void update_timed_regs(); + extern function void update_timed_regs_wdog(); + extern function void update_timed_regs_wkup(); + + // Predicts register wkup_cause whenever the busy flag is 0 and blocks other parts of the TB to + // predict it's value until an ongoing prediction finishes + // In addition, the prediction can be delayed by setting 'wait_for_we' if the TB wants the + // prediction to proceed after the wkup_cause write crosses the CDC boundary to be in sync with + // the RTL. + extern task predict_wkup_cause(bit wkup_cause, bit wait_for_we); + // Predicts register intr_state whenever the busy flag is 0. It checks the prediction went through + // and blocks other parts of the TB to predict its value until an ongoing prediction finishes + extern task predict_intr_state(bit [1:0] pred_intr_state, bit field_only = 0, bit is_wkup = 0); + + // Update tasks wait for the value to cross CDC boundaries by checking the write enable + // before the TB starts counting + extern task update_wdog_or_wkup_reg_timely(string path, timers_e timer_type); + extern task update_wdog_count_timely(); + extern task update_wdog_bite_thold_timely(); + extern task update_wdog_bark_thold_timely(); + extern task update_wkup_count_lo_timely(); + extern task update_wkup_count_hi_timely(); + extern task update_wkup_thold_lo_timely(); + extern task update_wkup_thold_hi_timely(); + + // Pushes predicted values for wdog/wkup_interrupt in a queue and keeps track of the RTL until + // the committed value propagates and becomes the register value + extern task wkup_intr_predicted_values(bit exp_wkup_intr); + extern task wdog_intr_predicted_values(bit exp_wdog_intr); + // Checks the predicted interrupt value is the current RTL value by backdoor reads + extern task check_intr_value_propagated(timers_e timer_type); + + // Converts from timers_e to 'timed_reg_e' + extern function aon_timer_intr_timed_regs::timed_reg_e timers_e2time_reg_e(timers_e timer_type); + // Check the value of `intr_wdog_timer_bark/wkup_timer_expired_o' matched the one in the + // intr_state register by the timed registers. + // intr_status_exp[WDOG] can't be used to compare since its value may have been wiped out by the + // time the comparison is carried out + extern function void check_aon_domain_interrupt(timers_e timer_type); + // Returns either `predicted_wdog_intr_q` or `predicted_wkup_intr_q` by reference depending on the + // timer type passed + extern function void return_pred_intr_q(timers_e timer_type, ref bit pred_q[$]); + // Checks the expected mirrored value against the value returned in the TL-UL read for intr_state + extern function void check_intr_state_bit(timers_e timer_type, bit actual_value); + + // Convenience function to check if the wkup/wdog_ctrl.enable bits are set or not depending on + // whether the enable argument is 0 or 1. + extern task wait_for_wdog_enable_matching(bit enable); + extern task wait_for_wkup_enable_matching(bit enable); + + extern virtual function void reset(string kind = "HARD"); + endclass : aon_timer_scoreboard function aon_timer_scoreboard::new (string name="", uvm_component parent=null); super.new(name, parent); endfunction : new +function void aon_timer_scoreboard::build_phase(uvm_phase phase); + super.build_phase(phase); + + // Set-up timed registers (intr_state): + timed_regs = aon_timer_intr_timed_regs::type_id::create("timed_regs"); + timed_regs.clk_rst_vif = cfg.clk_rst_vif; + timed_regs.ral = ral; + init_timed_regs(); +endfunction : build_phase + task aon_timer_scoreboard::run_phase(uvm_phase phase); super.run_phase(phase); fork + track_aon_clk_cycle(); compute_num_clks(); check_interrupt(); - monitor_interrupts(); + collect_fcov_from_rtl_interrupts(); + timed_regs.check_predictions(cfg.under_reset); join_none endtask : run_phase -task aon_timer_scoreboard::monitor_interrupts(); +task aon_timer_scoreboard::track_aon_clk_cycle(); + forever begin + cfg.aon_clk_rst_vif.wait_clks(1); // enabled is synchronised to the aon domain + aon_clk_cycle++; + end +endtask + +function void aon_timer_scoreboard::capture_timed_regs(output bfm_timed_regs_t state); + aon_timer_intr_timed_regs::timed_reg_e r = aon_timer_intr_timed_regs::TimedIntrStateWdogBark; + timers_e tmr_type = WDOG; + capture_timed_regs_independently(.state(state), .r(r), .tmr(tmr_type)); + r = aon_timer_intr_timed_regs::TimedIntrStateWkupExpired; + tmr_type = WKUP; + capture_timed_regs_independently(.state(state), .r(r), .tmr(tmr_type)); +endfunction : capture_timed_regs + +function void aon_timer_scoreboard::capture_timed_regs_independently(ref bfm_timed_regs_t state, + aon_timer_intr_timed_regs:: + timed_reg_e r, timers_e tmr); + state.r[r] = intr_status_exp[tmr]; +endfunction : capture_timed_regs_independently + +function void aon_timer_scoreboard::init_timed_regs(); + bfm_timed_regs_t init_regs; + aon_timer_intr_timed_regs::timed_reg_e r; + // Maximum delay (in DUT clock cycles) for a prediction to be met; most delays should take + // only a few cycles for internal changes to propagate, but some are substantially longer + // oweing to the immediate operation of the functional model. + int unsigned max_delay = 5; + // Capture the initial state of the loosely-timed registers. + capture_timed_regs(init_regs); + + // Remember the register state. + prev_timed_regs = init_regs; + // Create the register descriptions. + r = r.first(); + for (int i = 0; i < r.num(); i++) begin + timed_reg tr = timed_reg::type_id::create("tr"); + uvm_reg_data_t init_val = 0; + dv_base_reg_field fields[$]; + // Collect the field descriptions for this register. + if (!(r inside {aon_timer_intr_timed_regs::TimedIntrStateWdogBark, + aon_timer_intr_timed_regs::TimedIntrStateWkupExpired})) + `uvm_fatal(`gfn, "Invalid/unknown register") + ral.intr_state.get_dv_base_reg_fields(fields); + + // Report the initial value of this register as predicted by the BFM. + `uvm_info(`gfn, $sformatf("Reg %p : initial value 0x%0x", r, init_regs.r[r]), UVM_MEDIUM) + // Collect the initial values of the register fields, dropping any that we cannot predict. + foreach(fields[f]) begin + string field_name = fields[f].get_name(); + // Extract the initial value of this register field from the modeled register state. + uvm_reg_data_t mask = (1 << fields[f].get_n_bits()) - 1; + init_val = (init_regs.r[r] >> fields[f].get_lsb_pos()) & mask; + tr.add_field(fields[f], init_val, max_delay); + `uvm_info(`gfn, $sformatf("Register %p field %s : initially 0x%0x", r, + field_name, init_val), UVM_MEDIUM) + end + timed_regs.timed[r] = tr; + r = r.next(); + end +endfunction : init_timed_regs + +// Update the expectations for the timed registers; this should be called after any operation on +// the BFM that could affect the state of one or more of the timed registers. +function void aon_timer_scoreboard::update_timed_regs(); + aon_timer_intr_timed_regs::timed_reg_e r = r.first(); + bfm_timed_regs_t new_regs; + capture_timed_regs(new_regs); + `uvm_info(`gfn, "After capturing timed regs", UVM_DEBUG) + r = r.first(); + for (int i = 0; i < r.num(); i++) begin + // Has there been a change in the bits that we can predict? + uvm_reg_data_t unpred_mask = timed_regs.timed[r].unpred_mask; + if ((new_regs.r[r] & ~unpred_mask) != (prev_timed_regs.r[r] & ~unpred_mask)) begin + timed_regs.predict(r, prev_timed_regs.r[r], new_regs.r[r]); + end + r = r.next(); + end + // Remember the register state. + prev_timed_regs = new_regs; +endfunction : update_timed_regs + +// Updates timed register independently +function void aon_timer_scoreboard::update_timed_regs_independently(timers_e tmr); + bfm_timed_regs_t new_regs; + aon_timer_intr_timed_regs::timed_reg_e r = timers_e2time_reg_e(tmr); + uvm_reg_data_t unpred_mask = timed_regs.timed[r].unpred_mask; + + capture_timed_regs_independently(.state(new_regs), .r(r), .tmr(tmr)); + `uvm_info(`gfn, $sformatf("Updating Timed regs #intr_state - %0s",tmr.name()), UVM_DEBUG) + + // Has there been a change in the bits that we can predict? + if ((new_regs.r[r] & ~unpred_mask) != (prev_timed_regs.r[r] & ~unpred_mask)) begin + timed_regs.predict(r, prev_timed_regs.r[r], new_regs.r[r]); + end + // Remember the register state. + prev_timed_regs = new_regs; +endfunction : update_timed_regs_independently + +// need separate wdog/wkup update functions in case they are called at the same time +function void aon_timer_scoreboard::update_timed_regs_wdog(); + update_timed_regs_independently(.tmr(timers_e'(WDOG))); +endfunction : update_timed_regs_wdog + +function void aon_timer_scoreboard::update_timed_regs_wkup(); + update_timed_regs_independently(.tmr(timers_e'(WKUP))); +endfunction : update_timed_regs_wkup + +task aon_timer_scoreboard::collect_fcov_from_rtl_interrupts(); forever begin @(cfg.aon_intr_vif.pins); // Sample interrupt pin coverage for interrupt pins @@ -79,7 +329,307 @@ task aon_timer_scoreboard::monitor_interrupts(); end end end -endtask : monitor_interrupts +endtask : collect_fcov_from_rtl_interrupts + +task aon_timer_scoreboard::predict_wkup_cause(bit wkup_cause, bit wait_for_we); + fork + begin : non_blocking_fork + fork + begin : iso_fork + fork + begin : wkup_cause_timely + // Blocks only if 'predicting_value' = 1 + if (wait_for_we) + cfg.wait_for_we_pulse(".u_reg.aon_wkup_cause_we"); + + if (!ral.wkup_cause.predict(.value(wkup_cause), .kind(UVM_PREDICT_READ))) + `uvm_fatal(`gfn, $sformatf("%s prediction failed", `gmv(ral.wkup_cause))) + `uvm_info(`gfn, $sformatf("Updated predicted WKUP-CAUSE to 0x%0x", wkup_cause), + UVM_DEBUG) + if (wait_for_we) + last_wkup_cause_write_aon_clk_cycle = aon_clk_cycle; + end : wkup_cause_timely + begin : reset_kill + wait (under_reset); + end : reset_kill + join_any + disable fork; + end : iso_fork + join + end : non_blocking_fork + join_none +endtask : predict_wkup_cause + +// Update functions waits for the value to cross CDC boundaries before the TB starts counting +task aon_timer_scoreboard::update_wdog_or_wkup_reg_timely(string path, timers_e timer_type); + fork + begin: iso_fork + fork + begin : hdl_read_wdog_wkup_reg_we + cfg.wait_for_we_pulse(path); + // Update once the changes have kicked in. + case(timer_type) + WKUP: wkup_num_update_due = 1; + WDOG: wdog_num_update_due = 1; + default: `uvm_fatal(`gfn, $sformatf("Incorrect timer type:%0s", timer_type.name())) + endcase + end : hdl_read_wdog_wkup_reg_we + begin : reset_kill + wait (under_reset); + end : reset_kill + join_any + disable fork; + end : iso_fork + join +endtask : update_wdog_or_wkup_reg_timely + +// Update functions waits for the value to cross CDC boundaries before the TB starts counting +task aon_timer_scoreboard::update_wdog_count_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wdog_count_we"), .timer_type(WDOG)); + end : non_blocking_fork + join_none +endtask : update_wdog_count_timely + +task aon_timer_scoreboard::update_wdog_bite_thold_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wdog_bite_thold_gated_we"), + .timer_type(WDOG)); + end : non_blocking_fork + join_none +endtask : update_wdog_bite_thold_timely + +task aon_timer_scoreboard::update_wdog_bark_thold_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wdog_bark_thold_gated_we"), + .timer_type(WDOG)); + end : non_blocking_fork + join_none +endtask : update_wdog_bark_thold_timely + +task aon_timer_scoreboard::update_wkup_count_lo_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wkup_count_lo_we"), .timer_type(WKUP)); + end : non_blocking_fork + join_none +endtask : update_wkup_count_lo_timely + +task aon_timer_scoreboard::update_wkup_count_hi_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wkup_count_hi_we"), .timer_type(WKUP)); + end : non_blocking_fork + join_none +endtask : update_wkup_count_hi_timely + +task aon_timer_scoreboard::update_wkup_thold_lo_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wkup_thold_lo_we"), .timer_type(WKUP)); + end : non_blocking_fork + join_none +endtask : update_wkup_thold_lo_timely + +task aon_timer_scoreboard::update_wkup_thold_hi_timely(); + fork + begin : non_blocking_fork + update_wdog_or_wkup_reg_timely(.path(".u_reg.aon_wkup_thold_hi_we"), .timer_type(WKUP)); + end: non_blocking_fork + join_none +endtask : update_wkup_thold_hi_timely + +task aon_timer_scoreboard::predict_intr_state(bit [1:0] pred_intr_state, bit field_only = 0, + bit is_wkup=0); + fork + begin: non_blocking_fork + `uvm_info(`gfn, $sformatf("%m - pred_intr_state = 0x%0x",pred_intr_state), UVM_DEBUG) + fork + begin: iso_fork + fork + begin : intr_state_timely + bit exit_loop; + // The update can occur at the same time as a read access. This could occur in the A + // or D channels. + // If we predicted an update during the A or D-phase, we need to hold the prediction + // until after the whole access finishes. + // If prediction occurs at the D-phase, we check the 'ongoing_intr_state_read' flag, + // which was set in `process_tl_access` during the A-phase. We need to do this because + // D-phase handshake doesn't give information related to the TL-access and address. + while (cfg.m_tl_agent_cfg.vif.h2d.a_valid && cfg.m_tl_agent_cfg.vif.d2h.a_ready) begin + if (cfg.m_tl_agent_cfg.vif.h2d.a_opcode == tlul_pkg::Get) begin + bit [AddrWidth - 1 : 0] a_addr = cfg.m_tl_agent_cfg.vif.h2d.a_address; + string ral_name = "aon_timer_reg_block"; + uvm_reg_addr_t csr_addr = cfg.ral_models[ral_name].get_word_aligned_addr(a_addr); + if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin + uvm_reg csr = cfg.ral_models[ral_name].default_map.get_reg_by_offset(csr_addr); + + if(csr != null) begin + if(csr.get_name() == "intr_state") + cfg.clk_rst_vif.wait_n_clks(1); + else + exit_loop = 1; + end + else + exit_loop = 1; + end + else + exit_loop = 1; + end + else + exit_loop = 1; + + if (exit_loop) + break; + end + // If the update occurs during the D-phase, we wait until the D-phase ends + if (ongoing_intr_state_read) begin + while (cfg.m_tl_agent_cfg.vif.d2h.d_valid) begin + if (cfg.m_tl_agent_cfg.vif.h2d.d_ready) begin + cfg.clk_rst_vif.wait_n_clks(1); + // Leaving loop 1-cycle after handshake completion + break; + end + cfg.clk_rst_vif.wait_n_clks(1); + end + ongoing_intr_state_read = 0; + end + + if (!field_only) begin + if (!ral.intr_state.predict(.value(pred_intr_state), .kind(UVM_PREDICT_READ) )) + `uvm_fatal(`gfn, $sformatf("%s prediction failed", ral.intr_state.get_name())) + end + else begin + if (is_wkup) begin + if (!ral.intr_state.wkup_timer_expired.predict(.value(pred_intr_state[WKUP]), + .kind(UVM_PREDICT_READ) )) + `uvm_fatal(`gfn, $sformatf("%s prediction failed", + ral.intr_state.wkup_timer_expired.get_name())) + end + else begin + if (!ral.intr_state.wdog_timer_bark.predict(.value(pred_intr_state[WDOG]), + .kind(UVM_PREDICT_READ) )) + `uvm_fatal(`gfn, $sformatf("%s prediction failed", + ral.intr_state.wdog_timer_bark.get_name())) + end + end + end : intr_state_timely + begin : reset_kill + wait (under_reset); + end : reset_kill + join_any + disable fork; + end : iso_fork + join + end : non_blocking_fork + join_none +endtask : predict_intr_state + +task aon_timer_scoreboard::wkup_intr_predicted_values(bit exp_wkup_intr); + static int unsigned last_cycle_count = 0; + fork + begin : non_blocking_fork + if (last_cycle_count != timed_regs.time_now) begin + `uvm_info(`gfn, $sformatf("%m - Predicted wkup_intr = 0x%0x", exp_wkup_intr), UVM_DEBUG) + predicted_wkup_intr_q.push_back(exp_wkup_intr); + last_cycle_count = timed_regs.time_now; + fork + begin: iso_fork + fork + begin : wait_values_to_propagate + // do backdoor read and delete values no longer valid. + check_intr_value_propagated(WKUP); + end : wait_values_to_propagate + begin : reset_kill + wait (under_reset); + end : reset_kill + join_any + disable fork; + end : iso_fork + join + end + end : non_blocking_fork + join_none +endtask : wkup_intr_predicted_values + +task aon_timer_scoreboard::wdog_intr_predicted_values(bit exp_wdog_intr); + static int unsigned last_cycle_count = 0; + fork + begin : non_blocking_fork + if (last_cycle_count != timed_regs.time_now) begin + `uvm_info(`gfn, $sformatf("%m - Predicted wdog_intr = 0x%0x", exp_wdog_intr), UVM_DEBUG) + predicted_wdog_intr_q.push_back(exp_wdog_intr); + last_cycle_count = timed_regs.time_now; + fork + begin: iso_fork + fork + begin : wait_values_to_propagate + // do backdoor read and delete values no longer valid. + check_intr_value_propagated(WDOG); + end : wait_values_to_propagate + begin : reset_kill + wait (under_reset); + end : reset_kill + join_any + disable fork; + end : iso_fork + join + end // if (last_cycle_count != timed_regs.time_now) + end : non_blocking_fork + join_none +endtask : wdog_intr_predicted_values + +task aon_timer_scoreboard::check_intr_value_propagated(timers_e timer_type); + int unsigned idx; // Idx to the latest prediction + bit exp_value; + bit act_data; + bit value_matched; + + case(timer_type) + WKUP: begin + if (predicted_wkup_intr_q.size == 0) + `uvm_fatal(`gfn, "'predicted_wkup_intr_q' Queue is empty") + idx = predicted_wkup_intr_q.size -1; + // Synchronise with the next neg-edge + cfg.clk_rst_vif.wait_n_clks(1); + do begin + csr_rd(.ptr(ral.intr_state.wkup_timer_expired), .value(act_data), .backdoor(1)); + if (predicted_wkup_intr_q[idx] == act_data && ongoing_intr_state_read == 0) begin + value_matched = 1; + // Remove all the other predictions no longer valid: + for (int i = 0; i < idx; i++) + void'(predicted_wkup_intr_q.pop_front()); + end + else //negedge to avoid a race condition + cfg.clk_rst_vif.wait_n_clks(1); + end + while (value_matched == 0); + end + WDOG: begin + if (predicted_wdog_intr_q.size == 0) + `uvm_fatal(`gfn, "'predicted_wdog_intr_q' Queue is empty") + idx = predicted_wdog_intr_q.size -1; + // Synchronise with the next neg-edge + cfg.clk_rst_vif.wait_n_clks(1); + do begin + csr_rd(.ptr(ral.intr_state.wdog_timer_bark), .value(act_data), .backdoor(1)); + if (predicted_wdog_intr_q[idx] == act_data && ongoing_intr_state_read == 0) begin + value_matched = 1; + // Remove all the other predictions no longer valid: + for (int i = 0; i < idx; i++) + void'(predicted_wdog_intr_q.pop_front()); + end + else + cfg.clk_rst_vif.wait_n_clks(1); + end + while (value_matched == 0); + + end + endcase +endtask : check_intr_value_propagated task aon_timer_scoreboard::process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name); @@ -88,10 +638,10 @@ task aon_timer_scoreboard::process_tl_access(tl_seq_item item, tl_channels_e cha bit write = item.is_write(); uvm_reg_addr_t csr_addr = cfg.ral_models[ral_name].get_word_aligned_addr(item.a_addr); - bit addr_phase_read = (!write && channel == AddrChannel); - bit addr_phase_write = (write && channel == AddrChannel); - bit data_phase_read = (!write && channel == DataChannel); - bit data_phase_write = (write && channel == DataChannel); + bit addr_phase_read = (!write && channel == AddrChannel); + bit addr_phase_write = (write && channel == AddrChannel); + bit data_phase_read = (!write && channel == DataChannel); + bit data_phase_write = (write && channel == DataChannel); // if access was to a valid csr, get the csr handle if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin @@ -104,7 +654,14 @@ task aon_timer_scoreboard::process_tl_access(tl_seq_item item, tl_channels_e cha // if incoming access is a write to a valid csr, then make updates right away if (addr_phase_write) begin - void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask))); + // intr_state / wkup_cause are predicted in their own process + if(csr.get_name() == "intr_state" || csr.get_name() == "wkup_cause") + `uvm_info(`gfn, $sformatf("Write to %s", csr.get_name()), UVM_DEBUG) + else begin + // TODO: check the predict function return all OK for any other register to guarantee correct + // mirrored value prediction + void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask))); + end if (cfg.en_cov) begin //Sample configuration coverage cov.timer_cfg_cg.sample(prescaler, bark_thold, bite_thold, @@ -112,17 +669,55 @@ task aon_timer_scoreboard::process_tl_access(tl_seq_item item, tl_channels_e cha end end + // Flag to let the model know if a current read is being carried out. Otherwise, the TB may + // think the actual RTL intr_state value is different from the value being reported back if the + // change happens right around the same time as the access + if (csr.get_name() == "intr_state" && !write) + ongoing_intr_state_read = 1; + else + ongoing_intr_state_read = 0; + // 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 + // Interrupt state reads are checked here do_read_check = 1'b0; - if (data_phase_write) begin - uint intr_state_val = item.a_data; - if (intr_state_val[WKUP]) intr_status_exp[WKUP] = 1'b0; - if (intr_state_val[WDOG]) intr_status_exp[WDOG] = 1'b0; + if (addr_phase_write) begin + uint intr_state_val = item.a_mask[0] ? item.a_data : 0; + bit update_prediction; + `uvm_info(`gfn, $sformatf("Prior to update: intr_status_exp = 0x%0x",intr_status_exp), + UVM_DEBUG) + if (intr_state_val[WKUP]) begin + intr_status_exp[WKUP] = 1'b0; + update_prediction = 1; + wkup_intr_predicted_values(intr_status_exp[WKUP]); + end + if (intr_state_val[WDOG]) begin + intr_status_exp[WDOG] = 1'b0; + update_prediction = 1; + wdog_intr_predicted_values(intr_status_exp[WDOG]); + end + + if (update_prediction) begin + uvm_reg_data_t predicted_intr_status; + predicted_intr_status[WDOG] = intr_status_exp[WDOG]; + predicted_intr_status[WKUP] = intr_status_exp[WKUP]; + `uvm_info(`gfn, $sformatf("Updated intr_status_exp = 0x%0x",intr_status_exp), + UVM_DEBUG) + predict_intr_state(predicted_intr_status); + // The timed registers predictions need to be independent of each other + if (intr_state_val[WDOG]) + update_timed_regs_wdog(); + if (intr_state_val[WKUP]) + update_timed_regs_wkup(); + end + end + else if (data_phase_read) begin + check_intr_state_bit(.timer_type(WKUP), .actual_value(item.d_data[WKUP])); + check_intr_state_bit(.timer_type(WDOG), .actual_value(item.d_data[WDOG])); end // INTR_EN register does not exists in AON timer because the interrupts are // enabled as long as timers are enabled. @@ -132,64 +727,99 @@ task aon_timer_scoreboard::process_tl_access(tl_seq_item item, tl_channels_e cha end end "wkup_ctrl": begin + `uvm_info(`gfn, "ACCESSING Wkup_ctrl", UVM_DEBUG) prescaler = get_reg_fld_mirror_value(ral, csr.get_name(), "prescaler"); wkup_en = get_reg_fld_mirror_value(ral, csr.get_name(), "enable"); if (data_phase_write) wkup_num_update_due = 1; end "wkup_cause": begin - wkup_cause = csr.get_mirrored_value(); - intr_status_exp[WKUP] = item.a_data; + if (data_phase_read) begin + // Check mirrored value matched + `DV_CHECK_EQ(`gmv(ral.wkup_cause), item.d_data[0], + $sformatf("reg name: %0s", csr.get_full_name())) + end else if (data_phase_write && item.a_mask[0] && item.a_data[0] == 0) begin + // Predict new wkup_cause after the write + predict_wkup_cause(.wkup_cause(0), .wait_for_we(1)); + end end "wkup_count_lo": begin wkup_count[31:0] = csr.get_mirrored_value(); - if (data_phase_write) wkup_num_update_due = 1; + if (data_phase_write) + update_wkup_count_lo_timely(); end "wkup_count_hi": begin wkup_count[63:32] = csr.get_mirrored_value(); - if (data_phase_write) wkup_num_update_due = 1; + if (data_phase_write) + update_wkup_count_hi_timely(); end "wkup_thold_lo": begin wkup_thold[31:0] = csr.get_mirrored_value(); - if (data_phase_write) wkup_num_update_due = 1; + `uvm_info(`gfn, $sformatf("Written wkup_thold[31:0]=0x%0x",wkup_thold[31:0]), UVM_DEBUG) + if (data_phase_write) + update_wkup_thold_lo_timely(); end "wkup_thold_hi": begin wkup_thold[63:32] = csr.get_mirrored_value(); - if (data_phase_write) wkup_num_update_due = 1; + `uvm_info(`gfn, $sformatf("Written wkup_thold[63:22]=0x%0x",wkup_thold[63:22]), UVM_DEBUG) + if (data_phase_write) + update_wkup_thold_hi_timely(); end "wdog_ctrl": begin + `uvm_info(`gfn, "Accessing Wdog_ctrl", UVM_DEBUG) wdog_en = get_reg_fld_mirror_value(ral, csr.get_name(), "enable"); wdog_pause_in_sleep = get_reg_fld_mirror_value(ral, csr.get_name(), "pause_in_sleep"); end "wdog_count": begin wdog_count = csr.get_mirrored_value(); - if (data_phase_write) wdog_num_update_due = 1; + if (data_phase_write) + update_wdog_count_timely(); end "wdog_regwen": begin wdog_regwen = csr.get_mirrored_value(); end "wdog_bark_thold": begin bark_thold = csr.get_mirrored_value(); - if (data_phase_write) wdog_num_update_due = 1; + if (data_phase_write) + update_wdog_bark_thold_timely(); end "wdog_bite_thold": begin bite_thold = csr.get_mirrored_value(); - if (data_phase_write) wdog_num_update_due = 1; + if (data_phase_write) + update_wdog_bite_thold_timely(); end "intr_test": begin uint intr_test_val = item.a_data; - if (intr_test_val[WKUP]) intr_status_exp[WKUP] = 1'b1; - if (intr_test_val[WDOG]) intr_status_exp[WDOG] = 1'b1; - if (cfg.en_cov) begin - cov.intr_test_cg.sample(WKUP, intr_test_val[WKUP], - wkup_en, intr_status_exp[WKUP]); - cov.intr_test_cg.sample(WDOG, intr_test_val[WDOG], - wdog_en, intr_status_exp[WDOG]); - end - end + if (addr_phase_write) begin + // The timed registers predictions need to be independent of each other + if (intr_test_val[WKUP]) begin + intr_status_exp[WKUP] = 1'b1; + `uvm_info(`gfn, "Setting intr_status_exp[WKUP]", UVM_DEBUG) + update_timed_regs_wkup(); + wkup_intr_predicted_values(intr_status_exp[WKUP]); + end + if (intr_test_val[WDOG]) begin + intr_status_exp[WDOG] = 1'b1; + `uvm_info(`gfn, "Setting intr_status_exp[WDOG]", UVM_DEBUG) + update_timed_regs_wdog(); + wdog_intr_predicted_values(intr_status_exp[WDOG]); + end + + if (intr_test_val[WDOG] | intr_test_val[WKUP]) begin + `uvm_info(`gfn, "Updating intr_state due to intr_test write", UVM_DEBUG) + predict_intr_state(intr_status_exp); + end + if (cfg.en_cov) begin + cov.intr_test_cg.sample(WKUP, intr_test_val[WKUP], + wkup_en, intr_status_exp[WKUP]); + cov.intr_test_cg.sample(WDOG, intr_test_val[WDOG], + wdog_en, intr_status_exp[WDOG]); + end + end // if (addr_phase_write) + end // case: "intr_test" default: begin // No other special behaviour for writes end - endcase + endcase // case (csr.get_name()) // On reads, if do_read_check, is set, then check mirrored_value against item.d_data if (data_phase_read) begin @@ -201,8 +831,6 @@ task aon_timer_scoreboard::process_tl_access(tl_seq_item item, tl_channels_e cha end endtask : process_tl_access -// Task : check_interrupt -// wait for expected # of clocks and check for interrupt state reg and pin task aon_timer_scoreboard::check_interrupt(); forever begin wait (!under_reset); @@ -224,166 +852,442 @@ task aon_timer_scoreboard::check_interrupt(); endtask : check_interrupt task aon_timer_scoreboard::compute_num_clks(); - forever begin : compute_num_clks - // calculate number of clocks required to have interrupt from wkup - @(wkup_num_update_due or wdog_num_update_due); - wait(!under_reset); - if (wkup_num_update_due) begin - if (wkup_count <= wkup_thold) begin - wkup_num = ((wkup_thold - wkup_count + 1) * (prescaler + 1)); - end - else begin - wkup_num = 0; - end - `uvm_info(`gfn, $sformatf("Calculated WKUP_NUM: %d", wkup_num), UVM_HIGH) - end - if (wdog_num_update_due) begin - // calculate wdog bark - if (wdog_count < bark_thold) begin - wdog_bark_num = bark_thold - wdog_count; - end - else begin - wdog_bark_num = 0; - end - `uvm_info(`gfn, $sformatf("Calculated wdog_bark_num: %d", wdog_bark_num), UVM_HIGH) - if (wdog_count < bite_thold) begin - wdog_bite_num = bite_thold - wdog_count; - end - else begin - wdog_bite_num = 0; - end - `uvm_info(`gfn, $sformatf("Calculated wdog_bite_num: %d", wdog_bite_num), UVM_HIGH) + fork + begin : WKUP_thread + bit enable; + forever begin + @(wkup_num_update_due); + wait(!under_reset); + if (wkup_num_update_due) begin + wait_for_wkup_enable_matching(.enable(1)); + + if (wkup_count <= wkup_thold) begin + wkup_num = ((wkup_thold - wkup_count) * (prescaler + 1)); + end + else begin + wkup_num = 0; + end + `uvm_info(`gfn, $sformatf("Calculated WKUP_NUM: %d (wkup_count: %0d, wkup_thold = %0d", + wkup_num, wkup_count, wkup_thold), UVM_HIGH) + end + wkup_num_update_due = 0; + + `uvm_info(`gfn, "Triggering UVM event 'wkup_count_ev'", UVM_DEBUG) + wkup_count_ev.trigger(); + end // forever begin end - wkup_num_update_due = 0; - wdog_num_update_due = 0; - end // compute_num_clks + begin : WDOG_thread + // WDOG: + forever begin + @(wdog_num_update_due); + if (wdog_num_update_due) begin + // calculate wdog bark/bite + wait_for_wdog_enable_matching(.enable(1)); + + if (wdog_count < bark_thold) begin + wdog_bark_num = bark_thold - wdog_count; + end + else begin + wdog_bark_num = 0; + end + `uvm_info(`gfn, $sformatf("Calculated wdog_bark_num: %d", wdog_bark_num), UVM_HIGH) + + if (wdog_count < bite_thold) begin + wdog_bite_num = bite_thold - wdog_count; + end + else begin + wdog_bite_num = 0; + end + `uvm_info(`gfn, $sformatf("Calculated wdog_bite_num: %d", wdog_bite_num), UVM_HIGH) + end + wdog_num_update_due = 0; + `uvm_info(`gfn, "Triggering UVM event 'wdog_count_ev'", UVM_DEBUG) + wdog_count_ev.trigger(); + end // forever begin + end // block: WDOG_thread + join endtask : compute_num_clks task aon_timer_scoreboard::wait_for_sleep(); - wait ( !(wdog_pause_in_sleep & cfg.sleep_vif.sample_pin())); + // Using the RTL's internal synchronised signal to ensure we make the predictions in + // sync with the rtl + bit synchronised_sleep_mode_i; + do begin + synchronised_sleep_mode_i = cfg.hdl_read_bit(.path(".aon_sleep_mode")); + if ( (wdog_pause_in_sleep & synchronised_sleep_mode_i)) + cfg.aon_clk_rst_vif.wait_clks(1); + end while ( (wdog_pause_in_sleep & synchronised_sleep_mode_i)); endtask : wait_for_sleep +task aon_timer_scoreboard::collect_wkup_timer_coverage(ref event sample_coverage); + forever begin + @ (sample_coverage); + if (cfg.en_cov) begin + bit [63:0] rtl_count; + //reading RTL value since TB doesn't keep track of count + csr_rd(.ptr(ral.wkup_count_lo), .value(rtl_count[31:0]), .backdoor(1)); + csr_rd(.ptr(ral.wkup_count_lo), .value(rtl_count[63:32]), .backdoor(1)); + cov.wake_up_timer_thold_hit_cg.sample(intr_status_exp[WKUP], wkup_thold, rtl_count); + end + end +endtask : collect_wkup_timer_coverage + task aon_timer_scoreboard::run_wkup_timer(); event sample_coverage; + bit wkup_enabled; forever begin - wait (wkup_en); + wait (cfg.clk_rst_vif.rst_n === 1); + wait_for_wkup_enable_matching(.enable(1)); + `uvm_info(`gfn, "WKUP ctrl.enable signal is set", UVM_DEBUG) + fork begin - forever begin - @(sample_coverage.triggered); - if (cfg.en_cov) begin - bit [63:0] rtl_count; - //reading RTL value since TB doesn't keep track of count - csr_rd(.ptr(ral.wkup_count_lo), .value(rtl_count[31:0]), .backdoor(1)); - csr_rd(.ptr(ral.wkup_count_lo), .value(rtl_count[63:32]), .backdoor(1)); - cov.wake_up_timer_thold_hit_cg.sample(intr_status_exp[WKUP], - wkup_thold, - rtl_count); - end - end + collect_wkup_timer_coverage(sample_coverage); end begin // trying to count how many cycles we need to count - uint count = 0; - // It takes 4 aon clks from the write enabling the watchdog to take effect due to the CDC - // logic. - cfg.aon_clk_rst_vif.wait_clks(4); + uint count = 0; + bit local_interrupt = 0; + bit local_intr_exp = 0; + bit cdc_reg_compared = 0; + bit [63:0] count_backdoor_value = 0; + bit [11:0] preloaded_prescaler_value; + + wkup_count_ev.wait_ptrigger(); + `uvm_info(`gfn, "Start WKUP timer UVM event 'wkup_count_ev' Received", UVM_DEBUG) + + `uvm_info(`gfn, $sformatf("WKUP: Start the count (count=%0d < wkup_num=%0d)", + count, wkup_num), UVM_DEBUG) while (count < wkup_num) begin cfg.aon_clk_rst_vif.wait_clks(1); // reset the cycle counter when we update the cycle count needed count = wkup_num_update_due ? 0 : (count + 1); - `uvm_info(`gfn, $sformatf("WKUP Timer count: %d", count), UVM_HIGH) + `uvm_info(`gfn, $sformatf("WKUP Timer count: %d (wkup_num=%0d)", + count, wkup_num), UVM_DEBUG) -> sample_coverage; end + wait_for_wkup_enable_matching(.enable(1)); `uvm_info(`gfn, $sformatf("WKUP Timer expired check for interrupts"), UVM_HIGH) - intr_status_exp[WKUP] = 1'b1; - -> sample_coverage; + // TODO: fix after RTL fix (RTL currently doesn't reset the prescale_count_q when + // wkup_ctrl is written. RTL work is in PR#25616 + if (! uvm_hdl_read({cfg.path_to_rtl,".u_core.prescale_count_q"}, preloaded_prescaler_value)) + `uvm_error (`gfn, "HDL Read from tb.dut.u_core.prescale_count_q") // Interrupt should happen N+1 clock ticks after count == wkup_num. - cfg.aon_clk_rst_vif.wait_clks(prescaler+1); - // Wait for 2 extra cycles in AON clock domain to account for CDC randomization delay - // since wakeup interrupt is synchronized to AON clock domain - if (cfg.en_dv_cdc) begin - cfg.aon_clk_rst_vif.wait_clks(2); + // Oriignal delay: + // cfg.aon_clk_rst_vif.wait_clks(prescaler+1); + if (prescaler==0) begin + cfg.aon_clk_rst_vif.wait_clks(1); end - // Wait a further 5 clocks for the interrupt to propagate through logic in the clk domain - // to become visible on the top-level pins. - cfg.clk_rst_vif.wait_clks(5); - // Check interrupt pin - `DV_CHECK_CASE_EQ(intr_status_exp[WKUP], - cfg.intr_vif.sample_pin(.idx(WKUP))) + // TODO: applies to else_if/ese fix after RTL fix (RTL currently doesn't reset the + // prescale_count_q when wkup_ctrl is written + else if ((prescaler + 1) > preloaded_prescaler_value) begin + cfg.aon_clk_rst_vif.wait_clks((prescaler + 1 - preloaded_prescaler_value)); + end + else begin + bit wkup_incr; + bit [63:0] wkup_count, wkup_thold; + // Note: This is a bug/feature in the RTL. If the RTL was counting with + // wkup_ctrl.enable=1, prescaler = X, and by the time SW sends a wup_ctrl.enable=0 + // the internal prescaler counter `prescale_count_q` has a value of Y, if once we enable + // the wkup_ctrl.enable=1, the value written to wkup_ctrl.prescaler is lower than the + // value already in `prescaler_count_q`, the prescaler will have to overflow before the + // new prescaler value becomes effective + + // RTL's `prescale_count_q` may have a "pre-counted" value in it, and unless + // there's a reset, the interrupt won't propagate to the output until: + // prescale_count_q == reg2hw_i.wkup_ctrl.prescaler.q (In other words until + // wkup_incr is true + + // Read threshold, and count and see if we've incremented + do begin + csr_rd(.ptr(ral.wkup_count_lo), .value(wkup_count[31:0]), .backdoor(1)); + csr_rd(.ptr(ral.wkup_count_hi), .value(wkup_count[63:32]), .backdoor(1)); + + csr_rd(.ptr(ral.wkup_thold_lo), .value(wkup_thold[31:0]), .backdoor(1)); + csr_rd(.ptr(ral.wkup_thold_hi), .value(wkup_thold[63:32]), .backdoor(1)); + + if (wkup_count < wkup_thold) + cfg.aon_clk_rst_vif.wait_clks(1); + end while (wkup_count < wkup_thold); + + do begin + if (! uvm_hdl_read({cfg.path_to_rtl,".u_core.wkup_incr"},wkup_incr)) + `uvm_error (`gfn, "HDL Read from tb.dut.u_core.wkup_incr failed") + if (!wkup_incr) + cfg.aon_clk_rst_vif.wait_clks(1); + + end while(!wkup_incr); + // Extra delay to allow the interrupt to propagate + cfg.aon_clk_rst_vif.wait_clks(1); + end // else: !if((prescaler + 1) > preloaded_prescaler_value) + + // Using a local interrupt flag in case 'intr_status_exp[WKUP]' changes + // due to TL-UL accesses + local_interrupt = 1; + intr_status_exp[WKUP] = local_interrupt; + wkup_cause = `gmv(ral.wkup_cause); + wkup_cause |= intr_status_exp[WKUP]; + `uvm_info(`gfn, $sformatf("Predicting wkup_cause = 0x%0x",wkup_cause), UVM_DEBUG) + predict_wkup_cause(.wkup_cause(wkup_cause), .wait_for_we(0)); + -> sample_coverage; + + // WKUP interrupt ('intr_status_exp[WKUP]') may have been cleared by the time we reach + // the AON delay, so we overwrite it and revert it to its original value after + // updating timed regs + local_interrupt = intr_status_exp[WKUP]; + intr_status_exp[WKUP] = 1; + wkup_intr_predicted_values(intr_status_exp[WKUP]); + intr_status_exp[WKUP] = local_interrupt; + + // Synchronising to the pos-edge before counting edges in case the current cycle is already + // ongoing to avoid updating the mirrored value too early + @(cfg.clk_rst_vif.cb); + // The TB predicts intr_state at the right time by calling predict when DE is set and D + // signal carries the predicted value to ensure predictions are on time to avoid mismatches + while (!(cfg.hdl_read_bit(".hw2reg.intr_state.wkup_timer_expired.de") == 1 && + cfg.hdl_read_bit(".hw2reg.intr_state.wkup_timer_expired.d") == 1)) begin + cfg.clk_rst_vif.wait_n_clks(1); // extra cycle to update intr_state in time + end + + local_interrupt = intr_status_exp[WKUP]; + intr_status_exp[WKUP] = 1; + local_intr_exp = intr_status_exp; + update_timed_regs_wkup(); + // Predict intr_state.wkup field only + predict_intr_state(.pred_intr_state(local_intr_exp), .field_only(1), .is_wkup(1)); + // Restoring the value for 'intr_status_exp[WKUP]' + intr_status_exp[WKUP] = local_interrupt; + + // From the moment DE is set until the interrupt makes it way to the outside there's + // 2 cycles delay + cfg.clk_rst_vif.wait_clks(2); + check_aon_domain_interrupt(.timer_type(WKUP)); // Check wakeup pin `DV_CHECK_CASE_EQ(1, cfg.aon_intr_vif.sample_pin(.idx(1))) - `uvm_info(`gfn, $sformatf("WKUP Timer check passed."), UVM_HIGH) + + `uvm_info(`gfn, $sformatf("WKUP Timer checks passed."), UVM_HIGH) end begin wait (!wkup_en || !cfg.aon_clk_rst_vif.rst_n); `uvm_info(`gfn, $sformatf("WKUP Timer disabled, quit scoring"), UVM_HIGH) wkup_en = 0; + wait_for_wkup_enable_matching(.enable(0)); + `uvm_info(`gfn, $sformatf("WKUP Timer disabled, quit scoring"), UVM_HIGH) end join_any disable fork; end endtask : run_wkup_timer +function aon_timer_intr_timed_regs::timed_reg_e aon_timer_scoreboard::timers_e2time_reg_e(timers_e + timer_type + ); + aon_timer_intr_timed_regs::timed_reg_e return_value; + + if (timer_type == WDOG) + return_value = aon_timer_intr_timed_regs::TimedIntrStateWdogBark; + else if (timer_type == WKUP) + return_value = aon_timer_intr_timed_regs::TimedIntrStateWkupExpired; + else + `uvm_fatal(`gfn, $sformatf("Wrong timer index passed (%s)",timer_type.name)) + + return return_value; +endfunction + +function void aon_timer_scoreboard::check_aon_domain_interrupt(timers_e timer_type); + bit cdc_reg_compared = 0; + aon_timer_intr_timed_regs::timed_reg_e reg_idx = timers_e2time_reg_e(timer_type); + + for(int i=0; i < timed_regs.timed[reg_idx].fields.size(); i++) begin + if (timed_regs.timed[reg_idx].fields[i].pred_valid) begin + `DV_CHECK_CASE_EQ(timed_regs.timed[reg_idx].fields[i].pred_latest.val_new, + cfg.intr_vif.sample_pin(.idx(timer_type))) + cdc_reg_compared = 1; + end + end + + if (!cdc_reg_compared) begin + `uvm_fatal(`gfn, $sformatf({"TB failed to compare sys-clock %s interrupt pin - ", + "likely due to the CDC timed register not having a prediction"}, + timer_type.name)) + end + `uvm_info(`gfn, $sformatf("'intr_%0s=0x%0x' comparison matched", + timer_type == WKUP ? "wkup_timer_expired_o" : "wdog_timer_bark_o", + cfg.intr_vif.sample_pin(.idx(timer_type))), UVM_DEBUG) +endfunction : check_aon_domain_interrupt + +function void aon_timer_scoreboard::return_pred_intr_q(timers_e timer_type, ref bit pred_q[$]); + if (timer_type == WDOG) + pred_q = predicted_wdog_intr_q; + else if (timer_type == WKUP) + pred_q = predicted_wkup_intr_q; + else + `uvm_fatal(`gfn, $sformatf("Wrong timer index passed (%s)",timer_type.name)) + + if (pred_q.size == 0) + `uvm_fatal(`gfn, {"'predicted_", timer_type == WKUP ? "wkup" : "wdog", "_intr_q.size = 0'"}) +endfunction + +function void aon_timer_scoreboard::check_intr_state_bit(timers_e timer_type, bit actual_value); + bit cdc_reg_compared = 0; + bit pred_q[$]; + + return_pred_intr_q(timer_type, pred_q); + `DV_CHECK_CASE_EQ(pred_q[0], // Comparing against the oldes predicted value + actual_value) + + `uvm_info(`gfn, $sformatf("'intr_state.%0s=0x%0x' comparison matched", + timer_type.name, actual_value), UVM_DEBUG) +endfunction : check_intr_state_bit + +task aon_timer_scoreboard::collect_wdog_bark_timer_coverage(ref event sample_coverage); + forever begin + @ (sample_coverage); + if (cfg.en_cov) begin + bit [31:0] rtl_count; + //reading RTL value since TB doesn't keep track of count + csr_rd(.ptr(ral.wdog_count), .value(rtl_count), .backdoor(1)); + cov.watchdog_timer_bark_thold_hit_cg.sample(intr_status_exp[WDOG], bark_thold, rtl_count); + end + end +endtask : collect_wdog_bark_timer_coverage task aon_timer_scoreboard::run_wdog_bark_timer(); event sample_coverage; + // Used as a flag when enable=0 at the same time the interrupt propagates + bit predicting_interrupt; forever begin - wait (wdog_en); + wait_for_wdog_enable_matching(.enable(1)); + `uvm_info(`gfn, "wdog_ctrol.WDOG_EN = 1, allow watchdog to count", UVM_DEBUG) fork begin - forever begin - @(sample_coverage.triggered); - if (cfg.en_cov) begin - bit [31:0] rtl_count; - //reading RTL value since TB doesn't keep track of count - csr_rd(.ptr(ral.wdog_count), .value(rtl_count), .backdoor(1)); - cov.watchdog_timer_bark_thold_hit_cg.sample(intr_status_exp[WDOG], - bark_thold, - rtl_count); - end - end + collect_wdog_bark_timer_coverage(sample_coverage); end begin // trying to count how many cycles we need to count uint count = 0; - // It takes 4 aon clks from the write enabling the watchdog to take effect due to the CDC - // logic. - cfg.aon_clk_rst_vif.wait_clks(4); + bit is_enabled = 0; + bit local_interrupt = 0; + bit cdc_reg_compared = 0; + bit [31:0] count_backdoor_value; + bit wkup_cause_updated; + // Used to ensure the correct value is passed to predict intr_state + bit [1:0] local_intr_exp; + bit [1:0] backdoor_intr_state; + + wdog_count_ev.wait_ptrigger(); + `uvm_info(`gfn, "Start WDOG - bark timer UVM event 'wdog_count_ev' Triggered", UVM_DEBUG) + + `uvm_info(`gfn, $sformatf("WDOG: Start the count (count=%0d < wdog_num=%0d)", + count, wdog_bark_num),UVM_DEBUG) + while (count < wdog_bark_num) begin wait_for_sleep(); cfg.aon_clk_rst_vif.wait_clks(1); // reset the cycle counter when we update the cycle count needed count = wdog_num_update_due ? 0 : (count + 1); - `uvm_info(`gfn, $sformatf("WDOG Bark Timer count: %d", count), UVM_HIGH) + `uvm_info(`gfn, $sformatf("WDOG Bark Timer count: %d (wdog_bark_num=%0d)", + count, wdog_bark_num), UVM_HIGH) -> sample_coverage; end + + wait_for_wdog_enable_matching(.enable(1)); `uvm_info(`gfn, $sformatf("WDOG Bark Timer expired check for interrupts"), UVM_HIGH) + `uvm_info(`gfn, "Setting 'intr_status_exp[WDOG]'", UVM_DEBUG) intr_status_exp[WDOG] = 1'b1; + + wkup_cause = `gmv(ral.wkup_cause); + wkup_cause |= intr_status_exp[WDOG]; + predict_wkup_cause(.wkup_cause(wkup_cause), .wait_for_we(0)); -> sample_coverage; // Propagation delay of one cycle from aon_core to interrupt pins. cfg.aon_clk_rst_vif.wait_clks(1); - // Wait a further 5 clocks for the interrupt to propagate through logic in the clk domain - // to become visible on the top-level pins. - cfg.clk_rst_vif.wait_clks(5); - // Check interrupt and reset_req pins - `DV_CHECK_CASE_EQ(intr_status_exp[WDOG], - cfg.intr_vif.sample_pin(.idx(WDOG))) + // If the enable becomes unset at this time the prediction needs to finish + predicting_interrupt = 1; - // Check wakeup pin - `DV_CHECK_CASE_EQ(intr_status_exp[WDOG], - cfg.aon_intr_vif.sample_pin(.idx(1))) - `uvm_info(`gfn, - $sformatf("WDOG INTR Bark: %d", - intr_status_exp[WDOG]), - UVM_HIGH) + // Using flag to predict the interrupt + local_interrupt = intr_status_exp[WDOG]; + intr_status_exp[WDOG] = 1; + // If not using an aux variable 'intr_status_exp' may update prior to the call to + // predict_intr_state and end up predicting wrong value + local_intr_exp = intr_status_exp; + wdog_intr_predicted_values(local_intr_exp[WDOG]); + intr_status_exp[WDOG] = local_interrupt; + + // Synchronising to the pos-edge before counting edges in case the current cycle is already + // ongoing to avoid updating the mirrored value too early + @(cfg.clk_rst_vif.cb); + // The TB predicts intr_state at the right time by calling predict when DE is set and D + // signal carries the predicted value to ensure predictions are on time to avoid mismatches + while (!(cfg.hdl_read_bit(".hw2reg.intr_state.wdog_timer_bark.de") == 1 && + cfg.hdl_read_bit(".hw2reg.intr_state.wdog_timer_bark.d") == 1)) begin + cfg.clk_rst_vif.wait_n_clks(1); // extra cycle to update intr_state in time + end + + // Using flag to predict the interrupt + local_interrupt = intr_status_exp[WDOG]; + intr_status_exp[WDOG] = 1; + update_timed_regs_wdog(); + + // If not using aux variable 'intr_status_exp' may update prior to the call to + // predict_intr_state and end up predicting wrong value + local_intr_exp = intr_status_exp; + wdog_intr_predicted_values(local_intr_exp[WDOG]); + // Predicting only the WDOG field + predict_intr_state(.pred_intr_state(local_intr_exp), .field_only(1), .is_wkup(0)); + intr_status_exp[WDOG] = local_interrupt; + + // From the moment DE is set until the interrupt makes it way to the outside there's + // 2 cycles delay + cfg.clk_rst_vif.wait_clks(2); + // Check `intr_wdog_timer_bark_o` + check_aon_domain_interrupt(.timer_type(WDOG)); + + // It could happen intr_state reg was written betwen the time intr_status_exp[WDOG] was + // set until the output 'intr_wdog_timer_bark' was compared and set, if that's the case + // we set the variable to the value it should be + csr_rd(.ptr(ral.intr_state), .value(backdoor_intr_state), .backdoor(1)); + + if (backdoor_intr_state[WDOG] == 1 && intr_status_exp[WDOG]==0) begin + `uvm_info(`gfn, {"Tweaking 'intr_status_exp[WDOG]=1' due to a write", + "since the value was predicted"}, UVM_DEBUG) + intr_status_exp[WDOG] = 1; // Set again to ensure TB is in sync + end + // Reading actual enable to see if the output will be set + csr_rd(.ptr(ral.wdog_ctrl.enable), .value(is_enabled), .backdoor(1)); + // Check reset_req pins: + if (is_enabled) begin + bit predicted_wkup_req = `gmv(ral.wkup_cause); + // If the write to wkup_cause wasn't absorved in this same cycle, we compare against the + // prediction + if (last_wkup_cause_write_aon_clk_cycle != aon_clk_cycle) begin + `DV_CHECK_CASE_EQ(predicted_wkup_req, cfg.aon_intr_vif.sample_pin(.idx(1))) + end + else if (!(cfg.aon_intr_vif.sample_pin(.idx(1)) inside {0, predicted_wkup_req})) begin + // Otherwise, check whether the write to wkup_cause(0x0) unset the interrupt or + // whether it matches the predicted value + `uvm_fatal(`gfn, $sformatf("aon_wkup_req_o comparison failed (not matching 0/%0d)", + predicted_wkup_req)) + end + end // if (is_enabled) + else begin + // If disabled, wkup_output will be 0/1 depending on the value the flop ended up latching. + // Hence we don't compare + end + `uvm_info(`gfn,$sformatf("WDOG INTR Bark: %d", intr_status_exp[WDOG]), UVM_HIGH) + predicting_interrupt = 0; end begin - wait (!wdog_en || !cfg.aon_clk_rst_vif.rst_n); + wait (cfg.aon_clk_rst_vif.rst_n); + wait_for_wdog_enable_matching(.enable(0)); `uvm_info(`gfn, $sformatf("WDOG Timer disabled, quit scoring"), UVM_HIGH) + // Waiting a sys clock to see if the interrupt will propagate after the module + // just got disabled + cfg.clk_rst_vif.wait_clks(1); + wait (predicting_interrupt==0 || !cfg.aon_clk_rst_vif.rst_n); wdog_en = 0; end join_any @@ -391,36 +1295,66 @@ task aon_timer_scoreboard::run_wdog_bark_timer(); end endtask : run_wdog_bark_timer +task aon_timer_scoreboard::wait_for_wdog_enable_matching(bit enable); + bit local_enable; + do begin + csr_rd(.ptr(ral.wdog_ctrl.enable), .value(local_enable), .backdoor(1)); + `uvm_info(`gfn, $sformatf("[backdoor read] : WDOG_CTRL.enable = 0x%0x",local_enable), + UVM_DEBUG) + if (local_enable != enable) + cfg.aon_clk_rst_vif.wait_clks(1); + end + while(local_enable != enable); +endtask : wait_for_wdog_enable_matching + +task aon_timer_scoreboard::wait_for_wkup_enable_matching(bit enable); + bit local_enable; + do begin + csr_rd(.ptr(ral.wkup_ctrl.enable), .value(local_enable), .backdoor(1)); + `uvm_info(`gfn, $sformatf("[backdoor read] : WKUP_CTRL.enable = 0x%0x",local_enable), + UVM_DEBUG) + if (local_enable != enable) + cfg.aon_clk_rst_vif.wait_clks(1); + end + while(local_enable != enable); +endtask : wait_for_wkup_enable_matching + +task aon_timer_scoreboard::collect_wdog_bite_timer_coverage(ref event sample_coverage); + forever begin + @ (sample_coverage); + if (cfg.en_cov) begin + bit [31:0] rtl_count; + //reading RTL value since TB doesn't keep track of count + csr_rd(.ptr(ral.wdog_count), .value(rtl_count), .backdoor(1)); + cov.watchdog_timer_bite_thold_hit_cg.sample(wdog_rst_req_exp, bite_thold, rtl_count); + end + end +endtask : collect_wdog_bite_timer_coverage + task aon_timer_scoreboard::run_wdog_bite_timer(); event sample_coverage; forever begin - wait (wdog_en); + wait_for_wdog_enable_matching(.enable(1)); + `uvm_info(`gfn, "WDOG ctrl.enable signal is set", UVM_DEBUG) fork begin - forever begin - @(sample_coverage.triggered); - if (cfg.en_cov) begin - bit [31:0] rtl_count; - //reading RTL value since TB doesn't keep track of count - csr_rd(.ptr(ral.wdog_count), .value(rtl_count), .backdoor(1)); - cov.watchdog_timer_bite_thold_hit_cg.sample(wdog_rst_req_exp, - bite_thold, - rtl_count); - end - end + collect_wdog_bite_timer_coverage(sample_coverage); end begin // trying to count how many cycles we need to count uint count = 0; - // It takes 4 aon clks from the write enabling the watchdog to take effect due to the CDC - // logic. - cfg.aon_clk_rst_vif.wait_clks(4); + + wdog_count_ev.wait_ptrigger(); + `uvm_info(`gfn, "Start WDOG - bite timer UVM event 'wdog_count_ev' Received", UVM_DEBUG) + `uvm_info(`gfn, "Start WDOG - bark Start to count", UVM_DEBUG) while (count < wdog_bite_num) begin wait_for_sleep(); cfg.aon_clk_rst_vif.wait_clks(1); // reset the cycle counter when we update the cycle count needed + // OG: count = wdog_num_update_due ? 0 : (count + 1); - `uvm_info(`gfn, $sformatf("WDOG Bite Timer count: %d", count), UVM_HIGH) + `uvm_info(`gfn, $sformatf("WDOG Bite Timer count: %d, wdog_bite_num: %0d", + count, wdog_bite_num), UVM_HIGH) -> sample_coverage; end `uvm_info(`gfn, $sformatf("WDOG Bite Timer expired check for interrupts"), UVM_HIGH) @@ -435,18 +1369,31 @@ task aon_timer_scoreboard::run_wdog_bite_timer(); // Check reset_req pin `DV_CHECK_CASE_EQ(wdog_rst_req_exp, cfg.aon_intr_vif.sample_pin(.idx(0))) - - `uvm_info(`gfn, - $sformatf("WDOG INTR Bite: %d", - wdog_rst_req_exp), - UVM_HIGH) + `uvm_info(`gfn,$sformatf("WDOG INTR Bite: %d", wdog_rst_req_exp), UVM_HIGH) end begin wait (!wdog_en || !cfg.aon_clk_rst_vif.rst_n); - `uvm_info(`gfn, $sformatf("WDOG Timer disabled, quit scoring"), UVM_HIGH) wdog_en = 0; + + wait_for_wdog_enable_matching(.enable(0)); + `uvm_info(`gfn, $sformatf("WDOG Timer disabled, quit scoring"), UVM_HIGH) end join_any disable fork; end endtask : run_wdog_bite_timer + +function void aon_timer_scoreboard::reset(string kind = "HARD"); + super.reset(kind); + // reset local fifos queues and variables + `uvm_info(`gfn, "Resetting scoreboard", UVM_DEBUG) + intr_status_exp = 0; + wkup_cause = 0; + + ongoing_intr_state_read = 0; + predicted_wkup_intr_q = {}; + predicted_wdog_intr_q = {}; + // the register is initialised to 0x0 + predicted_wdog_intr_q.push_back(0); + predicted_wkup_intr_q.push_back(0); +endfunction : reset