??? 06/21/08 07:49 Read: times |
#156082 - why the different types? Responding to: ???'s previous message |
You keep asking "why each numeric type exists and what it does that the others don't."
Don't forget that VHDL started out as a modeling language; synthesis was implemented later. As such, you'd model things with integers or reals or perhaps even complex numbers. There's also a boolean type, which of course takes the values TRUE and FALSE. There is a bit type, which takes on the values '0' and '1', and the vector version bit_vector. I suppose you could define a vector of booleans, but that's rather inconvenient. A character type is provided, and a vector of characters is of course a string. A time type is provided because of course digital models care about when things occur. Now, when you model a real digital system, you know that things are not as simple as '0' and '1'. With that in mind, the VHDL gods created the std_ulogic type, where signals can take on any one of nine different values: 'U' -- Uninitialized 'X' -- Forcing Unknown '0' -- Forcing 0 '1' -- Forcing 1 'Z' -- High Impedance 'W' -- Weak Unknown 'L' -- Weak 0 'H' -- Weak 1 '-' -- Don't careOf course there is the vector version, called std_ulogic_vector. These type definitions are in the package ieee.std_logic_1164 (which is why that library use clause is always in your source). It should be obvious why these values are useful. Note that this is std_ulogic. The "u" means "unresolved." This means that if more than one driver is on a signal, the output is in conflict unless a resolution function is provided. (A bidirectional data bus is the obvious example.) The resolution function assigns priorities to the various signal "strengths" and the highest value wins and determines the final driven value. The type std_logic is the same thing as std_ulogic but with the resolution function "built in" so you don't have to worry about it. Now, back in the bad old days you had integer types and std_logic_vector. You could do math with integers, but modeling real I/O ports with integers is a challenge, mainly because you need to assign bits to pins. You couldn't do math with std_logic_vectors (or bit_vectors). Thus Synopsys created the std_logic_arith, std_logic_unsigned, and std_logic_signed packages, and unfortunately put them in the ieee library. std_logic_arith, in combination with either std_logic_unsigned OR std_logic_signed (NEVER both) allowed you to do math on std_logic_vectors, as well as convert between integers and std_logic_vectors. The reason you cannot mix std_logic_signed and std_logic_unsigned is because you'd get two definitions of each arithmetic operation. So an operation like: foo <= foo + "1001";is ambigious: is "1001" signed or unsigned? In fact, is foo itself signed or unsigned? (See why I keep harping on this?) The result of the sum differs! The VHDL folks realized that std_logic_arith was a disaster, so they created numeric_std and numeric_bit. These packages provide math and logical operations on std_logic_vectors and bit_vectors respectively, and there are unsigned and signed types for both. In practice, you'd use numeric_std because the different strength values afforded by std_logic (like high-Z and unknown) are more useful than just the '1' and '0' of bit. OK, so that's WHY the different types exist. You also ask, "What do I use when?" Here's what I do, in no particular order. If I need a counter where I don't need to use any specific bit for anything, I use natural (0 and positive integers). Things like array indices, timeout counters and mux selects are good candidates. I will usually constrain the range of this natural to whatever makes sense for the application. (I'll usually create a subtype that's the constrained-range natural.) So if I have some goofy array with ten entries, I'll create an index signal that is constrained to have a range 0 to 9. If somehow my code attempts to assign that index signal a value outside that range, an error is thrown at simulation time. NOTE: of course if you define a signal to have values between 0 and 9, it still gets synthesized to four flip-flops. So technically that signal can have a value between 0 and 15, but the behavior of the circuit should never be outside the range 0 to 9. The simulation failing when outside the intended range should be a red flag to you, saying something bad has happened. OK, now if I need to take numeric inputs from something or provide numeric outputs to something, I'll use either unsigned or signed, as indicated by the application. For example, a recent FPGA design took multiple samples from an ADC and averaged them. The ADC in question provided 2's-complement data, so the signed type was indicated. In this case, the FPGA input pins assigned to the ADC data bus were declared as parts of a std_logic_vector, and as the data were synchronized in the FPGA, they were converted to a signed value. (The counter that kept track of the number of samples collected was a natural!) Don't forget of course that when you add, you need to account for the overflow, and the more samples added together, the more bits needed in the accumulator. Accumulating four 8-bit samples gives a 10-bit result. The adder itself is simplicity: signal newval : signed(7 downto 0); signal acc : signed(9 downto 0); signal avg : signed(7 downto 0); accumulator : process (clk) is begin if rising_edge(clk) then if (clear = '1') then acc <= (others => '0'); -- it's still a bit vector else acc <= acc + resize(newval, acc'length); end if; -- clear end if; -- rising_edge end process accumulator;Note that I use the resize() function (in numeric_std) to sign-extend the 8-bit newval into the 10 bits of acc. VHDL requires that all vectors on both sides of the assignment operator have the same size; a compile-time error results if they aren't. (This is as opposed to Verilog, which will happily extend your vectors for you, but in a very non-obvious way.) Resize is very convenient because if you use a generic to determine the width of your vectors at compile time, your code doesn't change to allow for the different number of bits. The alternative, acc <= acc + "00" & newval;uses the concatenation operator & to zero-extend newval, which is fine if newval is unsigned and you know that newval is always two bits shorter than acc. If newval is signed, you really need to sign-extend the appropriate number lof bits, and you'd probably end up writing a function that does exactly what resize() already does. So if you want to average four samples, life is very simple: avg <= acc(9 downto 2);No muss, no fuss, although a comment indicating what you're doing would be nice. Now there is nothing stopping you from declaring subentity ports as unsigned or signed instead of std_logic_vector, and in fact, I do that a lot. Say I have a module that has a serial-port receiver, and that receiver parses incoming commands which store transmitted values in different registers. Say one of my values is a terminal count. I could save that as a std_logic_vector, and then the entity that needs it could then convert it to unsigned before/during use. But why not have the entity ins and outs be unsigned? It works fine. In fact, you could even use integer (constrained or not) in entity ports and they work fine. All that matters is that the things you hook up are the same type. The one exception to the above rule is at FPGA I/O pins. You can't use integer because the tools won't know what pins to use. You could use unsigned or signed, but if you create a test bench that exercises the chip (which you SHOULD do ALWAYS), then you won't be able to do a post-route simulation because the model generated by the tools will invariable create ports as std_logic_vector. This means that when you compile the post-route model, the ports won't match the instance of your chip in the top-level testbench. So stick to std_logic and std_logic_vector at port I/O, and use unsigned, signed, integer and natural as convenient in your code. By "convenient," I mean in a manner that makes the most sense to you and has the least verbosity. -a |