2
\$\begingroup\$

I'm having some trouble wrapping my head around this simulation waveform output. It reproduces in both Verilator and Icarus Verilog, so I'm assuming I'm just not understanding something about Verilog. I've boiled it down to a simple module:

`default_nettype none

module strobe (
  input wire        clk,
  input wire        reset,
  input wire        strobe_in,

  output reg [3:0]  counter_1,
  output reg [3:0]  counter_2
);
  
  // Count clock ticks for simulation
  reg [7:0] clk_count;
  reg       strobe_in_q;
  
  always @(posedge clk) begin
    if (reset) begin
      clk_count <= 0;
    end else begin
      clk_count <= clk_count + 1;
    end
  end

  always @(posedge clk) begin
    if (reset) begin
      strobe_in_q <= 0;
    end else begin
      strobe_in_q <= strobe_in;
    end
  end

  always @(posedge clk) begin
    if (reset) begin
      counter_1 <= 0;
    end else if (strobe_in) begin
      counter_1 <= counter_1 + 1;
    end
  end

  always @(posedge clk) begin
    if (reset) begin
      counter_2 <= 0;
    end else if (strobe_in_q) begin
      counter_2 <= counter_2 + 1;
    end
  end

endmodule

And here's a screenshot of the test bench simulation:

enter image description here

My question is why does strobe_in_q pulse at clock 06 instead of 07? counter_2 seems to behave as if it pulsed at 07. And that's what I would expect by delaying strobe_in by 1 clock cycle. Is this just a quirk of simulation? Or am I misunderstanding something fundamental about Verilog here? Thanks for any help!

Here's an EDA Playground link also showing this.

Edit: Putting it in one always block generates the same results:

module strobe (
  input wire  clk,
  input wire  reset,
  input wire  strobe_in,
  
  output reg [3:0]  counter_1,
  output reg [3:0]  counter_2
);
  
  // Count clock ticks for simulation
  reg [7:0] clk_count;
  reg       strobe_in_q;
  
  always @(posedge clk) begin
    if (reset) begin
      clk_count <= 0;
      strobe_in_q <= 0;
      counter_1 <= 0;
      counter_2 <= 0;
    end else begin
      clk_count <= clk_count + 1;
      strobe_in_q <= strobe_in;
      
      if (strobe_in) begin
        counter_1 <= counter_1 + 1;
      end
      
      if (strobe_in_q) begin
        counter_2 <= counter_2 + 1;
      end
    end
  end

endmodule

EDA Playground for one always block.

Edit: Here's the test bench:

`timescale 1ns/100ps
`default_nettype none

module strobe_tb();

  // Make a regular pulsing clock
  reg clk = 0;
  always #1 clk = !clk;

  reg reset = 0;
  reg strobe_in = 0;

  strobe UUT (
    .clk(clk),
    .reset(reset),
    .strobe_in(strobe_in),
    .counter_1(),
    .counter_2()
  );

  initial begin
    $dumpfile("strobe.vcd");
    $dumpvars(0);

    # 1 reset = 1;
    # 2 reset = 0;
    # 10 strobe_in = 1;
    # 2 strobe_in = 0;
    # 10 $finish;
  end

endmodule
\$\endgroup\$
5
  • \$\begingroup\$ Why do you have four separate always @(posedge clk) blocks? You should only need one? \$\endgroup\$
    – Shamtam
    Oct 2, 2021 at 3:32
  • \$\begingroup\$ Thought it might be simpler to split it out. The results are the same in one always block. \$\endgroup\$ Oct 2, 2021 at 3:39
  • \$\begingroup\$ Next question: Why are your signals (reset, strobe_in) lined up with your clock? Try shifting them so their edges don't line up with the positive edges of the clock. That may give you an idea of what's happening. \$\endgroup\$
    – Shamtam
    Oct 2, 2021 at 3:41
  • \$\begingroup\$ If those signals were coming from another module, wouldn't they be lined up on the positive edges? Should the test bench line the up with the negative edges? \$\endgroup\$ Oct 2, 2021 at 3:48
  • 1
    \$\begingroup\$ Your original code with separate always blocks is much simpler to read and understand. It is a matter of preference, not functionality. \$\endgroup\$
    – toolic
    Oct 2, 2021 at 11:35

3 Answers 3

3
\$\begingroup\$

The assumption is that your input signals (reset and strobe_in) are synchronous to the clock. You should drive those signals in your testbench similarly to how they are driven in the design. Use @(posedge clk) instead of using # delays, and use nonblocking assignments (<=) instead of blocking (=).

A clean way to do this is to use repeat loops to count clock cycles because they are more meaningful than the delays themselves. This is the first tiny step towards a transaction-based verification approach.

module strobe_tb();

  // Make a regular pulsing clock
  reg clk = 0;
  always #1 clk = !clk;

  reg reset;
  reg strobe_in;

  strobe UUT (
    .clk(clk),
    .reset(reset),
    .strobe_in(strobe_in),
    .counter_1(),
    .counter_2()
  );

initial begin
    $dumpfile("strobe.vcd");
    $dumpvars(0);

    // Initialize inputs at time 0
    reset     <= 1; // Assert reset
    strobe_in <= 0;

    repeat (2) @(posedge clk);
    reset <= 0; // Release reset

    repeat (5) @(posedge clk);
    strobe_in <= 1;

    repeat (1) @(posedge clk);
    strobe_in <= 0;

    repeat (5) @(posedge clk);
    $finish;
end

endmodule

enter image description here

\$\endgroup\$
2
  • \$\begingroup\$ Using nonblocking assignments <= when setting strobe_in in the testbench looks to fix the issue. What exactly is the difference between repeat (n) @(posedge clk) and # n ? Using repeat looks a little less error prone, since you cannot accidentally delay a half clock cycle. Are there other benefits? \$\endgroup\$ Oct 2, 2021 at 20:49
  • \$\begingroup\$ @DaveDribin: Yes, less error prone. The other advantage is that you set the clock period once in your code using #1, then everything is relative to that period. If you need to change the clock period for any reason, you just need to change the one line, instead of changing every line that has a #. Also, repeat (5) @(posedge clk) makes the intention of your code very clear: you are delaying 5 clock cycles. On the other hand, #10 means that you have to do a calculation in your head... "I know 1 is half the clock period, so the period is 2, and 10/2 is 5, so I have 5 clock periods". \$\endgroup\$
    – toolic
    Oct 2, 2021 at 21:23
2
\$\begingroup\$

If you are using blocking assignments, it's better to trigger the stimulus in the negedges to avoid race conditions at rising edge. For that purpose, you can change your clocking:

reg clk = 1;
always #1 clk = !clk;

Otherwise, in your current code, there is a race between different procedural blocks at instant #10. The events are:

  1. The stimulus strobe_in changes immediately to '1' in the procedural block of test bench at #10.
  2. Clocking block of test bench drives clk to '1' i.e., drives the rising edge of clk at #10.
  3. Reading of strobe_in occurs inside the always block of design at the rising edge at #10.

So there is a race scenario here on how Simulator should behave i.e., which value should actually be sampled to strobe_in_q. It depends on the order of execution of the above procedural blocks chosen by Simulator.

In some tools like ModelSim/QuestaSim, following work-around works, but may not in other tools:

#10 @(posedge clk) strobe_in = 1'b1 ;

Here, strobe_in is driven just after the event of posedge at #10 happened. But race still exists between the above write/assignment to strobe_in and the always block in the design which is reading this value at the same rising edge.

So, a sensible solution will be to use non-blocking assignment to avoid immediate assignment to strobe_in and thus schedule it to be read only in the next clock edge.

This should work on any simulator. And it's consistent with the actual design, where strobe_in would be a signal driven synchronous with clk:

#10 strobe_in <= 1'b1 ;
\$\endgroup\$
1
\$\begingroup\$

Blocking statements are being used in the testbench for strobe_in. So strobe_in_q gets the updated value during counter=6 itself. In non-blocking statements, all the variables in the LHS are evaluated in parallel just like in a real circuit.

Try something like this

# 1 reset = 1;
# 2 reset = 0;
# 10 strobe_in <= 1;
# 2 strobe_in <= 0;
# 10 $finish;

If strobe_in is assumed to be coming from a flop's output, it better to use the non-blocking statements. You can play around by changing blocking/non-blocking to get an idea of whats happening.

\$\endgroup\$

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.