3

I'm new to VHDL and I'm trying to implement a Truth table which in ABEL would have looked like:

Truth-table
([C2.FB, C1.FB, C0.FB, DISABLE] -> [A, B])
 [  X  ,  X   ,   X  ,    1   ] -> [0, 0];
 [  0  ,  0   ,   0  ,    0   ] -> [0, 3];
 [  0  ,  0   ,   1  ,    0   ] -> [3, 5];
 [  0  ,  1   ,   0  ,    0   ] -> [5, 6];
 [  0  ,  1   ,   1  ,    0   ] -> [6, 7];
 [  1  ,  0   ,   0  ,    0   ] -> [7, 6];
 [  1  ,  0   ,   1  ,    0   ] -> [6, 5];
 [  1  ,  1   ,   0  ,    0   ] -> [5, 3];
 [  1  ,  1   ,   1  ,    0   ] -> [3, 0];

From what I can see there is no real support for truth tables in VHDL but maybe a "with select statement" is as close as it gets?

with C select
A <= std_logic_vector(to_unsigned(0, A'length)) when "000",
     std_logic_vector(to_unsigned(3, A'length)) when "001",
     std_logic_vector(to_unsigned(5, A'length)) when "010",
     std_logic_vector(to_unsigned(6, A'length)) when "011",
          ...

Is it possible to assign both A and B in the same statement? For example:

A,B  <= 0,3 when "000",
        3,5 when "001",
      ...

Or do I have to make several statements (tiresome for long tables)? And how do I handle the DISABLE signal? Is there any better and more clever way to do this?

Thanks for any help!

Quist
  • 35
  • 1
  • 7
  • See http://stackoverflow.com/questions/24146866/why-is-this-assignment-ambiguous for some good guidance. What you are trying to do is legal, but the syntax is a bit more complicated. – fru1tbat Jun 13 '14 at 14:17
  • Also worth noting that in your specific case, `A = C`, and `B = not C` when enabled, but that's not exactly what you were asking. Still, nothing wrong with the occasional shortcut... – fru1tbat Jun 13 '14 at 14:19
  • Thats true :) I just added the values of A and B as an example. In "real world" they will be something else. – Quist Jun 13 '14 at 14:33
  • Bear in mind that you can concatenate signals, (including pieces of vectors) into new vectors. This allows you to have all (and only) relevant bits in your `with X select` construct. The only real limitation is the lack of support for don't cares. You can sort of get around this (support the don't care `-` of std_logic) with the `std_match` function, but that only works with if/elsif or when/else trees that use boolean evaluations. You can also assign an aggregate std_logic_vector in the select and split it out into the pieces you want later. – QuantumRipple Jun 13 '14 at 15:57
  • See [IR2060](http://www.eda.org/isac/IRs-VHDL-2002/IR2060.txt "2060"), and P1076 [Truth Tables](http://www.eda.org/twiki/bin/view.cgi/P1076/TruthTable "Truth Tables") which is currently languishing for lack of a champion. You can also use a conditional assignment statement to slip the disable in there, or surround the assignments in an if statement with disable in VHDL-2008 (which accepts selected signal assignment and conditional signal assignment as sequential statements). There's also the good old case statement. –  Jun 13 '14 at 19:30
  • I have updated the A and B values in the example above. In my original post A=C and B=notC which meant that no truth table was actually needed. – Quist Jun 15 '14 at 21:07

3 Answers3

2

Why are you using a truth table, if there are predefined VHDL mechanisms...?

B <= "000" when (disable = '1') else A;
C <= "000" when (disable = '1') else not A;

OR if needed as integer result

B <= 0 when (disable = '1') else to_integer(unsigned(A));
C <= 0 when (disable = '1') else to_integer(unsigned(not A));

Improvement 1:

You can write a function mux(...) or call it ite(...) (short for if-then-else) like this:

function ite(condition : boolean; a : std_logic_vector; b : std_logic_vector) return std_logic_vector is
begin
  if (cond = true) then
    return a;
  else
    return b;
  end if;
end function;

Now you can write the statements above a bit shorter:

B <= ite((disable = '1'), "000", A);
C <= ite((disable = '1'), "000", not A);

Improvement 2:

Calculate the result with basic boolean operations. You can blow-up your disable signal to as many bits as A is wide and then use the conjunction operator. (This is also called 'A is gated by disable' or gate circuit).

B <= (A'range => disable) and A;
C <= (A'range => disable) and not A;

My examples use now type conversion (your example has std_logic_vector as in- and outputs). if needed feel free to add conversion functions like unsigned(..) or to_integer(..).


Truth Tables / ROMs

If you really want to implement a truth table, I would advise to use a ROM. So define a constant, whose content is (pre-)calculated by es function. Then use your input as ROM address and the selected output as result.

Here an example which uses integers as result

subtype t_uint3 : integer range 0 to 7;
type t_uint3_vector is array (natural range <>) of t_uint3;
constant B_TRUTH_TABLE : t_uint3_vector(7 downto 0) := (0, 1, 2, 3, 4, 5, 6, 7);
constant C_TRUTH_TABLE : t_uint3_vector(7 downto 0) := (7, 6, 5, 4, 3, 2, 1, 0);

B <= B_TRUTH_TABLE(to_integer(unsigned(A)));
C <= C_TRUTH_TABLE(to_integer(unsigned(A)));

This example has now disable functionality. To add this, use the ROM in combination with one of the statements above.


Precalculation with a function: Replace the element list (0, 1, ..., 7) with a function like this:

function precalc_B_ROM return t_uint3_vector is
  variable result : t_uint3_vector(7 downto 0);
begin
  for i in result'range loop
    result(i) := i;
  end loop;
  return result;
end function;

function precalc_C_ROM return t_uint3_vector is
  variable result : t_uint3_vector(7 downto 0);
begin
  for i in result'range loop
    result(i) := result'high - i;  -- reverse numbers
  end loop;
  return result;
end function;

constant B_TRUTH_TABLE : t_uint3_vector(7 downto 0) := precalc_B_ROM;
constant C_TRUTH_TABLE : t_uint3_vector(7 downto 0) := precalc_C_ROM;

Edit 1

A combinatorial process which calculates several results.

process(inA, inB, ...)
begin
  -- default assignments
  outX <= '0';
  outY <= '0';
  outZ <= '0';

  case (inA & inB) is
    when "00" =>
      outY <= '1';
      outZ <= '1';
    when "01" =>
      outX <= '1';
      outY <= '1';
    [...]
    when others =>
      outZ <= '1';
  end case;
end process;

(Default assignments reduce lines of code in a when statement and in a clocked process hinder the synthesis to generate latches.)

So it's a question of coding if you return a record and split the result into sub-results or if you use a case with multiple assignments.

addendum The presented ROM can also return a record. => 1) define a record of your result; 2) pack this record in a new array/vector type; 3) define a ROM of this new type; 4) access a ROMs entry by address -> the result is your requested record.

Paebbels
  • 15,573
  • 13
  • 70
  • 139
  • I have realized that my example wasn't really perfect since I had designed the truth table so that A=C and B=not C. The question is about how to implement a truth table in the best way and if it is possible to assign two signals at the same time so I have now changed A and B to values that hopefully justifies a truth table a little bit more. – Quist Jun 15 '14 at 21:29
  • @Quist Ok, so if your function is more complex I would still advise to use a ROM and if necessary a function or set of them to calculate the ROMs content. Advantages of a ROM: a) truth tables are ROMs; b) ROMs are mapped into LUTs or if needed into LUT-ROMs or if the function is very big into (read-only) BlockRAM; c) ROMs are described in the arch. head and won't fill the body; d) **calculating ROMs per function is flexible in means of generics** | disadvantages of case statements: a) sometimes requires more lines of code; b) req. a process; c) is not expandable as a function of generics. – Paebbels Jun 16 '14 at 19:21
  • To the second point of your question: returning more the one result: IF several results are semantically related to each other, then it's of cause possible to return them all in one "access". To do so you can use a) the record based solutions of David Koontz and Morten Zilmer or b) see my 'edit 1' for using a combinatorial process which uses a case statement. Feel free to combine several kinds of description :) – Paebbels Jun 16 '14 at 19:32
  • Thank you for your extensive replies Paebbels! With ROMs, do you mean addressing an external ROM or do you mean that by writing the VHDL code in some special way a ROM optimized hardware is created/allocated in the FPGA? I'm new to this you know... – Quist Jun 25 '14 at 21:39
  • Yes, by ROM I mean a special case of writing VHDL. So what is the essence of a ROM? It's read-only and it's data is accessed by an address/selection signal. In most cases this kind of code is mapped into LUTs or LUT bases RAM blocks (LUT-RAM) which are read-only. My example B <= B_TRUTH_TABLE(to_integer(unsigned(A))); where A is a 3 bit address an B a 3 bit result of a int subtype will be a ROM. – Paebbels Jun 27 '14 at 05:51
1

Try using a case statement:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity truth_table is
end entity;

architecture foo of truth_table is
    signal C0,C1,C2:    std_logic;
    signal DISABLE:     std_logic;

    signal A:           std_logic_vector(2 downto 0);
    signal B:           std_logic_vector(2 downto 0);

begin
TT:
    process(C0,C1,C2,DISABLE)
        variable a_int:  integer range 0 to 7;
        variable b_int:  integer range 0 to 7;
        type ab_pair is array (0 to 1) of integer range 0 to 7;
        variable CD:    std_logic_vector(3 downto 0);

    begin
        CD := (C2 & C1 & C0 & DISABLE);
        case CD  is
            when "0000" => (a_int, b_int) := ab_pair'(0,7);
            when "0010" => (a_int, b_int) := ab_pair'(1,6);
            when "0100" => (a_int, b_int) := ab_pair'(2,5);
            when "0110" => (a_int, b_int) := ab_pair'(3,4);
            when "1000" => (a_int, b_int) := ab_pair'(4,3);
            when "1010" => (a_int, b_int) := ab_pair'(5,2);
            when "1100" => (a_int, b_int) := ab_pair'(6,1);
            when "1110" => (a_int, b_int) := ab_pair'(7,0);
            when others => (a_int, b_int) := ab_pair'(0,0); 
        end case;
       A <= std_logic_vector(to_unsigned(a_int,A'LENGTH));
       B <= std_logic_vector(to_unsigned(b_int,B'LENGTH));

    end process;
end architecture;

This cumbersome example shows A and B the same size, were they different sizes or types you'd use a record type instead of ab_pair.

This example analyzes, elaborates and simulates although doing nothing useful, showing it's a valid VHDL design specification.

1

If your synthesis tool supports the case? statement in VHDL-2008 (which for example Altera Quartus II ver. 13.1 does), then you can use the don't care ('-') encoding in std_logic as encoding for what you use as "X" in the truth table. Code is:

port(
  c_i       : in  std_logic_vector(2 downto 0);
  disable_i : in std_logic;
  a_o       : out std_logic_vector(2 downto 0);
  b_o       : out std_logic_vector(2 downto 0));
...
process (all) is
  type pair_t is record
    a : std_logic_vector(a_o'range);
    b : std_logic_vector(b_o'range);
  end record;
begin
  case? c_i & disable_i is
    when "---1" => (a_o, b_o) <= pair_t'(3X"0", 3X"0");
    when "0000" => (a_o, b_o) <= pair_t'(3X"0", 3X"7");
    when "0010" => (a_o, b_o) <= pair_t'(3X"1", 3X"6");
    when "0100" => (a_o, b_o) <= pair_t'(3X"2", 3X"5");
    when "0110" => (a_o, b_o) <= pair_t'(3X"3", 3X"4");
    when "1000" => (a_o, b_o) <= pair_t'(3X"4", 3X"3");
    when "1010" => (a_o, b_o) <= pair_t'(3X"5", 3X"2");
    when "1100" => (a_o, b_o) <= pair_t'(3X"6", 3X"1");
    when "1110" => (a_o, b_o) <= pair_t'(3X"7", 3X"0");
    when others => (a_o, b_o) <= pair_t'("XXX", "XXX");
  end case?;
end process;

For use of only VHDL-2002, for example supported in Xilinx tools, please see other answers.

Morten Zilmer
  • 15,586
  • 3
  • 30
  • 49
  • Thanks! I'm on Xilinx (ISE) however. Interesting to learn that Xilinx still lacks support for VHDL-2008. Seems as if ISE never will get it. – Quist Jun 26 '14 at 22:41