There are a few little syntactic problems with this code but overall it's not far off and it'll be a useful way into distinguishing between synthesisable and non-synthesisable - which really isn't what you are expecting!
1) The port map
port(therm : inout std_logic_vector(6 downto 0); -- thermometer code
bin : out std_logic_vector(2 downto 0); -- binary code
i : integer range 0 to 7);
Points to note here:
therm
and bin
have both a mode and a type; i
has only a type. As bin
is only a copy of i
you don't really need both in the port list. You can declare i
internally as a signal, or even in the process as a variable.
bin
represents an Unsigned number. So good style is to make it Unsigned; or even Integer, and convey more of the design intent to the reader. Both of these are perfectly synthesizable. Integer may give you a 32-bit integer, so make it bin : out natural range 0 to 7;
and you will get a 3 bit unsigned integer! The lesson here is that if you're doing lots of unnecessary type conversions, STOP: think, and you will find a way to use the type system instead of fight it.
therm
has been declared "inout
" but it is really an input to the process. (The fact that you are modifying it internally is an implementation detail, you REALLY don't want to expose the modified version to the outside world!) In fact, "inout
" really doesn't do what you think it does. Its real purpose is only for data buses and similar, where you communicate both ways on the same signal. If you use it as you are, then whatever drives the original value on therm
will still drive therm
, and your modified values will conflict with that to produce "X
", i.e. unknown value (Or in real hardware, possibly overheat and burn out!).
So eliminate i
, make bin
Unsigned or a subtype of Natural, and make therm
an input.
2) You need a local copy of therm
you can modify. Declare it locally as a signal.
architecture behavioral_g of therm2bin_g is
therm_int : std_logic_vector(6 downto 0);
begin
And i
needs to be declared locally too. It could be a signal too, but let's make it a variable in the process instead.
golden : process(therm)
variable i : integer := 0;
-- yes, it is important to initialise it!
begin
3) The loop.
First we need to copy the input into the internal signal, then we can modify it in the loop.
therm_int <= therm;
while (therm_int /= "00000000") loop
therm_int <= therm_int srl 1;
i := i + 1;
end loop;
To simplify it, you could make therm_int unsigned, and write while therm_int /= 0 loop
(losing the silly C-style parentheses in the process)
There is another problem here. Think of a process as like a little program in C (but without some of C's stupider "features") - variables work rather as you expect - BUT - signals are essentially designed for inter-process communication in a parallel processing system. If you think in C terms, the closest equivalent is a pipe opened on stdout and looped back into stdin! You can write to a pipe but nothing happens until you flush it...
In VHDL, the flush will only happen when the process suspends itself. As written, it cannot suspend itself until the loop ends and it reaches "end process
". And it cannot end the loop until "therm_int = 0
". And it cannot change the value seen on therm_int
until it suspends ... this is an infinite loop.
To understand why VHDL signals work this way, see this Q&A
The simplest way round this is to make therm_int
another process variable and use variable assignment :=
instead of signal assignment <=
for it.
And now it should work.
4) Synthesis.
But what isn't synthesisable about it?
Only this: You used a while
loop instead of a for
loop!
Think about that for a moment: you cannot generally predict how many cycles a while
loop will take. For synthesis, that means you have to generate an unknowable quantity of hardware!
But you could place an upper bound on the iterations, and generate hardware to cater for that upper bound. In fact a for
loop is automatically bounded, and synthesis tools can handle that.
So write the loop as
therm_int := therm;
for j in therm_int'range loop
if therm_int /= 0 then
therm_int := therm_int srl 1;
i := i + 1;
end if;
end loop;
and synthesise it.
Notes : for j in therm_int'range loop
; why j? Well we don't want to hide your i
variable!
And note that therm_int'range
automatically equates to (6 downto 0)
or however you declared therm_int
. This bombproofs your code against changes to the size of therm_int
or accidental buffer overflows. It's one little example of what I meant by using the type system instead of fighting it.
While we're at it, let's eliminate the shift, which is unnecessary!
for j in therm_int'reverse_range loop
if therm_int (j) = '1' then
i := j; -- last assignment is most significant '1' bit
end if;
end loop;
As written, this will synthesise to generate enough hardware to implement the task in a single clock cycle. In general, that would lead to rather slow clock cycles, and you would want to ration the work (perhaps a single loop iteration per clock). But that's another story...
VHDL accepts the ISO 8859-1 character set, this is UTF-8.
Your VHDL analyzer is having problems with some of the characters as a result of you copying the text directly from a book.
Your comments aren't delineated by two dashes (e.g. --
), your double quotation marks all need to be replaced with "
.
After which the VHDL code analyzes.
There's no discernible reason why d3
, d2
, d1
, and d0
can't be replaced with a bit_vector. There may uses with the present port interface in the book.
Formal ports are associated with actual signals in an association list. It's possible to associate individual elements of a vector with a base element.
Converting between non-closely related types can be done with conversion routines:
-- bcd_7seg.vhd
-- bcd-to-seven-segment decoder
entity bcd_7seg is
port(
d3, d2, d1, d0: in bit;
a, b, c, d, e, f, g: out bit
);
end entity;
architecture seven_segment of bcd_7seg is
signal input : bit_vector (3 downto 0);
signal output: bit_vector (6 downto 0);
begin
input <= d3 & d2 & d1 & d0;
with input select
output <= "0000001" when "0000",
"1001111" when "0001",
"0010010" when "0010",
"0000110" when "0011",
"1001100" when "0100",
"0100100" when "0101",
"1100000" when "0110",
"0001111" when "0111",
"0000000" when "1000",
"0001100" when "1001",
"1111111" when others;
-- separate the output vector to make individual pin outputs.
a <= output(6);
b <= output(5);
c <= output(4);
d <= output(3);
e <= output(2);
f <= output(1);
g <= output(0);
end architecture;
library ieee;
use ieee.std_logic_1164.all;
entity bcd_tb is
end entity;
architecture foo of bcd_tb is
signal d: std_logic_vector (3 downto 0) := "0100";
signal output: std_logic_vector (6 downto 0);
begin
DUT:
entity work.bcd_7seg
port map (
d3 => to_bit(d(3)),
d2 => to_bit(d(2)),
d1 => to_bit(d(1)),
d0 => to_bit(d(0)),
to_stdulogic(a) => output(6),
to_stdulogic(b) => output(5),
to_stdulogic(c) => output(4),
to_stdulogic(d) => output(3),
to_stdulogic(e) => output(2),
to_stdulogic(f) => output(1),
to_stdulogic(g) => output(0)
);
end architecture;
You can also associate individual elements of an array type port with the base element type or use conversion functions as are shown above for converting between type bit and the element type for std_logic_vector.
Best Answer
A better way than using std_logic_vector and the non-standard
std_logic_arith
andstd_logic_signed
libraries is touse IEEE.numeric_std.all;
and make the counterunsigned
(or signed in other contexts where you need negative values).And you can perform conversions to and from integer, and bitwise operations:
However in the given example, the bitwise operations turn out to be unnecessary: instead of masking bits to extract a 3-bit register address, you could use division and mod operations. Synthesis tools (mostly) aren't stupid : divide and mod by powers of 2 are optimised into the obvious field extractions and don't even generate "and" gates.
One benefit of this (apart from fewer ugly type conversions) is that you still profit from the integer subtype you declared for
hcount
: incrementing it to 236 will raise an overflow error in simulation, allowing you to catch and correct logic errors early.