| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| class pwrmgr_base_vseq extends cip_base_vseq #( |
| .RAL_T (pwrmgr_reg_block), |
| .CFG_T (pwrmgr_env_cfg), |
| .COV_T (pwrmgr_env_cov), |
| .VIRTUAL_SEQUENCER_T(pwrmgr_virtual_sequencer) |
| ); |
| `uvm_object_utils(pwrmgr_base_vseq) |
| |
| `uvm_object_new |
| |
| localparam int ActiveTimeoutInNanoSeconds = 10_000; |
| localparam int PropagationToSlowTimeoutInNanoSeconds = 15_000; |
| localparam int FetchEnTimeoutNs = 40_000; |
| |
| localparam int MaxCyclesBeforeEnable = 12; |
| |
| // Random wakeups and resets. |
| rand wakeups_t wakeups; |
| rand wakeups_t wakeups_en; |
| rand resets_t resets; |
| rand resets_t resets_en; |
| rand bit power_glitch_reset; |
| rand bit escalation_reset; |
| rand bit ndm_reset; |
| |
| rand bit en_intr; |
| |
| constraint resets_en_c { |
| solve resets, power_glitch_reset, escalation_reset, ndm_reset before resets_en; |
| |{resets_en & resets, power_glitch_reset, escalation_reset, ndm_reset} == 1'b1; |
| } |
| |
| rand bit disable_wakeup_capture; |
| |
| // Random control enables. |
| rand control_enables_t control_enables; |
| |
| // Random delays. |
| rand int cycles_before_clks_ok; |
| rand int cycles_between_clks_ok; |
| rand int cycles_before_io_status; |
| rand int cycles_before_main_status; |
| rand int cycles_before_usb_status; |
| rand int cycles_before_rst_lc_src; |
| rand int cycles_before_rst_sys_src; |
| rand int cycles_before_otp_done; |
| rand int cycles_before_lc_done; |
| rand int cycles_before_wakeup; |
| rand int cycles_before_reset; |
| |
| // Slow responder delays. |
| rand int cycles_before_core_clk_en; |
| rand int cycles_before_io_clk_en; |
| rand int cycles_before_usb_clk_en; |
| rand int cycles_before_main_pok; |
| |
| // This tracks the local objection count from these responders. We do not use UVM |
| // objections because uvm_objection::wait_for(UVM_ALL_DROPPED, this) seems to wait |
| // for all objections to be dropped, not just those raised by this. |
| local int fast_objection_count = 0; |
| local int slow_objection_count = 0; |
| |
| constraint cycles_before_clks_ok_c {cycles_before_clks_ok inside {[3 : 10]};} |
| constraint cycles_between_clks_ok_c {cycles_between_clks_ok inside {[3 : 10]};} |
| constraint cycles_before_io_status_c {cycles_before_io_status inside {[0 : 4]};} |
| constraint cycles_before_main_status_c {cycles_before_main_status inside {[0 : 4]};} |
| constraint cycles_before_usb_status_c {cycles_before_usb_status inside {[0 : 4]};} |
| constraint cycles_before_rst_lc_src_base_c {cycles_before_rst_lc_src inside {[0 : 4]};} |
| constraint cycles_before_rst_sys_src_base_c {cycles_before_rst_sys_src inside {[0 : 4]};} |
| constraint cycles_before_otp_done_base_c {cycles_before_otp_done inside {[0 : 4]};} |
| constraint cycles_before_lc_done_base_c {cycles_before_lc_done inside {[0 : 4]};} |
| constraint cycles_before_wakeup_c {cycles_before_wakeup inside {[2 : 6]};} |
| constraint cycles_before_reset_c {cycles_before_reset inside {[2 : 6]};} |
| constraint cycles_before_core_clk_en_c { |
| cycles_before_core_clk_en inside {[1 : MaxCyclesBeforeEnable]}; |
| } |
| constraint cycles_before_io_clk_en_c { |
| cycles_before_io_clk_en inside {[1 : MaxCyclesBeforeEnable - 2]}; |
| } |
| constraint cycles_before_usb_clk_en_c { |
| cycles_before_usb_clk_en inside {[1 : MaxCyclesBeforeEnable]}; |
| } |
| constraint cycles_before_main_pok_c {cycles_before_main_pok inside {[2 : MaxCyclesBeforeEnable]};} |
| |
| // This is used to trigger a software reset, as per rstmgr's `reset_req` CSR. |
| prim_mubi_pkg::mubi4_t sw_rst_from_rstmgr = prim_mubi_pkg::MuBi4False; |
| |
| bit do_pwrmgr_init = 1'b1; |
| // This static variable is incremented in each pre_start and decremented in each post_start. |
| // It is used to start and stop the responders when the parent sequence starts and ends. |
| local static int sequence_depth = 0; |
| pwrmgr_mubi_e mubi_mode; |
| |
| // This stops randomizing cycles counts that select from a pipeline, since |
| // changes can lead to missing or unexpected transitions. |
| task stop_randomizing_cycles(); |
| cycles_before_core_clk_en.rand_mode(0); |
| cycles_before_io_clk_en.rand_mode(0); |
| cycles_before_usb_clk_en.rand_mode(0); |
| cycles_before_main_pok.rand_mode(0); |
| endtask |
| |
| // Disable exclusions for CONTROL.USB_CLK_EN_ACTIVE and RESET_EN: they are meant for full-chip only. |
| function void disable_unnecessary_exclusions(); |
| csr_excl_item csr_excl = ral.get_excl_item(); |
| `uvm_info(`gfn, "Dealing with exclusions", UVM_MEDIUM) |
| csr_excl.enable_excl(.obj("pwrmgr_reg_block.control"), .enable(1'b0)); |
| csr_excl.enable_excl(.obj("pwrmgr_reg_block.reset_en"), .enable(1'b0)); |
| csr_excl.print_exclusions(UVM_MEDIUM); |
| endfunction |
| |
| virtual task pre_start(); |
| cfg.pwrmgr_vif.lc_hw_debug_en = lc_ctrl_pkg::Off; |
| cfg.pwrmgr_vif.lc_dft_en = lc_ctrl_pkg::Off; |
| mubi_mode = PwrmgrMubiNone; |
| `DV_GET_ENUM_PLUSARG(pwrmgr_mubi_e, mubi_mode, pwrmgr_mubi_mode) |
| `uvm_info(`gfn, $sformatf("pwrmgr mubi mode : %s", mubi_mode.name()), UVM_MEDIUM) |
| |
| if (do_pwrmgr_init) pwrmgr_init(); |
| disable_unnecessary_exclusions(); |
| cfg.slow_clk_rst_vif.wait_for_reset(.wait_negedge(0)); |
| stop_randomizing_cycles(); |
| fork |
| // Deactivate rst_main_n to make sure the slow fsm won't be confused into thinking |
| // a power glitch occurred, and wait some cycles so testing doesn't start until any |
| // side-effects are cleared. This confusion can arise if a sequence with random resets |
| // gets reset while sending a power glitch. |
| begin |
| cfg.pwrmgr_vif.rst_main_n = 1'b1; |
| cfg.slow_clk_rst_vif.wait_clks(7); |
| end |
| begin |
| if (sequence_depth == 0) begin |
| `uvm_info(`gfn, "Starting responders", UVM_MEDIUM) |
| slow_responder(); |
| fast_responder(); |
| end |
| ++sequence_depth; |
| super.pre_start(); |
| end |
| join |
| endtask : pre_start |
| |
| task post_apply_reset(string reset_kind = "HARD"); |
| super.post_apply_reset(reset_kind); |
| if (reset_kind == "HARD") begin |
| // Undo any pending resets. |
| cfg.pwrmgr_vif.rst_main_n = 1'b1; |
| cfg.pwrmgr_vif.update_resets(0); |
| end |
| |
| `uvm_info(`gfn, "waiting for fast active after applying reset", UVM_MEDIUM) |
| |
| // There is tb lock up case |
| // when reset come while rom_ctrl = {false, false}. |
| // So we need rom_ctrl driver runs in parallel with |
| // wait_for_fast_fsm_active |
| fork |
| wait_for_fast_fsm_active(); |
| init_rom_response(); |
| join |
| // And drive the cpu not sleeping. |
| cfg.pwrmgr_vif.update_cpu_sleeping(1'b0); |
| endtask |
| |
| task post_start(); |
| super.post_start(); |
| --sequence_depth; |
| if (sequence_depth == 0) begin |
| `uvm_info(`gfn, $sformatf( |
| "Waiting for all objections done with fast=%0d, slow=%0d", |
| fast_objection_count, |
| slow_objection_count |
| ), UVM_MEDIUM) |
| `DV_WAIT(fast_objection_count == 0 && slow_objection_count == 0) |
| `uvm_info(`gfn, "all local objections are done", UVM_LOW) |
| control_assertions(0); |
| `uvm_info(`gfn, "Stopping responders", UVM_MEDIUM) |
| disable slow_responder; |
| disable fast_responder; |
| end |
| endtask |
| |
| virtual task dut_init(string reset_kind = "HARD"); |
| super.dut_init(); |
| endtask |
| |
| virtual task dut_shutdown(); |
| // check for pending pwrmgr operations and wait for them to complete |
| // TODO |
| endtask |
| |
| virtual task apply_reset(string kind = "HARD"); |
| `uvm_info(`gfn, $sformatf("At apply_reset kind='%0s'", kind), UVM_MEDIUM) |
| fork |
| super.apply_reset(kind); |
| if (kind == "HARD") begin |
| // A short slow clock reset should suffice. |
| cfg.slow_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0), .reset_width_clks(5)); |
| end |
| cfg.esc_clk_rst_vif.apply_reset(); |
| cfg.lc_clk_rst_vif.apply_reset(); |
| // Escalation resets are cleared when reset goes active. |
| clear_escalation_reset(); |
| clear_ndm_reset(); |
| cfg.aon_clk_rst_vif.apply_reset(); |
| join |
| // And wait until the responders settle with all okay from the AST. |
| `DV_WAIT( |
| cfg.pwrmgr_vif.pwr_ast_rsp.main_pok && |
| cfg.pwrmgr_vif.pwr_ast_rsp.core_clk_val && |
| cfg.pwrmgr_vif.pwr_ast_rsp.io_clk_val) |
| `uvm_info(`gfn, $sformatf("Out of apply_reset kind='%0s'", kind), UVM_MEDIUM) |
| endtask |
| |
| virtual task apply_resets_concurrently(int reset_duration_ps = 0); |
| cfg.slow_clk_rst_vif.drive_rst_pin(0); |
| cfg.esc_clk_rst_vif.drive_rst_pin(0); |
| cfg.lc_clk_rst_vif.drive_rst_pin(0); |
| cfg.aon_clk_rst_vif.drive_rst_pin(0); |
| super.apply_resets_concurrently(cfg.slow_clk_rst_vif.clk_period_ps); |
| cfg.aon_clk_rst_vif.drive_rst_pin(1); |
| cfg.esc_clk_rst_vif.drive_rst_pin(1); |
| cfg.lc_clk_rst_vif.drive_rst_pin(1); |
| cfg.slow_clk_rst_vif.drive_rst_pin(1); |
| endtask |
| |
| // setup basic pwrmgr features |
| virtual task pwrmgr_init(); |
| // The fast clock frequency is set by ral. |
| // The real slow clock rate is 200kHz, but that slows testing down. |
| // Increasing its frequency improves DV efficiency without compromising quality. |
| cfg.slow_clk_rst_vif.set_freq_mhz(7); |
| `uvm_info(`gfn, $sformatf( |
| "slow clock freq=%fMHz, period=%0dns", |
| cfg.slow_clk_rst_vif.clk_freq_mhz, |
| cfg.slow_clk_rst_vif.clk_period_ps |
| ), UVM_MEDIUM) |
| cfg.esc_clk_rst_vif.set_freq_mhz(cfg.clk_rst_vif.clk_freq_mhz); |
| cfg.lc_clk_rst_vif.set_freq_mhz(cfg.clk_rst_vif.clk_freq_mhz); |
| cfg.aon_clk_rst_vif.set_freq_mhz(cfg.clk_rst_vif.clk_freq_mhz); |
| set_ndmreset_req('0); |
| control_assertions(0); |
| endtask |
| |
| virtual task setup_interrupt(bit enable); |
| csr_wr(.ptr(ral.intr_enable.wakeup), .value(enable)); |
| `uvm_info(`gfn, $sformatf("Wakeup interrupt is %0sabled", enable ? "en" : "dis"), UVM_MEDIUM) |
| endtask |
| |
| // May check intr_state.wakeup CSR against expected, and regardless, it checks that the |
| // interrupt output matches intr_state && intr_enable. The first check is disabled if |
| // check_expected is off, which is used when a reset and an interrupt come in close |
| // temporal proximity. |
| virtual task check_and_clear_interrupt(bit expected, bit check_expected = 1'b1); |
| bit enable; |
| `uvm_info(`gfn, "Checking and clearing interrupt", UVM_MEDIUM) |
| if (check_expected) begin |
| csr_rd_check(.ptr(ral.intr_state.wakeup), .compare_value(expected), |
| .err_msg("interrupt mismatch")); |
| end else begin |
| csr_rd(.ptr(ral.intr_state.wakeup), .value(expected)); |
| end |
| csr_rd(.ptr(ral.intr_enable.wakeup), .value(enable)); |
| `DV_CHECK_EQ(cfg.pwrmgr_vif.intr_wakeup, expected && enable) |
| csr_wr(.ptr(ral.intr_state.wakeup), .value(1'b1)); |
| endtask |
| |
| local function void raise_fast_objection(string label); |
| ++fast_objection_count; |
| `uvm_info(`gfn, $sformatf("Raising fast objection to %0d for %0s", fast_objection_count, label), |
| UVM_HIGH) |
| endfunction |
| |
| local function void drop_fast_objection(string label); |
| --fast_objection_count; |
| `uvm_info(`gfn, $sformatf("Dropping fast objection to %0d for %0s", fast_objection_count, label |
| ), UVM_HIGH) |
| endfunction |
| |
| local function void raise_slow_objection(string label); |
| ++slow_objection_count; |
| `uvm_info(`gfn, $sformatf("Raising slow objection to %0d for %0s", slow_objection_count, label), |
| UVM_MEDIUM) |
| endfunction |
| |
| local function void drop_slow_objection(string label); |
| --slow_objection_count; |
| `uvm_info(`gfn, $sformatf("Dropping slow objection to %0d for %0s", slow_objection_count, label |
| ), UVM_MEDIUM) |
| endfunction |
| |
| virtual function void set_ndmreset_req(logic value); |
| cfg.pwrmgr_vif.cpu_i.ndmreset_req = value; |
| endfunction |
| |
| // Generates expected responses for the slow fsm. |
| // - Completes the clock handshake with the ast: when a clk_en output changes, after a few |
| // cycles the ast is expected to set the corresponding clk_val input to the same value. |
| // - It is possible changes occur in fast succession, so the side-effect is pipelined. |
| // Uses macros because VCS flags an error for assignments to automatic variables, |
| // even if the variable is a ref to an interface variable. |
| |
| `define SLOW_DETECT(rsp_name_, req_) \ |
| forever \ |
| @req_ begin \ |
| raise_slow_objection(rsp_name_); \ |
| `uvm_info(`gfn, $sformatf( \ |
| "slow_responder: Will drive %0s to %b", rsp_name_, req_), UVM_MEDIUM) \ |
| end |
| |
| `define SLOW_SHIFT_SR(req_, rsp_sr_) \ |
| forever \ |
| @cfg.slow_clk_rst_vif.cb begin \ |
| rsp_sr_ = {rsp_sr_[MaxCyclesBeforeEnable-1:0], req_}; \ |
| end |
| |
| `define SLOW_ASSIGN(rsp_name_, cycles_, rsp_sr_, rsp_) \ |
| forever \ |
| @(rsp_sr_[cycles_]) begin \ |
| `uvm_info(`gfn, $sformatf( \ |
| "slow_responder: Driving %0s to %b after %0d AON cycles.", \ |
| rsp_name_, \ |
| rsp_sr_[cycles_], \ |
| cycles_ \ |
| ), UVM_MEDIUM) \ |
| rsp_ <= rsp_sr_[cycles_]; \ |
| drop_slow_objection(rsp_name_); \ |
| end |
| |
| task slow_responder(); |
| logic [MaxCyclesBeforeEnable:0] core_clk_val_sr; |
| logic [MaxCyclesBeforeEnable:0] io_clk_val_sr; |
| logic [MaxCyclesBeforeEnable:0] usb_clk_val_sr; |
| logic [MaxCyclesBeforeEnable:0] main_pd_val_sr; |
| fork |
| `SLOW_DETECT("core_clk_val", cfg.pwrmgr_vif.slow_cb.pwr_ast_req.core_clk_en) |
| `SLOW_SHIFT_SR(cfg.pwrmgr_vif.slow_cb.pwr_ast_req.core_clk_en, core_clk_val_sr) |
| `SLOW_ASSIGN("core_clk_val", cycles_before_core_clk_en, core_clk_val_sr, |
| cfg.pwrmgr_vif.slow_cb.pwr_ast_rsp.core_clk_val) |
| |
| `SLOW_DETECT("io_clk_val", cfg.pwrmgr_vif.slow_cb.pwr_ast_req.io_clk_en) |
| `SLOW_SHIFT_SR(cfg.pwrmgr_vif.slow_cb.pwr_ast_req.io_clk_en, io_clk_val_sr) |
| // Notice this splits updates due to io_clk_en in two processes: with a single process |
| // and a wait inside a quick sequence of changes would cause skipping some update, per |
| // SV scheduling semantics. |
| forever |
| @(io_clk_val_sr[cycles_before_io_clk_en]) begin |
| logic new_value = io_clk_val_sr[cycles_before_io_clk_en]; |
| `uvm_info(`gfn, $sformatf( |
| "slow_responder: Driving %0s to %b after %0d AON cycles.", |
| "io_clk_val", |
| new_value, |
| cycles_before_io_clk_en |
| ), UVM_MEDIUM) |
| if (new_value == 1) begin |
| cfg.clk_rst_vif.start_clk(); |
| cfg.lc_clk_rst_vif.start_clk(); |
| cfg.esc_clk_rst_vif.start_clk(); |
| end else begin |
| cfg.clk_rst_vif.stop_clk(); |
| cfg.lc_clk_rst_vif.stop_clk(); |
| cfg.esc_clk_rst_vif.stop_clk(); |
| end |
| end |
| forever |
| @(io_clk_val_sr[cycles_before_io_clk_en+2]) begin |
| logic new_value = io_clk_val_sr[cycles_before_io_clk_en+2]; |
| cfg.pwrmgr_vif.slow_cb.pwr_ast_rsp.io_clk_val <= new_value; |
| drop_slow_objection("io_clk_val"); |
| end |
| |
| `SLOW_DETECT("usb_clk_val", cfg.pwrmgr_vif.slow_cb.pwr_ast_req.usb_clk_en) |
| `SLOW_SHIFT_SR(cfg.pwrmgr_vif.slow_cb.pwr_ast_req.usb_clk_en, usb_clk_val_sr) |
| `SLOW_ASSIGN("usb_clk_val", cycles_before_usb_clk_en, usb_clk_val_sr, |
| cfg.pwrmgr_vif.slow_cb.pwr_ast_rsp.usb_clk_val) |
| |
| `SLOW_DETECT("main_pok", cfg.pwrmgr_vif.slow_cb.pwr_ast_req.main_pd_n) |
| `SLOW_SHIFT_SR(cfg.pwrmgr_vif.slow_cb.pwr_ast_req.main_pd_n, main_pd_val_sr) |
| `SLOW_ASSIGN("main_pok", cycles_before_main_pok, main_pd_val_sr, |
| cfg.pwrmgr_vif.slow_cb.pwr_ast_rsp.main_pok) |
| join_none |
| endtask : slow_responder |
| `undef SLOW_DETECT |
| `undef SLOW_SHIFT_SR |
| `undef SLOW_ASSIGN |
| |
| // Generates expected responses for the fast fsm. |
| // - Completes the reset handshake with the rstmgr for lc and sys resets: soon after a |
| // reset is requested the corresponding active low reset src must go low. |
| // - Completes the handshake with the clkmgr for io, main, and usb clocks: |
| // each status input needs to track the corresponding ip_clk_en output. |
| // - Completes handshake with lc and otp: *_done needs to track *_init. |
| // Macros for the same reason as the slow responder. |
| |
| `define FAST_RESPONSE_ACTION(rsp_name, rsp, req, cycles) \ |
| `uvm_info(`gfn, $sformatf( \ |
| "fast_responder %s: Will drive %0s to %b in %0d fast clock cycles", \ |
| rsp_name, rsp_name, req, cycles), UVM_HIGH) \ |
| cfg.clk_rst_vif.wait_clks(cycles); \ |
| rsp <= req; \ |
| `uvm_info(`gfn, $sformatf("fast_responder %s: Driving %0s to %b", rsp_name, rsp_name, req), UVM_HIGH) \ |
| |
| |
| task fast_responder(); |
| fork |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_rst_req.rst_lc_req begin |
| `uvm_info(`gfn, $sformatf( |
| "fast responder got rst_lc_req change to 0x%x", |
| cfg.pwrmgr_vif.fast_cb.pwr_rst_req.rst_lc_req |
| ), UVM_HIGH) |
| raise_fast_objection("rst_lc_src_n"); |
| `FAST_RESPONSE_ACTION("rst_lc_src_n", cfg.pwrmgr_vif.fast_cb.pwr_rst_rsp.rst_lc_src_n, |
| ~cfg.pwrmgr_vif.fast_cb.pwr_rst_req.rst_lc_req, |
| cycles_before_rst_lc_src) |
| if (cfg.pwrmgr_vif.fast_cb.pwr_rst_req.rst_lc_req[1] == 1'b0) begin |
| cfg.esc_clk_rst_vif.drive_rst_pin(1); |
| cfg.lc_clk_rst_vif.drive_rst_pin(1); |
| end else begin |
| // And clear all reset requests when rst_lc_req[1] goes high, because when |
| // peripherals are reset they should drop their reset requests. |
| cfg.esc_clk_rst_vif.drive_rst_pin(0); |
| cfg.lc_clk_rst_vif.drive_rst_pin(0); |
| clear_escalation_reset(); |
| clear_ndm_reset(); |
| cfg.pwrmgr_vif.update_resets('0); |
| cfg.pwrmgr_vif.update_sw_rst_req(prim_mubi_pkg::MuBi4False); |
| `uvm_info(`gfn, "Clearing resets", UVM_MEDIUM) |
| end |
| drop_fast_objection("rst_lc_src_n"); |
| `uvm_info(`gfn, "fast responder done with rst_lc_req change", UVM_HIGH) |
| end |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_rst_req.rst_sys_req begin |
| raise_fast_objection("rst_sys_src_n"); |
| `FAST_RESPONSE_ACTION("rst_sys_src_n", cfg.pwrmgr_vif.fast_cb.pwr_rst_rsp.rst_sys_src_n, |
| ~cfg.pwrmgr_vif.fast_cb.pwr_rst_req.rst_sys_req, |
| cycles_before_rst_sys_src) |
| drop_fast_objection("rst_sys_src_n"); |
| end |
| |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_clk_req.io_ip_clk_en begin |
| logic new_value = cfg.pwrmgr_vif.fast_cb.pwr_clk_req.io_ip_clk_en; |
| raise_fast_objection("io_status"); |
| `uvm_info(`gfn, $sformatf( |
| "fast_responder: Will drive %0s to %b in %0d fast clock cycles", |
| "io_status", |
| new_value, |
| cycles_before_io_status |
| ), UVM_HIGH) |
| cfg.clk_rst_vif.wait_clks(cycles_before_io_status); |
| if (new_value) cfg.esc_clk_rst_vif.start_clk(); |
| else cfg.esc_clk_rst_vif.stop_clk(); |
| cfg.clk_rst_vif.wait_clks(2); |
| cfg.pwrmgr_vif.fast_cb.pwr_clk_rsp.io_status <= new_value; |
| `uvm_info(`gfn, $sformatf( |
| "fast_responder: Driving %0s to %b", |
| "io_status", |
| cfg.pwrmgr_vif.fast_cb.pwr_clk_req.io_ip_clk_en |
| ), UVM_HIGH) |
| drop_fast_objection("io_status"); |
| end |
| |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_clk_req.main_ip_clk_en begin |
| raise_fast_objection("main_status"); |
| `FAST_RESPONSE_ACTION("main_status", cfg.pwrmgr_vif.fast_cb.pwr_clk_rsp.main_status, |
| cfg.pwrmgr_vif.fast_cb.pwr_clk_req.main_ip_clk_en, |
| cycles_before_main_status) |
| drop_fast_objection("main_status"); |
| end |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_clk_req.usb_ip_clk_en begin |
| raise_fast_objection("usb_status"); |
| `FAST_RESPONSE_ACTION("usb_status", cfg.pwrmgr_vif.fast_cb.pwr_clk_rsp.usb_status, |
| cfg.pwrmgr_vif.fast_cb.pwr_clk_req.usb_ip_clk_en, |
| cycles_before_usb_status) |
| drop_fast_objection("usb_status"); |
| end |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_lc_req.lc_init begin |
| raise_fast_objection("lc_done"); |
| `FAST_RESPONSE_ACTION("lc_done", cfg.pwrmgr_vif.fast_cb.pwr_lc_rsp.lc_done, |
| cfg.pwrmgr_vif.fast_cb.pwr_lc_req.lc_init, cycles_before_lc_done) |
| drop_fast_objection("lc_done"); |
| end |
| forever |
| @cfg.pwrmgr_vif.fast_cb.pwr_otp_req.otp_init begin |
| raise_fast_objection("otp_done"); |
| `FAST_RESPONSE_ACTION("otp_done", cfg.pwrmgr_vif.fast_cb.pwr_otp_rsp.otp_done, |
| cfg.pwrmgr_vif.fast_cb.pwr_otp_req.otp_init, cycles_before_otp_done) |
| drop_fast_objection("otp_done"); |
| end |
| join_none |
| endtask : fast_responder |
| `undef FAST_RESPONSE_ACTION |
| |
| function void control_assertions(bit enable); |
| `uvm_info(`gfn, $sformatf("%0sabling assertions", enable ? "En" : "Dis"), UVM_MEDIUM) |
| cfg.pwrmgr_clock_enables_sva_vif.disable_sva = !enable; |
| cfg.pwrmgr_rstmgr_sva_vif.disable_sva = !enable; |
| endfunction |
| |
| local task wait_for_fall_through(); |
| `DV_WAIT(!cfg.pwrmgr_vif.pwr_cpu.core_sleeping) |
| exp_intr = 1'b1; |
| `uvm_info(`gfn, "wait_for_fall_through succeeds", UVM_MEDIUM) |
| endtask |
| |
| local task wait_for_abort(); |
| `DV_WAIT( |
| !cfg.pwrmgr_vif.pwr_flash.flash_idle || !cfg.pwrmgr_vif.pwr_otp_rsp.otp_idle || |
| !cfg.pwrmgr_vif.pwr_lc_rsp.lc_idle) |
| exp_intr = 1'b1; |
| `uvm_info(`gfn, "wait_for_abort succeeds", UVM_MEDIUM) |
| endtask |
| |
| local task wait_for_low_power_transition(); |
| wait_for_reset_cause(pwrmgr_pkg::LowPwrEntry); |
| exp_wakeup_reasons = wakeups & wakeups_en; |
| exp_intr = 1'b1; |
| `uvm_info(`gfn, "Setting expected interrupt", UVM_MEDIUM) |
| endtask |
| |
| task process_low_power_hint(); |
| `uvm_info(`gfn, "Entering process_low_power_hint", UVM_MEDIUM) |
| // Timeout if the low power transition waits too long for WFI. |
| `DV_WAIT(cfg.pwrmgr_vif.fast_state != pwrmgr_pkg::FastPwrStateActive) |
| `uvm_info(`gfn, "In process_low_power_hint pre forks", UVM_MEDIUM) |
| // Clear expectations. |
| exp_wakeup_reasons = 1'b0; |
| fork |
| begin : isolation_fork |
| fork |
| wait_for_fall_through(); |
| wait_for_abort(); |
| wait_for_low_power_transition(); |
| join_any |
| disable fork; |
| end |
| join |
| // At this point we know the low power transition went through or was aborted. |
| // If it went through, determine if the transition to active state is for a reset, and |
| // cancel the expected interrupt. |
| if (exp_wakeup_reasons) begin |
| `DV_WAIT(cfg.pwrmgr_vif.slow_state == pwrmgr_pkg::SlowPwrStateMainPowerOn) |
| if (cfg.pwrmgr_vif.pwrup_cause == pwrmgr_pkg::Reset) begin |
| `uvm_info(`gfn, "Cancelling expected interrupt", UVM_MEDIUM) |
| exp_intr = 1'b0; |
| end |
| end |
| endtask |
| |
| // Updates control CSR. |
| task update_control_csr(); |
| fork |
| begin |
| ral.control.core_clk_en.set(control_enables.core_clk_en); |
| ral.control.io_clk_en.set(control_enables.io_clk_en); |
| ral.control.usb_clk_en_lp.set(control_enables.usb_clk_en_lp); |
| ral.control.usb_clk_en_active.set(control_enables.usb_clk_en_active); |
| ral.control.main_pd_n.set(control_enables.main_pd_n); |
| ral.control.low_power_hint.set(low_power_hint); |
| // Disable assertions when main power is down. |
| control_assertions(control_enables.main_pd_n); |
| `uvm_info(`gfn, $sformatf( |
| "Setting control CSR to 0x%x, enables=%p, low_power_hint=%b", |
| ral.control.get(), |
| control_enables, |
| low_power_hint |
| ), UVM_MEDIUM) |
| csr_update(.csr(ral.control)); |
| end |
| // Predict the effect of the potential low power transition. |
| if (low_power_hint) process_low_power_hint(); |
| join_any |
| endtask : update_control_csr |
| |
| // This enables the fast fsm to transition to low power when all nvms are idle after the |
| // transition is enabled by software and cpu WFI. When not all are idle the transition is |
| // aborted. |
| virtual task set_nvms_idle(logic flash_idle = 1'b1, logic lc_idle = 1'b1, logic otp_idle = 1'b1); |
| `uvm_info(`gfn, $sformatf( |
| "Setting nvms idle: flash=%b, lc=%b, otp=%b", flash_idle, lc_idle, otp_idle), |
| UVM_MEDIUM) |
| cfg.pwrmgr_vif.update_flash_idle(flash_idle); |
| cfg.pwrmgr_vif.update_lc_idle(lc_idle); |
| cfg.pwrmgr_vif.update_otp_idle(otp_idle); |
| endtask |
| |
| // Waits for the fast fsm becoming active after SW initiated low power, indicated by the |
| // fetch_en output going high. |
| task wait_for_fast_fsm_active(); |
| `uvm_info(`gfn, "starting wait for pwrmgr fast fsm active", UVM_MEDIUM) |
| `DV_SPINWAIT(wait (cfg.pwrmgr_vif.fetch_en == lc_ctrl_pkg::On);, |
| "timeout waiting for the CPU to be active", FetchEnTimeoutNs) |
| `uvm_info(`gfn, "pwrmgr fast fsm is active", UVM_MEDIUM) |
| endtask |
| |
| task wait_for_reset_cause(pwrmgr_pkg::reset_cause_e cause); |
| `DV_WAIT(cfg.pwrmgr_vif.pwr_rst_req.reset_cause == cause) |
| `uvm_info(`gfn, $sformatf("Observed reset cause_match 0x%x", cause), UVM_MEDIUM) |
| endtask |
| |
| virtual task wait_for_csr_to_propagate_to_slow_domain(); |
| csr_wr(.ptr(ral.cfg_cdc_sync), .value(1'b1)); |
| csr_spinwait(.ptr(ral.cfg_cdc_sync), .exp_data(1'b0), |
| .timeout_ns(PropagationToSlowTimeoutInNanoSeconds)); |
| `uvm_info(`gfn, "CSR updates made it to the slow domain", UVM_MEDIUM) |
| endtask |
| |
| // Checks the reset_status CSR matches expectations. |
| task check_reset_status(resets_t expected_resets); |
| csr_rd_check(.ptr(ral.reset_status[0]), .compare_value(expected_resets), |
| .err_msg("reset_status")); |
| endtask |
| |
| task fast_check_reset_status(resets_t expected_resets); |
| logic [pwrmgr_reg_pkg::NumRstReqs-1:0] init_reset_status; |
| `uvm_info(`gfn, "init reset status", UVM_MEDIUM); |
| // Wait to get out of low power state, since all reset status should have settled. |
| if (cfg.pwrmgr_vif.fast_state == pwrmgr_pkg::FastPwrStateLowPower) begin |
| `DV_SPINWAIT(wait(cfg.pwrmgr_vif.fast_state != pwrmgr_pkg::FastPwrStateLowPower);, |
| "fast state out of low power for reset timeout", 15_000) |
| end |
| |
| init_reset_status = cfg.pwrmgr_vif.reset_status; |
| if (expected_resets == init_reset_status) begin |
| // This is a success, so nothing more to do. |
| return; |
| end else begin |
| `DV_SPINWAIT(wait(cfg.pwrmgr_vif.reset_status != init_reset_status);, $sformatf( |
| "reset_status wait timeout exp:%x init:%x", expected_resets, init_reset_status), |
| 15_000) |
| // The various bits of reset_status could have different sync delays, wait some more. |
| cfg.clk_rst_vif.wait_clks(2); |
| `DV_CHECK_EQ(cfg.pwrmgr_vif.reset_status, expected_resets) |
| end |
| endtask |
| |
| // Checks the wake_status CSR matches expectations. |
| task check_wake_status(wakeups_t expected_wakeups); |
| csr_rd_check(.ptr(ral.wake_status[0]), .compare_value(expected_wakeups), |
| .err_msg("wake_status")); |
| endtask |
| |
| task fast_check_wake_status(wakeups_t expected_wakeups); |
| logic [pwrmgr_reg_pkg::NumWkups-1:0] init_wakeup_status; |
| `uvm_info(`gfn, "init wakeup", UVM_MEDIUM); |
| init_wakeup_status = cfg.pwrmgr_vif.wakeup_status; |
| |
| // Wait to get out of low power state, since all wake status should have settled |
| if (cfg.pwrmgr_vif.fast_state == pwrmgr_pkg::FastPwrStateLowPower) begin |
| `DV_SPINWAIT(wait(cfg.pwrmgr_vif.fast_state != pwrmgr_pkg::FastPwrStateLowPower);, |
| "fast state out of low power for wakeup timeout", 15_000) |
| end |
| |
| if (expected_wakeups == init_wakeup_status) begin |
| // This is a success, so nothing more to do. |
| return; |
| end else begin |
| `DV_SPINWAIT(wait(cfg.pwrmgr_vif.wakeup_status != init_wakeup_status);, $sformatf( |
| "wakeup_status wait timeout exp:%x init:%x", expected_wakeups, init_wakeup_status |
| ), 15_000) |
| // The various bits of wakeup_status could have different sync delays, so wait some more. |
| cfg.clk_rst_vif.wait_clks(2); |
| `DV_CHECK_EQ(cfg.pwrmgr_vif.wakeup_status, expected_wakeups) |
| end |
| endtask |
| |
| task fast_check_wake_info(wakeups_t reasons, wakeups_t prior_reasons = '0, bit fall_through, |
| bit prior_fall_through = '0, bit abort, bit prior_abort = '0); |
| pwrmgr_reg_pkg::pwrmgr_hw2reg_wake_info_reg_t initial_value, exp_value; |
| initial_value = cfg.pwrmgr_vif.wake_info; |
| |
| if (disable_wakeup_capture) begin |
| exp_value.reasons = prior_reasons; |
| exp_value.fall_through = prior_fall_through; |
| exp_value.abort = prior_abort; |
| end else begin |
| exp_value.reasons = (reasons | prior_reasons); |
| exp_value.fall_through = (fall_through | prior_fall_through); |
| exp_value.abort = (abort | prior_abort); |
| end |
| if (exp_value != initial_value) begin |
| // The various bits of wake_info could have different sync delays, so wait some more. |
| cfg.clk_rst_vif.wait_clks(1); |
| `DV_SPINWAIT(wait(cfg.pwrmgr_vif.wake_info != initial_value);, $sformatf( |
| "wake info wait timeout exp:%p init:%p", exp_value, initial_value), 15_000) |
| end |
| endtask : fast_check_wake_info |
| |
| // Checks the wake_info CSR matches expectations depending on capture disable. |
| // The per-field "prior_" arguments support cases where the wake_info register was not |
| // cleared and may contain residual values. |
| task check_wake_info(wakeups_t reasons, wakeups_t prior_reasons = '0, bit fall_through, |
| bit prior_fall_through = '0, bit abort, bit prior_abort = '0); |
| if (disable_wakeup_capture) begin |
| csr_rd_check(.ptr(ral.wake_info.reasons), .compare_value(prior_reasons), |
| .err_msg("With capture disabled")); |
| csr_rd_check(.ptr(ral.wake_info.fall_through), .compare_value(prior_fall_through), |
| .err_msg("With capture disabled")); |
| csr_rd_check(.ptr(ral.wake_info.abort), .compare_value(prior_abort), |
| .err_msg("With capture disabled")); |
| end else begin |
| csr_rd_check(.ptr(ral.wake_info.reasons), .compare_value(reasons | prior_reasons), |
| .err_msg("With capture enabled")); |
| csr_rd_check(.ptr(ral.wake_info.fall_through), |
| .compare_value(fall_through | prior_fall_through), |
| .err_msg("With capture enabled")); |
| csr_rd_check(.ptr(ral.wake_info.abort), .compare_value(abort | prior_abort), |
| .err_msg("With capture enabled")); |
| end |
| endtask : check_wake_info |
| |
| task clear_wake_info(); |
| // To clear wake_info, capture must be disabled. |
| csr_wr(.ptr(ral.wake_info_capture_dis), .value(1'b1)); |
| csr_wr(.ptr(ral.wake_info), .value('1)); |
| endtask |
| |
| function void send_escalation_reset(); |
| `uvm_info(`gfn, "Sending escalation reset", UVM_MEDIUM) |
| cfg.m_esc_agent_cfg.vif.sender_cb.esc_tx_int <= 2'b10; |
| endfunction |
| |
| function void clear_escalation_reset(); |
| `uvm_info(`gfn, "Clearing escalation reset", UVM_MEDIUM) |
| cfg.m_esc_agent_cfg.vif.sender_cb.esc_tx_int <= 2'b01; |
| endfunction |
| |
| function void send_ndm_reset(); |
| `uvm_info(`gfn, "Sending ndm reset", UVM_MEDIUM) |
| cfg.pwrmgr_vif.cpu_i.ndmreset_req = 1'b1; |
| endfunction |
| |
| function void clear_ndm_reset(); |
| `uvm_info(`gfn, "Clearing ndm reset", UVM_MEDIUM) |
| cfg.pwrmgr_vif.cpu_i.ndmreset_req = 1'b0; |
| endfunction |
| |
| task send_power_glitch(); |
| // Create glitch by 'glitch_power_reset'. An outgoing alert is only possible |
| // when main power is up. |
| if (control_enables.main_pd_n) expect_fatal_alerts = 1; |
| else expect_fatal_alerts = 0; |
| `uvm_info(`gfn, $sformatf( |
| "Sending power glitch, expecting %0s alert", expect_fatal_alerts ? "an" : "no"), |
| UVM_MEDIUM) |
| cfg.pwrmgr_vif.glitch_power_reset(); |
| endtask |
| |
| // bad_bits = {done, good} |
| task add_rom_rsp_noise(); |
| bit [MUBI4W*2-1:0] bad_bits; |
| int delay; |
| |
| repeat (10) begin |
| delay = $urandom_range(5, 10); |
| `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(bad_bits, |
| bad_bits[MUBI4W*2-1:MUBI4W] != prim_mubi_pkg::MuBi4True; |
| bad_bits[MUBI4W*2-1:MUBI4W] != prim_mubi_pkg::MuBi4False; |
| bad_bits[MUBI4W-1:0] != prim_mubi_pkg::MuBi4False; |
| bad_bits[MUBI4W-1:0] != prim_mubi_pkg::MuBi4True;) |
| `uvm_info(`gfn, $sformatf("add_rom_rsp_noise to 0x%x", bad_bits), UVM_HIGH) |
| cfg.pwrmgr_vif.rom_ctrl = bad_bits; |
| #(delay * 10ns); |
| end |
| endtask : add_rom_rsp_noise |
| |
| // Drive rom_ctrl at post reset stage |
| virtual task init_rom_response(); |
| if (cfg.pwrmgr_vif.rom_ctrl.done != prim_mubi_pkg::MuBi4True) begin |
| cfg.pwrmgr_vif.rom_ctrl.good = get_rand_mubi4_val( |
| .t_weight(1), .f_weight(1), .other_weight(1) |
| ); |
| cfg.pwrmgr_vif.rom_ctrl.done = get_rand_mubi4_val( |
| .t_weight(0), .f_weight(1), .other_weight(1) |
| ); |
| `DV_WAIT(cfg.pwrmgr_vif.fast_state == pwrmgr_pkg::FastPwrStateRomCheckDone) |
| cfg.pwrmgr_vif.rom_ctrl.good = get_rand_mubi4_val( |
| .t_weight(1), .f_weight(1), .other_weight(1) |
| ); |
| cfg.pwrmgr_vif.rom_ctrl.done = get_rand_mubi4_val( |
| .t_weight(0), .f_weight(1), .other_weight(1) |
| ); |
| cfg.aon_clk_rst_vif.wait_clks(10); |
| cfg.pwrmgr_vif.rom_ctrl.good = get_rand_mubi4_val( |
| .t_weight(1), .f_weight(1), .other_weight(1) |
| ); |
| cfg.pwrmgr_vif.rom_ctrl.done = get_rand_mubi4_val( |
| .t_weight(0), .f_weight(1), .other_weight(1) |
| ); |
| cfg.aon_clk_rst_vif.wait_clks(5); |
| cfg.pwrmgr_vif.rom_ctrl.good = get_rand_mubi4_val( |
| .t_weight(1), .f_weight(1), .other_weight(1) |
| ); |
| cfg.pwrmgr_vif.rom_ctrl.done = get_rand_mubi4_val( |
| .t_weight(0), .f_weight(1), .other_weight(1) |
| ); |
| cfg.aon_clk_rst_vif.wait_clks(5); |
| cfg.pwrmgr_vif.rom_ctrl.good = prim_mubi_pkg::MuBi4True; |
| cfg.pwrmgr_vif.rom_ctrl.done = prim_mubi_pkg::MuBi4True; |
| end |
| `uvm_info(`gfn, "Set rom response to MuBi4True", UVM_MEDIUM) |
| endtask |
| |
| endclass : pwrmgr_base_vseq |