Both are just different ways to express the same intent; there's no difference between how they'd behave.
I prefer the variable approach because it allows the assignment to be broken down into clear, individual steps:
- convert from integer type to vector
- replace some bits in the vector with the new value
- convert the new, updated vector back into an integer
In this example, the type conversion doesn't actually require any physical hardware - but it could.
My personal favourite use of variables in VHDL was to implement a CRC algorithm. The classical description of how to compute a CRC involves shifting the data a bit at a time into a shift register, and performing an XOR involving various taps (and the new data bit) to work out the value of the next bit to shift in.
This is all well and good, but it's not really much help if new data is available a byte at a time.
There's plenty of example code showing how to implement a byte-wide CRC, but it's not particularly readable. Big tables of magic numbers start appearing, and to someone familiar with the bit-at-a-time approach, the algorithm isn't immediately recognisable.
So, a nice trick to do in VHDL is to use variables, and to let the compiler do the hard work of figuring out how to deal with a whole byte at a time, given an algorithm that operates on a single bit at a time. Something like:
SIGNAL crc : STD_LOGIC_VECTOR (31 DOWNTO 0);
CONSTANT poly : STD_LOGIC_VECTOR (31 DOWNTO 0) := "10001...."; -- generator polynomial
SIGNAL new : STD_LOGIC_VECTOR (7 DOWNTO 0);
VARIABLE v_new_crc : STD_LOGIC_VECTOR (31 DOWNTO 0);
VARIABLE v_new_bit : STD_LOGIC;
IF clk'event AND clk = '1' THEN
v_new_crc := crc;
FOR i IN 0 TO 7 LOOP
v_new_bit := new (i); -- pick out the next bit from our new data byte
-- Step through the whole of the generator polynomial to find out where the taps are
-- If there's a tap in a given position, XOR the new data bit with the shift reg contents
FOR j IN 0 TO 31 LOOP
IF poly (j) = '1' THEN
v_new_bit := v_new_bit XOR v_new_crc (j);
END IF;
END IF;
v_new_crc (31 DOWNTO 1) := v_new_crc (30 DOWNTO 0);
v_new_crc (0) := v_new_bit;
END LOOP;
crc <= v_new_crc;
END IF;
I may have the details of the algorithm wrong, but I think as a way to illustrate the use of variables, it's still valid. What this code describes is a whole bunch of operations to be carried out on each clock edge, and the key thing to note is that on each edge there's exactly one assignment of a new value to a signal - namely, the update of signal 'crc' right at the end.
The new value of 'crc' is computed by following a sequence of steps incorporating a nested loop, and if it were being executed on a microprocessor core, it would be quite inefficient. But in VHDL, the meaning of that nested loop is evaluated at compile time, and the compiler's job is to work out a way to make the FPGA's hardware deliver the correct net end result.
Nothing in the FPGA actually iterates here. There's no state machine, and no sequence of steps that are actually executed one after the other.... just a single 32-bit value which is updated once per clock edge, based on the value of a data byte.