[dv/rstmgr] Add cascading resets SVA
Add SVA assertions checking rstmgr output resets are cascaded.
Add SVA assertion checking POR stretching.
Bind assertions to the rstmgr IP.
Clean up reset handling in the base sequence, allowing the
common CSR tests to succeed.
Signed-off-by: Guillermo Maturana <maturana@google.com>
diff --git a/hw/ip/rstmgr/dv/env/seq_lib/rstmgr_base_vseq.sv b/hw/ip/rstmgr/dv/env/seq_lib/rstmgr_base_vseq.sv
index ca26316..970d350 100644
--- a/hw/ip/rstmgr/dv/env/seq_lib/rstmgr_base_vseq.sv
+++ b/hw/ip/rstmgr/dv/env/seq_lib/rstmgr_base_vseq.sv
@@ -19,14 +19,15 @@
localparam int MAIN_FREQ_MHZ = 100;
localparam int USB_FREQ_MHZ = 48;
- localparam int RESET_CLK_PERIODS = 5;
- // This needs to be longer than RESET_CLK_PERIODS times the slowest clock,
- // which is the AON's.
- localparam int DELAY_FOR_RESETS_CONCURRENTLY_PS = 5_000_000;
+ // POR needs to be stable not less than 32 clock cycles, plus some extra, before it
+ // propagates to the rest of the logic.
+ localparam int POR_CLK_CYCLES = 40;
- // This should exceed the clock cycles needed by the reset stretcher, which is normally 32
- // AON cycles, but can be extended for tests that introduce reset glitches.
- localparam int RESET_STRETCHER_TIMEOUT_NS = 4_000_000;
+ // This is only used for the various clocks to start ticking, so can be any small number.
+ localparam int BOGUS_RESET_CLK_CYCLES = 2;
+
+ // Some extra cycles from reset going inactive before the CPU's reset goes inactive.
+ localparam int CPU_RESET_CLK_CYCLES = 10;
typedef enum {
LcTxTSelOn,
@@ -76,10 +77,6 @@
`uvm_object_new
- local function real freq_mhz_to_period_in_ps(real freq);
- return 1e12 / (freq * 1_000_000.0);
- endfunction
-
function void set_pwrmgr_rst_reqs(logic rst_lc_req, logic rst_sys_req);
cfg.rstmgr_vif.pwr_i.rst_lc_req = {rstmgr_pkg::PowerDomains{rst_lc_req}};
cfg.rstmgr_vif.pwr_i.rst_sys_req = {rstmgr_pkg::PowerDomains{rst_sys_req}};
@@ -178,16 +175,16 @@
task check_software_reset_csr_and_pins(logic [NumSwResets-1:0] exp_ctrl_n);
csr_rd_check(.ptr(ral.sw_rst_ctrl_n[0]), .compare_value(exp_ctrl_n),
.err_msg("Expected enabled updates in sw_rst_ctrl_n"));
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_device_n[1], exp_ctrl_n[0])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_host0_n[1], exp_ctrl_n[1])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_device_n[1], exp_ctrl_n[0])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_host0_n[1], exp_ctrl_n[1])
`DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_host0_core_n[1], exp_ctrl_n[2])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_host1_n[1], exp_ctrl_n[3])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_host1_n[1], exp_ctrl_n[3])
`DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_spi_host1_core_n[1], exp_ctrl_n[4])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_usb_n[1], exp_ctrl_n[5])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_usbif_n[1], exp_ctrl_n[6])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_i2c0_n[1], exp_ctrl_n[7])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_i2c1_n[1], exp_ctrl_n[8])
- `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_i2c2_n[1], exp_ctrl_n[9])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_usb_n[1], exp_ctrl_n[5])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_usbif_n[1], exp_ctrl_n[6])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_i2c0_n[1], exp_ctrl_n[7])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_i2c1_n[1], exp_ctrl_n[8])
+ `DV_CHECK_EQ(cfg.rstmgr_vif.resets_o.rst_i2c2_n[1], exp_ctrl_n[9])
endtask
// Sends either a low power exit or hardware request reset, and drops it once it should have
@@ -224,49 +221,42 @@
// TODO
endtask
- task fork_resets();
+ task por_reset();
+ cfg.rstmgr_vif.por_n = 1'b0;
+ cfg.aon_clk_rst_vif.wait_clks(POR_CLK_CYCLES);
+ cfg.rstmgr_vif.por_n = 1'b1;
+ @(posedge cfg.rstmgr_vif.resets_o.rst_por_io_div4_n[0]);
+ endtask
+
+ task start_clocks();
fork
- // This is the POR, so it should be applied once only.
- if (!reset_once) begin
- cfg.rstmgr_vif.por_n = 1'b0;
- cfg.clk_rst_vif.wait_clks(2);
- cfg.rstmgr_vif.por_n = 1'b1;
- reset_once = 1'b1;
- end
- cfg.aon_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0), .reset_width_clks(RESET_CLK_PERIODS));
- cfg.io_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0), .reset_width_clks(RESET_CLK_PERIODS));
+ cfg.aon_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0),
+ .reset_width_clks(BOGUS_RESET_CLK_CYCLES));
+ cfg.io_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0),
+ .reset_width_clks(BOGUS_RESET_CLK_CYCLES));
cfg.io_div2_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0),
- .reset_width_clks(RESET_CLK_PERIODS));
+ .reset_width_clks(BOGUS_RESET_CLK_CYCLES));
cfg.io_div4_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0),
- .reset_width_clks(RESET_CLK_PERIODS));
+ .reset_width_clks(BOGUS_RESET_CLK_CYCLES));
cfg.main_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0),
- .reset_width_clks(RESET_CLK_PERIODS));
- cfg.usb_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0), .reset_width_clks(RESET_CLK_PERIODS));
+ .reset_width_clks(BOGUS_RESET_CLK_CYCLES));
+ cfg.usb_clk_rst_vif.apply_reset(.pre_reset_dly_clks(0),
+ .reset_width_clks(BOGUS_RESET_CLK_CYCLES));
join
endtask
// This waits till the outgoing POR reset for the CPU goes inactive.
local task wait_for_cpu_out_of_reset();
- `DV_SPINWAIT(wait (cfg.rstmgr_vif.resets_o.rst_sys_n[1] == 1'b1);,
- "timeout waiting for POR reset to cpu output", RESET_STRETCHER_TIMEOUT_NS)
+ `DV_SPINWAIT_EXIT(wait (cfg.rstmgr_vif.resets_o.rst_sys_n[1] == 1'b1);,
+ cfg.clk_rst_vif.wait_clks(CPU_RESET_CLK_CYCLES);,
+ "timeout waiting for cpu reset inactive")
endtask
virtual task apply_reset(string kind = "HARD");
- `DV_CHECK_LT(freq_mhz_to_period_in_ps(AON_FREQ_MHZ) * RESET_CLK_PERIODS,
- DELAY_FOR_RESETS_CONCURRENTLY_PS, $sformatf(
- "apply_resets_concurrently delay (%0d) must exceed slowest reset (%0d)",
- DELAY_FOR_RESETS_CONCURRENTLY_PS,
- freq_mhz_to_period_in_ps(
- AON_FREQ_MHZ
- ) * RESET_CLK_PERIODS
- ))
fork
+ por_reset();
+ start_clocks();
super.apply_reset(kind);
- if (kind == "HARD") begin
- // Apply reset to all clk_rst_if instances so the clocks start, even if
- // the rst_n output is not connected.
- fork_resets();
- end
join
endtask
@@ -275,19 +265,11 @@
endtask
virtual task apply_resets_concurrently(int reset_duration_ps = 0);
- cfg.aon_clk_rst_vif.drive_rst_pin(0);
- cfg.io_clk_rst_vif.drive_rst_pin(0);
- cfg.io_div2_clk_rst_vif.drive_rst_pin(0);
- cfg.io_div4_clk_rst_vif.drive_rst_pin(0);
- cfg.main_clk_rst_vif.drive_rst_pin(0);
- cfg.usb_clk_rst_vif.drive_rst_pin(0);
- super.apply_resets_concurrently(DELAY_FOR_RESETS_CONCURRENTLY_PS);
- cfg.aon_clk_rst_vif.drive_rst_pin(1);
- cfg.io_clk_rst_vif.drive_rst_pin(1);
- cfg.io_div2_clk_rst_vif.drive_rst_pin(1);
- cfg.io_div4_clk_rst_vif.drive_rst_pin(1);
- cfg.main_clk_rst_vif.drive_rst_pin(1);
- cfg.usb_clk_rst_vif.drive_rst_pin(1);
+ fork
+ por_reset();
+ start_clocks();
+ super.apply_resets_concurrently(reset_duration_ps);
+ join
endtask
// setup basic rstmgr features
diff --git a/hw/ip/rstmgr/dv/sva/rstmgr_bind.sv b/hw/ip/rstmgr/dv/sva/rstmgr_bind.sv
index 7e2a240..1c78438 100644
--- a/hw/ip/rstmgr/dv/sva/rstmgr_bind.sv
+++ b/hw/ip/rstmgr/dv/sva/rstmgr_bind.sv
@@ -23,4 +23,21 @@
.rst_sys_src_n(pwr_o.rst_sys_src_n)
);
+ bind rstmgr rstmgr_cascading_sva_if rstmgr_cascading_sva_if (
+ .clk_i,
+ .clk_aon_i,
+ .clk_io_div4_i,
+ .clk_io_div2_i,
+ .clk_io_i,
+ .clk_main_i,
+ .clk_usb_i,
+ .por_n_i,
+ .resets_o,
+ .rst_lc_src_n(pwr_o.rst_lc_src_n),
+ .rst_sys_src_n(pwr_o.rst_sys_src_n),
+ .scan_rst_ni,
+ .scanmode_i
+ );
+
+
endmodule
diff --git a/hw/ip/rstmgr/dv/sva/rstmgr_cascading_sva_if.core b/hw/ip/rstmgr/dv/sva/rstmgr_cascading_sva_if.core
new file mode 100644
index 0000000..14f6d71
--- /dev/null
+++ b/hw/ip/rstmgr/dv/sva/rstmgr_cascading_sva_if.core
@@ -0,0 +1,21 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:rstmgr_cascading_sva_if:0.1"
+description: "RSTMGR cascading resets assertion interface."
+filesets:
+ files_dv:
+ depend:
+ - lowrisc:ip:lc_ctrl_pkg
+ - lowrisc:ip:pwrmgr_pkg
+ - lowrisc:ip:rstmgr
+
+ files:
+ - rstmgr_cascading_sva_if.sv
+ file_type: systemVerilogSource
+
+targets:
+ default:
+ filesets:
+ - files_dv
diff --git a/hw/ip/rstmgr/dv/sva/rstmgr_cascading_sva_if.sv b/hw/ip/rstmgr/dv/sva/rstmgr_cascading_sva_if.sv
new file mode 100644
index 0000000..d630247
--- /dev/null
+++ b/hw/ip/rstmgr/dv/sva/rstmgr_cascading_sva_if.sv
@@ -0,0 +1,152 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// This has assertions that check the reset outputs of rstmgr cascade properly.
+// This means higher level resets always cause the lower level ones to assert.
+// The hierarchy is
+// por > lc > sys > specific peripherals
+// In addition, a scan reset is at the same level as por.
+interface rstmgr_cascading_sva_if (
+ input logic clk_i,
+ input logic clk_aon_i,
+ input logic clk_io_div2_i,
+ input logic clk_io_div4_i,
+ input logic clk_io_i,
+ input logic clk_main_i,
+ input logic clk_usb_i,
+ input logic por_n_i,
+ input rstmgr_pkg::rstmgr_out_t resets_o,
+ input [rstmgr_pkg::PowerDomains-1:0] rst_lc_src_n,
+ input [rstmgr_pkg::PowerDomains-1:0] rst_sys_src_n,
+ input logic scan_rst_ni,
+ input lc_ctrl_pkg::lc_tx_t scanmode_i
+);
+
+ // The min and max bounds on the number of cycles for an edge to occur.
+ typedef struct {
+ int min;
+ int max;
+ } bounds_t;
+
+ // The bounds for a fall and rise edge to occur.
+ typedef struct {
+ bounds_t fall;
+ bounds_t rise;
+ } edge_bounds_t;
+
+ // This is used to check por_n_i active high leads to a rising edge of rst_por_aon_n[0].
+ // The number of cycles with por_n_i stable is 32 plus synchronizers and some filter stages.
+ localparam edge_bounds_t PorCycles = '{fall: '{min: 0, max: 4}, rise: {min: 32, max: 40}};
+
+ // This is used to check for regular synchronizing delay. Reset falls asynchronously so the
+ // fall min cycles is zero.
+ localparam edge_bounds_t SyncCycles = '{fall: '{min: 0, max: 3}, rise: {min: 1, max: 3}};
+
+ // Cycles are counted from the output rst_por_aon_n or scan reset edges. The rise times can be
+ // higher since in the chip the aon reset goes through the pwrmgr slow fsm where it causes an
+ // lc rise request and there may be multiple synchronizers in the path.
+ localparam edge_bounds_t LcCycles = '{fall: '{min: 0, max: 4}, rise: '{min: 2, max: 10}};
+
+ // In the real system the rise of rst_lc_src_n is triggered by the pwr_i.rst_lc_req input,
+ // which can take a few cycles since it comes from the pwrmgr after it gets reset,
+ // is generated with the aon clock, and gets synchronized before it triggers
+ // a rise in rst_lc_src_n. There is an SVA for the rise in pwrmgr_rstmgr_sva_if.
+
+ // The cycles are counted from Lc edges.
+ localparam edge_bounds_t SysCycles = '{fall: '{min: 0, max: 4}, rise: '{min: 0, max: 8}};
+
+ // The different peripheral edges are synchronized to their respective clocks,
+ // so these counts assume synchronization and are triggered on the correct clock.
+ localparam edge_bounds_t PeriCycles = '{fall: '{min: 0, max: 4}, rise: '{min: 2, max: 8}};
+
+ bit disable_sva;
+
+ // Macros to avoid excesive boiler-plate code below.
+ `define FALL_ASSERT(name, from, to, cycles, clk) \
+ `ASSERT(name``Fall_A, \
+ $fell(from) |-> ##[cycles.fall.min:cycles.fall.max] $fell(to), clk, disable_sva)
+
+ `define RISE_ASSERT(name, from, to, cycles, clk) \
+ `ASSERT(name``Rise_A, \
+ $rose(from) |-> ##[cycles.rise.min:cycles.rise.max] $rose(to), clk, disable_sva)
+
+ `define CASCADED_ASSERTS(name, from, to, cycles, clk) \
+ `FALL_ASSERT(name, from, to, cycles, clk) \
+ `RISE_ASSERT(name, from, to, cycles, clk)
+
+ // A fall in por_n_i leads to a fall in rst_por_aon_n[0].
+ `FALL_ASSERT(CascadePorToAon, por_n_i, resets_o.rst_por_aon_n[0], PorCycles, clk_aon_i)
+
+ // A number of consecutive cycles with por_n_i inactive (high) should cause the aon resets to
+ // become inactive. This checks POR stretching.
+
+ // The antecedent: por_n_i rising and being stably high for a minimum number of cycles.
+ sequence PorStable_S;
+ $rose(por_n_i) ##1 por_n_i [* PorCycles.rise.min];
+ endsequence
+
+ // The consequence: reset will rise after some cycles.
+ sequence EventualAonRstRise_S;
+ ##[0:PorCycles.rise.max - PorCycles.rise.min] $rose(resets_o.rst_por_aon_n[0]);
+ endsequence
+
+ // The reset stretching assertion.
+ `ASSERT(StablePorToAonRise_A, PorStable_S |-> EventualAonRstRise_S, clk_aon_i, disable_sva)
+
+ logic scan_reset_n;
+ always_comb scan_reset_n = scan_rst_ni || (scanmode_i != lc_ctrl_pkg::On);
+
+ logic [rstmgr_pkg::PowerDomains-1:0] effective_aon_rst;
+ always_comb effective_aon_rst = resets_o.rst_por_aon_n & {rstmgr_pkg::PowerDomains{scan_reset_n}};
+
+ // The internal reset is triggered by one of the generated reset outputs.
+ logic [rstmgr_pkg::PowerDomains-1:0] local_rst_n;
+ always_comb local_rst_n = resets_o.rst_por_io_div4_n;
+
+ for (genvar pd = 0; pd < rstmgr_pkg::PowerDomains; ++pd) begin : power_domains
+ // The AON reset triggers the various por reset for the different clock domains through
+ // syncronizers.
+ `CASCADED_ASSERTS(CascadeEffAonToRstPor, effective_aon_rst[pd],resets_o.rst_por_n[pd],
+ SyncCycles, clk_main_i)
+ `CASCADED_ASSERTS(CascadeEffAonToRstPorIo, effective_aon_rst[pd], resets_o.rst_por_io_n[pd],
+ SyncCycles, clk_io_i)
+ `CASCADED_ASSERTS(CascadeEffAonToRstPorIoDiv2, effective_aon_rst[pd], resets_o.rst_por_io_div2_n[pd],
+ SyncCycles, clk_io_div2_i)
+ `CASCADED_ASSERTS(CascadeEffAonToRstPorIoDiv4, effective_aon_rst[pd], resets_o.rst_por_io_div4_n[pd],
+ SyncCycles, clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeEffAonToRstPorUcb, effective_aon_rst[pd], resets_o.rst_por_usb_n[pd],
+ SyncCycles, clk_usb_i)
+
+ // The root lc reset is triggered either by the internal reset, or by the pwr_i.rst_lc_req
+ // input. The latter is checked independently in pwrmgr_rstmgr_sva_if.
+ `CASCADED_ASSERTS(CascadeLocalRstToLc, local_rst_n[pd], rst_lc_src_n[pd], LcCycles, clk_i)
+
+ // The root sys reset is triggered by the lc reset, or independently by external requests.
+ // The latter is checked independently in pwrmgr_rstmgr_sva_if.
+ `CASCADED_ASSERTS(CascadeLcToSys, rst_lc_src_n[pd], rst_sys_src_n[pd], SysCycles, clk_i)
+ end
+
+ // Peripheral resets cascade from sys.
+ // We only care for power domain 1 for peripherals.
+ `CASCADED_ASSERTS(CascadeSysToSpiDevice, rst_sys_src_n[1], resets_o.rst_spi_device_n[1],
+ PeriCycles, clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeSysToSpiHost0, rst_sys_src_n[1], resets_o.rst_spi_host0_n[1], PeriCycles,
+ clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeSysToSpiHost0Core, rst_sys_src_n[1], resets_o.rst_spi_host0_core_n[1],
+ PeriCycles, clk_io_i)
+ `CASCADED_ASSERTS(CascadeSysToSpiHost1, rst_sys_src_n[1], resets_o.rst_spi_host1_n[1], PeriCycles,
+ clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeSysToSpiHost1Core, rst_sys_src_n[1], resets_o.rst_spi_host1_core_n[1],
+ PeriCycles, clk_io_i)
+ `CASCADED_ASSERTS(CascadeSysToUsb, rst_sys_src_n[1], resets_o.rst_usb_n[1], PeriCycles,
+ clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeSysToUsbIf, rst_sys_src_n[1], resets_o.rst_usbif_n[1], PeriCycles,
+ clk_usb_i)
+ `CASCADED_ASSERTS(CascadeSysToI2C0, rst_sys_src_n[1], resets_o.rst_i2c0_n[1], PeriCycles,
+ clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeSysToI2C1, rst_sys_src_n[1], resets_o.rst_i2c1_n[1], PeriCycles,
+ clk_io_div4_i)
+ `CASCADED_ASSERTS(CascadeSysToI2C2, rst_sys_src_n[1], resets_o.rst_i2c2_n[1], PeriCycles,
+ clk_io_div4_i)
+endinterface
diff --git a/hw/ip/rstmgr/dv/sva/rstmgr_sva.core b/hw/ip/rstmgr/dv/sva/rstmgr_sva.core
index 177bd17..7ba9a3f 100644
--- a/hw/ip/rstmgr/dv/sva/rstmgr_sva.core
+++ b/hw/ip/rstmgr/dv/sva/rstmgr_sva.core
@@ -10,6 +10,7 @@
- lowrisc:tlul:headers
- lowrisc:fpv:csr_assert_gen
- lowrisc:dv:pwrmgr_rstmgr_sva_if
+ - lowrisc:dv:rstmgr_cascading_sva_if
files:
- rstmgr_bind.sv
diff --git a/hw/ip/rstmgr/dv/tb.sv b/hw/ip/rstmgr/dv/tb.sv
index 8a11a54..cb9f3e4 100644
--- a/hw/ip/rstmgr/dv/tb.sv
+++ b/hw/ip/rstmgr/dv/tb.sv
@@ -59,9 +59,11 @@
`DV_ALERT_IF_CONNECT
// dut
+ // IMPORTANT: Notice the rst_ni input is connected to one of dut's outputs.
+ // This is consistent with rstmgr being the only source of resets.
rstmgr dut (
.clk_i (clk),
- .rst_ni (rst_n),
+ .rst_ni (rstmgr_if.resets_o.rst_por_io_div4_n[rstmgr_pkg::DomainAonSel]),
.clk_aon_i (clk_aon),
.clk_io_div4_i(clk_io_div4),
.clk_main_i (clk_main),