# always_ff is always executed earlier than always_comb in ModelSim

I have an exercise that separating comb logic from sequential logic in always_ff block.

However, I found that the ordering of always_comb and always_ff executions is different between different simulators.

As far as I know, always_comb block is executed whenever the variable in sensitivity list changed. (Please correct me if I am wrong) and always_ff is executed at signal edge (e.g. clk).

From the programming prospective, the execution ordering of always_comb and always_ff is important, right? We always assign a variable in non-blocking way inside flip flop from a variable which is driven by always_comb?

out_pkt_size <= pkt_size; // pkt_size is drive by always_comb


You can checkout the code from edaplayground

always_comb begin
$display("%2g, always_comb : in_vld = %b",$time, in_vld);
if (in_vld) begin
pkt[HIBIT-:WIDTH] = in_data;
pkt_size = {<<8{pkt[HIBIT-:16]}};
end else begin
pkt = {WIDTH{1'hx}};
pkt_size = 'd0;
end
end

always_ff @(posedge clk or negedge reset_n) begin
$strobe( "%2g, always_ff(strobe) : out_pkt_size = %1d",$time, out_pkt_size);
$display("%2g, always_ff : in_vld = %b",$time, in_vld);
if (!reset_n) begin
out_pkt_size <= 1'd0;
end else begin
out_pkt_size <= pkt_size;
end
end


Run result from edaplayground (using Synopsys VCS as simulator):

You can see that always_comb is executed earlier than the always_ff.

initial ...
0, always_ff         : in_vld       = x
0, always_comb       : in_vld       = x
0, always_ff(strobe) : out_pkt_size = 0
10, always_comb       : in_vld       = 0
10, always_ff         : in_vld       = 0
10, always_ff(strobe) : out_pkt_size = 0
20, always_comb       : in_vld       = 1
20, always_ff         : in_vld       = 1
20, always_ff(strobe) : out_pkt_size = 140
30, always_comb       : in_vld       = 0
30, always_ff         : in_vld       = 0
30, always_ff(strobe) : out_pkt_size = 0
40, always_ff         : in_vld       = 0
40, always_ff(strobe) : out_pkt_size = 0
50, always_ff         : in_vld       = 0
50, always_ff(strobe) : out_pkt_size = 0
$finish called from file "testbench.sv", line 96.$finish at simulation time                   55


Result from ModelSim:

You can see that, however, the always_ff is always executed earlier than the always_comb. It results the out_pkt_size is 1 clock cycle behind.

run
# initial ...
#  0, always_comb       : in_vld       = x
#  0, always_ff         : in_vld       = x
#  0, always_ff(strobe) : out_pkt_size = 0
# 10, always_ff         : in_vld       = 0
# 10, always_comb       : in_vld       = 0
# 10, always_ff(strobe) : out_pkt_size = 0
# 20, always_ff         : in_vld       = 1
# 20, always_comb       : in_vld       = 1
# 20, always_ff(strobe) : out_pkt_size = 0
# 30, always_ff         : in_vld       = 0
# 30, always_comb       : in_vld       = 0
# 30, always_ff(strobe) : out_pkt_size = 140
# 40, always_ff         : in_vld       = 0
# 40, always_ff(strobe) : out_pkt_size = 0
# 50, always_ff         : in_vld       = 0

run

• That's the point with hardware description languages -- the correct functioning of your code should not depend on the order of execution of blocks in a simulator. When you build the actual hardware, everything happens in parallel anyway! – Dave Tweed Aug 4 '18 at 10:59
• The order of execution of different 'always' blocks is not defined, that's why you must use nonblocking assignments for flip-flops to get the desired behavior. You must stop thinking about hardware design from a "programming perspective". – Elliot Alderson Aug 4 '18 at 13:35

The difference in ordering between your always_ff and always_comb is a result of different assignment ordering between from your initial and always blocks.
Your initial block uses a #10 delay to make assignments to inputs sensitive in the always_comb block. That coincides with the assignment clk=~clk in the always block. So you have a race condition with the timing of the always_ff's positive edge of clk and the inputs to the always_comb
You need to modify the timing of initial block assignment to not be in a race by using non-blocking assignments, or using delays that put the assignments off from the posedge of the clock.