Analyse und Erweiterung der - karl

Analyse und Erweiterung der - karl
Masterarbeit
Analyse und Erweiterung der
Hardwarebeschreibungssprache HDCaml
Karl Flicker
————————————–
Institut für Technische Informatik
Technische Universität Graz
Vorstand: O. Univ.-Prof. Dipl.-Ing. Dr. techn. Reinhold Weiß
Begutachter: Univ.-Prof. Dipl.-Ing. Dr. techn. Bernhard Rinner
Betreuer: Dipl.-Ing. Dr. techn. Manfred Mücke
Graz, im März 2008
Kurzfassung
HDCaml ist eine Hardwarebeschreibungssprache, die auf der funktionalen Programmiersprache OCaml
(Objective Caml) basiert. Beide Sprachen sind Open-Source-Projekte und können beliebig verwendet und
modifiziert werden.
HDCaml beschränkt sich auf die Register-Transfer-Beschreibung von digitalen, synchronen Schaltungen mit einem einzigen, impliziten Taktsignal und ist in diesem Teilgebiet einfacher, übersichtlicher und
flexibler als z.B. VHDL. Im Vergleich zu VHDL wird die Wiederverwendbarkeit und Änderbarkeit von
Code wesentlich vereinfacht.
HDCaml kann in bestehende Arbeitsabläufe integriert werden, da es wahlweise Verilog-, VHDL- oder
C-Code (mit SystemC-Interface) erzeugt. Neben der Simulation in einem der Ausgabeformate können
HDCaml-Designs auch direkt in OCaml simuliert werden.
In der vorliegenden Masterarbeit wird HDCaml analysiert, auf Erweiterbarkeit überprüft und es werden
Verbesserungen vorgenommen und ausführlich beschrieben. Zu den umgesetzten Verbesserungen zählen
Überprüfungen auf Designfehler, neue Warnungen und zielführendere Fehlermeldungen. Auch der eingebaute Simulator wird vervollständigt und korrigiert. Im Rahmen der Literaturrecherche wird HDCaml mit
den funktionalen Sprachen HML, Confluence, HDFS und Sassy verglichen.
Anschließend wird HDCaml auf seine Eignung für Bildverarbeitung (BV) untersucht. Zu diesem Zweck
wird ein Faltungsfilter in HDCaml implementiert und getestet. Die HDCaml-Implementierung wird mit
einer Implementierung in der Sprache Sassy (Single-Assignment-C) verglichen, die für die Beschreibung
von hardwarenahen BV-Algorithmen besonders geeignet ist.
Die Ergebnisse der Arbeit zeigen, dass die Benutzbarkeit (insbesondere bei der Fehlersuche) durch die
vorgenommenen Erweiterungen wesentlich verbessert wird. Bezüglich Bildverarbeitung zeigt die Arbeit,
dass HDCaml dafür prinzipiell geeignet ist, jedoch nicht an speziell dafür konzipierte Sprachen wie Sassy
heranreicht.
Schlüsselworte:
HDCaml, OCaml, VHDL, Verilog, SystemC, HML, Confluence, HDFS, Sassy, SA-C, Simulation, HBS,
Hardwarebeschreibungssprache, funktionale Programmierung, generische Beschreibung, Bildverarbeitung
ii
Abstract
HDCaml is a hardware description language which is based on OCaml (Objective Caml). Both languages
are open-source projects and can be used and modified without restrictions.
The application domain of HDCaml models is confined to register-transfer-descriptions of digital, synchronous circuits with a single, implicit clock. In this subarea it is easier, more concise and more flexible
than e.g. VHDL. Compared to VHDL, reusability and genericity of code is considerably improved.
HDCaml can be integrated into existing workflows since it generates Verilog, VHDL or C code (with
SystemC interface). In addition to simulation of models in one of the output formats, HDCaml models can
be simulated directly within OCaml.
In this work, HDCaml is analyzed and possible extensions are reviewed. A subset of suggested extensions have been implemented and are described in detail. Amongst these improvements are better design
rule checks, new warnings and helpful error messages including line numbers. The built-in simulator is
completed and corrected, too. HDCaml is compared to the functional languages HML, Confluence, HDFS
and Sassy.
Subsequently HDCaml is analyzed regarding its suitability for image processing (IP). To this end a
convolution filter is implemented and tested. The HDCaml implementation is compared to a functionally
identical implementation using the Sassy (Single-Assignment-C) programming language, which is especially suitable for the description of IP algorithms for hardware.
The results of this thesis show, that the usability of HDCaml (especially regarding debugging) is considerably improved by the implemented extensions. Concerning image processing, the thesis shows, that
HDCaml is appropriate to this task, but that it doesn’t get close to languages like Sassy, which are specifically created for this purpose.
Keywords:
HDCaml, OCaml, VHDL, Verilog, SystemC, HML, Confluence, HDFS, Sassy, SA-C, Simulation, HDL,
hardware description language, functional programming, generic description, image processing,
iii
Inhaltsverzeichnis
Abkürzungen
xii
1
Einleitung
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Zielsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Gliederung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
Problemanalyse
2.1 Hardware-Beschreibungsmöglichkeiten . . . . . . . . . . . . . . .
2.1.1 Verhaltensbeschreibung . . . . . . . . . . . . . . . . . . .
2.1.2 RTL-Beschreibung . . . . . . . . . . . . . . . . . . . . . .
2.1.3 Strukturbeschreibung . . . . . . . . . . . . . . . . . . . . .
2.1.4 Generatoren für regelmäßig aufgebaute Strukturen . . . . .
2.2 Diskussion verbreiteter Hardwarebeschreibungssprachen . . . . . .
2.2.1 Einschränkungen . . . . . . . . . . . . . . . . . . . . . . .
2.2.2 Umgehen der Einschränkungen (Code-Generierung) . . . .
2.3 Verbesserung einer Sprache durch Einschränkung der Funktionalität
2.3.1 Am Beispiel von HDCaml . . . . . . . . . . . . . . . . . .
2.4 Implementierung: Compiler vs. Einbettung . . . . . . . . . . . . .
2.4.1 In Bezug auf HDCaml . . . . . . . . . . . . . . . . . . . .
2.5 Funktionale Sprachen . . . . . . . . . . . . . . . . . . . . . . . . .
2.5.1 Sprachen, die mehrere Paradigmen unterstützen . . . . . . .
2.5.2 Vorteile von OCaml . . . . . . . . . . . . . . . . . . . . .
2.6 Bildverarbeitung in Hardware . . . . . . . . . . . . . . . . . . . .
2.6.1 Hardwarebeschreibungssprachen für Bildverarbeitung . . .
2.6.2 Beispiel: Faltungsfilter . . . . . . . . . . . . . . . . . . . .
2.7 Kriterien für Evaluierung der Ergebnisse . . . . . . . . . . . . . . .
2.7.1 HDCaml-Erweiterungen . . . . . . . . . . . . . . . . . . .
2.7.2 Bildverarbeitung . . . . . . . . . . . . . . . . . . . . . . .
3
1
1
2
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
4
4
5
7
7
8
12
12
13
13
14
15
15
16
17
17
17
18
19
19
Die Hardwarebeschreibungssprache HDCaml
3.1 Funktionalität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.1 Konzeptuelle Einschränkungen . . . . . . . . . . . . . . . . . . .
3.1.2 Einschränkungen aufgrund nicht implementierter Sprachelemente
3.1.3 Vorteilhafte Eigenschaften von HDCaml . . . . . . . . . . . . . .
3.2 Aufbau eines HDCaml-Programms . . . . . . . . . . . . . . . . . . . . .
3.2.1 Funktionen und Wiederverwendung . . . . . . . . . . . . . . . .
3.2.2 Operatoren und Unterscheidung signed/unsigned . . . . . . . . .
3.2.3 Ebenen eines HDCaml-Programms . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
20
20
21
21
21
22
24
26
26
iv
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3.3
3.4
3.5
3.6
4
5
Arbeitsablauf . . . . . . . . . . . . . . . . . . . . . . . .
3.3.1 Schreiben der HBS-Modelle . . . . . . . . . . . .
3.3.2 Simulation der Schaltung . . . . . . . . . . . . . .
Hardwarebeschreibung . . . . . . . . . . . . . . . . . . .
3.4.1 Verhaltensbeschreibung . . . . . . . . . . . . . .
3.4.2 RTL-Beschreibung . . . . . . . . . . . . . . . . .
3.4.3 Strukturbeschreibung . . . . . . . . . . . . . . . .
3.4.4 Generatoren für regelmäßig aufgebaute Strukturen
Implementierung . . . . . . . . . . . . . . . . . . . . . .
3.5.1 Basissprache OCaml . . . . . . . . . . . . . . . .
3.5.2 Module und Build-Prozess von HDCaml . . . . .
3.5.3 Typsystem und Datenstrukturen . . . . . . . . . .
3.5.4 Dokumentation des Quellcodes . . . . . . . . . .
HDCaml im Internet . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
27
29
31
32
32
32
34
34
37
37
38
39
40
41
Mit HDCaml verwandte Projekte
4.1 HML . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Spracheigenschaften und Funktionalität . . . . .
4.1.2 Implementierung und Verwendung . . . . . . . .
4.1.3 Datentypen und Objekte . . . . . . . . . . . . .
4.1.4 Sprachkonstrukte . . . . . . . . . . . . . . . . .
4.1.5 Beschreibung von Hardware . . . . . . . . . . .
4.1.6 VHDL-Generierung . . . . . . . . . . . . . . .
4.2 Vergleich von HML mit HDCaml . . . . . . . . . . . .
4.2.1 Gemeinsamkeiten . . . . . . . . . . . . . . . . .
4.2.2 Unterschiede . . . . . . . . . . . . . . . . . . .
4.2.3 HDCaml-Beispiele . . . . . . . . . . . . . . . .
4.3 Confluence . . . . . . . . . . . . . . . . . . . . . . . .
4.3.1 Spracheigenschaften . . . . . . . . . . . . . . .
4.3.2 Dokumentation und Code-Beispiel . . . . . . . .
4.4 Vergleich von Confluence mit HDCaml . . . . . . . . .
4.4.1 Unterschiede . . . . . . . . . . . . . . . . . . .
4.4.2 Verbesserungen in HDCaml . . . . . . . . . . .
4.4.3 Nicht implementierte Funktionalität in HDCaml
4.5 HDFS . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.5.1 Basissprache F# . . . . . . . . . . . . . . . . .
4.5.2 Beschreibung von Hardware . . . . . . . . . . .
4.6 Vergleich von HDFS mit HDCaml . . . . . . . . . . . .
4.6.1 Verbesserungen in HDFS . . . . . . . . . . . . .
4.6.2 Nachteile in HDFS . . . . . . . . . . . . . . . .
4.7 Sassy . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.7.1 Spracheigenschaften . . . . . . . . . . . . . . .
4.8 Vergleich von Sassy mit HDCaml . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
42
42
42
42
43
45
47
47
48
48
49
50
51
51
52
52
52
54
55
56
56
56
56
57
58
58
59
60
Erweiterung von HDCaml
5.1 Aufbau von HDCaml 0.2.9 . . . . . . . . . . . .
5.2 Analyse der Verbesserungsmöglichkeiten . . . .
5.2.1 Verbesserte Fehlermeldungen . . . . . .
5.2.2 Korrektur fehlerhafter Implementierungen
5.2.3 Neue Funktionalität . . . . . . . . . . . .
5.2.4 HBS-Generatoren . . . . . . . . . . . . .
5.2.5 Sonstiges . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
61
61
62
63
64
65
65
66
v
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5.3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
66
67
69
70
70
70
71
71
71
73
73
73
73
75
76
77
79
Schlussbemerkung und Ausblick
6.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
81
81
5.4
5.5
5.6
6
Auswahl und Implementierung . . . . . . . . . .
5.3.1 Verbesserte Fehlermeldungen . . . . . .
5.3.2 Korrektur fehlerhafter Implementierungen
5.3.3 Neue Funktionalität . . . . . . . . . . . .
5.3.4 HBS-Generatoren . . . . . . . . . . . . .
5.3.5 Sonstiges . . . . . . . . . . . . . . . . .
Evaluierung der HDCaml-Erweiterungen . . . .
5.4.1 Kriterien und Änderungen . . . . . . . .
5.4.2 Evaluierung der Änderungen . . . . . . .
Implementierung eines Faltungsfilters . . . . . .
5.5.1 Hardware-Modell . . . . . . . . . . . . .
5.5.2 Testbench . . . . . . . . . . . . . . . . .
Evaluierung des Faltungsfilters . . . . . . . . . .
5.6.1 Korrektheit . . . . . . . . . . . . . . . .
5.6.2 Vergleich mit Spezialsprachen wie Sassy
5.6.3 Unterschiede im Aufwand . . . . . . . .
5.6.4 Simulation und Synthese . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Literaturverzeichnis
83
A HDCaml-0.2.10 Language Reference Manual
A.1 Module Design: Digital design entry. . . . . . . . . . . . . . . .
A.1.1 Enhanced features compared to HDCaml 0.2.9 . . . . .
A.1.2 Circuit Types . . . . . . . . . . . . . . . . . . . . . . .
A.1.3 Circuit Data . . . . . . . . . . . . . . . . . . . . . . . .
A.1.4 Signal Assignment and Annotation . . . . . . . . . . .
A.1.5 Width Utilities . . . . . . . . . . . . . . . . . . . . . .
A.1.6 Top Level Ports . . . . . . . . . . . . . . . . . . . . . .
A.1.7 Constants . . . . . . . . . . . . . . . . . . . . . . . . .
A.1.8 Bit Manipulation . . . . . . . . . . . . . . . . . . . . .
A.1.9 Bitwise Logicals . . . . . . . . . . . . . . . . . . . . .
A.1.10 Arithmetics . . . . . . . . . . . . . . . . . . . . . . . .
A.1.11 Shifting . . . . . . . . . . . . . . . . . . . . . . . . . .
A.1.12 Comparisons . . . . . . . . . . . . . . . . . . . . . . .
A.1.13 Muxing . . . . . . . . . . . . . . . . . . . . . . . . . .
A.1.14 Registers . . . . . . . . . . . . . . . . . . . . . . . . .
A.2 Module Simulate: Simulate designs within OCaml. . . . . . . .
A.2.1 Warning . . . . . . . . . . . . . . . . . . . . . . . . . .
A.3 Module Systemc: SystemC model generation. . . . . . . . . . .
A.3.1 Warning . . . . . . . . . . . . . . . . . . . . . . . . . .
A.3.2 Usage of the generated C-Model . . . . . . . . . . . . .
A.4 Module Verilog: Verilog netlist generation. . . . . . . . . . . . .
A.5 Module Vhdl: VHDL netlist generation. . . . . . . . . . . . . .
A.5.1 Warning . . . . . . . . . . . . . . . . . . . . . . . . . .
A.5.2 Workaround for tools which are not VHDL’93 compliant
vi
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
85
85
85
90
90
91
91
91
91
92
93
93
93
93
94
94
94
95
95
95
95
96
96
96
96
B Auszug aus dem Vortrag: A Confluence
97
C HDCaml-Details
C.1 Datentyp signal . . . . . . . . . . . . . . . . . . .
C.1.1 Dangling Signal . . . . . . . . . . . . . . . .
C.1.2 HBS-Annotation . . . . . . . . . . . . . . . .
C.2 Ableitung und Überprüfung der Bitbreiten . . . . . . .
C.2.1 Überprüfung der Bitbreiten durch den Compiler
C.3 Datentyp circuit . . . . . . . . . . . . . . . . . . .
C.3.1 Teilschaltungen (subcircuits) . . . . . . . . . .
C.4 Umbenennung von ungültigen Bezeichnern . . . . . .
C.5 Generierung des C-Modells . . . . . . . . . . . . . . .
C.6 Generierung der VHDL-Netzliste . . . . . . . . . . . .
C.7 Implementierung von bin_tree . . . . . . . . . . .
C.8 Varianten der Verwendung von HDCaml/OCaml . . . .
vii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
98
98
99
99
99
101
101
101
102
102
102
104
105
Abbildungsverzeichnis
1.1
Entwurfsprozess bei Benutzung von HDCaml . . . . . . . . . . . . . . . . . . . . . . . .
2.1
2.2
2.3
2.4
2.5
Halb- und Volladdierer . . . . . . . .
RTL-Beschreibung . . . . . . . . . .
Funktionsweise von map2 . . . . . .
Funktionsweise eines Faltungsfilters .
Implementierung eines Faltungsfilters
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
5
17
18
19
3.1
3.2
3.3
3.4
3.5
3.6
Interne Abläufe bei der Ausführung eines HDCaml-Programms . . .
Ablauf eines HDCaml-Programms . . . . . . . . . . . . . . . . . .
Erzeugung rückgekoppelter Register mit Hilfe des Dangling Signals
Funktionsweise von bin_tree . . . . . . . . . . . . . . . . . . .
Entstehung von OCaml . . . . . . . . . . . . . . . . . . . . . . . .
Abhängigkeiten der Module von HDCaml 0.2.10 voneinander . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
29
33
35
37
39
4.1
4.2
4.3
4.4
Verwandtschaft von HML und HDCaml . . .
Funktionsweise von map . . . . . . . . . . .
Verwandtschaft von Confluence und HDCaml
Aufbau von Schleifen in Sassy . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
48
51
54
59
5.1
5.2
5.3
5.4
5.5
Abhängigkeiten der Module von HDCaml 0.2.9 (mit VHDL-Gen.) voneinander
Funktionsweise von find_node . . . . . . . . . . . . . . . . . . . . . . . .
Erzeugen einer ausführbaren Simulation des Faltungsfilters . . . . . . . . . . .
Vergleich der Filterergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . .
Vergleich Originalbild und Ergebnisbild nach Hochpass-Filterung . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
62
69
75
76
76
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
viii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
Tabellenverzeichnis
2.1
Eigenschaften der Implementierungsvarianten von DSLs . . . . . . . . . . . . . . . . . .
13
3.1
Funktionen und Operatoren zur Schaltungsbeschreibung in HDCaml . . . . . . . . . . . .
25
4.1
4.2
4.3
4.4
4.5
4.6
Spracheigenschaften von HML im Vergleich mit VHDL
Datentypen in HML . . . . . . . . . . . . . . . . . . . .
Operatoren in HML . . . . . . . . . . . . . . . . . . . .
Signalzuweisungen in HML . . . . . . . . . . . . . . .
Unterschiede von HDCaml und HML . . . . . . . . . .
Unterschiede zwischen Sassy und HDCaml . . . . . . .
.
.
.
.
.
.
43
43
45
46
50
60
5.1
5.2
5.3
5.4
5.5
5.6
5.7
Zeilenanzahl der Module von HDCaml 0.2.9 (mit VHDL-Gen.) . . . . . . . . . . . . . . .
Zeilenanzahl der Module von HDCaml 0.2.10 . . . . . . . . . . . . . . . . . . . . . . . .
Übersicht: auf Änderungen zutreffende Kriterien . . . . . . . . . . . . . . . . . . . . . .
Zeilenanzahl der verschiedenen Implementierungen des Faltungsfilters . . . . . . . . . . .
VHDL-Codegenerierung und FPGA-Synthese für verschiedene HW-Modelle (mit Reset) .
VHDL-Codegenerierung und FPGA-Synthese für verschiedene HW-Modelle (ohne Reset)
Verschiedene Simulationen des Faltungsfilters (Minimalversion) . . . . . . . . . . . . . .
63
67
72
77
79
80
80
ix
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Verzeichnis der Listings
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
2.12
2.13
2.14
Verhaltensbeschreibung eines Volladdierers in VHDL . . . . . . . .
Verhaltensbeschreibung eines Halbaddierers in VHDL . . . . . . .
RTL-Beschreibung eines kombinatorischen Volladdierers in VHDL
RTL-Beschreibung eines 4-bit-Zählers in VHDL . . . . . . . . . .
Strukturbeschreibung eines Volladdierers in VHDL . . . . . . . . .
4-bit-Addierer mit generate in VHDL . . . . . . . . . . . . . .
4-bit Multiplizierer in Verilog . . . . . . . . . . . . . . . . . . . . .
3-fach Multiplexer mit unerwünschtem Latch in VHDL . . . . . . .
Register in VHDL . . . . . . . . . . . . . . . . . . . . . . . . . .
4-Input Multiplexer in VHDL . . . . . . . . . . . . . . . . . . . .
Addierer beliebiger Breite in VHDL . . . . . . . . . . . . . . . . .
Fakultät: Imperativ in OCaml . . . . . . . . . . . . . . . . . . . . .
Fakultät: Imperativ in C . . . . . . . . . . . . . . . . . . . . . . . .
Fakultät: Rekursiv in OCaml . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
5
5
6
6
7
9
9
10
10
11
16
16
16
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
3.13
3.14
3.15
Kurzes HDCaml-Programm zur Besprechung des Aufbaus . . . . . . . . .
Aus HDCaml-Beschreibung generierter VHDL-Code . . . . . . . . . . . .
Fehlerhaftes HDCaml-Programm . . . . . . . . . . . . . . . . . . . . . . .
Korrektes HDCaml-Programm . . . . . . . . . . . . . . . . . . . . . . . .
HDCaml-Beispielprogramm zur Besprechung des Arbeitsablaufs . . . . . .
Aus HDCaml-Beschreibung generierter VHDL-Code . . . . . . . . . . . .
Aus HDCaml-Beschreibung generierter Verilog-Code . . . . . . . . . . . .
Testbench für Simulation des C-Modells . . . . . . . . . . . . . . . . . . .
RTL-Beschreibung eines kombinatorischen Volladdierers in HDCaml . . .
Zähler beliebiger Breite in HDCaml . . . . . . . . . . . . . . . . . . . . .
Aus Halbaddierern und ODER-Gatter aufgebauter Volladdierer in HDCaml
Anwendungsbeispiele für map2 . . . . . . . . . . . . . . . . . . . . . . .
Anwendungsbeispiele für bin_tree . . . . . . . . . . . . . . . . . . . .
Code-Generator für beliebige Multiplexer in HDCaml . . . . . . . . . . . .
Schritte beim Kompilieren und Installieren von HDCaml . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
24
27
27
28
28
28
31
32
33
34
35
35
36
40
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
Wert-Deklaration in HML . . . . . . . . . . . . . . . . . . . . .
Intern-Deklaration in HML . . . . . . . . . . . . . . . . . . . . .
Deklaration von Bitbreiten in HML: global und pro Signal . . . .
Addierer als normale Funktion . . . . . . . . . . . . . . . . . . .
Addierer als HW-Funktion . . . . . . . . . . . . . . . . . . . . .
Zähler in HML . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 bit-Zähler in HML . . . . . . . . . . . . . . . . . . . . . . . . .
RTL-Beschreibung eines kombinatorischen Volladdierers in HML
Strukturbeschreibung eines Volladdierers in HML . . . . . . . . .
Array-Generator in HML . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
44
44
44
46
46
47
47
47
47
48
x
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4.11
4.12
4.13
4.14
4.15
4.16
4.17
4.18
4.19
Volladdierer in HDCaml (RTL-Beschreibung)
Array-Generator in HDCaml . . . . . . . . .
Array-Generator in HDCaml mit map . . . .
Aufwärts/Abwärts-Zähler in Confluence . . .
Aufwärts/Abwärts-Zähler in HDCaml . . . .
Verhaltensbeschreibung in Verilog . . . . . .
Verhaltensbeschreibung in HDFS . . . . . . .
Sassy-Äquivalent zur Verwendung von map2
Verwendung von map2 in HDCaml . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
50
50
51
53
53
57
57
59
60
5.1
5.2
5.3
5.4
5.5
5.6
5.7
Beispiel für eine Fehlermeldung mit Stack-Trace . . . .
Beispiel für eine Warnung . . . . . . . . . . . . . . . .
Minimal-Implementierung des Faltungsfilters in HDCaml
C-Testbench für Faltungsfilter (Minimalversion) . . . . .
Faltungsfilter in MATLAB . . . . . . . . . . . . . . . .
Faltungsfilter in Sassy . . . . . . . . . . . . . . . . . . .
Faltungsfilter in VHDL . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
68
69
74
74
75
77
78
C.1
C.2
C.3
C.4
C.5
C.6
C.7
C.8
C.9
Datentyp signal in HDCaml (leicht vereinfacht) . . . . . . . . . . . . .
Implementierung des Annotation-Operators in HDCaml (leicht vereinfacht)
Funktion width von HDCaml (leicht vereinfacht) . . . . . . . . . . . . .
Überprüfung der Bitbreiten am Beispiel von mux2 . . . . . . . . . . . . .
Datentyp circuit in HDCaml . . . . . . . . . . . . . . . . . . . . . . .
Header-File des generierten C-Modells . . . . . . . . . . . . . . . . . . . .
SystemC-Header-File des generierten C-Modells . . . . . . . . . . . . . .
Aus HDCaml-Beschreibung generierter VHDL-Code: Hilfsfunktionen . . .
Implementierung von bin_tree . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
98
100
100
101
101
103
103
104
104
xi
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Abkürzungen
ASIC
Application-Specific Integrated Circuit
API
Application Programming Interface
DSL
Domain-Specific Language
EDA
Electronic Design Automation
FNF
Free Netlist Format
FSM
Finite State Machine
FPGA
Field-Programmable Gate Array
GPL
General Public License
HBS
Hardwarebeschreibungssprache
HDL
Hardware Description Language
HML
Hardware ML
HW
Hardware
LGPL
Lesser General Public License
ML
Meta Language
MSR-SSLA
Microsoft Research Shared Source License Agreement
OCaml
Objective Caml
PGM
Portable Graymap
PSL
Property Specification Language
QPL
Q Public License
RTL
Register Transfer Level
SML
Standard Meta Language
VCD
Value Change Dump
VHDL
VHSIC Hardware Description Language
VHSIC
Very High Speed Integrated Circuits
xii
Kapitel 1
Einleitung
HDCaml[1] ist eine Hardwarebeschreibungsprache, die in die funktionale Sprache Objective Caml[2, 3]
(OCaml) eingebettet ist. Beide Sprachen sind Open-Source-Projekte.1 HDCaml wurde 2005 von Tom
Hawkins als Nachfolgeprojekt von Confluence[7] kreiert. Dieser Text bezieht sich auf die Versionen 0.2.9
und 0.2.10 von HDCaml. Ersteres ist die letzte von Tom Hawkins veröffentlichte Version, zweiteres die aus
der vorliegenden Masterarbeit hervorgegangene Version.2
HDCaml kann verschiedene Output-Formate (VHDL[8], Verilog[9], SystemC[10, 11]) erzeugen und
unterstützt weitere Features wie PSL-Assertions[12] und direkte Simulation von Hardware-Modellen innerhalb eines HDCaml-Programms mit Ausgabe von value change dumps (VCD[9, Kap. 18]). Eine genauere
Beschreibung von HDCaml befindet sich in Kapitel 3, S. 20. Das Benutzerhandbuch (Language Reference
Manual) von HDCaml 0.2.10 ist als Anhang A, S. 85 beigefügt.
1.1
Motivation
Zur Modellierung von digitalen Schaltungen haben sich die Hardwarebeschreibungssprachen VHDL[8] und
Verilog[9] durchgesetzt. Sie bieten mächtige Beschreibungsmöglichkeiten und sind universell einsetzbar.
Die beiden Sprachen werden von allen großen Herstellern von Entwicklungsumgebungen für den Entwurf
digitaler Systeme (Electronic Design Automation (EDA) Companies) seit Jahren unterstützt und somit existieren leistungsfähige und ausgereifte Werkzeuge für Simulation und Synthese.
VHDL hat eine langatmige, umständliche Syntax. In beiden Sprachen können nicht synthetisierbare
Beschreibungen formuliert werden und es ist sehr leicht, Fehler zu machen, indem manche Beschreibungen
zwar syntaktisch und semantisch gültig sind, jedoch etwas anderes beschreiben, als man beabsichtigt hatte.
Außerdem sind generische Beschreibungen nur sehr eingeschränkt möglich. Diese Eigenschaften werden
in Abschnitt 2.2, S. 7 weiter diskutiert.
Aus der Unzufriedenheit über die Eigenschaften der etablierten Sprachen heraus werden immer wieder neue Hardwarebeschreibungssprachen kreiert (Kapitel 4, S. 42 stellt einige dieser Sprachen vor). Diese
funktionieren meistens als Code-Generatoren für VHDL oder Verilog. Sie beschränken sich oft auf Teilmengen von Fähigkeiten und versuchen, in diesem kleineren Teilbereich besser zu sein als die etablierten
Sprachen. Innerhalb dieses Teilbereichs stehen dem Benutzer dann die Vorteile beider Welten zur Verfügung:
• einfachere Syntax und Semantik,
• flexible generische Beschreibungen und
• ausgereifte Werkzeuge für VHDL/Verilog.
HDCaml ist eine dieser neuen Sprachen. Man verwendet HDCaml als Code-Generator. Der Entwurfsprozess wird um einen zusätzlichen Arbeitsschritt ergänzt, in dem HDCaml-Designs in VHDL-Designs um1 HDCaml:
LGPL[5] (Lesser General Public License); OCaml: QPL[6] (Q Public License)
http://funhdl.org/download (HDCaml 0.2.9), http://karl-flicker.at (HDCaml 0.2.10)
2 Downloads:
1
KAPITEL 1. EINLEITUNG
2
gewandelt werden bzw. VHDL-Code aus HDCaml-Code generiert wird (funktioniert gleichermaßen mit
Verilog). Der restliche Arbeitsablauf bleibt gleich, d.h. man simuliert und synthetisiert seine Designs weiterhin mit den gewohnten Anwendungen (s. Abbildung 1.1).
HDCaml-Programm
Zusätzlicher Schritt
mit HDCaml
Code generieren
VHDL-Datei
Arbeitsablauf
ohne HDCaml
Simulation
Synthese
...
Abbildung 1.1: Entwurfsprozess bei Benutzung von HDCaml
Die vorliegende Arbeit wurde von Dr. Manfred Mücke betreut, in dessen Dissertation „An Enhanced Hardware Description Language Implementation for Improved Design-Space Exploration in High-Energy Physics Hardware Design“[13] die Sprache HDCaml auch eine wesentliche Rolle spielte.
Man kann HDCaml als Versuch verstehen, die Fähigkeiten von Confluence[7] neu zu implementieren und
dabei unter anderem folgende ungünstige Eigenschaften zu beseitigen:
• Der Compiler der Sprache Confluence ist für Entwickler schwer zu erweitern bzw. zu warten.
• Für Benutzer von Confluence ist es oft schwer, aus dessen Fehlermeldungen auf die Ursache eines
Problems im Confluence-Modell rückzuschließen.3
1.2
Zielsetzung
In der vorliegenden Masterarbeit soll HDCaml auf Benutzerfreundlichkeit bei seiner Verwendung analysiert werden und sein Quellcode soll auf Wartbarkeit bzw. Erweiterbarkeit untersucht werden. Es sollen
Verbesserungen implementiert und dokumentiert werden, die der Einfachheit der Benutzung dienen (z.B.
striktere Überprüfungen auf Designfehler, einfachere Debug-Möglichkeiten, zielführendere Fehlermeldungen, Behebung von Fehlfunktionen, etc.).
In einem weiteren Themenbereich, Bildverarbeitung, soll HDCaml anhand eines kleinen Beispiels auf
seine Verwendbarkeit zur Beschreibung von Bildverarbeitungs-Hardware untersucht werden. Das HDCamlModell soll auf Korrektheit geprüft und mit Implementierungen in anderen Sprachen verglichen werden.
1.3
Gliederung
In Kapitel 2, S. 3 wird die Problemstellung diskutiert. Kapitel 3, S. 20 bietet eine kurze Einführung in die
Hardwarebeschreibungssprache HDCaml. In Kapitel 4, S. 42 wird auf verwandte Projekte eingegangen. In
Kapitel 5, S. 61 wird das Design und die Implementierung der im Rahmen dieser Arbeit gemachten Änderungen vorgestellt und es werden die Ergebnisse diskutiert. Kapitel 6, S. 81 fasst die Erkenntnisse dieser
Arbeit zusammen und bietet einen Ausblick.
3 Bedingt
durch die lenient evaluation order (vgl. Anhang B, S. 97).
Kapitel 2
Problemanalyse
Nach einem Überblick über HW-Beschreibungsmöglichkeiten in Abschnitt 2.1 werden in Abschnitt 2.2 die
Einschränkungen der verbreiteten HW-Beschreibungssprachen diskutiert und gezeigt, wie man diese durch
Code-Generierung umgehen kann. In Abschnitt 2.3 wird dargelegt, dass es Vorteile für eine neu kreierte Sprache haben kann, ihre Funktionalität einzuschränken. In Abschnitt 2.4 werden Vor- und Nachteile
der Implementierungsmöglichkeiten von Domain-Specific Languages (DSLs) gegenübergestellt und in Abschnitt 2.5 die positiven Eigenschaften erörtert, die die funktionale Sprache OCaml für die Implementierung
von DSLs hat. Auf Betrachtungen über Bildverarbeitung in Hardware (Abschnitt 2.6) folgt Abschnitt 2.7
über die Kriterien, gemäß denen die Evaluierung der Ergebnisse der vorliegenden Arbeit erfolgen kann.
2.1
Hardware-Beschreibungsmöglichkeiten
Um definieren zu können, welche Art von HW-Beschreibung in HDCaml zum Einsatz kommt, muss vorher
geklärt werden, welche Möglichkeiten es dazu überhaupt gibt. VHDL (als Beispiel für eine gängige HBS)
bietet drei Beschreibungsmöglichkeiten:
• Verhaltensbeschreibung (behavioral description),
• RTL-Beschreibung (RTL-description)1 und
• Strukturbeschreibung (structural description).
Zusätzlich wird (sozusagen als vierte Beschreibungsart) auch noch untersucht, inwiefern sich VHDL dazu
eignet,
• Generatoren für regelmäßig aufgebaute Strukturen
zu schreiben.
HDCaml beschränkt sich auf synchrone, digitale Schaltungen mit einer implizit vorhandenen Clock. Es
werden deshalb zur besseren Vergleichbarkeit keine Sprachelemente von VHDL diskutiert, die asynchrone
Logik, Clock-Manipulationen etc. ermöglichen.
Es folgen VHDL-Beispiele (zum Teil aus [14, S. 5 ff] und [15, S. 177 ff] ) für die aufgezählten Beschreibungsmöglichkeiten. Dazu werden die Implementierungen von Halb- und Volladdierern mit den Inputs a,
b, cin (nur Volladdierer), und den Outputs sum und cout herangezogen. Abbildung 2.1, S. 4 zeigt deren
Aufbau aus logischen Gattern sowie die Konstruktion eines Volladdierers aus Halbaddierern.
1 in
[14], [15] und [16, S. 96] auch Datenfluss-Beschreibung (dataflow description) genannt
3
KAPITEL 2. PROBLEMANALYSE
4
(a) Halbaddierer (Gatter)
(b) Volladdierer (Gatter)
(c) Symbol des Halbaddierers
(d) Volladdierer aus Halbaddierern
Abbildung 2.1: Halb- und Volladdierer
2.1.1
Verhaltensbeschreibung
Eine Verhaltensbeschreibung definiert die Funktionalität einer Schaltung als Algorithmus ohne jeglichen
Hinweis darauf, wie die Struktur der Implementierung sein könnte. Siehe Listing 2.1 für das Beispiel eines
Volladdierers, in dem die Werte der drei Inputs in einer Variablen addiert werden und die Outputs mittels
Modulo bzw. dem Vergleichsoperator „>“ bestimmt werden. In VHDL ist die process / end process1
2
3
4
5
6
7
process ( a , b , cin )
variable r e s u l t : i n t e g e r := 0;
begin
r e s u l t := b i t _ t o _ i n t e g e r ( a ) + b i t _ t o _ i n t e g e r ( b ) + b i t _ t o _ i n t e g e r ( cin ) ;
sum <= b o o l e a n _ t o _ b i t ( ( r e s u l t mod 2 ) = 1 ) ;
c o u t <= b o o l e a n _ t o _ b i t ( r e s u l t > 1 ) ;
end p r o c e s s ;
Listing 2.1: Verhaltensbeschreibung eines Volladdierers in VHDL
Umgebung, innerhalb der Anweisungen sequentiell ausgeführt werden, ein wesentliches Merkmal für eine
Verhaltensbeschreibung. In einer Verhaltensbeschreibung sind sämtliche Sprachkonstrukte wie if, case,
etc. verwendbar. Listing 2.2, S. 5 ist ein Beispiel für einen Halbaddierer, dessen Funktionsweise nur durch
Fallunterscheidungen beschrieben wird.
Eine Verhaltensbeschreibung hat Vorteile und Nachteile. Als Vorteil ist klar zu erkennen, dass manche
Konstrukte damit einfacher als mit den anderen beiden Beschreibungsarten zu beschreiben sind (z.B. state
machines, die oft mit case -statements programmiert werden). Ein Nachteil ist, dass bei weitem nicht alle
Verhaltensbeschreibungen synthetisierbar sind, sondern üblicherweise nur eine kleine Teilmenge davon,
abhängig vom Synthese-Werkzeug.
2.1.2
RTL-Beschreibung
Eine Register-Transfer-Level-Beschreibung beschreibt eine Schaltung als Menge von Registern (Flip-Flops)
und kombinatorischer Logik, die Register und Ports (Eingänge und Ausgänge) miteinander verbindet (oder
auch rückkoppelt), wie in Abbildung 2.2, S. 5 visualisiert. Die komb. Logik wird in Form von nebenläufigen
Zuweisungen dargestellt, die Signale durch Anwendung von Operatoren miteinander verknüpfen.
KAPITEL 2. PROBLEMANALYSE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
5
process ( a , b )
begin
i f ( a = b ) then
sum <= ’ 0 ’ ;
i f ( a = ’1 ’) then
c o u t <= ’ 1 ’ ;
else
c o u t <= ’ 0 ’ ;
end i f ;
else
sum <= ’ 1 ’ ;
c o u t <= ’ 0 ’ ;
end i f ;
end p r o c e s s ;
Listing 2.2: Verhaltensbeschreibung eines Halbaddierers in VHDL
In VHDL hat eine RTL-Beschreibung folgende (optionale) Bestandteile:
• eine nebenläufige Menge von Gleichungen außerhalb eines Prozesses (kombinatorischer Teil) und
• eine Menge von „Register-Zuweisungen“, das sind auf das Taktsignal synchronisierte Zuweisungen
innerhalb eines Prozesses (sequentieller Teil).
Abbildung 2.2: RTL-Beschreibung
Listing 2.3 beschreibt als Beispiel für eine rein kombinatorische Schaltung einen Volladdierer. Listing 2.4,
S. 6 ist die RTL-Beschreibung eines 4-Bit-Zählers, in der sowohl ein kombinatorischer Teil (Zeilen 12–14)
als auch ein sequentieller Teil (16–23) vorkommt.
1
2
3
aXb <= a xor b ;
sum <= aXb xor c i n ;
c o u t <= ( a and b ) or ( aXb and c i n ) ;
Listing 2.3: RTL-Beschreibung eines kombinatorischen Volladdierers in VHDL
Die RTL-Beschreibung befindet sich auf einer niedrigeren Ebene als die Verhaltensbeschreibung, jedoch
auf einer höheren Ebene als die (anschließend diskutierte) Strukturbeschreibung. Es ist hierbei nicht nur
anzugeben, was passieren soll, sondern auch wie. Andererseits bleibt in Zeile 13 von Listing 2.4, S. 6 z.B.
offen, auf welche Art von Addierer der Operator „+“ vom Synthese-Werkzeug in Hardware abgebildet wird
(obwohl natürlich bit-genau spezifiziert ist, wie das Ergebnis der Addition auszusehen hat).
2.1.3
Strukturbeschreibung
Bei der Strukturbeschreibung werden bereits vorher definierte Komponenten instanziert und es wird angegeben, wie diese mit der übergeordneten Schaltung verbunden werden. Listing 2.5, S. 6 beschreibt einen
aus Halbaddierern (z.B. dem Halbaddierer von Listing 2.2) aufgebauten Volladdierer gemäß Schaltbild (d)
KAPITEL 2. PROBLEMANALYSE
1
2
3
4
5
entity counter
port ( r e s e t
clock
count
end ;
6
is
: in s t d _ l o g i c ;
: in s t d _ l o g i c ;
: o u t s t d _ l o g i c _ v e c t o r ( 3 downto 0 ) ) ;
6
7
8
9
10
11
12
13
14
architecture r t l of counter i s
s i g n a l n e x t _ c o u n t : s t d _ l o g i c _ v e c t o r ( 3 downto 0 ) ;
signal count_reg
: s t d _ l o g i c _ v e c t o r ( 3 downto 0 ) ;
signal incr
: s t d _ l o g i c _ v e c t o r ( 3 downto 0 ) ;
begin
incr
<= s t d _ l o g i c _ v e c t o r ’ ( " 0001 " ) ;
n e x t _ c o u n t <= s t d _ l o g i c _ v e c t o r ( u n s i g n e d ( c o u n t _ r e g ) + u n s i g n e d ( i n c r ) ) ;
count
<= c o u n t _ r e g ;
15
16
17
18
19
20
21
22
23
24
process ( clock , r e s e t )
begin
i f ( r e s e t = ’1 ’) then
c o u n t _ r e g <= ( o t h e r s = > ’ 0 ’ ) ;
e l s i f r i s i n g _ e d g e ( c l o c k ) then
c o u n t _ r e g <= n e x t _ c o u n t ;
end i f ;
end p r o c e s s ;
end ;
Listing 2.4: RTL-Beschreibung eines 4-bit-Zählers in VHDL
1
2
3
component h a l f _ a d d e r i s
p o r t ( a : i n b i t ; b : i n b i t ; sum : o u t b i t ; c o u t : o u t b i t ) ;
end component h a l f _ a d d e r ;
4
5
6
7
component o r 2 _ g a t e i s
port ( a : in b i t ; b : in b i t ; o r o u t : out b i t ) ;
end component o r 2 _ g a t e ;
8
9
10
11
ha1 : h a l f _ a d d e r p o r t map ( a => a ,
b => b ,
sum => ha1_s , c o u t => h a 1 _ c ) ;
ha2 : h a l f _ a d d e r p o r t map ( a => ha1_c , b => c i n , sum => ha2_s , c o u t => sum ) ;
or2 : or2_gate
p o r t map ( a => ha1_s , b => ha2_s , o r o u t => c o u t ) ;
Listing 2.5: Strukturbeschreibung eines Volladdierers in VHDL
von Abbildung 2.1, S. 4. Die port map gibt an, welche Anschlüsse des Volladdierers mit welchen des
Halbaddierers (half_adder) und des Oder-Gatters mit 2 Inputs (or2_gate) verbunden werden müssen. Dabei steht immer links vom Operator „ => “ der Name den der Anschluss innerhalb der instanzierten
Komponente hat, und rechts davon der Name in der übergeordneten Schaltung. Die zu verwendenden Komponenten müssen vor ihrer Benutzung mittels einer component-Deklaration (Zeilen 1–7 in Listing 2.5)
deklariert werden, wodurch angegeben wird, welche Ports (und Generics) die Komponente besitzt.2
Die Strukturbeschreibung ist die einfachste (in der Bedeutung von „primitivste“) Schaltungsbeschreibung. Theoretisch kann man die komplexesten Schaltungen einzig und allein durch Strukturbeschreibung
zusammenbauen,3 am meisten Sinn hat diese Beschreibungsart aber in zwei Fällen:
• zur exakten (auf das letzte Gatter genauen) Beschreibung von Strukturen, die sich durch Verhaltensbeschreibung oder RTL-Beschreibung nicht konstruieren lassen und
• zur Integration von großen Teil-Funktionsblöcken zu einem Gesamt-Design.
2 Ausserdem
3 Unter
ist i.A. noch eine component configuration notwendig, die einer Komponente ein Entity zuordnet.
der Voraussetzung, dass man die grundlegenden Gatter und Register als vordefinierte Komponenten zur Verfügung hat.
KAPITEL 2. PROBLEMANALYSE
2.1.4
7
Generatoren für regelmäßig aufgebaute Strukturen
In VHDL gibt es das generate - Statement, mit dem es (unter Einschränkungen, s.u.) möglich ist, regelmäßig aufgebaute Strukturen zu implementieren ohne alle Teil-Strukturen einzeln beschreiben zu müssen.
Listing 2.6 zeigt die Verwendung dieser Funktionalität zur Strukturbeschreibung eines 4-bit-Addierers,
der aus vier Volladdierern konstruiert ist. Die Instanzierung der Volladdierer erfolgt in einer Schleife (Zeilen
24–30, Schlüsselwörter „ for . . . generate “ und „ end generate “).
1
2
3
4
5
6
7
entity adder_4bit
port ( c a r r y _ i n
summand1
summand2
carry_out
sum
end a d d e r _ 4 b i t ;
is
: in b i t ;
: i n b i t _ v e c t o r ( 3 downto 0 ) ;
: i n b i t _ v e c t o r ( 3 downto 0 ) ;
: out b i t ;
: o u t b i t _ v e c t o r ( 3 downto 0 )
);
8
9
10
architecture a d d e r _ 4 b i t _ g e n e r a t e of adder_4bit i s
11
12
13
14
15
16
17
18
component f u l l _ a d d e r i s
port ( a
: in b i t ;
b
: in b i t ;
cin
: in b i t ;
sum : o u t b i t ;
c o u t : out b i t
);
end component f u l l _ a d d e r ;
19
20
s i g n a l c a r r y : b i t _ v e c t o r ( 4 downto 0 ) ;
21
22
23
24
25
26
27
28
29
30
begin
four_full_adders :
f o r i i n 3 downto 0 g e n e r a t e
f a : f u l l _ a d d e r p o r t map ( a
b
cin
sum
cout
end g e n e r a t e ;
=>
=>
=>
=>
=>
summand1 ( i ) ,
summand2 ( i ) ,
carry ( i ) ,
sum ( i ) ,
c a r r y ( i +1) ) ;
31
32
33
c a r r y _ o u t <= c a r r y ( 4 ) ;
c a r r y ( 0 ) <= c a r r y _ i n ;
34
35
end a d d e r _ 4 b i t _ g e n e r a t e ;
Listing 2.6: 4-bit-Addierer mit generate in VHDL
In Abschnitt 2.2.1, S. 8 wird diskutiert, warum diese Form der generischen Programmierung nicht flexibel genug ist, um damit wirklich ganze Klassen von Hardware-Strukturen allgemein beschreiben zu können.
2.2
Diskussion verbreiteter Hardwarebeschreibungssprachen
VHDL[8] und Verilog[9], die beiden Sprachen, die am häufigsten zur Beschreibung von Hardware verwendet werden, sind
•
•
•
•
mächtig (unterstützen asynchrone Logik, bidirektionale Busse, Verzögerungsmodelle, etc.),
standardisiert (IEEE-Standards),
weit verbreitet und
ausgezeichnet durch kommerzielle EDA-Programme unterstützt.
KAPITEL 2. PROBLEMANALYSE
2.2.1
8
Einschränkungen
Darüber hinaus haben diese Sprachen aber auch folgende Schwächen:
1.
2.
3.
4.
5.
langatmige, umständliche Syntax,
komplizierte (nicht intuitive), fehlerträchtige Semantik,
eingeschränkte Synthetisierbarkeit,
geringe Fähigkeit zur generischen Schaltungsbeschreibung und
zu wenig Flexibilität bei Design-Iterationen.
Diese Eigenschaften machen diese Sprachen einerseits für Anfänger relativ kompliziert benutzbar (Punkte
1 bis 3), andererseits aber auch für Experten relativ unbefriedigend (Punkte 3 bis 5).
Die vorliegende Arbeit kann nur sehr oberflächlich und punktuell auf die genannten Nachteile eingehen,
einige weitere Details zu dem Thema findet man in [13] und [16]. Das Ziel der folgenden Auflistung ist es,
aufzuzeigen, dass (auf Kosten einer eingeschränkten Funktionalität, z.B. der Beschränkung auf synchrone
Schaltungen) Raum für Vereinfachungen sowie für weitere Verbesserungen im Bereich der obenstehenden
Punkte gibt.
Syntax
Die Sprache VHDL, die unter der Vorgabe entwickelt wurde, sich möglichst an die Syntax von Ada zu halten (vgl. [16, Kap. 7]), hat eine langatmige und umständliche Syntax. Dazu gehören u.A. folgende Punkte:
• Klammerung von Blöcken mittels begin und end (ebenfalls in Verilog verwendet)
• Angabe von Bitbereichen mit den redundanten Schlüsselwörtern to und downto , z.B.
x <= y(7 downto 0) & z(3 to 4)
• Die starke Typisierung (strong typing) von VHDL erfordert viele explizite Typumwandlungen (type
casts), z.B. müssen Bitvektoren vom Typ std_logic_vector für Berechnungen in unsigned
oder signed und wieder zurück umgewandelt werden, z.B.:
sum <= std_logic_vector( unsigned(a) + unsigned(b) )
• Jede Komponente, die man beschreibt, muss einmal als entity deklariert und mindestens einmal als architecture definiert werden. Für den Fall, dass man ohnehin nur eine Architecture
implementieren will, ist das umständlich.
Die Syntax von Verilog hat eine leichte Ähnlichkeit mit C und ist deshalb kompakter (obwohl auch in
Verilog Blöcke mit begin und end geklammert werden, statt mit geschwungenen Klammern, wie in C).
Semantik
Allgemein gilt, dass verschiedene Beschreibungsmöglichkeiten (s. Abschnitt 2.1, S. 3) und die Unterstützung verschiedenster Logikstile die Sprachen mächtig, aber auch kompliziert machen. Folgende Punkte
zeigen, dass die Semantik von VHDL und Verilog nicht immer intuitiv und einfach, sondern manchmal
fehlerträchtig und umständlich ist:
• Nicht-intuitive Semantik von Registern und Signalen:
– In Verilog gibt es prinzipiell die Unterscheidung zwischen normalen Signalen (wire), die keine
Informationen speichern können, und Registern (reg), die für die Speicherung gedacht sind.
Listing 2.7, S. 9 zeigt ein Verilog-Beispiel, das einen 4-Bit Multiplizierer beschreibt. Obwohl
die Schaltung rein kombinatorisch ist, d.h. keine Register vorkommen, muss der Output prod
als Register deklariert werden (Zeile 5).4 Das Resultat der Synthese ist eine rein kombinatorische Schaltung.5
4 Mittels
continuous assignment („ assign prod = a * b “ statt den Zeilen 7–10) geht es ohne Register.
mit „Quartus II“ von Altera in der Version 6.0.
5 Getestet
KAPITEL 2. PROBLEMANALYSE
9
– In VHDL gibt es diese Unterscheidung zwischen Registern und normalen Signalen (Leitungen)
bei der Deklaration (z.B. Zeilen 8–10 von Listing 2.4, S. 6) nicht. Lediglich die Verwendung
bestimmt diese Eigenschaft. So wird erst durch die Zeilen 16–23 festgelegt, dass count_reg
ein Register ist. Auch durch geeignete Ausnützung von Verzögerungsmodellen können aus Leitungen praktisch Register werden [16, Kap. 7.2.3, S. 113].
1
2
3
4
5
module m u l t 4 ( a , b , p r o d ) ;
input [ 3 : 0 ] a ;
input [ 3 : 0 ] b ;
output [ 3 : 0 ] prod ;
reg
[ 3 : 0 ] prod ;
6
7
8
9
10
11
a l w a y s @( a , b )
begin
prod = a ∗ b ;
end
endmodule
Listing 2.7: 4-bit Multiplizierer in Verilog (vgl. [16, Fig. 5.13])
• Durch Unachtsamkeit kann man leicht semantisch korrekte Konstrukte erzeugen, die jedoch nicht das
machen, was man beabsichtigt hatte:
– z.B. unerwünschte Latches. Listing 2.8 ist der (fehlerhafte) Versuch, einen 3-fach-Multiplexer
mit einer 1-aus-N-Kodierung (one-hot code) des sel -Inputs zu implementieren. Der fehlende
else -Zweig bewirkt, dass der Wert des Outputs o gleich bleibt, wenn sel keinen der drei
abgefragten Werte besitzt. Das wird vom VHDL-Synthesizer durch ein Latch realisiert.
1
2
3
4
5
6
7
p r o c e s s ( s el , i1 ,
begin
if
s e l = " 001 "
e l s i f s e l = " 010 "
e l s i f s e l = " 100 "
end i f ;
end p r o c e s s ;
i2 , i 3 )
t h e n o <= i 1 ;
t h e n o <= i 2 ;
t h e n o <= i 3 ;
Listing 2.8: 3-fach Multiplexer mit unerwünschtem Latch in VHDL
– z.B. bei der Multiplikation in Verilog: Listing 2.7 dient auch für diesen Punkt als Beispiel.
Verilog erlaubt es (ohne Warnung), die Bitbreite des Produkts ( prod , Zeile 4) so zu wählen,
dass die höherwertigen Bits des Ergebnisses verloren gehen (im Beispiel sollte die Bitbreite des
Produkts 8 sein, damit sämtliche Information erhalten bleibt, die Breite ist jedoch nur 4).
• Man kann bei unzureichender Kenntnis der Sprache dazu verleitet werden, Schaltungen anders zu
modellieren, als man es vorgehabt hatte:
– z.B. in VHDL, wenn man von out-Ports auch liest, z.B. um den momentanen Wert eines Signals zu bekommen, den man gerade darauf geschrieben hat. VHDL’93 benötigt dazu einen
Port vom Typ inout oder buffer . Bei einer Verhaltensbeschreibung besteht die korrekte
Lösung darin, eine interne Variable zu verwenden um den Wert zwischenzuspeichern, bevor
man ihn auf den Output schreibt. Bei einer Strukturbeschreibung gibt es möglicherweise gar
keine andere Möglichkeit als buffer oder inout zu verwenden [16, Kap. 7.2.2, S. 106 f].
• Prinzipiell einfache, elementare Komponenten von digitalen Schaltungen sind umständlich zu beschreiben:
KAPITEL 2. PROBLEMANALYSE
10
– z.B. Register: in Listing 2.9 (Ausschnitt von Listing 2.4, S. 6) sind 8 Zeilen für ein einfaches
Register notwendig.
– z.B. Multiplexer: s. Listing 2.10 für einen 4-Input-Multiplexer in VHDL.
(In HDCaml gibt es für Register und N-fach-Multiplexer eigene Funktionen, s. Tabelle 3.1, S. 25.)
16
17
18
19
20
21
22
23
process ( clock , r e s e t )
begin
i f ( r e s e t = ’1 ’) then
c o u n t _ r e g <= ( o t h e r s = > ’ 0 ’ ) ;
e l s i f r i s i n g _ e d g e ( c l o c k ) then
c o u n t _ r e g <= n e x t _ c o u n t ;
end i f ;
end p r o c e s s ;
Listing 2.9: Register in VHDL
1
2
3
4
5
6
7
8
9
process ( sel , sig0 , sig1
begin
case s e l i s
when " 00 "
=> x <=
when " 01 "
=> x <=
when " 10 "
=> x <=
when o t h e r s => x <=
end c a s e ;
end p r o c e s s ;
, sig2 , sig3 )
sig0
sig1
sig2
sig3
;
;
;
;
Listing 2.10: 4-Input Multiplexer in VHDL
Synthetisierbarkeit
Synthese ist die automatische Umsetzung von Hardware-Beschreibungen in eine Form, die unmittelbar für
die Produktion von ASICs (application-specific integrated circuits) oder die Programmierung von FPGAs
(field-programmable gate arrays) verwendet werden kann (typischerweise handelt es sich dabei um Netzlisten).
Man kann in beiden Sprachen Beschreibungen formulieren, die nicht synthetisiert werden können. Insbesondere Verhaltensbeschreibungen sind nur synthetisierbar, wenn vom Programmierer bestimmte Muster
eingehalten werden, wobei der Grad des Erfolgs vom Synthese-Werkzeug abhängig ist.
Generische Beschreibungen
Als generische Beschreibungen wird in dieser Arbeit das Hardwarebeschreibungs-Äquivalent von generischer Programmierung verstanden. In Abschnitt 2.5, S. 15 wird kurz auf generische Programmierung in
funktionalen Sprachen eingegangen. Beispiele für Konstrukte, die generische Programmierung in imperativen Sprachen ermöglichen, sind die Templates von C++ bzw. die Generics von Java.
Es gibt in VHDL und Verilog zwar Ansätze für generische Beschreibungen, diese sind jedoch nicht generisch genug. Als erstes Beispiel für eine generische Beschreibung in VHDL wurde in Abschnitt 2.1.4, S. 7
ein 4-bit-Addierer (Listing 2.6, S. 7) demonstriert, bei dem die vier Volladdierer nicht einzeln, sondern in
einer Schleife instanziert werden.
Als nächsten Schritt der Verallgemeinerung (s. Listing 2.11, S. 11) kann man die Anzahl der zu instanzierenden Komponenten (durch Angabe der Bitbreite als generic, s. Zeile 2) parametrisieren und das
Beispiel dadurch zu einem Addierer beliebiger Breite weiterentwickeln.
KAPITEL 2. PROBLEMANALYSE
1
2
3
4
5
6
7
8
entity generic_adder is
generic ( width : i n t e g e r := 4 ) ;
port ( c a r r y _ i n
: in b i t ;
summand1 : i n b i t _ v e c t o r ( ( w i d t h −1) downto 0 ) ;
summand2 : i n b i t _ v e c t o r ( ( w i d t h −1) downto 0 ) ;
c a r r y _ o u t : out b i t ;
sum
: o u t b i t _ v e c t o r ( ( w i d t h −1) downto 0 )
end g e n e r i c _ a d d e r ;
11
);
9
10
11
architecture g e n e r i c _ a d d e r _ r t l of generic_adder i s
12
13
14
15
16
17
18
19
component f u l l _ a d d e r i s
port ( a
: in b i t ;
b
: in b i t ;
cin
: in b i t ;
sum : o u t b i t ;
c o u t : out b i t
);
end component f u l l _ a d d e r ;
20
21
s i g n a l c a r r y : b i t _ v e c t o r ( w i d t h downto 0 ) ;
22
23
24
25
26
27
28
29
30
31
begin
full_adders :
f o r i i n ( w i d t h −1) downto 0 g e n e r a t e
f a : f u l l _ a d d e r p o r t map ( a
=> summand1 ( i ) ,
b
=> summand2 ( i ) ,
c i n => c a r r y ( i ) ,
sum => sum ( i ) ,
c o u t => c a r r y ( i + 1 ) ) ;
end g e n e r a t e ;
32
33
34
c a r r y _ o u t <= c a r r y ( w i d t h ) ;
c a r r y ( 0 ) <= c a r r y _ i n ;
35
36
end g e n e r i c _ a d d e r _ r t l ;
Listing 2.11: Addierer beliebiger Breite in VHDL
Hierbei gilt allerdings die Einschränkung, dass bei der Deklaration von Bitvektoren (Zeilen 4, 5, 7 und
21 in Listing 2.11) deren Bitbreiten locally static expressions sein müssen. Die genauen Kriterien dafür
sind im VHDL-Standard spezifiziert ([8, S. 107 f]), kurz gesagt bedeutet das, dass die Ausdrücke innerhalb
der jeweiligen design unit (z.B. innerhalb des jeweiligen VHDL-Files) auflösbar sein müssen, wobei nicht
beliebige Rechenoperationen und Funktionsaufrufe verwendet werden dürfen.
Damit ist bei VHDL auch schon das Ende der Verallgemeinerbarkeit erreicht. Der nächste, wünschenswerte Schritt bei der generischen Beschreibung von Schaltungen wäre es nämlich, nicht nur eine bestimmte
Komponente in einer Schleife wiederholt zu instanzieren, sondern eine beliebige Komponente. Das ist in
VHDL jedoch nicht möglich, weil der Name der Komponente (z.B. „ full_adder “ in Zeile 26 von Listing 2.11) fix in der Funktion verankert ist und nicht parametrisiert werden kann.
Eine weitere Einschränkung von VHDL ist, dass Entities keine variable Anzahl von Ports haben können.
Daraus folgt, dass man gewisse Konstrukte nicht allgemein beschreiben kann, obwohl deren Funktionsweise allgemein beschreibbar wäre. Ein Beispiel dafür ist ein allgemeiner n-fach-Multiplexer (mit n = 2N
Eingängen für eine N Bit breite Steuerleitung).6
Als Resultat der mangelnden Flexibilität bezüglich generischer Beschreibungsmöglichkeiten muss man
Code oft händisch duplizieren, was mühsam und fehleranfällig ist.
6 Listing 3.14, S. 36 in Abschnitt 3.4.4, S. 36 zeigt einen HDCaml-Codegenerator (vgl. Abschnitt 2.2.2, S. 12), der VHDLNetzlisten für beliebige Multiplexer erzeugen kann.
KAPITEL 2. PROBLEMANALYSE
12
Design-Iterationen
Für ein starres, einmal festgelegtes Design, das später nie mehr geändert wird, ist VHDL eine hinreichend
komfortable Modellierungssprache. In der Realität werden Designs jedoch oft nachträglich geändert, um
Fehler zu beseitigen oder neue Funktionalität zu implementieren. Für diese Anforderung ist VHDL zu
unflexibel. In [13] zeigt M. Mücke, dass in VHDL kleine Änderungen am Algorithmus einem kompletten
Neuschreiben des Codes gleichkommen können.
2.2.2
Umgehen der Einschränkungen (Code-Generierung)
Trotz der Einschränkungen (z.B. bzgl. generischer Beschreibungen) von VHDL will man dennoch VHDL
als Input-Format für die EDA-Anwendungen verwenden, weil diese für Simulation und Synthese sehr leistungsfähig sind. Die Lösung ist es, VHDL-Code nicht händisch zu schreiben, sondern zu generieren.
Wie man den Code generiert, ist für das Funktionsprinzip irrelevant. Der Code-Generator kann z.B.
ein grafisches Werkzeug wie der NIOS SOPC-Builder7 sein, mit dem man VHDL-Code für ein komplettes
Eingebettetes System (system on a programmable chip – SOPC) auf einem FPGA erzeugen kann.
Wenn man die Code-Generierung wirklich allgemein und generisch halten will, dann ist es die beste
Möglichkeit, eine geeignete Programmiersprache für diesen Zweck zu verwenden, eine sogenannte domänenspezifische Sprache (domain-specific language – DSL). Zwei der folgenden Abschnitte diskutieren,
warum es vorteilhaft sein kann, solche DSLs einfach zu halten (Abschnitt 2.3, S. 12) und warum funktionale
Sprachen eine gute Basis für solche DSLs bieten (Abschnitt 2.5, S. 15).
Als weitere, sehr nützliche Eigenschaft produzieren solche Sprachen meistens nicht nur VHDL-Code
zur Synthese, sondern auch ausführbare Modelle in den Sprachen C oder C++ zur Simulation.
2.3
Verbesserung einer Sprache durch Einschränkung der Funktionalität
Wie bereits in Abschnitt 2.2, S. 7 diskutiert wurde, sind sehr allgemeine Hardwarebeschreibungssprachen
(wie VHDL und Verilog) oft nicht einfach und intuitiv verwendbar. Es liegt deshalb die Idee nahe, neue
Sprachen mit eigeschränkter Funktionalität zu kreieren, die zwar weniger allgemein, dafür aber einfacher
und unter anderen Gesichtspunkten besser sind.
Im Folgenden wird (ohne Anspruch auf Vollständigkeit) beschrieben, welche Vorteile durch bestimmte
Funktionalitäts-Einschränkungen erzielt werden können.
Weniger Möglichkeiten
Weniger Möglichkeiten machen manche Fehler unmöglich,8 z.B. Bus-Zugriffs-Konflikte, illegale ClockManipulationen, etc. Durch eine einfachere Syntax und/oder Semantik ist eine Sprache schneller erlernund einsetzbar.
Je eingeschränkter eine Sprache ist, desto einfacher ist ihre Implementierung, was die Wartbarkeit erhöht und Entwicklern das Einarbeiten in neue Codeteile erleichtert. Dadurch fallen potentiell weniger Kosten für Anwendungen an.
Nur synchrone RTL-Beschreibung
Verhaltensbeschreibungen können oft nicht automatisch in Netzlisten umgesetzt werden. Durch eine Beschränkung auf RTL-Beschreibungen sind Hardware-Modell hingegen garantiert synthetisierbarer.
7 Aus
der Entwicklungsumgebung „Quartus II“ von Altera.
aus [17]: ”You can’t break, what you can’t control.” (s.a. Anhang B, S. 97)
8 Zitat
KAPITEL 2. PROBLEMANALYSE
13
Kein kompliziertes Konzept von Zeit
Unter der Voraussetzung von Abschnitt 2.2.2, S. 12, nämlich, dass die Sprache ein Code-Generator für konventionelle HBS ist, ist es unnötig, die umfassenden Verzögerungsmodelle, wie es sie in VHDL gibt (Inertial, Transport und Reject-Inertial) zu duplizieren. Stattdessen gibt es z.B. ausschließlich verzögerungslos
gedachte, kombinatorische Logik sowie Register, die jeweils beim nächsten Takt-Zyklus ihren Inhalt ändern
können.
Dadurch vermeidet man beim Entwurf von Hardware-Modellen Timing-bedingte Fehler (z.B. Latches
statt Flip-Flops) und kann sich voll auf die Funktionalität konzentrieren. Außerdem ermöglichen die diskreten, gleichbleibende Zeitintervalle eine sehr schnelle Simulation. Zeitliche Analysen (z.B. kritische Pfade)
werden ohnehin von den VHDL- und/oder Verilog-Werkzeugen bei Simulation und Synthese durchgeführt.
2.3.1
Am Beispiel von HDCaml
HDCaml beschränkt sich auf digitale, sequentielle, synchrone Schaltungen ohne bidirektionale Busse und
mit einer impliziten, nicht manipulierbaren Clock.
Tabelle 3.1, S. 25 zeigt die vollständige Menge von Funktionen und Operatoren, mit denen in HDCaml
Hardware beschrieben wird. Die gesamte Sprachreferenz (language reference manual, Anhang A, S. 85)
von HDCaml 0.2.10 beansprucht nur 12 Seiten.
Bereits in Listing 2.9, S. 10 und Listing 2.10, S. 10 wurde gezeigt, dass Konstrukte wie Register und
Multiplexer in VHDL acht oder mehr Zeilen Code benötigen. In HDCaml sind diese beiden Beispiele
Einzeiler (Funktionen reg und mux ). In Kapitel 3, S. 20 wird die Sprache (u.A. anhand von weiteren
Code-Beispielen) detaillierter beschrieben.
2.4
Implementierung: Compiler vs. Einbettung
Domänenspezifische Programmiersprachen (domain-specific languages, DSLs) können auf verschiedene
Weisen implementiert werden [13, Kap. 2.3]. Die vorliegende Masterarbeit beschränkt sich auf die beiden
Varianten „Compiler“ und „Einbettung“ (embedding). Der Grund dafür ist, dass alle hier vorkommenden
Sprachen in eine dieser beiden Kategorien fallen.
Ein Compiler übersetzt die Konstrukte der DSL in solche einer Basissprache und Bibliotheksaufrufe
derselben. Der Compiler kann eine komplette statische Analyse des in der DSL geschriebenen Programms
vornehmen.
Bei der Einbettung werden die Konstrukte der DSL in eine existierende Wirts-Programmiersprache
(host language) eingebettet, indem neue abstrakte Datentypen und Operatoren dafür definiert werden. Eine
eingebettete DSL stellt also eine Bibliothek für die Wirtssprache dar.
Die beiden Varianten haben verschiedene Eigenschaften, wobei Vorteile der einen Variante Nachteile
der anderen Variante sind und umgekehrt. Die Eigenschaften sind in Tabelle 2.1 zusammengefasst und
werden im Folgenden ausführlicher erklärt:
Compiler
Syntax und Semantik der DSL
Bibliothek
beliebig
(+)
eingeschränkt
verfügbar
(+)
nicht verfügbar
(–)
Implementierungsaufwand
groß
(–)
gering
(+)
Für DSL verfügbare Bibliotheken
keine
(–)
alle der Wirtssprache
(+)
Beliebige Programme erstellbar
nein
(–)
ja
(+)
Variablennamen, Zeilennummern, etc.
Tabelle 2.1: Eigenschaften der Implementierungsvarianten von DSLs
(–)
KAPITEL 2. PROBLEMANALYSE
14
Syntax und Semantik: Ein Compiler hat die komplette Freiheit bei der Definition von Syntax und Semantik der implementierten Sprache. Eine eingebettete Sprache muss sich hierbei nach der Wirtssprache
richten.
Variablennamen, Dateinamen, Zeilennummern, etc.: Für einen Compiler, der den Quelltext eines Programmes liest, sind natürlich sämtliche dieser Informationen verfügbar. Ein in einer eingebetteten
Sprache ablaufendes Programm „weiß“ hingegen nichts darüber, sofern nicht die Wirtssprache Funktionen zur Verfügung stellt, um derartige Informationen zur Laufzeit abzufragen.
Implementierungsaufwand: Einen Compiler zu implementieren stellt trotz moderner Werkzeuge einen
ungleich höheren Aufwand dar, als eine eingebettete Sprache als Bibliothek für eine existierende
Wirtssprache zu schreiben.
Vorhandenen Bibliotheken: Für einen neu geschriebenen Compiler müssen auch Standard-Bibliotheksfunktionen erst geschrieben werden. Bei einer eingebetteten Sprache sind Bibliotheken für die Wirtssprache bereits in dem Ausmaß vorhanden, dass man damit komfortabel arbeiten kann.
Beliebige Programme erstellbar: Einer DSL, die als Compiler implementiert ist, ist üblicherweise keine Allzweck-Programmiersprache (general purpose programming language), es sind damit nur Programme erstellbar, für die diese DSL vorgesehen ist. Bei einer eingebetteten DSL ist die Wirtssprache
typischerweise eine general purpose language und verliert diese Eigenschaft nicht, nur weil man eine
Bibliothek dazunimmt, durch die die eingebettete Sprache implementiert wird.
Prinzipiell ist bei einer Sprache, die als Code-Generator für konventionelle Hardwarebeschreibungssprachen arbeitet (vgl. Abschnitt 2.2.2, S. 12) egal, ob sie als Compiler oder als eingebettete Sprache implementiert ist. Der Output der DSL ist ein Hardware-Modell in VHDL oder Verilog und für den Benutzer sollte es
wenig Unterschied machen, ob dieses durch einen Compiler oder durch den Ablauf eines Programms sowie
Aufrufen von Bibliotheksfunktionen (bei einer eingebettete Sprache) entstanden ist.
Es gibt für den Benutzer aber dennoch einen wesentlichen Nachteil von eingebetteten Sprachen gegenüber echten Compilern, nämlich (wie bereits erwähnt) dass typischerweise zur Laufzeit keine Informationen
über Zeilennummern, Funktionsnnamen etc. verfügbar sind. Das erschwert es, bzw. macht es fast unmöglich, sinnvolle Fehlermeldungen samt Zeilennummern auszugeben.
2.4.1
In Bezug auf HDCaml
HDCaml ist durch Einbettung in OCaml implementiert. Das hat zur Folge (s. Abschnitt C.2, S. 99), dass
erst zur Laufzeit das beschriebende HW-Modell auf korrekte Bitbreiten überprüft wird und Output-Dateien
generiert werden. Deshalb darf man bei Vergleichen mit Sprachen, die als Compiler arbeiten, nicht nur die
Compile-Time von HDCaml betrachten, sondern das Ergebnis von Compile-Time und Laufzeit zusammen
(am Besten „Code generieren“ genannt, vgl. Abbildung 1.1, S. 2) muss mit dem Compiler-Durchlauf der
anderen Sprache verglichen werden.
Es hat also beispielsweise keinen Sinn zu sagen, dass HDCaml ein dynamisches Typsystem hat, weil
manche Bitbreiten erst zur Laufzeit überprüft werden können. Es werden ja auch die Output-Dateien erst
zur Laufzeit geschrieben, deshalb kann es nicht passieren, dass es zu fehlerhaften Ergebnissen kommt, falls
zur Laufzeit Probleme gefunden werden.
Wie bereits beschrieben, erschweren fehlende Laufzeit-Informationen über Zeilennummern die Ausgabe von zielführenden Fehlermeldungen. Um in HDCaml dennoch einigermaßen sinnvoll Fehler lokalisieren zu können, wurde auf einen Workaround zurückgegriffen, der mit Hilfe von ungefangenen Exceptions
Stack Traces erzeugt (s. Abschnitt 5.3.1, S. 67 u. Abschnitt A.1.1.1, S. 85 für eine Beschreibung des Workarounds). Damit kann man zumindest immer einen Fehler nach dem anderen lokalisieren, was besser ist,
als überhaupt keine solche Information zu haben.
KAPITEL 2. PROBLEMANALYSE
2.5
15
Funktionale Sprachen
Funktionale Sprachen sind eine Teilfamilie der deklarativen Sprachen, und grenzen sich durch ein komplett
unterschiedliches Programmierparadigma von den imperativen Sprachen ab.
Imperative Sprachen haben als Grundprinzip die Beschreibung von Algorithmen durch eine Abfolge
von Seiteneffekten, d.h. es gibt Speicherstellen, die Werte aufnehmen können (= Variablen) und Befehle,
die diese Speicherstellen verändern (=Zuweisungen). Diese Art der Programmierung ist unmittelbar den
heute verwendeten Computer-Architekturen nachempfunden.
Funktionale Sprachen sind eher nach dem Vorbild mathematischer Funktionen modelliert, d.h. sie beschreiben Algorithmen durch Funktionen und Operatoren. Die Teilmenge der rein funktionalen Sprachen,
kommt vollkommen ohne imperative Elemente wie Zuweisungen und Schleifen aus. Bei ihnen werden
sämtliche Wiederholungen durch Rekursion formuliert. Bei rein funktionalen Sprachen ist die Reihenfolge
der Auswertung von Teilfunktionen beliebig, was dem Compiler die Optimierung erleichtert.9
Ein integraler Bestandteil von funktionalen Sprachen sind Funktionen höherer Ordnung. Funktionen
sind in funktionalen Sprachen „Datentypen erster Klasse“, d.h. man kann mit ihnen alles machen, was
man in imperativen Sprachen mit Basistypen (wie int in C) machen kann. Funktion können deshalb in
Variablen abgespeichert werden oder Parameter und Rückgabewert von anderen Funktionen sein. Solche
Funktionen, die auf Funktionen angewandt werden oder Funktionen zurückgeben, nennt man Funktionen
höherer Ordnung.
Die folgende Aufzählung beschreibt weitere, sehr nützliche Eigenschaften, die häufig in funktionalen
Sprachen zum Einsatz kommen (aber nicht auf diese beschränkt sind):
Starke Typisierung (strong typing): Die Typen sämtlicher Werte und Funktionen können bereits vom
Compiler auf Korrektheit überprüft werden.
Typableitung (type inference): Typen von Werten und Funktionen müssen nicht explizit angegeben werden, sondern werden durch Analyse deren Verwendung automatisch abgeleitet.
Polymorphie: Ein und dieselbe Funktion kann mit Argumenten verschiedenen Typs verwendet werden.
Generische Programmierung wird durch Polymorphie und Funktionen höherer Ordnung ermöglicht. Dabei werden Funktionen so geschrieben, dass sie möglichst allgemein eingesetzt werden können. Beispiel: Eine Sortierfunktion in OCaml, die eine Liste von Werten und eine Vergleichs-Funktion als
Parameter hat, kann Listen beliebigen Typs sortieren.
Automatische Speicherverwaltung entbindet den Programmierer vom mühsamen und fehlerträchtigen
Freigeben von nicht mehr gebrauchtem Speicher.
Pattern-Matching ermöglicht eine übersichtliche und kompakte Syntax, um Fallunterscheidungen zu programmieren.10 In OCaml können dafür prinzipiell alle eingebauten (Listen, Arrays, Tupel, etc.) und
selbstdefinierten Datentypen verwendet werden.
2.5.1
Sprachen, die mehrere Paradigmen unterstützen
Rein funktionale Sprachen finden bei der Mehrzahl der Programmierer keinen großen Anklang, u.A. weil sie
einen dazu zwingen, Algorithmen auf eine bestimmte Art und Weise zu schreiben, z.B. ohne herkömmliche
Schleifen (for, while). Abgesehen vom mentalen Aufwand für die Umstellung des Programmierstils gibt
es auch Algorithmen, die imperativ einfach leichter zu beschreiben sind als rekursiv.
Nichtsdestotrotz werden funktionale Eigenschaften von Programmierern gerne verwendet, wenn sie in
gängige Sprachen aufgenommen werden, die ansonsten hauptsächlich imperativen Charakter haben, wie
z.B. Ruby, Python oder ECMAScript (besser bekannt als JavaScript).
9 So können Teilfunktionen problemlos gleichzeitig auf verschiedenen Prozessoren laufen, weil sie sich gegenseitig nicht beeinflussen können. D.h. rein funktionale Programme sind potentiell leichter parallelisierbar.
10 Pattern-Matching ist im Entferntesten ähnlich wie das switch/case -Statement von Sprachen wie C, C++ und Java (dieses
funktioniert im Wesentlichen nur für Ganzzahlen), nur viel mächtiger.
KAPITEL 2. PROBLEMANALYSE
16
Multi-Paradigmen-Sprachen oder pragmatische Sprachen vereinen mehrere Paradigmen, so hat z.B.
OCaml funktionale, imperative und objektorientierte Eigenschaften. Als Beispiel für die imperativen Eigenschaften von OCaml ist in Listing 2.12 die Implementierung der Fakultäts-Funktion (n!) der Implemen1
2
3
4
5
6
7
let fac n =
l e t r es = ref 1 in
f o r i =1 t o n do
r e s := ! r e s ∗ i
done ;
! res
;;
1
2
3
4
5
6
7
Listing 2.12: Fakultät: Imperativ in OCaml
int fac ( int n) {
int res = 1;
f o r ( i n t i = 1 ; i <=n ; i ++) {
res = res ∗ i ;
};
return ( r e s ) ;
}
Listing 2.13: Fakultät: Imperativ in C
tierung derselben Funktion in C (Listing 2.13) gegenübergestellt. Die Listings zeigen, dass man auch in
OCaml mit den gewohnten imperativen Konzepten wie Variablen, Zuweisungen und for -Schleifen arbeiten kann, wenn man will. Speziell für den Fall der Fakultät ist aber die rekursive Variante (Listing 2.14) für
OCaml eleganter und üblicher.
1
2
3
4
5
l e t rec fac n =
match n w i t h
0 | 1 −> 1
| n
−> n ∗ f a c ( n −1)
;;
Listing 2.14: Fakultät: Rekursiv in OCaml
2.5.2
Vorteile von OCaml
Funktionale Sprachen gelten im Allgemeinen als gute Wirtssprachen für die Einbettung von DSLs (vgl.
[13, Kap. 2.3, S. 25]). Die konkreten Vorteile der Sprache OCaml für die Implementierung von DSLs lauten
zusammengefasst:
• OCaml ist einfach zu verwenden
• OCaml ermöglicht eine hohe Codedichte
• OCaml ermöglicht generische Programmierung
Ein Grund für die Einfachheit der Verwendung von OCaml ist die Typableitung, die es unnötig macht, Typen
im Programm anzugeben. Der Benutzer muss deshalb kein Wissen über die abstrakten Datentypen haben,
mit Hilfe derer die DSL implementiert ist, sondern einfach nur bestimmte Funktionen und Operatoren laut
Benutzerhandbuch verwenden.
Die hohe Codedichte von OCaml hat ihre Ursachen u.A. in Typableitung, Pattern-Matching, Polymorphie und Funktionen höherere Ordnung (s. auch den folgenden Punkt über generische Programmierung).
Die für die generische Programmierung verantwortlichen Eigenschaften sind Polymorphie und Funktionen höherere Ordnung. Mit generischer Programmierung kann man Algorithmen allgemein beschreiben.
Auch generische Programmierung führt zu weniger Fehlern, denn wenn eine solche nicht möglich ist, muss
man Code händisch duplizieren und anpassen, was neue, potentielle Fehlerquellen birgt.
Abbildung 2.3, S. 17 zeigt als einfaches Beispiel die Funktionsweise der generische Funktion map2 .
Man hat zwei Listen mit einer gleichen Anzahl von beliebigen Elementen und will diese jeweils paarweise zu einem Ergebnis kombinieren, das an die entsprechende Position in einer Ergebnis-Liste kommt. Die
Funktion, die jeweils zwei einzelne Elemente verknüpft (Operator genannt), soll ein Parameter der generischen Funktion sein. Die Funktion map2 existiert in OCaml bereits und kann mit beliebigen Datentypen
und beliebigen (dazu passenden) Operatoren verwendet werden.
KAPITEL 2. PROBLEMANALYSE
17
[
[
a1
a2
a3
...
an
b1
b2
b3
...
bn
]
]
[
a1*b1
a2*b2
a3*b3
...
an*bn
]
Abbildung 2.3: Funktionsweise von map2
Für Beschreibung von Hardware ist generische Programmierung wichtig, weil in elektronischen Designs
oft regelmäßige Strukturen auftreten, die man nicht „händisch“ aus ihren Teilen aufbauen will, sondern
automatisch in einer Schleife. Wie in Abschnitt 2.2.1 (unter „Generische Beschreibungen“, S. 10) diskutiert,
gibt es solche generische Funktionen in VHDL nur ansatzweise.
2.6
Bildverarbeitung in Hardware
Bildverarbeitung ist eine Anwendung, die sich ausgezeichnet für den Einsatz auf ASICs oder FPGAs eignet
und dort typischerweise eine viel höhere Geschwindigkeit erzielt als auf gewöhnlichen CPUs oder auf
Signalprozessoren. Gründe dafür sind (vgl.[18]):
•
•
•
•
2.6.1
Parallelität
Datenabhängigkeiten sind lokal und regelmäßig
nur einfache arithmetische oder logische Festkomma-Operationen notwendig
relativ geringe Bitbreiten notwendig (8 Bit pro Farbkanal sind oft ausreichend)
Hardwarebeschreibungssprachen für Bildverarbeitung
Bildverarbeitungsalgorithmen in Hardwarebeschreibungssprachen zu schreiben, die keine spezielle Unterstützung dafür bieten, ist üblicherweise mühsam und aufwändig. Das ist deshalb so, weil die optimale
Implementierung einer Schaltung in Hardware (z.B. die Kombination von Multiplizieren, Addierern und
Schieberegistern in Abbildung 2.5, S. 19) nicht mit der üblichen Vorstellung des Programmierers (Bilder
sind Arrays) übereinstimmt. Es ist deshalb immer wieder unnötiger Entwicklungs-Aufwand notwendig, um
einen (an und für sich trivialen) BV-Algorithmus in eine effiziente Hardware-Beschreibung umzuwandeln.
Es existieren Sprachen, die speziell für die Umsetzung von Bildverarbeitungs-Algorithmen in Hardware entwickelt wurden. „Sassy“[19, 20], eine dieser Sprachen, wird in dieser Arbeit (Abschnitt 4.7, S. 58)
genauer betrachtet.
Anhand des folgenden Beispiels wurde untersucht, ob und inwiefern die Sprache HDCaml bei der
Beschreibung von Bildverarbeitungs-Algorithmen einen Vorteil gegenüber verbreiteten Hardwarebeschreibungssprachen wie VHDL oder Verilog hat und ob sie mit Spezialsprachen wie Sassy vergleichbar ist.
2.6.2
Beispiel: Faltungsfilter
Als Anwendungsbeispiel zur Umsetzung eines einfachen Bildverarbeitungs-Algorithmus’ in Hardware war
die Implementierung eines Faltungsfilters vorgegeben. Abbildung 2.4, S. 18 zeigt dessen Funktionsweise.
KAPITEL 2. PROBLEMANALYSE
18
Ein Filter-Kernel (in der Abbildung „Mask“ genannt) wird sukzessive über alle Positionen des EingangsBildes gelegt und der resultierende Wert im Ergebnis-Bild ausgerechnet, indem die Koeffizienten des Kernels mit den darunterliegenden Bildwerten multipliziert und aufsummiert werden.
Origin
x
Mask
f(x-1,y-1)
f(x,y-1)
y
f(x+1,y-1)
w(-1,-1)
w(-1,0)
w(-1,1)
w(0,-1)
w(0,0)
w(0,1)
w(1,-1)
w(1,0)
w(1,1)
f(x-1,y) f(x-1,y+1)
Image f(x,y)
f(x,y)
Mask coefficients showing
coordinate arrangement
f(x,y+1)
f(x+1,y) f(x+1,y+1)
Pixels of image
section under mask
Abbildung 2.4: Funktionsweise eines Faltungsfilters (aus [21])
Diese Aufgabenstellung war bereits Bestandteil der Magisterarbeit von Reinhold Schmidt mit dem Titel
„Image Processing on Field Programmable Gate Arrays using the Hardware Description Language Confluence“[22]. HDCaml ist das Nachfolgeprojekt der Sprache Confluence[7] und hat die gleichen Zielsetzungen wie diese. Es ist deshalb sinnvoll, das gleiche Beispiel zu verwenden und die jeweiligen Ergebnisse
miteinander zu vergleichen.
Zur Implementierung in HDCaml wurde das gleiche Hardware-Modell (s. Abbildung 2.5, S. 19) wie in
[22] gewählt. Die Abbildung zeigt (vereinfacht) die Hardware für ein Faltungsfilter mit einer Kernel-Größe
von 3 mal 3 Punkten. Die einzelnen Pixel des Eingabe-Bildes werden (links oben) sequentiell in die Kette
von Schieberegistern geschoben. Sobald alle neun Register mit Pixel-Werten gefüllt sind, kann mit jedem
neuen Clock-Zyklus ein Pixel des Ausgabebildes berechnet werden, indem neun Multiplikationen und acht
Additionen gleichzeitig ausgeführt werden.
2.7
Kriterien für Evaluierung der Ergebnisse
Um die Ergebnisse der Verbesserungen an HDCaml sowie die Implementierung des BV-Beispiels zu bewerten, die im Rahmen dieser Arbeit gemacht wurden, sind Kriterien bzw. Metriken notwendig.
KAPITEL 2. PROBLEMANALYSE
19
Factor
Reg
Reg
Reg
Mul
Delay-Chain
Factor
Reg
Reg
Reg
Mul
Delay-Chain
Reg
Factor
Reg
Reg
Add
Reg
Mul
Abbildung 2.5: Implementierung eines Faltungsfilters (aus [22])
2.7.1
HDCaml-Erweiterungen
Zur Evaluierung der Änderungen sollen folgende Kriterien dienen:
Funktionalität: Wurde die Funktionalität der Sprache erweitert?
Korrektheit: Konnten Fehlfunktionen behoben werden?
Benutzerfreundlichkeit: Ist HDCaml benutzerfreundlicher als vorher? Funktioniert die Interaktion (bzw.
Kommunikation) zwischen HDCaml und dem Benutzer besser als vorher?
Aufwand für Modellierung: Können durch die Änderungen an HDCaml jetzt Hardware-Modelle kompakter als vorher formuliert werden?
Codequalität: Ist der Code der Bibliothek durch die Änderungen lesbarer, korrekter, homogener?
Dokumentation: Ist die Qualität der Dokumentation in der neuen Version besser?
Erweiterbarkeit: Ist HDCml durch die Änderungen einfacher zu erweitern?
Meinungen: Was sagen andere Benutzer der neueren Version darüber?
2.7.2
Bildverarbeitung
Bei den Kriterien für die Evaluierung zum Themenbereich Bildverarbeitung muss man sich folgende Fragen
stellen:
1. Ist das Resultat des Filters korrekt?
2. Ist HDCaml gleich gut geeignet um BV-Algorithmen in Hardware zu modellieren wie Spezialsprachen (z.B. Sassy)?
3. Wie ist der Aufwand um den Algorithmus zu implementieren? Gibt es qualitative und/oder quantitative Unterschiede im Aufwand im Vergleich zu Confluence und VHDL?
4. Wie gut lässt sich das HDCaml-Modell synthetisieren und simulieren?
Kapitel 3
Die Hardwarebeschreibungssprache
HDCaml
In diesem Kapitel wird die Sprache HDCaml vorgestellt. In Abschnitt 3.1 wird ein Überblick über Funktionalität, Einschränkungen und Vorteile von HDCaml gegeben. Die folgenden Abschnitte beschreiben den
Aufbau von HDCaml-Programmen (Abschnitt 3.2) und den Arbeitsablauf bei der Verwendung von HDCaml (Abschnitt 3.3). In Abschnitt 3.4 wird auf die Hardwarebeschreibungsmöglichkeiten in HDCaml
eingegangen. In Abschnitt 3.5 folgen Informationen über die Implementierung und Dokumentation der
Sprache HDCaml und in Abschnitt 3.6 Informationen über HDCaml im Internet.
3.1
Funktionalität
HDCaml ist eine domänenspezifische Programmiersprache (domain-specific language, DSL), die durch
Einbettung (Abschnitt 2.4, S. 13) in OCaml realisiert ist. Anders ausgedrückt: die Sprache HDCaml ist die
Sprache OCaml, zusammen mit einer speziellen Funktions-Bibliothek (im Folgenden HDCaml-Bibliothek
genannt).
Durch die Art der Implementierung von HDCaml kann man in dieser Sprache die gesamte Typenvielfalt und Sprach-Funktionalität von OCaml verwenden, genauso wie dessen umfangreiche StandardBibliotheken, wodurch man eine sehr mächtige und flexible Sprache zur Verfügung hat.
HDCaml unterstützt folgende Funktionalität:
• Beschreibung von digitalen, sequentiellen, synchronen Schaltungen mit einer globalen, impliziten
Clock auf der Register-Transfer-Ebene (Register Transfer Level, RTL),
• PSL-Assertions (nur teilweise implementiert)
• Erzeugung von synthetisierbarem Verilog-Code aus der RTL-Beschreibung
• Erzeugung von synthetisierbarem VHDL-Code aus der RTL-Beschreibung
• Schreiben beliebiger Signalverläufe in VCD-Files
• Simulation der Schaltung direkt in OCaml mit VCD-Output
• Erzeugung von C-Code (mit SystemC-Header), der die Schaltung simuliert
20
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
3.1.1
21
Konzeptuelle Einschränkungen
Beim Design von HDCaml wurde das Einsatzgebiet bewusst auf die Beschreibung einer Teilmenge von
Schaltungen reduziert (vgl. Abschnitt 2.3, S. 12). Als Resultat konnte auf viele Sprachelemente anderer
Hardwarebeschreibungssprachen verzichtet werden und die Implementierung von HDCaml konnte einfach
gehalten werden. Mit HDCaml kann man nur die Teilmenge der
• digitalen,
• sequentiellen,
• synchronen
Schaltungen beschreiben. Folgende Schaltungen können mit HDCaml nicht beschrieben werden:
•
•
•
•
analoge Schaltungen,
asynchrone Schaltungen,
Schaltungen, die Tristate-Elemente und bidirektionale Busse enthalten und
Schaltungen, die Clock-Manipulationen (z.B. clock gating) vornehmen.
Durch diese bewussten Einschränkungen kann viel Aufwand bei der Implementierung und Verwendung von
HDCaml vermieden werden. Trotzdem kann mit dieser Sprache eine wesentliche Teilmenge von digitalen
Schaltungen beschrieben werden.
Gleichzeitig wurde durch die Einschränkungen die Umsetzung der Funktionalität in der HDCamlBibliothek durch sehr wenig Code möglich. In der Version 0.2.9 besteht HDCaml aus knapp 3500 Zeilen
OCaml-Code (s. Tabelle 5.1, S. 63).
3.1.2
Einschränkungen aufgrund nicht implementierter Sprachelemente
Weitere, wesentliche Einschränkungen resultieren daraus, dass manche Features noch nicht implementiert
wurden. Es spricht jedoch nichts dagegen, dies in kommenden Releases von HDCaml zu tun. Bei diesen
Einschränkungen handelt es sich unter Anderem um folgende:
•
•
•
•
•
•
3.1.3
kein RAM/ROM
keine FSM (finite state machines)
keine black boxes
keine Komponenten in generierten HBS-Modellen
nur impliziter Reset für gesamte Schaltung, kein expliziter für einzelne Komponenten
nur eine einzige, implizite Clock für gesamte Schaltung, keine explizite für einzelne Komponenten
Vorteilhafte Eigenschaften von HDCaml
Folgende Eigenschaften von HDCaml sind besonders nützlich:
Typableitung: Diese Eigenschaft von OCaml ermöglicht eine einfache und kompakte Verwendung der
HDCaml-Funktionen, weil man Datentypen nicht explizit deklarieren muss, sondern diese vom Compiler automatisch abgeleitet werden können (vgl. Abschnitt 2.5.2, S. 16).
Bitbreiten-Ableitung: Diese Funktionalität bedeutet, dass die Bitbreite von Ergebnis-Bitvektoren automatisch bestimmt wird und nicht angegeben werden muss (ein Beispiel dafür ist Listing 3.1, S. 23 in
Abschnitt 3.2, technische Details dazu gibt es in Abschnitt C.2, S. 99).
Leichtes Umgruppieren von Funktionalität: Funktionalität kann nahezu beliebig umgruppiert werden,
ohne Typinformationen und Bitbreiten-Informationen an vielen verschiedenen Stellen im Programm
ändern zu müssen, wie es z.B. bei VHDL notwendig ist (vgl. [13, „Type inference“, S. 51f]). Das
erleichtert Design-Iterationen (vgl. Abschnitt 2.2.1 unter „Design-Iterationen“, S. 12) und erhöht die
Wiederverwendbarkeit von Code.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
22
Beliebige Programme möglich: Es ist die gesamte Funktionalität von OCaml nutzbar, nicht nur die Hardwarebeschreibungs-Funktionalität von HDCaml (vgl. Abschnitt 2.4, S. 13).
Generische Programmierung/Beschreibungen: Aus einer allgemeinen Beschreibung können HardwareModelle für ganze Klassen von digitalen Schaltungen generiert werden (vgl. Abschnitt 2.2.1 unter
„Generische Beschreibungen“, S. 10 und Abschnitt 3.4.4, S. 34).
Eingebaute Simulation: Diese Fähigkeit ermöglicht es (zusammen mit der Möglichkeit, beliebige Programme zu erstellen), dass man Testbenches für Hardware-Modell direkt in HDCaml schreiben kann
(z.B. Listing 3.5 in Abschnitt 3.3, S. 27). Da die Hardware-Beschreibungen generisch sind, ist es
vorteilhaft, auch für die Testbenches eine Sprache zu verwenden, die generische Programmierung
erlaubt.
Erzeugung eines C-Modells: Dadurch kann die Simulation der Hardware in übergeordnete SimulationsUmgebungen eingeordnet werden, die oft in C oder C++ geschrieben sind [13, S. 5]. Ein weiterer Vorteil gegenüber händischer Erstellung von C-Modellen ist, dass bei Design-Iterationen das generierte
C-Modell automatisch auch auf dem neuesten Stand gebracht wird [13, S. 34]. Siehe Listing 3.8, S. 31
in Abschnitt 3.3 für die Verwendung eines erzeugten C-Modells.
Integrierbar in bestehende Arbeitsabläufe: Durch die Erzeugung von Hardware-Modellen in den Sprachen VHDL, Verilog und C mit SystemC-Header (s. Abschnitt 3.3.1, S. 29).
3.2
Aufbau eines HDCaml-Programms
Die vorliegende Arbeit kann aus Platzgründen keine umfassende Einführung in die Sprachen OCaml und
HDCaml sein, sondern nur ein Überblick. Verschiedenste Dokumentation zu OCaml findet man auf dessen Webseite [2], ein gutes, einführendes Buch ist [4] (kostenloser Download möglich). Ein Tutorial für
HDCaml (und den notwendigen Teilen von OCaml) wurde 2006 von Daniel Sánchez Parcerisa erstellt [23].
Sowohl dieser, als auch der folgende Abschnitt (3.3 Arbeitsablauf, S. 27) sind für das Verständnis der
Funktionsweise von HDCaml unbedingt notwendig (insbesondere Abbildung 3.2, S. 29).
Beim Erstellen eines Hardware-Modells in der Sprache HDCaml muss man sich dessen bewusst sein,
dass man eigentlich ein OCaml-Programm schreibt, das bestimmte HDCaml-Bibliotheksfunktionen aufruft, also ein HDCaml-Programm. Zur Klarstellung dieses Sachverhalts wird anschliessend ein sehr kurzes
Programm besprochen. Abbildung 3.1, S. 23 dient als Illustration für den Ablauf des HDCaml-Programms
in Listing 3.1, S. 23.
Beschreibung des Programms
Mit den Zeilen 1 und 2 werden die Module Hdcaml und Design (dieses befindet sich innerhalb des
Namensraums des Moduls Hdcaml , wie auch in Abbildung 3.1, S. 23 angedeutet) geöffnet. Dadurch kann
man die in diesen Modulen enthaltenen Funktionen durch ihren Namen ansprechen, ohne die ganze Hierarchie im Namensraum angeben zu müssen. Man kann also z.B. input statt Hdcaml.Design.input
schreiben.
Das Modul Hdcaml beinhaltet die gesamte HDCaml-Bibliothek. Das Unter-Modul Design enthält
hauptsächlich Funktionen und Operatoren um die Elemente der digitalen Schaltung und deren Verbindungen untereinander zu spezifizieren. Tabelle 3.1, S. 25 listet alle Funktionen und Operatoren zur Schaltungsbeschreibung in HDCaml auf. Eine umfassende Beschreibung aller Funktionen der HDCaml-Bibliothek
befindet sich im Anhang A, S. 85.
In den Zeilen 4 und 10 werden Funktionen zur Strukturierung des Designs verwendet, die den Anfang
und den Namen ( start_circuit ”mult” ) und das Ende ( get_circuit ) der Schaltungsbeschreibung innerhalb des HDCaml-Programms definieren.
In den Zeilen 6 und 7 werden zwei Input-Ports der beschriebenen Schaltung definiert, einer mit dem
Namen ”a” und der Breite 3 und einer mit dem Namen ”b” und der Breite 5. In a und b wird auf die
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
1
2
23
open Hdcaml ; ;
open D e s i g n ; ;
3
4
s t a r t _ c i r c u i t " mult " ;
5
6
7
8
l e t a = input " a " 3 in
l e t b = input "b" 5 in
o u t p u t " prod " ( a ∗: b ) ;
(∗ 3 b i t ∗)
(∗ 5 b i t ∗)
( ∗ a u t o m a t i s c h , s . g e n e r i e r t e s VHDL ∗ )
9
10
11
l e t c = g e t _ c i r c u i t ( ) in
Vhdl . o u t p u t _ n e t l i s t c ;
Listing 3.1: Kurzes HDCaml-Programm zur Besprechung des Aufbaus
Abbildung 3.1: Interne Abläufe bei der Ausführung eines HDCaml-Programms
Rückgabewerte der beiden Funktionsaufrufe von input verwiesen. Dazu dient das Konstrukt „ let ...
= ... in ... “. Kommentare werden zwischen „(*“ und „*)“ eingeschlossen und können geschachtelt werden.
In Zeile 8 wird ein nicht-vorzeichenbehafteter Multiplizierer (vgl. Abschnitt 3.2.2, S. 26) in das Hardware-Modell aufgenommen, der das Produkt von a und b bildet und sein Ergebnis wird mit dem OutputPort namens „ prod “ verbunden. Man beachte, dass für den Output (im Gegensatz zu VHDL und Verilog)
keine Bitbreite angegeben werden muss, diese wird automatisch abgeleitet. Für Multiplikationen in HDCaml gilt: Bitbreite des Ergebnisses = Bitbreite von a + Bitbreite von b .
Der Aufruf von get_circuit in Zeile 10 liefert die Repräsentation der beschriebenen Schaltung
und diese wird in Zeile 11 dazu verwendet, ein VHDL-Modell der Schaltung zu schreiben.
Abschließend sei noch bemerkt, dass im gesamten Programm keine Typdeklarationen vorkommen, weil
sie durch die Typableitung von OCaml unnötig sind.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
24
Vergleich des HDCaml-Programms mit dem generierten VHDL-Modell
Das generierte VHDL-Modell ist als Listing 3.2 abgedruckt.1 Man sieht, dass aus dem Teil des HDCamlProgramms, der zwischen start_circuit und get_circuit liegt, ein Entity (Zeilen 5 bis 15) und
eine dazugehörige Architecture (Zeilen 17 bis 50, s. Fußnote 1) erzeugt wurden. Sowohl der Name des
Entities als auch der Name der erzeugten Datei entsprechen dem bei start_circuit angegebenem
Namen.
Die Inputs mit den Bitbreiten 5 und 3 sind in den Zeilen 10 und 11 deklariert, der Output mit der
automatisch abgeleiteten Bitbreite 8 in Zeile 13. In den Zeilen 8 und 9 sieht man die immer (auch bei rein
kombinatorischen Schaltungen wie der vorliegenden) implizit vorhandenen Inputs clock und reset .
Die nicht vorzeichenbehaftete Multiplikation steht in Zeile 48.
1
2
3
library ieee ;
use i e e e . s t d _ l o g i c _ 1 1 6 4 . a l l ;
use i e e e . n u m e r i c _ s t d . a l l ;
4
5
6
7
8
9
10
11
12
13
14
15
e n t i t y mult i s
port (
−− i n p u t s :
r e s e t : in s t d _ l o g i c ;
clock : in s t d _ l o g i c ;
b : i n s t d _ l o g i c _ v e c t o r ( 4 downto 0 ) ;
a : i n s t d _ l o g i c _ v e c t o r ( 2 downto 0 ) ;
−− o u t p u t s :
p r o d : o u t s t d _ l o g i c _ v e c t o r ( 7 downto 0 )
);
end ;
16
17
46
47
48
49
50
a r c h i t e c t u r e s t r u c t u r e of mult i s
begin
−−( c i r c u i t m u l t
p r o d <= s t d _ l o g i c _ v e c t o r ( u n s i g n e d ( a ) ∗ u n s i g n e d ( b ) ) ;
−−e n d c i r c u i t m u l t )
end ;
Listing 3.2: Aus HDCaml-Beschreibung generierter VHDL-Code
Beschreibung der HDCaml-internen Vorgänge
Abbildung 3.1, S. 23 zeigt die Vorgänge, die HDCaml-intern während der Ausführung des Programms
in Listing 3.1, S. 23 geschehen. Beim Aufruf von jeder Funktion und jedem Operator, die zum Modul
Design gehören (das sind start_circuit , input , output , *: , get_circuit ), verzweigt
der Programmfluss in die HDCaml-Bibliothek und es werden dort Manipulationen an einer dynamischen
Datenstruktur vorgenommen, die das Modell der aufzubauenden Schaltung darstellt. Der die Phase der
Schaltungseingabe abschließende Funktionsaufruf von get_circuit liefert als Rückgabewert das Modell der gesamten Schaltung. Dieses wird an die Funktion Vhdl.output_netlist übergeben, die
daraus das VHDL-Modell erzeugt.
3.2.1
Funktionen und Wiederverwendung
Ein Aufruf einer HDCaml-Funktion entspricht sinngemäß der Instanzierung einer Teilschaltung. Allerdings
„weiß“ HDCaml nichts von benutzerdefinierten Funktionen. Es „weiß“ lediglich etwas über den Aufruf von
HDCaml-Bibliotheksfunktionen. Deshalb können selbstgeschriebene Funktionen vollkommen nach Belieben verwendet werden.
1
Die fehlenden Zeilen 18 bis 45 werden in Abschnitt C.6, S. 102 besprochen.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
Strukturierung:
start_circuit, get_circuit
circuit, endcircuit
Anfang und Ende der Schaltungsbeschreibung
Anfang und Ende einer Teilschaltung
Signale, Zuweisung, Annotation:
„Dangling Signal“ (unverbundenes Signal)
Zuweisung an Dangling Signal
„Annotation“ (Benennung eines Signals)
signal
<==
-Bitbreite:
Bitbreite eines Signals
width
Ports:
input, output
Input/Output-Ports
Konstanten:
empty
const
one, ones, zero
vdd, high, gnd, low
Leerer Bitvektor (hat Bitbreite 0)
Konstanter Bitvektor
1, -1 (in 2er-Komplement-Darstellung) u. 0 mit N Bits
Logische 1, logische 0
Bit-Manipulation:
select, bit
++, concat
repeat
msb, msbs
lsb, lsbs
split, bits
Teilbereich bzw. einzelnes Bit eines Bitvektors
Verknüpfen zweier Bitvektoren bzw. einer Liste von BV
mehrfache Aneinanderreihung eines Bitvektors
MSB, Alle Bits ohne LSB
LSB, Alle Bits ohne MSB
Aufspaltung in zwei Teile bzw. in einzelne Bits
Logische Operatoren:
~:, &:, ^:, |:
NOT, AND, XOR, OR
Arithmetische Operatoren:
+:, -:, *:, *+
Add., Subtr., Mult. (unsigned, signed)
Shift-Operatoren:
<<:, <<+, >>:, >>+
Shift-Left und Shift-Right (jeweils unsigned, signed)
Vergleichs-Operatoren:
==:, /=:
<:, <+, <=:, <=+
>:, >+, >=:, >=+
gleich, ungleich
kleiner, kleiner-gleich (jeweils unsigned, signed)
größer, größer-gleich (jeweils unsigned, signed)
Multiplexer:
mux2, mux
2-fach- bzw. N-fach-Multiplexer
Register:
reg
Register
Tabelle 3.1: Funktionen und Operatoren zur Schaltungsbeschreibung in HDCaml
25
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
26
Eine Funktion muss deshalb nicht bei jedem Aufruf das gleiche Ergebnis liefern, sondern dieses kann
sich je nach Argumenten unterscheiden. Je nach Typ des Rückgabewertes hat man mehr oder weniger
Freiheitsgrade:
signal: Wenn der Rückgabewert ein Bitvektor ist, ist für das Typsystem von OCaml die Bitbreite egal.
D.h. eine Funktion kann in Abhängigkeit von ihren Argumenten unterschiedlich breite Bitvektoren
liefern. Sollten im Programm inkompatible Bitbreiten auftreten, wird ein Fehler gemeldet (Die Fehlererkennung findet in der HDCaml-Bibliothek statt, nicht im OCaml-Compiler).
signal list: Listen von Signalen können variable Länge haben, jedes Element der Liste kann eine
unterschiedliche Bitbreite haben.
Funktionen: in funktionalen Sprachen können Rückgabewerte auch Funktionen sein, die man später auswerten kann, usw. . .
Durch diese Flexibilität sind HDCaml-Funktionen potentiell wesentlich besser wiederverwendbar als z.B.
in VHDL geschriebene Komponenten.
Eine zu den generic -Angaben von VHDL-Entities vergleichbare Funktionalität kann in HDCaml
einfach durch weitere Parameter der Funktion erreicht werden. Es ist z.B. in HDCaml üblich, Funktionen
einen width (oder ähnlich) benannten Parameter zu geben, mit dem die Bitbreite der dadurch beschriebenen Schaltung variierbar ist, falls sie nicht ohnehin aus den Bitbreiten der Eingangsvektoren ableitbar ist.
Ein Beispiel dafür ist Listing 3.10, S. 33, ein weiteres Listing 3.14, S. 36.
3.2.2
Operatoren und Unterscheidung signed/unsigned
Bitvektoren sind in HDCaml immer nur Bitmuster und ihre Interpretation hängt von den Operatoren ab,
mit denen sie verwendet werden. Deshalb gibt es für alle relevanten Operatoren (das sind Logische Op.,
Arithmetische Op., Shift-Op. und Vergleichs-Op., s. Tabelle 3.1, S. 25) zwei Versionen. Die vorzeichenbehaftete (signed) Variante endet mit einem Pluszeichen (z.B. Multiplikation: „*+“, größer-gleich: „>=+“),
die nicht-vorzeichenbehaftete (unsigned) Variante mit einem Doppelpunkt („*:“, „>=:“).
3.2.3
Ebenen eines HDCaml-Programms
Eine Schaltungsbeschreibung in HDCaml ist ein ganz normales OCaml-Programm, das nach dem Start abgearbeitet wird. Diese Ebene von HDCaml nenne ich die „Programmebene“. Die andere Ebene, diejenige,
mit der Hardware beschrieben wird, soll hier als „HW-Ebene“ bezeichnet werden. Die durch das HDCaml
beschriebene Schaltung läuft selbstverständlich parallel, wenn sie in Hardware transferiert oder simuliert
wird. Das hat aber mit dem Programmfluss auf der Programmebene nichts zu tun.
In Sprachen wie Verilog und VHDL gibt es diese Trennung zwischen den beiden Ebenen zum Teil auch,
sie ist aber syntaktisch nicht so leicht zu erkennen (am ehesten noch bei den GENERATE-Statements2 )
was leichter zu Missverständnissen führen kann. In VHDL nennen sich die beiden Ebenen „Elaboration“
(enspricht der Programmebene) und „Execution“ (enspricht der HW-Ebene), vgl. [8, Kap. 12, S. 153].
Im VHDL-Beispiel von Abschnitt 2.1.4, S. 7, in dem gezeigt wurde, wie man einen 4-Bit-Addierer mit
Hilfe von generate aus vier Volladdieren konstruiert (Listing 2.6, S. 7), kann man die beiden Ebenen
gut sehen. Die Schleife in den Zeilen 24 bis 30 wird nicht in Hardware übersetzt, sondern sie läuft schon
vorher ab, zum Zeitpunkt der „Elaboration“. Die Strukturbeschreibung in den Zeilen 25 bis 29 dient zur Instanzierung eines Volladdierers und diese Instanzierung findet vier mal statt. Diese vier Volladdierer bilden
dann die Grundlage der „Execution“ von VHDL.
Listing 3.3, S. 27 zeigt ein Beispiel für eine nicht beabsichtigte Vermischung der Ebenen in einem
HDCaml-Programm. Dieser Code verwendet ein if (Programmebene) in einem Zusammenhang (Vergleich des Zählerstandes mit einem Maximalwert), der einen Multiplexer mux2 (HW-Ebene) erfordert, da
der Vergleich des Zählerstands ja in Hardware erfolgen soll. Korrekt muß der Code wie in Listing 3.4, S. 27
2 Verilog
hat ab Version 2001 auch sehr mächtige Generate-Funktionalität.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
1
2
3
l e t c o u n t e r w i d t h max =
l e t next = s i g n a l " next " width in
l e t count = reg high next in
4
2
3
l e t c o u n t e r w i d t h max =
l e t next = s i g n a l " next " width in
l e t count = reg high next in
4
n e x t <== i f c o u n t = max
then z e r o width
e l s e c o u n t + : one w i d t h ;
count
5
6
7
8
9
1
27
;;
6
7
8
9
Listing 3.3: Fehlerhaftes HDCaml-Programm
n e x t <== mux2 ( c o u n t ==: max )
( zero width )
( c o u n t + : one w i d t h ) ;
count
5
;;
Listing 3.4: Korrektes HDCaml-Programm
lauten. In Listing 3.3 ist der Vergleich if count = max syntaktisch korrekt, da count und max
den gleichen Datentyp haben ( signal ), liefert aber immer false , da count und max nicht das
gleiche Signal sind. Deshalb wird immer die im else -Zweig beschriebene Hardware ( count +: one
width ) zum Gesamtmodell hinzugefügt und der resultierende Zähler zählt über den Maximalwert max
hinaus.
Die beiden Ebenen sind eindeutig an den Namen von verwendeten Funktionen und Operatoren zu unterscheiden, alle jene, die in Tabelle 3.1, S. 25 aufgelistet sind, gehören zur HW-Ebene.
Zwischen den beiden Ebenen sind keine Werte übertragbar, mit einer Ausnahme: Irgendwie muß es
möglich sein, in der Schaltung konstante Werte abzulegen. Das passiert durch den Aufruf von Funktionen
(const, zero, one, ones) bzw. der Verwendung von Konstanten (vdd, high, gnd, low).
Diese können sozusagen Informationen in eine Richtung transportieren, nämlich von der Programmebene
in die HW-Ebene.
3.3
Arbeitsablauf
In desem Abschnitt wird anhand des Beispiels in Listing 3.5, S. 28 (Zähler mit konfigurierbarer Bitbreite) der Arbeitsablauf bei der Verwendung von HDCaml besprochen. Bereits in Abbildung 1.1, S. 2 wurde
gezeigt, wie sich der gewohnte Entwurfsprozess bei der Verwendendung von HDCaml ändert. Für den zusätzlichen Schritt „Code generieren“ gibt es mehrere Varianten, die in Abschnitt C.8, S. 105 besprochen
werden. Die hier empfohlene Variante mit den beiden Schritten „Kompilieren“ und „Ausführen“ ist in
Abbildung 3.2, S. 29 dargestellt. Die notwendigen Befehle für die beiden Schritte lauten (unter der Voraussetzung, dass das HDCaml-Programm count.ml heißt):
Kompilieren:
Ausführen:
ocamlc -g -o count.exe hdcaml.cma count.ml
ocamlrun -b count.exe
Fehlermeldungen und Ausgaben
Aufgrund der Implementierung von HDCaml (Einbettung in OCaml, s. Abschnitt 2.4, S. 13) sind die möglichen Meldungen und Ausgaben, die in den beiden Schritten (Abbildung 3.2, S. 29) erzeugt werden, von
grundlegend unterschiedlicher Art:
Kompilieren: Beim Übersetzen des HDCaml-Quellcodes durch den OCaml-Compiler werden alle Syntaxund Typfehler gefunden und dementsprechende Warnungen und Fehlermeldungen ausgegeben. Die
Bitbreite von Bitvektoren (Typ signal , s. Abschnitt 3.5.3, S. 39 u. Abschnitt C.1, S. 98) kann aus
technischen Gründen (s. Abschnitt C.2.1, S. 101) nicht vom OCaml-Compiler geprüft werden.
Bei kritischen Fehlern wird die Übersetzung des Programms abgebrochen und es wird keine ausführbare Datei erzeugt.
52
51
50
49
48
47
46
45
44
43
42
41
40
39
38
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
Listing 3.5: HDCaml-Beispielprogramm zur Besprechung des Arbeitsablaufs
counter_design 2;;
;;
simulate_counter circuit ;
Vhdl . o u t p u t _ n e t l i s t c i r c u i t ;
Verilog . o u t p u t _ n e t l i s t c i r c u i t ;
Systemc . output_model c i r c u i t ;
l e t c i r c u i t = g e t _ c i r c u i t ( ) in
l e t en = i n p u t " e n a b l e " 1 i n
l e t c o u n t = c o u n t e r w i d t h en i n
output " count_out " count ;
l e t counter_design width =
s t a r t _ c i r c u i t " counter_example " ;
l e t counter width enable =
l e t next_count = s i g n a l " next_count " width in
l e t count = reg enable next_count in
l e t sum = c o u n t + : ( one w i d t h ) i n
n e x t _ c o u n t <== sum ;
count
;;
;;
c l o s e _ o u t vcd
f o r i =0 t o 10 do
e n a b l e . ( 0 ) <− i mod 2 ;
S i m u l a t e . c y c l e sim ;
p r i n t f " c y c l e %2d : c o u n t _ o u t =%2d \ n " i c o u n t _ o u t . ( 0 ) ;
done ;
p r i n t f " Simulating : \ n" ;
l e t sim , i n p u t s , o u t p u t s = S i m u l a t e . c r e a t e c i r c u i t vcd i n
l e t enable
= List . assoc " enable "
i n p u t s in
l e t count_out = L i s t . assoc " count_out " outputs in
let simulate_counter circuit =
l e t vcd_name = " c o u n t e r _ e x a m p l e . vcd " i n
l e t vcd = o p e n _ o u t vcd_name i n
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
55
54
53
52
51
50
49
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
6
5
4
3
2
1
5
open Hdcaml ; ;
open D e s i g n ; ;
open P r i n t f ; ;
4
3
2
1
Listing 3.7: Aus HDCaml-Beschreibung generierter Verilog-Code
/ / ( c i r c u i t counter_example
a l w a y s @ ( p o s e d g e r e s e t or p o s e d g e c l o c k ) i f ( r e s e t ) n_12 <= 2 ’ b0 ;
e l s e i f ( e n a b l e ) n_12 <= n e x t _ c o u n t ;
a s s i g n n e x t _ c o u n t = ( n_12 + 2 ’ b01 ) ;
a s s i g n c o u n t _ o u t = n_12 ;
/ / e n d c i r c u i t counter_example )
endmodule
/ / named s i g n a l s :
wire
[1:0] next_count ;
/ / unnamed s i g n a l s :
reg
[ 1 : 0 ] n_12 ;
module c o u n t e r _ e x a m p l e ( r e s e t , c l o c k , e n a b l e , c o u n t _ o u t ) ;
/ / inputs :
input
reset ;
input clock ;
input enable ;
/ / outputs :
output [ 1 : 0 ] count_out ;
Listing 3.6: Aus HDCaml-Beschreibung generierter VHDL-Code
architecture s t r u c t u r e of counter_example i s
−− named s i g n a l s :
s i g n a l n e x t _ c o u n t : s t d _ l o g i c _ v e c t o r ( 1 downto 0 ) ;
−− unnamed s i g n a l s :
s i g n a l n_12 : s t d _ l o g i c _ v e c t o r ( 1 downto 0 ) ;
begin
−−( c i r c u i t c o u n t e r _ e x a m p l e
p r o c e s s ( c l o c k , r e s e t ) b e g i n i f ( r e s e t = ’ 1 ’ ) t h e n n_12 <= ( o t h e r s
= > ’0 ’) ; e l s i f r i s i n g _ e d g e ( c l o c k ) t h e n i f ( e n a b l e = ’ 1 ’ ) t h e n
n_12 <= n e x t _ c o u n t ; end i f ; end i f ; end p r o c e s s ;
n e x t _ c o u n t <= s t d _ l o g i c _ v e c t o r ( u n s i g n e d ( n_12 ) + u n s i g n e d ( (
s t d _ l o g i c _ v e c t o r ’ ( " 01 " ) ) ) ) ;
c o u n t _ o u t <= n_12 ;
−−e n d c i r c u i t c o u n t e r _ e x a m p l e )
end ;
entity counter_example i s
port (
−− i n p u t s :
r e s e t : in s t d _ l o g i c ;
clock : in s t d _ l o g i c ;
enable : in s t d _ l o g i c ;
−− o u t p u t s :
c o u n t _ o u t : o u t s t d _ l o g i c _ v e c t o r ( 1 downto 0 )
);
end ;
library ieee ;
use i e e e . s t d _ l o g i c _ 1 1 6 4 . a l l ;
use i e e e . n u m e r i c _ s t d . a l l ;
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
28
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
29
HDCaml-Quelltext
(count.ml)
HDCaml-Bibliothek
(hdcaml.cma)
Kompilieren
Fehlermeldungen
ausführbares HDCaml-Programm
(count.exe)
Ausführen
Verilog
VHDL
Fehlermeldungen
C / SystemC
(count.v) (count.vhd) (count.c, count.h)
Simulations-Ergebnisse
(u.A. count.vcd)
Abbildung 3.2: Ablauf eines HDCaml-Programms
Ausführen: Beim Ausführen des übersetzten Programms werden u.A. die Bitbreiten von Bitvektoren überprüft (s. Abschnitt C.2, S. 99). Diese Prüfungen erfolgen (zusammen mit anderen, s. Kapitel 5, S. 61)
in der HDCaml-Bibliothek und können Warnungen und Fehlermeldungen ausgeben.
Bei kritischen Fehlern wird die Ausführung des Programms mit einer möglichst aussagekräftigen
Fehlermeldung zum frühest-möglichen Zeitpunkt (Abschnitt 5.2.1, S. 63) abgebrochen und es werden keine Ergebnisdateien geschrieben.
Außerdem werden in diesem Schritt benutzerdefinierte Ausgaben (z.B. mit printf ) getätigt.
Im vorliegenden Beispiel (Listing 3.5, S. 28) gibt es im ersten Schritt keinen Output, da keine Syntax- oder
Typfehler vorliegen. Beim Ausführen werden die gewünschten Ergebnisdateien (VHDL, Verilog, C) unter
Ausgabe folgender Meldungen geschrieben:
Writing VHDL Netlist: counter_example.vhd
Writing Verilog Netlist: counter_example.v
Writing SystemC Model: counter_example.c counter_example.h counter_example_sc.h
Anschliessend wird der eingebaute Simulator verwendet um das HW-Modell zu simulieren.
3.3.1
Schreiben der HBS-Modelle
Die Modelle der Schaltung in den Sprachen VHDL, Verilog und C (mit SystemC-Header) werden durch
den Aufruf folgender Funktionen (Zeilen 44 bis 46 in Listing 3.5, S. 28) geschrieben:
Vhdl.output_netlist circuit
Verilog.output_netlist circuit
Systemc.output_model circuit
3.3.1.1
VHDL und Verilog
Der aus Listing 3.5, S. 28 erzeugte VDHL-Code ist in Listing 3.6, S. 28 (19 Zeilen) dargestellt,3 der generierte Verilog-Code in Listing 3.7, S. 28 (11 Zeilen). Die im Abschnitt 2.2.1, S. 8 erwähnte langatmige
3 Die
fehlenden Zeilen 21 bis 48 werden in Abschnitt C.6, S. 102 besprochen.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
30
Syntax von VHDL wird beim Vergleich der beiden generierten Modelle deutlich (Leerzeilen und Kommentare wurden für die Angabe der Zeilen Code in den Klammern nicht gezählt).
3.3.1.2
HBS-Annotation
In den erzeugten VHDL- (Listing 3.6) und Verilog-Modellen (Listing 3.7) gibt es jeweils ein Signal namens
n_12 . Wenn man das HDCaml-Programm mit den erzeugten HBS-Modellen vergleicht, sieht man, dass es
sich dabei um das Register in Zeile 28 von Listing 3.5 handelt.4
Es würde sich anbieten, für diesen Schaltungsknoten den Namen count zu verwenden, den Namen der
Variable, der das Register zugewiesen wird. Das ist aber in HDCaml nicht möglich, weil es als eingebettete
Sprache realisiert ist und deshalb keine Informationen über Variablennamen hat (vgl. Abschnitt 2.4, S. 13).
Um in den erzeugten HBS-Modellen dennoch aussagekräftige Namen für Schaltungsknoten zu erhalten,
kann man diesen in HDCaml-Programmen mit Hilfe des Annotation-Operators („--“) Namen zuweisen.
Um beispielsweise dem Knoten n_12 den Namen count_reg zu geben, müsste die betreffende Zeile
folgendermaßen lauten:
let count = "count_reg" −− (reg enable next_count) in
Als Ergebnis der Annotation wird in den VHDL- und Verilog-Modellen der Signalname n_12 durch
count_reg ersetzt. Die Benennung hat auch Auswirkungen auf die Sichtbarkeit von Signalen im generierten C-Modell (s. folgenden Abschnitt), das trifft jedoch nicht auf die eingebaute Simulation zu.
3.3.1.3
C und SystemC
Ein kompletter Abdruck des des generierten C-Modells ist aus Platzgründen nicht möglich.5 Teile davon
(die Header-Files) sind in Abschnitt C.5, S. 102 aufgelistet.
Die Testbench in Listing 3.8, S. 31 zeigt, wie das generierte C-Modell verwendet wird. Die Simulation
wird mit den gleichen Stimuli durchgeführt wie diejenige in den Zeilen 6 bis 23 von Listing 3.5, die den in
HDCaml integrierten Simulator verwendet.
Die notwendigen Anweisungen, um das generierte C-Modell zu kompilieren und mit der Testbench
(Datei testbench-example.c ) zu linken, lauten folgendermaßen (GNU C):
gcc -Wall -c counter_example.c
gcc -Wall -c testbench-example.c
gcc -Wall -o testbench-example.exe counter_example.o testbench-example.o
Um beliebig lange Bitvektoren darstellen zu können, werden diese als Arrays vom Typ unsigned long
dargestellt, wobei jedes Element des Arrays 32 Bit des Bitvektors enthält. Auch wenn ein Bitvektor maximal
32 Bits lang ist, muss man über das Array-Element 0 darauf zugreifen (siehe Zeile 22 für einen schreibenden
Zugriff, Zeile 24 für einen lesenden).
Signale können in der Testbench entweder mit der Funktion find_simulator_port nachgeschlagen werden (Zeilen 17 u. 18) oder man greift direkt auf die Variable zu, in der der Zustand des Simulators
gespeichert wird ( simulator_t sim , Zeile 9). Deren Aufbau ist im Header-File (Listing C.6, S. 103,
Zeilen 6 bis 23) festgelegt.
In der C-Testbench kann man auf alle benannten Schaltungsknoten zugreifen, das sind Inputs, Outputs,
Dangling Signals (Abschnitt 3.4.2.1, S. 32) und Signale, die mit dem Annotation-Operator benannt wurden
(s. Abschnitt 3.3.1.2, S. 30).
4 Eine der HDCaml-Erweiterungen, die im Rahmen dieser Arbeit programmiert wurde, stellt eine Debug-Funktion namens
find_node zur Verfügung, mit der das Finden von solchen Signalen automatisiert wird (vgl. Abschnitt A.1.1.6, S. 89 und
Kapitel 5, S. 61)
5 Das ist leicht verständlich, wenn man bedenkt, dass das C-Modell im Unterschied zum VHDL- oder Verilog-Modell ein eigenständig lauffähiges Programm ist, das den Code für den Simulator beinhaltet.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
1
2
31
# i n c l u d e < s t d i o . h>
# i n c l u d e < a s s e r t . h>
3
4
# include " counter_example . h"
5
6
7
8
9
i n t main ( )
{
int i ;
s i m u l a t o r _ t sim ;
10
signal_t enable ;
si gn al _t count_out ;
11
12
13
sim = n e w _ s i m u l a t o r ( ) ;
i n i t _ s i m u l a t o r ( sim ) ;
14
15
16
enable
= f i n d _ s i m u l a t o r _ p o r t ( sim , " e n a b l e " ) ;
a s s e r t ( enable . signal ) ;
c o u n t _ o u t = f i n d _ s i m u l a t o r _ p o r t ( sim , " c o u n t _ o u t " ) ; a s s e r t ( c o u n t _ o u t . s i g n a l ) ;
17
18
19
f o r ( i = 0 ; i <= 1 0 ; i ++)
{
enable . signal [0] = i % 2;
c y c l e _ s i m u l a t o r ( sim ) ;
p r i n t f ( " c y c l e %2d : c o u n t _ o u t =%2 l u \ n " , i , c o u n t _ o u t . s i g n a l [ 0 ] ) ;
};
20
21
22
23
24
25
26
d e l e t e _ s i m u l a t o r ( sim ) ;
return 0;
27
28
29
}
Listing 3.8: Testbench für Simulation des C-Modells
3.3.2
Simulation der Schaltung
Zur eingebauten Simulation existiert ein Abschnitt im HDCaml-Tutorial [23].6 Im vorliegenden Beispiel
(Listing 3.5, S. 28) wird die Simulation unmittelbar nach dem Schreiben der HBS-Modelle gestartet (in
Zeile 48, durch Aufruf der Funktion in den Zeilen 6 bis 20). Der Output der Simulation lautet:
Simulating:
cycle 0: count_out=
cycle 1: count_out=
cycle 2: count_out=
cycle 3: count_out=
cycle 4: count_out=
cycle 5: count_out=
cycle 6: count_out=
cycle 7: count_out=
cycle 8: count_out=
cycle 9: count_out=
cycle 10: count_out=
0
1
1
2
2
3
3
0
0
1
1
Wie erwartet zählt der Zähler nur jeden zweiten Takt, weil der enable-Input nur in jedem zweiten Takt den
Wert 1 erhält (durch die Zeile „enable.(0) <- i mod 2“). Die während der Simulation vorkommenden Logik-Pegel werden in der Datei counter_example.vcd (Name kann in Zeile 7 geändert
werden) mitgeschrieben und können mit jedem beliebigen VCD-Viewer betrachtet werden.7
Beliebig lange Bitvektoren werden bei der eingebauten Simulation ähnlich wie im C-Modell als Arrays
dargestellt. In OCaml handelt es sich dabei um den Typ int array . Da Integers in OCaml nur 31 Bit
6 s.
http://funhdl.org/wiki/doku.php?id=hdcaml:tutorial:simulation
GTKWave: http://intranet.cs.man.ac.uk/apt/projects/tools/gtkwave/
7 z.B.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
32
haben8 enthält jedes Element des Arrays 31 Bit des Bitvektors.
Analog zum C-Modell muss man auf das Array-Element 0 zugreifen, auch wenn ein Bitvektor maximal
31 Bits lang ist (siehe Zeile 17 von Listing 3.5, S. 28 für einen schreibenden Zugriff, Zeile 19 für einen
lesenden).
In der eingebauten Simulation kann man ausschliesslich auf Inputs und Outputs zugreifen. Diese werden
mit der Funktion List.assoc aus der Liste der Inputs bzw. Outputs herausgesucht (Zeilen 11 und 12).
3.4
Hardwarebeschreibung
Für die in Abschnitt 2.1, S. 3 aufgezählten Hardwarebeschreibungsmöglichkeiten gilt in HDCaml überblicksweise Folgendes:
•
•
•
•
Die Verhaltensbeschreibung gibt es in HDCaml nicht.
Die RTL-Beschreibung ist die zentrale Art der Hardwarebeschreibung in HDCaml.
Die Strukturbeschreibung ist in HDCaml von untergeordneter Bedeutung.
Bei Generatoren für regelmäßig aufgebaute Strukturen zeigt HDCaml die größten Stärken.
3.4.1
Verhaltensbeschreibung
Diese Beschreibungsart gibt es in HDCaml nicht. Manche Beschreibungen müssen deshalb in HDCaml
umständlicher ausfallen als z.B. in VHDL. Vorteile der fehlenden Verhaltensbeschreibung sind, dass alle
HW-Beschreibungen garantiert synthetisierbar sind und die für den Benutzer ganz klar erkennbare Trennung von Programmfluss und Netzlisten-Erzeugungsanweisungen, s. a. Abschnitt 3.2.3, S. 26.
3.4.2
RTL-Beschreibung
Das ist eine für HDCaml typische Beschreibungsart. Listing 3.9 zeigt einen kombinatorischen Volladdierer.
Siehe Listing 2.3, S. 5 für die äquivalente Beschreibung in VHDL. Die verwendeten Operatoren können in
Tabelle 3.1, S. 25 bzw. in Abschnitt A.1, S. 85 nachgeschlagen werden.
1
2
3
l e t aXb = a ^ : b
in
l e t sum = aXb ^ : c i n
in
l e t c o u t = ( a &: b ) | : ( aXb &: c i n ) i n
Listing 3.9: RTL-Beschreibung eines kombinatorischen Volladdierers in HDCaml
3.4.2.1
Beschreibung von rückgekoppelten Registern
Um in HDCaml rückgekoppelte Register9 (wie z.B. in Abbildung 2.2, S. 5 dargestellt) beschreiben zu können, ist die Einführung eines Konzepts notwendig, das in HDCaml als „Dangling Signal“ (sinngemäß:
„unverbundenes Signal“) bezeichnet wird. Ohne dieses könnte man z.B. die Schaltung von Punkt (e) der
Abbildung 3.3, S. 33 in HDCaml nicht beschreiben.
Listing 3.10, S. 33 implementiert einen beliebig breiten Zähler in HDCaml.10 Die Bitbreite wird durch
den Parameter width angegeben. Mit dem Parameter enable kann der Zähler aktiviert bzw. deaktiviert
werden.
8 Ein
Bit wird für interne Zwecke im Zusammenhang mit der garbage collection verwendet.
innerhalb kombinatorischer Logik (kombinatorische Schleifen) stellen keine synchrone Logik dar und sind deshalb in HDCaml nicht erlaubt.
10 Das Listing ist ein Ausschnitt aus Listing 3.5, S. 28, anhand dem in Abschnitt 3.3, S. 27 der Arbeitsablauf bei der Verwendung
eines HDCaml-Programms beschrieben wird.
9 Rückkopplungen
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
26
27
28
29
30
31
32
l e t counter width enable =
l e t next_count = s i g n a l " next_count " width in
l e t count = reg enable next_count in
l e t sum = c o u n t + : ( one w i d t h ) i n
n e x t _ c o u n t <== sum ;
count
;;
Listing 3.10: Zähler beliebiger Breite in HDCaml
(a) Schritt 1 (Zeile 27)
(b) Schritt 2 (Zeile 28)
(c) Schritt 3 (Zeile 29)
(d) Schritt 4 (Zeile 30)
(e) Schaltung ohne Dangling Signal
Abbildung 3.3: Erzeugung rückgekoppelter Register mit Hilfe des Dangling Signals
33
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
34
Die Zeilen 27 bis 30 des Listings entsprechen jeweils einem Schritt von Abbildung 3.3, S. 33, Punkte (a)
bis (d). Im ersten Schritt wird durch den Aufruf der Funktion signal eine unverbundenes Signal mit dem
Namen ”next_count” und der Breite width erzeugt, dessen Output im zweiten Schritt als Input des
Registers verwendet wird. Im dritten Schritt werden die Inputs eines Addierers (Operator +: ) mit dem
Output des Registers und der Darstellung der Zahl 1 mit width Bits ( one width ) verbunden. Schließlich wird im vierten Schritt die Schleife geschlossen, indem der Output des Addierers mit dem Input des
unverbundenen Signals verbunden wird (Operator <== ). Ein Dangling Signal hat auf die Funktionalität
der modellierten Schaltung keinerlei Einfluss, und man kann sich die Schaltung auch ohne dieses vorstellen, wie in Punkt (e) von Abbildung 3.3 dargestellt. Mehr Informationen zum Dangling Signal gibt es in
Abschnitt C.1.1, S. 99.
3.4.3
Strukturbeschreibung
In HDcaml gibt es die Strukturbeschreibung, sie ist allerdings nicht besonders komfortabel zu benutzen11
und bietet gegenüber der RTL-Beschreibung keine Vorteile. Deshalb ist sie in HDCaml von untergeordneter
Bedeutung.
Der Mechanismus, mehrfach vorkommende Teile einer Schaltung einmal zu definieren und beliebig
oft zu verwenden, ist in HDCaml mit Hilfe von Funktionen realisiert (s. Abschnitt 3.2.1, S. 24). Bei wiederholten Aufrufen einer Funktion wird die durch diese Funktion definierte Hardware wiederholt in die
Gesamtschaltung eingefügt.
Die zum VHDL-Beispiel in Listing 2.5, S. 6 äquivalente HDCaml-Beschreibung eines Volladdierers,
der aus zwei Halbaddierern und einem ODER-Gatter aufgebaut ist, würde man (unter Verwendung von
Funktionen) am ehesten wie in Listing 3.11 schreiben.
1
2
3
4
5
6
let full_adder a b cin
l e t ( ha1_s , h a 1 _ c ) =
l e t ( ha2_s , h a 2 _ c ) =
l e t cout
=
( ha2_c , c o u t )
;;
=
half_adder
half_adder
or2_gate
a
ha1_c
ha1_s
b
cin
ha2_s
in
in
in
Listing 3.11: Aus Halbaddierern und ODER-Gatter aufgebauter Volladdierer in HDCaml
3.4.4
Generatoren für regelmäßig aufgebaute Strukturen
In HDCaml können beliebig generische Beschreibungen implementiert werden. Hierzu dient z.B. die generische Funktion map2 (Abbildung 2.3, S. 17), die in Abschnitt 2.5.2, S. 16 vorgestellt wurde. In HDCaml
kann map2 mit Listen beliebigen Typs verwendet werden, auch mit Listen von Bitvektoren. Listing 3.12,
S. 35 zeigt ein paar Anwendungsbeispiele und deren Ergebnisse im interaktiven Toplevel-Environment von
OCaml.12
Eine weitere generische Funktion ist map (Abbildung 4.2, S. 51), diese kommt in Abschnitt 4.2.3, S. 50
nochmal vor, im Vergleich mit einer gleichwertigen Funktion der Sprache HML. Sie hat im Gegensatz zu
map2 nur eine Liste statt zweien als Parameter und statt einem binären Operator hat sie einen unären (in
der Abbildung f() genannt).
11 In
Confluence [7] ist eine Art der Strukturbeschreibung implementiert, die leichter zu benutzen ist, s. Abschnitt 4.4.3, S. 55.
Ergebnis der Zeile 10 wurde in Zeile 11 durch ... ersetzt, da die resultierende rekursive Datenstruktur nicht besonders gut
lesbar ist.
12 Das
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
1
2
35
# map2 ( + ) [ 1 ; 2 ; 3 ] [ 4 ; 5 ; 6 ] ; ;
− : i n t l i s t = [ 5; 7; 9 ]
3
4
5
# map2 ( ^ ) [ " a1 " ; " a2 " ] [ " b1 " ; " b2 " ] ; ;
− : s t r i n g l i s t = [ " a1b1 " ; " a2b2 " ]
6
7
8
# l e t min a b = mux2 ( a <: b ) a b ; ;
v a l min : s i g n a l −> s i g n a l −> s i g n a l = <fun >
9
10
11
# map2 min [ i n p u t " a1 " 4 ; i n p u t " a2 " 4 ]
− : signal l i s t = . . .
[ c o n s t " 1000 " ; c o n s t " 0101 " ] ; ;
Listing 3.12: Anwendungsbeispiele für map2
Eine etwas komplexere generische Funktion ist bin_tree , deren Prinzip in Abbildung 3.4 dargestellt
ist.13 Diese reduziert eine Liste von Elementen durch wiederholte Anwendung eines binären Operators auf
ein Element des gleichen Typs. Abbildung 3.4 zeigt das Ergebnis bei einer Liste mit 4 Elementen.
[
a1
a2
a3
a4
*
*
a1*a2
a3*a4
]
*
(a1*a2)*(a3*a4)
Abbildung 3.4: Funktionsweise von bin_tree
Listing 3.13 zeigt Anwendungsbeispiele von bin_tree und deren Ergebnisse, wieder im interaktiven OCaml Toplevel-Environment.14 Bei Verwendung von bin_tree mit Basistypen (wie int oder
string ) ist das Ergebnis wieder ein einziges Element dieses Typs.
1
2
# bin_tree (+) [ 1; 2; 3; 4 ] ; ;
− : i n t = 10
3
4
5
# bin_tree ( ∗ ) [ 1; 2; 3; 4 ] ; ;
− : i n t = 24
6
7
8
# b i n _ t r e e max [ 1 ; 2 ; 3 ; 4 ] ; ;
− : int = 4
9
10
11
# map s t r i n g _ o f _ i n t [ 1 ; 2 ; 3 ; 4 ] ; ;
− : string l i s t = [ "1" ; "2" ; "3" ; "4" ]
12
13
14
# b i n _ t r e e ( ^ ) ( map s t r i n g _ o f _ i n t [ 1 ; 2 ; 3 ; 4 ] ) ; ;
− : s t r i n g = " 1234 "
Listing 3.13: Anwendungsbeispiele für bin_tree
13 Eine
mögliche Implementierung von bin_tree in HDCaml ist Listing C.9, S. 104 in Abschnitt C.7, S. 104
sind Zeilen 10 und 13, in denen eine Liste von Ganzzahlen in eine Liste von Zeichenketten umgewandelt wird,
was durch den Aufruf der Funktion map mit string_of_int bewerkstelligt wird, vgl. Abbildung 4.2, S. 51.
14 Erwähnenswert
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
36
Bei der Anwendung von bin_tree auf eine Liste von Bitvektoren ist das Ergebnis das Modell einer
Hardware-Struktur, die ein ähnliches Aussehen wie Abbildung 3.4, S. 35 hat, abhängig von der Länge der
Liste. Die folgenden Zeilen zeigen ein paar Möglichkeiten für eine solche Anwendung (s. Tabelle 3.1, S. 25
für eine Tabelle der Funktionen und Operatoren von HDCaml):
Summe:
Produkt:
Minimum:
bin_tree (+:) x
bin_tree ( *:) x
let min a b = mux2 (a <: b) a b in
bin_tree min x
Verkettung:
bin_tree (++) x
Logisches XOR: bin_tree (^:) x
Code-Generator für eine ganze Klasse von Hardware-Strukturen
Listing 3.14, S. 36 zeigt ein HDCaml-Programm15 mit dem VHDL-Netzlisten für eine ganze Klasse von
Hardware-Strukturen erzeugt werden können, nämlich für Multiplexer mit beliebig vielen Eingängen (der
1
2
3
open Hdcaml ; ;
open D e s i g n ; ;
open P r i n t f ; ;
4
5
6
l e t w r i t e _ m u x name w i d t h _ s e l e c t w i d t h _ i t e m s =
s t a r t _ c i r c u i t name ;
7
l e t num_items = 1 l s l w i d t h _ s e l e c t i n
l e t i n p u t s = A r r a y . make num_items empty i n
f o r i =0 t o num_items −1 do
i n p u t s . ( i ) <− i n p u t ( s p r i n t f " mux_in%d " i ) w i d t h _ i t e m s
done ;
8
9
10
11
12
13
l e t s e l e c t = input " s e l " w i d t h _ s e l e c t in
l e t m = mux s e l e c t ( A r r a y . t o _ l i s t i n p u t s ) i n
o u t p u t " mux_out " m;
14
15
16
17
l e t c = g e t _ c i r c u i t ( ) in
Vhdl . o u t p u t _ n e t l i s t c
18
19
20
;;
21
22
23
24
l e t write_many_muxes ( ) =
l e t i t e m _ w i d t h s = [ | 8 ; 16 | ] i n
l e t sel_widths = [ | 3; 4; 5 | ] in
25
f o r c o u n t _ i =0 t o ( A r r a y . l e n g t h i t e m _ w i d t h s ) − 1 do
f o r c o u n t _ s =0 t o ( A r r a y . l e n g t h s e l _ w i d t h s ) − 1 do
l e t i , s = item_widths . ( count_i ) , sel_widths . ( count_s ) in
w r i t e _ m u x ( s p r i n t f " mux%d_%d " ( 1 l s l s ) i ) s i ;
done ;
done ;
26
27
28
29
30
31
32
;;
33
34
w r i t e _ m a ny _ m u x e s ( ) ; ;
Listing 3.14: Code-Generator für beliebige Multiplexer in HDCaml
Einfachheit halber ist diese eine Zweierpotenz) und beliebigen Bitbreiten. Wie in Abschnitt 2.2.1, S. 10
erwähnt, können VHDL-Entities keine variable Anzahl von Ports haben, in VHDL ist ein allgemeiner Multiplexer deshalb nicht als Entity beschreibbar.
15 Das
Beispiel ist rein imperativ geschrieben, damit es auch für Leser ohne Kenntnisse von funktionalen Sprachen verständlich ist.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
37
Die Funktion write_mux in den Zeilen 5 bis 20 schreibt eine VHDL-Netzliste eines Multiplexers.
Die Parameter haben folgende Bedeutung: name : Name der VHDL-Datei, width_select : Bitbreite
des (binär codierten) Select-Eingangs, width_items : Bitbreite jedes Eingangs.
Diese Funktion ist nicht dazu gedacht, innerhalb eines HDCaml-Modells verwendet zu werden (dafür
gibt es bereits die Funktion mux , s. Abschnitt A.1.13, S. 94; diese wird in in Zeile 15 des Listings aufgerufen), sondern dazu, VHDL-Dateien mit der gewünschten Funktionalität zu schreiben. D.h. ein HDCamlProgramm muss nicht zwingend dazu verwendet werden, vollständige Designs zu implementieren, sondern
man kann damit auch gezielt einzelne Komponenten in der gewünschten Ziel-HBS erstellen, um sich unnötige Schreibarbeit zu ersparen.
Die restlichen Zeilen (22 bis 34) von Listing 3.14, S. 36 rufen die Funktion write_mux mit verschiedenen Argumenten (alle Kombinationen, die in den Zeilen 23 und 24 angegeben sind) auf. Der Output bei
der Ausführung von Listing 3.14 lautet:
Writing
Writing
Writing
Writing
Writing
Writing
VHDL
VHDL
VHDL
VHDL
VHDL
VHDL
Netlist:
Netlist:
Netlist:
Netlist:
Netlist:
Netlist:
mux8_8.vhd
mux16_8.vhd
mux32_8.vhd
mux8_16.vhd
mux16_16.vhd
mux32_16.vhd
Man könnte das Programm auch so schreiben, dass die Argumente auf der Kommandozeile übergeben
werden können,16 dann müsste man den Code nicht mehr ändern, sondern bräuchte nur noch die ausführbare
Datei mit den geeigneten Argumenten starten, z.B. automatisiert aus einem Makefile heraus.
3.5
Implementierung
Das Konzept von HDCaml wurde stark von HDCamls Vorgängerprojekt Confluence beeinflusst, das als
Compiler in der Sprache OCaml implementiert war. Die Wartung bzw. Weiterentwicklung dieses Compilers erwies sich als sehr aufwändig. Deshalb wurde HDCaml von Tom Hawkins in einem neuen Anlauf als
eingebettete Sprache (in OCaml) mit den gleichen Zielen wie Confluence implementiert. Einen ausführlicheren Vergleich der beiden Sprachen bietet Abschnitt 4.4, S. 52.
3.5.1
Basissprache OCaml
HDCaml basiert auf der pragmatischen, funktionalen Sprache OCaml (Objective Caml). Diese Sprache
wird in Form eines Open-Source-Projekts entwickelt, das unter der Lizenz QPL[6] steht. OCaml ist prinzipiell eine funktionale Sprache (s. Abschnitt 2.5, S. 15), hat aber auch imperative und objektorientierte
Spracheigenschaften.
OCaml ist eine objektorientierte Weiterentwicklung von Caml, welches seinen Ursprung in ML (Meta Language) bzw. SML (Standard Meta Language) hat. Abbildung 3.5 zeigt den zeitlichen Ablauf der
Entwicklung.
Abbildung 3.5: Entstehung von OCaml (vgl. [24])
OCaml gibt es auf http://caml.inria.fr als installierbare Softwarepakete für Microsoft Windows, MacOS X und Linux. Man kann auch der Quellcode herunterladen und die Sprache selber kompilieren. Das funktioniert unter Linux, MacOS X, Cygwin und anderen Unix-ähnlichen Sytemen problemlos.
16 s.
http://funhdl.org/wiki/doku.php?id=hdcaml:tutorial:compiler
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
38
Man kann OCaml-Programme auf drei verschiedene Arten ausführen (in Abschnitt C.8, S. 105 wird genauer
darauf eingegangen, wie diese Varianten im Zusammenhang mit HDCaml benutzt werden können):
• in einer (ggf. interaktiven) Toplevel-Umgebung ( ocaml )
• durch Übersetzung in Bytecode und dessen Ausführung
(Compiler: ocamlc , Interpreter: ocamlrun ) und
• durch Übersetzung in echten, optimierten Maschinencode (native-code compilation) der jeweiligen
Hardware-Plattform ( ocamlopt ). Den native-code Compiler gibt es für 9 Prozessor-Architekturen
(IA32, PowerPC, AMD64, Alpha, Sparc, Mips, IA64, HPPA und StrongArm).
Ein Werkzeug namens „Caml Preprocessor Pretty-Printer“ ( camlp4 )17 ermöglicht es, die Syntax von
OCaml (in Grenzen) zu verändern. Damit könnte man bei einer DSL, die durch Einbettung in OCaml
realisiert ist, eine dem Problem besser angepasste Syntax der DSL realisieren.
OCaml-Programme können mit C kombiniert werden, indem man Object-Files oder Libraries der Sprache C zu einem OCaml-Programm dazulinkt. Das ermöglicht es, Funktionalität aus bereits existierenden
C-Programmen mit wenig Programmier-Aufwand in OCaml-Programmen zu verwenden oder neue, geschwindigkeitskritische Teilmodule in C zu schreiben. [4] enthält ein ausführliches und gut verständliches
Kapitel über die C-Schnittstelle von OCaml.
3.5.2
Module und Build-Prozess von HDCaml
Dieser Abschnitt beschreibt die Teilmodule und den Build-Prozess der HDCaml-Bibliothek in der Version
0.2.10, also der Version, die das Ergebnis der vorliegenden Arbeit ist.18 Name und Zweck der Module lauten
(in alphabetischer Reihenfolge) wie folgt:
Auxiliary
Circuit
Design
Hdl_output
Release
Simulate
Systemc
Verify
Verilog
Vhdl
Waveform
Verschiedene allgemeine Hilfsfunktionen ohne speziellen Bezug zu HDCaml.
Datenstrukturen zur Schaltungsrepräsentation.
Funktionen und Operatoren zur Beschreibung von Hardware-Modellen.
Gemeinsame Code-Basis der HBS-Generatoren für VHDL und Verilog.
Enthält Informationen über Änderungen in den verschiedenen Versionen.
OCaml-interne Simulation von Schaltungen.
Schreiben des C-Modells (mit SystemC-Header).
Funktionen und Operatoren zum Spezifizieren von PSL-Assertions.
Schreiben der Verilog-Netzliste.
Schreiben der VHDL-Netzliste.
Schreiben von Signalverläufen (Waveforms) in VCD-Dateien.
OCaml-Module heißen gleich wie die Dateinamen, in denen ihr Source steht, wobei der erste Buchstabe
groß geschrieben wird. Die Dateinamen bestehen nur aus Kleinbuchstaben. Der Source für das Modul
Circuit befindet sich z.B. in der Datei circuit.ml.
Abbildung 3.6, S. 39 zeigt die Abhängigkeiten aller relevanten19 Module voneinander.20 Die Richtung
der Pfeile weist von dem abhängigen Modul auf das benutzte Modul. Punktierte und durchgezogene Linien
haben die gleiche Bedeutung.21
Alle Module außer Waveform und Auxiliary verwenden die im Modul Circuit definierten
Datenstrukturen. Simulate verwendet Waveform um die in der Simulation errechneten Signale in
17 Dokumentation:
http://caml.inria.fr/resources/doc/index.en.html
Aufbau der Version 0.2.9 von HDCaml ist in Abschnitt 5.1, S. 61 beschrieben.
19 Das Modul Release dient ausschließlich der Dokumentation (Release Notes). Es beinhaltet keinen Code und steht somit in
keiner Beziehung zu den anderen Modulen, weshalb es in der Abbildung nicht enthalten ist.
20 Die Abbildung wurde mit den Werkzeugen ocamldoc und dot erstellt, siehe Abschnitt 3.5.4, S. 40
21 Die Abhängigkeiten von Circuit und Auxiliary wurden nur deshalb punktiert dargestellt, weil mehr als die Hälfte der
Pfeile auf diese beiden Module zeigen und bei durchgezogener Darstellung auf dem Diagramm die restlichen Abhängigkeiten viel
schwerer zu erkennen wären.
18 Der
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
39
Abbildung 3.6: Abhängigkeiten der Module von HDCaml 0.2.10 voneinander
eine VCD-Datei zu schreiben. Vhdl und Verilog basieren auf dem gemeinsamen HBS-Generator in
Hdl_output . Die Abhängigkeit vieler Module von Design liegt an der darin implementierten Funktionalität zur Umbenennung von Signalnamen (s. Abschnitt C.4, S. 102 u. Abschnitt A.1.1.3, S. 86).
Das Kompilieren und Installieren von HDCaml umfasst die Schritte von Listing 3.15, S. 40. Diese werden durch den Aufruf von make install initiiert. Beim Installieren von HDCaml in einer reinen (d.h.
ohne Cygwin) Windows-Umgebung gibt es das Programm make nicht. In diesem Fall kann eine BatchDatei verwendet werden, die Teil der HDCaml-Installationsanleitung von Daniel Sánchez Parcerisa ist.22
Alle Module werden bei der Installation von HDCaml in ein übergeordnetes Modul Hdcaml zusammengefasst, das in Form der Dateien hdcaml.cma und hdcaml.cmi vorliegt (Zeilen 22 und 23 von
Listing 3.15). Diese werden in das Bibliotheksverzeichnis von OCaml kopiert (Zeilen 24 und 25), dessen
Pfad man durch Eingabe von ocamlc -where anzeigen lassen kann.
3.5.3
Typsystem und Datenstrukturen
HDCaml ist durch Einbettung in OCaml implementiert. Das Typsystem von HDCaml ist somit jenes von
OCaml. Ocaml ist stark typisiert (strongly typed), d.h. inkonsistente Verwendung von Typen wird bereits
während der Übersetzung (compile time) festgestellt.
Die (z.T. rekursiven) Datenstrukturen, die zur internen Repräsentation einer Schaltung verwendet werden, sind im Modul Circuit deklariert. Es sind dies: circuit , width , id , signal , sink ,
sequence und property . Die einzigen Typen, mit denen der Benutzer von HDCaml beim Modellieren von Schaltungen und Schreiben von HBS-Modellen in Kontakt kommt, sind signal und circuit .
Signals sind Bitvektoren. Die meisten Funktionen von Tabelle 3.1, S. 25 haben diesen Typ (sowie Paare und Listen davon) als Parameter und/oder Rückgabewert. Zusätzlich kommen noch die grundlegenden
Datentypen int , string und unit vor.
Mit der Datenstruktur circuit werden fertige Schaltungen abgebildet. Der Rückgabewert von
get_circuit hat diesen Typ, sowie die Parameter von Verilog.output_netlist und
Vhdl.output_netlist .
Diese beiden, am meisten verwendeten Datentypen werden in Abschnitt C.1, S. 98 ( signal ) und
Abschnitt C.3, S. 101 ( circuit ) ausführlicher besprochen.
22 http://funhdl.org/wiki/doku.php?id=hdcaml:installation . Die Batch-Datei beinhaltet die Anweisungen
zur Installation von HDCaml 0.2.9, kann aber durch Vergleich mit Listing 3.15 an die neuere Version 0.2.10 angepasst werden.
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
40
o c a m l c . o p t −I s r c −g −c s r c / a u x i l i a r y . m l i
o c a m l c . o p t −I s r c −g −c s r c / a u x i l i a r y . ml
o c a m l c . o p t −I s r c −g −c s r c / r e l e a s e . ml
o c a m l c . o p t −I s r c −g −c s r c / waveform . m l i
o c a m l c . o p t −I s r c −g −c s r c / waveform . ml
o c a m l c . o p t −I s r c −g −c s r c / c i r c u i t . m l i
o c a m l c . o p t −I s r c −g −c s r c / c i r c u i t . ml
o c a m l c . o p t −I s r c −g −c s r c / d e s i g n . m l i
o c a m l c . o p t −I s r c −g −c s r c / d e s i g n . ml
o c a m l c . o p t −I s r c −g −c s r c / h d l _ o u t p u t . m l i
o c a m l c . o p t −I s r c −g −c s r c / h d l _ o u t p u t . ml
o c a m l c . o p t −I s r c −g −c s r c / v e r i l o g . m l i
o c a m l c . o p t −I s r c −g −c s r c / v e r i l o g . ml
o c a m l c . o p t −I s r c −g −c s r c / v h d l . m l i
o c a m l c . o p t −I s r c −g −c s r c / v h d l . ml
o c a m l c . o p t −I s r c −g −c s r c / s y s t e m c . m l i
o c a m l c . o p t −I s r c −g −c s r c / s y s t e m c . ml
o c a m l c . o p t −I s r c −g −c s r c / v e r i f y . m l i
o c a m l c . o p t −I s r c −g −c s r c / v e r i f y . ml
o c a m l c . o p t −I s r c −g −c s r c / s i m u l a t e . m l i
o c a m l c . o p t −I s r c −g −c s r c / s i m u l a t e . ml
o c a m l c . o p t −c −o hdcaml . cmo −I s r c −g −p a c k s r c / a u x i l i a r y . cmo s r c / r e l e a s e . cmo s r c /
waveform . cmo s r c / c i r c u i t . cmo s r c / d e s i g n . cmo s r c / h d l _ o u t p u t . cmo s r c / v e r i l o g . cmo s r c /
v h d l . cmo s r c / s y s t e m c . cmo s r c / v e r i f y . cmo s r c / s i m u l a t e . cmo
o c a m l c . o p t −a −o hdcaml . cma −I s r c −g hdcaml . cmo
cp hdcaml . cma ‘ o c a m l c −where ‘
cp hdcaml . cmi ‘ o c a m l c −where ‘
Listing 3.15: Schritte beim Kompilieren und Installieren von HDCaml
Die Datentypen, die von der eingebauten Simulation verwendet werden, sind durch das Beispiel in
Abschnitt 3.3.2, S. 31 (Listing 3.5, S. 28) und der Sprachreferenz in Abschnitt A.2, S. 94 ausreichend abgehandelt.
3.5.4
Dokumentation des Quellcodes
Die Dokumentation des Quellcodes von HDCaml erfolgt durch spezielle Kommentare im Code (vergleichbar mit Javadoc oder Doxygen) und wird mit Hilfe des Programms ocamldoc23 extrahiert. Der Aufruf
von make doc im Verzeichnis hdcaml-0.2.10 erzeugt im Unterverzeichnis doc die Dokumentation. Der Aufruf bewirkt die Ausführung des folgenden Befehls:
ocamldoc -html -I src -d doc src/*.ml src/*.mli
Die Dokumentation kann in mehreren Formaten erstellt werden, u.A. in HTML und LATEX. Die in
Anhang A, S. 85 enthaltene Sprachreferenz von HDCaml 0.2.10 wurde mit Hilfe des LATEX-Outputs erstellt (und leicht nachbearbeitet), die Dokumentation auf der Webseite24 von HDCaml 0.2.10 mit Hilfe des
HTML-Outputs.
Das Programm ocamldoc kann auch die gegenseitigen Abhängigkeiten von Programmteilen (Modulen) in Form von dot -Dateien ausgeben, die anschließend mit Hilfe des Programms dot aus dem Paket
graphviz25 visualisiert werden können (wie in Abschnitt 3.5.2, S. 38 mit dem HDCaml-Source erfolgt).
Zum Erzeugen der Abbildung in den Formaten Postscript (PS) und Portable Network Graphics (PNG) sind
z.B. folgende Anweisungen notwendig:
ocamldoc -I src -dot -o dep.dot src/*.ml
dot -Tps2 -Grotate=180
dep.dot > dep.ps
dot -Tpng -Grotate=180 -Gdpi=400 dep.dot > dep.png
23 http://caml.inria.fr/pub/docs/manual-ocaml/manual029.html
24 http://karl-flicker.at
25 http://www.graphviz.org
KAPITEL 3. DIE HARDWAREBESCHREIBUNGSSPRACHE HDCAML
3.6
41
HDCaml im Internet
Die URLs für die beiden besprochenen Versionen von HDCaml, das Diskussionsforum (allgemeines Forum
für funktionale HBS) und Dokumentation zu verschiedenen Themen lauten:
Version 0.2.9:
Version 0.2.10:
Diskussionsforum:
http://funhdl.org/wiki/doku.php?id=hdcaml
http://karl-flicker.at
http://groups.google.com/group/funhdl
Installationsanleitung und Tutorial von Daniel Sánchez Parcerisa:
http://funhdl.org/wiki/doku.php?id=hdcaml:installation
http://funhdl.org/wiki/doku.php?id=hdcaml:tutorial
Dokumentation zu HDCaml-Erweiterungen der vorliegenden Arbeit:26
http://funhdl.org/wiki/doku.php?id=hdcaml:releases:0.2.9.1
26 HDCaml
0.2.9.1 war eine Zwischenversion auf dem Weg zu HDCaml 0.2.10.
Kapitel 4
Mit HDCaml verwandte Projekte
4.1
HML
HML[25, 26] (Hardware ML) ist eine der Syntax von SML[27] (standard meta language) nachempfundene
Hardwarebeschreibungssprache. Die Sprache entstand ungefähr ab 1993 an der Cornell University, Ithaca,
NY, und wurde dort bis mindestens 1997 weiter entwickelt.1 HML ist weder als ausführbares Programm
noch als Quellcode verfügbar.
Das Ziel von HML ist es, durch Weglassen von bestimmten Sprachelementen im Vergleich zu VHDL
oder Verilog die Syntax knapp und präzise und damit die Verwendung möglichst einfach und effizient zu
machen (vgl. Abschnitt 2.3, S. 12). HML ist ein Code-Generator (Abschnitt 2.2.2, S. 12), der ausschließlich
VHDL produzieren kann.
4.1.1
Spracheigenschaften und Funktionalität
HML ist stark typisiert, polymorph, unterstützt Typableitung und hat Funktionen höherer Ordnung (s.
Abschnitt 2.5, S. 15 für die Definition dieser Begriffe). HML ist weder rein funktional noch imperativ im
eigentlichen Sinn. HML besitzt die Zuweisungen an Signale (s. Abschnitt 4.1.4.2, S. 45) als einziges imperatives Sprachelement.
Mit HML können ausschließlich digitale, sequentielle, synchrone Schaltungen mit nur einer einzigen,
impliziten Clock-Domain beschrieben werden. HML erzeugt als Output eine Hardware-Beschreibung in
VHDL und überlässt die weitere Schritte im Entwurfsprozess (Simulation, Synthese, etc.) den VHDLWerkzeugen (vgl. Abbildung 1.1, S. 2). Der Output von HML ist garantiert synthetisierbar, nicht synthetisierbare Konstrukte stehen in HML nicht zur Verfügung.
Durch Spracheigenschaften wie Typableitung, Polymorphie und Funktionen höherer Ordnung wird die
Wiederverwendbarkeit von Code stark verbessert. Tabelle 4.1, S. 43 fasst die Eigenschaften von HML im
Vergleich mit VHDL zusammen.
4.1.2
Implementierung und Verwendung
HML ist als Compiler (vgl. Abschnitt 2.4, S. 13) in der Sprache SML[27] implementiert und besteht aus
den drei Teilprogrammen parse , typecheck und hml2vhdl .
Mit parse kann die syntaktische Korrektheit einer HML-Hardwarebeschreibung überprüft werden,
typecheck überprüft zusätzlich noch die korrekte Verwendung von Datentypen, wobei Typableitung zum
Einsatz kommt. Der VHDL-Generator hml2vhdl ermöglicht es, zwei Varianten von VHDL-Modellen
aus einer HML-Beschreibung zu erzeugen (s. Abschnitt 4.1.6, S. 47).
1 Datumsangaben
abgeleitet aus [25, Literaturverweis OLLA93] und [26, kleingedruckter Text, S. 1]
42
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
Syntax
43
VHDL
HML
langatmig
knapp und präzise
Semantik
relativ kompliziert
einfach
Synthese
Teilmenge ist synthetisierbar
erzeugt 100% synthetisierbares VHDL
Typenüberprüfung
Benutzer muß Typen angeben
Typableitung
—
3
Polymorphie
—
3
in großer Anzahl verfügbar
Benutzung der VHDL-Werkzeuge
Funktionen höherer Ordnung
Werkzeuge
Tabelle 4.1: Spracheigenschaften von HML im Vergleich mit VHDL (vgl. [26, Tab. 1])
4.1.3
Datentypen und Objekte
HML stellt eine Reihe von Basistypen (basic types) zur Verfügung: unit , bool , bit , int ,
bit_vector und behavior , (s. Tabelle 4.2).
Der Typ unit ist das aus SML bekannte leere Tupel und ist mit void in C vergleichbar. Der boolesche
Datentyp bool wird für logische Bedingungen (z.B. in Abfragen) verwendet, während der Typ bit einen
Basistypen (basic types):
Unit
unit
()
Boolean
bool
true, false
Bit
bit
’0’, ’1’
Integer
int
Bereich innerhalb der Ganzen Zahlen
Bitvektor
bit_vector
z.B. "100110" . . . Array von Bits
Verhaltens-Typ
behavior
Typ einer Signal-Zuweisung oder Typ
des Rückgabewerts einer HW-Funktion
Erweiterte Typen (advanced types):
Funktionstyp
function_type
Typ von normalen Funktionen (regular functions)
HW-Funktionstyp
hardware_fun
Typ von Hardware-Funktionen
Benutzerdefinierte Typen (user-defined types):
Aufzählungstyp
type t = v1 | v2 | v3
Tabelle 4.2: Datentypen in HML (vgl. [25, Tab. 2.1])
Spezialfall (Bitbreite gleich 1) eines Bitvektors darstellt. Für die beiden Typen müssen unterschiedliche
Operatoren verwendet werden (s. Tabelle 4.3, S. 45). Ein int ist ein Bereich innerhalb der Ganzen Zahlen
(Z), bit_vector ist ein Bitvektor mit einem bestimmten Indexbereich, der auch die Bitbreite bestimmt.
In Abschnitt 4.1.3.2, S. 44 wird auf diese beiden Typen näher eingegangen. Der Verhaltens-Typ behavior
ist der Typ des Rückgabewerts einer Hardware-Funktion oder der Typ einer Signalzuweisung.
Zu den Erweiterten Typen (advanced types) zählen function_type , der Typ von normalen Funktionen und hardware_fun , derjenige von Hardware-Funktionen. Die beiden Arten von Funktionen werden
in Abschnitt 4.1.4.3, S. 45 diskutiert.
Schließlich gibt es in der Kategorie Benutzerdefinierte Typen (user-defined types) noch die Möglichkeit,
Aufzählungstypen zu definieren.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
4.1.3.1
44
Objekte und Deklarationen
Konstanten (constants) und Signale (signals), werden in HML unter dem Begriff Objekte (objects) zusammengefasst. HML-Objekte haben nichts mit Objekten in objektorientierten Programmiersprachen zu tun.
Objekte werden durch eine Wert-Deklaration (value declaration, Listing 4.1) oder eine Intern-Deklaration (intern declaration, Listing 4.2) deklariert. Außerdem stellen Parameter von Funktionen Deklarationen von Objekten dar.
1
2
3
4
5
let
v a l i d = exp
in
...
end
Listing 4.1: Wert-Deklaration in HML
1
2
3
4
5
let
i n t e r n id1 , id2 ,
in
...
end
...
Listing 4.2: Intern-Deklaration in HML
Ob ein Objekt eine Konstante oder ein Signal ist, wird vom Kontext bestimmt (s. Abschnitt 4.1.4.3, S. 45
zu den beiden Arten von Funktionen):
Intern-Deklaration: deklariert immer ein Signal.
Wert-Deklaration: deklariert bei Verwendung in einer HW-Funktion ein Signal mit einem definierten
Anfangswert, jedoch eine Konstante, wenn sie global oder in einer normalen Funktion vorkommt.
Funktions-Parameter: sind in HW-Funktionen Signale,2 in normalen Funktionen Konstanten.
4.1.3.2
Ganze Zahlen und Bitvektoren
Sowohl int als auch bit_vector sind als Zahlen verwendbar. Beide sind vorzeichenbehaftet und in
Zweierkomplementdarstellung codiert.
Für die Angabe der Bitbreiten von Bitvektoren, sowie für die Angabe des Wertebereichs von Ganzen
Zahlen gibt es jeweils zwei Varianten: global und pro Objekt. Die globale Angabe erfolgt bei Bitvektoren
mit „ width i, j “ ( i und j sind der obere und der untere Index des Bitvektors) und bei Ganzen Zahlen
mit „ range i, j “ ( i und j sind die kleinste und größte darzustellende Zahl). Die Angabe pro Objekt
erfolgt durch Spezifikation des Typs in der Deklaration von Objekten (Abschnitt 4.1.3.1).
Das Beispiel in Listing 4.3 zeigt beide Varianten der Angabe von Bitbreiten. In Zeile 1 wird eine globale
Bitbreite von 8 Bit angegeben, die für alle Objekte gilt, für die nicht eine explizite Bitbreiten-Angabe erfolgt
1
width 7 , 0
2
3
4
hw e x a m p l e ( a : b i t _ v e c t o r ( 3 , 0 ) , b , c ) =
(∗ . . . ∗)
Listing 4.3: Deklaration von Bitbreiten in HML: global und pro Signal (vgl. [25, S. 12])
(wie z.B. für a in Zeile 3, das 4 Bit breit ist). Die globale Angabe gilt im Beispiel somit für b und c . Die
Bereichsangaben für Ganze Zahlen durch „ range i, j “ (global) oder „ : int(i,j) “ (pro Objekt)
erfolgen analog.
2 Ausnahme:
in den sogenannten Array-Generatoren (Abschnitt 4.1.5.4, S. 47), können Parameter auch Konstanten sein (z.B. Parameter n und cell in Listing 4.10, S. 48). Siehe [25, S. 12f] für mehr Informationen.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
4.1.4
Sprachkonstrukte
4.1.4.1
Operatoren
45
Tabelle 4.3 zeigt alle in HML existierenden Operatoren. Die Bedeutung der meisten Operatoren ist unmittelbar verständlich. Folgende Operatoren bedürfen zusätzlicher Beschreibung:
@ . . . Aneinanderreihung (concatenation) zweier Bitvektoren.
Diesen Operator kann man nicht nur auf der rechten Seite (x := a @ b) einer Zuweisung verwenden, sondern auch auf der linken Seite (a @ b := x), dann handelt es sich dabei um PatternMatching.
Dieser Operator ist in [25, S. 19 f] beschrieben, aber laut [25, S. 20] nicht implementiert worden.
[] . . . Index-Operator für Bitvektoren.
a[i,j] liefert einen Teil-Bitvektor (bit slice) der Länge 1+j-i
a[i]
liefert das Bit an der Position i
; und || . . . parallele Ausführung zweier Behaviors („Behavior-Composer“).
a; b und a || b sind gleichwertig.3
Operatoren
Anwendbar auf Typen
~
int
Vorzeichen
Artithmetische Op.
Vergleichs-Op.
Logische Op.
Bitvektor-Op.
Boolesche Op.
„Behavior-Composer“
+
=
<>
>
div
*
>=
<
int, bit_vector
<=
and or nand nor xor xnor inv
@
int, bit_vector
int, bit_vector, bit
[]
andalso orelse not
;
||
bit_vector
bool
behavior
Tabelle 4.3: Operatoren in HML (vgl. [26, Tab. III])
Der Großteil der Operatoren kann gleichermaßen für int und bit_vector verwendet werden. HMLFunktionen, die ausschließlich arithmetische, Vergleichs- und logische Operatoren verwenden, sind polymorph.
4.1.4.2
Signalzuweisungen
HML hat zwei Arten von Signalzuweisungen, kombinatorische Zuweisung (combinational assignment) und
Register-Zuweisung (register assignment), s. Tabelle 4.4, S. 46. Der Unterschied zwischen beiden besteht
darin, dass die Register-Zuweisung implizit ein Register erzeugt, in dem das Ergebnis des Ausdrucks gespeichert wird, die kombinatorische Zuweisung jedoch nicht. Beide Zuweisung haben den Ergebnis-Typ
behavior .
4.1.4.3
Funktionen
Es gibt normale Funktionen (regular functions; auch nur „Funktionen“ oder „Software-Funktionen“ genannt) und Hardware-Funktionen (hardware functions). Normale Funktionen liefern Werte eines vom Benutzer wählbaren Typs, wie in funktionalen Sprachen üblich. Sie werden als Hilfsfunktionen für HWFunktionen verwendet.
3 Der Unterschied liegt darin, dass nicht beide an jeder beliebigen Stelle im HML-Code verwendet werden können. Der Operator
„ || “ darf nur in der obersten Ebene (top-level) einer Hardware-Funktion vorkommen, während „ ; “ überall erlaubt ist, s. [25,
Abschnitt 2.3.3.3].
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
46
Syntax
Bedeutung
Kombinatorische Zuweisung:
signal := expression
signal(t) := expression(t)
Register-Zuweisung:
signal <- expression
signal(t + 1) := expression(t)
Tabelle 4.4: Signalzuweisungen in HML (vgl. [25, Tab. 2.2])
Es gibt in HML auch anonyme Funktionen, wie in SML. So ist z.B. (fn x => 2*x) eine Funktion,
die einen übergebenen Wert verdoppelt zurückgibt. Anonyme Funktionen können in HML nur als normale
Funktionen und nicht als HW-Funktionen verwendet werden.
Hardware-Funktionen beschreiben Hardware. Der Rückgabewert einer HW-Funktion ist die resultierende HW-Beschreibung (Typ behavior ). Deshalb muss sämtliche Funktionalität in HW-Funktionen
über Zuweisungen an die Parameter der Funktion erfolgen, weil der Rückgabewert vom Benutzer nicht
verwendbar ist.
Diese Eigenschaft wird anhand zweier gleichwertiger Implementierungen eines Addierers verdeutlicht.
Die (normale) Funktion (Listing 4.4) hat zwei Parameter und liefert die Summe als Rückgabewert. Dieser
hat immer den gleichen Typ wie die Parameter.
1
2
3
fun add ( a , b ) =
a + b
...
4
5
6
7
8
9
10
11
1
2
3
hw add ( a , b , sum ) =
sum : = a + b
...
4
( ∗ Verwendung : ∗ )
let
v a l s = add ( x , y )
in
...
...
end
Listing 4.4: Addierer als normale Funktion
5
6
7
8
9
10
11
( ∗ Verwendung : ∗ )
let
intern s
in
add ( x , y , s ) ;
...
end
Listing 4.5: Addierer als HW-Funktion
Die HW-Funktion (Listing 4.5) benötigt einen dritten Parameter für die Zuweisung der Summe. Der
Rückgabewert hat immer den Typ behavior . Die HW-Funktion kann im Gegensatz zur normalen Funktion nicht wie in SML üblich verwendet werden (Zeile 7 in Listing 4.4), sondern nur mit einer Syntax (Zeile
9 von Listing 4.5), die einer Strukturbeschreibung in VHDL oder Verilog ähnelt.
Eine HW-Funktion kann aus mehreren Modulen vom Typ behavior bestehen, die durch „ || “ oder
„ ; “ („Behavior-Composer“, s. Abschnitt 4.1.4.1, S. 45) miteinander verknüpft werden. Wenn in einem
Modul eine der Zuweisungen „:=“ oder „<-“ vorkommt, dann handelt es sich um eine Verhaltens- oder
RTL-Beschreibung, sonst um eine Strukturbeschreibung. In HW-Funktionen können Module mit Struktur-,
RTL- und Verhaltensbeschreibungen auch gemischt vorkommen.
Hinweis zur Syntax
Folgende zwei Definitionen einer Funktion x mit zwei Parametern a und b sind in HML beide syntaktisch
korrekt und gleichwertig:
fun x(a, b) = ...
fun x a b = ...
Eine teilweise Auswertung („partial application“ oder „currying“) ist in HML nicht möglich, da beide Varianten intern so wie die erste Variante dargestellt werden (als Tupel). Beim Aufruf einer Funktion dürfen
ebenfalls beide Varianten ( z = x(a, b) oder z = x a b ) verwendet werden.
Für die zitierten Beispiele aus den Publikationen bedeutet das, dass sowohl bei Definition als auch
beim Aufruf einer Funktion beide Varianten vorkommen können, teilweise auch gemischt innerhalb eines
Beispiels.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
4.1.5
Beschreibung von Hardware
4.1.5.1
Verhaltensbeschreibung
47
Listing 4.6 ist die HML-Verhaltensbeschreibung eines Zählers. In HML können die beiden Zeilen alleine
schon ein gesamtes Design darstellen wenn unoptimierter VHDL-Code daraus erzeugt wird (s. Abschnitt
4.1.6). Wenn man optimiertes VHDL erzeugen will, muss die Bitbreite der Bitvektoren angegeben werden,
1
1
2
hw c o u n t e r ( a ) =
a <− i f a =15 t h e n 0 e l s e a +1
width 3 ,0
2
3
4
hw c o u n t e r ( a ) =
a <− i f a =15 t h e n 0 e l s e a +1
Listing 4.6: Zähler in HML
Listing 4.7: 4 bit-Zähler in HML
entweder global oder pro Signal. Listing 4.7 zeigt die Variante, bei der die Bitbreite global auf 4 Bit festgelegt wird. In beiden Fällen (Listing 4.6 und Listing 4.7) kann die Funktion auch als Komponente in einem
größeren Design verwendet werden.
4.1.5.2
RTL-Beschreibung
Listing 4.8 zeigt den zu Listing 2.3, S. 5 äquivalenten HML-Code (RTL-Beschreibung eines kombinatorischen Volladdierers).
4.1.5.3
Strukturbeschreibung
Als Beispiel zur Demonstration der Strukturbeschreibung wird, wie schon in Abschnitt 2.1, S. 3, ein Volladdierer verwendet. Listing 4.9 zeigt die HML-Strukturbeschreibung des Volladdierers.
1
1
2
3
4
5
6
7
8
hw f u l l _ a d d e r ( a , b , c i n , sum , c o u t ) =
let
i n t e r n aXb
in
aXb : = a xor b ;
sum : = aXb xor c i n ;
c o u t : = ( a and b ) or ( aXb and c i n ) ;
end
2
3
4
5
6
7
8
9
10
Listing 4.8: RTL-Beschreibung eines kombinatorischen Volladdierers in HML
4.1.5.4
hw f u l l A d d e r ( c i n , a , b , sum , c o u t ) =
let
i n t e r n aXb , ab , a b c
in
x o r 2 ( a , b , aXb )
||
x o r 2 ( c i n , aXb , sum ) | |
and2 ( c i n , aXb , a b c ) | |
and2 ( a , b , ab )
||
o r 2 ( ab , abc , c o u t )
end
Listing 4.9: Strukturbeschreibung eines Volladdierers
in HML ([26], Fig. 3, S. 3)
Generatoren für regelmäßig aufgebaute Strukturen
Von dieser Art der Hardwarebeschreibung ist in [25, 26] zwar die Syntax beschrieben, sie ist aber laut [25,
Abschnitt 4.3, S. 56] nicht implementiert worden.
Listing 4.10, S. 48 zeigt eine polymorphe HML-Generatorfunktion für eine bitweise Verknüpfung eines
Input- und Output-Bitvektors durch n-malige Instanzierung der Zelle cell.
4.1.6
VHDL-Generierung
Bei der Generierung von VHDL-Code aus HML-Beschreibungen gibt es zwei Varianten. Eine erzeugt sogenannten unoptimierten Code zum Zweck der Simulation, wobei HML-Signale auf den VHDL-Datentyp
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
1
2
3
48
hw a r r a y 1 c e l l i n p o u t p = c e l l ( i n p [ 0 ] ,
outp [0] )
| a r r a y n c e l l i n p o u t p = c e l l ( i n p [ n −1] , o u t p [ n −1]) | |
a r r a y ( n −1) c e l l ( i n p , o u t p )
Listing 4.10: Array-Generator in HML (vgl. [25, Fig. 2.6, S. 25])
Integer abgebildet werden. Die andere Variante erzeugt sogenannten optimierten VHDL-Code zum
Zweck der Synthese. Als Optimierung wird genannt, dass Multiplikationen mit 2 und Divisionen durch
2 in VHDL durch Shift-Operationen ersetzt werden. Bei dieser Variante werden HML-Signale auf VHDLBitvektoren abgebildet.
Software-Funktionen, Konstanten und benutzerdefinierte Aufzähltypen (type) werden 1:1 in VHDL
übernommen.
HW-Funktionen werden auf VHDL-Entities und dazugehörige Architectures abgebildet. Die Parameter von HW-Funktionen werden VHDL-Ports zugeordnet, sofern es sich dabei um Signale handelt (s.
Abschnitt 4.1.3.1, S. 44). Die Richtung der Ports (in, out oder inout) wird automatisch daraus abgeleitet, ob ein Parameter einer Funktion darin nur rechts, nur links oder auf beiden Seiten von Zuweisungen
vorkommt.
Eine kombinatorische Zuweisung im HML-Code (:=) wird in eine VHDL-Signalzuweisung („<=“)
außerhalb eines Prozesses übersetzt. Aus einer HML-Register-Zuweisung (<-) wird ein VHDL-Process,
Die Clock (clk) wird automatisch in die Port-Declaration eingefügt und bildet die Sensitivity-List des
Prozesses.
4.2
4.2.1
Vergleich von HML mit HDCaml
Gemeinsamkeiten
HML und HDCaml haben sich beide aus der ML-Sprachfamilie (meta language), einer Untergruppe der
funktionalen Programmiersprachen entwickelt. Abbildung 4.1 zeigt ML als gemeinsamen Vorfahren von
HML und HDCaml. Durchgezogene Pfeile in der Abbildung bedeuten eine direkte Entwicklung einer Sprache aus einer Anderen, punktierte Pfeile zeigen eine Vererbung von Ideen.
Abbildung 4.1: Verwandtschaft von HML und HDCaml (vgl. [24])
Beide funktionale Sprachen sind stark typisiert, polymorph und unterstützen Funktionen höherer Ordnung
und Typableitung.
HDCaml verfolgt die gleichen Ziele wie HML, nämlich durch Weglassen von bestimmten Fähigkeiten und Sprachelementen die Syntax knapp und präzise und damit die Verwendung möglichst einfach und
effizient zu machen. In beiden Sprachen können mit Hilfe von polymorphen Funktionen Generatoren für re-
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
49
gelmäßige Strukturen erstellt werden. Die in Abschnitt 4.1.1, S. 42 beschriebenen und in Tabelle 4.1, S. 43
zusammengefassten Eigenschaften gelten für HDCaml ebenso wie für HML.
4.2.2
Unterschiede
In diesem Kapitel werden Unterschiede zwischen HML und HDCaml aufgezeigt. Tabelle 4.5, S. 50 bietet
anschließend einen Überblick über die unterschiedlichen Eigenschaften.
Sprache allgemein
HML ist ein in SML implementierter Compiler für eine funktionale Sprache, die auch ein imperatives
Sprachelement besitzt, nämlich die Signal-Zuweisungen (s. Abschnitt 4.1.4.2, S. 45). HDCaml ist durch
Einbettung in die Sprache OCaml implementiert, die funktionale und zusätzlich auch imperative und objektorientierte Eigenschaften hat. Da OCaml eine general-purpose-Sprache ist, steht einem die Möglichkeit
zur Verfügung, damit beliebige Programme (und Testbenches) zu schreiben. Mit HML kann ausschließlich
Hardware beschrieben werden.
Beschreibung von Hardware
HML unterstützt im Gegensatz zu HDCaml nicht die automatische Ableitung der Bitbreiten von Bitvektoren durch Analyse ihrer Verwendung. Stattdessen müssen alle Bitbreiten explizit angegeben werden, entweder global oder pro Signal (s. Abschnitt 4.1.3.2, S. 44), wenn man synthetisierbaren VHDL-Code daraus
erzeugen will (s. Abschnitt 4.1.6, S. 47). In HDCaml muss der Benutzer nur die Bitbreite für Inputs und
Konstanten angeben. Die Bitbreite aller weiteren Signale wird abgeleitet (Abschnitt C.2, S. 99), was einen
großen Vorteil für die Wiederverwendbarkeit von Code bringt.
HML ermöglicht zusätzlich zur Strukturbeschreibung und RTL-Beschreibung (die beide auch in HDCaml existieren) die Verhaltensbeschreibung.
Es gibt in HDCaml den Unterschied zwischen den beiden Zuweisungsvarianten von HML (kombinatorische Zuweisung und Register-Zuweisung) nicht. Register erzeugt man stattdessen durch die Verwendung
der Funktion reg .
Hardware wird in HML durch die Verwendung von HW-Funktionen (Abschnitt 4.1.4.3, S. 45) beschrieben. In HDCaml gibt es den Unterschied zwischen Hardware- und Software-Funktionen nicht. Die Beschreibung von Hardware geschieht in HDCaml durch den Aufruf von bestimmten Bibliotheks-Funktionen
und -Operatoren (s. Tabelle 3.1, S. 25) bzw. der Verwendung von Signals (Bitvektoren).
Der Datentyp list existiert in HML nicht. Das schränkt die Möglichkeiten für Schleifen stark ein
und zwar auf Ganze Zahlen. Generatoren für regelmäßig aufgebaute Strukturen brauchen Schleifen in irgendeiner Art, um Hardware wiederholt in das Gesamtmodell einzufügen, deshalb sind auch diese durch
das Fehlen von Listen stark eingeschränkt. So kann man keine Funktionen schreiben, die Operationen für
Listen von Bitvektoren ausführen, wie map oder bin_tree .
Simulation
In HDcaml gibt es drei verschiedene Arten, ein Design zu simulieren:
1. direkt in OCaml,
2. durch Ausführung eines generierten C-Codes und
3. Simulation eines generierten VHDL- oder Verilog-Modells mit einem externen Simulator.
Der wesentliche Unterschied zwischen Varianten 2 und 3 ist der, dass im C-Code bereits der Simulator
integriert ist, während für VHDL und Verilog externe Simulatoren notwendig sind. In HML existiert nur
die dritte Variante mit VHDL.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
50
VHDL-Generierung
HML hat zwei Arten der VHDL-Generierung: optimiert und unoptimiert, s. Abschnitt 4.1.6, S. 47. In HDCaml gibt es keine Unterscheidung zwischen diesen beiden Varianten.4
HDCaml
Implementierung
HML
Einbettung in OCaml
Compiler (in SML impl.)
funktional, imperativ, objektorientiert
funktional, z.T. imperativ
Gen.-purpose-Eigenschaften
3
—
Bitbreite Inputs/Konstanten
Angabe pro Signal
pro Signal oder global
3
—
Bitbreiten-Ableitung
Hardwarebeschreibungsmögl.
RTL, Struktur
Verhalten, RTL, Struktur
Erzeugung von Registern
Funktion reg
Register-Zuweisung: <-
Funkt./Op. aus HDCaml-Bibl.
HW-Funktion
OCaml, C, extern
extern
keine Varianten
optimiert o. unoptimiert
Beschreibung von HW
Simulation
Varianten d. VHDL-Generierung
Tabelle 4.5: Unterschiede von HDCaml und HML
4.2.3
HDCaml-Beispiele
In HDCaml ist eine Strukturbeschreibung in der Art des HML-Beispiels von Listing 4.9, S. 47 zwar möglich, hat aber gegenüber einer RTL-Beschreibung keinen Vorteil. Man würde das Beispiel in HDCaml eher
so wie in Listing 4.11 als RTL-Beschreibung implementieren.
1
2
3
4
5
6
7
8
let full_adder a
l e t aXb = a
l e t sum = c i n
l e t abc = c i n
l e t ab
= a
l e t c o u t = ab
( sum , c o u t )
;;
b cin =
^: b
in
^ : aXb i n
&: aXb i n
&: b
in
| : abc in
Listing 4.11: Volladdierer in HDCaml (RTL-Beschreibung)
Analog dazu ist bei Generatoren für regelmäßig aufgebaute Strukturen (Listing 4.12) in HDCaml die
strukturelle Syntax von Listing 4.10, S. 48 nicht üblich, sondern eine solche, bei der der Input ein Parameter
der Funktion ist und der Output deren Rückgabewert ist (es sich also um einen Operator handelt).
1
2
3
4
5
l e t r e c a r r a y op i n p =
match w i d t h i n p w i t h
1 −> op i n p
| n −> ( op ( msb i n p ) ) ++ ( a r r a y op ( l s b s i n p ) )
;;
Listing 4.12: Array-Generator in HDCaml
In Listing 4.13, S. 51 wird eine noch einfachere Generatorfunktion gezeigt, die man durch den Einsatz
von map , einer Funktion höherer Ordnung, erhält (Abbildung 4.2, S. 51 illustriert deren Funktionsweise).
4 Es wäre leicht möglich, in HDCaml einen VHDL-Generator zu schreiben, dessen Output nur für eine schnelle Simulation und
nicht für die Synthese gedacht ist.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
1
2
3
51
l e t a r r a y op i n p =
c o n c a t ( L i s t . map op ( b i t s i n p ) )
;;
Listing 4.13: Array-Generator in HDCaml mit map
Diese wendet auf jedes Element einer Liste (die in Listing 4.13 von bits inp erzeugt wird) einen Operator an. Listing 4.13 ist funktional identisch mit Listing 4.12, S. 50. Die Funktion concat wandelt eine
Liste von Bitvektoren durch Aneinanderhängen in einen einzigen Bitvektor um.
[
a1
a2
a3
...
an
]
[
f(a1)
f(a2)
f(a3)
...
f(an)
]
Abbildung 4.2: Funktionsweise von map
Durch die Existenz von Listen und Arrays können in HDCaml wesentlich leistungsfähigere Generatorfunktionen als in HML geschrieben werden.
4.3
Confluence
Confluence[7] ist eine domänenspezifische Sprache (DSL), die als Compiler in OCaml realisiert ist (s.
Abschnitt 2.4, S. 13). Confluence ist ein Open-Source-Projekt,5 das unter den Lizenzen GPL[28] (die Sprache selbst) und LGPL[5] (die dazugehörigen Bibliotheken) steht.
Die Sprache Confluence ist die alleinige Kreation von Tom Hawkins, deren Entwicklung er ca. 2003 begann. Ihr Ziel war es, kompakte, generische RTL-Beschreibungen zu ermöglichen. Die Entwicklung wurde
Ende 2005 eingestellt, als der Autor seine Prioritäten auf das Nachfolgeprojekt HDCaml verlagerte.
Reinhold Schmidt befasste sich in seiner Magisterarbeit mit dem Titel „Image Processing on Field
Programmable Gate Arrays using the Hardware Description Language Confluence“[22] bereits ausführlich
mit dieser Sprache.
4.3.1
Spracheigenschaften
In einem Vortrag[17] aus dem Jahr 2005 (auszugsweise zitiert in Anhang B, S. 97) beschreibt Tom Hawkins
den Anwendungsbereich und die Einschränkungen und Vorteile von Confluence:
Anwendungsbereich und Vorteile
Confluence ist eine deklarative6 RTL-Design-Sprache, die funktionale Programmierung einsetzt um Hardware zu generieren. Der Vorteil dieser Art von HW-Beschreibung ist eine bessere Wiederverwendbarkeit
von Designs (Tom Hawkins spricht von „functional plug-n-play“[17]) im Vergleich mit üblichen Sprachen
wie VHDL oder Verilog. Diese entsteht einerseits durch nützliche Eigenschaften funktionaler Sprachen
5 Web:
http://funhdl.org/wiki/doku.php?id=confluence, Download: http://funhdl.org/download
6 Deklarative Sprachen sind eine Menge von Programmiersprachen, zu denen u.A. funktionale (wie OCaml) und logische Sprachen
(wie Prolog) gehören.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
52
wie Typableitung, Funktionen höherer Ordnung, Rekursive Datentypen und Pattern-Matching. Andererseits wird sie durch die automatische Ableitung von Bitbreiten von Bitvektoren aufgrund ihrer Verwendung
und deren Überprüfung auf kompatible Bitbreiten bei ihrer Verwendung wesentlich verbessert.
Der funktionale Ansatz bietet auch Vorteile im Zusammenhang mit der Verifikation der Korrektheit
von Schaltungen. Durch die bessere Wiederverwendbarkeit braucht man nur die Funktionalität von weniger
Komponenten verifizieren. Automatisch abgeleitete und geprüfte Bitbreiten von Bitvektoren garantieren
diesbezüglich korrekte Verbindungen von Schaltungsteilen.
Confluence ist verhältnismäßig einfach zu lernen. Mit Hilfe des Tutorials7 sollte es den meisten Benutzern (auch ohne Kenntnis von funktionalen Sprachen) innerhalb eines Tages möglich sein, selber einfache
Designs zu erstellen.
Confluence erzeugt aus einer Hardware-Beschreibung mehrere Output-Formate: Verilog, VHDL, JHDL
und C.
Die Beschränkung auf die Beschreibung von ausschließlich synchroner Logik auf dem RT-Level hat
auch Vorteile: erstens kann man viele Fehler erst gar nicht machen, Zitat[17]: „You can’t break, what you
can’t control“. Zweitens gewährleistet eine RTL-Beschreibung 100% synthetisierbaren HBS-Output. Drittens behält der Designer die uneingeschränkte Kontrolle über das Endergebnis.
Confluence hat laut [17] verglichen mit den verbreiteten HW-Beschreibungssprachen VHDL und Verilog eine deutlich höhere Informationsdichte8 und ermöglicht dadurch eine deutlich höhere Produktivität.
Einschränkungen
Confluence ist keine Mixed-Signal- oder Analog-Design-Sprache. Confluence ist keine Verhaltensbeschreibungssprache. Mit Confluence können nur synchrone RTL-Designs beschrieben werden. Es gibt weder pegelsensitive Latches, noch kombinatorische Schleifen oder andere asynchrone Logik. Schließlich gibt es
keine Tristate-Buffer oder Bidirektionale Busse.
Confluence unterstützt nicht die komplexen Verzögerungsmodelle anderer Sprachen sondern Zuweisungen werden immer sofort wirksam, es gibt keine Ereignislisten und Transaktionen.
Confluence schreibt bei der HBS-Generierung „flache Netzlisten“ („elaborated netlists“), das heißt,
Teilschaltungen (oder Komponenten) sind nicht mehr erkennbar.
4.3.2
Dokumentation und Code-Beispiel
Ausführliche Dokumentation zu Confluence befindet sich auf dessen Webseite.9 Als Code-Beispiel ist in
Listing 4.14, S. 53 ein Aufwärts/Abwärts-Zähler (up/down counter) abgedruckt, der der Webseite von Confluence entnommen ist.10
4.4
Vergleich von Confluence mit HDCaml
Abbildung 4.3, S. 54 zeigt die Verwandtschaft von Confluence und HDCaml. HDCaml ist durch Einbettung
in Ocaml implementiert und übernimmt die Design-Ziele von Confluence, welches als Compiler in der
Sprache OCaml implementiert ist.
4.4.1
Unterschiede
Trotz der gleichen Design-Ziele von HDCaml und Confluence gibt es einige Unterschiede zwischen den
beiden Sprachen.
7 http://funhdl.org/wiki/doku.php?id=confluence:docs:tutorial
8 Abschätzung
von Tom Hawkins: 3 bis 5 mal so dichter Code.
9 http://funhdl.org/wiki/doku.php?id=confluence
10 http://funhdl.org/wiki/doku.php?id=confluence:docs:examples:basic_code_generation_and_simulation
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
53
with
up_down_counter
is
component u p _ d o w n _ c o u n t e r
+ counter_width (∗ I n t e g e r c o n f i g u r a t i o n parameter . ∗)
+count_up
( ∗ 1− b i t s i g n a l . 1 c o u n t s up , 0 c o u n t s down . ∗ )
−c o u n t _ v a l u e
( ∗ The o u t p u t c o u n t e r s i g n a l . ∗ )
with
value_one
value_plus_one
value_minus_one
value_selected
is
v a l u e _ o n e <− { one c o u n t e r _ w i d t h $ }
v a l u e _ p l u s _ o n e <− c o u n t _ v a l u e ’+ ’ v a l u e _ o n e
v a l u e _ m i n u s _ o n e <− c o u n t _ v a l u e ’−’ v a l u e _ o n e
v a l u e _ s e l e c t e d <− c o u n t _ u p ’ then ’ v a l u e _ p l u s _ o n e ’ e l s e ’ v a l u e _ m i n u s _ o n e
c o u n t _ v a l u e <− { r e g c o u n t e r _ w i d t h v a l u e _ s e l e c t e d $ }
end
20
21
22
23
24
25
( ∗ I n s t a n t i a t e an 8− b i t u p _ d o w n _ c o u n t e r and c o n n e c t i n p u t s and o u t p u t s . ∗ )
{ up_down_counter 8
{ i n p u t " count_up " 1 $}
{ o u t p u t " co u nt _ va l ue " $}
}
Listing 4.14: Aufwärts/Abwärts-Zähler in Confluence
1
2
open Hdcaml ; ;
open D e s i g n ; ;
3
4
5
6
7
8
9
10
11
12
13
14
15
16
l e t up_down_counter
counter_width (∗ I n t e g e r c o n f i g u r a t i o n parameter . ∗)
count_up
( ∗ 1− b i t s i g n a l . 1 c o u n t s up , 0 c o u n t s down . ∗ )
=
l e t count_value = signal " count_value_sig " counter_width
l e t v a l u e _ o n e = one c o u n t e r _ w i d t h
l e t value_plus_one = count_value +: value_one
l e t v a l u e _ m i n u s _ o n e = c o u n t _ v a l u e −: v a l u e _ o n e
l e t v a l u e _ s e l e c t e d = mux2 c o u n t _ u p v a l u e _ p l u s _ o n e v a l u e _ m i n u s _ o n e
l e t c o u n t _ r e g = r e g vdd v a l u e _ s e l e c t e d
c o u n t _ v a l u e <== c o u n t _ r e g ;
count_value
( ∗ The o u t p u t c o u n t e r s i g n a l . ∗ )
;;
in
in
in
in
in
in
17
18
19
20
21
22
23
24
25
( ∗ I n s t a n t i a t e an 8− b i t u p _ d o w n _ c o u n t e r and c o n n e c t i n p u t s and o u t p u t s . ∗ )
l e t up_down_counter_design ( ) =
s t a r t _ c i r c u i t " up_down_counter " ;
l e t count_up = i n p u t " count_up " 1 in
o u t p u t " count_value " ( up_down_counter 8 count_up ) ;
l e t c i r c u i t = g e t _ c i r c u i t ( ) in
Systemc . output_model c i r c u i t ;
;;
26
27
up_down_counter_design ( ) ; ;
Listing 4.15: Aufwärts/Abwärts-Zähler in HDCaml
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
54
Abbildung 4.3: Verwandtschaft von Confluence und HDCaml
Syntax
Wenn man die Syntax betrachtet, handelt es sich bei Confluence und HDCaml (bzw. OCaml) um zwei
sehr unterschiedliche Sprachen. Eine der wenigen Gemeinsamkeiten besteht darin, dass beides funktionale
Sprachen sind. Listing 4.15, S. 53 zeigt die HDCaml-Version des Zählers von Listing 4.14, S. 53. Es wurden
so weit wie möglich gleiche Namen verwendet, damit die beiden Beispiele möglichst gut vergleichbar sind.
Art und Verwendung der Werkzeuge
Confluence funktioniert als Compiler. Durch Aufruf des Programms cf wird aus der Confluence-Programmdatei eine FNF-Netzliste (Free Netlist Format) erzeugt. Ein weiteres, zu Confluence gehörendes,
Programm namens fnf wandelt FNF-Netzlisten in Sprachen wie VHDL, Verilog, oder C um und ist somit
auch ein Compiler. HDCaml ist eine domänenspezifische Sprache (DSL), die in OCaml eingebettet ist. Die
notwendigen Befehle, um aus den Code-Beispielen ein C-Modell zu erzeugen, lauten:
Confluence:
cf -o up-down-counter.fnf up-down-counter.cf
fnf -read_fnf up-down-counter.fnf -write_c up-down-counter
HDCaml:11
ocamlc -g -o up-down-counter.exe hdcaml.cma up-down-counter.ml
ocamlrun -b up-down-counter.exe
Der Befehl zum Erzeugen von C-Code wird bei Verwendung von HDCaml nicht beim Übersetzen oder
Ausführen des Programms angegeben, sondern befindet sich im Programm selbst (s. Abschnitt 3.3.1, S. 29).
4.4.2
Verbesserungen in HDCaml
HDCaml ist (im Gegensatz zu Confluence) eine general-purpose-Programmiersprache, da alle (funktionalen, imperativen und objektorientierten) Eigenschaften von OCaml zur Verfügung stehen. Im Speziellen
sind damit auch die (bei Confluence stark eingeschränkten) I/O-Möglichkeiten unlimitiert.
HDCaml bietet die Möglichkeit der HBS-Annotation (s. Abschnitt 3.3.1.2, S. 30). Man kann nahezu
sämtlichen12 Schaltungs-Knoten Namen zuweisen und dadurch generierte HW-Beschreibungen einfacher
mit dem HDCaml-Programm in Verbindung bringen.
Die OCaml-Basis von HDCaml bietet die Möglichkeit, Design-Files in Form von separaten .ml Dateien separat zu kompilieren.
Eine HW-Beschreibung kann direkt im HDCaml-Programm simuliert werden („built-in Simulation“),
Diese Fähigkeit ermöglicht es, anspruchsvolle Testbenches direkt in OCaml zu schreiben (z.B. Listing 3.5
in Abschnitt 3.3, S. 27). Auch die Testbenches können somit von den Vorteilen der funktionalen Sprache
profitieren.
11 Beim Einsatz von HDCaml ist das nur eine der möglichen Varianten. Siehe Abschnitt C.8, S. 105 für eine Beschreibung anderer
Möglichkeiten.
12 Manche Knoten werden HDCaml-intern generiert und sind von außen nicht zugänglich, z.B. von den Funktionen bits oder
mux .
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
55
HDCaml ist bei manchen HW-Modellen wesentlich schneller als Confluence. Die Ausführungszeit für
die Erzeugung des C-Modells einer funktional identischen Schaltung (Faltungsfilter aus Abschnitt 5.5, S. 73
für 512 Pixel breite Bilder und 3x3 Pixel großem Kernel) betrug auf dem selben Computer bei HDCaml
ca. 20 Sekunden, bei Confluence/FNF 4825 Sekunden (dazwischen liegt ca. der Faktor 241). Dieses Geschwindigkeitsproblem hat laut [17] gleich wie der folgende Punkt (schwer verständliche Fehlermeldungen
in Confluence) seine Ursache in der „lenient evaluation order“ von Confluence.
HDCaml ermöglicht eine leichtere Fehlersuche als Confluence. Das hat drei Ursachen. Erstens hat die
Basissprache OCaml bereits sehr zielführende Fehlermeldungen. Zweitens ist die in HDCaml benutzte Methode, zyklische Strukturen in Schaltungen zu beschreiben (Dangling Signal, s. Abschnitt 3.4.2.1, S. 32),
wesentlich primitiver als diejenige von Confluence („lenient evaluation order“) und deshalb von HDCaml
leichter zu überprüfen. Meldet HDCaml dabei Fehler, sind diese vom Benutzer viel leichter nachzuvollziehen. Drittens wurden im Zuge der vorliegenden Arbeit wesentlich verbesserte Fehlermeldungen und neue
Debug-Funktionen implementiert.
HDCaml lässt sich relativ einfach ändern/erweitern. Confluence ist auch änderbar, jedoch nur mit wesentlich größerem Aufwand.
Die Verbesserungen von HDCaml gegenüber Confluence lauten zusammengefasst:
•
•
•
•
•
•
•
4.4.3
HDCaml ist eine general-purpose-Programmiersprache
Möglichkeit der HBS-Annotation
Separates Kompilieren von Teilprogrammen
In HDCaml eingebaute Simulation
Höhere Geschwindigkeit
Verständlichere Fehlermeldungen (leichtere Fehlersuche)
HDCaml ist einfach erweiterbar
Nicht implementierte Funktionalität in HDCaml
HDCaml hat keinen NuSMV-Output. Stattdessen gibt es den Ansatz, PSL-Assertions zu unterstützen, der
jedoch bei weitem nicht fertiggestellt ist. Es gibt in HDCaml auch keinen JHDL-Output.
HDCaml hat keine truth-tables, RAMs, black boxes und state machines.13 Ein weiteres fehlendes, aber
nützliches Feature sind Komponenten und explizite Resets und Enables dafür. In HDCaml gibt es nur einen
globalen Reset, Enables muss man andererseits für jedes einzelne Register angeben. Eine weitere Einschränkung ist, dass HDCaml nur eine Clock-Domain hat. Das kann für manche Anwendungen zu wenig
flexibel sein.
Confluence hat eine komfortablere Strukturbeschreibung als HDCaml, weil sie folgende Eigenschaften
hat: (1) Die Richtung des Datenflusses ist egal, es gibt keine Inputs und Outputs,14 sondern nur allgemein
Signale (2) die Reihenfolge, in der man Wires verbindet, ist egal. Diese Eigenschaften sind in Confluence
aufgrund der „lenient evaluation order“ erfüllt. Als Beispiele dafür seien nochmal die Implementierungen
der Aufwärts/Abwärts-Zähler genannt. Im Confluence-Beispiel (Listing 4.14, S. 53) kann man die Zeilen 14
bis 18 beliebig permutieren, das Programm bleibt immer korrekt und erzeugt genau die gleiche Netzliste.
Im Gegensatz dazu muss beim HDCaml-Programm (Listing 4.15, S. 53) die Reihenfolge der Zeilen 8 bis 14
größtenteils gleich bleiben, weil Werte, die durch let . . . in definiert werden, erst in darauf folgenden
Zeilen des Programms verwendbar sind. Zeile 15 ist der Rückgabewert der Funktion und muss jedenfalls
an deren Ende stehen.
Zusammengefasst lauten die in Confluence vorhandenen, jedoch in HDCaml nicht implementierten
Fähigkeiten folgendermaßen:
• NuSMV-Output
13 Confluence hat „inoffiziell“ RAM, dieses wird aber in der FNF-Netzliste in Strukturbeschreibung umgesetzt und wird damit von
den Synthese-Werkzeugen nicht als RAM erkannt (es ist nicht inferable).
14 Die Kennzeichnungen „ + “ und „ - “ für Inputs und Outputs von Komponenten werden von Confluence ignoriert. Ob Ports Inputs
oder Outputs sind, ergibt sich rein aus ihrer Verwendung.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
•
•
•
•
•
•
•
4.5
56
JHDL-Output
RAM
black boxes
state machines
Komponenten und explizite Resets und Enables dafür
Mehrere Clock-Domains
Komfortable Strukturbeschreibung
HDFS
HDFS [29] (Hardware-Description-F-Sharp)15 ist eine Hardwarebeschreibungssprache, die in die funktionale Sprache F#[30] eingebettet ist.
Das Open-Source-Projekt HDFS unterliegt der LGPL [5], die Lizenz von F# ist eine Version des MSRSSLA (Microsoft Research Shared Source License Agreement) [31].
HDFS wurde 2007 von Andy Ray als unabhängige Neuimplementierung von HDCaml veröffentlicht,
nachdem er vorher maßgeblich an der Weiterentwicklung von HDCaml beteiligt war.16
HDFS verfolgt die gleichen Ziele wie HDCaml. Wenn man sich auf die Fähigkeiten beschränkt, die es
bereits in HDCaml gibt, ist HDFS ziemlich gleich zu benutzen wie HDCaml. HDFS enthält jedoch eine
Reihe von Weiterentwicklungen (s. Abschnitt 4.6.1, S. 57) im Vergleich zu HDCaml. Um für HDCamlBenutzer den Umstieg auf HDFS zu vereinfachen, enthält HDFS eine HDCaml-kompatible Programmierschnittstelle (API, application programming interface).
4.5.1
Basissprache F#
F#[30] ist eine funktionale Sprache für Microsofts .NET-Entwicklungsumgebung, die größtenteils kompatibel zu OCaml ist. F# funktioniert auch in anderen Betriebssystemen als Microsoft Windows, z.B. bei
Verwendung des Mono Projects17 unter Linux, Solaris, Mac OS X und anderen Unix-ähnlichen Betriebssystemen.
4.5.2
Beschreibung von Hardware
RTL- und Strukturbeschreibung sowie Generatoren für regelmäßig aufgebaute Strukturen existieren in
HDFS nahezu in identischer Form wie in HDCaml, deshalb werden hier keine Beispiele dafür angegeben.
HDFS unterstützt auch die Verhaltensbeschreibung. Anhand eines Beispiel von der HDFS-Webseite18
werden Segmente einer HDFS-Verhaltensbeschreibung (Listing 4.17, S. 57) einer äquivalenten Verilog-Verhaltensbeschreibung (Listing 4.16, S. 57) gegenübergestellt (eine ausführliche Beschreibung ist der Dokumentation auf der Webseite zu entnehmen). Die Listings sind so formatiert, dass äquivalente Konstrukte so
oft wie möglich in gleichen Zeilen stehen.
4.6
Vergleich von HDFS mit HDCaml
HDFS und HDCaml sind in Aufbau, Funktionsweise, Fähigkeiten und Einschränkungen sehr ähnlich. Alle
Eigenschaften, die in den folgenden beiden Abschnitten nicht explizit erwähnt werden, sind in HDCaml
und HDFS mehr oder weniger gleich.
15 Webseite des Projekts: http://code.google.com/p/hdfs,
Diskussionsforum (Google Group): http://groups.google.com/group/hdfsharp.
16 Die Änderungen in den Releases 0.2.7 und 0.2.8 stammen laut Andy Rays Angaben größtenteils von ihm und auch der in Release
0.2.10 implementierte VHDL-Generator wurde ursprünglich von ihm geschrieben.
17 http://www.mono-project.com
18 http://code.google.com/p/hdfs/wiki/HdfsExampleBehave
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
1
2
reg [ 1 : 0 ] s t a t e ;
wire ready ;
3
4
7
8
9
10
11
12
13
14
19
20
21
22
23
24
25
26
27
28
29
30
31
32
4
6
7
8
9
behave [
10
b_switch ( s t a t e . q )
b_case ( c o n s t i 2
b_if ( s t a r t ) [
s t a t e $== 0 ;
] [];
r e a d y $== 1 ;
];
b_case ( c o n s t i 2
( ∗ . . . do some
s t a t e $== 2 ;
];
b_case ( c o n s t i 2
( ∗ . . . do some
s t a t e $== 3 ;
];
b_case ( c o n s t i 2
( ∗ . . . do some
s t a t e $== 0 ;
];
]
11
12
13
14
15
16
18
l e t s t a t e = b_regc enable 2 in
l e t ready = b_wire0 1 in
5
a l w a y s @( p o s e d g e c l o c k , p o s e d g e r e s e t )
begin
i f ( r e s e t ) begin
s t a t e <= 0 ;
end e l s e i f ( e n a b l e ) b e g i n
case ( s t a t e )
0: begin
if ( start )
s t a t e <= 1 ;
15
17
2
3
a s s i g n r e a d y = s t a t e == 0 ;
5
6
1
57
16
end
1: begin
/ / . . . do some work
s t a t e <= 2 ;
end
2: begin
/ / . . . do some work
s t a t e <= 3 ;
end
3: begin
/ / . . . do some work
s t a t e <= 0 ;
end
endcase
end
end
Listing 4.16: Verhaltensbeschreibung in Verilog
4.6.1
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[
0) [
1) [
work ∗ )
2) [
work ∗ )
3) [
work ∗ )
31
32
]
Listing 4.17: Verhaltensbeschreibung in HDFS
Verbesserungen in HDFS
HDFS ermöglicht aufgrund von F#-spezifischen Spracheigenschaften teilweise kompakteren Code als HDCaml und hat einige Fähigkeiten, die es in HDCaml nicht gibt.
Kompakterer Code:
F# hat zwei für HDFS relevante Spracheigenschaften, die OCaml nicht besitzt, und welche teilweise zu
einer kürzerer Schreibweise von HDFS-Programmen führen:
• Überladen (overloading) von Funktionen und Operatoren
• Properties19
In HDFS sind alle Operatoren überladen, für die das sinnvoll ist, z.B. die Vergleichsoperatoren, bei denen
die rechte Seite entweder ein Signal (Bitvektor), eine Zahl oder ein als String dargestellter Bitvektor (in
Verilog-Codierung) sein können. Folgende Ausdrücke sind in HDFS erlaubt, wobei x und y vom Typ
Signal sind:
• x <: y
• x <: 5
• x <: "3’b101"
Properties sind ein Mechanismus, der es ermöglicht, scheinbar existierende Member-Variablen anzusprechen, wobei tatsächlich aber spezielle set - und get -Funktionen aufgerufen werden. Im API von HDFS
werden Properties ausgiebig genutzt, u.A. beim eingebauten Simulator. Wenn p ein Port ist, dann liefert
19 Es
existiert keine deutsche Bezeichnung für dieses Konzept
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
58
z.B. p.y den Inhalt als vorzeichenbehaftetes Byte und p.ul als nicht-vorzeichenbehaftete 32 Bit-Integer
(eine vollständige Liste aller Properties gibt es auf der Webseite zur Simulation20 ). An alle Properties können auch Werte zugewiesen werden.
Fähigkeiten:
In HDFS ist folgende Funktionalität implementiert, die es in HDCaml nicht gibt:
•
•
•
•
•
•
•
•
•
•
•
Verhaltensbeschreibung
RAM
Eingebaute Darstellung von Signalverläufen (waveform viewer)
Eingebaute Strukturdarstellung
Verilog-Cosimulation mit ModelSim (Mentor Graphics)
Instanzierung von externen VHDL- oder Verilog-Komponenten
Interface zu Komponenten aus der Xilinx-FPGA-Bibliothek
Synthese für Xilinx-FPGAs
Zusätzliche Output-Formate: C# und Simulink (MATLAB)
Ausgabe der benötigten Ressourcen für ein HW-Modell (resource reporting)
Bibliothek von nützlichen Funktionen und HW-Modellen
4.6.2
Nachteile in HDFS
Die als nachteilig empfundenen Eigenschaften von HDFS sind genau jene, die zu Beginn der vorliegenden
Arbeit bei HDCaml 0.2.9 beobachtet wurden, nämlich das Fehlen von aussagekräftigen und zielführenden
Fehlermeldungen und sonstigen Informationen, die so früh wie möglich erfolgen sollen (s. Kapitel 5, S. 61).
Die in HDCaml 0.2.10 durchgeführten Erweiterungen fehlen in HDFS größtenteils. Dazu gehören:
•
•
•
•
•
Nachvollziehbare Umbenennungen von Signalnamen (s. Abschnitt C.4 u. Abschnitt A.1.1.3).
Warnungen bei Verwendung von gleichen Signalen (z.B. bei mux2 sel a a )
Fehlermeldung bei unverbundenen Dangling Signals.
Warnungen bei nicht benutzten Inputs oder Schaltungsknoten
Warnungen bei Operationen, die zu sinnlosen Ergebnissen führen (z.B. Shiften von mehr Bits als ein
Bitvektor hat)
• Debug-Funktionen (vgl. Abschnitt A.1.1.6, S. 89 und Abschnitt 5.3, S. 66)
4.7
Sassy
Sassy[19, 20] ist eine funktionale Sprache zur Beschreibung von Signalverarbeitungs-Algorithmen, die an
der Colorado State University im Rahmen des Cameron-Projects[32] kreiert wurde. Die diesbezüglichen
Publikationen wurden im Zeitraum 1998 bis 2002 veröffentlicht.21 Die Compiler der Sprache sind weder
als ausführbares Programm noch als Quellcode öffentlich verfügbar.
Das Ziel dieses Projekts ist es, die Implementierung von BV-Algorithmen auf FPGAs (bzw. rekonfigurierbarer Logik im Allgemeinen) ohne Wissen über Hardware-Design und klassische Hardwarebeschreibungssprachen wie VHDL oder Verilog zu ermöglichen.
Der Name „Sassy“ kommt durch die Aussprache der drei Zeichen „SA-C“ zustande, welche für „SingleAssignment-C“ stehen.22 Single Assignment bedeutet, dass jedem Bezeichner nur genau einmal ein Wert
zugewiesen und dieser danach nicht mehr verändert wird. Diese Eigenschaft ermöglicht dem Compiler eine
einfache Analyse und Optimierung des Codes (vgl. Abschnitt 2.5, S. 15). Die Syntax von Sassy ist derjenigen von C so ähnlich, wie es die unterschiedlichen Konzepte ermöglichen. Der optimierende Compiler von
Sassy erzeugt VHDL- und C-Modelle.
20 http://code.google.com/p/hdfs/wiki/HdfsSimulation
21 Auch
das letzte Änderungsdatum der Webseite des Cameron-Projects[32] ist das Jahr 2002.
existieren mindestens zwei „SA-C“ genannte Sprachen von unterschiedlichen Projekt-Teams, diese hier ist durch den Namen
„Sassy“ eindeutig definiert.
22 Es
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
4.7.1
59
Spracheigenschaften
In Sassy wird ein Bildverarbeitungsalgorithmus auf einer hohen Abstraktionsebene beschrieben, die mehr
an general-purpose-Programmiersprachen als an Hardwarebeschreibungssprachen erinnert. Dabei handelt
es sich um eine Verhaltensbeschreibung, Sassy besitzt keine RTL- oder Strukturbeschreibung. Ein Nachteil
der Verhaltensbeschreibung ist, dass der Entwickler weniger Einfluss auf die tatsächlich erzeugten Hardwaremodelle hat, als bei den anderen Beschreibungsmöglichkeiten (vgl. [13, Kap. 1]).
Die genaue Syntax von Sassy kann den verschiedenen Publikationen ([19, 20] und vielen weiteren23
auf der Webseite des Cameron-Projects[32]) entnommen werden. In Sassy sind mehrdimensionalen Arrays
und leistungsfähige Schleifen-Konstrukte das zentrale Sprachelement.
4.7.1.1
Schleifen
Das interessanteste Sprachelement von Sassy sind die Schleifenkonstrukte, deren allgemeiner Aufbau in
Abbildung 4.4 gezeigt wird. Obwohl die Syntax der Schleifen Ähnlichkeit mit C-Schleifen hat, handelt es
dich dabei nicht um imperative, sondern um funktionale Programmierung (s. Abschnitt 4.7.1.2, S. 59).
for
generator(s)
{
body
} return
return(s)
Abbildung 4.4: Aufbau von Schleifen in Sassy ([19])
Listing 4.18 zeigt als Beispiel für eine Schleife in Sassy die Implementierung der Funktionalität, für
die man in OCaml die Funktion map2 (s. Abbildung 2.3, S. 17 in Abschnitt 2.5.2) verwenden würde.
In diesem Beispiel bewirkt der Loop-Generator in Zeile 2, dass die Elemente der beiden Arrays A und B
synchron (wie bei der Berechnung eines Skalarprodukts, deshalb das Schlüsselwort dot für dot product24 )
durchgegangen werden, wobei innerhalb der Schleife die jeweils gerade aktuellen Werte der Arrays als a
1
2
3
int16 V[ : ] =
for a in A dot b in B {
} r e t u r n ( a r r a y ( a+b ) ) ;
Listing 4.18: Sassy-Äquivalent zur Verwendung von map2
und b zur Verfügung stehen. Der Loop-Body dieses Beispiels ist leer. Im Loop-Return (Zeile 3) wird die
tatsächliche Rechenoperation ( a+b ) ausgeführt und das Ergebnis als Array zurückgegeben.
Ein weiteres Code-Beispiel ist Listing 5.6, S. 77 in Abschnitt 5.6.2, S. 76, dieses verwendet den for
window -Iterator zur Implementierung eines Faltungsfilters.
4.7.1.2
Klassifizierung der Sprache
Sassy hat die Zielsetzung, die besten Eigenschaften von existierenden imperativen und funktionalen Sprachen zu einer Sprache zu kombinieren, die dem Compiler einfache Analyse und Optimierung ermöglicht
und gut für Bildverarbeitung geeignet ist. Laut seinen Autoren ist Sassy eine ausdrucks-orientierte, rein
funktionale, nicht imperative Sprache [19].
In Sassy ist das Hauptproblem von imperativen Sprachen, nämlich die Programmierung durch eine
Abfolge von Seiteneffekten, beseitigt. Es gibt in Sassy Schleifen, diese manipulieren Arrays aber nicht
ortsfest (in-place), sondern liefern ein Ergebnis-Array zurück, wie es in rein funktionalen Sprachen üblich
ist.25 Sassy hat im Gegensatz zu C auch keine Pointer, und verfügt über automatische Speicherverwaltung.
23 http://www.cs.colostate.edu/cameron/publications.html
24 Analog dazu gibt es das Schlüsselwort cross , das alle Kombinationen der beiden Array-Elemente bildet, wofür man in C zwei
verschachtelte Schleifen schreiben müsste.
25 Der Compiler kann daraus, wenn aus Effizienzgründen nötig, wieder in-place-Modifikationen machen.
KAPITEL 4. MIT HDCAML VERWANDTE PROJEKTE
4.8
60
Vergleich von Sassy mit HDCaml
Sassy und HDCaml sind nicht unmittelbar miteinander vergleichbar. Die beiden wesentlichen Eigenschaften, in denen die Sprachen übereinstimmen, sind:
• Beides sind funktionale Sprachen.
• Beide Sprachen sind Code-Generatoren für VHDL und C.
Unterschiede
Sassy ist für den Zweck, für den es gedacht ist (Bildverarbeitung bzw. allgemein Signalverarbeitung) wesentlich mächtiger und einfacher zu verwenden als HDCaml.
Der Hauptgrund dafür ist, dass bei Sassy dem Entwickler viel Arbeit durch den optimierenden Compiler abgenommen wird, der in HDCaml nicht existiert. Dort muss die optimale Umsetzung eines BVAlgorithmus in Hardware vom Entwickler selber vorgenommen werden. Ein weiterer Grund sind die leistungsfähigen, vordefinierten Schleifen-Generatoren (z.B. window -Iterator, dot- und cross- Produkte, etc.), die die häufigsten in Bildverarbeitung vorkommenden Operationen mit wenig Code ermöglichen.
Diese und weitere Unterschiede zwischen Sassy und HDCaml sind in Tabelle 4.6 zusammengefasst.
Implementierung der DSL
Beschreibungsart
Sassy
HDCaml
Compiler
Einbettung in OCaml
Verhaltensbeschreibung
RTL- u. Strukturbeschreibung
Signal- und Bildverarbeitung
Hardwarebeschreibung allgemein
Arrays
Bitvektoren
Typableitung
—
3
Bitbreiten-Ableitung
—
3
Funktionen höherer Ordnung
—
3
Generische Beschreibungen
—
3
Optimierender Compiler
3
—
Spezielle BV-Operationen
3
—
Einsatzzweck
Grundlegende Datenstruktur
Tabelle 4.6: Unterschiede zwischen Sassy und HDCaml
Code-Beispiel
Listing 4.19 zeigt die Verwendung der Funktion map2 in HDCaml. Das Beispiel implementiert die gleiche
Funktionalität wie das Sassy-Beispiel von Listing 4.18, S. 59. Der vorliegende Fall, nämlich dass ein äquivalenter Code in HDCaml kürzer als in Sassy formuliert werden kann, ist nicht die Regel. Ein Gegenbeispiel
dafür sind die Implementierungen eines Faltungsfilters in den Sprachen HDCaml (Listing 5.3, S. 74) und
Sassy (Listing 5.6, S. 77). im Kapitel 5, S. 61.
1
l e t v = map2 ( + : ) a b i n
Listing 4.19: Verwendung von map2 in HDCaml
Kapitel 5
Erweiterung von HDCaml
In Abschnitt 5.1 wird der Aufbau der Version 0.2.9 von HDCaml beschrieben. Anschließend werden Verbesserungsmöglichkeiten aufgelistet (Abschnitt 5.2) und die ausgewählten Änderungen an HDCaml dokumentiert (Abschnitt 5.3) und evaluiert (Abschnitt 5.4). Als praktisches Beispiel zum Themenbereich Bildverarbeitung folgt in Abschnitt 5.5 die Implementierung eines Faltungsfilters und in Abschnitt 5.6 dessen
Evaluierung.
5.1
Aufbau von HDCaml 0.2.9
Dieser Abschnitt beschreibt HDCaml 0.2.9 (die letzte von Tom Hawkins veröffentlichte Version),1 welches
durch einen VHDL-Generator2 von Andy Ray ergänzt wurde. Name und Zweck der Module von HDCaml
0.2.9 (plus VHDL-Generator) lauten (in alphabetischer Reihenfolge) wie folgt:
Atomic
Circuit
Design
Example
Release
Rename
Simulate
Systemc
Verify
Verilog
Vhdl
Waveform
„Atomic state transitions“ (unvollständig implementiert).
Datenstrukturen zur Schaltungsrepräsentation.
Funktionen und Operatoren zur Beschreibung des Designs.
Verwendungsbeispiel.
Enthält Informationen über Änderungen in den verschiedenen Versionen.
Umbenennung von ungültigen Signalnamen.
OCaml-interne Simulation von Schaltungen.
Schreiben des SystemC-Modells.
Funktionen und Operatoren zum Spezifizieren von PSL-Assertions.
Schreiben der Verilog-Netzliste.
Schreiben der VHDL-Netzliste.
Schreiben von Signalverläufen (Waveforms) in VCD-Dateien.
Abbildung 5.1, S. 62 zeigt die Abhängigkeiten aller oben genannten Module voneinander. Die Richtung der
Pfeile weist von dem abhängigen Modul auf das benutzte Modul. Das isolierte Modul Release dient ausschließlich der Dokumentation. Das nicht fertiggestellte Modul Atomic war der erste Versuch von Tom
Hawkins die Funktionalität zu implementieren, für die er später (2007) eine eigene Sprache (Atom [33])
entwickelte. Für HDCaml kann dieses Modul ignoriert werden. Modul Example stellt ein Verwendungsbeispiel von HDCaml dar, das die gesamte, dem Benutzer zur Verfügung stehende, Funktionalität demonstriert. Es ruft deshalb Funktionen aus sämtlichen relevanten Modulen außer Rename und Circuit
(diese beiden brauchen nie direkt verwendet zu werden) auf.3
1 Download von HDCaml 0.2.9: http://funhdl.org/download. Der Aufbau von HDCaml 0.2.10, also jener Version, die
das Ergebnis der vorliegenden Arbeit ist, ist in Abschnitt 3.5.2, S. 38 beschrieben.
2 Dieser sollte laut Tom Hawkins ohnehin Bestandteil von zukünftigen HDCaml-Versionen werden, dazu kam es aber nicht mehr,
da er sein Interesse auf seine neue Entwicklung „Atom“[33] fokussierte.
3 Der VHDL-Generator wird vom Beispiel nicht verwendet, da er nicht Teil von HDCaml 0.2.9 war.
61
KAPITEL 5. ERWEITERUNG VON HDCAML
62
Abbildung 5.1: Abhängigkeiten der Module von HDCaml 0.2.9 (mit VHDL-Gen.) voneinander
Alle weiteren Module außer Waveform verwenden die im Modul Circuit definierten, grundlegenden Datenstrukturen. Rename wird von Verilog und Vhdl verwendet um mehrfach vorkommende
und ungültige (z.B. mit Verilog- oder VHDL- Schlüsselwörtern identische) Signalnamen umzubenennen
(s. Abschnitt 5.3, S. 66 u. Abschnitt A.1.1.3, S. 86). Simulate verwendet Waveform um die in der Simulation errechneten Signalverläufe in eine VCD-Datei zu schreiben. Verify (zum Spezifizieren von
PSL-Assertions) verwendet verschiedene Funktionen von Design .
Die Gesamt-Zeilenanzahl (lines of code) von HDCaml 0.2.9 (mit VHDL-Generator) beträgt rund 3500.4
Tabelle 5.1, S. 63 listet die Anzahl der Code-Zeilen pro Module auf. Interface-Dateien ( .mli ) dienen dazu, die nach außen hin sichbaren Funktionen eines Moduls zu definieren. Eine fehlende Interface-Datei
bedeutet, dass alle Funktionen des Moduls nach außen hin sichtbar sind.
5.2
Analyse der Verbesserungsmöglichkeiten
Eine Analyse des Quellcodes und die Beschreibung von kleinen Hardware-Modellen in HDCaml führte zu
den in den folgenden Abschnitten beschriebenen Verbesserungsvorschlägen.
Dokumentation
Ein unmittelbar auffallender Umstand war der Mangel an Dokumentation zu HDCaml. Parallel zur vorliegenden Arbeit (in der alles sorgfältig dokumentiert wurde, s. Abschnitt 5.3, S. 67) erstellte Daniel Sánchez
Parcerisa eine Installationsanleitung5 und ein Tutorial [23].
4 Diese
Angabe beinhaltet Kommentare und Leerzeilen, entspricht also der Zeilenanzahl der Textdatei, die den Code repräsentiert.
5 http://funhdl.org/wiki/doku.php?id=hdcaml:installation
KAPITEL 5. ERWEITERUNG VON HDCAML
Modul
atomic.ml
Zeilen
120
Interface
atomic.mli
63
Zeilen
Gesamt
32
152
84
356
118
582
circuit.ml
272
circuit.mli
design.ml
464
design.mli
example.ml
299
—
299
release.ml
101
—
101
rename.ml
107
rename.mli
simulate.ml
387
simulate.mli
systemc.ml
614
verify.ml
verilog.ml
6
113
12
399
systemc.mli
4
618
232
verify.mli
83
315
195
verilog.mli
4
199
vhdl.ml
175
—
waveform.ml
152
waveform.mli
13
165
356
3474
Summe
3118
175
Tabelle 5.1: Zeilenanzahl der Module von HDCaml 0.2.9 (mit VHDL-Gen.)
5.2.1
Verbesserte Fehlermeldungen
HDCaml 0.2.9 hat keine zielführenden Fehlermeldungen und gibt auch keine Zeilennummern an. Die Überprüfungen auf Fehler sind auch generell zu wenig rigoros um alle feststellbaren Fehler zu finden.
Es sollen verbesserte Fehlermeldungen implementiert werden, die Fehler bereits während der Phase des
„Schaltungsaufbaus“ (damit ist alles gemeint, was in einem HDCaml-Programm zwischen den Aufrufen
von start_circuit und get_circuit passiert) finden. Die Ziele der verbesserten Fehlerüberprüfungen und -meldungen sind:
1. Alle feststellbaren Fehler sollen gefunden werden. In 0.2.9 werden manche Fehler erst durch ihre
Auswirkungen, d.h. durch Folgefehler gefunden. Andere Fehler werden überhaupt nicht gefunden
und äußern sich in fehlerhaften HBS-Modellen.
2. Fehler sollen so früh wie möglich gemeldet werden. In 0.2.9 werden manche Fehler nicht von der
Funktion gemeldet, die vom Benutzer aufgerufen wird, sondern von anderen Funktionen, die Teile
der Funktionalität implementieren. Z.B. ruft die Funktion bit die Funktion select auf. Fehler
bei der Indizierung werden dann von select und nicht von bit gemeldet.
3. Während des Schaltungsaufbaus sollen alle Überprüfungen durchgeführt werden, die in dieser Phase
möglich sind. In 0.2.9 werden manche Überprüfungen in anderen Modulen durchgeführt, z.B. in den
HBS-Generatoren. Das bedeutet, dass Funktionalität zur Fehlererkennung möglicherweise mehrfach
und unterschiedlich implementiert ist.
4. Es soll eine exakte Lokalisierung von Fehlern möglich sein, d.h. man benötigt eine Verbindung zwischen der Fehlermeldung und einer konkreten Zeilennummer im Quelltext. In 0.2.9 geben Fehlermeldungen keine Zeilennummern an.
Mit verbesserten Fehlermeldungen, die die obenstehenden Ziele erfüllen, soll es den Benutzern von HDCaml möglich sein, Fehlermeldungen mit der Position ihrer Ursache im HDCaml-Programm in Verbindungen bringen zu können und so Korrekturen in kürzerer Zeit durchführen zu können, als das vorher möglich
war.
KAPITEL 5. ERWEITERUNG VON HDCAML
64
Warnungen
Nicht alle interessanten Hinweise, die einem die HDCaml-Bibliotheksfunktionen bei der Überprüfung eines
Schaltungs-Modells geben können, sind kritische Fehler. Es kann sich auch um Warnungen bzw. Informationen handeln.
Es sollte neben Fehlermeldungen eine weitere Kategorie von Hinweisen geben, die nur informative
Zwecke haben und die Ausführung des HDCaml-Programms nicht unterbrechen.
Debug-Funktionen
In generierten HBS-Modellen kommen zwei Arten von Signalen vor: benannte und unbenannte Signale. Zu den benannten Signalen gehören Inputs, Outputs, Dangling Signals und solche, die mittels HBSAnnotation (Abschnitt 3.3.1.2, S. 30) benannt wurden. Diese Signale erhalten in generierten HBS-Modellen
die ihnen zugewiesenen Namen. Für alle anderen Signale können nicht automatisch aussagekräftige Namen
erzeugt werden, weil HDCaml auf keine Informationen über OCaml-Variablennamen zugreifen kann (vgl.
Abschnitt 2.4, S. 13). Für diese unbenannten Signale werden in den generierten HBS-Modellen fortlaufende
Nummern verwendet, denen der Präfix „ n_ “ vorangestellt wird, um gültige Bezeichner daraus zu machen.
Bei der Fehlersuche in HDCaml-Modellen ist es oft notwendig, die Position im Quelltext zu ermitteln,
an der ein bestimmter Schaltungsknoten definiert wurde. Das ermöglicht den Rückschluss von generiertem
HBS-Code (z.B. VHDL) auf HDCaml-Quellcode. Diese Zuordnung manuell zu machen ist wegen den
oben beschriebenen, unbenannten Signalen nicht trivial. Auch das Suchen von benannten Signalen kann
aufwändig sein, falls Signalnamen von HDCaml unbenannt werden, weil sie mehrfach vorkommen oder
ungültige Bezeichner einer der generierten Sprachen sind.
Für diese beiden Fälle (Suchen von Knotennummern, Suchen von Signalnamen) sollten dem Benutzer
spezielle Debug-Funktionen zur Verfügung stehen.
5.2.2
Korrektur fehlerhafter Implementierungen
Code-Analyse und Experimente zeigten, dass manche Module nicht fehlerfrei bzw. vollständig implementiert waren. So wurden folgende Fehler gefunden:
OCaml-interner Simulator (Modul Simulate ):
Der Code für die beiden Arten von Multiplikationen ( mul_u , mul_s ) fehlt komplett. Außerdem befinden sich Fehler in den Operationen (vgl. Definition des Datentyps signal in Listing C.1, S. 98) add ,
subtract (Addition und Subtraktion), eq , lt (Vergleiche: gleich, kleiner), mux (Multiplexer), reg
(Register), select (Selektieren eines Teil-Bitvektors) und concat (Aneinanderhängen von Bitvektoren).
Umbenennung von Signalnamen (Modul Rename ):
Mehrfach vorkommende Signale der Form „ praefix_zahl “ mit gleichem Präfix aber sich unterscheidender Nummerierung (z.B. muxin_1 , muxin_2 , . . . ) werden ohne Notwendigkeit umbenannt (die Zahl
wird verändert).
C-Output (Modul Systemc ):
Es fehlt die Umbenennung von Signalnamen (weitere Fehler wurden von Andy Ray entdeckt, s. Abschnitt
5.3.2, S. 69).
VHDL-Generator (Modul Vhdl ):
Der doppelte Unterstrich (underscore) ist in VHDL-Bezeichnern nicht erlaubt. Dieser wird aber als Trennzeichen für die Benennung von Signalnamen in Teilschaltungen verwendet, z.B. counter_ _next (s.
Abschnitt C.3.1, S. 101 und Abschnitt A.1.1.3, S. 86). Außerdem funktioniert die HBS-Annotation für Register nicht (Abschnitt 3.3.1.2, S. 30).
KAPITEL 5. ERWEITERUNG VON HDCAML
65
Unverbundene Dangling Signals:
Wenn am Ende des Schaltungsaufbaus unverbundene Dangling Signals übrig bleiben, führt das in HDCaml 0.2.9 zu defekten HBS-Modellen, was man erst bei der nachfolgenden Bearbeitung mit den EDAWerkzeugen bemerkt. Dieser Fehler sollte zum frühest-möglichen Zeitpunkt (das ist der Aufruf von get_
circuit , denn vorher würde ja immer noch die Möglichkeit des Verbindens bestehen) erkannt werden.
5.2.3
Neue Funktionalität
Die Implementierung folgender neuen Funktionen wäre sinnvoll:
FSM (finite state machine):
FSMs werden häufig durch die Verhaltensbeschreibung modelliert. Da es diese in HDCaml nicht gibt, ist
die Modellierung von FSMs besonders umständlich. HDCaml sollte FSMs in Form von Tabellen haben,
wie es sie in Confluence gibt.6
RAM (random access memory):
Man kann ein RAM als RTL-Beschreibung darstellen, allerdings wird es dann von VHDL- und VerilogSynthesizern nicht als RAM erkannt (es ist nicht „inferable“). Das hat zur Folge, dass nicht echtes RAM
dafür verwendet wird, das moderne FPGAs beinhalten, sondern kombinatorische Logik und Register.
Schaltungs-Optimierung:
Manche Operationen können bereits zur Laufzeit („Programmebene“ im Gegensatz zur „HW-Ebene“, s.
Abschnitt 3.2.3, S. 26) des HDCaml-Programms berechnet werden, wenn sie auf Konstanten angewendet
werden (constant propagation bzw. constant folding). So kann z.B. bit (const "101") 1 (Bit 1 des
Bitvektors ”101” selektieren) durch const "0" ersetzt werden.
Durch solche Optimierungen könnte die interne Schaltungsrepräsentation vereinfacht werden, was kleinere generierte HBS-Modelle (VHDL, Verilog, C) zur Folge hätte und damit auch eine schnellere Simulation und Synthese derselben. Auch die OCaml-interne Simulation könnte durch die einfachere Repräsentation
schneller ablaufen.
Komponenten in erzeugten HBS-Modellen:
Momentan erzeugen die VHDL- und Verilog-Generatoren „flache Netzlisten“ („elaborated netlists“), d.h.
falls ein und dieselbe Komponente mehrfach im Modell der Schaltung vorkommt, ist das in den Netzlisten
nicht mehr erkennbar.
Wenn Komponenten in erzeugten HBS-Modellen (z.B. VHDL) als solche erkennbar wären, würde der
resultierende Code besser lesbar sein und die Korrelation zwischen HDCaml-Modell und dem VHDL-Code
wäre leichter herzustellen.
5.2.4
HBS-Generatoren
Einzelne Bits in VHDL:
1 Bit breite Signale werden als std_logic_vector(0 downto 0) statt als std_logic ausgegeben. Das Gleiche gilt für die Indizierung: x(2 downto 2) statt x(2) . Der Verilog-Generator erzeugt
im Gegensatz dazu die kürzeren Formen. Auch im VHDL-Code sollten die kürzeren Formen erzeugt werden.
Zusammenfassung von Prozessen:
Momentan wird (Verilog und VHDL) jedes Register als eigener Prozess in den HBS-Output geschrieben.
Da HDCaml nur synchrone Logik beschreibt, würde ein einziger Prozess für alle Register reichen. Das würde den Output übersichtlicher machen. Diese Änderung könnte auch die Performance von kommerziellen
Simulatoren verbessern, da manche für jeden Prozess einen eigenen Thread erzeugen.
6 http://funhdl.org/wiki/doku.php?id=hdl_comparison#state_machine
KAPITEL 5. ERWEITERUNG VON HDCAML
66
Identischer Code:
Mehrere Funktionen (ca. 50 Zeilen) sind in verilog.ml und vhdl.ml identisch und werden nur in
diesen beiden Modulen verwendet. Sie sollten zusammengefasst und in ein allgemeines Modul ausgelagert
werden.
Listen der Schlüsselwörter:
Die Listen der Schlüsselwörter der jeweiligen Sprachen (VHDL, Verilog, SystemC) gehören nicht in das
Modul Rename , wo sie sich in 0.2.9 befinden (mit Ausnahme der Schlüsselwörter von C, C++ und SystemC, die ganz fehlen), sondern in das Modul der jeweiligen Sprache (z.B. gehört verilog_keywords
in die Datei verilog.ml ).
5.2.5
Sonstiges
Kombinatorische Schleifen:
Solche sind in HDCaml nicht erlaubt, weil sie keine synchrone Logik darstellen. HDCaml 0.2.9 erkennt
kombinatorische Schleifen im Schaltungsmodell beim Schreiben von SystemC-Modellen und bei der eingebauten Simulation,7 eine Erkennung fehlt aber beim Schreiben von VHDL- und Verilog-Netzlisten.
Da Schleifen in HDCaml nur durch Dangling Signals (Abschnitt 3.4.2.1, S. 32) erzeugt werden können,
bietet es sich an, genau dort (beim Verbinden mittels „<==“) auf das Schließen einer Schleife zu prüfen.
Das würde auch dem Benutzer leichter ermöglichen, die an der Schleife beteiligten Signale zu finden.
Multiplexer:
Es wird nicht gewarnt, wenn der Steuer-Eingang mehr Bits als notwendig hat. Diese Warnung sollte ausgegeben werden. Außerdem ist das Verhalten nicht definiert, das auftritt, wenn die Anzahl der auszuwählenden Signale keine Zweierpotenz ist. Das Verhalten sollte klar definiert werden (und bei Bedarf sollten
Warnungen ausgegeben werden).
Unvollständige Funktion:
Die Funktion string_of_signal in Circuit ist unfertig und wird auch nirgends verwendet. Sie
sollte fertiggeschrieben oder entfernt werden.
Dokumentation:
In der Sprachreferenz ( ocamldoc ) ist nicht angegeben, welche Bitbreite das Ergebnis der Multiplikationen hat. Diese Information sollte ergänzt werden.
Shift-Operatoren:
Es existieren die Operatoren >>: , <<: (unsigned shift right/left) und >>+ (signed shift right) aber nicht
<<+ (signed shift left). Man braucht zwar nicht unbedingt einen eigenen Operator für signed shift left, weil
diese Operation mit einem unsigned shift left identisch ist, das sollte aber in der Dokumentation angemerkt
werden. Alternativ könnte man den Operator einführen und (unverändert) auf den nicht vorzeichenbehafteten Operator abbilden.
5.3
Auswahl und Implementierung
Die neue Version von HDCaml, in der ein großer Teil der in Abschnitt 5.2, S. 62 vorgeschlagenen Änderungen durchgeführt wurde, hat die Versionsnummer 0.2.10.8 Der Quelltext zu sämtlichen Änderungen wurde
im Internet veröffentlicht.9
7 Die Erkennung von kombinatorischen Schleifen ist ein Nebenprodukt der Funktion order_signals_sequential , die die
Knoten des Schaltungsmodells in die richtige Reihenfolge für die Abarbeitung der Simulation bringt. Wenn keine solche Reihenfolge
gefunden wird, dann müssen die Signale voneinander abhängig sein, es muss also eine kombinatorische Schleife vorliegen.
8 Die Module von HDCaml 0.2.10 sind in Abschnitt 3.5.2, S. 38 beschrieben.
9 Download und Dokumentation: http://karl-flicker.at
KAPITEL 5. ERWEITERUNG VON HDCAML
67
Zusammenfassung
Tabelle 5.2 listet die Anzahl der Code-Zeilen (lines of code) von HDCaml 0.2.10 auf. Der Code-Umfang
wurde mit 7275 Zeilen (im Vergleich zu 3474 bei HDCaml 0.2.9) ca. verdoppelt.10
Modul
Zeilen
auxiliary.ml
223
circuit.ml
281
design.ml
3048
Interface
auxiliary.mli
Zeilen
Gesamt
61
284
circuit.mli
100
381
design.mli
476
3524
49
562
685
hdl_output.ml
513
hdl_output.mli
release.ml
170
—
simulate.ml
648
simulate.mli
37
170
systemc.ml
794
systemc.mli
101
895
verify.ml
234
verify.mli
83
317
verilog.ml
119
verilog.mli
4
123
vhdl.ml
143
vhdl.mli
26
169
waveform.ml
152
waveform.mli
13
165
950
7275
Summe
6325
Tabelle 5.2: Zeilenanzahl der Module von HDCaml 0.2.10
Dokumentation
Sämtliche von mir gemachten Änderungen an HDCaml wurden ausführlich dokumentiert, sowohl im Code
(ocamldoc)11 als auch im HDCaml-Wiki12
5.3.1
Verbesserte Fehlermeldungen
Die Phase des Schaltungsaufbaus verfügt jetzt über umfassendere und zielführendere (weil mit Zeilennummern versehene) Fehlermeldungen, die den Zielen von Abschnitt 5.2.1, S. 63 entsprechen.
Realisierung von Zeilennummern
Von HDCaml 2.9.0 ausgegebene Fehlermeldungen beinhalten keine Zeilennummern, weil HDCaml durch
die Art seiner Implementierung (Einbettung in OCaml, s. Abschnitt 2.4, S. 13) über die dafür notwendigen
Informationen (wie Dateinamen, Zeilennummern oder Funktionsnamen) nicht verfügt.
Es gibt jedoch einen Workaround, der es ermöglicht, Fehlermeldungen mit Zeilennummern auszugeben.
Dieser funktioniert, indem die HDCaml-Bibliothek beim Auftreten eines Fehlers eine ungefangene Exception wirft,13 welche bei geeigneten Compiler- und Laufzeit-Optionen die Ausgabe eines Stack-Traces
10 Siehe
Fußnote 4, S. 62 zur Bestimmung der Zeilenanzahl.
Sprachreferenz von HDCaml 0.2.0 in Anhang A, S. 85 und auf http://karl-flicker.at
12 Wiki-Seite von HDCaml 0.2.9.1, einer Zwischenversion auf dem Weg zu HDCaml 0.2.10:
http://funhdl.org/wiki/doku.php?id=hdcaml:releases:0.2.9.1
13 Execptions der Sprache OCaml werden z.B. in [4, Kap. 2] erklärt. Sie funktionieren konzeptuell sehr ähnlich wie Exceptions in
C++. Eine Exception wird mit dem Schlüsselwort exception deklariert und mit raise geworfen. Exceptions können gefangen
werden, indem der Code, der sie auslösen kann, von einem try . . . with - Konstrukt umfasst wird. Das Schlüsselwort with wird
von einer Auflistung der möglichen Execptions und den dazugehörenden Fehlerbehandlungen in Form von Pattern-Matching gefolgt.
Wenn eine Exception nicht gefangen (d.h. nicht behandelt) wird, wird sie an die nächsthöhere Ebene der Aufrufhierarchie (call stack)
weitergeleitet. Wenn eine Exception zur obersten Ebene gelangt, ohne gefangen zu werden, bezeichnet man diese als ungefangene
Exception. Eine solche bewirkt einen Programmabbruch.
11 siehe
KAPITEL 5. ERWEITERUNG VON HDCAML
68
bewirkt. Damit das funktioniert, muss ein HDCaml-Programm folgendermaßen übersetzt und ausgeführt
werden (vgl. Abschnitt A.1.1.1, S. 85):
ocamlc -g -o example_circuit.exe hdcaml.cma example_circuit.ml
ocamlrun -b example_circuit.exe
Die Option -g von ocamlc bewirkt, dass Debug-Information in die ausführbare Datei aufgenommen
wird, die für den Stack-Trace notwendig ist. Die Option -b von ocamlrun aktiviert die Ausgabe von
Stack-Traces bei ungefangenen Exceptions.
Dieser Mechanismus erlaubt es, Fehlerursachen exakt zu lokalisieren. Der Nachteil an dieser Lösung ist,
dass immer nur eine Fehlermeldung auf einmal ausgegeben werden kann, weil die ungefangene Exception
zu einer Beendung des Programms führt. Die Fehlerursache muss beseitigt werden, bevor man zur nächsten
Fehlermeldung kommt.
Fehlermeldungen
Listing 5.1 zeigt eine exemplarische Fehlermeldung. Die Zeilen 8 bis 10 zeigen den Stack-Trace, der die
Zeilennummern beinhaltet. Der Ort des Fehlers im HDCaml-Programm des Benutzers wird im Stack-Trace
immer unterhalb der Zeile angezeigt, die auf src/design.ml verweist (hier Zeile 8). Die vorliegende
Fehlermeldung wurde von einer kombinatorischen Schleife in einem HDCaml-Modell verursacht.
1
2
-----------------------------------------------------------------------------qbar <== AND(_,_):
3
4
5
6
7
8
9
10
ERROR: this assignment would close a combinational loop.
Right side signal is dependent on left side signal.
-----------------------------------------------------------------------------Fatal error: exception Design.Design_error
Raised at file "src/design.ml", line 1590, characters 37-49
Called from file "fehler4.ml", line 23, characters 4-22
Called from file "fehler4.ml", line 33, characters 15-56
Listing 5.1: Beispiel für eine Fehlermeldung mit Stack-Trace
Die ersten sechs Seiten der Dokumentation des Moduls Design (Abschnitt A.1, S. 85) beschreiben die
neuen, verbesserten Überprüfungen und Fehlermeldungen. Die Abschnitte behandeln folgende Themen:
Allgemeine Regeln für Überprüfungen (Check Rules) . . . . . . . . . . . . . . . . . . . . Abschnitt A.1.1.2, S. 85
Umbenennung von Signalnamen (Renaming) . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abschnitt A.1.1.3, S. 86
Einstellen der Strenge von Überprüfungen (Configure Strict Checks) . . . . . . . Abschnitt A.1.1.4, S. 87
Ein- u. Ausschalten von Exceptions (Configure Error Reporting) . . . . . . . . . . Abschnitt A.1.1.5, S. 88
Debug-Funktionen (Trace Names and Nodes) . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abschnitt A.1.1.6, S. 89
Diese sind im Anhang ausführlich beschrieben und werden hier nicht wiederholt.
Warnungen
Es gibt die Kategorie INFO von Meldungen, bei der das Programm nicht abgebrochen wird. Sie entspricht
Warnungen in üblichen Programmiersprachen. Es können auch für Warnungen Stack-Traces aktiviert werden, damit ihre Position im Programm leichter festgestellt werden kann, s. Abschnitt A.1.1.5, S. 88.
Es wurde eine große Zahl von neuen Überprüfungen eingeführt, die solche Warnungen erzeugen (s.
Abschnitt A.1.1.2, S. 85 sowie die Beschreibungen zu den Funktionen und Operatoren in der Sprachreferenz
ab Abschnitt A.1.3, S. 90).
Listing 5.2, S. 69 zeigt das Aussehen einer Warnung. Diese informiert den Benutzer darüber, dass er
versucht, einen 8 Bit breiten Bitvektore 10 Bits weit nach links zu verschieben, was zu dem konstanten
Ergebnis ” 00000000 ” führt, das vom Benutzer wahrscheinlich nicht beabsichtigt war.
KAPITEL 5. ERWEITERUNG VON HDCAML
1
2
69
-----------------------------------------------------------------------------INPUT("input_width8") <<: 10:
3
4
5
6
INFO:
shift amount >= width of signal (10 vs. 8).
This results in a signal of 8 zero-bits.
------------------------------------------------------------------------------
Listing 5.2: Beispiel für eine Warnung
Debug-Funktionen
Es wurden zwei solche Funktionen programmiert, die die Fehlersuche vereinfachen: find_name und
find_node , s. Abschnitt A.1.1.6, S. 89. Mit find_node , wird es dem Benutzer ermöglicht, die Position im Quelltext zu ermitteln, an der ein bestimmter Schaltungsknoten definiert wurde. Abbildung 5.2
Abbildung 5.2: Funktionsweise von find_node
illustriert die Zuordnung des Schaltungsknotens 14 ( n_14 ) im erzeugten VHDL-Code zu der Stelle im
HDCaml-Programm ( node-test.ml ), an der er erzeugt wird. Das passiert durch den Aufruf der Funktion find_node mit dem Argument 14 (in Zeile 5). Beim Erreichen dieser Stelle wird eine Exception
geworfen und der resultierende Stack-Trace verweist auf Zeile 15 im HDCaml-Quelltext.
5.3.2
Korrektur fehlerhafter Implementierungen
Es wurden alle gefundenen Fehler beseitigt:
OCaml-interner Simulator:
Die Multiplikationen wurden ergänzt, die weiteren Fehler behoben.
Umbenennung von Signalnamen:
Das Modul Rename wurde ganz entfernt und die Funktionalität (im Modul Design ) gänzlich neu implementiert. Umbenennungen finden jetzt bereits beim Schaltungsaufbau statt. Es werden alle Output-Sprachen
KAPITEL 5. ERWEITERUNG VON HDCAML
70
(C, SystemC, VHDL, Verilog) berücksichtigt. Mit der Neuimplementierung konnte auch der unnötige Umbenennungen betreffende Fehler behoben werden.
C-Output:
Bei der Neuimplementierung der Umbenennungs-Funktionalität (s. voriger Punkt), wurde auch der COutput berücksichtigt (s. Abschnitt A.1.1.3, S. 86). Die von Andy Ray zur Verfügung gestellten Fehlerkorrekturen (mul_u, mul_s, concat, select) wurden in den Code integriert
VHDL-Generator:
Für VHDL-Bezeichner werden jetzt extended identifiers [8, Abschnitt 13.3.2] verwendet,14 aber nicht generell für alle Signale, sondern nur, wenn es notwendig ist (s. Abschnitt A.5.2, S. 96).
Der Fehler bei der HBS-Annotation für Register im VHDL-Generator wurde durch die Fusionierung
der HBS-Generatoren (s. Abschnitt 5.3.4, S. 70) beseitigt.
Unverbundene Dangling Signals:
Es erfolgt eine Fehlermeldung und das Programm wird abgebrochen (s. Dokumentation zu get_circuit
in Abschnitt A.1.3, S. 90)
5.3.3
Neue Funktionalität
Es wurde keiner der Vorschläge von Abschnitt 5.2.3, S. 65 implementiert.
5.3.4
HBS-Generatoren
Es wurden alle vorgeschlagenen Änderungen außer dem Zusammenfassen von Prozessen durchgeführt:
Einzelne Bits in VHDL:
Bits werden jetzt als std_logic deklariert. Die Indizierung von einzelnen Bits erfolgt jetzt mittels
x(index) .
Identischer Code:
Der gemeinsame Code der Module Vhdl und Verilog wurde komplett fusioniert und befindet sich jetzt
im Modul Hdl_output . Die beiden Module sind nur mehr Frontends für diesen allgemeinen Generator.
Die generierten HBS-Modelle (VHDL und Verilog) wurden außerdem durch Kommentare und übersichtlichere Sortierung von Signalen besser lesbar gemacht.
Listen der Schlüsselwörter:
Die Liste der Schlüsselwörter der verschiedenen Sprachen befindet sich jetzt in den Modulen der jeweiligen
Code-Generatoren: Verilog : Verilog; Vhdl : VHDL; Systemc : C, C++, SystemC
5.3.5
Sonstiges
Kombinatorische Schleifen:
Diese werden jetzt bereits beim Schaltungsaufbau (beim Aufruf des Operators „ <== “) detektiert und als
Fehler (mit Zeilennummer) gemeldet.
Multiplexer: Es werden jetzt verschiedenste Bedingungen überprüft und Warnungen und Fehlermeldungen
ausgegeben. Die Liste der Elemente wird immer auf die Länge einer Zweierpotenz ergänzt, bei Bedarf wird
das letzte Element wiederholt. Siehe Abschnitt A.1.13, S. 94.
14 Extended identifiers wurden in VHDL’93 eingeführt und erlauben praktisch beliebige Bezeichner. Diese werden zwischen Backslashes eingeschlossen, z.B. \ counter_ _next\.
KAPITEL 5. ERWEITERUNG VON HDCAML
71
Unvollständige Funktion:
string_of_signal (Modul Circuit ) wurde fertiggestellt (Entfernen hätte Kompatibilitätsprobleme verursachen können).
Dokumentation:
Ergebnis-Bitbreite der Multiplikationen ( (width a)+(width b) ) wurde ergänzt.
Shift-Operatoren:
<<+ (signed shift left) wurde als Kopie von <<: (unsigned shift left) ergänzt.
5.4
5.4.1
Evaluierung der HDCaml-Erweiterungen
Kriterien und Änderungen
Laut Abschnitt 2.7.1, S. 19 sind auf die Änderungen an HDCaml folgende Kriterien anzuwenden:
1. Funktionalität: Wurde die Funktionalität der Sprache erweitert?
2. Korrektheit: Konnten Fehlfunktionen behoben werden?
3. Benutzerfreundlichkeit: Ist HDCaml benutzerfreundlicher als vorher? Funktioniert die Interaktion
(bzw. Kommunikation) zwischen HDCaml und dem Benutzer besser als vorher?
4. Aufwand für Modellierung: Können durch die Änderungen an HDCaml jetzt Hardware-Modelle
kompakter als vorher formuliert werden?
5. Codequalität: Ist der Code der Bibliothek durch die Änderungen lesbarer, korrekter, homogener?
6. Dokumentation: Ist die Qualität der Dokumentation in der neuen Version besser?
7. Erweiterbarkeit: Ist HDCml durch die Änderungen einfacher zu erweitern?
8. Meinungen: Was sagen andere Benutzer der neueren Version darüber?
Die an HDCaml durchgeführten Änderungen sind in folgender Aufzählung zusammengefasst:
1. Bessere Erkennung von Fehlern und Design-Rule-Violations
•
•
•
•
2.
3.
4.
5.
6.
7.
8.
9.
10.
5.4.2
Striktere Fehlerkriterien (für Teilmenge von Funkt./Op. konfigurierbar)
Unbenutzte Inputs oder Schaltungsknoten
Erkennung von kombinatorischen Schleifen (beim Schließen derselben mit <==)
Nicht definierte Schaltungsknoten (unverbundene Dangling Signals)
Informativere und zielführender Fehlermeldungen, mit Angabe von Zeilennummern
Fehler immer zum frühest möglichen Zeitpunkt erkennen und melden
Neue, nützliche Debug-Funktionen ( find_name , find_node )
Umbenennung von Signalnamen (und Hinweis an Benutzer) bereits beim Schaltungsaufbau
Neue Warnungen für wahrscheinlich ungewollte Kombinationen von Funktionsargumenten
Fehlerbeseitigungen (allgemein und speziell im OCaml-internen Simulator)
Code umstrukturiert (Module entfernt, dazugegeben, etc.)
Gemeinsamen Code der Generatoren für Verilog und VHDL zusammengefasst
Dokumentation ergänzt (im Code und im Wiki)
Evaluierung der Änderungen
Die bei den Kriterien 1 bis 7 gestellten Fragen können mit „ja“ oder „nein“ beantwortet werden (für den
Punkt Meinungen trifft das nicht zu). Die Objektivität der Kriterien reicht von objektiv (Punkte 1, 2, 4) über
relativ objektiv (3, 6, 7) bis hin zu subjektiv (5). Meine Antworten zu den Fragen sind in Tabelle 5.3, S. 72
zusammengefasst, wobei auch Rückmeldungen von Kollegen (s. Abschnitt 5.4.2.1, S. 72) mit einbezogen
wurden.
KAPITEL 5. ERWEITERUNG VON HDCAML
72
Zutreffend auf Änderung
Kriterium
1
2
3
4
5
6
7
8
9
10
1. Funktionalität:
3
3
3
3
3
3
—
—
—
—
2. Korrektheit:
3a
—
—
—
3b
—
3
3c
3d
—
e
3. Benutzerfreundlichkeit:
4. Aufwand für Modellierung:
3
—
3
—
3
—
3
—
3
—
3
—
3
—
—
—
—
—
3f
—
5. Codequalität:
6. Dokumentation:
—
3
—
3
—
3
—
3
—
3
—
3
3
3
3
—
3
—
3g
3
7. Erweiterbarkeit:
3h
—
—
—
3i
—
3j
3
3k
3
Anmerkungen:
a In Version 0.2.9 wurden fehlerhafte HBS-Modell geschrieben, wenn unverbundene Dangling Signals existierten. Das kann jetzt
nicht mehr vorkommen.
b In Version 0.2.9 wurden manche Signalnamen grundlos umbenannt. In der neuen Version ist das korrigiert, außerdem wird der
Benutzer über die genauen Gründe aller Umbenennungen informiert.
c Die Entfernung des Moduls Rename und die Neuimplementierung der Funktionalität im Modul Design beseitigte einen
Fehler bei den Umbenennungen (s. Anmerkung b).
d In Version 0.2.9 war der Code des Verilog- und des VHDL-Generators komplett getrennt implementiert. Im erzeugten VHDLModell funktionierte die HBS-Annotation für Register nicht. Das wurde durch die Zusammenfassung des Codes korrigiert.
e Es ist benutzerfreundlicher, wenn alles funktioniert wie erwartet.
f Eine bessere Dokumentation (z.B. Sprachreferenz) steigert die Benutzerfreundlichkeit.
g Eine bessere Dokumentation des Codes steigert die Codequalität.
h Durch die strikteren Überprüfungen bekommt ein neuer Output-Generator mit Sicherheit ein korrektes Schaltungsmodell übergeben (d.h. eines mit korrekten Bitbreiten und ohne unverbundene Dangling Signals).
i Für eine neue Output-Sprache braucht man nur die Liste ihrer reservierten Wörter zu registrieren. Zur Vermeidung von ungültigen Signalnamen in dieser Sprache ist damit alles erledigt.
j Ein fehlerfreier Code ist leichter erweiterbar, da man auf die vorhandene Funktionalität vertrauen und aufbauen kann.
k Ein neuer Code-Generator für eine Sprache, die Ähnlichkeit mit VHDL oder Verilog hat, könnte jetzt in sehr wenigen Zeilen
Code implementiert werden.
Tabelle 5.3: Übersicht: auf Änderungen zutreffende Kriterien
Auffallend an Tabelle 5.3 ist, dass Kriterium 4 von keiner der Änderungen erfüllt wird. Das heißt, es
wurden keine neuen Sprachelemente eingeführt oder Bibliotheksfunktionen geschrieben, die die es ermöglichen, Hardware mit weniger Code zu modellieren.15
Auf sehr viele Änderungen trafen die Kriterien 1 (Funktionalität), 2 (Korrektheit), 3 (Benutzerfreundlichkeit), 6 (Dokumentation) und 7 (Erweiterbarkeit) zu. Die Zielsetzung (Abschnitt 1.2, S. 2), HDCaml anhand des Themenbereichs Einfachheit der Benutzung auf Erweiterbarkeit bzw. Wartbarkeit zu testen wurde
also voll erfüllt.
5.4.2.1
Meinungen
Die Erweiterungen von HDCaml, insbesondere Änderungen 1 bis 7 und 10 wurden von Manfred Mücke
und Daniel Sánchez Parcerisa verwendet und positiv bewertet. Andy Ray (der Autor von HDFS [29], s.
Abschnitt 4.5, S. 56), mit dem die Änderungen per Mail diskutiert wurden, begrüßte die nachvollziehbaren
Umbenennungen (Änderung Nr. 5).16
15 Die einzige Ausnahme ist die neue Funktion concat , die eine Liste von Bitvektoren zu einem einzigen Bitvektor zusammenfügt.
Eine Anwendung dieser Funktion zeigt Listing 4.13, S. 51.
16 Zitat aus seiner diesbezüglichen Mail:
”Your fixes for input, output and (especially) signal names are a godsend. Does that actually mean signal renaming in verilog.ml is no
longer required? Anyway, I had just hit a problem with systemc.ml where multiple internal wires with the same name were clashing
and couldn’t easily get round it.”
KAPITEL 5. ERWEITERUNG VON HDCAML
5.5
73
Implementierung eines Faltungsfilters
Als praktisches Beispiel für ein HDCaml-Hardwaremodell wurde ein Faltungsfilter implementiert, zur Problemstellung siehe Abschnitt 2.6.2, S. 17.
5.5.1
Hardware-Modell
Es wurden zwei Versionen des Filters modelliert:
• Minimalversion
• Version mit BV-Bus, Randbehandlung und Sättigung der Ergebniswerte
Die Minimalversion des Faltungsfilters (Listing 5.3, S. 74) entspricht von der Funktionalität her exakt dem
Schema in Abbildung 2.5, S. 19 sowie der Sassy-Implementierung (Listing 5.6, S. 77) in Abschnitt 5.6.2.
Die Funktionalität der umfangreicheren Version ist mit der in [22] implementierten Confluence-Version
des Filters identisch und wurde in HDCaml nachgebildet um einen Vergleich zwischen den beiden Sprachen
zu ermöglichen.17 Sie unterscheidet sich in folgenden drei Punkten von der Minimalversion:
• Anbindung an den BV-Bus (s. [22, Kap. 4.2.2]),
• Spezialbehandlung für Pixel, die am Rand des Bildes liegen und
• Sättigung der Ergebniswerte.
Der BV-Bus ist ein on-chip Bus, der speziell zum Transport von Bilddaten entworfen wurde, siehe [22] für
mehr Informationen dazu. Die Randbehandlung von Pixeln ist notwendig, weil Teile des Kernels außerhalb
des Eingabe-Bildes liegen, wenn sich der Kernel ganz am Rand (oder in der Ecke) davon befindet. Im vorliegenden Beispiel (sowohl HDCaml- als auch Confluence-Version) werden die Pixelwerte außerhalb des
Eingabe-Bildes als Null interpretiert.18 Die Sättigung der Pixelwerte des Ergebnis-Bildes ist notwendig,
wenn dieses den gleichen Wertebereich wie das Eingabe-Bild haben soll. In den vorliegenden Implementierungen sollen sowohl Eingabe- als auch Ausgabe-Bild 8 Bit-Werte enthalten. Die Zwischenergebnisse der
aufsummierten Produkte können den gesamten Wertebereich der dafür zur Verfügung stehenden, vorzeichenbehafteten 16 Bit-Zahl einnehmen. Um wieder auf nicht vorzeichenbehaftete 8 Bit-Zahlen zu kommen,
werden Werte, die kleiner als 0 sind auf 0 und Werte, die größer als 255 sind auf 255 gesetzt.19
5.5.2
Testbench
Listing 5.4, S. 74 zeigt die Testbench für das C-Modell, das aus dem minimalen HDCaml-Modell des Faltungsfilters (Listing 5.3, S. 74) erzeugt wird. Abbildung 5.3, S. 75 visualisiert die Zusammenhänge. Die
notwendigen Befehle zum Erzeugen der ausführbaren Testbench können in Abschnitt 3.3.1.3, S. 30 nachgelesen werden. Die nicht abgebildeten Zeilen 38 bis 105 beinhalten die Funktionen read_image und
write_image zum Lesen und Schreiben von Bildern im PGM-Format (portable graymap).20 Dieses
Format wurde gewählt, weil es besonders einfach aufgebaut ist.
5.6
Evaluierung des Faltungsfilters
Die folgenden vier Abschnitte entsprechen den vier Kriterien für die Evaluierung der Ergebnisse zum Themenbereich Bildverarbeitung von Abschnitt 2.7.2, S. 19.
17 Das
Listing der ausführlichen Version ist hier nicht abgedruckt, weil es 5 Seiten einnehmen würde.
Fehlen der Randbehandlung in der Minimalversion des Filters hat zur Folge, dass bei einem 3x3 Pixel großem Kernel der 1
Pixel breite Rand des Ergebnisbildes undefinierte Werte enthält.
19 Die Minimalversion erzeugt Ergebnis-Bilder mit den 16 Bit-Zwischenergebnissen.
20 Definition und Standard-Implementierung im Netpbm-Projekt: http://netpbm.sourceforge.net
18 Das
42
41
40
39
38
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
Listing 5.3: Minimal-Implementierung des Faltungsfilters in HDCaml
t e s t _ f i l t e r 512 3 ; ;
l e t t e s t _ f i l t e r pic_width kernel_width =
start_circuit " minimal_filter " ;
l e t k e r n e l = L i s t . map c o n s t
[ " 11111111 " ; " 11111111 " ; " 11111111 " ;
" 11111111 " ; " 00001001 " ; " 11111111 " ;
" 11111111 " ; " 11111111 " ; " 11111111 " ] i n
l e t in_dat = input " in_dat " 8 in
l e t f i l t e r e d = f i l t e r pic_width kernel_width in_dat kernel in
output " out_dat " f i l t e r e d ;
l e t c i r c u i t = g e t _ c i r c u i t ( ) in
Systemc . output_model c i r c u i t ;
;;
l e t f i l t e r pic_width kernel_width data_in kernel =
l e t chain = data_chain pic_width kernel_width data_in in
l e t p r o d = L i s t . map2 ( ∗+ ) k e r n e l c h a i n i n
l e t sum = b i n _ t r e e ( + : ) p r o d i n
r e g h i g h ( s e l e c t sum 7 0 )
;;
l e t data_chain pic_width kernel_width data_in =
l e t chain = delay (2∗ pic_width + k e r n e l _ w i d t h ) d a t a _ i n in
l e t c h a i n _ a r r = o f _ l i s t chain in
( t o _ l i s t ( sub c h a i n _ a r r (0∗ p i c _ w i d t h ) k e r n e l _ w i d t h ) ) @
( t o _ l i s t ( sub c h a i n _ a r r (1∗ p i c _ w i d t h ) k e r n e l _ w i d t h ) ) @
( t o _ l i s t ( sub c h a i n _ a r r (2∗ p i c _ w i d t h ) k e r n e l _ w i d t h ) )
;;
l e t rec delay reg_count d a t a _ i n =
match r e g _ c o u n t w i t h
0 −> [ d a t a _ i n ]
| n −> l e t c u r r _ r e g = r e g h i g h d a t a _ i n i n
c u r r _ r e g : : ( d e l a y ( r e g _ c o u n t −1) c u r r _ r e g )
;;
open A r r a y ; ;
open P r i n t f ; ;
open Hdcaml ; ;
open D e s i g n ; ;
120
119
118
117
116
115
114
113
112
111
110
109
108
107
106
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
/ ∗ c a l c u l a t i o n f o r 3 x3 k e r n e l s ∗ /
}
++ o u t _ p o s ;
d e l e t e _ s i m u l a t o r ( sim ) ;
}
}
}
−−d e l a y _ c o u n t ;
i f ( d e l a y _ c o u n t <= 0 )
{
i f ( out_pos < ( width∗ h e i g h t ) )
c [ o u t _ p o s ] = sim−> s i g n a l s . m i n i m a l _ f i l t e r . o u t _ d a t [ 0 ] ;
f o r ( i m g _ i d x = 0 ; i m g _ i d x < 2 ; i m g _ i d x ++)
{
f o r ( j = 0 ; j < h e i g h t ; j ++)
{
f o r ( i = 0 ; i < w i d t h ; i ++)
{
sim−> s i g n a l s . m i n i m a l _ f i l t e r . i n _ d a t [ 0 ] = a [ i + j ∗WIDTH ] ;
c y c l e _ s i m u l a t o r ( sim ) ;
}
Listing 5.4: C-Testbench für Faltungsfilter (Minimalversion)
return ( 0 ) ;
f r e e ( image ) ;
free ( result );
r e a d _ i m a g e ( " t e s t f i l e . pgm " , image ) ;
r u n _ s i m u l a t i o n ( image , r e s u l t , HEIGHT , WIDTH ) ;
w r i t e _ i m a g e ( r e s u l t , " r e s u l t . pgm " ) ;
i n t main ( v o i d )
{
i n t ∗ r e s u l t = ( i n t ∗) m a l l o c ( s i z e o f ( i n t )∗HEIGHT∗WIDTH ) ;
i n t ∗ image = ( i n t ∗) m a l l o c ( s i z e o f ( i n t )∗HEIGHT∗WIDTH ) ;
}
i, j;
img_idx , o u t _ p o s = 0 ;
delay_count = width +4;
r u n _ s i m u l a t i o n ( i n t ∗a , i n t ∗c , i n t h e i g h t , i n t w i d t h )
s i m u l a t o r _ t sim = n e w _ s i m u l a t o r ( ) ;
i n i t _ s i m u l a t o r ( sim ) ;
void
{
int
int
int
# d e f i n e WIDTH 512
# d e f i n e HEIGHT 512
# include " minimal_filter . h"
KAPITEL 5. ERWEITERUNG VON HDCAML
74
KAPITEL 5. ERWEITERUNG VON HDCAML
5.6.1
75
Korrektheit
Um die Korrektheit der HDCaml-Implementierung des Faltungsfilters zu zeigen, wurde das selbe Filter in
MATLAB implementiert, s. Listing 5.5. Die Abbildung 5.4, S. 76 zeigt, wie die Korrektheit der HDCaml1
k = d o u b l e ( [ [ −1 −1 −1 ] ; [ −1
9 −1 ] ; [ −1 −1 −1 ] ] ) ;
2
3
4
I = d o u b l e ( i m r e a d ( ’ t e s t f i l e . pgm ’ ) ) ;
R = filter2 (k , I );
5
6
7
8
9
10
11
12
13
14
15
for i = 1:512
for j = 1:512
i f R ( i , j ) > 255
R( i , j ) = 255;
end
i f R( i , j ) < 0
R( i , j ) = 0 ;
end
end
end
16
17
18
Rui8 = u i n t 8 ( R ) ;
i m w r i t e ( Rui8 , ’ m a t l a b . pgm ’ , ’pgm ’ , ’ E n c o d i n g ’ , ’ ASCII ’ ) ;
Listing 5.5: Faltungsfilter in MATLAB
Implementierung (s. Abbildung 5.3) durch den Vergleich der Resultate mit jenen von MATLAB überprüft
wurde. Die Ergebnisse waren (bis auf Randbereiche) identisch.
Außerdem wurden die Ergebnisse der Confluence-Implementierung mit denjenigen der HDCaml-Implementierung verglichen. Die Testbench für das aus Confluence generierte C-Modell unterscheidet sich nur
in zwei Details (Aufbau des Datentyps simulator_t ; Funktion calc_simulator statt cycle_
simulator ) von Listing 5.4, S. 74. Die Ergebnisse dieser beiden Implementierungen stimmten zu 100%
überein, auch in den Randbereichen, da die identische Randbehandlung implementiert wurde.
Abbildung 5.5, S. 76 demonstriert die Plausibilität des Ergebnisses grafisch. Der 3x3-Filterkernel mit
den Koeffizienten von -1 am Rand und 9 in der Mitte, wie er in allen Implementierungen verwendet wird,
stellt einen Hochpass dar, muss also schärfend wirken. Das Ergebnisbild der Simulation entspricht den
Erwartungen.
Filter in HDCaml
Code generieren
C-Testbench
C-Model des Filters
Kompilieren / Linken
ausführbares Filter
Abbildung 5.3: Erzeugen einer ausführbaren Simulation des Faltungsfilters
KAPITEL 5. ERWEITERUNG VON HDCAML
76
Bild
Mit C-Model filtern
gefiltertes Bild (C)
Mit MATLAB filtern
gefiltertes Bild (MATLAB)
Vergleichen
Abbildung 5.4: Vergleich der Filterergebnisse
(a) Originalbild
(b) Hochpass-gefiltertes Bild
Abbildung 5.5: Vergleich Originalbild und Ergebnisbild nach Hochpass-Filterung
5.6.2
Vergleich mit Spezialsprachen wie Sassy
Listing 5.6, S. 77 zeigt die Sassy-Implementierung des Faltungsfilters (Minimalversion). In Zeile 4 wird
ein weiterer Loop-Generator (s. Abschnitt 4.7.1.1, S. 59) der Sprache Sassy verwendet, nämlich der for
window -Iterator. Dieser führt genau jene Bewegung eines Kernels (oder Windows) über ein Array durch,
die für ein Faltungsfilter benötigt wird (vgl. Abbildung 2.4, S. 18).
Die Implementierung in Sassy ist wesentlich kürzer, übersichtlicher und eleganter als die HDCamlVersion (s. auch Abschnitt 4.8, S. 60). In Abschnitt 6.2, S. 81 wird diskutiert, wie man Sassy-ähnliche Fähigkeiten in HDCaml implementieren könnte.
(Das Ergebnis der Sassy-Implementierung kann nicht mit denjenigen der anderen Implementierungen –
HDCaml, Confluence, MATLAB – verglichen werden, weil Sassy nicht öffentlich verfügbar ist.)
KAPITEL 5. ERWEITERUNG VON HDCAML
1
2
3
4
5
6
7
8
9
77
i n t 1 6 [ : , : ] f i l t e r _ f u n c t i o n ( u i n t 8 Image [ : , : ] ) {
i n t 8 K[ 3 , 3 ] = { { −1, −1, −1}, { −1, 9 , −1}, { −1, −1, −1} } ;
int16 Result [ : , : ] =
f o r window W[ 3 , 3 ] i n Image {
int16 v =
for k in K dot w in W {
} r e t u r n ( sum ( k∗w) ) ;
} return ( array ( v ) ) ;
} return ( Result ) ;
Listing 5.6: Faltungsfilter in Sassy
5.6.3
Unterschiede im Aufwand
Tabelle 5.4 zeigt die Zeilenanzahl der verschiedenen Implementierungen des Faltungsfilters. Die Angabe
von Code-Zeilen (lines of code) ist keine exakte Maßzahl, sondern kann nur einen ungefähren Eindruck der
Komplexität eines Programms geben.
Die vollständigen (inkl. BV-Bus, Randbehandlung und Sättigung) Versionen in HDCaml und Confluence unterscheiden sich nicht wesentlich in der Zeilenanzahl: beide haben knapp 300 Zeilen Code. Da man
in HDCaml und Confluence Hardware gleich beschreibt (RTL-Beschreibung) und beide Sprachen ähnliche
funktionale Eigenschaften haben, ist das kein unerwartetes Ergebnis.
Sprache
Version
MATLAB
Minimal
Zeilen
2
Sassy
Minimal
9
HDCaml
Minimal
36
VHDL
Minimal
54
HDCaml
Mit BV-Bus, Randbehandlung und Sättigung
268
Confluence
Mit BV-Bus, Randbehandlung und Sättigung
296
Tabelle 5.4: Zeilenanzahl der verschiedenen Implementierungen des Faltungsfilters
Interessanter wird es beim Vergleich der Minimalversionen des Filters in MATLAB, Sassy und HDCaml. Dabei werden die Zeilen Code im MATLAB-Programm (Listing 5.5, S. 75) nicht gerechnet, die zum
Lesen und Schreiben der Bilder und zur Sättigung der Werte dienen, da diese Funktionalität im HDCaml(Listing 5.3, S. 74) und Sassy-Programm (Listing 5.6, S. 77) auch nicht enthalten ist. Damit bleiben im
MATLAB-Programm nur die Zeilen 1 und 4 also 2 Zeilen.
Die nächstkürzeste Implementierung ist mit 9 Zeilen Code diejenige in Sassy. Die HDCaml-Implementierung (Listing 5.3, S. 74) ist mit 36 Zeilen (Leerzeilen wurden nicht gezählt) 4 mal so lang wie diejenige
in Sassy (und außerdem bei weitem nicht so einfach zu lesen bwz. zu schreiben).
Die VHDL-Implementierung (Listing 5.7, S. 78) ist mit 54 Zeilen um 50% länger als die HDCamlImplementierung. Bei diesem kurzen Beispiel konnte in HDCaml keine wesentlich kompaktere Formulierung als in VHDL erzielt werden. Was dennoch auffällt, sind die vielen Typ- und Konstanten-Deklarationen,
die notwendig sind, um das VHDL-Modell einigermaßen wartbar und flexibel zu gestalten.
Qualitative Unterschiede gibt es zwischen den Sprachen, in denen eine High-Level- bzw. Verhaltungsbeschreibungen möglich ist (MATLAB und Sassy) einerseits und den RTL-Beschreibungssprachen HDCaml und Confluence andererseits. Wesentliche quantiative Unterschiede gibt es nur dort, wo bereits qualitative Unterschiede existieren. Die qualitativ gleichwertigen Sprachen Confluence und HDCaml unterscheiden sich nicht wesentlich.
KAPITEL 5. ERWEITERUNG VON HDCAML
1
2
78
library ieee ;
use i e e e . n u m e r i c _ s t d . a l l ;
3
4
5
6
7
package t y p e s _ c o n s t a n t s
c o n s t a n t window_width
constant pixel_width
constant image_width
is
: i n t e g e r := 3;
: i n t e g e r := 8;
: i n t e g e r := 512;
8
9
10
c o n s t a n t max_window_index : i n t e g e r : = ( window_width∗window_width ) −1;
c o n s t a n t c h a i n _ l e n g t h : i n t e g e r : = ( ( window_width −1)∗ i m a g e _ w i d t h ) + window_width ;
11
12
13
subtype p i x e l _ t
i s s i g n e d ( ( p i x e l _ w i d t h −1) downto 0 ) ;
s u b t y p e r e s _ p i x e l _ t i s s i g n e d ( ( ( 2 ∗ p i x e l _ w i d t h ) −1) downto 0 ) ;
14
15
16
17
type p i x e l _ v e c t o r
i s a r r a y ( n a t u r a l range < >) o f p i x e l _ t ;
t y p e r e s _ p i x e l _ v e c t o r i s a r r a y ( n a t u r a l range < >) o f r e s _ p i x e l _ t ;
end t y p e s _ c o n s t a n t s ;
18
19
20
21
22
library ieee ;
use i e e e . s t d _ l o g i c _ 1 1 6 4 . a l l ;
use i e e e . n u m e r i c _ s t d . a l l ;
u s e work . t y p e s _ c o n s t a n t s . a l l ;
23
24
25
26
27
28
29
e n t i t y conv3x3 i s
port ( r e s e t :
clock :
data_in :
data_out :
end ;
in s t d _ l o g i c ;
in s t d _ l o g i c ;
in p i x e l _ t ;
out p i x e l _ t
);
30
31
32
33
34
35
a r c h i t e c t u r e r t l o f conv3x3 i s
c o n s t a n t k e r n e l : p i x e l _ v e c t o r ( max_window_index downto 0 ) : =
( " 11111111 " , " 11111111 " , " 11111111 " ,
−−
−1, −1, −1
" 11111111 " , " 00001001 " , " 11111111 " ,
−−
−1, 9 , −1
" 11111111 " , " 11111111 " , " 11111111 " ) ; −−
−1, −1, −1
36
37
38
signal chain
: p i x e l _ v e c t o r ( c h a i n _ l e n g t h −1 downto 0 ) ;
s i g n a l o v e r l a p _ a r e a : p i x e l _ v e c t o r ( max_window_index downto 0 ) ;
39
40
41
42
43
44
45
signal mult_res
:
s i g n a l sum
:
begin
o v e r l a p _ a r e a <= c h a i n (
chain (
chain (
r e s _ p i x e l _ v e c t o r ( max_window_index downto 0 ) ;
res_pixel_t ;
(2∗ i m a g e _ w i d t h ) + window_width −1 downto 2∗ i m a g e _ w i d t h ) &
(1∗ i m a g e _ w i d t h ) + window_width −1 downto 1∗ i m a g e _ w i d t h ) &
(0∗ i m a g e _ w i d t h ) + window_width −1 downto 0∗ i m a g e _ w i d t h ) ;
46
47
48
49
50
multipliers :
f o r i i n max_window_index downto 0 g e n e r a t e
m u l t _ r e s ( i ) <= k e r n e l ( i ) ∗ o v e r l a p _ a r e a ( i ) ;
end g e n e r a t e ;
51
52
53
54
sum <= ( ( ( m u l t _ r e s ( 8 ) + m u l t _ r e s ( 7 ) ) + ( m u l t _ r e s ( 6 ) + m u l t _ r e s ( 5 ) ) ) +
(( mult_res (4) + mult_res (3)) + ( mult_res (2) + mult_res ( 1 ) ) ) ) +
mult_res ( 0 ) ;
55
56
57
58
59
60
61
62
63
64
65
66
process ( clock , r e s e t )
begin
i f ( r e s e t = ’1 ’) then
c h a i n <= ( o t h e r s => ( o t h e r s = > ’ 0 ’ ) ) ;
d a t a _ o u t <= ( o t h e r s = > ’ 0 ’ ) ;
e l s i f r i s i n g _ e d g e ( c l o c k ) then
c h a i n <= d a t a _ i n & c h a i n ( c h a i n _ l e n g t h −1 downto 1 ) ;
d a t a _ o u t <= sum ( p i x e l _ w i d t h −1 downto 0 ) ;
end i f ;
end p r o c e s s ;
end ;
−− s h i f t r e g
−− o u t p u t r e g
Listing 5.7: Faltungsfilter in VHDL
KAPITEL 5. ERWEITERUNG VON HDCAML
5.6.4
79
Simulation und Synthese
Tabelle 5.5 zeigt die Ergebnisse der VHDL-Codegenerierung und Synthese. Es wurde die Software „Quartus II“ von Altera in der Version 6.0 SP 1 verwendet, um eine Netzliste für das Device EP2C20 der FPGAFamilie „Cyclone II“ zu erzeugen. Die ersten drei Spalten nach der Bezeichnung des Modells zeigen die
Daten zur VHDL-Codegenerierung. Spalte 2 („Dauer“) gibt die Zeit an, die notwendig ist um aus dem
Quellcode (HDCaml oder Confluence) die VHDL-Netzliste zu erzeugen,21 die nächsten beiden Spalten
deren Größe.
Die Gruppe der nächsten vier Spalten zeigt die Ergebnisse der Synthese. Die dafür benötigte Zeit scheint
zum Teil linear mit der Codegröße der „flachen“ Netzliste (elaborated netlist) zu wachsen.22 Die Anzahl
der benötigten Logic Elements ist bei den vollständigen bzw. minimalen Modellen jeweils ziemlich oder
ganz gleich. Die Multiplikation mit den konstanten Koeffizienten -1 und 9 (s. z.B. Zeilen 33 bis 35 von
Listing 5.7, S. 78) wurde von Quartus in der Mehrzahl der Fälle in einfache arithmetische Operationen umgewandelt.23 Die Gründe für die relativ unterschiedlichen Maximalfrequenzen (bei gleicher Funktionalität)
sind nicht bekannt.
VHDL-Codegenerierung
Modell
Dauer
Confluence
HDCaml
HDCaml (minimal)
VHDL (minimal)
FPGA-Synthese (Quartus II)
Ressourcen
fmax
L.E.
×
[MHz]
1.230
10.586
17
55,2
1.045.201
231.631
462
352
10.501
8.254
0
0
65,2
98,8
—
345
8.254
0
87,2
Dateigröße
Dauer
[s]
[Zeilen]
[Bytes]
[s]
5.128
88.678
2.853.742
7
2
10.396
2.111
—
—
L.E. . . . Logic Elements
×. . . Eingebettete 9-bit-Multiplizierer.
Tabelle 5.5: VHDL-Codegenerierung und FPGA-Synthese für verschiedene HW-Modelle (mit Reset)
Die Experimente zeigen, dass Quartus in der Lage ist, bei der Synthese Speicher (statt Register) für die
Schiebregister zu verwenden (RAM inference). Diese Ableitung erfolgt nur, wenn kein explizites ResetSignal für die Register verwendet wird. Tabelle 5.6, S. 80 zeigt die Ergebnisse der Code-Generierung und
Synthese für HW-Modelle ohne explizites Reset-Signal.24 Es kann beobachtet werden, dass die Synthese
trotz nahezu identischer Modelle generell deutlich schneller (zwischen 2,6 und 5,7 mal so schnell) als bei
den Varianten mit Reset-Signal ist. Die Verwendung von RAM bewirkt eine wesentlich effizientere Ausnutzung der FPGA-Ressourcen, denn 10.174 Memory Bits sind nur 4% von insgesamt 239.616 verfügbaren
Bits, während bei den Varianten mit Reset-Signal (Tabelle 5.5) 10.586 Logic Elements 56% der insgesamt
18.752 verfügbaren L.E. ausmachen. Es könnten also Filter mit wesentlich größeren Kernels oder mit mehreren kaskadierten Filterstufen auf dem FPGA implementiert werden. Ein weiterer Vorteil der Verwendung
von FPGA-RAM ist die wesentlich schnellere Simulation in Quartus, siehe Tabelle 5.7, S. 80.
Zusammengefasst kann gesagt werden, dass die von HDCaml generierten Netzlisten eine schnelle Synthese, eine effiziente Nutzung von FPGA-Ressourcen und hohe Maximalfrequenzen der FPGAs ermöglichen.
21 Die Dauer für die Erzeugung des VHDL-Codes aus dem Confluence-Modell beträgt 5.128 Sekunden, also knapp 86 Minuten.
Davon entfallen 21 Minuten auf die Erzeugung der FNF-Netzliste und der Rest auf die Erzeugung von VHDL-Code aus der FNFNetzliste s. Abschnitt 4.4, S. 52 ff.
22 S. Tabelle 5.5: die (in Bytes) ca. 2,7 mal so große Confluence-generierte VHDL-Netzliste für das volle Modell (mit BV-Bus,
Randbehandlung und Sättigung) braucht ca. 2,7 mal so lange wie die HDCaml-generiert Netzliste.
23 D.h. sie wurden durch Additionen und Subtraktionen ersetzt. Lediglich im Confluence-generierten Modell war das nicht der Fall,
dieses benötigt sogar 17 (statt 9) Multiplizierer.
24 Im Confluence- und VHDL-Modell kann die Verwendung (oder Nichtverwendung) eines Reset-Inputs direkt modelliert werden.
HDcaml erzeugt bei der Codegenerierung ein implizites Reset-Signal. Dieses kann aber zu Testzwecken leicht durch Verändern zweier
Zeilen in der generierten VHDL-Netzliste von einem Input zu einem konstanten Signal geändert werden, das immer logisch Null ist.
KAPITEL 5. ERWEITERUNG VON HDCAML
80
VHDL-Codegenerierung
Modell
Dauer
Confluence
FPGA-Synthese (Quartus II)
Dateigröße
FPGA-Ressourcen
fmax
M.B.
×
[MHz]
392
10.174
17
50,9
156
133
348
108
10.156
8.128
0
0
61,6
94,9
129
108
8.128
0
87,2
Dauer
[s]
[Zeilen]
[Bytes]
[s]
L.E.
4.678
88.673
2.832.997
215
7
2
10.396
2.111
1.045.201
231.631
—
—
—
HDCaml
HDCaml (minimal)
VHDL (minimal)
L.E. . . . Logic Elements
M.B. . . . Memory Bits
×. . . 9-bit-Multiplizierer.
Tabelle 5.6: VHDL-Codegenerierung und FPGA-Synthese für verschiedene HW-Modelle (ohne Reset)
Simulation
Tabelle 5.7 zeigt die (nach Geschwindigkeit sortierten) Ergebnisse verschiedener Simulationen des minimalen Faltungsfilters. Die Simulationszeiten gelten für die Filterung eines Bildes mit den Dimensionen 512 x 512 Pixel auf einer 900 MHz-CPU (AMD Duron) unter Cygwin mit gcc 3.4.4. Die beiden CQuelldateien wurden mit der Option -O3 kompiliert, die HDCaml-interne Simulation mit ocamlopt
(native-code compiler) der Version 3.08.1 von OCaml. Zum Vergleich wurde der aus der Timing-Analyse
von Quartus berechnete Wert angegeben, der auf dem realen FPGA erreicht werden würde. Die benötigte
Zeit von 2,7 ms pro Bild entspricht dabei einer maximal erreichbaren Framerate von ca. 370 Bildern/s.
Art der Simulation
FPGA mit 98,8 MHza
C-Algorithmus
Aus HDCaml generiertes C
HDCaml-intern
Quartus (ohne Reset)
Quartus (mit Reset)
Messung
# Bilder
Simulationszeit / Bild
Zeit [s]
(berechneter Wert)
[s]
0,0027
0,031
10.000
310
100
10
670
307
6,7
30,7
≈ 10 / 26b
≈ 1 / 26c
736
1.536
1.929,4
40.265,3
Anmerkungen:
a Das
ist die von Quartus berechnete Maximalfrequenz für das minimale HDCaml-Modell auf einem Altera Cyclone II.
wurden 100.000 Taktzyklen simuliert (512 · 512/100.000 = 2, 62144).
c 10.000 Taktzyklen, wie obenstehend.
b Es
Tabelle 5.7: Verschiedene Simulationen des Faltungsfilters (Minimalversion)
Die HDCaml-interne Simulation ist nur um den Faktor 4,6 langsamer als die Ausführung des generierten
C-Modells (obwohl das Modell „interpretiert“ und nicht „ausgeführt“ wird). Der Unterschied zwischen
generiertem C und direkter Implementierung des Algorithmus in C ist prinzipbedingt sehr groß, für das
vorliegende Beispiel liegt der Faktor 216 zwischen den beiden Varianten.
Zusammengefasst können durch Verwendung des erzeugten C-Modells HDCaml-Designs ca. 288 mal
so schnell simuliert werden,25 wie in der schnelleren (ohne Reset, mit FPGA-RAM) Variante der beiden
Quartus-Simulationen.
25 Veranschaulichung
des Faktors 288: statt über Nacht (16 Stunden) zu laufen braucht eine Simulation nur 3,33 Minuten.
Kapitel 6
Schlussbemerkung und Ausblick
6.1
Zusammenfassung
In der vorliegenden Arbeit wurde die funktionale Hardwarebeschreibungssprache HDCaml analysiert, die
innerhalb ihres Einsatzbereichs einfache und leistungsfähige Hardware-Beschreibungsmöglichkeiten bietet.
Gegenüber verbreiteten Sprachen wie VHDL oder Verilog hat HDCaml Vorteile in den Bereichen
•
•
•
•
•
•
Syntax (Knappheit),
Semantik (synchrone RTL-Beschreibung),
Generatoren für regelmäßig aufgebaute Strukturen,
Verschiedene Output-Formate (C, VHDL, Verilog),
Einfache Erweiterbarkeit (z.B. neue Formate), da Open Source und
Schnelle Simulation.
HDcaml wurde auf Erweiterbarkeit hin untersucht und in vielen Punkten erweitert, insbesondere den Themenbereich Einfachheit der Benutzung betreffend (vgl. Kapitel 5, S. 61).
Es wurde ein Hardware-Modell aus dem Themengebiet der Bildverarbeitung (Faltungsfilter) implementiert anhand dem der gesamte Arbeitsablauf bei Verwendung von HDCaml dokumentiert wurde. Die Ergebnisse zeigen dass HDCaml eine einfach zu verwendende Sprache ist, deren Output-Dateien ausgezeichnet
für schnelle Simulation und effiziente Synthese auf FPGAs geeignet sind (s. Abschnitt 5.6.4, S. 79).
Im Vergleich zu VHDL ermöglicht HDCaml eine etwas kompaktere Beschreibung1 und durch die Erzeugung des C-Modells eine wesentlich schnellere Simulation. Die Synthese liefert ungefähr die gleichen
Ergebnisse.
Die Sprache Confluence, die die gleichen Design-Ziele wie HDCaml verfolgt, ist bei Modellierung,
Simulation und Synthese ziemlich gleich wie HDCaml. Ein wesentlicher Nachteil von Confluence sind die
extrem langen Zeiten zum Erzeugen des C-Modells und der HBS-Netzlisten.
Mit Spezialsprachen für die Beschreibung von BV-Algorithmen, wie Sassy, kann HDCaml bei der Modellierung nicht konkurrieren. Simulation und Synthese konnten für Sassy leider nicht ausprobiert werden,
da die Sprache nicht öffentlich verfügbar ist.
6.2
Ausblick
Sprache
HDCaml ist zwar einsetzbar, es gibt jedoch einige Sprachelemente, die in Zukunft implementiert werden
könnten um Hardware besser beschreiben bzw. synthetisieren zu können, z.B. finite state machines oder
inferable RAM (vgl. Abschnitt 5.2.3, S. 65).
1 Bei
dem relativ kurzen Beispiel konnte HDCaml seine Stärken bei der Modellierung nicht besonders deutlich zeigen.
81
KAPITEL 6. SCHLUSSBEMERKUNG UND AUSBLICK
82
Ebenfalls wäre eine Sammlung von häufig benutzten Hilfsfunktionen (z.B. zum Umrechnen von Dezimalzahlen in HDCaml-Konstanten) und Hardware-Modellen (z.B. FIR-Filter) nützlich, die dem Benutzer
von HDCaml Arbeit bei der Modellierung ersparen würden.
Die verwandte (und von HDCaml inspirierte) Sprache HDFS (Abschnitt 4.5, S. 56) hat sowohl viele
dieser neuen Sprachelemente als auch die Sammlung von Hilfsfunktionen und HW-Modellen implementiert. Andererseits könnte HDFS auch von den Erweiterungen profitieren, die in der vorliegenden Arbeit in
HDCaml implementiert wurden. Deshalb sollte untersucht werden, inwiefern eine Fusion von HDCaml und
HDFS möglich ist. Dazu gibt es mindestens die beiden Möglichkeiten
• Erweiterung von HDFS um HDCaml-Funktionalität oder
• Erweiterung von HDCaml um HDFS-Funktionalität.
Da die beiden Sprachen OCaml (Basis von HDCaml) und F# (Basis von HDFS) eng verwandt sind, wäre
der Quellcode in beiden Fällen relativ leicht portierbar.
Wenn man das Prinzip „Verbesserung einer Sprache durch Einschränkung der Funktionalität“ zu Grunde
legt (vgl. Abschnitt 2.3, S. 12), wie es bei HDCaml der Fall ist, sollte auch überlegt werden, wenig nützliche Funktionalität zu entfernen. Ein Kandidat dafür wären Teilschaltungen (s. Abschnitt C.3.1, S. 101),
die in ihrer momentanen Form relativ wenig Nutzen bringen, jedoch die Codebasis von HDCaml (speziell Codegeneratoren, interne Schaltungsrepräsentation, OCaml-interner Simulator und Umbenennungen,
s. Abschnitt C.4, S. 102) unnötig verkomplizieren. Alternativ könnte man Teilschaltungen auch zu echten
Komponenten ausbauen (was sehr wahrscheinlich das Ziel von Tom Hawkins war, bevor er die Entwicklung
von HDCaml einstellte).
Bildverarbeitung
Das untersuchte BV-Beispiel zeigt, dass HDCaml momentan keine Sprache ist, die besondere Fähigkeiten
für Bildverarbeitung aufweist. Im Vergleich zwischen HDCaml und Sassy (Abschnitt 4.7, S. 58) hat letztere
Sprache zwei wesentliche Vorteile:
• Leistungsfähige Schleifen-Generatoren (z.B. window -Iterator, Abschnitt 5.6.2, S. 76) und
• Optimierender Compiler.
Beide Merkmale wären in HDCaml mit entsprechendem Aufwand implementierbar.2 Große Teile der Funktionalität von Sassy sind in der funktionalen Sprache HDCaml ohnehin bereits vorhanden (z.B. map ,
map2 , fold , etc.), lediglich mit einer unterschiedlichen Syntax. Durch seine funktionalen Eigenschaften (Abschnitt 2.5.2, S. 16), insbesondere Polymorphie und Funktionen höherer Ordnung, könnte in HDCaml weitere Funktionalität leicht in Form von Bibliotheksfunktionen ergänzt werden (z.B. der window Iterator). Der Teil, der wesentlich größeren Aufwand und fachspezifisches Wissen erfordern würde, wäre
der optimierende Compiler.
2 Nicht
jede beliebige Hardwarebeschreibungssprache würde die Implementierung einer solchen Funktionalität ermöglichen.
Literaturverzeichnis
[1] Thomas A. Hawkins: HDCaml. http://www.funhdl.org/wiki/doku.php?id=hdcaml
(besucht am 7.3.2008).
[2] Xavier Leroy et al.: The Caml Language.
http://caml.inria.fr (besucht am 7.3.2008).
[3] Xavier Leroy with Damien Doligez, Jacques Garrigue, Didier Rémy and Jérome Vouillon: The Objective Caml system release 3.09, Documentation and users manual, October 2005.
[4] Pagano, Bruno, Emmanuel Chailloux und Pascal Manoury: Développement d’applications avec Objective CAML. O’Reilly, 1. Auflage, Januar 2000, ISBN 2-84177-121-0. Übersetzung ins Englische: http://caml.inria.fr/pub/docs/oreilly-book/index.html (besucht am
7.3.2008).
[5] Free Software Foundation (FSF): GNU Lesser General Public License (LGPL). http://www.
gnu.org/licenses/lgpl.html (besucht am 7.3.2008).
[6] Trolltech AS, Norway: Q Public License (QPL). http://trolltech.com/products/qt/
licenses/licensing/qpl (besucht am 7.3.2008).
[7] Thomas A. Hawkins: Confluence.
confluence (besucht am 7.3.2008).
http://www.funhdl.org/wiki/doku.php?id=
[8] Institute of Electrical and Electronics Engineers, Inc.: IEEE Standard VHDL Language Reference
Manual, IEEE Std 1076-1993 (Revision of IEEE Std 1076-1987) , June 1994.
[9] Institute of Electrical and Electronics Engineers, Inc.: IEEE Standard for Verilog Hardware Description Language, IEEE Std 1364-2005 (Revision of IEEE Std 1364-2001) , April 2006.
[10] Institute of Electrical and Electronics Engineers, Inc.: IEEE Std 1666 - 2005 IEEE Standard SystemC
Language Reference Manual, 2006.
[11] Open SystemC Initiative (OSCI): SystemC. http://www.systemc.org/home (besucht am
7.3.2008).
[12] PSL/Sugar Consortium:
Homepage.
http://www.pslsugar.org
(besucht am 7.3.2008).
[13] Mücke, Manfred: An Enhanced Hardware Description Language Implementation for Improved
Design-Space Exploration in High-Energy Physics Hardware Design. Dissertation, Technische Universität Graz, Juli 2007.
[14] Bergé, Jean Michel, Alain Fonkoua, Serge Maginot, and Jacques Rouillard: VHDL Designer’s Reference. Kluwer Academic Publishers, 1992, ISBN 0-7923-1756-4.
[15] Rainer Dorsch: Skript zur Vorlesung Rechnerorganisation / Technische Informatik II. http://
www.iti.uni-stuttgart.de/%7Erainer/Lehre/ROTI01/skript.pdf (besucht am
7.3.2008), October 2001.
83
LITERATURVERZEICHNIS
84
[16] Ghosh, Sumit: Hardware Description Languages: Concepts and Principles.
ISBN 0-7803-4744-7.
IEEE Press, 2000,
[17] Thomas A. Hawkins:
A Confluence – An Engineer’s Adventure into Functional Programming. http://web.archive.org/web/20050430184606/http://confluent.org/
papers/Confluence.ppt (besucht am 7.3.2008), February 2005.
[18] Gokhale, Maya B. and Paul S. Graham: Reconfigurable Computing. Accelerating Computation with
Field-Programmable Gate Arrays. Springer, 2005, ISBN 0-387-26105-2.
[19] Hammes, Jeffrey P., Bruce A. Draper, and A. P. Willem Böhm: Sassy: A Language and Optimizing Compiler for Image Processing on Reconfigurable Computing Systems. Conference on Vision
Systems – ICVS, pages 83–97, January 1999.
[20] Böhm, W., J. Hammes, B. Draper, M. Chawathe, C. Ross, R. Rinker, and W. Najjar: Mapping a Single
Assignment Programming Language to Reconfigurable Systems. The Journal of Supercomputing,
21(2):117–130, 2002.
[21] Gonzales, Rafael C. and Richard E. Woods: Digital Image Processing. Prentice Hall, second edition,
2002.
[22] Reinhold Schmidt: Image Processing on Field Programmable Gate Arrays using the Hardware Description Language Confluence. Diplomarbeit, Technische Universität Graz, Mai 2006.
[23] Parcerisa, Daniel Sánchez: HDCaml Tutorial. http://www.funhdl.org/wiki/doku.php
?id=hdcaml:tutorial (besucht am 7.3.2008), 2006.
[24] O’Reilly:
History of Programming Languages.
http://www.oreilly.com/news/
graphics/prog_lang_poster.pdf (besucht am 7.3.2008).
[25] Li, Yanbing: HML: An Innovative Hardware Description Language and its Translation to VHDL.
Master’s thesis, Cornell University, Ithaca, NY, August 1995.
[26] Li, Yanbing and Miriam Leeser: HML, a Novel Hardware Description Language and Its Translation
to VHDL. IEEE Transactions on Very Large Scale Integration (VLSI) Systems, 8(1):1–8, February
2000.
[27] Milner, Robin, Mads Tofte, Robert Harper, and D. MacQueen: The Definition of Standard ML (Revised). MIT Press, 1997, ISBN 0262631814.
[28] Free Software Foundation (FSF): GNU General Public License (GPL). http://www.gnu.org
/licenses/gpl.html (besucht am 7.3.2008).
[29] Andy Ray: HDFS. http://code.google.com/p/hdfs (besucht am 7.3.2008).
[30] Microsoft Research:
FSharp (F#).
fsharp.aspx (besucht am 7.3.2008).
http://research.microsoft.com/fsharp/
[31] Microsoft Corporation: Microsoft Research Shared Source License Agreement (MSR-SSLA), NONSTANDARD FOR F# Compiler. http://research.microsoft.com/fsharp/fsharplicense.txt (besucht am 7.3.2008).
[32] Colorado State University, Department of Computer Science: Cameron Project.
cs.colostate.edu/cameron (besucht am 7.3.2008).
[33] Thomas A. Hawkins:
(besucht am 7.3.2008).
Atom.
http://www.
http://www.funhdl.org/wiki/doku.php?id=atom
Anhang A
HDCaml-0.2.10 Language Reference Manual
A.1
Module Design: Digital design entry.
A.1.1
Enhanced features compared to HDCaml 0.2.9
This release enhances the design entry module of HDCaml 0.2.9 with the following features:
• strict error checking
• possibility to deactivate strict checks for a subset of functions/operators
• possibility to exactly locate errors in the source code
• debugging support (find names and nodes) with exact localisation in source
• renaming of illegal/duplicate identifiers (names) for input, output, signal, --,
start_circuit and circuit.
• additional warnings concerning possibly unwanted combinations of arguments to functions / operators
• detecting combinational loops during assignment of dangling signals with the <== operator.
• checking for unconnected dangling signals (which count as errors because they can be the reason for
defective VHDL/Verilog code)
• listing unused inputs
• listing unused circuit nodes in general
• checking for balanced use of structuring functions like start_circuit/get_circuit and
circuit/endcircuit
• design entry func. / op. (including get_circuit) must not be used before start_circuit
A.1.1.1
Instructions: Compile & Run
This release introduces a workaround which enables localization of HDCaml errors by source-code line
numbers. It works by raising an uncaught exception, which causes display of a stack trace. This way one
HDCaml error per compilation can be conveniently located. Compile and run your HDCaml program as
shown below to make it work:
ocamlc -g -o example_circuit.exe hdcaml.cma example_circuit.ml
ocamlrun -b example_circuit.exe
(Option -g of ocamlc causes the executable to contain debug info which is needed for the next command. Option -b of ocamlrun causes the run time system to print a stack trace when an uncaught
exception occurs.)
A.1.1.2
Check Rules
Any function or operator can check for different error or info conditions which are documented as part of
the description of each function/operator. There are a couple of common principles, though, which apply to
every function/operator:
Error conditions:
1. The empty signal is not allowed as an argument to any function or operator which takes type
signal as a parameter. (#)
2. No function or operator returns an empty signal, i.e. arguments which would cause an empty signal
to be returned are considered erroneous. (#)
85
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
86
3. Empty Strings ("") are not allowed for signal/circuit/port names (exception: anonymous signals: see
A.1.4, p. 91).
4. Correct bitwidth of signal operands is enforced at operator call (see operator description for details).
5. Structuring functions like start_circuit/get_circuit and circuit/endcircuit must
be balanced, i.e. no opening function without closing one and vice versa.
6. Negative numbers are no valid argument to any function or operator.
7. Zero (0) is no valid argument to any function or operator except for indices and shift-amounts. (#)
8. Indices of a signal with width N have to be in the range 0. . . (N-1).
Note: error conditions 1., 2. and 7. (marked with (#)) are not used for functions/operators of class 2 (see
definition below in subsection A.1.1.4, p. 87) when set_strict_checks is set to false.
Info conditions:
1. Using the same signal twice as arguments for one function or operator might be unwanted and issues
an info in most cases (exception: multiplications).
2. Duplicate names get renamed during design entry. This is reported for every renamed signal.
A.1.1.3
Renaming
HDCaml 0.2.10 allows only names which are legal in all of its output languages: Verilog, VHDL, C (C++,
SystemC). This boils down to the following rules:
• legal names contain only letters (a-z, A-Z), digits (0-9) and the underscore (_)
• legal names start with a letter
• names are case sensitive
Due to the nature of identifiers/names in HDCaml (strings) they can contain illegal characters or can be
used multiple times without the OCaml compiler being able to detect such wrong use. Therefore the HDCaml library must take care of this issue. HDCaml doesn’t produce error messages and doesn’t stop the
program in case of illegal names used, but tries to modify them so that they become legal. This procedure is
called renaming in this documentation. The user can get the behavior of conventional compilers (i.e. illegal
names cause errors) by setting raise_exception_at_info true. In Version 0.2.10 the renaming
was moved completely into the module at hand (Design) and it gives the user exact reasons for why certain
names are not acceptable and how they get renamed.
There are reasons to rename identifiers although they are legal in terms of the rules above:
• they are identical to keywords of generated languages
• they are identical to implicitly generated signals of HDCaml (clock, reset, vdd, gnd, high, low)
• they are used multiple times
• they contain illegal substrings:
– name ends with the rename suffix (_renamed), followed by a number, e.g. x_renamed15
– name contains the subcircuit delimiter (double underscore), e.g. x_ _y
– name consists of the node prefix (n_) immediately followed by a number, e.g. n_1
The new names are (roughly) calculated in this way:
1. Illegal characters and substrings are deleted from the name.
2. If nothing of the original name remains, start from the scratch with the new name sig (if there are
more of these completely illegal names, this results in names of the form sig_renamed<n>).
3. The new name is compared to language keywords. (#)
4. The new name is prepended with the current subcircuit prefix.
5. The new name (plus subcircuit prefix) is compared to previously used identifiers and language keywords.
6. If one of those matches or the result of step 3 was positive, the new identifier gets the suffix
_renamed plus a consecutive number. In case the original identifier ended with _renamed<n>,
this is removed prior to the operation.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
87
(#) The reason for step 3 is that signal names are used as struct members without subcircuit prefix in
SystemC. Example: the name "if" of a signal in subcircuit "x", which would be a legal name ("x_ _if")
in VHDL and Verilog would nevertheless create an error in the generated C code, where "if" would be
used as a member name of the struct "x".
A.1.1.4
Configure Strict Checks
Concerning strictness of error checks, the functions and operators of HDCaml can be divided into two
classes:
1. functions/operators where strict checks are absolutely useful
2. functions/operators where strict checks may be useful or not, depending on the requirements and
programming habits of the user
Functions/operators of class 1 are:
• start_circuit, get_circuit, circuit, endcircuit
• signal, <==, -• input, output
• one, bit, msb, lsb
• all logical operators: ~:, &:, ^:, |:
• all arithmetic operators: +:, -:, *:, *+
• all comparisons: ==:, /=:, <:, >:, <=:, >=:, <+, >+, <=+, >=+
• all shifts: <<:, <<+, >>:, >>+
• mux2, mux, reg
Functions/operators of class 2 are:
• const, zero, ones
• select, ++, concat, repeat, msbs, lsbs, split, bits
(The constants empty, vdd, gnd, high and low don’t belong to any of the two classes.)
The functions/operators of class 2 are often used to build signals recursively, like it is usual in functional
languages with lists. The following similarities can be observed:
Functional languages
list
[]
::
HDCaml
signal
empty
++
List.hd
msb or lsb
List.tl
msbs or lsbs
Notes
(only defined for list/signal 6=[] / empty;
msb and lsb belong to class 1 therefore)
Other functions/operators of class 2 can be defined in such a way, that they return empty for corner cases,
which can also be useful in generally describing a circuit:
• const ""
• zero 0
• ones 0
• select signal msb lsb with msb < lsb (not very intuitive but status quo)
• concat []
• repeat signal 0
• split signal with (width signal) < 2
(returns a pair where one or both elements can be empty)
• bits empty (returns [])
Functions/operators of class 2 can add expressive power to HDCaml when used carefully. But they can also
cause subsequent errors, which are harder to detect in case of unthoughtful use. To reflect this properties,
there is a switch that allows the user to turn checks for class 2 on and off.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
88
You can find additional descriptions of differences between the two modes further below, at the documentation of the respective functions/operators. These are of the form: ”(strict: limitations when
set_strict_checks true; ; else: limitations when set_strict_checks false;)”.
val set_strict_checks : bool -> unit
Switch to turn the strict checks on and off for functions/operators of class 2 (see definition above).
The default setting is true, i.e. strict checks are active for all functions/operators, including those
of class 2. This default was chosen to provide HDCaml beginners with more useful error messages
at the start.
Experienced HDCaml users can turn the switch off to enable additional expressive power of
HDCaml. This may also be useful to make old HDCaml programs compatible with 0.2.10 in an easy
way.
You can even have both benefits (more useful checks and more expressive power), although not at
the very same time. But you can set strict checks on and off for single functions or parts of your
HDCaml program like this:
set_strict_checks false;
some_tricky_recursive_function x y z ;
set_strict_checks true;
There is no recommended setting of that switch that fits the needs of all users. Just be aware,
that you will get potentially less useful error messages if you disable strict checks.
set_strict_checks true; . . . strict checks are turned on for class 2 (default behavior).
set_strict_checks false; . . . strict checks are turned off for class 2.
val get_setting_strict_checks : unit -> bool
Returns current setting of switch above.
A.1.1.5
Configure Error Reporting
There is a number of new functions in this release which allow the user to specify the behavior of the error
reporting. These functions need only be called if the default behavior should be changed. Therefore old
HDCaml designs are fully compatible.
There are two kinds of messages: errors and infos. An error denotes a critical problem in the source
code and leads to a termination of the program via an uncaught exception. An info is a message the user
might be interested in. The program continues to run after an info. (This is the default behavior which can
be changed for both kinds of messages.) Default behavior (when switches are not used):
• infos don’t raise exceptions
• errors raise exceptions
val raise_exception_at_info : bool -> unit
Causes an info to have the same consequences as an error, i.e. it raises an exception. This switch
can be used to find the context of an info message and must be set to true to make optimal use
of find_name and find_node.
raise_exception_at_info true; . . . next info will terminate program.
raise_exception_at_info false; . . . program will continue to run after an info (default
behavior).
val get_setting_exception_at_info : unit -> bool
Returns current setting of switch above.
val raise_exception_at_error : bool -> unit
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
89
Allows to inhibit the raising of an exception after occurrence of an error, that means the program is
further executed. This allows to see subsequent messages during one run but produces potentially
defective VHDL, Verilog or SystemC files. Do not enable this switch unless you know exactly
what you do!
raise_exception_at_error true; . . . raises exception after error (default behavior).
raise_exception_at_error false; . . . no exception; program continues to run.
val get_setting_exception_at_error : unit -> bool
Returns current setting of switch above.
These functions can be used repeatedly at any position in the HDCaml program. E.g. exceptions for infos
could be turned on only during a single function call:
raise_exception_at_info true;
some_function a b c ;
raise_exception_at_info false;
The same is true for the other configuration functions.
A.1.1.6
Trace Names and Nodes
val find_name : string -> unit
Creates an info output when the given name is being used in signal, -- (annotation), input,
output, start_circuit or circuit (start of subcircuit).
Together with the lists of unconnected signals and unused inputs (which are displayed when
get_circuit is called) and raise_exception_at_info true this can be used to track
down one unconnected dangling signal or unused input per run (plus one initial run to get the list).
The function can be used to find any name, by the way, not only unconnected signals or unused
inputs.
Example: find_name "subcircuit_ _mysignal";
Note: signal names, annotated names and subcircuit names may include a subcircuit prefix of the
form "subcircuit_ _" (subcircuit name plus double underscore) if they are used inside of a
subcircuit. Inputs and outputs don’t get such prefixes.
This function might seem pointless because the user knows when he uses which name. There are
situations, however, that make tracing of names tedious or even impossible:
•
•
•
•
items get renamed because of multiple use of the same name
names are created dynamically
the same name (annotation, signal) occurs in multiple different subcircuits
anonymous signals (signal "" width) are used, see A.1.4, p. 91
In these cases this function might come in handy. It can be used at any position in the HDCaml
program. Only the trace for the last name ("name2" in this example) will be active if you use it
repeatedly, like:
find_name "name1";
find_name "name2";
The empty string as an argument (i.e. find\_name "";) turns name tracing off. This has the
same effect as not using this function at all.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
90
val find_node : int -> unit
Creates an info output when the given node number is being generated.
Together with the list of unused nodes (which is displayed when get_circuit is called) and
raise_exception_at_info true this can be used to track down one unused node per run
(plus one initial run to get the list).
The OCaml compiler is sometimes helpful in finding unused nodes, too, by reporting unused
variables. But it doesn’t find every unused node (as of version 3.09.3), for example in this code
fragment (inp_list is used once, so ocamlc doesn’t complain):
let inp = input "name" 4 in
let inp_list = bits inp in
output "first_bit" (List.hd inp_list);
The function can be used to find any node, by the way, not only unused ones. Have you ever
wondered, for example, which construct in your HDCaml program generates nodes like n_17 in the
Verilog or VHDL code? With this function you can simply look it up, like find_node 17;.
Attention: node numbers change when a HDCaml program is changed. So don’t forget to turn
find_node off after finding a certain node and changing the code, because otherwise you would
find a completely different node which happens to get the same number afterwards.
This function can be used at any position in the HDCaml program. Only the trace for the last node (2
in this example) will be active if you use it repeatedly, like:
find_node 1;
find_node 2;
Zero as an argument (i.e. find_node 0;) turns node tracing off. This has the same effect as not
using this function at all.
A.1.2
Circuit Types
type signal = Circuit.signal
type circuit = Circuit.circuit
A.1.3
Signals represent bit vectors.
A circuit is a completed circuit design.
Circuit Data
val start_circuit : string -> unit
Starts a new circuit design. Initializes the internal circuit database.
val get_circuit : unit -> circuit
Returns the circuit design and resets the internal database.
Checks for unused/unconnected elements and unbalanced use of circuit/endcircuit :
•
•
•
•
too few calls of endcircuit ⇒ error
unconnected dangling signals are left ⇒ error
unused inputs are left ⇒ info
unused circuit nodes are left ⇒ info
Every element can be searched for. Use find_name "name" for inputs, dangling signals and
opening subcircuits (circuit), find_node number for circuit nodes.
val circuit : string -> unit
Creates a new subcircuit and namespace.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
91
val endcircuit : unit -> unit
Closes a subcircuit. Issues an error if too few subcircuits have been opened.
A.1.4
Signal Assignment and Annotation
val signal : string -> int -> signal
signal name w. Creates a dangling signal, which may be assigned later.
Anonymous signals (new in 0.2.10):
Passing an empty string as name (signal "" w) creates an ”anonymous signal”. This means that
a name is being created automatically by HDCaml. The created name looks like that of other
unnamed nodes, e.g. "n_15" for a anonymous signal which happens to get assigned the id 15
internally.
The use of anonymous signals is convenient in user written library functions which define sequential
logic (i.e. registers with feedback and therefore dangling signals are used). In these cases the user no
longer has to make up meaningful names.
val (<==) : signal -> signal -> unit
Assigns a value to a dangling signal.
It’s an error if the assignment closes a combinational loop.
val (--) : string -> signal -> signal
A.1.5
Applies a label (name) to a signal.
Width Utilities
Width of a signal.
val width : signal -> int
The following functions check whether width constraints are fulfilled and raise the exception
Design_error otherwise:
val check_width : int -> signal -> unit
Checks that a signal has a given width.
val check_width_bit : signal -> unit
Checks that a signal is a single bit.
val check_width_nonzero : signal -> unit Checks that a signal has a nonzero width.
val check_width_same : signal -> signal -> unit
Checks that two signals have the same width.
A.1.6
Top Level Ports
val input : string -> int -> signal
val output : string -> signal -> unit
A.1.7
Creates a top-level input.
Assigns a top-level output.
Constants
Note: Result of const, zero, one and ones must be at least one bit wide.
val const : string -> signal
val zero : int -> signal
val one : int -> signal
val ones : int -> signal
val empty : signal
val vdd : signal
Creates a constant. const string; String must be
1’s and 0’s. (strict: string 6=""; else: string may be "")
Creates a constant 0. zero width;
(strict: width≥1 ; else: width≥0)
Creates a constant 1.
Creates a constant of 111’s. ones width;
(strict: width≥1 ; else: width≥0)
An empty signal. (width=0)
A single bit set to 1.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
val gnd : signal
val high : signal
val low : signal
A.1.8
92
A single bit set to 0.
New in 0.2.10. A single bit set to 1.
New in 0.2.10. A single bit set to 0.
Bit Manipulation
val select : signal -> int -> int -> signal
Selects a section of a signal. LSB (right most) is indexed with 0.
select signal msb lsb. (strict: msb≥lsb ; else: msb<lsb is allowed)
val bit : signal -> int -> signal
Selects an individual bit from a signal.
val (++) : signal -> signal -> signal
Concatenates two signals together. left ++ right.
(strict: only one side may be empty ; else: both sides may be empty)
val concat : signal list -> signal
New in 0.2.10. Concatenates a list of signals together. Head of list is MSB. List items may have
different widths. (strict: list must not be [] and at least one item must have non-zero width; else:
list may be [], all items may be empty )
val repeat : signal -> int -> signal
Repeats concatenation N times. repeat signal n;
(strict: signal6=empty, n≥1; else: signal may be empty, n≥0)
val msb : signal -> signal
Selects the most significant bit (left most).
val msbs : signal -> signal
Selects all bits except the LSB. msbs a (strict: width a ≥ 2; else: width a ≥ 1)
val lsb : signal -> signal
Selects the least significant bit (right most).
val lsbs : signal -> signal
Selects all bits except the MSB. lsbs a (strict: width a ≥ 2; else: width a ≥ 1)
val split : signal -> signal * signal
Splits a signal into two equal parts. If signal has odd number of bits, left side will have one more bit
than right. split a (strict: width a ≥ 2; else: no restrictions)
val bits : signal -> signal list
Splits a signal into a list of bits. MSB is head of list.
(strict: width a ≥ 1; else: no restrictions)
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
A.1.9
93
Bitwise Logicals
Notes: (for all operators except (~:) )
• Both signals must have the same width
• An info is generated if both arguments are the same signal.
val
val
val
val
(~:)
(&:)
(^:)
(|:)
A.1.10
:
:
:
:
signal
signal
signal
signal
->
->
->
->
Bitwise NOT.
Bitwise AND.
Bitwise XOR.
Bitwise OR.
signal
signal -> signal
signal -> signal
signal -> signal
Arithmetics
Note: An info is generated if both arguments of addition or subtraction are the same signal. (This is not the
case for multiplications.)
val (+:) : signal -> signal -> signal
Addition. All signals have same width, including the result.
val (-:) : signal -> signal -> signal
Subtraction. All signals have same width, including the result.
val (*:) : signal -> signal -> signal
Unsigned multiplication. a *: b. Width of result is (width a)+(width b).
val (*+) : signal -> signal -> signal
Signed multiplication. a *+ b. Width of result is (width a)+(width b).
A.1.11
Shifting
Notes: signal (shift-op) shift
• shift must not be negative
• An info is generated if shift=0 or shift >= width a.
val (<<:) : signal -> int -> signal
val (<<+) : signal -> int -> signal
val (>>:) : signal -> int -> signal
val (>>+) : signal -> int -> signal
A.1.12
Unsigned shift left.
New in 0.2.10. Signed shift left.
(Just for completeness, identical to (<<:))
Unsigned shift right.
Signed shift right.
Comparisons
Notes:
• Both signals must have the same width
• An info is generated if both arguments are the same signal.
val
val
val
val
val
val
val
val
val
val
(==:)
(/=:)
(<:)
(>:)
(<=:)
(>=:)
(<+)
(>+)
(<=+)
(>=+)
:
:
:
:
:
:
:
:
:
:
signal
signal
signal
signal
signal
signal
signal
signal
signal
signal
->
->
->
->
->
->
->
->
->
->
signal
signal
signal
signal
signal
signal
signal
signal
signal
signal
->
->
->
->
->
->
->
->
->
->
signal
signal
signal
signal
signal
signal
signal
signal
signal
signal
Equal.
Not equal.
Unsigned less than.
Unsigned greater than.
Unsigned less than or equal.
Unsigned greater than or equal.
Signed less than.
Signed greater than.
Signed less than or equal.
Signed greater than or equal.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
A.1.13
94
Muxing
val mux2 : signal -> signal -> signal -> signal
2-input mux: mux2 select on_high on_low. Width of select must be 1. Both signals
(on_high, on_low) must have the same width.
An info is generated if both arguments (on_high, on_low) are the same signal.
val mux : signal -> signal list -> signal
N-Input Mux: mux ctrl items
The control input ctrl is binary encoded (a N bit wide control signal can select 2^N items). If
ctrl has more bits than necessary, the most significant redundant bits are simply ignored. If the
number of items is no power of 2, the last element is replicated to get a list of the correct length (e.g.
if ctrl is 3 bits wide, but items only has 6 elements (2 missing), ctrl=5 or 6 or 7 will all select
the last item in your list).
Errors:
• The list of signals (items) is empty.
• Some elements of the list have different widths than others.
• Input ctrl doesn’t have enough bits to select all items.
Infos:
• All items are the same signal
• The number of items is no power of 2
• The control input has more bits than necessary.
A.1.14
Registers
val reg : signal -> signal -> signal
Register. reg enable data. Width of enable must be 1.
An info is generated if both arguments (enable, data) are the same signal.
A.2
Module Simulate: Simulate designs within OCaml.
type simulator
Data structure holding the current state of the simulated circuit.
type port = string * int array
Input and output data is represented as an array of integers, where each element covers 31 bits. The
ordering is little endian: index 0 is the least significant bits. A port, either input or output, provides
access to the circuit under simulation. A port provides the associated port name and simulation data
array.
val create : Circuit.circuit -> Pervasives.out_channel ->
simulator * port list * port list
Creates a simulator from a circuit design. Given a circuit and and output channel for VCD, returns
the simulator and list of input and output ports for interfacing with the simulator.
val cycle : simulator -> unit
val reset : simulator -> unit
Performs a calculation and
advances the simulator one timestep.
Resets the state of the simulator.
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
A.2.1
95
Warning
There is a situation where the simulator produces wrong results. This occurs when there is combinational
logic between registers and outputs (you’ll get a INFO when using outputs this way). In this case the results
of these outputs are delayed one cycle. You can avoid this problem in one of two ways:
• avoid combinational logic between registers and outputs
• use the workaround below at a performance cost (roughly 1/2 performance)
Workaround:
• remove comment characters in line 632 of simulate.ml
• add comment characters to line 631 of simulate.ml
• (optional) deactivate INFO generating code, lines 616-621
• make install
The source code is released with the faster but potentially incorrect code activated. There is a solution which
is both fast and correct. It should be implemented in the releases to come.
A.3
Module Systemc: SystemC model generation.
val output_model : Circuit.circuit -> unit
Writes a C model (<circuit>.h, <circuit>.c ) and a SystemC wrapper (<circuit>_sc.h).
A.3.1
Warning
There could be a situation where the (System)C-code produces wrong results. This could occur when there
is combinational logic between registers and outputs (you’ll get a INFO when using outputs this way). In
this case the results of these outputs could be delayed one cycle. You can avoid this potential problem by
avoiding combinational logic between registers and outputs.
The presence of this potential bug has neither be confirmed nor disproved yet. The problem should be
addressed in the releases to come.
A.3.2
Usage of the generated C-Model
A.3.2.1
Types and Functions
Types (see generated .h-file):
unsigned long *
simulator_t
signal_t
Type used to store bit vectors (signals). Each element of the array stores 32 bits.
Holds the current state of the simulated circuit, defines ports and named signals.
Struct returned by find_simulator_port. Contains the pointer to the data
(signal) and the width of the signal (width).
Functions:
simulator_t new_simulator();
Creates the simulator (”Constructor”).
void delete_simulator(simulator_t); Deletes the simulator (”Destructor”).
void init_simulator(simulator_t);
Initializes the simulator.
simulator_t cycle_simulator();
Advances the simulator one timestep.
signal_t find_simulator_port(simulator_t, char* name); (New in 0.2.10.)
Queries for ports/signals by name. Struct member signal gets assigned the NULL pointer
if the name was not found (can be conveniently tested with assert(x.signal);).
ANHANG A. HDCAML-0.2.10 LANGUAGE REFERENCE MANUAL
A.3.2.2
96
Example
Assuming the names my_design.c and my_design.h for the generated C files a testbench could look
like this:
i8=find_simulator_port(sim, "i8");
assert(i8.signal);
o4=find_simulator_port(sim, "o4");
assert(o4.signal);
#include <stdio.h>
#include <assert.h>
#include "my_design.h"
for (i = 0; i < 5; i++)
{
i8.signal[0]=i*32;
cycle_simulator(sim);
printf("cycle %2d: o4=%ld \n",
i, o4.signal[0] );
};
int main()
{
int i;
simulator_t sim;
signal_t i8;
signal_t o4;
// input
// output
delete_simulator(sim);
return 0;
sim = new_simulator();
init_simulator(sim);
}
This example uses the function find_simulator_port. You can of course still access the struct members in sim->signals.my_design directly, like in previous versions.
A.4
Module Verilog: Verilog netlist generation.
val output_netlist : Circuit.circuit -> unit
Writes Verilog netlist (<circuit>.v) to a file.
A.5
Module Vhdl: VHDL netlist generation.
val output_netlist : Circuit.circuit -> unit
Writes VHDL netlist (<circuit>.vhd) to a file.
A.5.1
Warning
As this code generator is new in HDCaml 0.2.10 it hasn’t been publically tested yet. Please consider it as
experimental!
A.5.2
Workaround for tools which are not VHDL’93 compliant
The VHDL generator creates extended identiers (names which are delimited with a backslash, e.g. \name\)
in theses cases:
• subcircuits are used, therefore names contain double underscores ("_ _")
• a name contains capital letters (to allow for case sensitivity)
• a name ends with an underscore
In case your tools aren’t VHDL’93 compliant, simply don’t use subcircuits and names with capital letters
or trailing underscores.
Anhang B
Auszug aus dem Vortrag: A Confluence
Dieser Anhang zitiert die für die vorliegende Arbeit relevanten Folien aus Tom Hawkins’ Vortrag
„A Confluence – An Engineer’s Adventure into Functional Programming“[17]
Scope of Confluence
• Restricted to Synchronous RTL
– No clock manipulation.
• Confluence is a declarative RTL design language.
– No level sensitive latches.
– Use functional programming to ...
∗
∗
∗
∗
∗
∗
Generate hardware structures.
Increase design reuse.
Lower source code line count; reduce bugs.
Speed the rate of RTL construction.
Ensure synchronous, FPGA friendly code.
Maintain designer control.
– No combinational loops or other asynchronous logic.
• Lacks Good Netlist Annotation
– Difficult to trace a netlist back to its source.
– Difficult to associate design constraints with netlist.
• No modular (separate) compilation.
• Confluence is not ...
– Generates an elaborated netlist.
– a general purpose programming language.
∗ Affects implementation flows.
– a „high level“ design language.
– Can a language be lenient, yet modular?
– a mixed signal or analog design language.
– a behavioral modeling language.
∗ However, rapidly constructed design makes
a good model.
Confluence Advantages
– a verification language.
∗ However, functional programming enables
all sorts of possible verification strategies.
· Greater design reuse
⇒ fewer components to verify.
· Data width induction.
· Structural verification.
· Functional plug-n-play.
• Simple
– Some users up-and-running in a day – even without
functional programming experience!
– Dynamic Type Checking
• Multiple Models from One Source
– Verilog, VHDL, C, SMV, ...
• Restricted to Synchronous RTL
Confluence Limitations
– You can’t break, what you can’t control.
• Limited I/O Facilities
• Lenient Evaluation Order
– Inputs: Command line arguments (argv).
– Natural for expressing hardware loops.
– Outputs: print a , show b
• Increased Information Density
• Dynamic Type Checking
• Restricted to Confluence Primitive Set
– On average 3-5X fewer lines of code.
– No blackboxes (temporary).
∗ Fewer Lines ⇔ Fewer Bugs
– No memories (blackboxes in future).
– Without loss of control.
• Lenient Evaluation Order
∗ Confluence is still at RTL.
– Difficult to identify and report
root cause of errors.
• Many Opportunities for Design Reuse
– Thanks to lexical scoping, first class closures, etc.
– Slow. Fundamental limit of dataflow computation?
97
Anhang C
HDCaml-Details
C.1
Datentyp signal
In HDCaml heißen Bitvektoren Signale. Der rekursive Datentyp signal (Listing C.1, S. 98)1 beschreibt
u.A. Eingabe, Konstanten und Bitmanipulationen, Abspeicherung in Registern und verschiedene kombina1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type width = i n t
type id = i n t ; ;
type s i g n a l =
Signal_input
| Signal_signal
| Signal_const
| Signal_empty
| Signal_select
| Signal_concat
| Signal_not
| Signal_and
| Signal_xor
| Signal_or
| Signal_eq
| Signal_lt
| Signal_add
| Signal_sub
| Signal_mul_u
| Signal_mul_s
| Signal_mux
| Signal_reg
;;
;;
of
of
of
of
of
of
of
of
of
of
of
of
of
of
of
of
of
of
id
id
id
id
id
id
id
id
id
id
id
id
id
id
id
id
id
id
∗ s t r i n g ∗ width
∗ s t r i n g ∗ width ∗ s i g n a l ref
∗ string
( ∗ s t r i n g o f ’ 0 ’ and ’ 1 ’
(∗ zero width s i g n a l
∗ int ∗ int ∗ signal
( ∗ msb , l s b : msb >= l s b
∗ signal ∗ signal
∗ signal
∗ signal ∗ signal
∗ signal ∗ signal
∗ signal ∗ signal
∗ signal ∗ signal
(∗ equal
∗ signal ∗ signal
(∗ l e s s than
∗ signal ∗ signal
∗ signal ∗ signal
∗ signal ∗ signal
∗ signal ∗ signal
∗ s i g n a l ∗ s i g n a l ∗ s i g n a l ( ∗ s e l e c t , h i g h , low
∗ signal ∗ signal
(∗ enable , data
∗)
∗)
∗)
∗)
∗)
∗)
∗)
23
24
25
26
27
type s in k =
Sink_output of id ∗ s t r i n g ∗ s i g n a l
| ( ∗ . . . t h r e e more s i n k s , u s e d f o r PSL a s s e r t i o n s
;;
. . . ∗)
Listing C.1: Datentyp signal in HDCaml (leicht vereinfacht)
torische Verknüpfungen von Bitvektoren. Die Varianten des Datentyps haben folgende Bedeutung (Hinweise auf Funktionen und Operatoren beziehen sich auf Tabelle 3.1, S. 25 bzw. auf Anhang A, S. 85):
Input mit einem Namen und einer Bitbreite (vgl. Funktion input ).
Dangling Signal (s. Abschnitt C.1.1, S. 99 und Abschnitt 3.4.2.1, S. 32) mit einem
Namen, einer Bitbreite und einer Referenz auf einen Bitvektor, der nachträglich
ein gültiges Signal zugewiesen werden kann (vgl. signal , <== und - - ).
Signal_const
Konstanter Bitvektor (String aus den Zeichen ’0’ und ’1’ , vgl. Funktionen
const , high , vdd , low , gnd , one , ones , zero ).
Signal_empty
Leerer Bitvektor (Bitbreite 0, vgl. Konstante empty ).
Signal_select Auswahl eines Teil-Bitvektors (vgl. Funktionen select , bit , msb , msbs ,
lsb , lsbs , split ).
Signal_concat Verknüpfung zweier Bitvektoren (vgl. ++ , concat und repeat ).
Signal_not , Signal_and , Signal_xor , Signal_or
Logische Verknüpfung von Bitvektoren (vgl. ~:, &:, ^:, |: ).
Signal_input
Signal_signal
1 Aus
der Datei circuit.ml der HDCaml-Bibliothek, zum besseren Verständnis leicht vereinfacht.
98
ANHANG C. HDCAML-DETAILS
99
Signal_eq ,
Signal_lt
Vergleiche von Bitvektoren (vgl. ==:, /=:, <:, <+, <=:, <=+,
>:, >+, >=:, >=+).
Signal_add , Signal_sub , Signal_mul_u , Signal_mul_s
Arithmetische Operationen (vgl. +:, -:, *:, *+).
Signal_mux
Multiplexer (vgl. mux2 und mux ).
Signal_reg
Register (vgl. reg ).
Alle Signale (Instanzen bzw. Objekte des Datentyps signal ) haben eine fortlaufende Nummer ( id )
zur eindeutigen Identifizierung. Outputs sind nicht Bestandteil des Datentyps signal , sondern gehören
(zusammen mit PSL-Assertions) zum Typ sink . Die Sink_output -Variante dieses Datentyps ist in
Zeile 25 von Listing C.1, S. 98 angegeben.
C.1.1
Dangling Signal
Die Verwendung des Dangling Signals wurde bereits in Listing 3.10, S. 33 demonstriert (Variable next_
count , Zeilen 27 und 30). Die dazugehörenden Signaturen lauten (vgl. Abschnitt A.1.4, S. 91):
val signal : string -> int -> signal
val (<==) : signal -> signal -> unit
(* signal name width *)
(* a <== b
*)
Die beiden Parameter für den Namen und die Bitbreite des Dangling Signals erfordern eine Erklärung, weil
prinzipiell beide nicht notwendig wären. Für den Namen gibt es zwei Gründe:
• Es hilft beim Debuggen von Programmen, wenn Dangling Signals Namen haben. Sonst könnte eine
Fehlermeldung aufgrund unverbundener Signale keinerlei Information darüber beinhalten, um welche
es sich handelt.
• Der Annotation-Operator von HDCaml ist mit Hilfe des Dangling Signals implementiert,
siehe Abschnitt C.1.2, S. 99.
Die anzugebende Bitbreite erleichtert die Ableitung und Überprüfung der Bitbreiten von Signalen wesentlich (siehe dazu auch Abschnitt C.2, S. 99). Bei der Verwendung eines Dangling Signals garantiert der Benutzer für zwei Punkte:
1. dass dieses Signal später einen gültigen Bitvektor zugewiesen bekommt und
2. dass dieser später zugewiesene Bitvektor die angegebene Bitbreite haben wird.
Im weiteren Ablauf des HDCaml-Programms werden diese beiden Punkte überprüft, und bei der Verletzung einer der beiden Zusicherungen wird das Programm mit einer Fehlermeldung abgebrochen. Punkt 1
wird am Ende der HDCaml-Hardware-Beschreibung (beim Aufruf von get_circuit) überprüft. Wenn
unverbundene Dangling Signals übrig bleiben, ist das ein Fehler, der zum Abbruch des Programmes führt,
noch bevor Output-Dateien erzeugt werden (vgl. Abschnitt A.1.3, S. 90). Die Prüfung auf Punkt 2 erfolgt
beim späteren Zuweisen des tatsächlichen Bitvektors mit <==.
C.1.2
HBS-Annotation
Der Annotation-Operator (Abschnitt 3.3.1.2, S. 30), mit dem man jedem Signal einen Namen geben kann,
wird HDCaml-intern durch Verwendung des Dangling Signals realisiert, wie Listing C.2, S. 100 (vereinfacht) zeigt.
C.2
Ableitung und Überprüfung der Bitbreiten
Listing C.3, S. 100 zeigt die rekursive Funktion width 2 die die Breite von Bitvektoren aus ihrer Verwendung ableitet. Textuell beschrieben lauten die Vorschriften für die verschiedenen Varianten des Datentyps:
2 Aus
der Datei circuit.ml der HDCaml-Bibliothek, leicht vereinfacht.
ANHANG C. HDCAML-DETAILS
1
2
3
4
5
100
l e t (−−) name a =
l e t b = s i g n a l name ( w i d t h a ) i n
b <== a ;
b
;;
Listing C.2: Implementierung des Annotation-Operators in HDCaml (leicht vereinfacht)
Die Bitbreite von Inputs wird bei ihrer Verwendung angegeben.
Die Bitbreite von Dangling Signals wird bei ihrer Verwendung angegeben.
Die Bitbreite von Konstanten ist die Länge des Strings aus den Zeichen ’0’ u. ’1’.
Das leere Signal hat die Bitbreite 0.
Ein Teil-Bitvektor hat die Bitbreite msb - lsb + 1.
Eine Aneinanderreihung zweier Bitvektoren ist so breit, wie die Summe der
Bitbreiten der beiden Teil-Bitvektoren.
Signal_not , Signal_and , Signal_xor , Signal_or
Die logischen Operatoren behalten die Bitbreite ihrer Operanden bei.
Signal_eq , Signal_lt
Vergleichsoperatoren haben ein einzelnes Bit (Bitbreite 1) als Ergebnis.
Signal_add , Signal_sub
Summe und Differenz behalten die Bitbreite ihrer Operanden bei.
Signal_mul_u , Signal_mul_s
Die Ergebnis-Bitbreite der Multiplikationen ist die Summe der Bitbreiten
der beiden Faktoren.
Signal_mux , Signal_reg
Multiplexer und Register haben die Breite ihrer Eingangs-Bitvektoren.
Signal_input
Signal_signal
Signal_const
Signal_empty
Signal_select
Signal_concat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
l e t rec width s i g n a l =
match s i g n a l w i t h
| S i g n a l _ i n p u t ( _ , _ , w)
| S i g n a l _ s i g n a l ( _ , _ , w, _ )
| Signal_const (_ , v)
| Signal_empty _
| S i g n a l _ s e l e c t ( _ , msb , l s b , _ )
| Signal_concat (_ , a , b)
| Signal_not
(_ , a)
| Signal_and
(_ , a , _)
| Signal_xor
(_ , a , _)
| Signal_or
(_ , a , _)
| Signal_eq
(_ , _ , _)
| Signal_lt
(_ , _ , _)
| Signal_add
(_ , a , _)
| Signal_sub
(_ , a , _)
| Signal_mul_u ( _ , a , b )
| Signal_mul_s (_ , a , b )
| Signal_mux
(_ , _ , a , _)
| Signal_reg
(_ , _ , a)
;;
−>
−>
−>
−>
−>
w
String . length v
0
msb − l s b + 1
width a + width b
−> w i d t h a
−> 1
−> w i d t h a
−> w i d t h a + w i d t h b
−> w i d t h a
Listing C.3: Funktion width von HDCaml (leicht vereinfacht)
Die Funktion width kann vom Benutzer verwendet werden, um die Bitbreite von Signalen zu ermitteln.
Sie wird auch HDCaml-intern verwendet, um sicherzustellen, dass Argumente von Funktionen und Operatoren die passende Bitbreite haben. Beispielsweise muß der Control-Input eines Multiplexers (mux2) die
Breite 1 haben, die beiden auswählbaren Alternativen müssen gleich breit sein. Listing C.4, S. 101 zeigt
ANHANG C. HDCAML-DETAILS
101
(sinngemäß) den Code, der die Bitbreiten der Argumente eines zweifach-Multiplexers überprüft.
1
2
3
4
l e t mux2
l e t wc
l e t wh
l e t wl
control
= width
= width
= width
on_high
control
on_high
on_low
on_low =
in
in
in
5
i f wc<>1 | | wh=0 | | wl =0 | | wh<>wl t h e n r a i s e D e s i g n _ e r r o r ;
6
7
(∗ . . .
8
9
weitere Verarbeitung
. . . ∗)
;;
Listing C.4: Überprüfung der Bitbreiten am Beispiel der Funktion mux2 von HDCaml (sinngemäß)
C.2.1
Überprüfung der Bitbreiten durch den Compiler
Bitvektoren verschiedener Breiten sind für den Compiler nicht verschiedene Typen, sondern nur ein einziger
Typ: signal . Es ist deshalb dem Compiler nicht möglich, zur Compile-Zeit Bitbreiten-Fehler zu diagnostizieren. Es werden allerdings alle Bitbreiten-Fehler zur Laufzeit gefunden, weshalb es nicht möglich ist,
fehlerhafte Output-Dateien zu generieren.
Was wäre notwendig, um in HDCaml den Compiler zur Überprüfung der korrekten Bitbreiten heranzuziehen? Eine Möglichkeit wäre, den OCaml-Compiler zu modifizieren. Möglicherweise wäre camlp4
dafür verwendbar (s. Abschnitt 3.5.1, S. 37), vielleicht müsste man aber auch direkt den Quellcode des
OCaml-Compilers anpassen. Damit wäre HDCaml keine echte eingebettete Sprache mehr, sondern ein Mittelding aus eingebetteter Sprache und Compiler für eine DSL (vgl. Abschnitt 2.4, S. 13).
C.3
Datentyp circuit
Listing C.5, S. 101 zeigt die Definition des Datentyps circuit aus der Datei circuit.ml. Mit Objek1
type c i r c u i t = C i r c u i t of id ∗ s t r i n g ∗ c i r c u i t
l i s t ∗ signal
l i s t ∗ sink l i s t
;;
Listing C.5: Datentyp circuit in HDCaml
ten dieses Typs werden ganze Schaltungs-Modelle beschrieben. Eine Schaltung besteht gemäß der Typdefinition von circuit aus folgenden fünf Teilen:
id :
string :
circuit list :
signal list :
sink list :
C.3.1
Eine eindeutige Identifikationsnummer
Der Name der Schaltung (oder Teilschaltung)
Eine Liste von Teilschaltungen (rekursive Definition)
Eine Liste von Bitvektoren und ihren Verknüpfungen
Eine Liste von Outputs und PSL-Assertions
Teilschaltungen (subcircuits)
Wie man an der (rekursiven) Definition des Typs circuit in Listing C.5, S. 101 sieht, kann man Teilschaltungen (subcircuits) beliebig tief verschachteln. Im HDcaml-Programm werden Teilschaltungen durch
die Funktionen circuit und endcircuit spezifiziert (vgl. Abschnitt A.1.3, S. 90). Die Hierarchie
der Teilschaltungen spiegelt sich folgendermaßen in den erzeugten HBS-Modellen wider (das hat auch
Auswirkungen auf die Umbenennungen von ungültigen Bezeichnern, s. Abschnitt C.4, S. 102):
C: Die Hierarchie wird in Form von verschachtelteten Structures ( struct ) innerhalb des Datentyps
simulator_t abgebildet.
ANHANG C. HDCAML-DETAILS
102
VHDL, Verilog: Die Hierarchie wird abgebildet, indem benannte Signale für jede Hierarchie-Ebene einen
Präfix der Form teilschaltung_ _ bekommen
Beispiel: Das Code-Fragment
circuit ”a”; circuit ”b”; let s = signal ”s” 4 in () endcircuit; endcircuit;
bewirkt in den Outputformaten:
C: Innerhalb von simulator_t werden folgende verschachtelten Structures generiert:
struct { struct { unsigned long * s ; } b; } a;
VHDL, Verilog: Das Signal ”s” bekommt den neuen Namen ”a_ _b_ _s” .
(Inputs und Outputs befinden sich immer in der obersten Ebene der Schaltungshierarchie, für sie gilt oben
Gesagtes nicht, auch wenn sie innerhalb von Teilschaltungen definiert werden.)
C.4
Umbenennung von ungültigen Bezeichnern
Da Signalnamen in HDCaml beliebige Strings sind, muss sichergestellt werden, dass in generierten Dateien
keine Schlüsselwörter der jeweiligen Sprachen und auch sonst keine ungültigen Zeichen vorkommen. Zu
diesem Zweck erfolgt bereits in der Phase des Schaltungsaufbaus eine Prüfung und eventuelle Umbenennung (wobei der Benutzer durch eine Warnung informiert wird) der Signalnamen.
Die Menge der erlaubten Bezeichner ist die Schnittmenge der in den jeweiligen Sprachen erlaubten
Bezeichnern, was zu den in Abschnitt A.1.1.3, S. 86 angeführten Einschränkungen und Regeln zur Umbenennung führt. Auch Teilschaltungen (Abschnitt C.3.1, S. 101) haben Auswirkungen auf diese Regeln:
• Signalnamen müssen sowohl samt ihrem Teilschaltungs-Präfix ( ”a_ _b_ _s” in obenstehendem
Beispiel) als auch ohne ihn (nur ”s” ) auf Übereinstimmung mit Schlüsselwörtern von OutputSprachen geprüft werden.
• auch Teilschaltungsnamen ( ”a” und ”b” im Beispiel) müssen auf Übereinstimmung mit Schlüsselwörtern von Output-Sprachen geprüft werden.
• Der als Trennzeichen für den Teilschaltungs-Präfix genutzte doppelte Unterstrich ist in VHDL nicht
erlaubt. Das Problem muss durch Verwendung von extended identifiers umgangen werden (vgl.
Abschnitt A.5.2, S. 96).
C.5
Generierung des C-Modells
Listing C.6, S. 103 zeigt das Header-File des C-Modells, das aus dem HDCaml-Programm von Listing 3.5,
S. 28 generiert wurde. Der eigentliche C-Code (Datei counter_example.c ) ist mit ca. 350 Zeilen
zu umfangreich um ihn in diese Arbeit aufzunehmen. Listing C.7, S. 103 zeigt den generierten SystemCHeader, mit dem es möglich ist, das C-Modell in Designprozesse einzubinden, die SystemC verwenden.
C.6
Generierung der VHDL-Netzliste
In früheren Abschnitten fehlten in den Abdrucken der generierten VHDL-Modelle immer ein paar Zeilen
weil sie dort zum Verständnis des Beispiels nichts beigetragen, sondern nur unnötig Platz gebraucht hätten. In diesen Zeilen, die in Listing C.8, S. 104 dargestellt werden, werden einige Hilfsfunktionen definiert,
die anschließend vom generierten Code verwendet werden. Diese Hilfsfunktionen erleichtern die Erzeugung von VHDL-Code, weil mit ihrer Hilfe sämtliche im Datentyp signal vorkommenden Varianten als
Ausdrücke geschrieben werden können, die an jeder beliebigen Stelle im Programm vorkommen könnten.
Bei der Generierung von Verilog-Code sind solche Hilfsfunktionen nicht notwendig, weil in Verilogs
Syntax alle notwendigen Ausdrücke bereits existieren, z.B. der von der Sprache C bekannte ternäre Operator, der in Verilog zur Beschreibung eines 2-fach Multiplexers verwendet werden kann:
(sel ? on_hi : on_lo) .
45
44
43
42
41
40
39
38
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
Listing C.6: Header-File des generierten C-Modells
# ifdef __cplusplus
}
# endif
/ / Simulator Cycle Ca l c u l a t i on
void c y c l e _ s i m u l a t o r ( s i m u l a t o r _ t ) ;
/ / Find s i g n a l i n s i m u l a t o r s t r u c t u r e
typedef s t r u c t _ s i g n a l _ t { unsigned long ∗ s i g n a l ; unsigned long width ;
} signal_t ;
s i g n a l _ t f i n d _ s i m u l a t o r _ p o r t ( s i m u l a t o r _ t sim , char ∗name ) ;
/ / Simulator I n i t i a l i z a t i o n
void i n i t _ s i m u l a t o r ( s i m u l a t o r _ t ) ;
/ / Simulator Destructor
void d e l e t e _ s i m u l a t o r ( s i m u l a t o r _ t ) ;
/ / Simulator Constructor
simulator_t new_simulator ( ) ;
typedef struct simulator_s ∗ s i m u l a t o r _ t ;
/ / S i m u l a t o r Data Type
struct simulator_s {
struct {
struct {
/ / inputs :
u n s i g n e d l o n g ∗ e n a b l e ; / / i n p u t e n a b l e : 1 b i t s , 1 words
/ / outputs :
u n s i g n e d l o n g ∗ c o u n t _ o u t ; / / o u t p u t c o u n t _ o u t : 2 b i t s , 1 words
/ / wires :
u n s i g n e d l o n g ∗ n e x t _ c o u n t ; / / w i r e n e x t _ c o u n t : 2 b i t s , 1 words
u n s i g n e d l o n g ∗ low ; / / w i r e low : 1 b i t s , 1 words
u n s i g n e d l o n g ∗ h i g h ; / / w i r e h i g h : 1 b i t s , 1 words
u n s i g n e d l o n g ∗ gnd ; / / w i r e gnd : 1 b i t s , 1 words
u n s i g n e d l o n g ∗ vdd ; / / w i r e vdd : 1 b i t s , 1 words
/ / wires in s u b c i r c u i t s :
} counter_example ;
} signals ;
u n s i g n e d l o n g memory [ 1 0 ] ;
};
# ifdef __cplusplus
e x t e r n "C" {
# endif
40
39
38
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
Listing C.7: SystemC-Header-File des generierten CModells
}
~counter_example ( )
{
d e l e t e _ s i m u l a t o r ( sim ) ;
}
SC_CTOR ( c o u n t e r _ e x a m p l e )
{
sim = n e w _ s i m u l a t o r ( ) ;
i n i t _ s i m u l a t o r ( sim ) ;
SC_METHOD( i n i t ) ;
s e n s i t i v e << r e s e t ;
SC_METHOD( c y c l e ) ;
s e n s i t i v e << c l o c k ;
}
void c y c l e ( )
{
sim−> s i g n a l s . c o u n t e r _ e x a m p l e . e n a b l e [ 0 ] =
( unsigned long ) ( e n a b l e . read ( ) ) ;
c y c l e _ s i m u l a t o r ( sim ) ;
c o u n t _ o u t . w r i t e ( ( ( s c _ b i g u i n t <2 >) ( sim−>
s i g n a l s . counter_example . count_out
[0]) ) ) ;
}
void i n i t ( )
{
i n i t _ s i m u l a t o r ( sim ) ;
}
s i m u l a t o r _ t sim ;
SC_MODULE( c o u n t e r _ e x a m p l e )
{
/ / Ports
sc_in <bool > r e s e t ;
sc_in <bool > c l o c k ;
sc_in <bool > e n a b l e ;
s c _ o u t < s c _ u i n t <2> > c o u n t _ o u t ;
# include " systemc . h"
# include " counter_example . h"
ANHANG C. HDCAML-DETAILS
103
ANHANG C. HDCAML-DETAILS
104
21
−− HDCaml s h o r t h a n d f u n c t i o n s :
f u n c t i o n eq ( a , b : s t d _ l o g i c _ v e c t o r ) r e t u r n s t d _ l o g i c i s
begin
i f a = b t h e n r e t u r n ’ 1 ’ ; e l s e r e t u r n ’ 0 ’ ; end i f ;
end ;
f u n c t i o n eq ( a , b : s t d _ l o g i c )
return s t d _ l o g i c i s
begin
i f a = b t h e n r e t u r n ’ 1 ’ ; e l s e r e t u r n ’ 0 ’ ; end i f ;
end ;
22
23
24
25
26
27
28
29
30
31
function l t ( a , b : std_logic_vector )
begin
i f ( unsigned ( a ) < unsigne d ( b ) ) then
end ;
function l t ( a , b : std_logic )
begin
i f ( ( a = ’ 0 ’ ) and ( b = ’ 1 ’ ) ) t h e n
end ;
32
33
34
35
36
37
38
39
return s t d _ l o g i c i s
r e t u r n ’ 1 ’ ; e l s e r e t u r n ’ 0 ’ ; end i f ;
return s t d _ l o g i c i s
r e t u r n ’ 1 ’ ; e l s e r e t u r n ’ 0 ’ ; end i f ;
40
function
begin
if s =
end ;
function
begin
if s =
end ;
41
42
43
44
45
46
47
48
mux ( s : s t d _ l o g i c ; a , b : s t d _ l o g i c _ v e c t o r ) r e t u r n s t d _ l o g i c _ v e c t o r i s
’ 1 ’ t h e n r e t u r n a ; e l s e r e t u r n b ; end i f ;
mux ( s : s t d _ l o g i c ; a , b : s t d _ l o g i c )
return s t d _ l o g i c
is
’ 1 ’ t h e n r e t u r n a ; e l s e r e t u r n b ; end i f ;
Listing C.8: Aus HDCaml-Beschreibung generierter VHDL-Code: Hilfsfunktionen
C.7
Implementierung von bin_tree
Listing C.9, S. 104 zeigt eine mögliche Implementierung der generischen Funktion bin_tree in OCaml
(vgl. Abbildung 3.4, S. 35). Wenn die Anzahl der Elemente der Liste keine Zweierpotenz ist, dann müssen
1
l e t b i n _ t r e e bin_op l =
2
l e t r e c combine l
match l w i t h
[]
−>
| [a]
−>
| a : : b : : r e s t −>
in
3
4
5
6
7
8
=
[]
[a]
( b i n _ o p a b ) : : ( combine r e s t )
9
l e t rec r e p e a t l =
if List . length l = 1
t h e n L i s t . hd l
e l s e r e p e a t ( combine l )
in
10
11
12
13
14
15
repeat l
16
17
;;
Listing C.9: Implementierung von bin_tree
zu irgendeinem Zeitpunkt in der Abarbeitung der Liste mindestens einmal zwei Elemente aus unterschiedlichen Ebenen miteinander verknüpft werden. Die vorliegende Implementierung besitzt nur den binären
Operator bin_op als Parameter. Sie reicht ein solches einzelnes Element unverändert an die nächste Ebene weiter (Zeile 6). Das könnte zu wenig flexibel sein, z.B. wenn man in einem Hardware-Modell zur
ANHANG C. HDCAML-DETAILS
105
Geschwindigkeitssteigerung zwischen den Ebenen Register haben will (pipelining). Man könnte auch als
weiteren Parameter einen unären Operator einführen, der in diesem Fall auf das einzelne Element angewandt wird.
C.8
Varianten der Verwendung von HDCaml/OCaml
Man hat bei der Verwendung von HDCaml die Wahl zwischen mindestens drei Varianten:
Übersetzung und Ausführung in einem Schritt:
ocaml hdcaml.cma example.ml
Zwei getrennte Schritte mit explizitem Erzeugen einer ausführbaren Datei:
ocamlc -g -o example.exe hdcaml.cma example.ml
ocamlrun -b example.exe
Das ist die einzige Variante, mit der der Exception-Workaround (Abschnitt 5.3.1, S. 67) zum Anzeigen von
Zeilennummern funktioniert. Ab OCaml 3.10 kann auch bei der Verwendung des native-code compilers
( ocamlopt ) mit der Option -g Debug-Code in die ausführbare Datei aufgenommen werden. Die Option
zur Aktivierung von Stack-Traces ( -b ) kann dabei nicht bei ocamlrun angegeben werden, da dieser
Befehl nur für Bytecode verwendbar ist. Stattdessen muss die Umgebungs-Variable OCAMLRUNPARAM
geignet gesetzt werden:
export OCAMLRUNPARAM=b
ocamlopt -g -o example.exe hdcaml.cmxa example.ml
./example.exe
Unbedingt empfehlenswert ist der optimierende Compiler bei Verwendung der eingebauten Simulation (vgl.
Abschnitt 5.6.4, S. 79). Da die Übersetzungsdauer aber fast gleich wie bei Bytecode-Erzeugung ist, kann
man ruhig immer ocamlopt verwenden.
Die Datei hdcaml.cma (bzw. hdcaml.cmxa bei Verwendung des optimierenden Compilers) ist die
Programmbibliothek, die die Funktionalität von HDCaml enthält, und muß bei den ersten beiden Varianten
immer explizit angegeben werden.
Erzeugen eines „custom toplevel systems“
Mittels ocamlmktop -o hdcaml.exe hdcaml.cma
und Ablegen dieser ausführbaren Datei irgendwo im Suchpfad des Betriebssystems, kann diese Datei anschließend zum Ausführen eines HDCaml-Programms in einem Schritt (wie in der ersten beschriebenen
Variante) verwendet werden, z.B.:
hdcaml.exe example.ml
Bei dieser Variante werden hdcaml.cma und ocaml zu einem neuen OCaml interactive toplevel namens hdcaml.exe verbunden, das gleich wie ocaml zu verwenden ist, aber die benötigten Bibliotheksfunktionen bereits enthält.
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement