In Embedded Systems Design, you will write hardware descriptions for the FPGA using VHDL. Some of you may have used VHDL before in Advanced Logic Design. In Embedded Systems, you will create much more complex hardware descriptions than in ALD, so it is important to get a better grasp of the language's features and pitfalls. Professor Edward's slides do a good job of explaining the language syntax, so this section will focus on mistakes to avoid and advanced features that can save you time (and prevent you from getting cramped fingers).
Remember commandment IV of VHDL, "Thou Shalt Assign All Outputs".
If you don't, the Quartus compiler will infer a level-sensitive latch.
Edward's slides mention this in the context of performing combinational
logic in a process
statement, but this can also happen in a selected
signal assignment or conditional signal assignment statement.
with a select b <=
"001" when "00",
"010" when "01",
"100" when "10";
The above code is for a 2-to-3 full decoder. However, what happens when the
input is "11"? In that case the previous value will be used, so this
statement synthesizes a latch instead of a decoder. You can avoid inferring
latches by always putting an others
clause in your selected or conditional
assignments.
with a select b <=
"001" when "00",
"010" when "01",
"100" when "10",
"000" when others;
Timing of signals will be the cause of most of the bugs in your VHDL descriptions. A lot of the confusion with timing arises from how timing works in edge-sensitive process statements. Here's a few examples that will clear up what changes when.
Assume that here, a
is an input, b
is an internal signal, and c
is an
output. Also assume that a
only changes on the rising edge of the clock.
First consider what happens when b
is assigned combinationally from a
and
c
is assigned from b
in a process statement.
b <= a;
process (clk)
begin
if rising_edge(clk) then
c := b;
end if;
end process;
In this case, b
will change as soon as a
changes. However, since the
change comes in on a rising edge, c
does not detect the change until a
cycle later.
Note that we use the :=
assignment operator here. This will cause the value
of c
to change as soon as the change to b
becomes visible in the process
statement. If instead we use the <=
assignment operator, the value of c
would change on the next rising edge.
b <= a;
process (clk)
begin
if rising_edge(clk) then
c <= b;
end if;
end process;
That is the key difference between :=
and <=
. The :=
operator makes the
change on the same cycle, and the change is visible to assignments in the
process statements on the same cycle. The <=
operator makes the change on
the next rising edge, and the change is visible on the next cycle.
To see how that affects timing, let's consider a few examples where b
is
assigned inside the process statement.
process (clk)
begin
if rising_edge(clk) then
b := a;
c := b;
end if;
end process;
The change in a
is not visible until one cycle after.
Since the assignments to b
and c
both use :=
, both signals will change
one cycle after a
.
If instead, we use <=
for b
, the change will not be visible to c
until the next cycle.
process (clk)
begin
if rising_edge(clk) then
b <= a;
c := b;
end if;
end process;
Of course, if <=
is used for both signals, the delay from a
changing to
c
changing becomes longer still.
process (clk)
begin
if rising_edge(clk) then
b <= a;
c <= b;
end if;
end process;
The guide suggests that you stick to combinational assignment and the <=
operator in process statements for inputs and outputs to an entity.
Use :=
only when you need an intermediate signal in a single-cycle operation.
In general, the :=
operator can be replaced by combinational assignment,
but use whatever is convenient for you.
In your design, you will often want to detect edges of asynchronous signals or synchronous signals changing on a different clock. An example of this would be detecting when a push button is pressed or released. One way to detect an edge is to just put the edge in your process statement's sensitivity list, but this is generally not a good idea. Remember, the second commandment of VHDL is "Thou Shalt be Synchronous". Fortunately, there is a straighforward technique for detecting edges synchronously.
entity edge_detector is
port (clk : in std_logic;
async : in std_logic;
rising : out std_logic;
falling : out std_logic);
end edge_detector;
architecture rtl of edge_detector is
signal cur_level : std_logic;
signal prev_level : std_logic;
begin
process (clk)
begin
if rising_edge(clk) then
cur_level <= async;
prev_level <= cur_level;
end if;
end process;
rising <= cur_level and (not prev_level);
falling <= (not cur_level) and prev_level;
end rtl;
This unit will set rising
and falling
high for one cycle after a rising
edge or falling edge, respectively, on the async
input.
When writing your hardware description, there are times when you will want to replicate several identical hardware units. For instance, say you have an adder entity, and you want to create an entity that takes four inputs, adds some value to each of them, and produce four outputs. It would be pretty tedious to type out four separate port mappings. You can save yourself the trouble by using a for/generate statement.
entity par_adder is
port (a0 : in unsigned(7 downto 0);
a1 : in unsigned(7 downto 0);
a2 : in unsigned(7 downto 0);
a3 : in unsigned(7 downto 0);
b : in unsigned(7 downto 0);
s0 : out unsigned(7 downto 0);
s1 : out unsigned(7 downto 0);
s2 : out unsigned(7 downto 0);
s3 : out unsigned(7 downto 0));
end par_adder;
architecture rtl of par_adder is
type byte_array_type is array(0 to 3) of unsigned(7 downto 0);
signal a_arr : byte_array_type;
signal s_arr : byte_array_type;
begin
a_arr(0) <= a0;
a_arr(1) <= a1;
a_arr(2) <= a2;
a_arr(3) <= a3;
s0 <= s_arr(0);
s1 <= s_arr(1);
s2 <= s_arr(2);
s3 <= s_arr(3);
ADDGEN : for i in 0 to 3 generate
ADD : entity work.adder port map (
a => a_arr(i),
b => b,
s => s_arr(i)
);
end generate ADDGEN;
end rtl;
You will still need to manually assign to array signals if you use this
technique, but at least you do not have to type entity work.adder port map
four times. You can see how this would be convenient if you generalized it to
larger numbers of repeated entities.