Pfad:
Home =>
AVR-Überblick =>
Programmiertechniken => Befehlsausführung
(This page in English:
)
Ausführung von Instruktionen in AVRs
Programminstruktionen sind als 16-Bit-Worte im Flashspeicher der AVRs
gespeichert, sie werden vom Programmiergerät dort hinein geschrieben.
1.1 Programmzähler
Der Programmspeicher beginnt an der Adresse 0x0000. Zu Beginn und beim
Reset wird diese Adresse in den Programmzähler (Program counter,
PC) geschrieben, das dort befindliche Instruktionswort ausgelesen,
dekodiert und schließlich ausgeführt.
Der Programmzähler wird nach jeder Instruktion um Eins erhöht
und die dort stehende Instruktion ausgeführt. Es sei denn, die
Instruktion ist ein Sprungbefehl und verbiegt den Programmzähler:
dann wird der lineare Programmablauf verändert und es sind
Instruktionen außer der Reihe dran.
Was ist, wenn die Instruktionen dann mal "alle" sind? Wenn
der AVR dahin gelangt (was es sorgsam zu vermeiden gilt), liest er 0xFFFF
aus dem nicht programmierten Speicher. Das ist ein ungültiges
Befehlswort und er tut rein gar nix, außer dem Erhöhen des
Programmzählers und dem Holen des nächsten Befehls.
Und wenn nun der gesamte Programmspeicher alle ist? Nun, dann beginnt er
wieder bei Null. Das macht dann quasi einen Reset, aber ohne das Leermachen
der Register und das Setzen der Portregister auf Default-Werte. Das kriegt
man aber nur mit einem unprogrammierten AVR oder bei einem ersten
Programmfehler hin.
1.2 Dekodierung
Dekodierung bedeutet die Auswahl aus den mehr als 100 verschiedenen Arten
von Instruktionen, die AVRs beherrschen und ausführen können.
So bewirkt z. B. die Instruktion LDI r,c, dass die Konstante
c, die sich im Instruktionswort in den Bits 0 bis 4 und 8 bis 11 befindet,
an den Eingang1 der arithmetisch/logischen Einheit (arithmentic/logic unit,
ALU) geschrieben und beim nächsten Pegelwechsel am Takteingang in das
Register r (+16) geschrieben wird.
Die Operation LDI ist dabei in den vier obersten Bits 12 bis 15 des
Instruktionswortes mit 0b1110 kodiert. Die Registernummer steht in den Bits
4 bis 7 des Instruktionswortes. Da vier Bits nur 16 verschiedene Werte
annehmen können, sind nur 16 der 32 möglichen Register ansteuerbar:
es sind die oberen Register R16 bis R31. Die unteren Register können
mit dieser Instruktion nicht gesetzt werden.
Ähnlich auf die oberen 16 Register eingeschränkt sind z. B.
auch die Instruktionen SUBI r,k, ANDI r,k und
ORI r,k,
die von Registerwerten Konstanten abziehen (SUBI) bzw. binär UND-
(ANDI) und ODER- (ORI) verknöpfen und das Ergebnis in das Register
r zurück schreiben. Viele Instruktionen lassen aber das
Verknüpfen aller Register miteinander zu (z. B. SUB rx,ry,
AND rx,ry und OR rx,ry), so dass diese Einschränkung
auf die oberen Register eher eine Ausnahme darstellt.
Alle Bitkombinationen, aus denen sich das Instruktionswort zusammensetzt,
sind in dem Dokument "avr-instruction-set-manual" für alle
Instruktionen in der Descrption unter 16-bit-Opcode angegeben.
Das Dokument gibt es als PDF auf der Microchip-Webseite zum Download.
Zum Seitenanfang
1.3 Die ALU
Viele Instruktionen bewirken arithmetische oder logische Operationen.
Das macht in Prozessoren die Arithmetisch/Logische Einheit
(Arithmetic/Logic Unit, ALU): sie kann bis zu zwei Werte an ihren
Eingängen miteinander verknüpfen und z. B, arithmetisch
addieren oder subtrahieren oder binär UND, ODER oder Exklusiv-ODER
errechnen. Was diesmal gemacht werden soll, sagt der Instruktions-Dekoder.
Die ALU schreibt das Ergebnis der Operation z. B. in ein Zielregister.
Bei der Operation auftretende Ereignisse wie z. B. Überläufe
bei arithmetischen Operationen (Carry) oder Nullergebnisse bei binären
Verknüpfungen (Zero) werden als Flaggen oder Flags bezeichnet und
landen im Statusregister. Dort können diese Flaggen mittels weiterer
Instruktionen ausgewertet werden und z. B. in Verzweigungen oder
bedingten Sprüngen enden.
Zum Seitenanfang
Der Ablaufmechanismus ist so konstruiert, dass während der
Ausführung einer Instruktion bereits die nächste gelesen und
dekodiert wird. Nach Abschluss des Dekodierens kann daher sofort die
nächste Instruktion ausgeführt werden, weil der Lese- und
Dekodiervorgang bereits vorher erfolgt ist.
Dieser Pre-Fetch geht nur dann schief, wenn die Instruktion zu einer
&Aunml;nderung des Programmz&aumjl;hlers führt und das schon von der
nachfolgenden Adresse gelesene und dekodierte Instruktionswort verworfen
werden muss. Das ist der Fall, wenn eine Sprunginstruktion ausgeführt
wird. Ist dies ein unbedingter Sprung (JMP oder RJMP) oder
ist die Bedingung eines bedingten Sprunges BRxC label oder
BRxS label erfüllt, muss das neue Sprungziel eingelesen
und dokumentiert werden. Solche Instruktionen benötigen daher zwei
statt nur eines Taktes.
Als Beispiel hier das binäre UND zweier 8-Bit-Zahlen in den Registern
R16 und R17. Ver-UND-et werden 0b01010101 (0x55) und 0b10101010 (0xAA). Das
Ergebnis muss natürlich Null sein. Der Quellcode in Assembler dazu:
ldi R16,0x55 ; R16 auf hexadezimal 55
ldi R17,0xAA ; R17 auf hexadezimal AA
and R16,R17 ; UND, Ergebnis in Register R16
Die Simulationen erfolgten alle mit
avr_sim.
Links der Registerinhalt nach den beiden Ladebefehlen: die beiden
Hexadezimalzahlen sind in den beiden Registern angekommen. Rechts ist
auch die AND-Instruktion ausgeführt: die ALU hat das Ergebnis,
Null, in R16 geschrieben.
Da das Ergebnis der letzten Operation Null ist, ist die Nullflagge Z im
Statusregister (links) auf Eins gesetzt. Damit lässt sich nun weiter
arbeiten.
Alle drei Operationen haben bei 1 MHz Takt genau 3 µs
lang gedauert: jede dieser Operationen hat eine Mikrosekunde gebraucht.
Ohne Pre-Fetch stünden hier sechs Mikrosekunden, also eine
Verdoppelung der Ausführungsgeschwindigkeit.
Schließt sich diesem UND ein bedingter Sprung an, der nur dann
ausgeführt wird, wenn das Ergebnis Null ist, lautet der Quellcode:
ldi R16,0x55 ; R16 auf hexadezimal 55
ldi R17,0xAA ; R17 auf hexadezimal AA
and R16,R17 ; UND, Ergebnis in Register R16
breq Label ; Wenn Null (equal) springe nach Label
nop ; Hier wenn nicht gesprungen
Label: ; Hier wenn gesprungen und wenn nicht gesprungen
Nun hat die Ausführung des gesamten Quellcodes 5 µs
gedauert: die ausgeführte Sprunginstruktion BREQ Label
hat zwei Mikrosekunden benötigt, weil der Pre-Fetch durch
den Sprung schief ging.
Zum Seitenanfang
Um zu verstehen, wie die AVRs ihre Befehle verarbeiten, hier am Beispiel
des Addierbefehls dessen detaillierte Ausführung.
Das 16-Bit-Instruktionswort (instruction word) enthält in den Bits
10 bis 15 den Binärwert 0b000011. Das signalisiert der ALU, dass
die beiden Register an den ALU-Eingängen IN1 und IN2 zu addieren
sind.
In den Bits 0 bis 3 sowie in Bit 9 steht, welches Register von den 32
möglichen an den Eingang IN1 kommt. Analog geben die Bits 4 bis 8
an, welches Register mit IN2 verbunden wird. Beide Auswahlen erfolgen
in den Multiplexern MUX.
Die ALU addiert dann beide Registerwerte. Das Rechenergebis in OUT
wird dann in das Register geschrieben, das ebenfalls an MUX1 anliegt.
Die ALU stellt ferner fest, ob bei der Addition
- ein Überlauf erfolgt ist, das Ergebnis der Addition also
größer als dezimal 255 oder hexadezimal 0xFF ist und
und setzt oder löscht die Flagge C (für Carry) im
Statusregister entsprechend,
- Null herauskam, was der Fall wäre, wenn entweder beide
Register zu Beginn Null waren oder wenn die Addition genau 256
dezimal ergab. Entsprechend wird die Null-Flagge Z (für
Zero) im Statusregister gesetzt oder gelöscht.
- Entsprechend werden auch die Flaggen N, V, S und H dem
Rechenergebnis angepasst.
Alle Instruktionen, die zwei Register miteinander verknüpfen,
also z. B.
- die Addition mit Überlauf ADC, Kennzeichen
0b000111,
- die Subtraktion ohne SUB, Kennzeichen 0b0b000110, und
mit Überlauf, SBC, Kennzeichen 0b000010,
- der Vergleich ohne CP, Kennzeichen 0b000101, und mit
Überlauf CPC, Kennzeichen 0b000001, oder
- binäres UND AND, Kennzeichen 0b001000, und ODER
OR, Kennzeichen 0b001010,
sind in den Instruktionsworten genauso kodiert, nur die obersten sechs
Bits Kennzeichen sind, wie zu erkennen, anders zusammengesetzt.
Da für jedes der beiden Quellregister 5 Bits im
Instruktionswort zur Verfügung stehen, kann jedes der 32
Register mit jedem anderen verknüpft werden. Also auch mit sich
selbst. Das gibt lustige Effekte, denn bei UND und ODER mit sich
selbst kommt nur dann Null heraus, wenn alle Bits im Register
Null sind. ATMEL hat dem UND eines Registers mit sich selbst
ein weiteres Mnemonic spendiert, es heißt TST r und
setzt das Z-Flag wenn das Register r Null ist. Wer noch eine
andere Variante probieren möchte, nimmt stattdessen
OR r,r, das tut zwar dasselbe, erzeugt nur anderen
Binärcode als TST r.
Einen ähnlichen Effekt hat ADC mit sich selbst: alle
Bits im Register werden dadurch um eine Bitposition nach links
geschoben, das unterste Bit wird aus dem Carry bezogen und das
oberste Bit 7 landet im Carry. Das ist dasselbe wie die Instruktion
ROL (Rotate left), die ebenfalls ein eigenes Mnemonic
spendiert bekommen hat.
Noch so eine Pseudoinstruktion: EOR r,r löscht alle
gesetzten Bits im Register und macht sie Null, weil Exklusiv-ODER
alle Bits zu Null macht, die entweder beide Null oder beide Eins
waren. Auch diesem wurde ein eigenes Mnemonic spendiert: CLR r,
es produziert aber das gleiche Instruktionswort wie EOR r,r.
So kann man die Anzahl Instruktionen auch erhöhen, ohne dass
man der ALU neue Befehle beibringen muss.
Man kann es aber nicht oft genug betonen: alle beschriebenen
Vorgänge beim ADD laufen in einem einzigen Taktzyklus der
CPU ab (und nicht in mehreren wie bei den PICs). Und dabei sind
keine verlangsamenden SRAM-Zugriffe nötig, alles steht in
den leicht zugänglichen 32 Registern.
Zum Seitenanfang
Als Beispiel für den Ablauf der Instruktionsverarbeitung
in AVRs abschließend noch ein 16-Bit-Addierer mit
Überlauf-Erkennung. Sein Quellcode lautet:
.equ n16bitZahl1 = 12345 ; Die erste 16-Bit-Zahl, kein Takt
.equ n16bitZahl2 = 45678 ; Die zweite 16-Bit-Zahl, kein Takt
ldi R16,Low(n16bitZahl1) ; Das LSB von Zahl 1 in R16, ein Takt
ldi R17,High(n16bitZahl1) ; Dessen MSB in R17, ein Takt
ldi R18,Low(n16bitZahl2) ; Das LSB von Zahl 2 in R18, ein Takt
ldi R19,High(n16bitZahl2) ; Dessen MSB in R19, ein Takt
add R16,R18 ; Addiere die beiden LSB, ein Takt
adc R17,R19 ; Und die beiden MSB mit dem Ueberauf, ein Takt
brcc Fertdisch ; Wenn kein Ueberlauf: springe, zwei bei Sprung
ldi R16,0xFF ; Setze Ergebnis auf hoechsten Wert, LSB
ldi R17,0xFF ; dto., MSB
Ferdisch:
Links sind die vier Ladebefehle, ihr Ergebnis in den Registern und die
Dauer von 4 µs zu sehen. Rechts ist zusätzlich auch
noch die Addition und der Sprung erfolgt. Die Übertragsflagge C
war nicht gesetzt, also erfolgte der Sprung und das Ergebnis ist nach
8 µs fertig.
Provozieren wir einen Überlauf, indem wir die erste 16-Bit-Zahl
auf 23456 setzen, dann sieht das Ergenis etwas anders aus.
Jetzt ist R17:R16 0x5BA0, die andere erste Zahl (links). Rechts ist
zu sehen: nach dem Addieren ist die C-Flagge gesetzt, weil ein
Überlauf aufgetreten ist. Die beiden Register sind daher auf
0xFFFF zu setzen.
Hier ist das Ergebnis entsprechend auf den Höchstwert 0xFFFF
korrigiert. Es sind nun 9üµs vergangen, also nur ein Takt
mehr als bei der ersten Zahl. Aus den zwei Takten, die im ersten
Fall mit dem Sprung zugebracht wurden, ist ein Takt geworden. Das
hat den ersten Setzbefehl auf 0xFF kompensiert, so dass nur der
zweite Setzbefehl verlängernd zu Buche schlägt.
Alle Taktprobleme lassen sich mit den hier gezeigten Methoden
auseinandernehmen und analysieren. Der Taktablauf bleibt im Griff
und ist einfach zu verstehen.
Zum Seitenanfang
©2019 by
http://avr-asm-tutorial.net