Combinational logic "IF" and "assign" statement in systemverilog

Shuaiyu Jiang picture Shuaiyu Jiang · Feb 27, 2015 · Viewed 10.1k times · Source

I found a very strange behaviour when design my ALU, hope someone can have a look it and tell me what is going on.

Here is the code

module adder (
output logic signed[31:0] y,
output logic Cout,
input logic signed[31:0] a, b,
input logic Cin, sub
);

logic [31:0] adder_b;

assign adder_b = b ^ {32{sub}};
assign {Cout, y} = {a[31],a} + {adder_b[31],adder_b} +Cin;

endmodule

////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////



module andlogic (
output logic [31:0] y,
input logic [31:0] a, b
);

assign y = a & b;

endmodule



////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////

module orlogic (
output logic [31:0] y,
input logic [31:0] a, b
);

assign y = a | b;

endmodule

////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////


module xorlogic (
output logic [31:0] y,
input logic [31:0] a, b
);

assign y = a ^ b;

endmodule


///////////////////////////////////////////////////
///////////////////////////////////////////////////
///////////////////////////////////////////////////

module ALU(
output logic signed[31:0] Result,
output logic N,Z,C,V,
input logic signed[31:0] a, b,
input logic [2:0] ALU_control
);

wire [31:0] adder_rlt, and_rlt, or_rlt, xor_rlt;
logic Cin;


adder adder (
        .y (adder_rlt),
    .a (a),
    .b (b),
    .Cin (Cin),
    .Cout (Cout),
    .sub (sub)
);

andlogic andlogic (
        .y (and_rlt),
    .a (a),
    .b (b)
);

orlogic orlogic (
        .y (or_rlt),
    .a (a),
    .b (b)
);

xorlogic xorlogic (
        .y (xor_rlt),
    .a (a),
    .b (b)
);


assign C = Cout;
assign sub = ALU_control[1];
assign Cin = ALU_control[1];
assign N = Result[31];
//assign Z = (Result ==0 )? 1:0;
assign V = {{~a[31]} & {~b[31]} & Result[31]}|{a[31] & b[31] & {~Result[31]}};

always_comb
begin 

 if (Result == 0)   Z = 1;
 else   Z = 0;

 case(ALU_control)

 3'b001:  Result = adder_rlt;

 3'b010:  Result = adder_rlt;

 3'b011:  Result = and_rlt;

 3'b100:  Result = or_rlt;

 3'b101:  Result = xor_rlt;

 default: Result = 0;

 endcase

end

endmodule   

The first 4 modules are individual functions of my ALU, the adder contains both addition and subtraction. Then here is the odd thing:

My ALU has 4 flags, Z represent Zero, it sets when the value of output Result is 0. If I use these code to describe the behaviour of Z

always_comb
begin 

 if (Result == 0)   Z = 1;
 else   Z = 0;

The simulation result is wrong, Z some time is 1, and some time is 0, and it looks like not depend on the value of Result at all.

What is more odd is the result of the synthesis result. Here the picture shows a part of my synplify synthesis result.

Synthesis result

The gate level looks like correct, Z is a AND gate of all inverted Result signals, which when Result == 0, the output Z should be 1.

However, I spend all my afternoon yesterday try to figure out how to fix this bug, I fount that if I use assign statement instead of using if statement, then the simulation gives correct behaviour. assign Z = (Result ==0 )? 1:0;

I thought this two version of describing Z should be same! After I modified my code by using

assign Z = (Result ==0 )? 1:0;

The synthesis result still same as the picture I showed above...

Can someone tell me what is going on? Thanks so much!!!

Answer

Greg picture Greg · Feb 27, 2015

I believe the issue is with your order of operation. always_comb blocks execute procedurally, top to bottom. In simulation, Z is updated first with the existing value of Result (from the previous time the always block was executed). The Result is updated and Z is not re-evaluated. Result is not part of the sensitivity list because it is a left-hand value. Therefore, Z will not get updated until a signal that assigns Result changes.

Synthesis is different, it connects equivalent logic gates which may result in asynchronous feedback. Logical equivalent is not the same as functional equivalent. This is why synthesis is giving you what you logically intend and RTL simulation is giving what you functionally wrote. Explaining the reasons for the differences is outside the scope of this question.

When coding a RTL combinational block, just do a little self check and ask yourself:

  • Are the values at the end of a single pass of this always-block the intended final values? If no, rearrange your code.
  • Will any current values in this always-block be used assign any values in the next pass? If yes, rearrange your code or synchronize with a flip-flop.

In ALU case, tt is an easy fix. Change:

always_comb begin
  if (Result==0) Z = 1;  // <-- Z is evaluated using the previous value of Result
  else Z = 0;

  /*Logic to assign Result*/ // <-- change to Result does not re-trigger the always 
end

To:

always_comb begin
  /*Logic to assign Result*/  // <-- update Result first

  if (Result==0) Z = 1; // <-- Z is evaluated using the final value of Result
  else Z = 0;
end

An alternative solution (which I highly discourage) is to replace the always_comb with The IEEE1364-1995 style of combinational logic. Where the sensitivity list is manually defined. Here you can add Result to get the feed back update:

always @(ALU_control or adder_rlt or add_rlt or or_rtl or xor_rtl or Result)

I highly discourage because it easy to miss a necessary signal, unessary signals waste simulation time, creates a risk of zero time infinite loop, and you still don't get a guaranty functional equivalent between RTL and synthesis.