Verilog/SV: can assign + ternary produce latches?

pbernardi

Full Member level 2
Hello All,

Looking how a latch can be generated in Verilog/SV, normally the following case is cited:

- use of always@(*) where not all "reg" are updated (normally in cases where you have an "if" condition without a "else" condition when making combinational logic)

However, I have the feeling that the same condition can be achieved when using assign and ternary operator (?, for example:

(note: code generated by head, might be inaccurate)

Code Verilog - [expand]1
2
3
4
5
6
7
8
reg [7:0] a;

always@(*)
begin
if (res) a = 8'h00;
else if (cond1) a = op1;
else if (cond2) a = op2;
end

generates a latch, because there are conditions where "a" is not updated. This is equivalent of:

Code Verilog - [expand]1
2
3
4
5
wire [7:0] a;

assign a = res ? 8'h00 :
cond1 ? op1 :
cond2 ? op2 : a;

But it seems the first one is always cited when generating latches and second one is never cited. Also, the tooling I use at the moment (Vivado 2018.3) does not detect latches in the second case.

I am missing something, or this is a case not covered normally, in a way even some (few? most? all?) tools cannot detect?

FvM

Super Moderator
Staff member
Yes, both codes are exactly equivalent.

Why do you need latch detection for the concurrent code, the latch condition has to be written explicitly on the R.H.S., hence it's quite obvious.

ThisIsNotSam

But it seems the first one is always cited when generating latches and second one is never cited. ?
you have to realise they are very different. in the assign case, a is explicitly assigned to itself. this is not the case in the procedural assignments... and that is why students tend to overlook it.

pbernardi

Full Member level 2

In fact, I had the idea that if I avoid using always@(*), I would avoid latches - mostly because the other case is never talked about. But now I see this is not always the case.

Also it bugs me that in some cases, I make a similar replacement from case 1 (always@(*)) to case 2 (assign) in my code, and Vivado stops generating latches - it does not show the warning during synthesis and does not show the latches in the build report. But I got some cases where the latch is correctly detected, so this bug (feature?) is not always consistent.

dave_59

This is one of the reason SystemVerilog has always_comb, always_latch, always_ff - to show and check your intent

andre_teprom

andre_teprom

points: 2

Super Moderator
Staff member
Also it bugs me that in some cases, I make a similar replacement from case 1 (always@(*)) to case 2 (assign) in my code, and Vivado stops generating latches - it does not show the warning during synthesis and does not show the latches in the build report. But I got some cases where the latch is correctly detected, so this bug (feature?) is not always consistent.
This is because Vivado and previously ISE weren't always that great at language compliance. Over the years I'd seen all kinds of strange behaviors.

My recommendation is to only write code in specific proven ways that always synthesize correctly and don't diverge from that unless you've proven that a different coding style works well across multiple vendors tools. I also avoid using always@(*) a lot and anytime I use any always statement with an if statement I will write the if like this:
Code:
if () begin
end else if () begin
end else
end
and then fill in the rest of the code, makes it less likely you will forget to cover all branches.

vGoodtimes

More recently, like within the last 5-10 years, this style has started gaining traction. at least online.
Code:
always@(*) begin
a = 0; // default.  might come from some registered version of "a" like "current_a" if desired.  or some input.  or some constant
if (something) begin
a = b;
end else if (somethingelse) begin
a = c;
end
end
This style improves readability and avoids latches. Especially for always@(*) blocks with lots of outputs/cases.