You also get latches when you don't fully flesh out if/else or case
structures. This is a common problem I see all the time. For example:
process(A,B,C)
begin
if( B = '1')
A <= '1';
elsif( C = '1' )
A <= '0';
end if;
end process;
What happens if both B and C are '0'? A must retain the last state, so
you have essentially created a latch. I find the best way to avoid
creating UNINTENTIONAL latches is to always create a default condition.
Using the above example:
process(A,B,C)
begin
A <= '0'; -- insert a default condition, so that the signal gets
updated everytime the process is called.
if( B = '1')
A <= '1';
elsif( C = '1' )
A <= '0';
end if;
end process;
This is a simple case, but it illustrates the idea of always assigning
a signal a value during every pass to avoid latches. As a general rule,
I write the following instead of using 'else'.
process( <input signals> )
begin
Output <= <default_value>;
if( <condition> )then
Output <= <alternate_value>;
end if;
end process;
For registers (D flip-flops), I always include as the very first line D
<= Q, and use the two-process model. For example:
process( reset_n, clk )
begin
if( reset_n = '0' )then
A_q <= '0'';
elsif( rising_edge(clk) )then
A_q <= A_d;
end if;
end process;
process(A_q,B,C)
begin
A_d <= A_q; -- insert as the default condition d <= q.
if( B = '1')
A_d <= '1';
elsif( C = '1' )
A_d <= '0';
end if;
end process;
This virtually guarantees that you get the D flip-flop and
combinational logic you are expecting. I generally recommend the two
process model for synchronous designs anyway, because it enforces the
idea of the clock explicitly. It also makes the demarcation between
combinational and registered logic clear. Of course, for me it isn't
generally optional. The company I work for requires the "two-process"
model for all code written, and requires an explanation if you don't
use it.
Lastly, almost any circumstance where you might consider a latch, a D
FF can be used instead. For example, I am working with a legacy
application where the clock isn't fast enough to reliably sample
"short" pulses. In this case, we use the input to clock a FF, which
toggles between high and low. (d <= not q) The output is fed into an
XOR type edge detector, which fires a single clock pulse every time the
input flop changes state. This allows us to reliably detect short
pulses that we might miss with a traditional sampling scheme - but
still avoids the pitfalls of a traditional SR latch.