Disclaimer: I take no responsibility for the information
on this page. Although by default I retain
an exclusive copyright on this material, by using any of this, you
agree to assume all liability and
consequences that may arise. In other words, if you
don't like it, or you suffer harm from using it,
don't blame me. If you don't agree to this, then leave now
by clicking here or stay
to find out some
valuable information.
Where to Start...
So you've decided that you want to use an FPGA with VHDL code.
When I first started out, I thought it
was going to be a piece of cake. After all, what could be more
simple? Write everything behaviorally, use
classical programming skills, burn a chip and turn in your project
to your supervisor. Of course, this works
for all of about five minutes...then you realize what a pain
things are.
FPGAs are about the closest thing you can get to a totally integrated
digital system without using full
custom IC design. But you have to treat the process with respect.
By that I mean you have to write well-
behaved VHDL code, and fully understand the processes that are happening
within the software. If not,
you get a very expensive keychain ornament after you're done.
I'm going to assume a few things here, including:
Well-Behaved VHDL Code for Synthesis
What exactly is this kind of VHDL code? Well, you can basically
partition VHDL into two different
styles: code for synthesis and code for simulation. Simulation
code implies that, as long as your code
is syntactically correct with respect to the VHDL standard, it will
behave within a simulator like QHSIM
as you intend it to. You can add delays, wait statements, nest
if-then-else and case statements, play with
events and do basically whatever else you want.
Unfortunately, you are constrained quite a bit when it comes to writing
synthesis code. Code for
synthesis implies that you are coding VHDL to be put on some logic
device, such as a CPLD or an
FPGA. That means that you often can't insert "wait for" statements,
or depend on both edges of an
event as a trigger, or do a lot of other things that you could
do if you were coding for simulation only.
Even where certain code styles are allowed, you won't necessarily be
able to synthesize a particular
design in a compact way. Things can get out of control really
quickly, so the best way is to know some
of the most common pitfalls of coding. If you get in the habit
of coding for synthesis, you can make your
life easier when designing, and increase the chances for portability
to other devices.
What are the "Synthesis Tools" Anyway?
Synthesis tools are programs that prepare VHDL code that you've written
for implementation
in an FPGA. They take either behavioral or structural VHDL code,
transform the code into FPGA
primitives or "standard native components" specific to the device,
and ultimately yield a fuse file which
can be used in an FPGA burner. Along the way, there are several
stops, including compilation into a
preliminary format (EDIF in the case of Actmap), optimization for area
or speed, specification of
constraints such as pre-assigned pin placements and delay targets,
and final extraction into a fuse file
or into a "back-annotated" delay file. The diagram below shows
the hierarchy and what each
component of the synthesis tools does:
The beauty of synthesis tools is that they isolate the digital designer
from a lot of the very intricate
details specific to the part being used, and even from traditional
design techniques. Minimization of
logic, state machine creation and even arithmetic function creation
don't have to be done using the
"classical" digital design methods. For example, my EE552 project
partner and I implemented a
binary-to-dual-seven-segment converter with a range of 0 to 99 in 65
modules! (And a logic module
in an Actel FPGA is three 2:1 MUXes and an OR gate). Try doing
that using traditional techniques,
and you'll see that pen-and-paper succumb to the almighty synthesis
tool. That doesn't mean that
you don't have to think anymore, however. The issues within
digital design using FPGAs and
synthesis tools with VHDL are different, but just as critical.
You can't just code anything and
expect the tools to behave properly, because there are a lot of constraints.
What Are Some Of The Common Pitfalls?
By no means is this a comprehensive list. I may have missed a
few things or not run into some of the
problems that you're running into. If you see any significant
gaps, mail me and I'll get around
to
changing it. A lot of this is cribbed directly from Actel's documentation
for Actmap (thanks to Eric
Masson for the reference), but also from my experience and resultant
headaches.
1. Non-modular code - if you do anything in VHDL, write
in a modular style. This means that you use
some of the built-in features of the language, such as processes, packages,
and components. Breaking
your code down into smaller fragments makes it easier to analyze, understand,
and debug. Some more
specific things that I suggest are:
Top Level:
entity top_level is port(blah:blahtype(4 downto 0));
end top_level;
architecture modular of top_level is
component foobar
port(kaboom:out std_logic_vector(2 downto 0));
end component foobar;
component cheapcounter
port(ouch:in std_logic_vector(2 downto 0)
yipe: out std_logic_vector(4
downto 0));
end component cheapcounter;
signal link_signal:std_logic_vector(2 downto 0);
-- links signals between two components
for all: foobar use entity work.foobar(foobehavior);
for all: cheapcounter use entity work.cheapcounter(countbehavior);
begin
fubard : foobar port map(kaboom=>link_signal);
countischeap : cheapcounter(ouch=>link_signal,yipe=>blah);
end modular;
By splitting your code up, you can track errors to specific modules
and observe signals in the
simulator by module (e.g. /foobar/kaboom is the "kaboom" signal in
the component "foobar").
Also, you can see what's happening at both ends of the signal by looking
at the linkage signal
so you don't get the proverbial "tail wagging the dog" or other assorted
bizarre problems.
You have to choose between the trigger or the clock. If
you're desperate, you could use
separate combinational logic like an XOR gate and flip-flop tricks
that I talk about below. And
using a "case" statement won't get around the problem.
score <= temp after 25 ns;
is not synthesizable. Actmap will give you an error when you try
to compile because it can't
guarantee a 25ns delay on a particular assignment.
Note the specific clock frequency of 4.194MHz. Why that particular
one? Well, it isn't just because
it was available hardware, but also because I can divide-by-2 a total
of 23 times and I get a number
very close to 0.500 Hz. In general, if you need something
like this done, the rule of thumb is that the
base-2 logarithm of the clock frequency should come out very
close to a whole number. This
way, you can divide it simply by using a counter, and you don't even
need a preload function or direction
control. Also, just let the count overflow...you don't really
need to care about the carry out.
e.g. if (clear='0') then
count <= "00000000";
-- zero the count on a clear signal
elsif (clock'event and clock='1') then
count <= count +
"00000001"; -- increment on system clock event
output <= count(7);
-- assign output to last bit which will
end if;
-- change every time overflow occurs
Yet another alternative (and you have to be really hard-up to get to
this point IMHO) is to use an implied
comparator and derive the number of clock pulses it takes to reach
a certain time, then trigger when it's
equal to a set value.
e.g. constant triggertime : std_logic_vector(9
downto 0) := "0101011010";
...
counter : process(reset,clock)
begin
if (clear='1') then
count <= "000000000";
elsif (clock'event and
clock='1') then
count
<= count + "0000000001";
end if;
end process blek;
...
outassign : process(count)
begin
if (count=triggertime)
then
output
<= not(output); -- invert the previous output value
clear
<= '0'; -- on
each iteration and clear counter
else
clear
<= '1';
end if;
end process outassign;
This will chew up more silicon than the previous counter, so it's ultimately
up to the designer what s/he
wants to do.
4. "Open-ended" and nested conditional statements - Suppose
you have a 16-bit wide signal. You
need to do an address decoder for a device on a bus, so you type:
if (address = "0000111100001111") then
data_bus_signal <= "0001";
--(or the equivalent as a CASE statement)
end case;
Now, this is all fine and dandy, but when you try to compile it, you
have an enormous number of modules
used up in the FPGA. What you don't see is an implied
handling of all of the other cases of the "address"
signal. It's as if you had typed:
if (address = "0000000000000000") then data_bus_signal
<=
elsif (address = "0000000000000001") then data_bus_signal
<= "0000";
...
elsif (address = "1111111111111111") then data_bus_signal
<= "0000";
end case;
Now, this would take you months to type out. It also takes Actmap
a relatively long time, and in the
process chews up a lot of silicon area. A better way to do this
is to make a closed-ended conditional
statement so that you prevent the synthesis of unnecessary logic.
You would retype the above statement
as:
if (address = "0000111100001111") then data_bus_signal
<= "0001";
elsif (address /= "0000111100001111")
then data_bus_signal <= "0000";
end if;
By specifying "elsif" as opposed to "else" (or "when others" instead
of excluding "when others" in a "case"
statement), you prevent the synthesis tool from adding unnecessary
logic. It's like blinders for horses.
When you race them, they need them to keep focused and not go all over
the place when you want to
go straight ahead. I do want to mention two caveats to this:
One short note one nested conditional statements (e.g. if blah then
if blah then if blah then ...). Unless
you have a specific need for such structures (or a conceptual failing,
and I usually fall in the latter
category), you can cause your code to become excessively large.
Try and split it up as per #1 above,
or use multiple processes and pass signals between them.
5. Structural VHDL code from Designer and Actgen - one
would think that structural code is the
ubiquitous solution to synthesis problems. It isn't. Actmap
likes to choke on this type of code regularly
so don't use it. For one thing, if you follow all the rest of
the rules for well-behaved VHDL synthesis
code, Actmap will run the macro generator internally and produce the
equivalent code. I personally
avoided using Actgen, but if you need to, you can search the Actel
site "Guru" for "How do I add an
ACTgen macro to my VHDL design?" It's pretty intense, so be careful
if you want to use it.
6. VHDL '92/'93 constructs - don't use these.
They aren't well supported in synthesis tools from
what I've seen. They're more for simulation purposes. It's
hard to go wrong with VHDL '87 it
seems (although it can be inconvenient), plus you're allowing yourself
better migration to other tools
for synthesis and simulation later on if you need that.
7. Loop statements - most traditional firmware programmers
know that statements like "while" and
"loop" are dangerous. They cause compiled code to almost literally
explode in size. The same
things happen in the VHDL world, only worse - they usually won't synthesisze.
Don't use them.
I believe the one exception to this is if you use a for with the loop
statement in a certain way, though
I'm not sure. Corrections are always welcome :).
8. "Unusual" data types - there are a few data constructs
that cause major havoc within the
synthesis environment. If you're lucky enough to have it compile,
you probably won't be saving
yourself a lot of space by using it. Some of the ones to watch
for include:
One more important point - you should use non-binary data types for
two things: generic
components (see bit-width above) and enumerated data types (particularly
for state machines
for which VHDL is a gift from Heaven).
9. Using the "inout" signal direction between internal components
- if you try to use the
"inout" to specify your signal direction on components inside
your top level, the design won't work
properly. Just use multiple signal assignments from the same
signal and "multiplex" the feedback.
If, for some reason, you absolutely have to use inout signals
in your design, you're going to have to
route them to the outside of the chip, then hardwire them back to another
I/O pad. If this seems
inefficient, it is. Avoid it at all costs.
10. Using simulator tools to compile code through revision
- this is generally a bad idea.
Why? Well, you eventually want to compile this for synthesis.
Even assuming you took most of the
above points to heart, you may still encounter some weird stuff
that doesn't compile. You'd never
find this out until after you've written all your code that
compiles nicely for the simulator. By first
compiling with the synthesis tool, you can see whether or not your
design has the general form for
synthesis. Once you're certain that your code compiles for synthesis,
you can be almost positive
that the synthesis tool will deal with it. Not always, but most
of the time.
What I mean by "not always" is that CAD/CAE tools have a tendency to
differ in the way they
handle libraries from the synthesis tools. Each synthesis tool
vendor has their own way of handling
basic mathematical functions, data types, and other elements of the
language, just as each design
and simulation tool vendor. No matter what you use, always include
the following library statements
within your code:
library ieee; --always put this in, it's
the same either way
use ieee.std_logic_1164.all; --allows
use of std_logic types
In the particular case of Mentor Graphics and Actmap, you need to do the following:
Copyright 1997 Mike
Daskalopoulos and Ishfaqur Rahman. All rights reserved.
Read this legal
notice before using any information contained in this document.