In Logic Design courses we all learned that it is possible to minimize a logic function, for example by using a Karnaugh map or the Quine–McCluskey algorithm. We also learned that "Don't Care" values increase the minimization potential.
For example take a register file. The write_address
and write_data
signals don't really matter when the write_enable
signal is '0'
. Thus, they should be assigned a "Don't Care" value to allow more optimizations in the logic that is driving these signals (i.e. not in the register file itself).
What is the correct way to specify such "Don't Care" values in VHDL in order to allow the synthesis tool more room for possible optimizations?
So far I've found the following things which might be suitable. But I'm not really sure what the pros and cons of each approach are:
- Simply not assigning the signal. This seems like it could work. However I found that it doesn't work when you want to define a "do nothing constant" of some
record
type, since record constants need to be fully specified (at least Modelsim tells me so). - The
std_logic_1164
package defines the value'-' -- Don't care
forstd_ulogic
. This looks like it is the semantically correct choice for an explicit "don't care", but I've never seen it used anywhere (except in the unrelated VHDL-2008case?
constructs). - Modelsim uses the value
'X'
to display undefined signals. However I'm not sure if synthesis tools understand an explicit'X'
-assignment as "don't care".
Here's an oversimplified code snippet for clarification, where I've initialized the don't care signals with '-'
.
As you can see, the signal control.reg_write_address
can have 3 different values: "----"
, instruction(11 downto 8);
and instruction(3 downto 0);
. Now I'd expect this to be synthesized to a 2-input multiplexer if '-'
is interpreted as "don't care". Had I initialized the signal with (others => '0')
instead of '-'
, the tool would have to generate a 3-input multiplexer instead.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package mytypes is
type control_signals_t is record
write_enable : std_logic;
write_address : std_ulogic_vector(3 downto 0);
read_address : std_ulogic_vector(3 downto 0);
end record;
-- All members of this constant must be fully specified.
-- So it's not possible to simply not assign a value.
constant CONTROL_NOP : control_signals_t := (
write_enable => '0',
write_address => (others => '-'),
read_address => (others => '-')
);
end package;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library cfx;
use cfx.mytypes.all;
entity control_unit is
port(
instruction : in std_ulogic_vector(15 downto 0);
write_data : out std_ulogic_vector(15 downto 0);
ctrl : out control_signals_t
);
end entity;
architecture rtl of control_unit is
begin
decode_instruction : process(instruction) is
begin
-- Set sensible default values that do nothing.
-- Especially all "write_enable" signals should be '0'.
-- Everything else is mostly irrelevant (don't care).
ctrl <= CONTROL_NOP;
write_data <= (others => '-');
if instruction(15 downto 12) = "1100" then
-- Load 8 bit of data into the register file
ctrl.write_enable <= '1';
write_data <= std_ulogic_vector(resize(signed(instruction(7 downto 0)), 16));
ctrl.write_address <= instruction(11 downto 8);
elsif instruction(15 downto 8) = "11111001" then
-- Load 4 bit of data into the register file
write_data <= std_ulogic_vector(resize(signed(instruction(7 downto 4)), 16));
ctrl.write_address <= instruction(3 downto 0);
elsif instruction(15 downto 8) = "10110101" then
-- Read from the register file. Don't use the write signals at all.
ctrl.read_address <= instruction(3 downto 0);
end if;
end process;
end architecture;
Best Answer
I will leave it to an LRM expert to provide a more detailed answer, but in short, your approach should be valid - I ran a quick test with a recent version of Quartus, and it handles
'-'
like it's supposed to - the logic generated is reduced as expected when the output is defaulted to'-'
('X'
works too, by the way). More on the approaches you listed:Not assigning the signal isn't really an option for your example, of course, if you don't want latches. If it's a clocked process, you're slightly better off, but you'll still get enables where you might not need them. Maybe I'm missing your intent here.
'-'
, as previously noted, is probably the best option, for both semantic and practical reasons.Depends on what you mean by "undefined".
'X'
is technically "unknown".'U'
is for uninitialized signals, which ModelSim displays as"X"
for hex representations.'X'
does seem to work, though, as I noted above.Another alternative would be to do the optimization yourself and remove one case from being tested explicitly:
This has significant disadvantages (mostly related to code clarity), though, and I would probably opt for a more ideal solution.
Incidentally,
'-'
is also commonly used withstd_match()
, which I would consider using for your decoding, e.g.:Though at that point, you're probably better off just using
case?
.