Einführung in die Informatik 2

Werbung
Einführung in die Informatik 2
Benjamin Gufler
Erstellt mit LATEX
II
Inhaltsverzeichnis
II Rechnerstruktur, Hardware, Maschinennahe Programmierung
1
1 Codierung / Informationstheorie
1.1 Codes / Codierung . . . . . . . . . . .
1.1.1 Binärcodes einheitlicher Länge
1.1.2 Codes variabler Länge . . . . .
1.1.3 Serien-/ Parallelwortcodierung
1.1.4 Codebäume . . . . . . . . . . .
1.2 Codes und Entscheidungsinformation .
1.3 Sicherung von Nachrichtenübertragung
1.3.1 Codesicherung . . . . . . . . .
1.3.2 Übertragungssicherheit . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
6
7
7
8
9
9
10
2 Binäre Schaltnetze und Schaltwerke
2.1 Boolesche Algebra / Boolesche Funktionen . . . . . . . .
2.1.1 Boolesche Funktionen . . . . . . . . . . . . . . .
2.1.2 Partielle Ordnung auf BF . . . . . . . . . . . . .
2.2 Normalformen boolescher Funktionen . . . . . . . . . . .
2.2.1 Das boolesche Normalformtheorem . . . . . . . .
2.2.2 Vereinfachte Normalformen (DNF) . . . . . . . .
2.3 Schaltnetze . . . . . . . . . . . . . . . . . . . . . . . . .
2.3.1 Schaltfunktionen und Schaltnetze . . . . . . . . .
2.3.2 Darstellung von Schaltnetzen . . . . . . . . . . .
2.3.3 Halbaddierer . . . . . . . . . . . . . . . . . . . .
2.3.4 Arithmetische Schaltnetze . . . . . . . . . . . . .
2.3.5 Zahldarstellung . . . . . . . . . . . . . . . . . . .
2.3.6 Weitere arithmetische Operationen . . . . . . . .
2.3.7 Schaltnetze zur Übertragung von Information . .
2.4 Schaltwerke . . . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Schaltwerksfunktionen . . . . . . . . . . . . . . .
2.4.2 Schaltfunktionen als Schaltwerksfunktionen . . .
2.4.3 Schaltwerke . . . . . . . . . . . . . . . . . . . . .
2.4.4 Schaltwerksfunktionen und endliche Automaten .
2.4.5 Schaltwerke zum Speichern: Verzögerungsnetze .
2.4.6 Klassen von Schaltwerken . . . . . . . . . . . . .
2.4.7 Komposition von Schaltwerken und Schaltnetzen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
11
13
13
13
14
15
15
16
18
20
21
24
24
25
25
26
26
26
27
28
29
3 Aufbau von Rechenanlagen
3.1 Strukturierter Aufbau von
3.1.1 Der Rechnerkern .
3.1.2 Speichereinheit . .
3.1.3 E/A . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
31
31
33
33
33
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Rechenanlagen
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
III
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
IV
INHALTSVERZEICHNIS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
35
37
38
4 Maschinennahe Programmierung
4.1 Maschinennahe Programmiersprachen . . . . . . . . . . .
4.1.1 Binärwörter als Befehle . . . . . . . . . . . . . . .
4.1.2 Der Befehlsvorrat der MI . . . . . . . . . . . . . .
4.1.3 Einfache Maschinenprogramme . . . . . . . . . . .
4.1.4 Assemblersprachen . . . . . . . . . . . . . . . . . .
4.1.5 Ein- und Mehradressform . . . . . . . . . . . . . .
4.1.6 Unterprogrammtechniken . . . . . . . . . . . . . .
4.2 Adressiertechniken und Speicherverwaltung . . . . . . . .
4.2.1 Konstante . . . . . . . . . . . . . . . . . . . . . . .
4.2.2 Operandenversorgung über Register . . . . . . . .
4.2.3 Absolute Adressierung . . . . . . . . . . . . . . . .
4.2.4 Relative Adressierung . . . . . . . . . . . . . . . .
4.2.5 Indizierung, Zugriff auf Felder . . . . . . . . . . . .
4.2.6 Symbolische Adressierung . . . . . . . . . . . . . .
4.2.7 Geflechtsstrukturen und indirekte Adressierung . .
4.2.8 Speicherverwaltung . . . . . . . . . . . . . . . . . .
4.2.9 Stackverwaltung von blockstrukturierten Sprachen
4.3 Techniken maschinennaher Programmierung . . . . . . . .
4.3.1 Auswertung von Ausdrücken / Termen . . . . . . .
4.3.2 Maschinennahe Realisierung von Ablaufstrukturen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
39
39
40
40
41
43
44
45
47
47
48
48
48
48
49
50
52
52
54
55
56
3.2
3.1.4 Befehle und Daten auf Maschinenebene . . .
3.1.5 Operandenspezifikation und Adressrechnung .
3.1.6 Der Befehlszyklus . . . . . . . . . . . . . . .
Hardwarekomponenten . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
Teil II
Rechnerstruktur, Hardware,
Maschinennahe
Programmierung
1
3
Stichworte:
• Zeichenweise Darstellung von Information → Codierung
• Schaltungen / Schaltwerke - Schaltlogik
• Rechnerstrukturen: Aufbau von Rechneranlagen
• Maschinennahe Programmierung
4
Kapitel 1
Codierung /
Informationstheorie
In der Codierung studieren wir die Darstellung von Information durch Zeichenfolgen
(vgl. formale Sprache).
1.1
Codes / Codierung
Wir konzentrieren uns stark auf eine Darstellung durch zwei Zeichen (Bits - Binary
Digits).
B = {L, 0}
Bn Menge der Binärwörter der Länge n (n - Bit - Wörter)
Sei A ein Alphabet (linear geordneter endlicher Zeichensatz), B ein Alphabet.
c : A → B Code / Codierung
c : A∗ → B ∗ Wortcodierung
Wichtig auf einer Codierung ist die Umkehrbarkeit:
d : {c(a) : c ∈ A} → A
mit d(c(a)) = a. Voraussetzung: c injektiv.
Binärcodierung:
c : A → B∗
Achtung: B∗ ist lexikographisch linear geordnet (0 < L).
1.1.1
Binärcodes einheitlicher Länge
Wir betrachten nun für gegebenes n ∈ N die Abbildung c : A → Bn .
Achtung: Wir setzen voraus, dass c injektiv ist, aber nicht notwendigerweise surjektiv.
Für zwei Codewörter aus Bn können wir nach dem Abstand fragen, wir sprechen
von Hamming-Abstand:
hd : Bn timesBn → N0
n
X
hd(ha1 , . . . , an i , hb1 , . . . , bn i) =
d(ai , bi )
i=1
5
6
KAPITEL 1. CODIERUNG / INFORMATIONSTHEORIE
wobei für x, y ∈ B gilt
(
1
d(x, y) =
0
falls x 6= y
falls x = y
Auf dieser Basis definieren wir einen Hammingabstand für einen Binärcode c:
hd(c) = min {hd(c(a), c(b)) : a, b ∈ A ∧ a 6= b}
hd(c) = 0
c nicht injektiv.
Um den Hammingabstand hoch zu halten, benutzt man Paritätsbits.
Beispiel Gegeben sei eine Codierung c : A → Bn mit hd(c) ≥ 1. Gesucht ist eine
Codierung c0 : A → Bn+1 mit hd(c) ≥ 2. Wir definieren:
(
L falls die Quersumme von b gerade ist
pb : Bn → B, pb(b) =
0 sonst
und für a ∈ A
c0 (a) = c(a) ◦ hpb(c(a))i
Behauptung: hd(c0 ) ≥ 2
Beweis: Seien a, a0 ∈ A, a 6= a0 gegeben.
1. Fall: hdc (a, a0 ) ≥ 2 ⇒ hdc0 (a, a0 ) ≥ 2
2. Fall: hdc (a, a0 ) = 1. Dann gilt pb(a) 6= pb(a0 ), da die Quersumme von a gerade ist,
wenn die Quersumme von a0 ungerade ist und umgekehrt. Also ist hdc0 (a, a0 ) = 2.
Wichtiges Thema der Codierung: Kryptographie: Verschlüsselung von Nachrichten.
Weiteres Thema: Codierung der Ziffern.
Direkter Code: c : {0, . . . , 9} → B4
c(z)
1-aus-10-Code
z
0 0000
000000000L
1 000L
00000000L0
2 00L0
0000000L00
3 00LL
000000L000
4 0L00
00000L0000
0000L00000
5 0L0L
6 0LL0
000L000000
7 0LLL
00L0000000
8 L000
0L00000000
9 L00L
L000000000
1.1.2
Codes variabler Länge
Wir betrachten nun Codes, bei denen die Codewörter unterschiedliche Längen besitzen.
Beispiele
• altes Fernsprechwählsystem:
1 7→ L0
2 7→ LL0
..
.
0 7→ LLLLLLLLLL0
• Morsecode
1.1. CODES / CODIERUNG
1.1.3
7
Serien-/ Parallelwortcodierung
Bei der Übertragung von binär codierter Information unterscheiden wir zwei Situationen:
• ein Draht / sequentielle Übertragung
• n Drähte (n > 1) / parallele Übertragung
Bei Übertragung eines Wortes w ∈ A∗ mit Binärcodierung c : A → B∗ einfachstes
Vorgehen:
c∗ : A∗ → B∗
c∗ (ha1 , . . . , an i) = c(a1 ) ◦ c(a2 ) ◦ · · · ◦ c(an )
Kritische Frage: Ist c∗ injektiv, wenn c injektiv ist?
Nein! Gegenbeispiel: Seien a1 , a2 ∈ A mit a1 6= a2 , c(a1 ) =< L > und c(a2 ) =<
LL >. Dann ist c∗ (< a1 a1 a1 >) =< LLL >= c∗ (< a1 a2 >).
Frage: Unter welchen Voraussetzungen ist die Antwort ja?
Antwort:
1. Codes gleicher Länge
2. die Fano-Bedingung gilt
Fano-Bedingung von Coole: Kein Codewort c(a1 ) für a1 ∈ A ist Präfix eines Codeworts c(a2 ) für a2 ∈ A mit a1 6= a2 .
Behauptung: Gilt für c die Fano-Bedingung, so ist c∗ injektiv.
Beweis (durch Widerspruch): Seien a = ha1 , . . . , an i , b = hb1 , . . . , bm i ∈ A∗ , a 6= b
und c∗ (a) = c∗ (b). O.B.d.A. sei n < m. Sei i so gewählt, dass ak = bk für 1 ≤ k ≤ i
und n = i oder ai+1 6= bi+1 .
1. Fall: ai+1 6= bi+1 ; dann gilt c(ai+1 ) Präfix von c(bi+1 ) oder c(bi+1 ) Präfix von
c(ai+1 ). Widerspruch zur Fano-Bedingung.
2. Fall: n = i; dann gilt (wegen m > n) c∗ (hbi+1 , . . . , bm i) = ε. Widerspruch zur
Fano-Bedingung.
Parallelwortcodierung:
c : A → Bn
ck∗ : A∗ → (Bn )∗
ck∗ (ha1 , . . . , ak i) = hc(a1 )i ◦ · · · ◦ hc(ak )i
Beispiel
   
L
0
L
0 0 0
   
L L  0 
0
L
0
L 0 L
0 0 0
L L 0
0 L 0
Trivial: ck∗ injektiv, falls c injektiv.
1.1.4
Codebäume
Codes lassen sich durch Tabellen oder durch Codebäume (s. Abb. (1.1)) darstellen.
B
C
D
A
L 0LL 0L0 00
Trivialerweise ist für Codes, dargestellt durch Codebäume, stets die Fano-Bedingung gegeben.
8
KAPITEL 1. CODIERUNG / INFORMATIONSTHEORIE
L
0
A
L
0
D
L
0
B
C
Abbildung 1.1: Codebaum
1.2
Codes und Entscheidungsinformation
Kernfrage: Wie viel Information trägt eine Nachricht?
Prinzip: Um so ungewöhnlicher (seltener) eine Information ist, desto mehr Informationsgehalt hat sie. Dafür setzen wir gegebenenfalls Wahrscheinlichkeiten für
Nachrichten voraus. Eine stochastische Nachrichtenquelle für ein endliches
Alphabet A erzeugt eine Folge von Zeichen aus A, bei der zu jedem Zeitpunkt die
Wahrscheinlichkeiten für jedes Zeichen einer vorgegebenen zeitunabhängigen Wahrscheinlichkeitsverteilung entsprechen.
Beobachtung: Seltene Zeichen tragen viel Information, häufige Zeichen
wenig.
P
Gegeben: A Alphabet, p : A → [0, 1] Wahrscheinlichkeiten (d.h. a∈A p(a) = 1 und
p(a) > 0∀a ∈ A).
Mittlerer Entscheidungsgehalt (Entropie):
X
1
H=
p(a) ld
p(a)
a∈A
Entropie: Maß für Gleichverteilung der Wahrscheinlichkeiten: H groß ⇔ Wahrscheinlichkeiten etwa gleichverteilt, H klein ⇔ starke Unterschiede in den Wahrscheinlichkeiten.
H
Zeichen i p(i)
1
a
2
1
1
b
Beispiel
2
3
a
4
≈ 0, 8
1
b
4
Seien für die Zeichen aus A Wahrscheinlichkeiten p : A → [0, 1] sowie eine Binärcodierung c : A → B gegeben. Wir können die mittlere Wortlänge L berechnen als
X
L=
p(a)|c(a)|
a∈A
wobei |c(a)| die Länge von c(a) bezeichnet. Um L klein zu halten, codieren wir Zeichen mit großen Wahrscheinlichkeiten mit kurzen Codewörtern. Größere Spielräume
erhalten wir, wenn wir nicht Einzelzeichen, sondern Wörter aus A∗ codieren. →
Huffman-Algorithmus
P
Jede Menge M ⊆ A von Zeichen hat auch eine Wahrscheinlichkeit p(M ) = a∈M p(a).
Es ist p(A) = 1. Ziel: A so in zwei disjunkte Teilmengen A0 und AL aufteilen, dass
p(A0 ) ≈ 12 ≈ p(AL ) ist.
1.3. SICHERUNG VON NACHRICHTENÜBERTRAGUNG
Beispiel
i
a
b
p(i)
3
4
1
4
i
aa
ab
ba
bb
9
p(i)
L=
9
16
3
16
3
16
1
16
1 X
p(w)|c(w)|
m
m
w∈A
Shannonsches Codierungstheorem:
1. Für beliebige Wortcodes gilt H ≤ L.
2. Der Wert L − H kann durch geschickte Wahl der Wortcodes beliebig klein
gemacht werden.
Redundanz eines Codes: L − H
Relative Redundanz eines Codes: 1 − H
L
Gesetz von Merkel: Die Reaktionszeit t einer Versuchsperson, um aus n Gegenständen
einen bestimmten auszuwählen, lässt sich berechen durch
t = 200 + 180 ld(n)[msec]
1.3
Sicherung von Nachrichtenübertragung
Beim Übertragen von Nachrichten sind zwei Aspekte von besonderer Bedeutung:
• Kapazität: Wie viele Informationseinheiten pro Zeiteinheit können übertragen
werden?
• Störung: Wie hoch ist die Wahrscheinlichkeit, dass Daten fehlerhaft übertragen werden?
1.3.1
Codesicherung
Diskreter Kanal ohne Speicher: Überträgt Binärwerte, wobei Störungen auftreten
können, deren Wahrscheinlichkeiten zu jedem Zeitpunkt gleich sind (vgl. stochastische Nachrichtenquelle).
Irrtumswahrscheinlichkeiten:
p0 Wahrscheinlichkeit, dass das Zeichen 0 als L übertragen wird
pL Wahrscheinlichkeit, dass das Zeichen L als 0 übertragen wird
einseitige Störung: (p0 = 0 ∧ pL > 0) ∨ (p0 > 0 ∧ pL = 0)
symmetrische Störung: p0 = pL
In manchen Fällen führt eine Störung nicht zu einer Veränderung L zu 0 oder 0
zu L, sondern führt auf ein Zeichen, das als fehlerhaft erkannt werden kann (Verlustzeichen). Wir erhalten eine Übertragung von Zeichen aus {0, L} in Zeichen aus
{0, L, ⊥} (⊥: Fehlerzeichen).
Ziel bei Kanälen mit Übertragungsfehlern: Die Wahrscheinlichkeit für Fehler soll
möglichst klein gehalten werden, d.h. Fehler sollen erkannt und korrigiert werden
(durch Redundanz).
Lemma: Hat ein Code Hammingabstand h, so können Störungen, die weniger als h
Zeichen betreffen, sicher erkannt werden.
Idee: Prüf- / Paritätsbits einsetzen
10
1.3.2
KAPITEL 1. CODIERUNG / INFORMATIONSTHEORIE
Übertragungssicherheit
Kanäle haben beschränkte Übertrangugskapazität:
s1 : Anzahl der Zeichen, die ein Kanal pro Zeiteinheit überträgt
s0 : Anzahl der Zeichen, die pro Zeiteinheit übertragen werden sollen
R = ss01 : Senderate
R < 1: Überkapazität, d.h. Codesicherung durch Redundanz möglich
R > 1: Unterkapazität: Nachricht kann nur in Teilen übertragen werden, kommt
gestört an.
Beispiel R = 31 → wir übertragen statt der Einzelzeichen jedes Zeichen drei Mal
hintereinander. Bei Störungen mit pE < 0, 5 decodieren wir wie folgt:
000, 00L, 0L0, L00 → 0
Die Fehlerrate sei p für Einzelzeichenübertragung; dann ist 3p2 − 2p3 die Wahrscheinlichkeit einer Störung bei Dreifachübertragung
14
Liegt die Übertragungsrate bei 16
, dann können wir 14-Bit-Wörter als 16-BitWörter mit 2 Paritätsbits übertragen.
Falls R ≥ 1 ist, reicht die Kapazität nicht aus. Trotzdem können wir Nachrichten
(gestört) übertragen.
Beispiel R = 3, d.h. wir sollen drei Mal so viel Nachrichten übertragen, als
möglich ist.
Idee: Drei Bits werden auf ein Bit komprimiert (Mehrheitstechnik. s.o.) und beim
Empfang wieder in drei gleiche Bits umgewandelt.
Irrtumswahrscheinlichkeit: (R > 1)
pE =
p
R−1
+
R
2R
Kapitel 2
Binäre Schaltnetze und
Schaltwerke
Zur Darstellung diskreter (symbolischer) Information reichen zwei Werte (Bits)
aus. Zur Darstellung technischer Art bits es viele Möglichkeiten:
• elektrische:
– Spannung / keine Spannung
– Strom / kein Strom
– Licht / kein Licht
• mechanische:
– Wasser / kein Wasser
– Dampf / kein Dampf
Wir betrachten im Weiteren nicht die technische Darstellung von Bits, sondern studieren, wie wir bestimmte Information (Zahlen, Datenstrukturen) durch Bitsequenzen darstellen können und wir wir Verarbeitungsvorgänge durch boolesche Funktionen realisieren können.
2.1
Boolesche Algebra / Boolesche Funktionen
Die Wahrheitswerte bilden mit den Operationen ¬, ∨ und ∧ eine boolesche Algebra.
Prädikate bilden ebenfalls eine boolesche Algebra: Statt einer Prädikatsdarstellung
p : M → B können wir stets eine Mengendarstellung S ⊆ M verwenden. Jedes
Prädikat über M definiert genau eine Teilmenge von M und umgekehrt.
Sp = {x ∈ M : p(x)}: p(x) = (x ⊂ Sp)
Allgemein gilt: ∧ ∼ ∩, ∨ ∼ ∪, ¬ ∼ Komplement und ⇒∼⊆.
2.1.1
Boolesche Funktionen
n-stellige boolesche Funktion: f : Bn → B
Beispiel:
1. 0-stellige boolesche Funktionen: Bn = {ε}
Es gibt genau zwei 0-stellige boolesche Funktionen: true, false.
11
12
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
2. 1-stellige boolesche Funktionen: f : B → B
true
false
x
¬x
konstant L
konstant 0
Identität
Negation
3. 2-stellige boolesche Funktionen: f : B2 → B
true
false
x
y
¬x
¬y
x∨y
x∧y
x ∨ ¬y
¬x ∨ y
Konstanten
(x ∧ y) ∨ (¬x ∧ ¬y)
(x ∧ ¬y) ∨ (¬x ∧ y)
¬(x ∨ y)
¬(x ∧ y)
x ∧ ¬y
¬x ∧ y
Äquivalenz
Antivalenz
nor
nand
Bisubtraktion
Projektionen
negative Projektionen
Disjunktion
Konjunktion
Inverse Implikation, Subjunktion
Implikation
4. 3-stellige boolesche Funktionen: f : B3 → B
...
n
Anzahl der n-stelligen booleschen Funktionen 2(2 )
Für jedes n ∈ N bilden die n-stelligen booleschen Funktionen eine boolesche Algebra.
BFn : Menge der booleschen Funktionen der Stelligkeit n.
Seien f, g ∈ BFn . Dann sind (¬f ), (f ∧ g) ∈ BFn und es gilt:
• (¬f )(x1 , . . . , xn ) = ¬f (x1 , . . . , xn )
• (f ∧ g)(x1 , . . . , xn ) = f (x1 , . . . , xn ) ∧ g(x1 , . . . , xn )
• (f ∨ g)(x1 , . . . , xn ) = f (x1 , . . . , xn ) ∨ g(x1 , . . . , xn )
Außerdem ist (f ∧ ¬f ), (f ∨ ¬f ) ∈ BFn .
Wie lassen sich boolesche Funktionen darstellen?
1. Durch Formeln oder Aussagenlogik:
Sei f ∈ BFn . Dann können wir f (x1 , . . . , xn ) durch die aussagenlogischen
Terme über den Identifikatoren x1 , . . . , xn (z.B.: f (x1 , x2 , x3 ) = x1 ∨(¬x1 ∧x2 ))
darstellen.
2.2. NORMALFORMEN BOOLESCHER FUNKTIONEN
x1
x2
2. Durch Tabellen:
x3
f (x1 , x2 , x3 )
0
0
0
0
0
0
L
0
0
L
0
L
0
L
L
L
L
0
0
L
13
L
0
L
L
L
L
0
L
L
L
L
L
3. Entscheidungsdiagramme (Bäume)
2.1.2
Partielle Ordnung auf BF
Für eine beliebige boolesche Algebra definieren wir eine partielle Ordnung f ≥ g
durch f ≥ g ⇔def f = f ∧ g. Aus den Gesetzen der booleschen Algebra lässt sich
nachweisen, dass ≥ eine partielle Ordnung ist. f ≥ g ist gleichbedeutend mit f ⇒ g.
f ∨ ¬f ist kleinstes Element: true (schwächste Aussage). f ∧ ¬f ist größtes Element:
false (stärkste Aussage).
2.2
Normalformen boolescher Funktionen
Eine boolesche Funktion kann immer durch boolesche Terme dargestellt werden. Es
existieren viele syntaktisch verschiedene Terme, die die gleiche Funktion darstellen:
(¬x1 ∧ ¬x2 ) ∨ (x1 ∧ x2 ) ≡ (x1 ⇒ x2 ) ∧ (x2 ⇒ x1 ) ≡ (¬x1 ∨ x2 ) ∧ (¬x2 ∨ x1 )
Wir behandeln im Weiteren folgende Fragen:
1. Gibt es einheitliche Termdarstellungen für BF (Normalformen)?
2. Wie können wir aus einer Tabellen- oder Entscheidungsbaumdarstellung eine
Termdarstellung bekommen?
3. Wir können wir besonders einfache und kurze Termdarstellungen bekommen?
Diese Fragen sind auch für die technische Realisierung von booleschen Funktionen durch Schaltungen entscheidend, da Termdarstellungen direkt in Schaltungen
umgesetzt werden können.
2.2.1
Das boolesche Normalformtheorem
Ziel: Wir wollen für beliebige boolesche Funktionen eine Termnormalform definieren
und zeigen, wie diese aus einer Tabelle erzeugt werden kann.
Wir definieren eine disjunktive Normalform (DNF):
hdefi ::= hkfi {∨ hkfi}∗ |0|L
hkfi ::= hliterali {∧ hliterali}∗
hliterali ::= {¬} hidi
Beispiel
(x1 ∧ ¬x2 ∧ x3 ∧ ¬x4 ) ∨ (¬x1 ∧ x2 ∧ x3 ∧ x4 ) ∨ (∧x1 ∧ ¬x2 ∧ x3 ∧ ¬x4 )
Als Identifikatoren wählen wir x1 , . . . , xn . In der vollständigen DNF kommt in jedem
Literal jeder Identifikator genau ein Mal vor.
Hilfskonstruktion: Seien b1 , . . . , bn ∈ B. Wir definieren einen Term
minterm(b1 , . . . , bn ) = (v1 ∧ · · · ∧ vn )
wobei
(
xi
vi =
¬xi
falls bi = L
falls bi = 0
14
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
Beispiel
minterm(0, L, L, 0, L) = (¬x1 ∧ x2 ∧ x3 ∧ ¬x4 ∧ x5 )
Theorem: Für f ∈ BFn gilt:
_
f (x1 , . . . , xn ) =
(f (b1 , . . . , bn ) ∧ minterm (b1 , . . . , bn ))
b1 ,...,bn ∈B
Beweis: durch Induktion über n.
Beispiel
x1
x2
x3
f (x1 , x2 , x3 )
L
L
L
L
L
L
0
L
L
0
L
0
L
0
0
0
0
L
L
0
0
L
0
L
0
0
L
0
0
0
0
0
f (x1 , x2 , x3 ) =(L ∧ x1 ∧ x2 ∧ x3 ) ∨ (L ∧ x1 ∧ x2 ∧ ¬x3 )
∨ (0 ∧ x1 ∧ ¬x2 ∧ x3 ) ∨ (0 ∧ x1 ∧ ¬x2 ∧ ¬x3 )
∨ (0 ∧ ¬x1 ∧ x3 ∧ x3 ) ∨ (L ∧ ¬x1 ∧ x2 ∧ ¬x3 )
∨ (0 ∧ ¬x1 ∧ ¬x2 ∧ x3 ) ∨ (0 ∧ ¬x1 ∧ ¬x2 ∧ ¬x3 )
=(x1 ∧ x2 ∧ x3 ) ∨ (x1 ∧ x2 ∧ ¬x3 ) ∨ (¬x1 ∧ x2 ∧ ¬x3 )
Analog zur disjunktiven gibt es die konjunktive Normalform (KNF); die Rollen von
und und oder sind dort vertauscht.
Die DNF beantwortet die am Eingang gestellten Fragen. Jede boolesche Funktion
hat eine eindeutige vollständige DNF.
2.2.2
Vereinfachte Normalformen (DNF)
Die vollständige DNF lässt sich oft weiter vereinfachen.
Regeln zur Vereinfachung
. . . ∨ (t1 ∧ · · · ∧ ti−1 ∧ xi ∧ ti+1 ∧ · · · ∧ tn ) ∨ . . .
∨(t1 ∧ · · · ∧ ti−1 ∧ ¬xi ∧ ti+1 ∧ · · · ∧ tn ) ∨ . . .
⇒ · · · ∨ (t1 ∧ · · · ∧ ti−1 ∧ ti+1 ∧ · · · ∧ tn ) ∨ . . .
Beispiel
(x1 ∧ x2 ∧ x3 ) ∨ (x1 ∧ x2 ∧ ¬x3 ) ∨ (¬x1 ∧ x2 ∧ ¬x3 )
=(x1 ∧ x2 ) ∨ (¬x1 ∧ x2 ∧ ¬x3 )
Nun können Terme folgender Gestalt auftreten: . . . ∨ (t1 ∧ t2 ) ∨ · · · ∨ (t1 ) ∨ . . . . Diese
Terme können ersetzt werden durch . . . ∨ (t1 ) ∨ . . . .
Durch diese Regeln kann von der vollständigen DNF zur vereinfachten DNF übergegangen werden.
Binäre Entscheidungsdiagramme
Da die Identifikatoren geordnet sind (x1 , . . . , xn ) können wir Entscheidungsbäume
für boolesche Funktionen konstruieren.
• n = 0, d.h. f ∈ BF0 ; der Entscheidungsbaum ist ein Blatt b ∈ B, und zwar
L falls f (ε) = L
0 falls f (ε) = 0
2.3. SCHALTNETZE
15
Abbildung 2.1: Beispiel eines Schaltplans
• n > 0: Wir definieren den Entscheidungsbaum wie folgt:
0
L
EBL EB0
wobei EBL der Entscheidungsbaum der booleschen Funktion fL ∈ BFn−1 mit
fL (x1 , . . . , xn−1 = f (L, x1 , . . . , xn−1 )
und EB0 der Entscheidungsbaum zur booleschen Funktion f0 ∈ BFn−1 mit
f0 (x1 , . . . , xn−1 ) = f (0, x1 , . . . , xn−1 )
seien.
2.3
Schaltnetze
Zur Realisierung von boolschen Funktionen (wir sprechen ab jetzt von Schaltfunktionen) können Schaltnetze verwendet werden. Schaltnetze entsprechen gerichteten,
azyklischen Graphen mit Eingängen und Ausgängen.
Definition: Ein Schaltnetz mit n Eingängen und m Ausgängen ist ein gerichteter,
azyklischer Graph mit n Eingangskanten und m Ausgangskanten, dessen Knoten
mit den Namen boolscher Funktionen markiert sind.
Schaltnetze sind mit boolschen Termen eng verwandt. Jeder boolsche Term lässt
sich als ein Schaltnetz darstellen. Die so entstehenden Schaltnetze haben jedoch
die Eigentümlichkeit, dass jede Kante genau ein Ziel hat. Deshalb führen wir im
Folgenden neben boolschen Termen eine Funktionaltermdarstellung für boolsche
Funktionen ein, durch die beliebige Schaltnetze mit Kanten mit Mehrfachziel (Verzweigung) dargestellt werden können.
Im Folgenden behandeln wir eine Reihe boolscher Funktionen, ihre Darstellung
durch boolsche Terme und durch Funktionsterme (funktionale Terme). Funktionsterme sind durch Verknüpfungen aus Grundfunktionen aufgebaut. Die Verknüpfungen entsprechen der graphischen Darstellung durch Schaltnetze.
Der Schaltplan, gegeben durch das Schaltnetz, kann direkt in eine technische
Realisierung umgesetzt werden.
2.3.1
Schaltfunktionen und Schaltnetze
Schaltfunktion: f : Bn → Nm , m, n ∈ N.
Schaltglied: (s. Abb. 2.2) Ein Schaltnetz besteht aus einer Menge von Schaltgliedern,
die über Kanten verbunden sind. Es entsteht ein gerichteter Graph. In Schaltnetzen
sind keine Zyklen zugelasen. Jede Kante hat genau eine Quelle, unter Umständen
aber mehrere Ziele, d.h. Kanten können sich verzweigen.
16
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
...n...
f
...m...
Abbildung 2.2: Schaltglied
...n...
&
Abbildung 2.3: Konjunktions-Schaltglieder
• Konjunktion: logisches
• Disjunktion: logisches
und (s. Abb. 2.3)
oder (s. Abb. 2.4)
• Negation: (s. Abb. 2.5)
Sei ein Schaltnetz mit n Eingängen und m Ausgängen gegeben. Wird für jedes
Schaltglied des Netzes eine Schaltfunktion angegeben, so definiert das Schaltnetz
selbst wieder eine Schaltfunktion aus SFm
n . In einem Schaltnetz entspricht
• jedes Schaltglied einer Schaltfunktion
• jede Kante einem Wahrheitswert
2.3.2
Darstellung von Schaltnetzen
Es gibt zwei grundlegende Möglichkeiten, Schaltnetze durch Formeln darzustellen:
1. benannte Kanten
2. Kombination (Komposition) von Schaltgliedern
Benannte Kanten: Jeder Knoten in einem Netz (vgl. Abb. 2.6) entspricht einer
Gleichung (y1 , . . . , ym ) = f (x1 , . . . , xn ). Dieses Darstellungsverfahren eignet sich
gut für kleine Schaltnetze ohne Regelmäßigkeiten.
Besser für große Schaltnetze mit starken Regelmäßigkeiten eignen sich kombinatorische Schreibweisen.
...n...
>= 1
Abbildung 2.4: Disjunktions-Schaltglieder
2.3. SCHALTNETZE
17
1
Abbildung 2.5: Negations-Schaltglieder
x1
...
xn
f
y 1 ... y m
Abbildung 2.6: Benannte Kanten
m2
1
1. Parallele Komposition (s. Abb. 2.7): Seien f1 ∈ SFm
n1 und f2 ∈ SFn2 . Dann ist
m1 +m2
f = (f1 k f2 ) ∈ SFn1 +n2 , und es gilt f (x1 , . . . , xn1 +n2 ) = (y1 , . . . , ym1 +m2 ),
wobei
f1 (x1 , . . . , xn1 ) = (y1 , . . . , ym1 )
f2 (xn1 +1 , . . . , xn1 +n2 ) = (ym1 +1 , . . . , ym1 +m2 )
0
m
2. Sequentielle Komposition (s. Abb. 2.8): Seien f1 ∈ SFm
n , f2 ∈ SFm . Es ist
m0
f = (f1 ◦ f2 ) ∈ SFn mit f (x1 , . . . , xn ) = f2 (f1 (x1 , . . . , xn )).
Eigenschaften: k, ◦ sind assoziativ, aber nicht kommutativ.
Zusatzfunktionen
• Projektion
πin ∈ SF1n
1≤i≤n
n
πi (x1 , . . . , xn ) = xi
• Identität
I ∈ SF11
In ∈ SFnn
I(x) = x
In (x1 , . . . , xn ) = (x1 , . . . , xn )
f
...
...
f1
f2
...
...
Abbildung 2.7: Parallele Komposition
18
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
n
...
f
f1
m
...
f2
...
m’
Abbildung 2.8: Sequentielle Komposition
• Verzweigung
V (x) = (x, x)
V ∈ SF21
Vn ∈ SF2n
n
Vn (x1 , . . . , xn ) = (x1 , . . . , xn , x1 , . . . , xn )
• Permutation
P ∈ SF22
Pn ∈ SFnn
P (x1 , x2 ) = (x2 , x1 )
Pn (x1 , . . . , xn ) = (x2 , . . . , xn , x1 )
• Tupelung
m2
1
f1 ∈ SFm
n , f2 ∈ SFn
2
[f1 , f2 ] ∈ SFm−1+m
n
[f1 , f2 ] = (Vn ◦ (f1 k f2 ))
• Senken
U ∈ SF01
Un ∈ SF0n
• Konstanten
K(0), K(L) ∈ SF10
2.3.3
Der
a
b
s
u
Kn (0), Kn (L) ∈ SFn0
Halbaddierer
Halbaddierer ist ein Schaltglied HA ∈ SF22 ; HA(a, b) = (u, s).
0 L 0 L
0 0 L L
0 L L 0
0 0 0 L
s = (¬a ∧ b) ∨ (a ∧ ¬b)
u=a∧b
Antivalenz
Diese Gleichungen führen auf den Aufbau gemäß Abbildung 2.9.
Umformung ergibt: s = ¬(¬(a∨b)∨(a∧b)). Damit kann der Halbaddierer effizienter
aufgebaut werden (s. Abb. 2.10).
2.3. SCHALTNETZE
19
a
b
u
s
Abbildung 2.9: Ineffiziente Schaltung für Halbaddierer
a
b
u
s
Abbildung 2.10: Effizientere Schaltung für Halbaddierer
20
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
a
b
c
VA
u
s
Abbildung 2.11: Volladdierer
a
b
c
VA
HA
HA
u
s
Abbildung 2.12: Volladdierer aus Halbaddierern
2.3.4
Arithmetische Schaltnetze
Beim Addieren von Binärworten, die Zahlen binär darstellen, verwenden wir Volladdierer (s. Abb. 2.11).
Tabelle:
a 0 L 0 L 0 L 0 L
b 0 0 L L 0 0 L L
c 0 0 0 0 L L L L
s 0 L L 0 L 0 0 L
u 0 0 0 L 0 L L L
n-stellige Binäraddition in Binärzahldarstellung:
+2 ∈ SFn2n : han . . . a1 i +2 hbn . . . b1 i = hsn . . . s1 i
Zuzüglich wird ein Übertrag u erzeugt: u = hun+1 . . . u1 i mit u1 = 0.
Es gilt
(uk+1 , sk ) = VA(ak , bk , uk )
Dies definiert s1 , . . . , sn , d.h. das Ergebnis hsn . . . s1 i (s. Abb. 2.13). un+1 zeigt an,
ob die Arithmetik übergelaufen ist.
Induktive Definition eines Addiernetzes:
AN1 = VA
ANn+1 = (I2 k An )(VA k In )
2.3. SCHALTNETZE
a n bn
21
un
VA
a 2 b2
a 1 b1 0
VA
VA
...
sn
s2
u3
u n+1
s1
u2
Abbildung 2.13: Addiernetz für zwei n-Bit-Worte
(vgl. Abb. 2.14)
Analog definieren wir die Binärsubtraktion −2 : (Bn )2 → Bn , han . . . a1 i −2
hbn . . . b1 i = hsn . . . s1 i.
si = (ai ∧ ¬bi ∧ ¬ui ) ∨ (¬ai ∧ bi ∧ ¬ui ) ∨ (¬ai ∧ ¬bi ∧ ui ) ∨ (ai ∧ bi ∧ ui )
u1 = 0
ui+1 = (¬ai ∧ bi ) ∨ (¬ai ∧ ui ) ∨ (bi ∧ ui )
Achtung: Der Übertrag repräsentiert nun einen negativen Wert.
Bemerkung: Falls un+1 = L ist, dann gilt b >2 a (>2 : (Bn )2 → B).
Die Subtraktion liefert als Abfallprodukt den Größenvergleich von a und b.
2.3.5
Zahldarstellung
Positive Zahlen (natürliche Zahlen im Intervall [0, 2n − 1]) stellen wir durch die
übliche Gewichtdarstellung dar:
w (han . . . a1 i) =
n
X
w(ai )2i−1
i=1
wobei w(L) = 1 und w(0) = 0 seien.
Zur Darstellung negativer Zahlen: Statt Darstellung der Zahl durch den Absolutwert und ein Vorzeichen wählen wir eine Darstellung mit einem negativen Gewicht.
Einerkomplementdarstellung
c1 : [−2n + 1, 2n − 1] → Bn+1
d1 : Bn+1 → [−2n + 1, 2n − 1]
d1 (c1 (z)) = z ∀z ∈ [−2n + 1, 2n − 1]
d1 (hbn+1 . . . b1 i) = (−2n + 1)w(bn+1 +
n
X
w(bi )2i−1
i=1
Achtung: Wir erhalten zwei Darstellungen der Null!
d1 ( h0 . . . 0i ) = 0 = d1 ( hL . . . Li ) = −2n + 1 +
| {z }
| {z }
positive Null
negative Null
n
X
2i−1
i=1
Die Vorteile der Einerkomplementdarstellung sehen wir, wenn wir arithmetische
Operationen ausführen.
22
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
an
a 1 b1 u
bn
...
AN n
...
sn
u’
a n+1 b n+1
an
bn
s1
a 1 b1 u
...
AN n
u’
VA
u’’
sn
...
s1
AN n+1
s n+1
Abbildung 2.14: zur induktiven Definition des Addiernetzes
2.3. SCHALTNETZE
23
Beispiel: Komplementbildung
d1 (hbn+1 . . . b1 i) = z ⇒ c1 (−z) = h¬bn+1 . . . ¬b1 i
Addition in der Einerkomplementdarstellung: Wir definieren
hsn+2 . . . s1 i = h0an+1 . . . a1 i +2 h0bn+1 . . . b1 i
hrn+2 . . . r1 i = han+1 an+1 . . . a1 i +2 hbn+1 bn+1 . . . b1 i +2 h0 . . . 0sn+2 i
Behauptung: Bei der Addition der durch a und b in Einerkomplementdarstellung
gegebenen Zahlen tritt genau dann ein Überlauf auf (d.h. die Summe der Zahlen
liegt außerhalb von [−2n + 1, 2n − 1]), falls rn+1 6= rn+2 .
an+1 an+1 an . . . a1
bn+1 bn+1 bn . . . b1
sn+1 sn . . . s1
• 1. Fall: an+1 = bn+1 = 0, d.h. beide Zahlen sind positiv, d.h. Überlauf genau
dann, wenn un+1 = L, rn+2 = 0 ⇒ rn+2 6= rn+1 .
• 2. Fall: an+1 6= bn+1 , d.h. eine negative und eine positive Zahl werden addiert.
Gilt un+1 = 0, dann ist rn+1 = rn+2 = L. Gilt un+1 = L, dann ist rn+1 =
r+2 = 0.
• 3. Fall: an+1 = bn+1 = L. Beide Zahlen sind negativ. Nur wenn un+1 = L
ist, findet kein Überlauf statt. Dann gilt rn+1 = rn+2 = L; sonst gilt rn+1 6=
rn+2 ⇒ Überlauf
Ergebnis: In Einerkomplementdarstellung können wir praktisch ohne Aufwand das
Komplement bilden (negieren des Zahlenwerts) und bei Addition der Zahlen unabhängig von der Frage, ob eine der Zahlen (oder beide) negativ ist, ein einfaches
Addiernetz verwenden. Zusätzlich können wir noch durch Verwendung eines zusählichen Bits einen arithmetischen Überlauf feststellen.
Zweierkomplementdarstellung
c2 : [−2n , 2n − 1] → Bn+1
d2 : Bn+1 → [−2n , 2n − 1]
d2 (hbn+1 . . . b1 i) = −2n w(bn+1 ) +
n
X
w(bi )2i−1
i=1
Bemerkungen:
Nur eine Darstellung der Null! d2 (hL . . . Li) = −1.
Komplementbildung:
d2 (hbn+1 . . . b1 i) = z ⇒ c2 (−z) = h¬bn+1 . . . ¬b1 i +2 h0 . . . 0Li
(Einer rückt auf bei der Komplementbildung.)
Dafür wird die Addition einfacher:
hrn+1 . . . r1 i = han+1 an+1 . . . a1 i +2 hbn+1 bn+1 . . . b1 i
Wieder gilt: rn+2 6= rn+1 ⇔ Überlauf bei Addition.
Achtung: Kein Einerrücklauf bei Addition!
24
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
2.3.6
Weitere arithmetische Operationen
Auch Multiplikation und Division von Binärzahlen können wir durch Schaltnetze
darstellen. Wir beschränken uns auf positive Zahlen.
Multiplikation
∗2 : Bm × Bn → Bm+n , ham . . . an i ∗2 hbn . . . b1 i = hpm+n . . . p1 i. i
Zur Definition von p verwenden wir eine Hilfsdefinition für ri = rm+n+
. . . r1i mit
(
ak−i+1 ∧ bi falls i ≤ k ≤ m + i − 1
i
rk =
0
sonst
1
n
. . . r11 .
Damit gilt hpm+n . . . p1 i = rm+n . . . r1n +2 · · · +2 rm+n
Division
/2 : Bm × Bn → Bm−n+1 für m ≥ n; ham . . . a1 i /2 hbn . . . b1 i = hqm−n+1 . . . q1 i.
Wir setzen ham . . . a1 i <2 hbn . . . b1 0i voraus. Dies kann durch anfügen führenden
Nullen (falls hbn . .
. b1 i =
6 h0
werden. Wir definieren
. . . 0i) immer erreicht
eine Folge
von Binärworten rni . . . r0i durch rn0 . . . r00 = ham . . . am−n+1 i. Gilt rni . . . r0i ≥2
h0bm . . . b1 i, so gilt
qm−n−i+1 = L
. . . r0i+1 = rest( rni . . . r0i −2 h0bm . . . b1 i) ◦ ham−n−i+2 i
rni+1
Sonst ist
qm−n−i+1 = 0
i
rni−1 . . . r0i = rn−1
. . . r0i am−n−i+2
Fazit: Alle arithmetischen Operationen auf natürlichen, ganzen Zahlen und Binärbrüchen (Fixpunktschreibweise) können durch Schaltnetze realisiert werden.
Achtung: Genauigkeit beschränkt - wir rechnen in einem endlichen Teilbereich der
Zahlen.
Gleiches gilt für Operationen der Aussagenlogik.
2.3.7
Schaltnetze zur Übertragung von Information
Wir können über Leitungen Nachrichten übertragen. Sind unterschiedliche Partner
an einer Übertragung beteiligt, so besteht oft der Wunsch, dass Nachrichten gezielt
von einem Partner zum anderen übertrangen werden (Bsp. Telefon, E-Mail). Dies
Erfordert eine Adressierung der Nachrichten (im Bsp. Telefonnummern, E-MailAdressen).
Wir betrachten als elementares Beispiel Schaltungen mit Steuerleitungen. Sei {1, . . . , n}
die Menge der Teilnehmer und c : {1, . . . , n} → Bm eine Codierfunktion, die jedem
Teilnehmer einen Binärcode zuordnet. Weiter sei d : Bm → {1, . . . , m}. Forderung:
d(c(i)) = i ∀i ∈ {1, . . . , n}.
Einfaches
- aus - n - Code
* Beispiel: 1 +
c(i) =
. . 0}
0
. . 0}10
| .{z
| .{z
i−1
n−i
Sei ferner eine weniger redundante Binärcodierung c0 für {1, . . . , n} gegeben:
c0 : {1, . . . , n} → Bj
DF : Bj → Bn
DF(c0 (i))0c(i) ∀1 ≤ i ≤ n
2.4. SCHALTWERKE
25
Teilnehmer 1
j
...
Teilnehmer n
k
...
. . .
k
...
DF
n
...
. k. .
Abbildung 2.15: Multiplexer
Eine Schaltung wie in Abbildung 2.15 bledent aus n Eingangsbündeln der Breite
k alle bis auf eines aus; welches weitergegeben wird, bestimmt die Adresse für DF.
Diese Schaltung nennen wir Multiplexer:
MX : Bj × Bk·n → Bk
Demultiplexer:
DMX : Bj × Bk → Bk·m
2.4
Schaltwerke
Bisher haben wir Schaltnetze betrachtet. Diese hatten keine Zyklen. Nun betrachten
wir Schaltwerke. Diese sind wie Schaltwerke, allerdings sind nun Zyklen zugelassen.
In der Praxis wird eine Schaltung nicht nur ein Mal benutzt (d.h. es wird nicht nur
ein Binärwort eingegeben), sondern nacheinander wird eine Folge von Binärwörtern
eingegeben und an den Ausgängen entsteht eine Folge von Binärworten.
2.4.1
Schaltwerksfunktionen
Schaltfunktion: f : Bn → Bm
Schaltwerksfunktion: g : (Bn )∗ → (Bm )∗ , wobei |g(x)| = |x| für x ∈ (Bn )∗ , d.h.
jedes Eingabewort wird auf ein Ausgabewort abgebildet.
Beispiel: Schaltwerksfunktion
x1 . . . xn y1 . . . ym Zeitpunkt (Takt)
(x1 ) L
0
L
0
1
(y 1 )
2
(x ) 0
L
0
L
2
(y 2 )
3
(x ) L
0
L
0
3
(y 3 )
4
0
L
4
(y 4 )
(x ) 0
L
5
(x ) 0
L
0
L
5
(y 5 )
6
(x ) L
L
L
L
6
(y 6 )
..
..
..
..
..
..
..
.
.
.
.
.
.
.
Achtung: Zwischen xi und y j dürfen auch für i 6= j Abhängigkeiten bestehen.
Wichtig: Zeitflusseigenschaft (Kausalität): Ausgaben zum Zeitpunkt j dürfen höchstens von Eingaben zum Zeitpunkt i ≤ j abhängen.
26
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
Präfixordnung auf (Bn )∗ : Für x, y ∈ (Bn )∗ gelte
x v y ⇔def ∃z ∈ (Bn )∗ : x ◦ z = y
Die Kausalität entspricht der Präfix-Monotonie: x v x0 ⇒ g(x) v g(x0 ).
Schaltwerksfunktion: g : (Bn )∗ → (Bm )∗ mit |g(x)| = |x| ∀x ∈ (Bn )∗ und g präfixmonoton (d.h. Kausalität ist gegeben).
2.4.2
Schaltfunktionen als Schaltwerksfunktionen
Gegeben sei eine Schaltfunktion f : Bn → Bm . Wir definieren eine Schaltwerksfunktion
f ∗ : (Bn )∗ → (Bm )∗
durch
f ∗ ( x1 , x2 , . . . , xk ) := f (x1 ), f (x2 ), . . . , f (xk )
Die Schaltwerksfunktion f ∗ hat die Eigenschaft, dass die Ausgabe f (xk ) zum Zeitpunkt k lediglich von der Eingabe xk zum Zeitpunkt k abhängt. Die Funktion
speichert nichts von den früheren Eingaben. Solche Schaltwerksfunktionen heißen auch kombinatorisch, die anderen sequentiell.
2.4.3
Schaltwerke
Ein Schaltwerk ist ein gerichteter Graph mit Knoten, die Schaltwerksfunktionen
entsprechen, und Kanten, die Leitungen entsprechen. Jetzt sind Zyklen zugelassen.
In der Praxis werden wir jedoch nur eingeschränkt Zyklen betrachten.
2.4.4
Schaltwerksfunktionen und endliche Automaten
Ein endlicher Automat hat einen Zustand aus einer endlichen Zustandsmenge state.
Er bekommt in einem gegebenen Zustand eine Eingabe aus einer Eingabemenge in
und erzeugt eine Ausgabe y aus einer Ausgabemenge out und einen neuen Zustand aus state. Wir sprechen von einem Zustand.
Wir stellen die Zustandsübergänge durch eine Zustandsübergangsfunktion
δ : state × in → state × out
dar.
Bei Schaltwerken betrachten wir Automaten, bei denen alle drei Mengen durch
Binärwörter dargestellt werden (z, m, n ∈ N):
δ : Bz × Bn → Bz × Bm
Solche Automaten lassen sich kompakt durch Zustandsübergangsdiagramme darstellen.
Beispiel: B2 × B2 → B2 × B2 . Zustandsübergangsdiagramm: s. Abb. 2.16.
Achtung: Bestimmte Binärkombinationen treten als Zustände und Eingänge nicht
auf.
Die Kreise stellen die Zustände dar. Ein Pfeil vom Kreis σ1 zum Kreis σ2 mit
Beschriftung x/y steht für δ(σ1 , x) = (σ2 , y).
Ein Zustandsübergang besteht in einem Wechsel von einem Zustand σ1 zu einem
Zustand σ2 . Ausgelöst wird der Übergang von einer Eingabe x und er erzeugt eine
Ausgabe y.
Schaltwerke können in ihrem Verhalten durch
2.4. SCHALTWERKE
27
L0/L0
00/0L
0L/0L
L0
0L
00/L0
L0/L0
0L/0L
Abbildung 2.16: Zustandsübergangsdiagramm
• Automaten mit Ein-/Ausgabe
• Schaltwerksfunktion
beschrieben werden. Dabei gilt: jede Schaltewerksfunktion definiert einen Automaten.
Automaten definieren Schaltwerksfunktionen
Gegeben sei δ : state × in → state × out. Jeder Zustand σ ∈ state definiert eine
Schaltwerksfunktion fσ : in∗ → out∗ wie folgt: Seien a ∈ in, x ∈ in∗ und b ∈ out.
fσ (hai ◦ x) = hbi ◦ fσ0 (x) ⇔ delta(σ, a) = (σ 0 , b)
fσ beschreibt das Ein-/Ausgabeverhalten des Automaten, wenn er im Zustand σ
gestartet wird.
Beispiel: obiger Automat Es gilt fL0 (h00i ◦ x) = hL0i ◦ fL0 (x) und f0L (h00i ◦
x) = hoLi ◦ f0L (x), d.h. fσ (h00i ◦ x) = hσi ◦ fσ (x) (Lesegleichung: Durch Eingabe
von h00i wird der Zustand ausgegeben (gelesen) und beibehalten).
Für σ, σ 0 ∈ {0L, L0} gilt fσ (hσ 0 i ◦ x) = hσ 0 i ◦ fσ0 (x) (Schreibgleichung: Der Zustand
σ 0 wird geschrieben: Die Eingabe von σ 0 ∈ {0L, L0} führt dazu, dass der Automat
in den Zustand σ 0 übergeht).
Fazit: Der Automat speichert durch seinen Zustand genau ein Bit, das beliebig oft
gelesen oder überschrieben werden kann.
Schaltwerksfunktionen definieren Automaten
Wir ordnen einer Schaltwerksfunktion f : in∗ → out∗ einen Automaten δf : SWF ×
in → SWF × out zu. Dabei steht SWF für die Menge der Schaltwerksfunktioonen
in∗ → out∗ . Dabei gelte für g, g 0 ∈ SWF, a ∈ in, b ∈ out: δf (g, a) = (g 0 , b), wobei b
und g 0 definiert sind durch g(hai ◦ x) = hbi ◦ g 0 (x) für alle x ∈ in∗ .
Da g eine Schaltwerksfunktion ist, ist b durch a eindeutig bestimmt. Es gilt g 0 (x) =
rest(g(hai ◦ x)). Jede Funktion f ∈ SWF definiert einen Zustand des Automaten.
Jedes Schaltwerk kann in seinem Verhalten entweder als Automat mit Ein-/Ausgabe
oder als Schaltwerksfunktion beschrieben werden - die Darstellungen sind ineinander
überführbar.
2.4.5
Schaltwerke zum Speichern: Verzögerungsnetze
Rückkopplung: das Flip-Flop (s. Abb. 2.17)
Es gilt Qneu = ¬(r ∨ Q), Qneu = ¬(s ∨ Q).
Beispiel für das Schalten des Flip-Flops:
• Schreiben:
28
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
s
Q
Q
r
Abbildung 2.17: Flip-Flop: Nor-Latch
L L L L L ...
s
r
0 0 0 0 0 ...
Q
0 0 L L L ...
Q
L 0 0 0 0 ...
Qneu 0 L L L L . . .
Qneu 0 0 0 0 0 . . .
Idee: Wir wiederholen die gleiche Eingabe aus s und r, bis die Q, Q stabilisieren.
• Lesen:
s
r
Q
Q
Qneu
Qneu
0
0
0
L
0
L
0
0
0
L
0
L
0
0
0
L
0
L
0
0
0
L
0
L
...
...
...
...
...
...
Das Flip-Flop ähnelt in seinem Schaltverhalten stark dem Automaten, den wir
im vorhergehenden Beispiel beschrieben haben. Allerdings treten beim Schreiben
Zwischenzustände auf. Das Flip-Flop stabilisiert sich nach einem Zwischenschritt.
Allgemeine Feststellung:
1. Bei komplizierten Schaltungen können vorher Zwischenschritte erforderlich
sein, um einen stabilen Zustand und eine stabile Ausgabe zu erreichen.
2. Unter gewissen Umständen tritt keine Stabilisierung ein.
Bei Schaltungen, die sich stabilisieren, ergeben sich zwei Sichten:
• Mikrosicht: Automat, Schaltwerksfunktion, in der der Stabilisationsvorgang
sichtbar ist.
• Makrosicht: Abstraktion der Mikrosicht. Wir wiederholen in der Mikrosicht
die Eingabe, bis Stabilisierung eintritt, und nehmen die vorliegende Ausgabe
als Ausgabe und den dann vorliegenden Zustand als neuen Zustand. Die Zahl,
wie oft die Eingabe wiederholt wird, bis eine Stabilisierung erreicht wird,
bestimmt die Frequenz und den Takt der Schaltung.
2.4.6
Klassen von Schaltwerken
Typischerweise baut man nicht Schaltwerke mit beliebig komplizierten, verzögerungsfreien Rückkopplungsleitungen, sondern verwendet Flip-Flops zur Realisierung
von Speichern und verwendet sonst nur verzögerte Rückkopplungen.
2.4. SCHALTWERKE
29
Verzögerung (delay):
Dn : (Bn )∗ → (Bn )∗
a ∈ Bn
Dna (hx1 . . . xn i) = hax1 . . . xn−1 i
d.h. die Ausgabe ist um einen Takt verzögert.
2.4.7
Komposition von Schaltwerken und Schaltnetzen
Aus Speichergliedern (Flip-Flops) und Verzögerungsgliedern können wir, unter Verwendung der Konzepte aus den Schaltnetzen, beliebig komplizierte und mächtige
Schaltungen aufbauen:
• Speicher
• Verarbeitungswerke
• Übertragungsnetze
Damit lassen sich durch Schaltungen Bausteine (Chips) realisieren, aus denen dann
Rechner aufgebaut werden können.
30
KAPITEL 2. BINÄRE SCHALTNETZE UND SCHALTWERKE
Kapitel 3
Aufbau von Rechenanlagen
Eine Rechenanlage kann zunächst als riesiges Schaltwerk begriffen werden. Diese
Sichtweise zeigt uns, wie wir uns die Realisierung einer Rechenanlage vorstellen
können, ist aber für die Nutzung und Programmierung nicht sehr hilfreich. Programme entsprechen auf Schaltwerksebene bestimmten gespeicherten Binkombinationen (Bitwörtern).
Für das Entwickeln größerer Programme brauchen wir eine strukturierte Sicht auf
die Rechenanlage, durch die Programme greifbarer werden.
3.1
Strukturierter Aufbau von Rechenanlagen
Das Grobkonzept eines Rechners (von-Neumann-Architektur) ist sein 50 Jahren
praktisch unverändert. Eine Rechenanlage besteht demnach aus:
• Rechnerkern / Prozessor / CPU
• Speicher
• Verbindungshardware (Bussysteme)
• Peripherie (Ein-/Ausgabevorrichtungen)
Wir sind im Weiteren primär an der Programmierung von Rechenanlagen interessiert. Somit interessiert uns die physikalische Struktur nur insoweit, als sie die
Programme auf Maschinenebene beeinflusst.
Dabei stellt sich die Frage, mit welchen Mitteln die Struktur einer Rechenanlage
modelliert wird. Wir verwenden die Idee der Zustandsmaschine um eine Rechenanlage zu beschreiben. Dabei konzentrieren wir uns auf die Zustandsübergänge, die
durch die Ausführung von Befehlen ausgelöst werden. Der Zustand einer Rechenanlage ist gegeben durch die Belegung ihrer Speicherzellen und Register (spezielle
zusätzliche Speicherzellen für besondere Aufgaben).
Zur Erläuterung der Struktur einer Rechenanlage und ihrer Programmierung verwenden wir eine Maschine MI (angelehnt an die VAX von Digital). Die Bestandteile des Rechners, insbesondere seine Speicherzellen und Register, stellen wir durch
Programmvariable geeigneter Sorten dar und die Wirkungsweise der Programme /
Befehle durch zuweisungsorientierte Programme, die auf diesen Programmvariablen
arbeiten.
Wichtig: Im Speicher stehen Programme und Daten!
Der Modellmaschine MI besteht im Prinzip aus vier Rechnerkernen, zwei E/AProzessoren, dem Arbeitsspeicher und dem Bussystem.
31
32
KAPITEL 3. AUFBAU VON RECHENANLAGEN
Rechnerkern
E/A−Prozessoren
Steuerwerk
Peripherie
Rechenwerk
Bus (Verbindung)
Speichereinheit
Abbildung 3.1: Schematischer Aufbau eines von-Neumann-Rechners
Plattenspeicher
Arbeitsspeicher
E/A 1
E/A 2
Adressbus
Datenbus
Steuerbus
Abbildung 3.2: Spezifische Rechnerarchitektur der MI
. . .
. . .
Rechnerkern
Terminals
3.1. STRUKTURIERTER AUFBAU VON RECHENANLAGEN
3.1.1
33
Der Rechnerkern
Rechnerkern: Hardware-Betriebsmittel, welches autonom den Kontrollfluss steuert (entscheidet, welcher Befehl ausgeführt wird) und die Datentransformationen
ausführt. Der Rechnerkern wird in Steuerewerk und Rechenwerk unterteilt.
Das Steuerwerk enthält den Taktgeber und die Register, die die Information enthalten, die für die Ablaufsteuerung erforderlich ist (Befehlszähler, . . . ). Das Steuerwerk
erzeugt auch die Steuersignale für die Ansteuerung des Rechenwerks.
Das Rechenwerk enthält die Operandenregister und die Schaltnetze/-werke zur
Ausführung der Operationen. Damit bestimmt das Rechenwerk den Befehlsvorrat.
Die Register im Rechnerkern sind Speicherzellen mit extrem kurzen Zugriffszeiten.
Die Bestandteile der MI werden durch folgende Deklarationen von Programmvariablen charakterisiert:
16 Register für 32-Bit-Wörter: var [0:31] array bit R0, ..., R15
Aufgaben:
R0 : R13 frei verwendbar für Programmierung
R14
Zeiger für Stapel (stack pointer, SP)
R15
Befehlszähler (program counter, PC)
Zusäzlich im Steuerewerk:
var [0:7] array bit IR
Instruktionsregister
var [0:31] array bit PSL Prozessorstatusregister
Weitere Hilfsregister für Steuer-/Rechenwerk:
var [0:31] array bit tmp0, ..., tmp3 Hilfsregister für die ALU
var [0:7] array bit am
legt Adressiermodus fest
var [0:31] array bit adr
für die Adressrechnung
var [0:31] array bit index
für die Indexrechnung
Die Anzahl der Register in einem Rechnerkern kann sehr stark schwanken (früher
oft nur ein Register: Akkumulator, heute ganze Registerbänke).
3.1.2
Speichereinheit
Die Speichereinheit besteht aus einer Folge von Speicherzellen, auf die über Adressen zugegriffen wird.
MI:
var [0:232 − 1] array [0:7] array bit M Speicher
var [0:31] array bit MAR
Speicheradressregister
var [0:31] array bit MBR
speicherregister
3.1.3
E/A
Die Ein-/Ausgabe erfolgt über Peripheriegeräte durch die E/A-Prozessoren. (genaueres später)
3.1.4
Befehle und Daten auf Maschinenebene
Auf Maschinenebene werden alle Daten und Befehle durch Bitwörter dargestellt
(Binärdarstellung). Ein Befehl ist ein Binärwort, das im Hauptspeicher gespeichert
werden kann und bei Ausführung (laden in den Rechnerkern und Interpretation)
eine Zustandsänderung der Maschine bewirkt.
Bestandteile eines Befehls:
• Charakterisierung der Operation
• Charakterisierung der Operanden
34
KAPITEL 3. AUFBAU VON RECHENANLAGEN
Wir unterscheiden eine ganze Reihe unterschiedlicher Befehlsarten.
Die Operanden kennzeichnen die Werte, mit denen wir arbeiten: Sie werden immer
durch Bitwörter dargestellt, können aber ganz unterschiedliche Werte bezeichnen:
• Adressen
• Zahlen
• Charakter
Darstellung von Zahlen durch Bitwörter:
natürliche Zahlen
Binärschreibweise
ganze Zahlen
Zweierkomplementdarstellung
Fixpunktdarstellung
Darstellung von Zahlen aus [−1, 1[
Gleitpuntkdarstellung Darstellung von Zahlen durch Wert und Exponent
Festpunktdarstellung: Zweierkomplementdarstellung (32 Bit), Multiplikation mit
2−31 .
Gleitpunktdarstellung: b = hb0 b1 . . . bn bn+1 . . . bn+m i mit b0 : Vorzeichen, b1 . . . bn
Exponent, bn+1 . . . bn+m Mantisse.
Wir berechnen den dargestellten Zahlenwert mit Hilfe folgender Hilfswerte:
e=
n
X
2n−i w(bi )
i=1
v=
m
X
2−i w(bn+1 )
i=1
Es gilt 0 ≤ e < 2n , 0 ≤ v < 1.
exp = e − 2n−1 + 1 ⇒ −2n−1 + 1 ≤ exp ≤ 2n−1
man = 1 + v ⇒ 1 ≤ man < 2
Fälle der Interpretation:
• Normalfall: −2n−1 + 1 < exp < 2n−1 . Wir erhalten als dargestellt Zahl
(−2)w(b0 ) · 2exp · man
• Darstellung der Null: e = 0 ∧ v = 0
• Unterlauf: e = 0 ∧ v 6= 0
• Überlauf: e = emax = 2n − 1 ∧ v = 0
• Fehlerfall: e = emax = 2n − 1 ∧ v 6= 0
Unterlauf, Überlauf und Fehlerfall sind keine gültigen Zahldarstellungen.
Auf Maschinenebene werden alle Daten durch Bitsequenzen dargestellt.
Sorte
Kennung
Wortlänge
sort byte = [0:7] array bit
B
8
sort half-word = [0:15] array bit
H
16
sort word = [0:31] array bit
W
32
sort floating-point = [0:31] array bit
F
32 (8 + 23)
sort double-float = [0:63] array bit
D
64 (11 + 52)
Problem: Einer Ziffernfolge 37216 ist nicht mehr anzusehen, ob sie in Oktal-, Dezimaloder Hexadezimaldarstellung ist.
Ausweg: (37216)8 oktal, (37216)10 dezimal, (37216)16 hexadezimal
Konvention: Wir verwenden Dezimaldarstellung und geben Oktal- und Hexadezimaldarstellung explizit an.
3.1. STRUKTURIERTER AUFBAU VON RECHENANLAGEN
35
Auch Befehle werden auf Maschinenebene durch Bitsequenzen darstestellt. Bequemer und lesbarer sind Darstellungen, bei denen die Teile des Befehls durch Namen
udn Dezimalzahlen dargestellt werden.
Bestandteile eines Befehls:
• Operation: Angabe, welche Umformung bzw. welche Umspeicherung ausgeführt
wird
• Operandenspezifikation: Angabe, mit welchen Werten / Speicherzellen / Registern dabei gearbeitet wird
Syntax:
hcommandi ::= hinstructionParti {hopParti {, hopParti}∗ }
Es gibt Maschinen mit starrem Befehlsformat (festgelegte Länge der Bitsequenz).
Nachteil: Jeder Befehl hat die gleiche Anzahl von Bits für Operandenspezifikationen
zur Verfügung. Vorteil: Jeder Befehl passt genau in eine Speicherstelle, das Ansteuern von Befehlen wird einfacher.
Die MI hat flexible Befehlsformate.
Der Instruktionsteil (Operationsangabe) des Befehls wird im Rechnerkern (im Steuerwerk) in Steuerbits umgesetzt, die die entsprechenden Umformungen und Umspeicherungen auslösen.
3.1.5
Operandenspezifikation und Adressrechnung
Jeder Befehl arbeitet mit bestimmten Werten (Bitsequenzen) und bestimmten Speicherplätzen / Registern. Wir sprechen von Operanden.
Beispiel: Einfachste Art der Operandenspezifikation: wir geben das Register W
R9 (W: Kennung, R9: Register) oder die Speicherzelle W 1743 direkt an. Damit
werden die Speicherzellen 1743-1746 angesprochen.
Die einfachste Form der Angabe von Operanden ist die direkte (absolute) Adressierung: Es werden Register / Adresse explizit angegeben. Weil wir an mehr Flexibilität interessiert sind und uns in einem Programm nicht festlegen wollen, an welcher
Stelle im Speicher es (absolut) steht, verwenden wir kompliziertere Adressierverfahren. Dazu verwenden wir eine Operandenspezifikation. Dies ist eine Angabe, aus der wir die Operanden berechnen können (Adressrechnung). Jeder Operand
entspricht einer Lokation (einem Speicherplatz oder Register; genauer: einem
Ausschnitt aus einem Speicher / Register oder einer Folge). Ein Operand bestimmt
einen bestimmten Platz (Lokation) in der Maschine, an der eine Bitsequenz gespeicher ist.
sort operand = register (type k, nat n) | memo (type k, nat n)
sort type = {B, H, W, F, D}
Eine Operandenspezifikation ist ein syntaktischer Ausdruck, der angibt, wie der
Operand zu berechnen ist. Jede Operandenspezifikation wird bei Ausführung eines Befehls im Befehlszyklusin einen Operanden umgerechnet. Dazu verwenden wir
folgende Hilfsprozeduren:
1
2
4
6
8
fct val = (operand x) seq bit:
if x in register then R[n(x)][32-wl(k(x)):31]
else get (wl(k(x))/8, n(x))
fi
fct get = (nat i, nat n) seq bit:
if i = 0 then empty
else conc (M[n], get(i-1, n+1))
fi
36
KAPITEL 3. AUFBAU VON RECHENANLAGEN
Adressierarten:
• direkte Adressierung: Die absolute Adresse (oder das Register) wird im
Adressteil angegeben.
• direkte Angabe des Operandenwerts im Befehl als Binär-, Hexadezimal- oder Dezimalzahl.
• relative Adressierung: Zum Adressteil im Befehl wird der Inhalt eines
Registers addiert.
• indirekte Adressierung / Adresssubstitution: Die Speicherzelle, die
über den Adressteil angesteuert wird, enthält selbst eine Adresse der Speicherzelle, die den Operanden bildet.
• Indizierung: Zur ermittelten Adresse wird ein weiterer Wert addiert (typische Anwendung: hochzählen eines Zählers in einer Wiederholungsanweisung
zum Zugriff auf Feldelemente)
BNF-Syntax der MI-Operandenspezifikation:
hopParti ::= habsoluteAddressi
| himmediateOpi
| hregisteri
| hrelativeAddressi
| hindexedRelativeAddressi
| hindirectAddressi
| hindexedIndirectAddressi
| hstackAddressi
Ein Teil der Adressierart wird durch folgende Werte bestimmt:
1
2
4
sort operandSpec = opspec (nat reg, bool isr, bool idr,
incr icr, nat rel, indexSpec int)
sort indexSpec = {-} | reg (nat)
sort incr = {-1 | 0 | 1 }
• absolute Adressierung:
habsoluteAddressi ::= hintexpi
(integer expression)
• Operand als Wert:
himmediateOpi ::= I hintexpi
• Register als Operand:
hregisteri ::= R0| . . . |R15|P C|SP
• relative Adresse:
hrelativeAddressi ::= {hintexpi +}! hregisteri
• indizierte relative Adressierung:
hindexedRelativeAddressi ::= hrelativeAddressi hindexi
hindexi ::= / hregisteri /
3.1. STRUKTURIERTER AUFBAU VON RECHENANLAGEN
37
• indirekte Adressierung:
hindirectAddressi ::=!(hrelativeAddressi)|!! hregisteri
• indizierte indirekte Adressierung:
hindexedIndirectAddressi ::= hindirectAddressi hindexi
• Stack-Adressierung:
hstackAddressi ::= −! hregisteri |! hregisteri +
3.1.6
Der Befehlszyklus
Der Befehlszyklus bezeichnet die Folge der Schritte, die bei der Ausführung eines
Befehls ausgeführt werden. Die Maschinen durchlaufen immer wieder die gleiche
Sequenz von Schritten und führen dabei jeweils einen Befehl aus. Wir beschreiben
den Befehlszyklus wiederum durch ein Programm. In der MI besteht ein Befehl aus
einem Operationsteil und bis zu vier Operanden.
In der Maschine ist der Operationsteil durch ein Byte repräsentiert. In diesem Byte
sind die folgenden Informationen verschlüsselt:
fct args = (byte) nat
Anzahl der Operanden
fct kenn = (byte) type
Kennung
fct result = (byte) bool Wird ein Resultat erzeugt?
Der Befehlszyklus der MI entspricht dem folgenden Programm.
1
2
4
6
8
10
12
14
16
18
20
22
24
26
28
while not(stop) do
fetch_operator;
if 1 <= args(IR) then
fetch_operand (kenn(IR), tmp1[32-wl(kenn(IR)):31])
fi;
if 2 <= args(IR) then
fetch_operand (kenn(IR), tmp2[32-wl(kenn(IR)):31])
fi;
if 2 <= args(IR) then
fetch_operand (kenn(IR), tmp3[32-wl(kenn(IR)):31])
fi;
execute (IR);
if result (IR) then
put_result
fi
od
proc fetch_operator =:
fetch (B, R15, IR);
R15 := R15 + 1
proc fetch = (type k, [0:31] array bit adr,
var array[0:wl(k)-1] array bit r):
MAR := adr;
for i := 1 to wl(k)/8 do
MBR[(i-1)*8:i*8-1] := M[MAR];
MAR := MAR + 1
od;
r := MBR[0:wl(k)-1]
38
KAPITEL 3. AUFBAU VON RECHENANLAGEN
30
32
34
36
38
proc put = (type k, [0:31] array bit adr,
var array[0:wl(k)-1] array bit r):
MAR := adr;
MBR[0:wl(k)-1] := r;
for i := 1 to wl(k)/8 do
M[MAR] := MBR[(i-1)*8:i*8-1];
MAR := MAR + 1;
od
Für die Adressrechnung zur Ermittlung der Operanden benötigen wir eine weitere
Information, die aus dem Byte ermittelt wird, das den Adressiermodus bestimmt:
fct reg = (byte) nat
Registerzahl
fct isr = (byte) bool
steht Operand im Register?
fct isrel = (byte) bool relative Adressierung?
fct idr = (byte) bool
indirekte Adressierung?
fct isidx = (byte) bool Indexadressierung?
fct icr = (byte) incr
Registerinkrement / -dekrement
Bei der Ausführung eines Befehls (execute) wird nicht nur ein Ergebnis berechnet
und zurückgeschrieben. Es werden auch bestimmte Bits in einem zusätzlichen Register (dem Prozessorstatusregister) gesetzt. Dabei sind folgende vier Bits für uns
von besonderer Bedeutung:
C carry
Übertrag
V overflow Überlauf
Z zero
Resultat ist Null
N negative Resultat ist negativ
Die Schritte zum Bereitstellen der Operanden und der Abspeicherung des Resultats
sind für alle Befehle, abhängig von der Zahl der Argumente und der Frage, ob ein
Resultat anfällt, gleich. Die Wirkung eines Befehls wird durch die Rechenvorschrift
proc execute = (byte): ...
beschrieben.
3.2
Hardwarekomponenten
Wir unterscheiden folgende Bestandteile einer Rechenanlage:
• Prozessor
• Speicher
– Hauptspeicher
– Hintergrundspeicher
– Speichermittel für das Archivieren
• Eingabegeräte
• Ausgabegeräte
• Datenübertragungsgeräte / Netze
Kapitel 4
Maschinennahe
Programmierung
Die im letzten Kapitel eingeführten Programmstrukturen beschreiben am Beispiel
der MI die Wirkungsweise eines Rechners. Nun führen wir die einzelnen Befehle ein,
beschreiben ihre Wirkung und formulieren erste kurze Maschinenprogramme.
Grundsätzliche Feststellungen:
• Ein Maschinenprogramm ist eine endliche Folge von Befehlen, die zur Ausführungszeit im Hauptspeicher stehen. Damit hat jeder Befehl eine absolute
und eine relative Adresse.
• Diese Befehle werden, gesteuert durch den IC (PC) nacheinander ausgeführt.
• Jedes Programm, das nicht in Maschinensprache geschrieben ist, muss
– von Hand oder durch ein Übersetzungsprogramm (Compiler) in Maschinensprache übersetzt werden, bevor es ausgeführt werden kann, oder
– ein Interpretationsprogramm (Interpreter) angegeben werden; dies ist
ein in Maschinensprache gegebenes Programm, das das Programm in
Programmiersprache durchläuft und dabei die entsprechenden Befehle
ausführt.
4.1
Maschinennahe Programmiersprachen
Struktur, Form und Umfang eines Befehlssatzes und der einzelnen Befehle wird
durch den Rechnerkern geprägt.
Maschinenprogramme sind Folgen von Befehlen und weisen somit keine besondere
Struktur auf. Es ist Aufgabe des Programmierers, Maschinenprogramme strukturiert zu schreiben. Deshalb:
! Trickreiche Programmierung vermeiden!
! Transparente Programmstrukturierung anstreben!
! Ausreichend dokumentieren!
! Umfangreiche Programme in Programmteile strukturieren!
39
40
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
4.1.1
Binärwörter als Befehle
In der MI entspricht ein Programm zur Ausführungszeit einer Folgt von Bytes im
Speicher. In dieser Form ist ein Programm für Menschen praktisch unlesbar.
∗
hbinaryMachineCommandi ::= hbytei
hbinaryAddressi ::={0|L}8L
hbinaryMachineProgrammi ::={hbinaryAddressi hbytei}∗
|{hbinaryAddressi hbinaryMachineCommandi}
Wir vermeiden diese Form der Befehlsangabe und wählen eine etwas lesbarere. Dabei werden Befehle durch Angabe der Befehlsbezeichnung, des Typs der Operanden
und der Adressen in Dezimalschreibweise beschrieben.
4.1.2
Der Befehlsvorrat der MI
Die Wirkung eines Befehls wird durch die Rechenvorschrift execute im Befehlszyklus festgelegt. Dabei werden gewisse Hilfsregister / Spezialregister und insbesondere das PSL (Prozessorstatusregister) geändert.
Beispiele für Befehle der MI:
• Transportbefehle - Umspeichern von Informationen
MOVE W 1000, 2000
Es wird der Inhalt der Speicherzellen 1000 - 1003 in tmp1 geladen, in tmp0
umgesetzt und in die Speicherzellen 2000 - 2003 zurückgespeichert.
MOVE B 1000, 2000
Der Inhalt der Speicherzelle 1000 wird mit Hilfe der temporären Register in
die Zelle 2000 geschrieben.
MOVE B 1000, R4
R4[24:31] wird durch das Byte in Zelle 1000 überschrieben.
MOVEA 1000, 2000
(move address) ist wirkungsgleich zu MOVE W I1000, 2000: der Wert (die
Adresse) 1000 wird den Speicherzellen 2000 - 2003 zugewiesen.
CLEAR B R7
Dieser Befehl bewirkt das Überschreiben von R7[24:31] durch das Byte 0016 .
• Logische Operationen
OR W R8, 3001, R1
bewirkt bitweise Disjunktion der 32 Bit in R8 mit den 32 Bit in 3001 - 3004; das
Resultat wird in Register R1 geschrieben. Typische Anwendung: Maskieren
von Werten.
• Arithmetische Operationen
ADD, SUB, MULT, DIV
Wirkung entsprechend der Zahldarstellung (gesteuert über die Kennung), s.
später
• Vergleichsoperationen
CMP W 3007, R6
Dieser Befehl dient dazu, gewisse Bits im PSL zu setzen. Im Beispiel werden
die 32 Bit in den Zellen 3007 - 3010 in tmp1 und die 32 Bit aus R6 in tmp2
geschrieben. Dann wird tmp1 mit tmp2 verglichen:
1
2
Z := (tmp1 = tmp2)
N := (tmp1 < tmp2)
4.1. MASCHINENNAHE PROGRAMMIERSPRACHEN
41
• In Sprungbefehlen können wir nun auf die so gesetzten Bits Bezug nehmen.
JUMP 5000
Einfacher (unbedingter) Sprung: der Befehlszähler wird mit Adresse 5000 belegt.
JEQ 5000
(jump equal): Der Befehlszähler wird mit Adresse 5000 belegt, falls Z=L
gilt; anderenfalls hat der Befehl keine Wirkung.
• Shiftbefehle erlauben es, die Bits in einem Operanden zu verschieben. (im
Bsp: Rechtsshift um 2 Bit)
LL000L0L
0LLL000L
Zwei Bits werden frei und können entweder durch Nachziehen von 0 oder L
belegt werden oder durch kreisförmiges Durchschieben.
Dieser Befehlssatz ist nur ein Ausschnitt der wichtigsten Befehle der MI. Reale Maschinen haben oft erheblich umfangreichere Befehlssätze, wobei oft nur Abkürzungen für Befehle oder das Zusammenfassen mehrerer Befehle zu einem dadurch
möglich wird.
Wir haben als wichtige Befehle nur die ausgeklammert, die für die Systemprogrammierung von Bedeutung sind.
4.1.3
Einfache Maschinenprogramme
Zur Ausführungszeit der Programme stehen Daten und Programme gleichermaßen
binär codiert im Speicher. Dabei ist es ratsam, Daten und Programme in getrennten
Speicherbereichen abzulegen.
Beispiel: Speicherorganisation
Adresse (hex) Verwendung
0000 0000
reserviert
0000 0400
Programmspeicher
3FFF FFFF
4000 0000
Kontrollbereich (Daten, Stack)
7FFF FFFF
8000 0000
Systembereich
BFFF FFFF
C000 0000
reserviert für Systemprogramme
FFFF FFFF
Die im Beispiel verwendeten Adressen sind virtuell, d.h. die tatsächlich im Hauptspeicher vorhandene Menge von Adressen ist erheblich kleiner. Zur Ausführungszeit
werden den virtuellen Adressen richtige Adressen zugeordnet. Ein Teil der Daten
steht im Hintergrundspeicher und wird bei Bedarf in den Hauptspeicher geladen.
Zur Erhöhung der Lesbarkeit unserer Programme arbeiten wir mit Adressen, ohne
uns um die Frage zu kümmern, ob diese virtuell oder real sind, und verwenden in
Programmen symbolische Adressen für Sprungbefehle.
Beispiele für Maschinenprogramme
42
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
1. Berechnen des Maximums zweier Zahlen: Seien zwei Zahlen (32 Bit) im Speicher durch die Adressen in den Registern R1 und R2 gegeben. Das Maximum
der Zahlen soll in Register R0 geschrieben werden.
1
2
4
6
CMP W !R1, !R2
JLE max2
MOVE W !R1, R0
JUMP ende
max2: MOVE W !R2, R0
ende:
2. Durchsuchen eines Speicherabschnitts. Gegeben seien Adressen in R0 und R1,
wobei R0 ¡ R1 gelte. Der Speicher von R0 bis R1 soll nach einem 32-Bit-Wert,
der in R2 steht, durchsucht werden. Falls der Wert gefunden wird, ist die
Adresse dazu in das Register R3 zu schreiben. Anderenfalls wird 0 nach R3
geschrieben.
Bei etwas komplizierteren Aufgaben ist es hilfreich, die Lösung zuerst als problemorientiertes Programm zu formulieren und dann erst das MI-Programm
zu schreiben.
1
2
4
6
8
R3 := R0;
suche: if M[R3] =
else R3 :=
if R3
fi;
R3 :=
fi
ende:
R2 then goto ende
R3 + 4;
<= R1 then goto suche
0
Umsetzung in ein MI-Programm:
1
2
4
6
8
MOVE W R0, R3
suche: CMP W !R3, R2
JEQ ende
ADD W I4, R3
CMP W R3, R1
JLE suche
CLEAR W R3
ende:
3. Sortieren der Inhalte eines Speicherbereichs.
Aufgabe: Man sortiere die Bytes im Speicher zwischen der Adresse in R0 und
der Adresse in R1 aufsteigend. R2, . . . , R11 dürfen als Hilfsregister verwendet
werden.
Wir verwenden einen einfachen Algorithmus:
1
2
4
6
8
R2 := R0;
while R2 < R1 do
if M[R2] > M[R2+1] then
M[R2], M[R2+1] := M[R2+1], M[R2];
if R0 < R2 then R2 := R2 - 1 fi
else R2 := R2 + 1
fi
od
4.1. MASCHINENNAHE PROGRAMMIERSPRACHEN
43
Wir brechen das Programm in Sprungbefehle auf:
1
2
4
6
8
10
12
R2 := R0;
if R2 >= R1 then goto ende fi;
R3 := R2 + 1;
if M[R2] > M[R3] then goto vertauschen fi;
R2 := R3;
goto m_while;
vertauschen: R4[24:31] := M[R2];
M[R2] := M[R3];
M[R3] := R4[24:31];
if R0 = R2 then goto m_while;
R2 := R2 - 1;
goto m_while;
ende:
m_while:
Nun ist das Schreiben des MI-Programms einfach:
1
2
4
6
8
10
12
14
16
4.1.4
MOVE W R0, R2
CMP W R1, R2
JLE ende
ADD W I1, R2, R3
CMP B !R3, !R3
JLT vertauschen
MOVE W R3, R2
JUMP loop
vertauschen: MOVE B !R2, R4
MOVE B !R3, !R2
MOVE B R4, !R3
CMP W R0, R2
JEQ loop
SUB W I1, R2
JUMP loop
ende:
loop:
Assemblersprachen
Assemblersprachen sind maschinennahe Programmiersprachen, die durch geringfügige Erweiterung reiner Maschinensprachen entstehen. Die Umsetzung von Assemblersprachen in reine Maschinensprachen erfolgt durch Programme, sogenannte
Assemblierer (engl. assembler). Typische Erweiterungen in Assembler sind:
• mehrfache Marken
• eingeschränkte arithmetische Ausdrücke
• direkte Operanden (in der MI ohnehin vorgesehen)
• symbolische Adressierung (analog zu Programmvariablen)
• Segmentierung (Bindungs- und Gültigkeitsbereiche für Namen)
• Definition und Einsetzung von Substitutionstexten (Makros)
• Substitution von Makros = Makroexpansion
Über die geschickte Einführung von Makros können wir auf bestimmte Anwendungssituationen zugeschnittene Assemblerstile definieren.
44
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
4.1.5
Ein- und Mehradressform
Eine wesentliche Charakteristik einer Maschinensprache ist die Anzahl der Operanden pro Befehl.
• starres Befehlsformat: einheitliche Zahl von 0, 1, 2, 3 Operanden pro Befehl
• flexibles Befehlsformat (siehe MI): Befehle haben zwischen 0 und n ∈ N Operanden
Bei starrem Befehlsformat sprechen wir von Einadressform, wenn jeder Befehl genau
einen Operanden enthält (entsprechend Zwei- und Dreiadressform). Wir erhalten bei
Dreiadressform folgende Syntax:
hbefehli ::= hregisteri ::=[hoperandi
| − hoperandi
|AC
| hoperandi hoperatori hoperandi]
if hbedingungi then goto hmarkei f i
|if
|skip
goto hmarkei
|goto
hbefehlsfolgei ::={{hmarkei}∗ hbefehli}∗
hspeedi ::= hkonstantei
|AC
|h[1]|h[2]| . . .
|a|b| . . .
hbedingungi ::= hregisteri = 0
| hregisteri =
6 0
| hregisteri < 0
| hregisteri ≤ 0
hoperatori ::={+, −, ∗, /, . . .}
hregisteri ::=AC
|a|b| . . .
|h[1]|h[2]| . . .
Wir sprechen von einem Programm in Einadressform, wenn in jedem Befehl außer
AC höchstens eine weitere Registerangabe auftritt. Der AC ist das zentrale Rechenregister, auf dem alle Berechnungen ausgeführt werden.
Achtung: Dreiadressformen können immer in Einadressformen übersetzt werden.
Beispiel
a := b + c
Dreiadressform
wird umgeschrieben in
AC := b
AC := AC + c
a := AC
4.1. MASCHINENNAHE PROGRAMMIERSPRACHEN
4.1.6
45
Unterprogrammtechniken
Wird eine Folge von Befehlen an mehreren Stellen in einem Maschinenprogramm
benötigt, so bietet es sich an, das Programm nicht mehrfach zu schreiben, sondern
• ein Makro zu definieren
• ein Unterprogramm zu definieren, d.h. ein Programmstück, auf das über eine
Marke gesprungen werden kann, und das über einen Rücksprung verlassen
wird.
Beim Springen in ein Unterprogramm sind folgende Daten zu übergeben (d.h.
abzuspeichern, so dass das Unterprogramm darauf Zugriff hat):
• Rücksprungadresse
• Parameter
Beim Rücksprung sind unter Umständen gewisse Resultate aus dem Unterprogramm
an das aufrufende Programm zu übergeben. Techniken zur Übergabe der Daten:
• Abspeichern in bestimmten Zellen im Unter- oder Hauptprogramm
• Abspeichern in Registern
• Abspeichern im Stack
Wichtig: Einheitliche, für das ganze Programm gültige Regeln für die Übergabe der
Parameter und Rücksprungadresse festlegen.
Geschlossenes Unterprogramm: Ansprung erfolgt über Anfangsadresse, Rücksprung
über die vor Ansprung angegebene Adresse.
Rekursives Unterprogramm: Aus dem Unterprogramm wird die Anfangsadresse angesprungen. Dann muss die Rückkehradrese im Stack verwaltet werden.
Beim offenen Einbau eines Unterprogramms (insbesondere beim Einsatz von Makroexpansion) kann Rekursion nicht behandelt werden. Die MI sieht spezielle Befehle für Unterprogrammaufrufe vor, die jedoch auch durch Befehle ersetzt werden
können, die den Stack manipulieren und durch Sprungbefehle:
• CALL speichert den aktuellen Stand des Befehlszählers (Register PC) im Stack
ab und setzt den Befehlszähler auf die angegebene Adresse.
• RET bewirkt die Rückkehr an die Adresse hinter der Aufrufstelle. Diese wird
aus dem Stack in den PC geladen und der Stack wird zurückgesetzt.
Beispiel: Aufruf eines Unterprogramms zur Berechnung der Fakultät
MOVE W ..., R0 (Argument in R0 ablegen)
CALL fac (Unterprogramm aufrufen)
MOVE W R2, ... (Resultat abspeicher)
Wichtig: Durch Unterprogrammaufrufe werden Teile des Speichers und der Register
verändert. Für die Nutzung eines Unterprogramms ist es wichtig zu wissen:
• wie Parameter / Resultat übergeben werden
• welche Teile des Speichers / der Register durch das Unterprogramm verändert
werden
Bei einer strukturierten Programmierung auf Maschinenebene kommt der systematischen Nutzung von Unterprogrammen eine entscheidende Bedeutung zu. Unterprogramme sind sorgfältig zu dokumentieren.
46
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
Im allgemeinen Fall arbeitet das Hauptprogramm mit allen Registern. Das gleiche
gilt für das Unterprogramm. Deshalb ist es naheliegend, vor dem Aufruf des Unterprogramms alle Register zu retten (im Speicher zwischenzuspeichern). Dann
kann das Unterprogramm alle Register frei nutzen. Nach Rücksprung werden die
alten Registerwerte restauriert. Bei der MI gibt es einen speziellen Befehl zum Retten der Register im Stack:
PUSHR bewirkt ein Abspeichern der Register R14 bis R0 auf dem Stack entsprechend
der folgenden Befehlssequenz:
1
2
4
SUB W I4, R14
MOVE W R14, !R14
SUB W I4, R14
MOVE W R13, !R14
...
Der Stackpointer wird insgesamt um 60 dekrementiert und die Registerinhalte werden im Stack gespeichert.
Der Befehl POPR bewirkt das Restaurieren der Register aus dem Stack. Der Stackpointer wird um 60 erhöht.
Ratsam ist es, bei der Verwendung von Unterprogrammen eine einheitliche Standardschnittstelle zu nutzen. Prinzipien:
• Im aufrufenden Programm werden die Parameter des Unterprogramms in der
Reihenfolge pn , . . . , p1 auf dem Stack abgelegt. Für ein Ergebnis wird ebenfalls Speicherplatz im Stack reserviert. Dann erfolgt der Aufruf des Unterprogramms. Die Rückkehradresse steht im Stack.
• Im Unterprogramm werden die Registerwerte gesichert. Der nun erreichte
Stackpointer-Stand wird in R13 abgelegt. R13 dient also als Basisadresse für
den Stackpointer-Stand zur Aufrufzeit. In R12 tragen wir die Basisadresse der
Parameter ein. Bei der Rückkehr aus dem Unterprogramm können wir diese
Basisadresse verwenden, um den alten Stack-Pegel wiederherzustellen.
• Nach der Ausführung des Unterprogramms wird über POPR der Registerzustand wiederhergestellt und über RET erfolgt der Rücksprung.
• Nach Rückkehr stehen die Parameter und das Resultat im Stack. Nach Zurücksetzen des Stacks um die Parameter steht das Resultat zu Beginn des Stacks
und kann abgespeichert werden.
Also:
1
2
4
6
...
MOVE kn pn, -!SP
...
MOVE k1 p1, -!SP
CALL up
ADD W I..., SP
...
8
10
12
14
up: PUSHR
MOVE W SP, R13
MOVEA 64+!R13, R12
...
MOVE W R13, SP
POPR
RET
4.2. ADRESSIERTECHNIKEN UND SPEICHERVERWALTUNG
47
Beispiel: ggt
1
2
MOVE W I0, -!SP
MOVE W ..., -!SP
MOVE W ..., -!SP
CALL ggt
ADD W I8, SP
MOVE W !SP+, ...
4
6
8
10
12
14
16
18
20
22
24
26
ggt:
PUSHR
MOVE W SP, R13
MOVEA 64+!R13, R12
CMP W 4+!R12, !R12
JNE else
then:
MOVE W !R12, 8+!R12
JUMP rueck
else:
JLT op1lop2
SUB W 4+!R12, !R12
JUMP reccall
op1lop2: SUB W !R12, 4+!R12
reccall: MOVE W I0, -!SP
MOVE W !R12, -!SP
MOVE W 4+!R12, -!SP
CALL ggt
ADD W I8, SP
MOVE W !SP+, 8+!R12
rueck:
MOVE W R13, SP
POPR
RET
4.2
Adressiertechniken und Speicherverwaltung
In höheren Programmiersprachen werden Datenstrukturen zur strukturierten Darstellung von Informationen verwendet:
• Grunddatentypen
• Enumerationssorten
• Records
• Union
• Array
• rekursive Datensorte
• Referenzen
Im Folgenden interessiert uns die Frage, wie wir diese Datenstrukturen auf Maschinenebene darstellen und darauf zugreifen können.
4.2.1
Konstante
Eine Konstante entspricht einer Operandenspezifikation, die einen Wert repräsentiert, ohne dabei auf Register oder den Speicher Bezug zu nemen. In der MI schreiben wir z.B. I 2735. Zur Ausführungszeit ist der Wert 2735 in Binärdarstellung im
48
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
Hauptspeicher abgelegt. Zur Ausführungszeit des entsprechenden Befehls zeigt der
Befehlszähler in der Adressrechnung auf die Adresse im Hauptspeicher, unter der
der Wert abgelegt ist.
4.2.2
Operandenversorgung über Register
Bei der Operandenversorgung über Register sind die Zugriffszeiten in der Regel
geringer als beim Zugriff auf den Hauptspeicher. In der MI: Rn, 0 ≤ n ≤ 15.
4.2.3
Absolute Adressierung
Im Befehl wird die Adresse als Wert angegeben. Dies bedeutet, dass das Programm
bzw. seine Daten im Hauptspeicher fixiert sind, d.h. die Lage im Hauptspeicher
festgelegt ist. Dies bedeutet eine Inflexibilität, die zu vermeiden ist. Absolute Adressierung macht höchstens für eine Reihe von Systemprogrammen Sinn.
4.2.4
Relative Adressierung
Idee: Wir adressieren relativ zu einer frei festlegbaren Anfangsadresse für
• den Programmcode
• die Daten
Ein Maschinenprogramm heißt relativ adressiert, wenn es keine Adressteile bzw.
zur Verwendung als Adressteile vorgesehene Operandenteile enthält, die auf eine
absolute Lage des Programms oder seiner Daten im Speicher Bezug nehmen. Nur
dann können wir das Programm und seine Daten an beliebigen Stellen im Speicher
ansiedeln.
Für die Methodik wichtige Frage: Ist die Tatsache, dass ein Maschinenprogramm
relativ adressiert ist, einfach zu erkennen und zu überprüfen (Codierstandards)?
In der MI ist natürlich syntaktisch erkennbar, ob ausschließlich mit relativer Adressierung in der Operandenspezifikation gearbeitet wird. Dies garantiert allerdings
noch nicht, dass das Programm relativ adressiert ist. (Achtung: Unterschied Syntax
/ Semantik!)
4.2.5
Indizierung, Zugriff auf Felder
In höheren Programmiersprachen verwenden wir Felder ([n:m] array m a). Wir
behandeln nun die Frage, wie solche Felder in einer Maschine (im Hauptspeicher)
abgelegt werden und wie systematisch darauf zugegriffen wird. Wir legen die Elemente eines Felds konsekutiv im Speicher ab. Um den Zugriff zu organisieren, verwenden wir folgende Darstellung:
Adresse
Inhalt
4711
4711 + 1 − n fiktive Anfangsadresse a0 für a[0]
4712
a[n]
4713
a[n+1]
..
..
.
.
4711+m
a[m]
Für den Index k, n ≤ k ≤ m, erhalten wir durch k + a0 die Adresse des Feldelements. Sei α die Anfangsadresse des Felds. Dann schreiben wir in die Speicherzelle
die fiktive Adresse α + 1 − n.
Vorteil: Wenig Speicher für Verwaltungsinformationen zum Feld, uniformer Zugriff.
Nachteil: keine vollständigen Informationen zur Überprüfung, ob der Index außerhalb der Indexgrenzen liegt.
4.2. ADRESSIERTECHNIKEN UND SPEICHERVERWALTUNG
49
In der MI: Enthält R0 die fiktive Anfangsadresse des Felds und R1 den Index k, so
wird auf a[k] durch !R0/R1/ zugegriffen. Enthält R0 hingegen die effektive Anfangsadresse des Felds, so erfolgt der Zugriff auf das k-te Feldelement durch !!R0/R1/.
Diese Zugriffstechnik ist problemlos mit der relativen Adressierung kombinierbar.
Die Zugriffstechnik setzt voraus, dass zur Ausführungszeit beim Anlegen des
Felds (auf dem Stack) die Feldgrenzen festliegen und sich nicht mehr ändern.
Mehrstufige Felder können nach dem selben Prinzip behandelt werden: Sei [n1:m1,
..., ni:mi] array m a mit nq ≤ mq für 1 ≤ q ≤ i gegeben. Wir verwenden folgendes Schema:
Adresse
Inhalt
4711
a0
fiktive Adresse von a[0, ..., 0]
4712
s1
Spanne m1 − n1
..
..
..
.
.
.
4711+i-1
si−1
4711+i
a[n1, ..., ni]
Fiktive Anfangsadresse:
Spanne mi−1 − ni−1
erstes Feldelement
a0 = α + i − (n1 + s1 (n2 + s2 (. . . )))
Adresse des Elements a[k1, ..., ki] (mit nq ≤ kq ≤ mk für 1 ≤ q ≤ i):
a0 + k1 + s1 (k2 + s2 (. . . ))
Beispiel:
1
2
4
Einfacher Algorithmus auf Feldern (alle Feldelemente addieren)
v := 0;
for i := 1 to n do
v := v + a[i]
od
Werte in der MI:
R0 Anfangsadresse der Feldelemente (zeigt auf das erste Feldelement a[1])
R1 Zähler
R2 Abbruchswert
R3 Aufnahme des Resultats
1
2
4
6
8
MOVE W I n, R2
CLEAR W R3
CLEAR W R1
for: CMP W R2, R1
JLT ende
ADD W !R0/R1/, R3
ADD W I 1, R1
JUMP for
ende: HALT
4.2.6
Symbolische Adressierung
Auch die relative Adressierung ist noch sehr fehleranfällig, da der Programmierer
in Registern und Zahlen sein Programm verstehen muss. Bequemer und weniger
fehleranfällig ist das Arbeiten in Programmen mit frei gewählten Bezeichnungen. Bei
Assemblerprogrammen sprechen wir von symbolischen Adressen. Bei frei gewählen
Bezeichnungen sind folgende Prinzipien zu beachten:
•
sprechende Bezeichnungen
50
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
•
data dictionary anlegen: ein Verzeichnis aller Begriffe und deren Bedeutung
• Codierungsstandards für die Wahl von Bezeichnungen
Vor der Ausführung des Programms werden die Bezeichnungen vom Assemblierer
durch Adressen ersetzt. Dazu benutzt man eine Datenstruktur Adressbuch, in
der die Beziehung Bezeichnung – relative Adresse festgelegt ist. Dabei ist darauf zu
achten, dass diese Zuordnung so gewählt ist, dass das Programm effizient ist:
• wenig Speicher verbraucht
• kurze Ausführungszeiten hat
Dazu werden Programme gezielt optimiert (unter Einsatz von Pipelining und Caching).
4.2.7
Geflechtsstrukturen und indirekte Adressierung
Bei problemorientierten Programmiersprachen findet sich das Konzept Referenz /
Verweis / Zeiger / Pointer. Auf der MI-Ebene bilden wir Referenzen auf Adressen
ab. Die Referenz auf einen Wert oder eine Variable wird zu der Adresse der Speicherzelle, die diesen Wert enthält oder diese Variable repräsentiet. Bei umfangreichen
Datenpaketen / -strukturen (beispielsweise Feldern) entsprechen die Referenzen den
Anfangsadressen des Datenpakets.
Referenzen entsprechen den Konzept der indirekten Adressierung. Referenzen /
Verweise bieten große Vorteile für die Effizienz von Programmen:
• Statt großer Datenpakete können wir in Prozedur- / Unterprogrammaufrufen
die Verweise / Anfangsadresse übergeben.
• In Geflechtsstrukturen können wir gewisse Daten, die für eine Reihe von Programmelementen bedeutsam sind, einheitlich verwalten (data sharing).
Beispiel: Eine Typvereinbarung für Pointer / Records, aus der wir verkettete
Listen (aber auch Bäume) aufbauen können, lautet wie folgt:
1
2
4
6
type
rlist = ^slist;
list = record
inhalt: s;
naechster, letzter: rlist
end;
Die Abbildung auf den Speicher (MI) könnte - unter der Annahme, dass der Typ s
der Kennung W entspricht - so aussehen, dass ein Knoten aus 3 * 4 Bytes besteht,
von denen je ein 4-Byte-Block die Adresse naechster, letzter und den abgelegten Wert enthält.
Wir nehmen an, dass durch die Listenelemente eine zweifach verkettete Liste aufgebaut wird, wobei im ersten Element der Verweis letzter nil ist und im letzten
Listenelement der Verweis naechster nil ist.
Durchlaufen der Liste:
1
2
4
zeiger := listenanfang;
while zeiger <> nil do
if zeiger^.inhalt = suchmuster then
goto gefunden
fi;
4.2. ADRESSIERTECHNIKEN UND SPEICHERVERWALTUNG
6
8
zeiger := zeiger^.naechster
od
goto nicht_gefunden
R0
Darstellung in der MI: R1
R2
1
2
4
6
Zeiger
Listenanfang
Suchmusteradresse
MOVE W R1, R0
loop: CMP W I 0, R0
JEQ nicht_gefunden
CMP W !R2, 8+!R0
JEQ gefunden
MOVE W !R0, R0
JUMP loop
Programm für das Anhängen eines Listenelements am Ende der Liste:
1
2
4
6
8
10
12
14
if listenanfang <> nil then
zeiger := listenanfang;
while zeiger^.naechster <> nil do
zeiger := zeiger^.naechster
od;
zeiger^.naechster := neu;
neu^.letzter := zeiger;
neu^.inhalt := ...;
neu^.naechster := nil
else
listenanfang := neu;
neu^.letzter := zeiger;
neu^.inhalt := ...;
neu^.naechster := nil
fi
R0
R2
R3
R4
1
2
4
6
8
10
12
14
16
Zeiger
Listenanfang
neu
inhalt
MOVE W R2, R0
CMP W I 0, R0
JNE loop
CLEAR W !R3
CLEAR W 4+!R3
MOVE W R4, 8+!R3
MOVE W R3, R2
JUMP ende
loop:
CMP W I 0, !R0
JEQ listenende
MOVE W !R0, R0
JUMP loop
listenende: MOVE W R3, !R0
CLEAR W !!R0
MOVE W R0, 4+!!R0
MOVE W R4, 8+!!R0
51
52
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
4.2.8
Speicherverwaltung
Für die Ausführung eines Maschinenprogramms mit komplexen Daten ist eine Speicherorganisation erforderlich. Darunter verstehen wir die Systematik der Ablage von
Programm und Daten im Speicher. Ziele dabei sind:
• effiziente Nutzung des Speichers
• einfache, gut handhabbare Strukturen
Beobachtungen:
• Praktisch alle Programme benötigen während ihrer Ausführung zusätzlichen
Speicherplatz, der stückweise belegt und wieder freigegeben wird.
• Oft werden Speicherplätze in der umgekehrten Reihenfolge, in der sie belegt
werden, wieder freigegeben (Stack-Prinzip) - der zuletztbelegte Platz wird
zuerst wieder freigegeben.
• Für gewisse Datenstrukturen eignet sich das Stack-Prinzip nicht (Zeiger- /
Referenzstrukturen).
Damit nutzen wir drei Arten der Verwaltung von Speicher:
• statischer Speicher: Hier werden alle Daten abgelegt, die über die gesamte
Laufzeit des Programms benötigt werden.
• Stack: Hier werden alle Daten abgelegt, die nach dem Stack-Prinzip benötigt
und freigegeben werden. Bei der Blockstruktur wird beim Betreten eines
Blocks der Speicher für die lokalen Variablen (bei Prozeduraufrufen für Parameter) bereitgestellt und beim Verlassen des Blocks wieder freigegeben.
• Listenspeicher (Heap): Hier werden alle Daten abgelegt, deren Speicherplätze
nicht im Stack verwaltet werden können. Dabei wird im Quellprogramm in der
Regel Speicherplatz explizit angefordert (Kreieren von Objekten, new/allocate
zum Bereitstellen von Speicher für Zeiger). Die Freigabe von Speicher erfolgt:
– entweder explizit (deallocate)
Vorteil: Der Programmierer kann die Speicherverwaltung kontrollieren.
Nachteil: Erhöhte Komplexität der Programme, Gefahr ins Leere zeigender Referenzen (dangling references).
– oder implizit durch Speicherbereinigung (garbage collection). Dabei
wird durch Erreichbarkeitsanalyse festgestellt, auf welche Zeigerelemente / Objekte noch zugegriffen werden kann; die nicht mehr erreichbaren
werden entfernt.
Vorteil: erheblich einfacher, keine Fehlergefahr durch Zeiger ins Leere.
Nachteil: weniger effizient, Echtzeitverhalten der Programme kaum vorhersagbar.
4.2.9
Stackverwaltung von blockstrukturierten Sprachen
Eine Programmiersprache heißt blockstrukturiert, wenn lokale Variablen /
Konstanten verwendet werden, die in abgegrenzten Bindungsbereichen deklariert
werden, soweit keine Referenzen / Zeiger auftreten. Für lokale Bezeichner / Variablen unterscheiden wir:
• Deklaration / Bindung (genau ein Mal)
• benutzendes Auftreten
4.2. ADRESSIERTECHNIKEN UND SPEICHERVERWALTUNG
53
Da die gleiche Bezeichnung in unterschiedlichen Bindungsbereichen gebunden werden kann, ist die Zuordnung zwischen Bindung und Nutzung wichtig (vgl. statische gegen dynamische Bindung). Blockstrukturen entsprechen Klammerstrukturen.
Durch Durchnummerieren der Blockklammern kann jeder Block eindeutig gekennzeichnet werden. Durch dieses Verfahren können wir Blöcke eindeutig kennzeichnen.
Blockschachtelungstiefe (BST): Anzahl der einen Block umfassenden Blöcke + 1.
Die Blöcke definieren einen Baum.
Beispiel:
1
2
4
6
8
10
Die Blockstruktur
begin
begin
begin
end
begin
end
end
begin
end
end
entspricht dem Baum
1
1.1
1.2
1.1.1 1.1.2
In diesem Fall bestimmt die statische Struktur der Blöcke die Struktur des Stacks.
Beispiel:
1
2
4
Blockorientiertes Programm mit Prozeduraufruf
| // 1
proc p =:
| // 1.1
...
| // 1.1
6
8
| // 1.2
...
| // 1.2
10
12
14
16
| // 1.3
...
p;
...
| // 1.3
| // 1
Hier unterscheiden wir für jeden Abschnitt im Stack (der den lokalen Variablen
eines Blocks entspricht) den statischen Vorgänger (in der Aufschreibung der nächste
umfassende Block) und den dynamischen Vorgänger (in der Ausführung der nächste
umfassende Block) (s. Abb 4.1).
In der Speicherorganisation für blockorientierte Sprachen verwenden wir einen
Stack, in dem jeweils auch der Verweis auf den statischen und dynamischen Vorgänger sowie die Blockschachtelungstiefe abgelegt wird. Ein Prozeduraufruf entspricht
54
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
1.1
1
1
...
1.3
1.3
1.3
1
1
1
1
Abbildung 4.1: Blöcke auf dem Stack
imer dem Betreten eines Blocks.
Jeder Speicherabschnitt auf dem Stack enthält schematisch folgende Informationen:
• statischer Vorgänger
• dynamischer Vorgänger
• Blockschachtelungstiefe
• Rückkehradresse (bei Prozeduraufrufen)
• Parameter (bei Prozeduraufrufen)
• Resultat (bei Prozeduraufrufen)
• lokale Variablen
4.3
Techniken maschinennaher Programmierung
Ein komplexes maschinennahes Programm kann kaum in einem Anlauf gleich als
Maschinenprogramm geschrieben werden. Geschickter ist es, zunächst ein problemorientiertes Programm zu schreiben, dies zu analysieren und zu verifizieren und
dann in ein Maschinenprogramm umzusetzen. Die Umsetzung erfolgt
• entweder per Hand
• oder durch ein Übersetzungsprogramm (Compiler)
Typische Bestandteile höherer Programmiersprachen:
• Datenstrukturen
• Blockstrukturen
• Ablaufstrukturen
– Ausdrücke
– Zuweisungen
– if - then - else
– Wiederholungsanweisungen
– Funktionen, Prozeduren
Maschinennahe Umsetzung:
• Speicherung
• Umsetzung in Konzepte maschinennaher Sprachen (s.u.)
4.3. TECHNIKEN MASCHINENNAHER PROGRAMMIERUNG
4.3.1
55
Auswertung von Ausdrücken / Termen
Ein Ausdruck besteht aus einem Gebirge von Funktionsaufrufen.
abs(ab − cd) + ((e − f )(g − j))
+
abs
-
*
-
-
* efgj
*
a bcd
Zugehöriges Single-Assignment-Programm:
1
2
4
6
8
h1
h2
h3
h4
h5
h6
h7
h8
:=
:=
:=
:=
:=
:=
:=
:=
a * b;
c * d;
h1 - h2;
abs (h3);
e - f;
g - j;
h5 * h6;
h4 + h7;
:=
:=
:=
:=
:=
:=
:=
:=
a * b;
c * d;
h1 - h2;
abs (h1);
e - f;
g - j;
h2 * h3;
h1 + h2;
oder:
1
2
4
6
8
h1
h2
h1
h1
h2
h3
h2
h1
Zur Optimierung des Speicherbedarfs beim Auswerten von Ausdrücken verwenden
wir statt Hilfsvariablen einen Stack. Operationen finden immer auf dem ersten Element des Stacks statt. Deshalb kann das erste Element auch in einer speziellen
Hilfsvariable AC gehalten werden.
Operationen:
• lege
Wert auf den Stack
• führe ein- oder zweistellige Operation auf dem Stack aus
Das Arbeiten auf dem Stack zur Auswertung eines Ausdrucks kann einfach durch
die Postfix-Darstellung des Operatorbaums erreicht werden.
im Beispiel:
1
2
4
a b * c d * - abs e f - g j - * +
AC := a
push AC; AC := b
AC := top * AC; pop
push AC; AC := c
56
6
8
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
push AC; AD := d
AC := top * AC; pop
AC := top - AC; pop
AC := abs (AC)
...
In der MI können wir ohne Verwendung eines Registers direkt auf dem Stack arbeiten:
1
2
MOVE W a, -!SP
MULT W !SP+, !SP
...
Wir schreiben nun ein Programm, das einem kleinen Ausschnitt aus einem Compiler
entspricht. Es nimmt die Umsetzung von Ausdrücken in eine Sequenz von Operationen auf dem Stack vor. (Das erzeugte Programm legt schließlich das Ergebnis in
AC ab, der Stack ist nach Ausführung des Programms wieder im Anfangszustand.)
Wir benötigen eine Datenstruktur zur Darstellung von Operatorbäumen.
1
2
sort expr = nullary (symbol sym)
| mono (op op, expr e0)
| duo (expr e1, op op, expr e2)
Dabei nehmen wir an, dass die Sorten symbol (Menge der Identifikatoren für Werte)
und op (Operationssymbole) gegeben sind.
Im Folgenden wird ein Programm angegeben, das einen Operatorbaum als Eingang nimmt und ein Stackprogramm zur Auswertung des durch den Operatorbaum
gegebenen Ausdrucks ausdruckt:
1
2
4
6
8
10
12
proc eaf = (expr e):
if e in nullary then
print ("AC := ", sym(e))
elif e in mono then
eaf (e0(e));
print ("AC := ", op(e), "AC")
else
eaf (e1(e));
print ("push AC");
eaf (e2(e));
print ("AC := top ", op(e), "AC");
print ("pop")
fi
4.3.2
Maschinennahe Realisierung von Ablaufstrukturen
Eine Zuweisung
1
x := E
wird umgesetzt in (sei e der Operatorbaum zu E):
1
2
eaf (e);
x := AC
Die bedingte Anweisung
1
if E = 0 then S1 else S2 fi
wird wie folgt umgesetzt (sei e der Operatorbaum zu E):
4.3. TECHNIKEN MASCHINENNAHER PROGRAMMIERUNG
1
2
4
6
8
t:
10
12
57
eaf (E);
if AC = 0 then
goto t
fi
.....
(Maschinencode fuer S2)
.....
goto ende
.....
(Maschinencode fuer S1)
.....
ende:
Die Wiederholungsanweisung
1
while E <> 0 do S od
wird zu:
1
2
4
6
8
while: eaf (e);
if AC = 0 then
goto ende
fi
....
(Maschinencode fuer S)
....
goto while
ende:
Im Folgenden schreiben wir ein Programm, das die Umsetzung (Übersetzung) eines
while / if - Programms in ein maschinennahes Programm vornimmt. Wir benötigen
eine Sorte zur Repräsentation von Programmen:
1
2
4
sort statement = assign (symbol x, expr e)
| if (expr cond, statement st, statement se)
| while (expr e, statement s)
| seq (statement s1, statement s2)
Im Übersetzungsvorgang werden zusätzlich Marken für Sprünge benötigt. Wir nehmen an, dass wir zu einer gegebenen Marke m in der Sorte label Über eine Funktion
1
fct next = (label) label
verfügen, so dass m, next(m), next(next(m)), . . . eine Folge unterschiedlicher Marken liefert (Generator für Marken).
Programm für das Erzeugen eines maschinennahen Programms:
1
2
4
6
8
10
proc gen = (statement z, var label m):
if z in assign then
eaf (e(z));
print (x(z), " := AC")
elif z in seq then
gen (s1(z), m);
gen (s2(z), m)
elif z in if then
label me := m;
label mt := next (m);
58
12
14
16
18
20
22
24
26
28
KAPITEL 4. MASCHINENNAHE PROGRAMMIERUNG
m := next (mt);
eaf (cont(z));
print ("if AC = 0 then goto ", mt, " fi");
gen (se(z), m);
print ("goto ", me);
print (mt, ":");
gen (st(z), m);
print (me, ":")
else
label mw := m;
label ma := next (m);
m := next (ma);
print (mw, ":");
eaf (c(z));
print ("if AC = 0 then goto ", ma, "fi");
gen (s(z), m);
print ("goto ", mw);
print (ma, ":")
fi
Herunterladen