1
\$\begingroup\$

I've been practicing writing some more advanced testbenches for my Verilog circuits. I thought I'd work with something simple: a double counter setup, where c0 is 3-bits long and c1 is 16-bits long. On paper, my circuit should work as follows:

c0: 0, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, ...

c1: 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, ...

I quickly made the circuit and tested it by hand, and it works as expected.

My module:

module double_counter (reset, clk, c0, c1);
     input reset, clk;
     output reg [2:0] c0;
     output reg [15:0] c1;

     always @(posedge reset or posedge clk) begin
          if (reset) begin
               c0 <= 0;
               c1 <= 2;
          end
          else if (c0 == 7) begin
               c0 <= 1;
               c1 <= c1 + 1;
          end
          else c0 <= c0 + 1;
     end

endmodule

The testbench I wanted to write would check at every positive edge of the clock if the counters were equal to some expected values. However, I'm getting a ton of errors (according to the testbench, not syntax errors) and I am not sure what I am doing wrong. Can anyone please help me understand what's going on?

My (old) attempt at a testbench:

module double_counter_tb;
     reg reset, clk;
     wire [2:0] c0;
     wire [15:0] c1;
     reg [2:0] expected_c0;
     reg [15:0] expected_c1;
     reg error;

     integer i;
     integer total_errors = 0;
     
     double_counter DUT (reset, clk, c0, c1);

     initial begin
          clk = 0;
          forever begin
               #20 clk = ~clk;
          end
     end

     initial begin
          error = 1'b0;
          reset = 0;
          #80;
          reset = 1;
          #40;
          expected_c0 = 0;
          expected_c1 = 2;
          if (expected_c0 != c0 | expected_c1 != c1) begin 
               $display("$0t, Error in reset test");
               total_errors = total_errors + 1;
               error = 1'bx;
          end
          else error = 1'b0;
          #40;
          reset = 0;
          #80;
          repeat (200) @(posedge clk) begin
               expected_c0 = expected_c0 + 1;
               if (expected_c0 == 7) begin
                    expected_c0 = 1;
                    expected_c1 = expected_c1 + 1;
               end
               if (expected_c0 != c0 | expected_c1 != c1) begin
                    $display("$0t, Error in test c0=%b, c1=%b", c0, c1);
                    total_errors = total_errors + 1;
                    error = 1'bx;
               end
               else error = 1'b0;
          end
          $display("Finished with %0d errors", total_errors);
     end

endmodule

I'm running a relatively old version of ModelSim (I think it was 5.7 since that's all I had handy) if that is of any help. Thanks in advance!

EDIT: Based on @dave_59's answer, I was able to make considerable improvements to my previous testbench. My final, working testbench, looks like the following:

module double_counter_tb;
     reg reset, clk;
     wire [2:0] c0;
     wire [15:0] c1;
     reg [2:0] expected_c0;
     reg [15:0] expected_c1;
     reg error;

     parameter fileout = "double_counter_results.txt";
     integer i, f;
     integer total_errors = 0;
     
     double_counter DUT (reset, clk, c0, c1);

     initial begin
          clk = 0;
          forever begin
               #20 clk = ~clk;
          end
     end

     initial begin
          f = $fopen(fileout, "w");
          $fwrite(f,"Beginning test...\n\n");
          error = 1'b0;
          reset = 0;
          #80;
          reset = 1;
          #10;
          expected_c0 <= 0;
          expected_c1 <= 2;
          if (expected_c0 != c0 | expected_c1 != c1) begin 
               $display("$0t, Error in reset test");
               $fwrite(f,"$0t, Error in reset test\n");
               total_errors = total_errors + 1;
               error = 1'bx;
          end
          else error = 1'b0;
          reset = 0;
          repeat (100) @(posedge clk) begin
               if (expected_c0 == 7) begin
                    expected_c0 <= 1;
                    expected_c1 <= expected_c1 + 1;
               end
               else expected_c0 <= expected_c0 + 1;
               if (expected_c0 != c0 | expected_c1 != c1) begin
                    $fwrite(f,"Error in test c0=%b c1=%b, expected c0=%b, c1=%b\n", c0, c1, expected_c0, expected_c1);
                    $display("$0t, Error in test c0=%b, c1=%b ", c0, c1);
                    $display("$0t, expected c0=%b, c1=%b", expected_c0, expected_c1);
                    total_errors <= total_errors + 1;
                    error <= 1'bx;
               end
               else error <= 1'b0;
          end
          $display("Finished with %0d errors", total_errors);
          $fwrite(f, "Finished with %0d errors", total_errors);
          $fclose(f);
          $finish;
     end

endmodule
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

A couple of problems.

You are using nonblcoking assigments in your DUT, but blocking assignments in your testbench. When you do the comparison, the DUT has not updated its values yet.

You wait too many clock cycles after reset.

This works for me

 initial begin
       $dumpfile("dump.vcd"); $dumpvars;
          error = 1'b0;
          reset = 0;
          #80;
          reset = 1;
          #10;
          expected_c0 = 0;
          expected_c1 = 2;
          if (expected_c0 != c0 | expected_c1 != c1) begin 
               $display("$0t, Error in reset test");
               total_errors = total_errors + 1;
               error = 1'bx;
          end
          else error = 1'b0;
          reset = 0;
       repeat (100) @(negedge clk) begin
               if (expected_c0 == 7) begin
                    expected_c0 = 1;
                    expected_c1 = expected_c1 + 1;
               end else                expected_c0 = expected_c0 + 1;

               if (expected_c0 != c0 | expected_c1 != c1) begin
                 $write("$0t, Error in test c0=%b, c1=%b ", c0, c1);
                 $display("$0t, expected c0=%b, c1=%b", expected_c0, expected_c1);

                    total_errors = total_errors + 1;
                    error = 1'bx;
               end
               else error = 1'b0;
          end
          $display("Finished with %0d errors", total_errors);
       $finish;
     end
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Awesome, thanks! It works fine now. I took a look at the things you mentioned, and it definitely makes sense and I'm not sure how I missed it when first writing it. I'll add my new implementation (heavily based on yours) in an edit on my question. If you don't mind me asking though, what's the use for the $dumpfile and $dumpvars exactly? I've never used these functions until now and I was wondering what I can do with the .vcd file it creates \$\endgroup\$
    – aofarmakis
    Commented Feb 19 at 3:56
  • 1
    \$\begingroup\$ For waveform viewing on edaplayground.com/x/DcLB \$\endgroup\$
    – dave_59
    Commented Feb 19 at 5:19
-1
\$\begingroup\$

It looks like you're using bitwise "OR" instead of logical or:

Instead of:

if (expected_c0 != c0 | expected_c1 != c1) begin

Try:

if ((expected_c0 != c0) || (expected_c1 != c1)) begin

in both places.

\$\endgroup\$
4
  • \$\begingroup\$ This answer is incorrect. It makes no difference which operator is used in this case. You should prove this to yourself by running a Verilog simulation. \$\endgroup\$
    – toolic
    Commented Feb 19 at 11:51
  • \$\begingroup\$ @toolic -- While you are technically correct, the code is bad code, because it accidentally works, and relies on this particular implementation, and is not guaranteed to work on every architecture. This fails the visual inspection test, and should never be tolerated just because it happens to work. The prime technical imperative of software engineering is the management of complexity, and with complexity always exponentiating, we must use every tool in our toolbox. I stand by my answer, though you are technically correct. \$\endgroup\$ Commented Feb 19 at 12:08
  • \$\begingroup\$ @toolic -- I am still learning SystemVerilog, so I don't know the answer to the question I'm about to ask you. Is bitwise-or of two logical expressions (as done here) considered good SystemVerilog code? Thanks. \$\endgroup\$ Commented Feb 19 at 12:17
  • 1
    \$\begingroup\$ @MicroservicesOnDDD probably needs a seperate question, but my suggestion is always use logical operators unless you need a multi-bit result. \$\endgroup\$
    – dave_59
    Commented Feb 19 at 19:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.