11
\$\begingroup\$

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 for std_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-2008 case? 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;
\$\endgroup\$
  • \$\begingroup\$ Could you elaborate on what you're trying to do with write_address and write_data? What optimization do you expect to take place? \$\endgroup\$ – fru1tbat Apr 16 '14 at 20:11
  • \$\begingroup\$ I hope the example makes it more clear what I want to achieve. \$\endgroup\$ – Fritz Apr 17 '14 at 8:15
9
\$\begingroup\$

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:

if instruction(15 downto 8) = "11111001" then
  write_data <= std_ulogic_vector(resize(signed(instruction(7 downto 4)), 16));
else
  write_data <= std_ulogic_vector(resize(signed(instruction(7 downto 0)), 16));
end if;

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 with std_match(), which I would consider using for your decoding, e.g.:

if std_match(instruction(15 downto 8), "1100----") then

Though at that point, you're probably better off just using case?.

\$\endgroup\$
6
\$\begingroup\$

In short: It's legal VHDL and it's typically supported by synthesis tools.

It is however rather uncommon to see it used. I don't really know why. Your code seems to me to be a good example of when it would be meaningful to use it.

There is however one drawback that one should be aware of: at synthesis, the functions driving outputs where don't care's are involved could be different between synthesis runs. This makes the synthesis less deterministic. If outputs that have been defined as don't care are used (by mistake), this can make the error harder to find.

Tool support

At least the following tools will accept don't cares and make use of the optimization possibilities:

  • Xilinx (ref.: "XST User Guide")
  • Altera (ref.: "Recommended HDL Coding Styles")
  • Synplify (ref.: "Synplify reference manual")

Xilinx and Altera will treat '-' and 'X' as don't care, Synplify will treat those and furthermore 'U' and 'W' (weak) as don't care.

\$\endgroup\$
  • 1
    \$\begingroup\$ I had another application of that drawback. The code worked in simulation, but not on the FPGA, because my code looked like: if signal = '1' then a; else b; end if;. Unfortunately the signal wasn't 1 or 0 but -. So in the simulation, the else branch was executed, but in hardware the - turned out to be a 1, so the true branch was executed... \$\endgroup\$ – Fritz Jun 16 '16 at 18:55
  • \$\begingroup\$ Yeah, I've had similar bugs pass simulation, but most often in my case there are the 'U's, common in the beginning of simulations, that have been used leading to some else code block being executed. It would be wonderful if conditionals could somehow be made to propagate 'U's, similar to the behaviour of concurrent boolean expressions. \$\endgroup\$ – Carl Jun 17 '16 at 15:04
  • \$\begingroup\$ After I found that bug I made sure to always write something like if signal = '1' then output <= '1'; elsif signal='0' then output <= '0'; else output <= '-'; end if;. And I added the following to all registers and memories: assert not is_X(write_enable) report "we=" & str(A_write_enable) severity ERROR; and if write_enable = '1' then assert not is_X(write_addr) report "write_addr=str(write_addr) severity ERROR; end if;. Plus the same for write_data. Together, that should catch almost all of these errors. \$\endgroup\$ – Fritz Jun 18 '16 at 10:16
  • \$\begingroup\$ That's a way, but that's way too verbose for me. I would wish for this possibility within the VHDL language. \$\endgroup\$ – Carl Jun 20 '16 at 10:32
  • 1
    \$\begingroup\$ Well yes, VHDL is a bit verbose, but that's just the VHDL way. :D On the other hand it is also very explicit, and doesn't do "black magic" behind my back, which I find quite nice (cf. The Zen of Python "Explicit is better than implicit"). \$\endgroup\$ – Fritz Jun 21 '16 at 12:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.