Sprungnavigation:

zum Inhalt

FPGA mit VHDL

Tipps und Tricks mit VHDL

Es gibt immer wieder kleinere Problemchen, die dann dazu führen, dass man Stunden vor dem Synthese- und Simulationswerkzeug sitzt, um den Fehler zu finden.
Hier wird nun einfach, ohne es zu strukturieren, aufgeführt, worauf man achten sollte.

meine Tipps und Tricks zu VHDL:

 

Simulationsfehler im ModelSim beim Laden einer Datei

Am Anfang der Architektur beschreiben Sie eine Konstante mit einem Dateinamen, um die Datei im späteren einlesen zu können. Dazu machen Sie folgende Angabe:

constant filename : string := "JUMPTEST.HEX";

Im weiteren Verlauf erstellen Sie einen Prozess, der die Datei öffnet:

file hex_file : TEXT open read_mode is filename;

und dann in einer Schleife einliest

while not endfile (hex_file) loop

Im Simulator erhalten Sie dann folgende Fehlemeldung an der Stelle, wo die Schleife die Datei einlesen soll:

# ** Fatal: (vsim-7) Failed to open VHDL file "JUMPTEST.HEX" in rb mode.
# No such file or directory. (errno = ENOENT)

Den Fehler bekommen Sie schnell weg, wenn Sie der Datei einen absolute Pfad vorranstellen:

constant filename : string := "d:\vhdl\pic-risc5-test\JUMPTEST.HEX";

die Initialisierung von Memory (HEX-File oder MIF-File)

Sowohl ALTERA als auch XILINX bieten in Ihren Wizards zur Einrichtung eines Memory-Blockes die Möglichkeit an, das Memory zu initialisieren.
So haben die Wizards eine Dialogbox, in der man eine lokale Datei (HEX-File oder MIF-File) einbinden kann.
In der entsprechenden Dialogbox wird dann die Datei zum Beispiel so benannt:
"../../pic/projekte/pin-toggle.hex"
Bis dahin alles Prima! Der Compiler compiliert das Design und man kann es anschliessend auf das FPGA laden.

Nun kommt der Zeitpunkt, dan dem etwas nicht stimmt und man möchte das Design dann Simulieren.
Dann kommt der Punkt der Wahrheit und man muss feststellen, dass ModelSim genau an der Stelle meckert, an der die Datei geladen wird bzw. geladen werden soll.
ModelSim kann mit dieser relativen Pfadangabe und dem Trennzeichen für die Ordner nichts anfangen.
Also geht man hin und editiert per Hand im Wizard die Pfadangabe auf diese Art:
"d:\pic\projekte\pin-toggle.hex"
So geht es dann im Compilier als auch in der Simulation.

Hochzählen eines Zählers um Eins (Up-Counter)

Folgender Beispiel-Code soll drei Zähler (a, b und c) darstellen, die jeweils bei Clock um eins erhöht werden sollen:

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.std_logic_signed.all;

ENTITY test_arc IS
PORT (
     a : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
     b : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
     c : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
     );
END test_arc;

ARCHITECTURE test OF test_arc IS
BEGIN

do_process: PROCESS (clock, reset)
BEGIN
  IF reset = '0' THEN
     a <= (others => '0');
     b <= (others => '0');
     c <= (others => '0');
  ELSIF clock'EVENT AND clock = '1' THEN
     a <= a + '1';
     b <= b + "1";
     c <= c + "00000001";
  END IF;
END PROCESS;
END test;

Doch zählen die Zähler wirklich richtig hoch?

Zählfehler bei einem Up-Counter

Anscheinend nicht! Obwohl auf dem ersten Blick alle Zähler "richtig" Codiert sind, zählt der Zähler b falsch.
Woran liegt das nun? Es liegt daran, dass alle Zähler vom Signaltyp Standard-Logic-Vector sind und die numerische Addition um eins entsprechend bei Zähler b falsch interpretiert wird.

Single-Quots dürfen nur bei dem Signaltyp Standard-Logic verwendet werden. Weisst man eine solche Deklaration trotzdem einem Signal des Typs Standard-Logic-Vector zu, so füllt das Synthese-Werkzeug die führenden Bits mit Nullen. So wird aus '1' dann "00000001". Die Zählung ist richtig.

Nutz man, wie bei Zähler b, die Deklaration "1" obwohl es sich um ein Signal des Types Standard-Logic-Vector handelt, so ersetzt das Synthese-Werkzeug es zu "11111111". Die Zählung ist falsch.

Der Zähler c ist so am sinnvollsten beschrieben, da hier direkt klar ist, um was es geht. Die Zählung ist richtig.

Benennung von Signalen im allgemeinen

Die Lesbarkeit von VHDL-Dateien steht und fällt mit der Benennung der Variablen.
Signale sollten deshalb im Klartext mit deren Funktion benannt sein. Besteht die Funktion aus mehreren Wörtern, so sind diese direkt aneinander zu binden, wobei der ersten Buchstabe jeweils gross geschrieben wird.
Hier mal ein Beispiel dazu:
Paket, Counter, AdrCounter, DatCounter, SensorDaten, SensorElement, WESensorRAM

Benennung von Statemachine-Signalen

Statemachines sind prima Konstrukte, um Ablaufsteuerung vorzunehmen.
Damit man diese im Quelltext besser von den anderen Signalen unterscheiden kann, sind sie mit einem kleinen "s" am Anfang zu versehen.
Hier mal ein Beispiel dazu:
TYPE type_sHaendleDatas IS ( sReset, sStartSystem, sCheckValues, sSaveValuesInRAM);
signal sHaendleDatas : type_sHaendleDatas;

Signalzuweisungen ausserhalb von Prozessen

Während man Signale die im Concurrentbereich erzeugt wurden, auch in Prozessen verarbeiten kann,
kann man nicht das gleiche Signal im Concurrentbereich sowie innerhalb eines Prozesses mit Werten zuweisen.
Auch nicht, wenn es aus logischer oder zeitlicher Abfolge nicht zu einer Kollision führen würde.
Das Simulationswerkzeug erzeugt keine Fehlermeldung und simuliert den Code so wie gewünscht.
Das Synthese-Werkzeug erzeugt auch keine Fehlermeldung, so dass der Entwickler glaubt, alles richtig gemacht zu haben.
Auf dem FPGA kommt es dann aber zu nicht reproduzierbaren Fehlern.

WAIT-Anweisung

Die WAIT-Anweisung ist nicht Synthesefähig! Sie kann nur im Simulator für Testbenches eingesetzt werden.

Sensitivity List bei Prozessen

Während ein Prozess in einer Testbench keine Sensitivity List haben muss, muss hingegen in einer synthesefähigen VHDL-Datei die Sensitivity List mindestens aus dem Clock-Signal bestehen. Sinnvoller sogar aus Clock- und Reset-Signal.

Triggern auf das Clock-Signal (steigende und fallende Flanke)

Ein Prozess wird gestartet, wenn sich die Signale in der Sensitivity List ändern.
In den meisten Fällen folgt dann im Quelltext folgende Syntax:
if (CLK'event and CLK='1') then
Also, wenn der Clock sich ändert (event) und 1 wird (CLK=1), dann folgen darunter stehende Anweisungen. Also mit steigender Flanke von Clock.
Wenn die Hardware des FPGA das unterstützt, kann auch auf der negativen Flanke von Clock gestartet werden. Das sieht dann so aus:
if (CLK'event and CLK='0') then

Signalvectoren mit 0 oder 1 vorbesetzen

Es kommt häufiger vor als man denkt: Siganle werden an irgend einer Stelle mit Nullen oder Einsen vorbesetzt.
Das macht man dann eigentlich so:
AdrCounter <= "0000000000"; -- 10Bit breiter Adressbus
Wenn Sie jetzt einen 1000 Zeilen langen VHDL-Quelltext haben oder dieses Signal in mehreren Untergruppen (Dateien) so verwendet haben, dann müssen Sie jede Zeile ändern, wenn sich die Busbreite mal ändern sollte.
Besser und effektiver ist die Verwendung des Befehls "others":
AdrCounter <= (others => '0');
Dann entscheidet das Simulations- bzw. das Synthese-Werkzeug wieviele Nullen es dort einfügen muss.
Je nach Hersteller können Sie auch Signale mit Einsen vorbesetzten. Der Compiler sagt Ihnen dann schon, ob er das gut findet: AdrCounter <= (others => '1');

Was tun bei: inferring latch(es) for signal or variable?

Sie sind dabei ein FPGA zu synthetisieren und schauen sich die Warnings an, die der Compiler erzeugt hat. Bei einem Eintrag werden Sie stutzig:

Warning (NR): VHDL Process Statement warning at DATEI.vhd(ZEILE): inferring latch(es) for signal or variable "Q", which holds its previous value in one or more paths through the process

Ein Prozess wird fehlerfrei wie folgt beschrieben:

process (CLK, RST)
begin
 if (RST='1') then
  Q <= '0';
  Z <= '0';
 elsif (CLK'event and CLK='1') then -- CLK rising edge
  Q <= D;
  Z <= A;
 end if;
end process;

Hier werden zwei FlipFlops (Q und Z) beschrieben.
Im folgenden Prozess gibt es einen Fehler:

process (CLK, RST)
begin
 if (RST='1') then
  Q <= '0';
  Z <= '0';
 elsif (CLK'event and CLK='1') then -- CLK rising edge
  Q <= D;
 end if;
end process;

Hier wird nur ein FlipFlop (Q) richtig beschrieben.
Bei dem zweiten FlipFlop (Z) wird lediglich der Ausgang im Reset-Fall zurückgesetzt.

Was mache ich mit unbenutzen Signalen?

Es kommt immer mal wieder vor, dass man eine Instanz verwenden muss, die sehr allgemein gehalten ist.
Der Entwickler hat es sehr gut gemeint und uns Anwendern eine Reihe von Signalen zur Verfügung gestellt, die wir nutzen können.

Namensgleichheit von Componente und Signalnamen

Ein Componentname darf nicht gleichzeitig ein Signalname sein.
Wenn das so ist, dann gibt es eine Fehlermeldung und man muss eines der Beiden umbenennen:
Bsp: sysclk -> sys_clk

component sysclk
PORT
   (
   areset          : IN STD_LOGIC  := '0';
   inclk0          : IN STD_LOGIC  := '0';
   c0              : OUT STD_LOGIC;
   locked          : OUT STD_LOGIC
   );
END component;

signal SYSCLK : std_logic;

Dieses Beispiel erzeugt den Fehler.

Problem: std_logic_vector(0 downto 0)

Problem: Ein Wizard erstellt eine Entity mit Signalen des typs std_logic_vector(0 downto 0) und diese müssen in der Port Map mit einem Signal des Type std_logic beschrieben werden.

component diff_sysclk_in_iobuf_in_e4j
PORT
   (
   datain      :    IN  STD_LOGIC_VECTOR(0 DOWNTO 0);
   datain_b    :    IN  STD_LOGIC_VECTOR(0 DOWNTO 0);
   dataout     :    OUT  STD_LOGIC_VECTOR(0 DOWNTO 0)
   );
END component;

diff_sysclk_in_inst : diff_sysclk_in_iobuf_in_e4j
PORT MAP (
   datain       => xSYSCLK_P,
   datain_b     => xSYSCLK_N,
   dataout      => SYS_CLK
   );

Dies führt zu einer Fehlermeldung:
Error 10476: VHDL error at top.vhd153: type of identifier xSYSCLK_P does not agree with its usage as std_logic_vector type
Die Entity und die Componente kann nicht geändert werden, wohl aber die Zuweisung:

diff_sysclk_in_inst : diff_sysclk_in_iobuf_in_e4j
PORT MAP (
   datain(0)     => xSYSCLK_P,
   datain_b(0)   => xSYSCLK_N,
   dataout       => SYS_CLK
   );
 
Qualitätsmanagement-Stempel von YASKO
Qualitätsmanagement nach
DIN EN ISO 9001:2015
Logo des FED
Mitglied im Fachverband für
Elektronik-Design e.V. (FED)