SVA Simple challenge: Write design and SVA for a down counter

This is a simple design and verification challenge. I’ll post my design and SVA code after I get a few responses from users. If there is an ambiguous requirement, then define your assumptions, and create the design and assertions. BTW, though simple, this design and assertions does take some thoughts!
Requirements:
Loadable down-counters are commonly used to countdown a loadable value, and stop counting when the count reaches zero. Terminal count is asserted when the count reaches zero.
dncounter interface

 module dncounter  #(WIDTH = 4) (
  output logic[WIDTH-1:0] count_out, 
  output logic termcount, 
  input logic[WIDTH-1:0] data_in, 
  input logic ld_enb, count_enb, rst_n, clk // active lo reset
  );
   // Define this design 
endmodule // dncounter

// testbench 
import uvm_pkg::*; `include "uvm_macros.svh" 
module top; 
    parameter WIDTH = 4;
    logic[WIDTH-1:0] count_out; 
    logic termcount;
    logic[WIDTH-1:0] data_in; 
    logic ld_enb, count_enb, rst_n, clk=0;
    
	dncounter  #(.WIDTH(WIDTH))  dncounter1(.*);
	
	initial forever #10 clk=!clk;   

	initial begin 
		rst_n <= 1'b0; 
		@(posedge clk)#2  rst_n <= 1'b1;  // Updated
		repeat(200) begin 
			@(posedge clk);   
			#2 if (!randomize(data_in, ld_enb, count_enb, rst_n)  with 
					{ ld_enb     dist {1'b1:=1, 1'b0:=8};
					  count_enb dist {1'b1:=1, 1'b0:=2};
					  rst_n     dist {1'b1:=12, 1'b0:=1};
					}) `uvm_error("MYERR", "This is a randomize error")
					end 
					$finish; 
		end 
endmodule 

Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us

  • SVA Handbook 4th Edition, 2016 ISBN 978-1518681448
  • A Pragmatic Approach to VMM Adoption 2006 ISBN 0-9705394-9-5
  • Using PSL/SUGAR for Formal and Dynamic Verification 2nd Edition, 2004, ISBN 0-9705394-6-0
  • Real Chip Design and Verification Using Verilog and VHDL, 2002 isbn 0-9705394-2-8
  • Component Design by Example ", 2001 ISBN 0-9705394-0-1
  • VHDL Coding Styles and Methodologies, 2nd Edition, 1999 ISBN 0-7923-8474-1
  • VHDL Answers to Frequently Asked Questions, 2nd Edition ISBN 0-7923-8115

In reply to ben@SystemVerilog.us:

Interesting…

In reply to Prabhu Kamalanathan:

I would like to see RTL design and assertions for this simple loadable down-dounter with a terminal count. As I said, I already have the design and the assertions, and the testbench, and it work fine. This is a simple challenge, if any of you is willing to share.
Will share my design after a reply fith a design an assertions.


// interface 
module dncounter  #(WIDTH = 4) (
  output logic[WIDTH-1:0] count_out,  // counter ouput
  output logic termcount,  // terminal cont when counter==0
  input logic[WIDTH-1:0] data_in, // initial count, loaded with ld_enb
  input logic ld_enb, count_enb, rst_n, clk // active lo reset
  );

Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us

In reply to ben@SystemVerilog.us:

I’m curious where you want to go with this, so I’ll bite. I don’t have a testbench to submit, but I do have assertions. I used a formal tool to prove them:


module dncounter_assertions #(WIDTH = 4) (
  input bit               rst_n,
  input bit               clk,

  input logic             count_en,
  
  input logic             load_en,
  input logic [WIDTH-1:0] data_in,
  
  input logic [WIDTH-1:0] count_out,
  input logic             term_count
);
  default disable iff !rst_n;

  default clocking cb @(posedge clk);
  endclocking

  
  count_out_reset_val : assert property (
    $rose(rst_n) |-> count_out == 0
  );

  // Not specified, but I think it's ok to say that before preloading we've
  // finished counting.
  term_count_reset_val : assert property (
    $rose(rst_n) |-> term_count == 1
  );


  load : assert property (
    load_en |=> count_out == $past(data_in)
  );

  // Not specified, but I'll define load as having priority over count.
  count : assert property (
    count_en && !load_en |=> count_out == $past(count_out) - 1
  );

  term_count_tracks_count_out : assert property (
    //term_count iff count_out == 0
    (count_out == 0 |-> term_count) and
      (term_count |-> count_out == 0)
  );

  // ...
endmodule

Some things weren’t fully specified so I chose what to implement. I said that when coming out of reset, we start in the terminal state. This means that pre-loading is mandatory. It would have also probably been valid to start in the full value state to be able to count down immediately. I also said that loading has priority over counting.

I also added a constraint to disallow counting from the terminal state:


module // ...
  
  no_count_when_term : assume property (
    term_count |-> !count_en
  );
endmodule

I wrote some simple coverage points for some cases I deemed interesting:


module // ...

  load_0 : cover property (
    load_en && data_in == 0
  );

  load_0_after_non_zero_count : cover property (
    load_en && data_in == 0 && count_out != 0
  );

  decrement_to_0 : cover property (
    count_out != 0 && count_en && !load_en
  );

endmodule

Here’s the RTL code I ended up with:

module dncounter #(WIDTH = 4) (
  input bit                rst_n,
  input bit                clk,

  input logic              count_en,
  
  input logic              load_en,
  input logic [WIDTH-1:0]  data_in,
  
  output logic [WIDTH-1:0] count_out,
  output logic             term_count
);
  
  always @(posedge clk or negedge rst_n)
    if (rst_n == 0) begin
      count_out <= 0;
    end
    else begin
      if (load_en) begin
        count_out <= data_in;
      end
      else if (count_en) begin
        count_out <= count_out - 1;
      end
    end

  // I implemented 'term_count' as a registered output, but it coult also be
  // implemented combinatorially (with some area savings, but output delay).
  always @(posedge clk or negedge rst_n)
    if (rst_n == 0) begin
      term_count <= 1;
    end
    else begin
      if (load_en) begin
        if (data_in == 0)
          term_count <= 1;
        else
          term_count <= 0;
      end
      else if (count_en) begin
        if (count_out == 1)
          term_count <= 1;
        else
          term_count <= 0;
      end
    end
endmodule

I chose to implement term_count sequentially, but I could have just as well implemented it combinatorially. I would have probably saved on logic (comparators), but I would have had output delay on the signal.

Now that I think about it a bit more, maybe it would have made more sense to define that in the terminal state, even when count comes, count_out stays 0. This way an integrator can just tie the count signal to 1 and play with loading.

Another version of RTL

NOTE: Feedback’s are welcome



module dncounter  #(WIDTH = 4) (
  output logic[WIDTH-1:0] count_out,  // counter ouput
  output logic termcount,  // terminal cont when counter==0
  input logic[WIDTH-1:0] data_in, // initial count, loaded with ld_enb
  input logic ld_enb, count_enb, rst_n, clk // active lo reset
  );

  // Assumption
  // 1 -> termcount should be ZERO post power on rest 
  //  	  (i.e. counter should initialized with WIDTH'b1 duruing reset)
  // 2 -> counter should not be over-run once it's triggered termcount (even count_enb HIGH)
  //      (i.e. counter should re-initialized via ld_enb (or) reset once it's triggered termcount)


  logic [WIDTH-1:0] 	dncounter;
  logic [WIDTH-1:0]  	dncounter_nxt;
  logic 		dncounter_at_zero, dncounter_at_zero_flag;

  // Output Generation
  assign count_out 	= dncounter;
  assign termcount 	= dncounter_at_zero;


  // Check whether counter reached ZERO or not
  assign dncounter_at_zero= (|dncouter) ? 1'b0 : 1'b1;
  
  // Initialize the counter when ld_enb is HIGH
  // Decrement the counter when " count_enb is HIGH and counter does not reached ZERO (i.e. after ld_enb HIGH)"
  assign dncounter_nxt  = (ld_enb) ? data_in : ((count_enb && ~dncounter_at_zero_flag) ? (dncounter - 1'b1) : dncounter);


  always @ (posedge clk or negedge rst_n)
  begin
	if(~rst_n)
		dncounter <= WIDTH'b1;  // To avoid termcount assertion immediately after power on reset
	else
		dncounter <= dncounter_nxt;
  end
 
 always @ (posedge clk or negedge rst_n)
 begin
	if(~rst_n)
		dncounter_at_zero_flag <= 1'b0;		 
	else if(ld_en) // clearing the flag 
		dncounter_at_zero_flag <= 1'b0;
	else  // registering the termcount assertion. it's used along with count_enb to avoid counter over-runnig
		dncounter_at_zero_flag <= dncounter_at_zero;
 end 

 endmodule 



In reply to Tudor Timi:
See embedded comments

*In reply to ben@SystemVerilog.us:*I’m curious where you want to go with this, so I’ll bite.

Thanks for providing a substantial reply.
My goals for asking for these challenges include:

  1. Demonstrate how assertions provide several benefits, besides verification.
  2. Assertions challenge ambiguous requirements, and force the clarification of issues, even if minute (more on that below).
  3. Demonstrate the need for reviews, and that includes assertions, code, and verification methodologies.
  4. Provide my reviews and comments, perhaps for the benefits of the community.
  5. Promote the use of assertions.

I don’t have a testbench to submit, but I do have assertions. I used a formal tool to prove them:

Good approach. BTW, I did provide a testbench (see my original post above). I found that the quickest way to build a testbench to verify assertions is to use the SV randomize with weighted distributions. Example:

	initial begin 
		rst_n <= 1'b0; 
		@(posedge clk) #2 rst_n <= 1'b1;
		repeat(200) begin 
			@(posedge clk);   
			#2if (!randomize(data_in, ld_enb, count_enb, rst_n)  with 
					{ ld_enb     dist {1'b1:=1, 1'b0:=8};
					  count_enb dist {1'b1:=1, 1'b0:=2};
					  rst_n     dist {1'b1:=12, 1'b0:=1};
					}) `uvm_error("MYERR", "This is a randomize error")
					end 
					$finish; 
		end 

// Your code 
...  
  count_out_reset_val : assert property (
    $rose(rst_n) |-> count_out == 0 );

  // Not specified, but I think it's ok to say that before preloading we've
  // finished counting.
  term_count_reset_val : assert property (
    $rose(rst_n) |-> term_count == 1 );

[Ben] Is is really a good idea, from an operation viewpoint, to have
term_count==1 upon a reset? If this was my design, I would have preferred a count_out to be '1.
[Ben] Why the $rose(rst_n)? Though the $rose if often used because the interest is indeed on just the rose(signal), in this case, we’re talking about a reset, thus, the assertion should be true for every cycle rst_n is asserted. Since rst_n is active low, the !rst_n or the rst_n==1’b1 should be used instead.


  load : assert property (
    load_en |=> count_out == $past(data_in)
  ); 

The rst_n is ignored here.
No default disable iff defined.


  // Not specified, but I'll define load as having priority over count.
  count : assert property (
    count_en && !load_en |=> count_out == $past(count_out) - 1
  ); // [Ben] Good assumption 

  term_count_tracks_count_out : assert property (
    //term_count iff count_out == 0
    (count_out == 0 |-> term_count) and
      (term_count |-> count_out == 0)
  );

[Ben] Interesting corollary; I like it. Again, no default disable iff

Some things weren’t fully specified so I chose what to implement. I said that when coming out of reset, we start in the terminal state. This means that pre-loading is mandatory. It would have also probably been valid to start in the full value state to be able to count down immediately. I also said that loading has priority over counting.
I also added a constraint to disallow counting from the terminal state:


no_count_when_term : assume property (
term_count |-> !count_en );

[Ben] Good that you wrote assumptions to clarify your design, and for use by formal verification.

I wrote some simple coverage points for some cases I deemed interesting:


load_0 : cover property (
load_en && data_in == 0  );
load_0_after_non_zero_count : cover property (
load_en && data_in == 0 && count_out != 0
);
decrement_to_0 : cover property (
count_out != 0 && count_en && !load_en
);

[Ben] Good idea to add coverage.

Here’s the RTL code I ended up with:

module dncounter #(WIDTH = 4) (
input bit                rst_n,
input bit                clk,
input logic              count_en,
input logic              load_en,
input logic [WIDTH-1:0]  data_in,
output logic [WIDTH-1:0] count_out,
output logic             term_count
);
always @(posedge clk or negedge rst_n)
if (rst_n == 0) begin
count_out <= 0;
end
else begin
if (load_en) begin
count_out <= data_in;
end
else if (count_en) begin
count_out <= count_out - 1;
end
end
// I implemented 'term_count' as a registered output, but it coult also be
// implemented combinatorially (with some area savings, but output delay).
always @(posedge clk or negedge rst_n)
if (rst_n == 0) begin
term_count <= 1;
end
else begin
if (load_en) begin
if (data_in == 0)
term_count <= 1;
else
term_count <= 0;
end
else if (count_en) begin
if (count_out == 1)
term_count <= 1;
else
term_count <= 0;
end
end
endmodule

I chose to implement term_count sequentially, but I could have just as well implemented it combinatorially. I would have probably saved on logic (comparators), but I would have had output delay on the signal.
Now that I think about it a bit more, maybe it would have made more sense to define that in the terminal state, even when count comes, count_out stays 0. This way an integrator can just tie the count signal to 1 and play with loading.

[Ben] Yes.
Ben’s solution

module dncounter  #(WIDTH = 4) (
  output logic[WIDTH-1:0] count_out, 
  output logic termcount, 
  input logic[WIDTH-1:0] data_in, 
  input logic ld_enb, count_enb, rst_n, clk
  );

  always_ff @ (posedge clk or negedge rst_n) 
	begin 
      if (!rst_n)       count_out <= '1;		
	  else if (ld_enb)  count_out <= data_in;
	  else if (count_enb)	
		if (count_out != 0)   count_out <= count_out - 1'b1;
	end			
	
	assign termcount = (count_out == 0) ?   1'b1 :   1'b0; 
	// assertions 
	default clocking @(posedge clk); endclocking
	default disable iff !rst_n;
	ap_load: assert property(##1 ld_enb |=> count_out==$past(data_in));  
	ap_count: assert property(##1 !ld_enb && count_enb && count_out!=0 |=> count_out==$past(count_out) - 1'b1);  
	ap_nocount: assert property(##1 !ld_enb && count_out==0 |=> $stable(count_out) );  
	ap_termcount0:  assert property(count_out !='0 |-> !termcount);  
	ap_termcount1:  assert property(count_out =='0 |-> termcount);  
	ap_reset: assert property(disable iff(1'b0) !rst_n |=> count_out=='1);  
endmodule // dncounter

Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us

  • SVA Handbook 4th Edition, 2016 ISBN 978-1518681448
  • A Pragmatic Approach to VMM Adoption 2006 ISBN 0-9705394-9-5
  • Using PSL/SUGAR for Formal and Dynamic Verification 2nd Edition, 2004, ISBN 0-9705394-6-0
  • Real Chip Design and Verification Using Verilog and VHDL, 2002 isbn 0-9705394-2-8
  • Component Design by Example ", 2001 ISBN 0-9705394-0-1
  • VHDL Coding Styles and Methodologies, 2nd Edition, 1999 ISBN 0-7923-8474-1
  • VHDL Answers to Frequently Asked Questions, 2nd Edition ISBN 0-7923-8115

In reply to Prabhu Kamalanathan:
Thanks for providing a reply with code and assertions. From my reply above, you can most likely appreicate the need for assertions, even for sch a trivial design.

Another version of RTL

...
// Assumption
// 1 -> termcount should be ZERO post power on rest 
//  	  (i.e. counter should initialized with WIDTH'b1 during reset)
// 2 -> counter should not be over-run once it's triggered termcount (even count_enb HIGH)
//      (i.e. counter should re-initialized via ld_enb (or) reset once it's triggered termcount)

[Ben] Those were also my assumptions.


  logic [WIDTH-1:0] 	dncounter;
  logic [WIDTH-1:0]  	dncounter_nxt;
  logic 		dncounter_at_zero, dncounter_at_zero_flag;

  // Output Generation
  assign count_out 	= dncounter;
  assign termcount 	= dncounter_at_zero;


  // Check whether counter reached ZERO or not
  assign dncounter_at_zero= (|dncouter) ? 1'b0 : 1'b1;
  
  // Initialize the counter when ld_enb is HIGH
  // Decrement the counter when " count_enb is HIGH and counter does not reached ZERO (i.e. after ld_enb HIGH)"
  assign dncounter_nxt  = (ld_enb) ? data_in : ((count_enb && ~dncounter_at_zero_flag) ? (dncounter - 1'b1) : dncounter); 

[Ben] You seem to have taken a “gate-level” rather than an RTL approach in your coding. I say this because you use temporary variables to compute the computational logic prior to loading it into the registers. That is more gate-level, and have done that in the past with drawing tools that picked gates and drew lines for interconnects. Actually, I would date myself if I also admit that I used velum paper and peeled primitives (FF, gates) off a sheet of pre-printed primitives. These were then pasted onto the paper, and I used a drafting ruler to make the interconnects. Do they still teach drafting in engineering school? :)

IMHO, this make the code more difficult to follow, and adds line to the code. See my code below, which is more RTL.


  always @ (posedge clk or negedge rst_n)
  begin
	if(~rst_n)
		dncounter <= WIDTH'b1;  // To avoid termcount assertion immediately after power on reset 

[Ben] WIDTH’b1 is illegal, should be '1. See code below


module top; // [Ben] 
	parameter WIDTH=4; 
	bit[WIDTH-1 : 0] a, b;   
 initial begin  
 	b= WIDTH'b1; // syntax error, unexpected BASE, expecting ';'.
 	a='1; 
   end 
endmodule  
 [your code]
	else
		dncounter <= dncounter_nxt;
  end
 
 always @ (posedge clk or negedge rst_n)
 begin
	if(~rst_n)
		dncounter_at_zero_flag <= 1'b0;		 
	else if(ld_en) // clearing the flag 
		dncounter_at_zero_flag <= 1'b0;
	else  // registering the termcount assertion. it's used along with count_enb to avoid counter over-runnig
		dncounter_at_zero_flag <= dncounter_at_zero;
 end 

 endmodule 

[/quote]
My code

module dncounter  #(WIDTH = 4) (
  output logic[WIDTH-1:0] count_out, 
  output logic termcount, 
  input logic[WIDTH-1:0] data_in, 
  input logic ld_enb, count_enb, rst_n, clk
  );

  always_ff @ (posedge clk or negedge rst_n) 
	begin 
      if (!rst_n)       count_out <= '1;		
	  else if (ld_enb)  count_out <= data_in;
	  else if (count_enb)	
		if (count_out != 0)   count_out <= count_out - 1'b1;
	end			
	
	assign termcount = (count_out == 0) ?   1'b1 :   1'b0; 
	// assertions 
	default clocking @(posedge clk); endclocking
	default disable iff !rst_n;
	ap_load: assert property(##1 ld_enb |=> count_out==$past(data_in));  
	ap_count: assert property(##1 !ld_enb && count_enb && count_out!=0 |=> count_out==$past(count_out) - 1'b1);  
	ap_nocount: assert property(##1 !ld_enb && count_out==0 |=> $stable(count_out) );  
	ap_termcount0:  assert property(count_out !='0 |-> !termcount);  
	ap_termcount1:  assert property(count_out =='0 |-> termcount);  
	ap_reset: assert property(disable iff(1'b0) !rst_n |=> count_out=='1);  
endmodule // dncounter

Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us

  • SVA Handbook 4th Edition, 2016 ISBN 978-1518681448
  • A Pragmatic Approach to VMM Adoption 2006 ISBN 0-9705394-9-5
  • Using PSL/SUGAR for Formal and Dynamic Verification 2nd Edition, 2004, ISBN 0-9705394-6-0
  • Real Chip Design and Verification Using Verilog and VHDL, 2002 isbn 0-9705394-2-8
  • Component Design by Example ", 2001 ISBN 0-9705394-0-1
  • VHDL Coding Styles and Methodologies, 2nd Edition, 1999 ISBN 0-7923-8474-1
  • VHDL Answers to Frequently Asked Questions, 2nd Edition ISBN 0-7923-8115

In reply to ben@SystemVerilog.us:

I don’t get what you mean by “no default disable iff”. W.r.t. $rose(rst_n), I’m still mulling it over. During reset, the circuit is anyway in a coma. What you care about is what values you get when coming out of reset. Even (some of) the ARM specifications specify behavior like this, e.g. what value HREADY has when coming out of reset.

One thing I noticed we were both missing is a check for “no count without count enable”.

In reply to ben@SystemVerilog.us:

Thank you very much… Ben.

In reply to Tudor Timi:

In reply to ben@SystemVerilog.us:
I don’t get what you mean by “no default disable iff”. W.r.t. $rose(rst_n), I’m still mulling it over. During reset, the circuit is anyway in a coma. What you care about is what values you get when coming out of reset. Even (some of) the ARM specifications specify behavior like this, e.g. what value HREADY has when coming out of reset.
One thing I noticed we were both missing is a check for “no count without count enable”.

[Prabhu] Yes, it’s missing
The assert property for same


ap_nocount_wo_cen: assert property(##1 !ld_enb && !count_enb && count_out!=0 |=> $stable(count_out));

(or)

ap_nocount_wo_cen: assert property(##1 !ld_enb && !count_enb && count_out!=0 |=> count_out==$past(count_out) );

In reply to Prabhu Kamalanathan:

[quote]In reply to Tudor Timi:

On the $fell(rst_n) (rst_n is active lo) I can see your point.
However, There are issues with this $fell(rst_n) assertion.
Assume for the moment that you give to a not very experienced designer the following assertion that expressed your requirements on reset:
ap_reset_fell: assert property(disable iff(1’b0) $fell(rst_n) |=> count_out=='1); The designer would then correctly, per this requirement code the following:


	ap_reset_fell: assert property(disable iff(1'b0) $fell(rst_n) |=> count_out=='1);  
  logic rst_reg=1'b1;
  always_ff @ (posedge clk) //  or negedge rst_n) 
     begin 
	rst_reg <= rst_n; // needed for the $fell 
        if (!rst_n && rst_reg)   count_out <= '1;	// $fell(rst_n)
	else if (ld_enb)         count_out <= data_in;
	else if (count_enb)	
	   if (count_out != 0)   count_out <= count_out - 1'b1;
	end
// **** HOWEVER, what I really want is 
ap_reset: assert property(disable iff(1'b0) !rst_n |=> count_out=='1); 
// BECAUSE, what if after rst_n==1'b0 for 5 cycles, and during that time, I have a ld_enb? 
// Above code would load, when I really want the count_out to be reset to zeros. 
// So the assertions would work, but the intent is failing. 

****** Thus, you do not want a $rose or $fell on the reset signal. ******

Assume for the moment that I have another requirement that states that if
(count_out==0) then with 20 cycles, count_out==0; that puts a limit on the ld_enb and count.
I could code it in one of the following ways:


ap_0to0_in20_bug: assert property(count_out==0 |-> ##[1:20] count_out==0); 
// With no default disable, that assertion would fail if I have a reset during the 
// This is because count_out is not zero because the reset that forces it back to '1. 
// To correct this, you could add the disable iff
	ap_0to0_in20: assert property(disable iff(!rst_n) 
                  count_out==0 |-> ##[1:20] count_out==0); 
// However, I prefer to use the 
default disable iff !rst_n;
// and to cancel that effect in special assertions, I set in the assertion 
// disable iff(1'b0)
ap_reset: assert property(disable iff(1'b0) !rst_n |=> count_out=='1);  

Correct! That is why assertions should be reviewed!
Ben Cohen
http://www.systemverilog.us/ ben@systemverilog.us

  • SVA Handbook 4th Edition, 2016 ISBN 978-1518681448
  • A Pragmatic Approach to VMM Adoption 2006 ISBN 0-9705394-9-5
  • Using PSL/SUGAR for Formal and Dynamic Verification 2nd Edition, 2004, ISBN 0-9705394-6-0
  • Real Chip Design and Verification Using Verilog and VHDL, 2002 isbn 0-9705394-2-8
  • Component Design by Example ", 2001 ISBN 0-9705394-0-1
  • VHDL Coding Styles and Methodologies, 2nd Edition, 1999 ISBN 0-7923-8474-1
  • VHDL Answers to Frequently Asked Questions, 2nd Edition ISBN 0-7923-8115

In reply to ben@SystemVerilog.us:
On the $fell(rst_n) (rst_n is active lo) I can see your point.
[/quote]

I think we’re talking about different things here. For an active low reset, $fell(…) is true when the reset starts being applied. $rose(…) is true when coming out of reset. My intent wasn’t to check that when going into reset some condition was true. The intent was to check what happens when coming out of reset.

Using $rose(…) would catch the bug you described.

In reply to Tudor Timi:

But you still need to test when rst_n is in the low state, whether it is at all cycles it is lo, or when fell. This is because the counter gets reset at lo.
The point is to keep the assertions simple.
If reset_is_lo → count==0
An opinion!
Ben