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.
This line:
behaves like this:
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:You used
++in 2 other places. They should be changed as well.When I make the change, I get a different
result.txtfile. However, I did not use Vivado to run my simulation.Refer to IEEE Std 1800-2017, section 11.4.2 Increment and decrement operators: