always block not always triggering at event

60 views Asked by At

I am using Xilinx Vivado 2023.2 (on Windows 11). The following code is supposed to calculate the peak-peak and mean value of an incoming signal. For unknown reason, the Vivado simulator skips the last two always blocks (of Peak_Mean.sv) sometimes (peak_reg and mean_reg).

In this small setup the result.txt is the same as ref.txt. Nevertheless, the module is a small part of a bigger project, and there this behavior is not tolerated.

Peak_Mean.sv

`timescale 1ns / 1ps

module Peak_Mean #
(
   parameter SYSTEM_FREQUENCY = 100000000, // [Hz]
   parameter frequency = 1000 // [Hz] // max. SYSTEM_FREQUENCY/duty_window // Note: SYSTEM_FREQUENCY/frequency should be an integer to ensure expected behavior
)
(
   input clk,
   input nReset,
   
   input enable,
   input signed [ 17 : 0 ] in_signal,
   
   output [ 17 : 0 ] peak_peak, // no sign bit (always positive)
   output signed [ 17 : 0 ] mean
);

   localparam sample = SYSTEM_FREQUENCY/frequency;
      
   reg [ $clog2(sample)-1 : 0 ] counter_freq;
   always@( posedge clk ) begin
      if( !nReset || (counter_freq == sample-1) ) begin
         counter_freq <= 0;
      end else begin
         ++counter_freq;
      end
   end
   
   reg signed [ 17 : 0 ] max_reg;
   always@( posedge clk ) begin
      if( !nReset || (counter_freq == sample-2) ) begin
         max_reg <= 18'b100000000000000000;
      end else begin
         if( enable ) begin
            if( !in_signal[17] ) begin
               if( (in_signal > max_reg) || max_reg[17] ) begin
                  max_reg <= in_signal;
               end
            end else begin
               if( ((~in_signal+1) < (~max_reg+1)) && max_reg[17] ) begin
                  max_reg <= in_signal;
               end
            end
         end
      end
   end   
   
   reg signed [ 17 : 0 ] min_reg;
   always@( posedge clk ) begin
      if( !nReset || (counter_freq == sample-2) ) begin
         min_reg <= 18'b011111111111111111;
      end else begin
         if( enable ) begin
            if( !in_signal[17] ) begin
               if( (in_signal < min_reg) && !min_reg[17] ) begin
                  min_reg <= in_signal;
               end
            end else begin
               if( ((~in_signal+1) > (~min_reg+1)) || !min_reg[17] ) begin
                  min_reg <= in_signal;
               end
            end
         end
      end
   end
   
   reg [ 17 : 0 ] peak_reg;
   always@( posedge clk ) begin
      if( !nReset ) begin
         peak_reg <= 0;
      end else begin
         if( counter_freq == sample-3 ) begin
            peak_reg <= max_reg - min_reg;
         end
      end
   end
   
   reg signed [ 18 : 0 ] mean_reg;
   always@( posedge clk ) begin
      if( !nReset ) begin
         mean_reg <= 0;
      end else begin
         if( counter_freq == sample-3 ) begin
            mean_reg <= (max_reg + min_reg) >>> 1;
         end
      end
   end
      
   assign peak_peak = peak_reg;
   assign mean = mean_reg;

endmodule

tb_Peak_Mean.sv (Note: signal.hex and result.txt path)

`timescale 1ns / 1ps

module tb_Peak_Mean();
   
   localparam TEST_LEN = 32;
   
   localparam SYSTEM_FREQUENCY = 100000000;
   localparam frequency = 1000;
   
   localparam sample = SYSTEM_FREQUENCY/frequency;

   // system
   reg clk;
   reg nReset;
   
   // stimuli
   reg [ $clog2(sample)-1 : 0 ] sample_counter;
   reg [ $clog2(TEST_LEN) : 0 ] counter_tb_state;
   
   reg signed [ 17 : 0 ] testvec_in_signal [ 0 : TEST_LEN-1 ];
   
   // dut io
   reg enable;   
   wire signed [ 17 : 0 ] in_signal = testvec_in_signal[ counter_tb_state ];
   
   wire [ 17 : 0 ] peak_peak;
   wire signed [ 17 : 0 ] mean;
   
   // dump file
   integer f;
   
   // DUT
   Peak_Mean #
   (      
      .SYSTEM_FREQUENCY(SYSTEM_FREQUENCY),
      .frequency(frequency)
   ) Peak_Mean_inst (
      .clk(clk),
      .nReset(nReset),
      
      .enable(enable),
      .in_signal(in_signal),
      
      .peak_peak(peak_peak),
      .mean(mean)
   );
   
   // load stimuli
   initial begin
      $readmemh( "signal.hex", testvec_in_signal );
   end
   
   // generate clock
   always begin
      clk = 1;
      forever #5 clk = ~clk; // 100MHz
   end
   
   always@( posedge clk ) begin
      if( !nReset || (sample_counter == sample-1) ) begin
         sample_counter <= 0;
      end else begin
         ++sample_counter;
      end
   end
   
   // testbench   
   always@( posedge clk ) begin
      if( !nReset ) begin
         enable <= 0;
      end else begin
         case( sample_counter )
            0.05*sample-1:  enable <= 1;
            0.25*sample-1:  enable <= 1;
            0.45*sample-1:  enable <= 1;
            0.65*sample-1:  enable <= 1;
            0.85*sample-1:  enable <= 1;
            
            default: enable <= 0;
         endcase
      end
   end
   
   always@( posedge clk ) begin
      if( !nReset ) begin
         counter_tb_state <= 0;
      end else begin
         if( enable ) begin
            ++counter_tb_state;
            if( counter_tb_state >= TEST_LEN ) begin
               $fclose( f );
               $finish;
            end
         end
      end
   end
   
   initial begin
      nReset <= 0;
      repeat(4)@( posedge clk );
      nReset <= 1;
      f = $fopen( "result.txt", "w" );
      $fwrite( f, "Peak_Peak   Mean\n" );
   end
      
   always@( * ) begin
      $fwrite( f, "  %h     %h\n", peak_peak, mean );
   end
      
endmodule

signal.hex

00000
00000
00000
00000
00000
FFFFF
FFFFF
FFFFF
FFFFF
FFFFF
00000
00F00
200F0
00000
00000
00AAA
1FFFF
77777
20000
08855
2AAAA
3BBBB
24444
27890
35467
10045
03000
09876
14597
19455
00000
00000

ref.txt (see result.txt)

Peak_Peak   Mean
  00000     00000
  00000     3ffff
  20e10     307f8
  3ffff     3ffff
  17777     2ffff
  16455     0e22a

I set a breakpoint on every always@( posedge clk ). According to my understanding, each always block should trigger once on a positive clock edge. Strangely, this is not the case here (or at least on my system).

In the first few cycles, a positive clock triggers each always blocks. Then at cycle 5 and 6, a positive clock edge does not trigger the last two always blocks (peak_reg and mean_reg). Afterwards, a positive clock edge triggers each always block again.

The suspension of the last two blocks becomes more serious in the larger project. There the last two blocks are triggered very rarely, which leads to incorrect behavior.

Am I understanding something wrong? Is there a mistake in my code?

Is there a setting (which I cannot find) in Vivado which forces it to always trigger always blocks at events, in case they are optimized in the simulation for some reason? Is this a Vivado bug?

EDIT 1: If I add a usless register to the last two always blocks (see code below), they are triggerd and change the value of peak_reg and mean_reg.

   reg useless_peak_reg;
   reg [ 17 : 0 ] peak_reg;
   always@( posedge clk ) begin
      if( !nReset ) begin
         useless_peak_reg <= 0;
         peak_reg <= 0;
      end else begin
         useless_peak_reg <= useless_peak_reg + 1;
         if( counter_freq == sample-3 ) begin
            peak_reg <= max_reg - min_reg;
         end
      end
   end

Nevertheless, the assign statements at the end of the file are not updating the associated outputs.

EDIT 2: My current workaround is to define the outputs as registers and replace the always blocks. e.g.:

   always@( posedge clk ) begin
      if( !nReset ) begin
         peak_peak <= 0;
      end else begin
         if( counter_freq == sample-3 ) begin
            peak_peak <= max_reg - min_reg;
         end
      end
   end

I still don't understand why everything behaves like this.

1

There are 1 answers

3
toolic On

This line:

     ++counter_freq;

behaves like this:

     counter_freq = counter_freq + 1;

In other words, the auto-increment operator (++) is a blocking assignment.

However, since you are trying to describe sequential logic, you should use a nonblocking assignment (<=). Change the code to this:

     counter_freq <= counter_freq + 1;

You used ++ in 2 other places. They should be changed as well.

When I make the change, I get a different result.txt file. However, I did not use Vivado to run my simulation.

Refer to IEEE Std 1800-2017, section 11.4.2 Increment and decrement operators:

These increment and decrement assignment operators behave as blocking assignments.