Rechnen in VHDL

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Um in VHDL synthetisierbare Berechnungen zu beschreiben gibt es verschiedene Möglichkeiten.

Rechnen mit Ganzzahlen

Integer-Variablen

Eine Möglichkeit, die von den meisten Synthesetools unterstützt wird, ist das Rechnen im Integer-Bereich. Das heißt, entweder man arbeitet von Vornherein mit Integer-Signalen, oder man konvertiert einen std_logic_vector zu einem Integer und benutzt die ganz normalen Rechenoperatoren wie +, -, * usw.

Wichtig ist dabei, den Integertyp auf den Bereich zu begrenzen, der auch wirklich benötigt wird, sonst werden alle Berechnungen mit 32 Bit Wortlänge implementiert:

variable x: integer range 0 to 100;
...
x := x + 1;

Integer bieten sich für interne Berechnungen an, z. B. Zähler, die bei Erreichen eines bestimmten Zählerstandes einen Ausgang setzen; wenn man dagegen z. B. eine ALU implementiert, die mit externen Vektoren einer bestimmten Breite rechnen muss, ist es günstiger/bequemer direkt auf Basis von Vektoren zu rechnen.

Schlecht: Direktes Rechnen mit std_logic_vector

Die von vielen Synthesetools unterstützten, aber nicht IEEE-standardisierten Packages std_logic_unsigned und std_logic_signed erlauben es, direkt mit std_logic_vector zu rechnen. Je nachdem ob das signed oder unsigned-Package eingebunden wird werden alle (!) Berechnungen als signed oder unsigned interpretiert - das ist natürlich unsauber. Trotzdem werden diese Packages noch häufig verwendet, besonders auch von automatischen Synthesetools (z.B. SystemGenerator), obwohl es mit numeric_std gar nicht mehr nötig ist.

Besser: Rechnen mit numeric_std

Das Package numeric_std definiert zwei neue Typen signed und unsigned als Array von std_logic, und definiert für diese Typen alle gebräuchlichen Rechenoperatoren, auch zusammen mit Integern (x + 1):

signal x: unsigned(7 downto 0); -- Zahlenbereich: 0 bis 2**8-1
signal y: signed(7 downto 0); -- Zahlenbereich: -2**7 bis 2**7-1
...
x <= x + 1;
y <= x + y;

Nur bei der Zuweisung von Integern an unsigned/signed-Signale muss man von Hand konvertieren:

x <= to_unsigned(2, x'length);
y <= to_signed(2, y'length);

Der zweite Parameter gibt die Länge des Vektors an in den konvertiert werden soll.

Um von unsigned/signed zu Integer zu konvertieren gibt es die Funktion to_integer:

i <= to_integer(x);

Umsetzbare Operatoren

Egal ob man mit Integern oder unsigned/signed rechnet, die Software kann nur bestimmte Rechenoperationen in Hardware umsetzen. Schiebeoperationen, Additionen und Subtraktionen sind nie ein Problem, Multiplikationen werden in Multiplizierernetzwerke umgesetzt oder auf Hardwaremultiplizierer gemappt. Divisionen und Modulo-Operationen dagegen werden in der Regel nicht oder nur für Zweierpotenzen unterstützt (Xilinx ISE). Werden Divisionen benötigt, muss man einen Divisionsalgorithmus von Hand implementieren oder einen entsprechenden IP-Core einbinden.

Festkommazahlen (Fixed Point)

numeric_std und Skalierung von Hand

Natürlich ist es möglich numeric_std (signed, unsigned) oder Integer zu verwenden und die Skalierung entsprechend der gewünschten Zahleninterpretation von Hand durchzuführen. Wie das geht wird z. B. hier erklärt: https://noppa.aalto.fi/noppa/kurssi/s-89.3510/materiaali/fixed-point_algorithm_development.pdf

IEEE.Fixed_Pkg

Für die nächste Version von VHDL ist das Paket IEEE.Fixed_Pkg vorgesehen. Ähnlich wie bei numeric_std werden hier neue Typen definiert, ufixed (unsigned fixed point) und sfixed (signed fixed point). Die Anzahl der Vor- und Nachkommastellen wird bei der Signal-/Variablendeklaration angegeben.

signal a, b : sfixed (7 downto -6); -- 14 Bit breit, 6 Nachkommastellen

Der Vorteil ist, dass man sich die manuelle Skalierung sparen kann, und mit der Funktion resize einfach zwischen verschiedenen Zahlenformaten konvertieren kann.

Leider wird weder das package noch die Vorgehensweise mit Vorkomma- und Nachkomma-Bits zu arbeiten, von den einschlägigen Synthesetools unterstützt.

Gleitkommazahlen (Floating Point)

Gleitkommazahlen haben den Vorteil, dass man große Dynamikbereiche abdecken kann, für die man mit Festkommazahlen riesige Bitlängen benötigen würde. Der Nachteil ist der höhere Rechenaufwand: für die Addition zweier Festkommazahlen reicht ein gewöhnlicher Addierer, bei Gleitkommazahlen sind dazu auch Multiplikationen nötig, wenn die beiden Zahlen verschiedene Exponenten haben. Bei der Realisierung in Hardware (FPGAs) bedeutet das dass mehr Fläche benötigt wird und die Taktrate sinkt (bzw. mehr Zyklen für einen Berechnungsschritt nötig sind).

Als erstes muss gesagt werden, dass VHDL sehr wohl Gleitkommazahlen unterstützt (Typ real), diese aber nicht ohne weiteres synthetisierbar sind. Man muss die Zahlen auf jeden Fall erst in eine std_logic-Darstellung bringen.

Rechnen "von Hand" bzw. mit FPU-Cores

Auch mit Gleitkommazahlen kann man natürlich "von Hand" arbeiten, indem man beliebige Vektoren in Mantisse und Exponent zerlegt und diese getrennt verarbeitet. Für Rechnungen mit Gleitkommazahlen gibt es auch verschiedene fertige Cores, z. B. FPU100 bei OpenCores, welche die Berechnungen in mehreren Zyklen durchführen.

IEEE.Float_Pkg

Analog zu IEEE.Fixed_Pkg gibt es ein entsprechendes Package für Floating Point. Der Nachteil gegenüber FPU-Cores ist, dass die Berechnungen in diesem Package rein kombinatorisch implementiert sind, das heißt keine getaktete Struktur vorgesehen ist. Auch wenn man die Ein- und Ausgänge einer Rechenoperation mit mehreren Takten verzögert, schafft Xilinx ISE 9.2 es nicht, daraus eine Pipelinestruktur zu synthetisieren. Die Folge: Die Struktur wird sehr groß und die Berechnungen sehr langsam. Das ließe sich seitens der Hersteller theoretisch beheben, aber angesichts der Tatsache, dass Float_Pkg mit vielen Tools noch nicht einmal kompilierbar ist, ist das wohl noch in weiter Ferne. Zudem würde eine willkürliche Taktung auf die mögliche Geschwindigkeit des FPGAs Rücksicht nehmen müssen und letzlich trotzdem nachoptimiert werden.

Inzwischen bieten die neueren Versionen allerdings die Möglichkeit, der Resynthese auf Logikebene, welche in der Regel gut in der Lage ist, die zur Verfügung stehenden FlipFlops gut zu verteilen. Diese Möglichkeit besteht konkret in der Verwendung der Option "asynchronous to synchronous" (Xilinx) sowie der Nachschaltung von FF-Bänken, die mit dem "register balancing" (Xilinx) bzw dem "register retiming" (Altera) in die Kombinatorik hineingeschoben werden.